「你必须知道的JavaScript」到底“这”是什么啊

为什么要用this

使用this可以让我们灵活地在不同的上下文中重复的使用函数,不需要针对每个对象编写不同版本的函数。

因此,掌握this的指向,对我们来说额外重要。

什么是上下文?

当一个函数被调用时,会创建一个活动记录,包括函数在哪被调用,调用方式,传入参数等,this则是其中的一个属性,在函数执行的过程中会用到。

对this的误解

  • this指向函数本身?no!
  • this指向函数作用域?no!

this指向是在进行时绑定的,与函数声明的位置没有任何关系,取决于函数的调用方式,在哪里被调用。

## 函数的调用位置

首先我们想理解一下调用位置和调用栈,这里有一段函数。

function baz({
  // 此处的调用栈为 baz
  // 调用位置:全局作用域
  console.log('baz');
  bar();
}

function bar({
  // 此处的调用栈为 baz -> bar
  // 调用位置:baz
  console.log('bar');
  foo();
}

function foo({
  // 此处的调用栈为 baz -> bar -> foo
  // 调用位置:bar
  debugger
  console.log('foo')
}

baz()
复制代码

我们在浏览器中执行一下它:

可以看到当前位置的函数调用列表,这个就是调用栈。而栈中的第二个元素,就是该函数的调用位置。

this的绑定规则

默认绑定

this指向全局对象,当函数内使用严格模式时,this会绑定到undefined。

有个细节:决定this绑定对象的并不是调用位置是否处于严格模式,而是该函数的函数体是否是处于严格模式。见下面的代码:

function foo(){
    "use strict";
    console.log(this.a);
}

function foo1(){
    console.log(this.a);
}

var a = 2;
foo(); // TypeError:this is undefined


(function(){
    "use strict";
    foo1(); // 2
})();
复制代码

隐式绑定

判断调用位置是否有上下文对象,或者是否被某个对象拥有或包含该函数引用,符合条件的话,this会隐式绑定为该对象。

  • 函数中的this只会指向它上一层的

    function foo(){
       console.log(this.a);   
    }
    var obj2 = {
        a:3,
        foo:foo
    };
    var obj1 = {
        a:2;
        obj2:obj2
    };
    obj1.obj2.foo() // 3 而不是2
    
    隐式
    复制代码
  • 隐式丢失

    看下面的例子,fn虽然是obj.foo的一个引用,但实际上引用的是foo函数本身,因此可以fn()是一个不带修饰的函数调用,应用了默认绑定。

    注意:无论是在Object中直接定义函数,还是想定义函数再添加为引用属性,这个函数严格来说都不属于这个对象。

    function foo(){
       console.log(this.a);   
    }
    var obj = {
        a:3,
        foo:foo
    };
    
    var a = 2;
    var fn = obj.foo;
    fn(); // 2 而不是3
    复制代码

显式绑定

直接指定this的绑定对象

  • call(thisArg,arg1,arg2,...) 与 apply(...)

    • 参数

      • this的绑定对象
      • 参数列表
    • 使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数

如果传入一个原始值,会被转化为其对象形式,以下同理。

  • bind(thisArg,arg1,arg2,...)——硬绑定

    • 参数

      • this的绑定对象
      • 参数列表
    • 创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。

  • API调用的上下文

    某些API提供了可选参数,其作用和bind(...)一样,确保你的回调函数使用指定的this。

    function foo(el){
        console.log(el,this.id)
    }
    var obj = {
        id:'ok'
    }
    [1,2,3].forEach(foo,obj);
    //1ok 2ok 3ok
    复制代码

new绑定

创建过程:

  1. 创建一个全新的对象
  2. 执行[[Prototype]]连接
  3. 将该对象绑定到函数调用的this
  4. 如果函数中没有返回其他对象,自动返回创建的对象。
function foo(a){
    this.a = a
}
var bar = new foo(2);
console.log(bar.a); // 2
复制代码

## 绑定规则优先级

  1. 函数是否在new中调用 ?this绑定的是新创建的对象 :继续往下
  2. 函数是否通过call、apply或者bind调用 ?this绑定指定对象:继续往下
  3. 函数是否在某个上下文对象中调用 ?this绑定该上下文对象 :继续往下
  4. 默认绑定,是否是严格模式 ?undefined :全局对象。

绑定的其他情况

  • 在call、apply等中,如果将要绑定的this传入null作为占位符时,在非严格模式下,会应用默认绑定规则绑定到全局对象,将导致不可预计的后果,如修改全局对象。

    • 创建一个空的非委托的对象,将this指向它。

    Object.create(null) 不会创建Object.prototype这个委托,比{}更空。

  • 间接引用(见隐式绑定中的隐式丢失)

  • 使用软绑定

    if (!Function.prototype.softBind) {
        Function.prototype.softBind = function (obj,...args1{
            //传入的obj是我们想要设置的默认的this绑定值
            var fn = this;
            var bound = function (...args2{
                return fn.apply(
                    // 如果this指向全局对象,说明默认绑定,则绑定为obj
                    (!this || this === (window || global)) ? obj : this,
                    //新的参数列表
                    [...args1,...args2]
                );
            };
            bound.prototype = Object.create(fn.prototype);
            return bound;
        };
    }
    //来自《你不知道的JavaScript》上册
    复制代码

箭头函数的this

箭头函数的this是根据外层(函数或者全局)作用域来决定的。常用于回调函数中。

function foo(){
    return ()=>{
        console.log(this.a)
    }
}
var obj = {
    a:2;
}
var obj2 = {
    a:3;
}
var bar = foo.bind(obj);
bar.call(obj2); // 2 而不是3,箭头函数的绑定无法修改。
复制代码

猜你喜欢

转载自juejin.im/post/7040707728599154695