JavaScript中的this

学习JavaScript的时候,this曾让我困惑不已,因为不清楚this所指向的是什么,在阅读一些代码的时候总是迷迷糊糊,不知所云。弄懂this后,看JavaScript代码的时候比原来更清晰,对语言本身也有了更深的理解。在JavaScript中,this非常重要但又特别容易弄错,所以我在此总结一下this的点点滴滴,希望能给那些对this还不明白的朋友们一点帮助吧。


一、this的起源

了解this是怎么来的,对更好地理解this还是很重要的。

当一个函数被调用的时候,也就是执行流进入一个函数的时候,会自动创建一个被称为执行上下文的记录(其实这个时候也就产生了作用域链),这个记录里包含了各种必要信息,比如函数的调用方式,函数的参数(也就是arguments)等,this就包含在这个信息里。

二、对this的误解

我刚接触this的时候,我简单地觉得this就是指向函数所在的对象。比如:

1
2
3
4
5
6
7
var myObj = {
message: "Hi, Ontides!",
say: function say(){
console.log(this.message);
}
};
myObj.say(); //Hi, Ontides!

这里确实如所预想的那样,打印出了“Hi, Ontides”,但是,函数并不一定在一个对象中,如果是一个全局函数,那么它里面的this指向就不能简单地理解为“指向函数所在的对象”那么了。

实际上,JavaScript中this的指向和this所在函数定义的位置没有任何关系(ES5及以前,因为ES6中的箭头函数不是这样,稍后会说)。我们之所以会认为this和函数所声明的地方有关,是因为在JavaScript中的作用域遵循词法作用域,因此,我们很容易使用词法作用域的分析方式去分析this的具体指向。实际上,this的处理方式和动态作用域的处理方式相同。this的指向取决于函数调用的位置,而不是函数定义的位置。

##三、this真正的指向

上面提到,函数调用的位置决定了this绑定的对象。在JavaScript中,对应函数四种调用方式,this的绑定一共有四种方式,分别为隐式绑定,显式绑定、new绑定和默认绑定。

1. 隐式绑定

当函数的调用位置具有上下文对象,或者说函数是在一个对象下调用的时候,this会被隐式绑定到这个上下文对象上。如:

1
2
3
4
5
6
7
8
9
function foo(){
console.log(this.a);
}
var obj = {
a: 2,
foo: foo
};
//这里this指向了obj
obj.foo() //2

2. 显式绑定

当一个函数使用call或者apply的方式调用的时候,this会被显式地绑定到call或者apply所指定的对象。如:

1
2
3
4
5
6
7
8
var a = 3;
function foo(){
console.log(this.a);
}
var obj = {
a: 2
};
foo.call(obj); //2

这里因为函数foo再调用的时候使用.call()的方式调用,所以this指向了obj而不是默认的全局对象。

3. new绑定

当对函数进行构造调用的时候(即使用new操作符进行调用),this的指向会发生改变。为了更好阐述new绑定,这里先阐述一下对函数构造调用的的过程。

当对一个函数尽兴构造调用的时候,会经历以下步骤:

  1. 创建一个新的对象
  2. 将构造函数的作用域赋给新的对象
  3. 执行构造函数中的代码
  4. 返回新对象

在步骤2中this被绑定到了这个新对象。

4. 默认绑定

当前几种绑定都不适用的情况下JavaScript引擎会对this执行默认绑定。这里的函数调用方式是独立函数调用,如:

1
2
3
4
5
var a = 1;
function foo(){
console.log(this.name);
}
foo(); //1

在默认绑定的情况下this被绑定到了全局对象,因为全局变量a是全局对象的一个属性,因此这里输出了a中的内容。

这里需要注意的是,再严格模式下,默认绑定将不会绑定到全局对象上,而会绑定到undefined,如:

1
2
3
4
5
6
"use strict";
var a = 2;
function foo(){
console.log(this.a);
}
foo(); //TypeError: undefined is not an object

这里抛出了类型错误的提示,因为this被绑定到undefined,而undefined不是一个对象。

四、this绑定优先级

this绑定一共四条规则,那么在判断this绑定的时候应该怎样运用规则呢?通过this绑定的优先级来判断。

优先级从高到低分别为 new绑定 > 显式绑定 > 隐式绑定 > 默认绑定。

五、值得注意的几个点

1. 小心隐性绑定

有些情况一些函数看起来像是隐性绑定,而实际上是应用了默认绑定。如:

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

这里虽然把obj.foo赋给了bar,但是实际上只是把对foo的引用赋给了bar,因此这里bar的调用只是普通的独立函数调用。

还有一种更为常见而容易出错的情况发生在传入回调函数时:

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

同样,如果理解函数名是对函数的引用的话,这个结果就容易理解了呃。传递到doFoo的参数是函数foo,函数的调用方式是独立调用,因此这里的this并不适用隐形绑定规则,而是采用默认绑定的规则。

2. 箭头函数

ES6中的箭头函数不适用与上面四个规则。

关于箭头有两点需要注意:

  1. 箭头函数中的this由函数所在的外部作用域来决定的

    这里箭头函数中this遵循词法作用域的规则。如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    var a = 2;
    var obj = {
    a: 1,
    foo: function foo(){
    setTimeout(()=>{
    console.log(this.a);
    },30);
    }
    }
    obj.foo(); //1

    这里定时器中的this并没有指向全局对象,是绑定到obj,可以看出是继承了外层函数中的this的绑定对象。

  2. 箭头函数的this绑定无法修改

    箭头函数使用自己独特的this绑定原则,不能通过.call()等方式改变this指向。

文章目录
  1. 1. 一、this的起源
  2. 2. 二、对this的误解
    1. 2.1. 1. 隐式绑定
    2. 2.2. 2. 显式绑定
    3. 2.3. 3. new绑定
    4. 2.4. 4. 默认绑定
  3. 3. 四、this绑定优先级
  4. 4. 五、值得注意的几个点
    1. 4.1. 1. 小心隐性绑定
    2. 4.2. 2. 箭头函数
|