Higanbana

深入理解闭包(三)——确定this指向

我们在前面说过,在执行上下文创建过程中做了三件事:创建变量对象,生成作用域链,确定this指向。今天我们就来探讨一下this指向的问题。首先先回顾一下我们执行上下文的生命周期图:


执行上下文是函数被调用时创建的,创建过程包括确定this指向,所以this的指向是在函数被调用时确定的

我们知道,this对象是在运行时基于函数的执行环境绑定的:在全局环境中,this指向window,当函数被作为某个对象的方法调用时,this指向那个对象。不过实际情况中往往没有那么好判断,今天我们就来梳理一下。

全局对象中的this

在全局环境下,this永远指向window。

1
console.log(this); //window

函数中的this

this指向调用这个函数的对象。

1
2
3
4
5
6
var a = 10;
function test() {
var a = 20;
console.log(this.a);
}
test(); //10

由于函数test是被全局对象(window)调用的,因此函数内部的this指向window。

1
2
3
4
5
6
7
8
9
10
11
var a = 20;
var obj = {
a: 10,
b:this.a,
fn: function () {
return this.a;
}
}
console.log(obj.fn()); //10
console.log(obj.b); //20

由于函数fn是被对象obj调用的,因此函数fn内部的this指向对象obj。
另外,由于obj不是个函数,不适合上面的规则,我们要单独讨论,如果obj对象在全局创建,那么obj里面的this指向window。

构造函数

构造函数和其他函数的唯一区别,就是他们的调用方式不同。任何函数,只要通过new操作符来调用,那它就可以作为构造函数,调用时经历以下四个步骤:

  1. 创建一个新对象;
  2. 将构造函数的作用域赋给新对象(因此this就指向了这个新对象);
  3. 执行构造函数中的代码(为这个新对象添加属性);
  4. 返回新对象。
1
2
3
4
5
6
function Test() {
this.a = 1;
console.log(this.a); //1
}
var fun = new Test();
console.log(fun.a); //1

创建新对象new Test()后,this指向这个新对象,然后为这个新对象添加属性a = 1,再执行console.log(this.a),此时输出的就是刚添加的a值。返回这个新对象传递给了实例对象fun,此时this指向了实例fun,因此fun.a也为1。

函数用call或apply调用

我们可以利用call或apply手动设置this的指向,这两个方法的第一个参数都是this将要指向的对象,后面的参数,都是向将要执行的函数传递参数。其中call以一个一个的形式传递,apply以数组的形式传递,这是他们唯一的不同。

1
2
3
4
5
6
7
8
9
var obj = {
num: 10
}
function test(a, b) {
console.log(this.num + a + b);
}
test(20,30); //NaN
test.call(obj, 20, 30); // 60
test.apply(obj, [20, 30]); // 60

这个例子很容易理解,本来调用test函数this指向全局对象,是无法访问到obj对象中的num的,但是利用call和apply方法将this指向了obj对象,所以可以顺利输出。

以上就是我目前知道的关于this指向的几种情况,以后可能会再补充。