js 中 this 的指向问题

this 的值取决于代码的执行环境(execution context),this 存在于函数中,在声明时不会被绑定,只在调用时被绑定。只要记住,谁调用,绑定谁,就不会糊涂。

1.默认绑定

函数直接在全局环境下执行,严格模式下 this 为 undefined,非严格模式下 this 为 window。

function f(){
	console.log(this);
}
f();	//window
‘use strict‘
function f(){
	console.log(this);
}
f();	//undefined

注意:这里仅指在函数内部的 this,在全局环境下,this 都指向 window,不管是不是严格模式

"use strict";
console.log(this === window);	//true

2.隐式绑定

函数存在对象中,用该对象调用该函数

var obj = {
	f(){
		console.log(this)
	}
};
obj.f();	//obj

这里 this 指向,并不是因为函数 f 声明在 obj 内,而是因为 obj 调用了 f(),如果函数 f 声明在全局环境里也是可以的

var f = function() {
	console.log(this)
};
var obj = {
	f: f
};
obj.f();

函数是在堆空间中存放,然后把地址存放在 obj 对象的 f 属性下,原始的对象以字典结构保存,具体的可以看这篇博文:JavaScript 的 this 原理

如果你直接把函数赋值给对象的属性,则 this 也是指向该对象

document.onclick = function() {
	console.log(this)
}

这相当于给 document 对象的 onclick 属性赋值,this 自然就指向 document 对象。

如果函数暴露给了全局,则 this 指向全局

var obj = {
	f: function (){
		return function (){
			console.log(this)
		}
	}
};
obj.f()();	//window,严格模式为undefined

如果函数为私有函数,则也是指向全局

var obj = {
	f: function (){
		console.log(this);	//obj
		(function (){
			console.log(this);	//window
		})();
	}
};
obj.f();

对于这种函数内部的私有函数,没办法定义它的执行环境,所以引擎内部把他扔给了 window 对象,所以 this 指向 window。

3.强绑定

这个很好记,就是用 call,apply,bind 函数改变 this 的指向,这个是绑定谁就指向谁。这里需要注意的是 bind 函数,因为他的返回值是一个函数,这个函数的 this 是不可被修改的。

function f(){
	console.log(this)
}
var obj1 = {name:‘obj1‘};
var obj2 = {name:‘obj2‘};
var bf = f.bind(obj1);
bf.call(obj2);	//output:{name: "obj1"}

4.构造函数中的 this

构造函数如果和普通函数一样使用,那没什么区别

var x = 0;
function A(x) {
	this.x = x;
}
A(1);
console.log(x)	//output: 1,这里的 this 指向 window

因为 new 操作符,绑定了 this,MDN上面有 new 操作符做了哪些事:

new 关键字会进行如下的操作:

  1. 创建一个空的简单JavaScript对象(即**{}**);
  2. 链接该对象(即设置该对象的构造函数)到另一个对象 ;(这里指实现原型链,把对象的原型 _proto_ 指向构造函数的 prototype )
  3. 将步骤1新创建的对象作为**this**的上下文 ;(这里的上下文就是指执行环境)
  4. 如果该函数没有返回对象,则返回**this**
function A(x) {
	this.x = x;
	this.f = function (){
		console.log(this)
	}
}
var a = new A(1);
a.f();	//output: 

js 中 this 的指向问题

5.箭头函数与 this

首先,箭头函数是没有 this 的,它的 this 来源于它声明时的外部环境。

阮一峰老师的《ECMAScript 6 入门》这本书里面有个例子挺好的,我就直接拿来用了

function foo() {
  return () => {
    return () => {
      return () => {
        console.log(‘id:‘, this.id);
      };
    };
  };
}

var f = foo.call({id: 1});

var t1 = f.call({id: 2})()(); // id: 1
var t2 = f().call({id: 3})(); // id: 1
var t3 = f()().call({id: 4}); // id: 1

为什么执行一次 call 函数后,后面都不会改变 this 的指向了呢?

让我们用 Babel 把 foo 函数转换成 ES5 的写法后一切就会明了了:

"use strict";

function foo() {
  var _this = this;
    
  return function () {
    return function () {
      return function () {
        console.log(‘id:‘, _this.id);
      };
    };
  };
}

只要一执行 foo(),this 的值就被固定了,_this 中存的是绑定的对象的地址。

在学习 Vue 时,经常能看见这样的提醒:

注意,不应该使用箭头函数来定义 method 函数 (例如 plus: () => this.a++)。理由是箭头函数绑定了父级作用域的上下文,所以 this 将不会按照期望指向 Vue 实例,this.a 将是 undefined。

不止是 methods ,computed、watch 等函数也是一样,看一下如果用箭头函数会怎样把:

var a = 3;
var vm = new Vue({
	data: {
		a: 1
	},
	methods: {
		plus: () => {
			this.a++
		}
	}
});
vm.plus();
console.log(vm.a,a);	//output: 1	4

这里,this 绑定的是 window,如果不用箭头函数,当然是 vm。

6.回调函数中的 this

其实,回调函数的 this 和主函数的调用对象没有什么关系,主要看主函数的内部怎么去处理这个回调函数。

function foo(f) {
	console.log(this);
	f();
}
var obj = {name: ‘obj‘};
foo.call(obj, function() {
	console.log(this)
})

js 中 this 的指向问题

当然,讨论这个是没有意义的,主要是要记住一些特殊的异步函数的 callback 内部的 this 指向,如:

setTimeout 函数的 callback 内部的 this 永远指向 window,无论在何处执行 setTimeout ,当然,这不包括回调为箭头函数。还有 addEventListener 的 callback 中的 this 指向绑定事件的 DOM 对象 。

如果回调函数是箭头函数呢?

同样的你把箭头函数转换成 ES5 语法代码,就很明了。

document.addEventListener(‘click‘,()=>{
	console.log(this)
},false);	//#document

Babel 转换:

"use strict";

var _this = void 0;

document.addEventListener(‘click‘, function () {
  console.log(_this);
}, false);	//undefined

当需要函数作为参数时,使用箭头函数一定要注意 this ,箭头函数绑定了父级作用域的上下文。比方说上面 Vue 的那个例子,如果我一定要用箭头函数可以吗?我觉得是可以的:

var vm = new Vue({
	data: {
		a: 1
	},
	methods: {
		plus: () => {
			vm.a++
		}
	}
});
vm.plus();
console.log(vm.a)

把 this 换成具体的对象就解决啦,哈哈。

js 中 this 的指向问题

上一篇:Mac版phpstorm 一次性折叠所有函数/方法的快捷键


下一篇:PHP foreach 中的 &