this会根据运行时当前运行环境的不同指向不同的对象
var obj = {
color:'black',
foo: function(){
console.log(this);
}
}
var obj2 = {
color:'white'
}
//在obj的环境下调用foo,this指向obj
obj.foo();
//把obj.foo函数内存地址赋值给obj2.foo
obj2.foo = obj.foo;
//在obj2环境下调用foo,this指向obj2
obj2.foo();
由于this指向的不确定性,我们使用时候应该尽量避免使用多层this:
var o = {
f1: function () {
console.log(this);
var f2 = function () {
console.log(this);
}();
}
}
o.f1()
因为我们知道,对象中可以保存一般的属性或者是一个函数,如果是一般的属性,那么属性的值保存在属性描述对象的value属性里面;而如果是一个函数,那么引擎会将函数单独保存在内存中,然后再将函数的地址赋值给属性的value属性,所以这段代码相当于执行了:
var temp = function () {
console.log(this);
}
var obj = {
f1: function () {
console.log(this);
var f2 = temp();
}
}
obj.f1()
所以第一次log this时候this指向的是对象obj,而f1函数里面有个立即执行函数f2,它的this是指向顶层对象window的,所以第二次log的this指的是window:
解决办法:就是把当前的this指向的对象地址保存在一个变量self里面,然后内层函数想要调用的时候就直接调用self:
var obj = {
f1: function () {
console.log(this);
var self = this;
var f2 = function(){
console.log(self);
}();
}
}
obj.f1()
结果:
这时候在f2内部调用self,实际指向的就是外面的obj,不会发生this指向的改变带来的不确定性问题
总结一下,这个方法的原理就是使用一个变量固定this的值,然后内层函数调用这个变量
还有一个需要注意的地方:
JavaScript 提供了严格模式,也可以硬性避免这种问题。严格模式下,如果函数内部的this指向顶层对象,就会报错。
var obj = {
count: 0
};
obj.inc = function () {
'use strict';
this.count++
};
var f = obj.inc;
f();
上面代码中,inc方法通过声明use strict采用严格模式,这时内部的this一旦指向顶层对象,就会报错。
解决办法之一是使用call()方法改变inc函数内部的指向,让它指回obj:
var obj = {
count: 0
};
obj.inc = function () {
'use strict';
this.count++
};
var f = obj.inc;
f.call(obj);
这样就不会报错了,具体细节我们后面再讨论。
我们再看一个例子,这个例子说明了应该尽量避免数组处理方法中的 this
数组的map和foreach方法,允许提供一个函数作为参数。这个函数内部不应该使用this。
var obj = {
v: 'hello',
p: ['a1', 'a2'],
f: function f() {
this.p.forEach(function (item) {
// 内层函数的this指向的是顶层对象window
console.log(this.v + ' ' + item);
});
}
}
obj.f()
因为内层函数的this指向的是顶层对象window,自然读取不到window.v,所以返回的是undefined,拼接在一起就是undefined a1和undefined a2
要解决这个问题,常见的两个办法:
方法1:使用中间变量固定this
var obj = {
v: 'hello',
p: ['a1', 'a2'],
f: function f() {
//使用中间变量self固定this
var self = this;
this.p.forEach(function (item) {
// 这里的self指向的是obj对象本身
console.log(self.v + ' ' + item);
});
}
}
obj.f()
方法2:给forEach方法传入thisArg参数:
var obj = {
v: 'hello',
p: ['a1', 'a2'],
f: function f() {
this.p.forEach(function (item) {
console.log(this.v + ' ' + item);
},this);
// ^---- 注意这里我们传递的this指向的就是obj,每次调用forEach里面方法的时候,this都指向obj对象
}
}
obj.f()
输出:
所以我们得到一个重要的结论:内层函数的this不指向外部,而指向顶层对象window。
如果还是不明白,我们看一个MDN的例子,我改进了一下,结果可以看的更详细:
我们按照每个数组中的元素值,更新一个对象的属性:
function Counter() {
this.sum = 0;
this.count = 0;
}
Counter.prototype.add = function (array) {
console.log('enter function add, this --->' + this);
array.forEach(function (entry) {
console.log('enter function forEach, this --->' + this);
this.sum += entry;
++this.count;
});
// ^---- Note这里啥都没
};
const obj = new Counter();
obj.add([2, 5, 9]);
console.log(obj.sum);
console.log(obj.count);
按照设想,应该会在obj.add([2,5,9])以后,函数内部遍历这个[2,5,9]数组,每次遍历对Counter的实例obj里面的sum属性加上当前遍历到的数组元素的值,然后obj.count的值每次+1,所以最终的结果应该是打印出:
obj.sum=2+5+9=16,
obj.count=1+1+1=3
实际运行结果:
为什么会这样呢,就是因为forEach函数内部的function匿名函数中的this指向的是window对象
解决办法:将this当作foreach方法的第二个参数,固定它的运行环境。
这样就可以了,运行结果
本文参考了以下资料:
网道 / 面向对象编程 / this 关键字
MDN Array.prototype.forEach()