从《深入JS中的作用域和执行上下文》中可以得知,当JS引擎执行一段JS可执行代码时,会创建对应的执行上下文。而在创建执行上下文的过程中,要经历三个步骤:创建变量对象、建立作用域链以及确定this指向。
由上,首先记住一句话,this,是在创建执行上文时被确定的。而执行上下文,从《深入作用域链》中可以知道是在函数被激活(调用)的时候才被创建的。
在绝大多数情况下,函数的调用方式决定了this的值。this不能在执行期间被赋值,并且在每次函数被调用时this的值也可能会不同。 ——引自MDN
1
2
3
4
5
6
7
8
9
10
11 var a = 10;
var obj = {
a: 20
}
function fn() {
this = obj; // 这句话试图修改this,运行后会报错
console.log(this.a);
}
fn();
全局上下文
无论是否在严格模式下,在全局执行上下文中(在任何函数体外部)this 都指代全局对象。
1 | // 在浏览器中, window 对象同时也是全局对象: |
函数上下文
在函数内部,this的值取决于函数被调用的方式。
1. 独立函数中的 this
1 | // 非严格模式,且this的值不是由该调用设置,所以默认指向全局对象 |
1 | // 严格模式下,this将保持他进入执行上下文时的值,下面的this默认为undefined |
所以,在严格模式下,如果 this 没有被执行上下文(execution context)定义,那它将保持为 undefined。
再来看一道题:1
2
3
4
5
6
7
8
9
10
11var a = 20;
var foo = {
a: 10,
getA: function() {
return this.a;
}
}
console.log(foo.getA()); // 10
var test = foo.getA;
console.log(test()); // 20
foo.getA() 中, getA 是调用者,他不是独立调用,被对象 foo 所拥有,因此它的 this 指向了 foo 。而 test() 作为调用者,尽管他与 foo.getA 的引用相同,但是它是独立调用的,因此 this 指向 undefined ,在非严格模式,自动转向全局 window 。
结论:如果函数独立调用,那么该函数内部的this,则指向undefined。但是在非严格模式中,当this指向undefined时,它会被自动指向全局对象。
2. 对象的方法中的 this
- 作为普通对象的方法调用
1
2
3
4
5
6
7
8var o = {
prop: 37,
f: function() {
return this.prop;
}
};
console.log(o.f()); // 37
这样的调用方式,不受函数定义位置或者方式的影响。比如下面,我们也可以先定义函数,再将其附属到对象o上。1
2
3
4
5
6
7
8
9var o = {prop: 37};
function independent() {
return this.prop;
}
o.f = independent;
console.log(o.f()); // 37
这表明函数是从o的f成员调用的才是重点。并且:
this 的绑定只受最靠近的成员引用的影响。
1 | var name = "windowName"; |
- 原型链中的this
1
2
3
4
5
6
7
8
9
10var o = {
f: function() {
return this.a + this.b;
}
};
var p = Object.create(o);
p.a = 1;
p.b = 4;
console.log(p.f()); // 5
对象p没有属于它自己的f属性,它的f属性继承自它的原型。查找p的f属性过程首先从 p.f 的引用开始,所以函数中的 this 指向p。也就是说,因为f是作为p的方法调用的,所以它的this指向了p。
结论:如果该方法存在于一个对象的原型链上,那么this指向的是调用这个方法的对象,就像该方法在对象上一样。
- getter 与 setter 中的 this
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19function sum() {
return this.a + this.b + this.c;
}
var o = {
a: 1,
b: 2,
c: 3,
get average() {
return (this.a + this.b + this.c) / 3;
}
};
Object.defineProperty(o, 'sum', {
get: sum,
enumerable: true,
configurable: true});
console.log(o.average, o.sum); // 2, 6
结论:用作 getter 或 setter 的函数都会把 this 绑定到设置或获取属性的对象。
3. 构造函数中的 this
当一个函数用作构造函数时(使用new关键字),它的this被绑定到正在构造的新对象。
1
2
3
4
5
6 function C() {
this.a = 37;
}
var o = new C();
console.log(o.a); // 37
在这里说一下new的过程:
- var obj = {}; // 创建一个空对象
- 将构造函数的作用域赋给新对象(因此this就指向了这个新对象)
- 执行构造函数中的代码(为这个新对象添加属性和方法)
- 返回新对象
!!! 虽然构造器返回的默认值是this所指的那个对象,但它仍可以手动返回其他的对象(如果返回值不是一个对象,则返回this对象)。比如像下面:
1 | function C2(){ |
4. 箭头函数中的 this
1 | function Timer() { |
Timer函数内部设置了两个定时器,分别使用了箭头函数和普通函数。前者的this绑定定义时所在的作用域(即Timer函数),后者的this指向运行时所在的作用域(即全局对象)。所以,3100 毫秒之后,timer.s1被更新了 3 次,而timer.s2一次都没更新。