this 关键字是 JavaScript 中比较复杂的机制之一,它是一个很特别的关键字,被自动定义在所有函数的作用域中。人们很容易把 this 理解成指向函数自身,这其实是不对的。每个函数的 this 是在调用时被绑定的,取决于函数的调用位置。
绑定规则
默认绑定
这是最常用的函数调用类型即独立函数调用,当无法应用其他规则时,便走这条默认规则。此时 this 会被绑定到全局对象,严格模式下会被绑定到 undefined 。
首先我们来看下面这段代码
function test() { console.log(this.a); } var a = 2; test(); // 2
我们可以看到当 test 函数被调用时,this.a 指向了我们定义的全局变量 a。这里 test 函数是独立函数的调用,应用的是默认绑定的规则,此时 this 指向全局对象。
我们再来看看使用严格模式时的运行情况
function test() { 'use strict'; console.log(this.a); } var a = 2; test(); // TypeError: Cannot read property 'a' of undefined
可以看到,在严格模式下,this 将会绑定到 undefined。
隐式绑定
这条规则是要考虑调用位置是否有上下文对象。此时 this 被绑定到这个上下文对象上面。
function test() { console.log(this.a); } var obj = { a: 2, test: test }; obj.test(); // 2
可以看到 test 函数是使用 obj 这个上下文对象来调用的,此时 this 会绑定到 obj 这个上下文对象,this.a 其实就是 obj.a 都是 2。
显式绑定
显式绑定是指我们通过 call 、apply 方法强制改变函数运行时的上下文对象。它们的第一个参数是一个对象,函数调用时 this 就会绑定到这个对象上面。
function test() { console.log(this.a); } var obj = { a: 2 }; test.call(obj); // 2
我们通过 test.call(obj) 强制把 this 绑定到 obj 上。这里我们想一下,如果把 null、undefined 作为 this 的绑定对象传入会怎么样呢?
function test() { console.log(this.a); } var obj = { a: 2 }; var a = 1; test.call(null); // 1 test.call(undefined); // 1
可以看到,当显示绑定到 null、undefined 时,这些值在函数调用时会被忽略,此时会使用默认规则来绑定 this 到全局对象。
new 绑定
我们首先先要了解下通过 new 来调用函数,发生了哪些步骤。
1. 创建一个全新的对象
2. 新对象会被执行原型连接
3. 新对象会绑定到函数调用的 this
4. 如果函数没有返回其他对象,那么 new 表达式中的函数调用会自动返回这个新对象
我们看下面代码
function Test (a) { this.a = a; } var test = new Test(2); test.a // 2
使用 new 来调用函数 Test 时,会创建一个新的对象并且把它绑定到 Test 调用中的 this 上。
优先级
我们已经知道了 this 指向的 4 条规则,那么当一个函数调用时同时满足多条规则时该怎么办呢?这时候我们确定这 4 条规则的优先级。
记住: new 绑定 > 显式绑定 > 隐式绑定 > 默认绑定。
所以我们可以按照下列顺序来判断 this 指向:
-
new 绑定:函数是否是通过 new 调用的?如果是那么 this 绑定的就是新创建的对象。
-
显式绑定:函数是否通过 call、apply 调用的?如果是那么 this 绑定的就是指定的对象。
-
隐式绑定:函数是否在某个上下文对象中调用?如果是那么 this 绑定的就是那个上下文对象。
-
默认绑定:如果以上都不是的话,使用默认绑定。this 绑定到全局对象上,注意如果在严格模式下,this 会绑定到 undefined 上。
值得一提的是,ES6 中的箭头函数并不会使用以上四条绑定规则,箭头函数会继承外层函数调用的 this 绑定。其实和 ES6之前代码中的 var self = this 机制一样。
更多精彩内容,欢迎关注微信公众号~