Javascript的this

Javascript的this永远指向函数执行时的上下文,而不是定义时的。JavaScript 语言之中,一切皆对象,运行环境也是对象,所以函数都是在某个对象之中运行,this就是函数运行时所在的对象(环境)。
JavaScript 支持运行环境动态切换,也就是说,this的指向是动态的,没有办法事先确定到底指向哪个对象。

Javascript中, this的值取决于调用者。

对象方法调用

当一个函数被作为对象的一个方法调用时,this指向该对象,该方法赋值给另一个对象,就会改变this的指向。如:

1
2
3
4
5
6
7
8
var testObj = {
a: 1,
getValue: function() {
console.log(this.a);
}
};
obj.getValue(); // 输出1, 此时的this指向testObj

注意: 该模式中, this到对象的绑定发生在方法被调用的时候。

下面这几种用法,都会改变this的指向:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 情况一
(obj.foo = function () {
console.log(this);// window
})()
// 等同于
(function () {
console.log(this);// window
})()
// 情况二
(false || function () {
console.log(this);// window
})()
// 情况三
(1, function () {
console.log(this);// window
})()

函数方法调用

当调用者仅仅是一个函数方法时, 属于全局性调用,此时的this指向全局对象(Global), 如:

1
2
3
4
5
var a = 1;
function getValue() {
console.log(this.a);
}
getValue(); // 输出1, 此时的this指向Global.

构造器调用

通过new调用的函数就是构造函数, 通过这个函数生成一个新对象,this指向该构造器函数生成的新的实例对象, 如:

1
2
3
4
5
6
7
8
9
function Test(value) {
this.a = value;
};
test.prototype.getValue = function() {
console.log(this.a);
};
var test = new Test(1);
test.getValue(); // 输出1
console.log(test.value) // 输出1

this指向了test对象.

apply/call调用

apply()是函数对象的一个方法,它的作用是改变函数的调用对象,它的第一个参数就表示改变后的调用这个函数的对象。
因此,this指的就是这第一个参数。apply()的参数为空时,默认调用全局对象

1
2
3
4
5
6
7
8
9
10
11
12
13
var value = 888
var Test = function(value) {
this.value = value;
}
Test.prototype.getValue = function() {
console.log(this.value);
}
var obj = {
value: 666
};
Test.prototype.getValue.apply() // 输出 888, getValue方法中的this指向了全局对象
//修改apply的参数为obj
Test.prototype.getValue.apply(obj); // 输出 666, getValue方法中的this指向了obj

使用注意点

避免多层 this

由于this的指向是不确定的,所以切勿在函数中包含多层的this

1
2
3
4
5
6
7
8
9
10
11
12
var o = {
f1: function () {
console.log(this);
var f2 = function () {
console.log(this);
}();
}
}
o.f1()
// Object
// Window

上面代码包含两层this,结果运行后,第一层指向对象o,第二层指向全局对象,因为实际执行的是下面的代码。

1
2
3
4
5
6
7
8
9
10
var temp = function () {
console.log(this);
};
var o = {
f1: function () {
console.log(this);
var f2 = temp();
}
}

一个解决方法是在第二层改用一个指向外层this的变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
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的值,然后内层函数调用这个变量,是非常常见的做法,请务必掌握。

JavaScript 提供了严格模式,也可以硬性避免这种问题。严格模式下,如果函数内部的this指向顶层对象,就会报错。

1
2
3
4
5
6
7
8
9
10
var counter = {
count: 0
};
counter.inc = function () {
'use strict';
this.count++
};
var f = counter.inc;
f()
// TypeError: Cannot read property 'count' of undefined

上面代码中,inc方法通过'use strict'声明采用严格模式,这时内部的this一旦指向顶层对象,就会报错。

避免数组处理方法中的 this

数组的mapforeach方法,允许提供一个函数作为参数。这个函数内部不应该使用this

1
2
3
4
5
6
7
8
9
10
11
12
13
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不指向外部,而指向顶层对象。

解决这个问题的一种方法,就是前面提到的,使用中间变量固定this

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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方法的第二个参数,固定它的运行环境。

1
2
3
4
5
6
7
8
9
10
11
12
13
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

避免回调函数中的 this

回调函数中的this往往会改变指向,最好避免使用。

1
2
3
4
5
6
7
var o = new Object();
o.f = function () {
console.log(this === o);
}
// jQuery 的写法
$('#button').on('click', o.f);

上面代码中,点击按钮以后,控制台会显示false。原因是此时this不再指向o对象,而是指向按钮的 DOM 对象,因为f方法是在按钮对象的环境中被调用的。这种细微的差别,很容易在编程中忽视,导致难以察觉的错误。

为了解决这个问题,可以采用下面的一些方法对this进行绑定,也就是使得this固定指向某个对象,减少不确定性。

绑定 this 的方法

this的动态切换,固然为 JavaScript 创造了巨大的灵活性,但也使得编程变得困难和模糊。有时,需要把this固定下来,避免出现意想不到的情况。

JavaScript 提供了callapplybind这三个方法,来切换/固定this的指向。

参考这篇博客