this 关键字是 JavaScript 中最复杂的机制之一,是一个特别的关键字,被自动定义在所有函数的作用域中.

为什么要搞懂this

  • 面向oop编程需要知道什么是this
  • 在各种function调用过程中可以清晰的弄懂为什么。
  • 可以更好的理解大师们的代码
  • 防止面试被咔嚓。。。(血的教训)

this是什么?

顾名思义,this就是一个指针,但是他并不是指向自身!而是指向调用函数的对象。(这句话很重要。也是本篇文章贯穿始终的主旨。)

this的指向判定标准

前人的判定

  1. 函数是否是new调用,如果是,则指向new创建的实例。
  2. 函数是否是bind方法返回的?如果是,则this指向指定对象。
  3. 函数是否是通过apply/call调用的?如果是,则this指向指定对象。
  4. 是否作为对象的方法调用? 如果是,则this指向该对象。
  5. this指向全局。

为什么这样判定?

为什么通过这五种方式来判定呢?我们首先要知道this的绑定规则有哪些?

  • 默认绑定
  • 隐式绑定
  • 显示绑定
  • new 绑定

下面我们来分别挨个说明每个规则,来验证我们上述的判定标准。

默认绑定

在不能应用其他绑定规则时使用的默认规则,通常是独立函数调用。

function getName(){
console.log('Hello',this.name);
}
var name = 'mySelf';
getName()

在调用getName的时候,应用了默认绑定,在非严格模式下this指向全局对象,严格模式下thisundefined,

  • 在浏览器模式下this指向了全局对象window,所以运行结果是HellomySelf
  • 在node环境下this指向的全局对象是node-runtime

在该文章中我们主要讨论浏览器中的表现。

隐式绑定

函数的调用存在上下文关系,典型案例如abc.foo():

var obj = {
tag: 'a',
func: function(){
console.log(this.tag)
}
}
var tag = 'b';
obj.func();

执行结果是a;那如果我把sayHi的定义提升到外边,如下所示:

function h(){
console.log(this.tag)
}
var obj = {
tag: 'obj',
func: h
}
var tag = 'window';
obj.func();

执行结果跟上面是一样的。这是因为在函数sayHi调用的时候,此时的上下文对象属于person,隐式绑定会将函数中的this绑定到person这个上下文对象上

需要注意的是:在嵌套对象的调用时,只有离this最近的那一层会影响到调用位置。

function h(){
console.log(this.tag)
}
var obj1 = {
tag: 'obj1',
func: h
}
var obj2 = {
tag:'obj2',
obj:obj1
}
var tag = 'window';
obj2.obj.func();

结果是obj1,这是因为只有执行到obj1.func的时候,才能确定函数hthis指向的上下文对象为obj1.

隐式绑定有一个很大的坑,就是会丢失绑定。也就是说,我们以为的this指向,其实并不是this真正的指向(我们以为他是雷锋,但是他却是雷峰塔)

隐式绑定的丢失主要体现在两个方面:

  1. 函数重新赋值,如下面代码所示:

    function h(){
    console.log(this.tag)
    }
    var obj1 = {
    tag:'obj1',
    func:h
    }
    var tag = 'window'
    var callh = obj1.func;
    callh(); //window

    这是因为callh直接指向了函数h的引用,在执行callh的时候上下文对象为document.window

  2. 在回掉函数和事件回调中也会丢失绑定,如下代码所示

    function h(){
    console.log(this.tag)
    }
    var obj1 = {
    tag:'obj1',
    func:function(){
    setTimeout(function(){
    console.log(this.tag)
    })
    }
    }
    var obj2 = {
    tag:'obj2',
    func:h
    }
    var tag = 'window'
    obj1.func(); // window
    setTimeout(obj2.func,200); // window
    setTimeout(function(){
    obj2.func()
    },200) //obj2
    • 第一个输出是因为命中了默认绑定,this指向了document.window
    • 第二个输出是因为obj2.func赋值给了setTimeout的回调入参之后又执行这个入参,所以隐式绑定丢失,this指向了document.window
    • 第三条输出是因为命中了隐式绑定,this指向了obj2

显式绑定

通过 call,apply,bind 的方式,显式的指定this所指向的对象。call,apply,bind的第一个参数,就是对应函数的this所指向的对象。callapply的作用一样,只是传参方式不同。callapply都会执行对应的函数,而bind方法不会。

function h(){
console.log(this.tag)
}
var obj1 = {
tag: 'obj1',
func: h
}
var tag = 'window';
var h2 = function(func){
func()
}
h2.call(obj1,obj1.func) //window

因为obj1.func作为入参传入的时候,已经丢失了隐式绑定的this

new关键字绑定

使用new来调用函数,会自动执行下面的操作:

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

绑定优先级

new 绑定 > 显式绑定 > 隐式绑定 > 默认绑定

绑定例外

如果我们将null或者是undefined作为this的绑定对象传入call、apply、bind, 这些值在调用时会被忽略,实际应用的是默认绑定规则。

箭头函数

箭头函数是 ES6 中新增的,它和普通函数有一些区别,箭头函数没有自己的 this,它的 this 继承于外层代码库中的 this。箭头函数在使用时,需要注意以下几点:

  • 函数体内的 this 对象,继承的是外层代码块的 this。
  • 不可以当作构造函数,也就是说,不可以使用 new 命令,否则会抛出一个错误。
  • 不可以使用 arguments 对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
  • 不可以使用 yield 命令,因此箭头函数不能用作 Generator 函数。
  • 箭头函数没有自己的 this,所以不能用 call()、apply()、bind() 这些方法去改变 this 的指向.

总结

1 . 函数是否在 new 中调用 (new 绑定),如果是,那么 this 绑定的是新创建的对象。
2 . 函数是否通过 call,apply 调用,或者使用了 bind(即硬绑定),如果是,那么 this 绑定的就是指定的对象。
3 . 函数是否在某个上下文对象中调用 (隐式绑定),如果是的话,this 绑定的是那个上下文对象。一般是 obj.foo()。
4 . 如果以上都不是,那么使用默认绑定。如果在严格模式下,则绑定到 undefined,否则绑定到全局对象。
5 . 如果把 Null 或者 undefined 作为 this 的绑定对象传入 call、apply 或者 bind,这些值在调用时会被忽略,实际应用的是默认绑定规则。
6 . 如果是箭头函数,箭头函数的 this 继承的是外层代码块的 this。

参考