this

1. 默认绑定 (Default Binding)

这是最基础的规则。当函数独立调用,没有带任何修饰符(没有 new,没有 obj.,没有 call)时,应用此规则。

  • 规则:
    • 非严格模式:this 指向全局对象(浏览器中是 window,Node.js 中是 global)。
    • 严格模式 ('use strict'):this 指向 undefined。
js
12345
function sayHi() {
  console.log(this);
}

sayHi(); // window (非严格模式) / undefined (严格模式)

面试陷阱:这也是你之前做错的那道题的原因。const say = person.sayName; say(); 这里 say 是独立调用的,所以 this 丢了,变成了全局或 undefined。


2. 隐式绑定 (Implicit Binding)

当函数作为对象的方法被调用时,this 指向那个对象。

  • 规则:this 指向调用链中最后一层的对象。
JavaScript
12345678
const user = {
  name: 'Alice',
  greet: function() {
    console.log(this.name);
  }
};

user.greet(); // Alice -> user 调用了 greet

隐式丢失(常见坑):

如果把对象的方法赋值给一个变量,或者作为参数传入回调函数,隐式绑定会丢失,退化为默认绑定。

JavaScript
1234
const fn = user.greet;
fn(); // undefined (因为变成了独立调用)

setTimeout(user.greet, 100); // undefined (回调函数通常也是独立调用的)

3. 显式绑定 (Explicit Binding): call, apply, bind

这三个方法都在 Function.prototype 上,允许你强制指定函数的 this 指向。

A. call 和 apply

这两个方法非常相似,都会立即执行函数。区别仅在于传递参数的方式。

  • call(thisArg, arg1, arg2, ...):参数是一个接一个传的。
  • apply(thisArg, [argsArray]):参数是放在一个数组里传的。
JavaScript
1234567891011
function introduce(lang, hobby) {
  console.log(`${this.name} likes ${lang} and ${hobby}`);
}

const p1 = { name: 'Bob' };

// 使用 call:参数逗号隔开
introduce.call(p1, 'JS', 'Gaming'); // "Bob likes JS and Gaming"

// 使用 apply:参数放数组里
introduce.apply(p1, ['JS', 'Gaming']); // "Bob likes JS and Gaming"

记忆口诀:apply 对应 array(数组),call 对应 comma(逗号)。

B. bind

bind 不会立即执行函数,而是返回一个新的函数。这个新函数的 this 被永久地“锁死”(硬绑定)到了传入的第一个参数上。

  • 场景:常用于 React 类组件的事件处理,或者解决 setTimeout 中的 this 丢失问题。
JavaScript
1234567891011
const p2 = { name: 'Charlie' };

function sayName() {
  console.log(this.name);
}

const boundFn = sayName.bind(p2); // 只是绑定,没执行
boundFn(); // "Charlie"

// 即使之后再 call 别人,也改变不了 bind 的结果
boundFn.call({ name: 'Dave' }); // 依然是 "Charlie"

4. new 绑定 (New Binding)

当使用 new 关键字调用构造函数时,会发生以下步骤:

  1. 创建一个全新的空对象。
  2. 将这个对象的 [[Prototype]] 链接到构造函数的 prototype。
  3. 将函数内部的 this 指向这个新对象。
  4. 如果函数没有返回其他对象,则自动返回这个新对象。
JavaScript
123456
function Person(name) {
  this.name = name; // this 指向新创建的实例
}

const tom = new Person('Tom');
console.log(tom.name); // "Tom"

5. 特殊规则:箭头函数 (Arrow Function)

箭头函数没有自己的 this。它不遵循以上 4 条规则。

  • 规则:箭头函数的 this 完全取决于定义时(也就是写代码时)所处的外层(词法)作用域。
  • 特点:
    • 一旦确定,无法被 call、apply、bind 修改。
    • 常用于解决回调函数中 this 指向错误的问题。
JavaScript
123456789101112131415161718
const obj = {
  name: 'Arrow',
  // 定义时,外层作用域是全局(对象字面量不构成作用域)
  say: () => {
    console.log(this.name);
  },
  // 普通函数创造了一个作用域
  waitAndSay: function() {
    // 这里的箭头函数定义在 waitAndSay 内部
    // 所以它继承 waitAndSay 的 this (即 obj)
    setTimeout(() => {
      console.log(this.name); 
    }, 100);
  }
};

obj.say(); // undefined (指向全局)
obj.waitAndSay(); // "Arrow" (正确继承了 obj)

总结与优先级 (Priority)

如果同一个调用位置存在多种规则,谁说了算?优先级如下(从高到低):

  1. new 绑定 (最高)
  2. 显式绑定 (call, apply, bind)
  3. 隐式绑定 (obj.func())
  4. 默认绑定 (func()) (最低)

箭头函数是一个特例,它忽略所有绑定规则(除了 new,但箭头函数不能被 new),只认词法作用域。

一张表复习 call, apply, bind

特性callapplybind
立即执行?是是否 (返回新函数)
参数形式arg1, arg2, ...[arg1, arg2, ...]arg1, arg2, ...
用途借用方法、改变上下文处理数组参数 (如 Math.max)预设参数、回调函数绑定