浅解 JavaScript 中的 this

113 阅读7分钟

浅解 JavaScript 中的 this

什么是 this

  • this 是 JavaScript 中的一个关键字,它指向当前代码执行时的“上下文”对象。简单来说,this 表示“谁在调用这个函数”或“这个函数属于哪个对象”。
  • 关键点this 的值不是固定的,它在运行时根据函数的调用方式动态决定。
  • ES6 影响:普通函数的 this 由调用上下文决定,而 ES6 引入的箭头函数(=>)的 this 固定为定义时外层作用域的 this(词法作用域)。
  • 目标:通过本篇内容,你将理解 this 的四种绑定规则、箭头函数的特殊行为,并通过示例和实践掌握其用法。

this 的四种绑定规则

JavaScript 中的 this 有四种主要绑定规则,优先级从高到低为:new 绑定 > 显式绑定 > 隐式绑定 > 默认绑定。以下逐一讲解,每种规则配多个示例。

1. 默认绑定(Default Binding)

  • 定义:当函数独立调用(不是作为对象的方法或通过其他绑定方式)时,this 指向全局对象(浏览器中是 window,Node.js 中是 global)。在严格模式("use strict")下,thisundefined

  • 场景:直接调用函数、函数作为回调但未指定上下文。

  • 示例

    1. 独立函数调用

      function sayHello() {
        console.log(this); // window(非严格模式)或 undefined(严格模式)
      }
      sayHello();
      
    2. 嵌套函数问题

      function outer() {
        function inner() {
          console.log(this); // window 或 undefined
        }
        inner();
      }
      outer();
      
    3. 严格模式

      "use strict";
      function test() {
        console.log(this); // undefined
      }
      test();
      
  • 注意

    • 默认绑定是最低优先级,常导致意外的 this 值。
    • 在非严格模式下,this 指向全局对象可能引发变量污染。
  • 实践:运行以上代码,观察 this 的值;在严格模式和非严格模式下对比输出。

2. 隐式绑定(Implicit Binding)

  • 定义:当函数作为对象的方法调用时,this 指向调用该方法的对象(即点号 . 前的对象)。
  • 场景:对象方法调用、链式调用。
  • 示例
    1. 对象方法
      const user = {
        name: "Alice",
        greet: function() {
          console.log(this.name);
        }
      };
      user.greet(); // Alice(this 指向 user)
      
    2. 链式调用
      const app = {
        config: {
          setting: "dark",
          show: function() {
            console.log(this.setting);
          }
        }
      };
      app.config.show(); // dark(this 指向 app.config)
      
    3. 丢失隐式绑定
      const user = {
        name: "Bob",
        greet: function() {
          console.log(this.name);
        }
      };
      const fn = user.greet; // 提取方法
      fn(); // window.name 或 undefined(丢失 user 上下文,变为默认绑定)
      
  • 注意
    • 隐式绑定依赖调用时的对象。如果方法被单独提取(如赋值给变量),this 会丢失,退化为默认绑定。
    • 常见于回调函数(如 setTimeout 或事件监听器)中。
  • 实践:创建一个对象,调用其方法,观察 this;将方法赋值给变量后调用,验证 this 丢失。

3. 显式绑定(Explicit Binding)

  • 定义:通过 callapplybind 方法显式指定函数的 this 值。
  • 场景:需要强制设置 this,如借用方法或修复上下文。
  • 方法说明
    • call(thisArg, arg1, arg2, ...):立即调用函数,指定 this 和参数。
    • apply(thisArg, [arg1, arg2, ...]):立即调用函数,参数以数组传递。
    • bind(thisArg):返回新函数,this 固定为指定值。
  • 示例
    1. 使用 call
      function greet() {
        console.log(this.name);
      }
      const user = { name: "Alice" };
      greet.call(user); // Alice(this 强制绑定到 user)
      
    2. 使用 bind
      const user = { name: "Bob" };
      const boundGreet = function() {
        console.log(this.name);
      }.bind(user);
      boundGreet(); // Bob(this 固定为 user)
      
    3. 修复丢失的 this
      const user = {
        name: "Charlie",
        greet: function() {
          console.log(this.name);
        }
      };
      setTimeout(user.greet, 1000); // window.name 或 undefined
      setTimeout(user.greet.bind(user), 1000); // Charlie(绑定 user)
      
  • 注意
    • bind 创建新函数,callapply 立即调用。
    • 显式绑定优先级高于隐式绑定。
  • 实践:用 callbind 修复一个回调函数的 this 丢失问题;尝试用 apply 传递数组参数。

4. new 绑定

  • 定义:当函数作为构造函数通过 new 调用时,this 指向新创建的对象。
  • 场景:创建对象实例,面向对象编程。
  • 示例
    1. 基本构造函数
      function User(name) {
        this.name = name;
        console.log(this); // User { name }
      }
      const user = new User("Alice"); // this 指向新对象
      
    2. 结合对象方法
      function Car(model) {
        this.model = model;
        this.show = function() {
          console.log(this.model);
        };
      }
      const myCar = new Car("Toyota");
      myCar.show(); // Toyota(this 指向 myCar)
      
  • 注意
    • new 绑定的优先级最高。
    • 构造函数通常以大写字母开头(如 User),以示区分。
  • 实践:创建一个构造函数,实例化对象并调用方法,观察 this 的值。

ES6 箭头函数与 this

  • 定义:箭头函数(=>)不绑定自己的 this,而是继承定义时外层作用域的 this(词法作用域)。
  • 用途
    • 适合需要固定 this 的场景,如回调函数(mapsetTimeout)。
    • 不适合需要动态 this 的场景,如对象方法或事件监听器。
  • 关键点
    • 箭头函数的 this 在定义时确定,无法通过 callapplybind 改变。
    • 没有自己的 argumentsprototype,不能用作构造函数。
  • 示例
    1. 固定 this
      const user = {
        name: "Alice",
        greet: function() {
          setTimeout(() => console.log(this.name), 1000); // Alice(继承 user 的 this)
        }
      };
      user.greet();
      
    2. 对比普通函数
      const user = {
        name: "Bob",
        greet: function() {
          setTimeout(function() {
            console.log(this.name); // window.name 或 undefined
          }, 1000);
        }
      };
      user.greet();
      
    3. 嵌套箭头函数
      const group = {
        title: "Team",
        members: ["Alice", "Bob"],
        show: function() {
          this.members.forEach(member => {
            console.log(`${this.title}: ${member}`); // Team: Alice, Team: Bob
          });
        }
      };
      group.show();
      
    4. 箭头函数误用
      const button = {
        text: "Click me",
        handleClick: () => {
          console.log(this.text); // undefined(this 指向 window/global)
        }
      };
      button.handleClick();
      
  • 注意
    • 箭头函数适合回调函数,但不适合定义对象方法或事件处理函数。
    • 不能用 new 调用箭头函数:
      const Arrow = () => {};
      new Arrow(); // TypeError: Arrow is not a constructor
      

常见问题与解答

  1. :为什么 this 在不同场景下值不同?
    • this 的值由函数调用方式决定,遵循四种绑定规则(new > 显式 > 隐式 > 默认)。
  2. :如何避免 this 丢失?
      • 使用 bind 固定 this
        const user = { name: "Alice", greet: function() { console.log(this.name); } };
        setTimeout(user.greet.bind(user), 1000); // Alice
        
      • 使用箭头函数继承外层 this
        const user = { name: "Alice", greet: function() { setTimeout(() => console.log(this.name), 1000); } };
        user.greet(); // Alice
        
      • this 保存到变量:
        const user = {
          name: "Alice",
          greet: function() {
            const self = this;
            setTimeout(function() { console.log(self.name); }, 1000);
          }
        };
        user.greet(); // Alice
        
  3. :箭头函数什么时候不该用?
    • :不适合需要动态 this 的场景,如对象方法或事件监听器:
      const obj = {
        value: 42,
        getValue: () => console.log(this.value) // undefined(this 指向 window/global)
      };
      obj.getValue();
      
  4. :如何调试 this 的值?
      • 在函数中添加 console.log(this) 检查。
      • 使用浏览器开发者工具(F12),设置断点观察 this
      • 示例:
        function test() {
          console.log(this);
        }
        test(); // 检查输出
        

记忆技巧

  • 类比:把 this 想象成“电话的接听者”:
    • 默认绑定:没人接听,电话响在全局(windowundefined)。
    • 隐式绑定:对象是“主人”,this 指向它(点号前的对象)。
    • 显式绑定:你强行指定接听者(callapplybind)。
    • new 绑定:新建一个“接听者”对象(新实例)。
    • 箭头函数:像“录音电话”,固定指向定义时的主人(外层 this)。
  • 口诀this 看调用,对象点号优先,new 最强,箭头锁定外层。

实践建议

  1. 测试不同场景
    • 写一个对象,包含普通函数和箭头函数方法,对比 this 的输出:
      const obj = {
        name: "Test",
        normal: function() { console.log(this); },
        arrow: () => console.log(this)
      };
      obj.normal(); // { name: "Test", ... }
      obj.arrow(); // window 或 undefined
      
    • setTimeout 测试普通函数和箭头函数的 this 差异:
      const user = {
        name: "Alice",
        greet: function() {
          setTimeout(function() { console.log(this); }, 1000); // window 或 undefined
          setTimeout(() => console.log(this), 1000); // user
        }
      };
      user.greet();
      
  2. 修复 this 问题
    • 找一段丢失 this 的代码(如回调函数),用 bind 或箭头函数修复:
      const user = {
        name: "Bob",
        greet: function() {
          setTimeout(this.greet.bind(this), 1000); // Bob
        }
      };
      user.greet();
      
  3. 小项目
    • 写一个计数器对象,包含 increment 方法,用普通函数和箭头函数实现,对比 this 行为:
      const counter = {
        count: 0,
        increment: function() {
          setTimeout(() => {
            this.count++;
            console.log(this.count);
          }, 1000);
        }
      };
      counter.increment(); // 1
      
  4. 社区互动
    • 在 X 平台搜索 #JavaScript 或 #this,查看开发者分享的 this 相关问题。
    • 分享你的代码(如上面的计数器),求反馈或提问。

综合示例:结合 ES6 特性

以下是一个综合示例,展示 this 在普通函数、箭头函数和对象中的行为:

const team = {
  name: "Developers",
  members: ["Alice", "Bob"],
  showMembers: function() {
    // 普通函数:this 指向 team
    console.log(`Team: ${this.name}`);
    // forEach 回调用箭头函数,this 继承 team
    this.members.forEach(member => {
      console.log(`${this.title}: ${member}`);
    });
  },
  showLater: function() {
    // 普通函数在 setTimeout 中丢失 this
    setTimeout(function() {
      console.log(this.name); // undefined 或 window.name
    }, 1000);
    // 箭头函数保留 this
    setTimeout(() => console.log(this.name), 1000); // Developers
  }
};
team.showMembers();
// 输出:
// Team: Developers
// Developers member: Alice
// Developers member: Bob
team.showLater(); // Developers(箭头函数)

总结

  • this 的核心:由函数调用方式决定,遵循四种绑定规则(new > 显式 > 隐式 > 默认)。
  • 箭头函数:固定 this 为外层作用域,适合回调,不适合动态 this 场景。