JavaScript 中的 this指向

JavaScript 中的 this 关键字确实有点特别,它的值并非在函数定义时确定,而是在函数被调用时动态绑定的,取决于函数的调用方式和上下文。这意味着同一个函数在不同的调用场景下,this 的指向可能完全不同。

下面这个表格汇总了 this 的主要绑定规则,帮你快速建立整体概念:

绑定规则调用方式this 指向备注
默认绑定独立函数调用全局对象 (非严格模式) / undefined (严格模式)在浏览器中,全局对象是 window
隐式绑定作为对象的方法调用调用该方法的对象注意隐式丢失的情况
显式绑定使用 call, apply, bind 调用指定的对象bind 会返回一个绑定了 this 的新函数
new 绑定使用 new 关键字调用构造函数新创建的对象实例
箭头函数任何方式(自身无 this,从父作用域继承)定义时所在上下文的 this无法通过 call, apply, bind 改变

👆 表格中的内容是对 this 绑定规则的概要,接下来我们详细看看每种情况。

🔍 详解绑定规则与示例

1. 默认绑定(独立函数调用)

当函数独立调用,未关联任何对象时:

function showThis() {
  console.log(this);
}
showThis(); // 在浏览器中,非严格模式下指向 window 

严格模式 ('use strict') 下,此方式调用的函数其 thisundefined

2. 隐式绑定(作为对象方法调用)

当函数作为对象的方法被调用时,this 通常指向调用该方法的对象

const person = {
  name: 'Alice',
  greet: function() {
    console.log(`Hello, ${this.name}!`);
  }
};
person.greet(); // 输出 "Hello, Alice!" - this 指向 person 对象

注意隐式丢失:将对象的方法赋值给一个变量再调用,this 可能会指向全局对象(非严格模式)或 undefined(严格模式),而非原对象:

const greetFunc = person.greet;
greetFunc(); // 输出 "Hello, undefined!" (非严格模式下可能输出 "Hello, [全局对象的name]!")

3. 显式绑定(使用 call, apply, bind)

你可以使用 call, applybind 显式地设置函数内部的 this 值:

  • callapply:都会立即调用函数,并指定 this 和参数,主要区别在参数形式(call 参数逗号分隔,apply 参数为数组)。

    function introduce(age, city) {
      console.log(`I'm ${this.name}, ${age} years old, from ${city}.`);
    }
    const person1 = { name: 'Bob' };
    introduce.call(person1, 25, 'New York'); // "I'm Bob, 25 years old, from New York."
    introduce.apply(person1, [25, 'New York']); // 同上
    
  • bind:会创建一个新函数,该新函数的 this永久绑定到指定的对象,且之后无论以何种方式调用,其 this 都不会改变。

    const boundIntroduce = introduce.bind(person1, 30); // 可以预先绑定部分参数
    boundIntroduce('London'); // "I'm Bob, 30 years old, from London."
    

4. new 绑定(构造函数调用)

使用 new 关键字调用构造函数时,this 会绑定到新创建的对象实例上:

function Person(name) {
  this.name = name;
  this.sayName = function() {
    console.log(this.name);
  };
}
const person2 = new Person('Charlie');
person2.sayName(); // 输出 "Charlie" - this 指向新创建的 person2 实例

5. 箭头函数中的 this

箭头函数没有自己的 this,它会继承定义时所处外层(父级)词法作用域的 this,且无法通过 call, apply, bind 等方法改变:

const outerThis = this; // 假设此处是全局,outerThis 指向 window (浏览器环境)
const obj = {
  name: 'David',
  regularFunc: function() {
    console.log('Regular:', this.name); // 指向 obj
  },
  arrowFunc: () => {
    console.log('Arrow:', this.name); // 继承定义时的 this,可能指向 window
  }
};
obj.regularFunc(); // "Regular: David"
obj.arrowFunc();   // "Arrow: undefined" (若全局无 name) 或 "Arrow: [全局name]"

箭头函数常用于解决回调函数中 this 丢失的问题,例如在 setTimeout 或事件监听器中:

const obj = {
  value: 42,
  start: function() {
    // 传统做法,在闭包中保存 this 引用
    const that = this;
    setTimeout(function() {
      console.log(that.value); // 42
    }, 100);

    // 使用箭头函数,更简洁
    setTimeout(() => {
      console.log(this.value); // 42,箭头函数继承了 start 方法的 this(即 obj)
    }, 100);
  }
};
obj.start();

⚠️ 常见场景与陷阱

  1. 回调函数中的 this 丢失:将对象方法作为回调函数(如传递给 setTimeout, setInterval, 事件监听器、数组高阶函数等)传递时,容易发生 this 丢失,其不再指向原对象。
    解决方案:使用箭头函数、bind 提前绑定,或在闭包中保存 this 引用(常用变量名 that, self, _this)。

  2. DOM 事件处理函数中的 this:在通过 addEventListener 绑定的事件处理函数中,this 通常指向触发事件的 DOM 元素。但如果使用箭头函数,this 会继承定义时的上下文,可能不会指向 DOM 元素。

  3. 类中的 this:在 JavaScript 类中,方法和构造函数中的 this 通常指向类的实例。但若将类方法单独提取出来使用,可能会丢失 this 绑定。有时需要在构造函数中使用 bind 或使用类字段箭头函数来确保绑定。

🎯 优先级总结

当多种规则同时可能适用时,this 绑定的优先级从高到低一般为:
new 绑定 > 显式绑定 (call/apply/bind) > 隐式绑定 (方法调用) > 默认绑定 (独立函数调用)
箭头函数的 this 由词法作用域决定,不参与此优先级比较。

💡 最佳实践

  1. 理解调用上下文:始终注意函数是如何被调用的。
  2. 善用箭头函数:在需要继承外部 this 或避免 this 丢失的回调场景中,优先使用箭头函数。
  3. 必要时显式绑定:使用 bind 确保函数始终在正确的上下文中执行。
  4. 使用严格模式:严格模式 ('use strict') 下,意外的默认绑定会指向 undefined 而非全局对象,有助于发现错误。

理解 this 的关键在于分析函数的调用方式和调用位置。多练习、多思考不同场景下的指向,你会逐渐得心应手。

评论