首先,this总是返回一个对象,简单说,就是返回属性或方法“当前”所在的对象。
由于对象的属性可以赋给另一个对象,所以属性所在的当前对象是可变的,即this的指向是可变的。
总结一下,JavaScript语言之中,一切皆对象,运行环境也是对象,所以函数都是在某个对象之中运行,this就是这个对象(环境)。这本来并不会让用户糊涂,但是JavaScript支持运行环境动态切换,也就是说,this的指向是动态的,没有办法事先确定到底指向哪个对象,这才是最让初学者感到困惑的地方。
如果一个函数在全局环境中运行,那么this就是指顶层对象(浏览器中为window对象)。可以近似地认为,this是所有函数运行时的一个隐藏参数,指向函数的运行环境。
this的使用可以分成以下几个场合:
1)在全局环境使用this,它指的就是顶层对象window。
this === window // true
function f() {
console.log(this === window); // true
}
上面代码说明,不管是不是在函数内部,只要是在全局环境下运行,this就是指顶层对象window。
2)构造函数中的this,指的是实例对象。
var Obj = function (p) {
this.p = p;
};
Obj.prototype.m = function() {
return this.p;
};
var o = new Obj('Hello World!');
o.p // "Hello World!"
o.m() // "Hello World!"
上面代码定义了一个构造函数Obj。由于this指向实例对象,所以在构造函数内部定义this.p,就相当于定义实例对象有一个p属性;然后m方法可以返回这个p属性。
3)对象的方法中的this
当A对象的方法被赋予B对象,该方法中的this就从指向A对象变成了指向B对象。所以要特别小心,将某个对象的方法赋值给另一个对象,会改变this的指向。
var obj ={
foo: function () {
console.log(this);
}
};
obj.foo() // obj
上面代码中,obj.foo方法执行时,它内部的this指向obj。
但是,只有这一种用法(直接在obj对象上调用foo方法),this指向obj;其他用法时,this都指向代码块当前所在对象(浏览器为window对象)。
// 情况一
(obj.foo = obj.foo)() // window
// 情况二
(false || obj.foo)() // window
// 情况三
(1, obj.foo)() // window
上面代码中,obj.foo先运算再执行,即使它的值根本没有变化,this也不再指向obj了。
可以这样理解,在JavaScript引擎内部,obj和obj.foo储存在两个内存地址,简称为M1和M2。只有obj.foo()这样调用时,是从M1调用M2,因此this指向obj。但是,上面三种情况,都是直接取出M2进行运算,然后就在全局环境执行运算结果(还是M2),因此this指向全局环境。
上面三种情况等同于下面的代码。
// 情况一
(obj.foo = function () {
console.log(this);
})()
// 情况二
(false || function () {
console.log(this);
})()
// 情况三
(1, function () {
console.log(this);
})()
同样的,如果某个方法位于多层对象的内部,这时为了简化书写,把该方法赋值给一个变量,往往会得到意料之外的结果。
var a = {
b: {
m: function() {
console.log(this.p);
},
p: 'Hello'
}
};
var hello = a.b.m;
hello() // undefined
上面代码中,m是多层对象内部的一个方法。为求简便,将其赋值给hello变量,结果调用时,this指向了顶层对象。为了避免这个问题,可以只将m所在的对象赋值给hello,这样调用时,this的指向就不会变。
var hello = a.b;
hello.m() // Hello
4)在Node中,this的指向又分成两种情况。全局环境中,this指向全局对象global;模块环境中,this指向module.exports。
// 全局环境
this === global // true
// 模块环境
this === module.exports // true
this的使用应注意一下几点:
1)避免多层this
由于this的指向是不确定的,所以切勿在函数中包含多层的this。
var o = {
f1: function () {
console.log(this);
var f2 = function () {
console.log(this);
}();
}
}
o.f1()
// Object
// Window
上面代码包含两层this,结果运行后,第一层指向该对象,第二层指向全局对象。实际执行的是下面的代码。
var temp = function () {
console.log(this);
};
var o = {
f1: function () {
console.log(this);
var f2 = temp();
}
}
一个解决方法是在第二层改用一个指向外层this的变量。
var o = {
f1: function() {
console.log(this);
var that = this;
var f2 = function() {
console.log(that);
}();
}
}
o.f1()
// Object
// Object
上面代码定义了变量that,固定指向外层的this,然后在内层使用that,就不会发生this指向的改变。
事实上,使用一个变量固定this的值,然后内层函数调用这个变量,是非常常见的做法,有大量应用,请务必掌握。
2)避免数组处理方法中的this
数组的map和foreach方法,允许提供一个函数作为参数。这个函数内部不应该使用this。
var o = {
v: 'hello',
p: [ 'a1', 'a2' ],
f: function f() {
this.p.forEach(function (item) {
console.log(this.v + ' ' + item);
});
}
}
o.f()
// undefined a1
// undefined a2
foreach方法的回调函数中的this,其实是指向window对象,因此取不到o.v的值。原因跟上一段的多层this是一样的,就是内层的this不指向外部,而指向顶层对象。
解决这个问题的一种方法,是使用中间变量。
var o = {
v: 'hello',
p: [ 'a1', 'a2' ],
f: function f() {
var that = this;
this.p.forEach(function (item) {
console.log(that.v+' '+item);
});
}
}
o.f()
// hello a1
// hello a2
另一种方法是将this当作foreach方法的第二个参数,固定它的运行环境。
var o = {
v: 'hello',
p: [ 'a1', 'a2' ],
f: function f() {
this.p.forEach(function (item) {
console.log(this.v + ' ' + item);
}, this);
}
}
o.f()
// hello a1
// hello a2
3)避免回调函数中的this
回调函数中的this往往会改变指向,最好避免使用。
可以采用下面的一些方法对this进行绑定,也就是使得this固定指向某个对象,减少不确定性。
绑定 this 的方法:
JavaScript提供了call、apply、bind这三个方法,来切换/固定this的指向。