箭头函数中的this如何与闭包结合使用?

发布于 2024-05-12  413 次阅读


本文于 2024年5月12日 9:20 更新,注意查看最新内容

当 JavaScript 执行到一段可执行代码时,会创建一个可执行上下文。执行上下文可以理解为当前代码的执行环境。 执行上下文的周期可以分为两个阶段。

  1. 创建阶段

    在这个阶段,可执行上下文会创建变量对象、建立作用域链以及确定 this 指向问题

  2. 代码执行阶段

    创建完成后,就开始执行代码,完成变量赋值、函数引用以及执行其他代码。

如何判断 this 的指向

this 对象是基于当前运行环境执行时所绑定的。

  1. 隐式绑定

    通过某个对象的属性指向函数,再通过这个对象属性调用函数。这就是函数在调用的时候给绑定了上下文对象,或者说被这个对象拥有。

    function getName() {
      console.log('this:%s,name:%s', this, this.name);
    }
    var name = 'globalName';
    var bar = {
      name: 'barName',
      getName: getName,
    };
    bar.getName();
    

    getName是一个单独函数,严格来说它不属于 bar,只不过在调用的时候使用bar.getName引用函数,也就是说 getName 函数被调用的时候添加了对 bar 的引用,当函数有引用文对象的时候,函数内部的 this 就会被绑定到 bar。

    所以上述代码执行的结果如下图所示:

    现在代码稍微修改一下

    var name = 'globalName';
    function getName() {
      console.log('this:%s,name:%s', this, this.name);
    }
    var bar = {
      name: 'barName',
      getName: getName,
    };
    //将引用地址赋值给一个新的变量
    const fn = bar.getName;
    fn();
    

    运行结果如下:

    函数getName丢失了绑定对象,变成了 全局对象(或者 undefined,这取决于是否函数是否在严格模式下执行)。

  2. 显示绑定
    通过 call 或者 apply 方法强制把 this 绑定到指定的对象。
    var name = 'globalName';
    function getName(...params) {
      console.log('this:%s,name:%s,params:%s', this, this.name, params);
    }
    var bar = {
      name: 'barName',
    };
    getName.apply(bar, [10, 11]);
    getName.call(bar, 10, 12);
    const fn = getName.bind(bar, 10, 13);
    fn();​

    温馨提示

    applycall区别仅是其他参数传递方式不同。applycall第一个参数如果传递的值为基本类型,JavaScript 会有一个装箱的操作,也就是把基本类型包装成它对应的对象形式。


    为了解决隐式绑定出现 this 丢失的问题,我们可以增加一个中间函数解决这个问题。

    var name = 'globalName';
    function getName() {
      console.log('this:%s,name:%s', this, this.name);
    }
    var bar = {
      name: 'barName',
    };
    function foo() {
      //强制将getName函数的this绑定到bar
      return getName.apply(bar);
    }
    foo();

    3.new 绑定

    使用 new 的方式创建的实例,this 指向这个实例(新创建的对象)。 使用 new 操作符创建的实例,经历 4 个步骤。

    1. 创建一个新对象
    2. 新对象的 proto 属性指向函数的原型
    3. 将构造函数的作用域赋给新对象(所以 this 是指向这个新对象)。执行构造函数中的代码,为这个新对象添加属性
    4. 如果函数没有返回其他对象,则返回这个新对象
      function Person(name, age) {
        this.name = name;
        this.age = age;
      }
      // 在这里可以把Person当作一个构造函数
      // 所以在new Person的时候就相当于把它的作用域指向了p
      // 所以Person中的this是指向p的
      const p = new Person();​

    4.默认绑定

    箭头函数本身不具备独立的this概念,其内部的this是由箭头函数所处的词法作用域来决定的。具体而言,箭头函数会继承外层函数的this绑定。

    要概括箭头函数中this的行为,可以考虑以下情况:

    1. 使用 new 生成对象: 如果箭头函数被new方式调用,它不会创建一个新的对象,也不会改变this的指向,而是继承外层函数的this
    2. 显式绑定(callapplybind等): 在箭头函数中,无论如何都无法改变其this的指向,因为箭头函数没有自己的this,它会继承外层函数的this
    3. 隐式绑定: 如果箭头函数在某个对象的方法中被调用,它会继承该方法调用时的上下文对象作为this
    4. 默认规则: 如果箭头函数不符合以上情况,它会继承外层函数的this,或者如果外层函数没有绑定this,则会继承其所处的最近一层词法作用域的this

    箭头函数的this绑定机制是静态的,它在定义时就确定了,不会随着调用方式的改变而改变。


这短短的一生,我们最终都会失去。