`this` 绑定规则与四种场景

第二编 · 第三章:`this` 绑定规则与四种场景的深入分析


this 是什么

JavaScript 的 this 是一个让很多人困惑的概念。但它的本质其实很简单:this 是函数被调用时的执行上下文对象

理解 this 的关键是:不要问"什么是 this",而要问"this 在哪里被绑定的"。this 的值取决于函数被调用的方式,而不是函数定义的位置。

JavaScript 有四种 this 绑定的场景。


场景一:默认绑定

当函数独立调用,不带任何修饰时,this 指向全局对象(在浏览器里是 window,在 Node.js 里是 global)。

function foo() {
  console.log(this); // 在浏览器里是 window
}

foo(); // 默认绑定

但注意,严格模式下'use strict'),全局对象不是 this 的默认值,而是 undefined

'use strict';
function foo() {
  console.log(this); // undefined
}
foo();

这是为什么在严格模式下,如果 thisundefinedwindow 不符合预期,代码可能会报错。


场景二:隐式绑定

当函数作为对象的属性被调用时,this 指向这个对象。

const obj = {
  name: 'obj',
  sayName() {
    console.log(this.name);
  }
};

obj.sayName(); // 输出 'obj',因为 this 指向 obj

关键是看函数被调用时"前面有没有点"。如果有,这个点前面的对象就是 this 的值。

但有一个常见的陷阱:

const obj = {
  name: 'obj',
  sayName() {
    console.log(this.name);
  }
};

const sayName = obj.sayName; // 把函数提取出来
sayName(); // 输出 undefined(严格模式)或报错(非严格模式下 this 是 window)

sayName 被提取出来赋值给全局变量后,调用 sayName() 时前面没有点,所以 this 不再指向 obj,而是指向全局对象。


场景三:显式绑定

JavaScript 提供三种方法可以显式绑定 thiscallapplybind

callapply

const obj1 = { name: 'obj1' };
const obj2 = { name: 'obj2' };

function introduce(greeting) {
  console.log(greeting + ', I am ' + this.name);
}

introduce.call(obj1, 'Hello'); // 输出 'Hello, I am obj1'
introduce.apply(obj2, ['Hi']);   // 输出 'Hi, I am obj2'

两者的区别是参数格式:call 接受逗号分隔的参数列表,apply 接受一个参数数组。

bind

bind 创建一个新函数,this 被永久绑定到指定值:

const obj = { name: 'obj' };

function introduce(greeting) {
  console.log(greeting + ', I am ' + this.name);
}

const boundIntroduce = introduce.bind(obj);
boundIntroduce('Hello'); // 输出 'Hello, I am obj'
boundIntroduce('Hi');     // 输出 'Hi, I am obj'

一旦用 bind 绑定了 this,无论怎么调用都会使用绑定的值。这是创建"预设 this"函数的常用方式。


场景四:new 绑定

当用 new 关键字调用构造函数时,this 指向新创建的实例对象。

function Person(name) {
  this.name = name;
}

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

new 操作符做了四件事:

  1. 创建一个新对象
  2. 把构造函数的 this 绑定到新对象
  3. 执行构造函数(给新对象添加属性)
  4. 返回新对象

所以在构造函数里,this 指向新创建的实例。


箭头函数的 this

箭头函数不遵循这四种绑定规则。箭头函数的 this 继承自定义时所在的词法环境

const obj = {
  name: 'obj',
  // 普通函数,作为对象的方法
  sayName() {
    console.log(this.name);
  },
  // 箭头函数
  sayNameArrow: () => {
    console.log(this.name); // this 继承外层的全局 this
  }
};

obj.sayName();       // 输出 'obj'
obj.sayNameArrow();  // 输出 undefined(严格模式)或 window.name(非严格模式)

因为箭头函数的 this 是词法绑定的,它不能被 callapplybind 重新绑定。

const obj = { name: 'obj' };

const arrowFn = () => {
  console.log(this.name);
};

arrowFn.call(obj); // 仍然输出 undefined,不受 call 影响

这让箭头函数在某些场景下非常方便(如回调函数里需要保持外层的 this),但在另一些场景下会导致问题(如对象方法)。


常见问题和面试题

问题一:setTimeout 里的 this

const obj = {
  name: 'obj',
  delayedName() {
    setTimeout(function() {
      console.log(this.name); // 输出 undefined 或 window.name
    }, 100);
  }
};

setTimeout 回调函数是独立调用的(前面没有点),所以 this 指向全局对象,而不是 obj

解决方式:

// 方式一:保存 this 引用
delayedName() {
  const self = this;
  setTimeout(function() {
    console.log(self.name);
  }, 100);
}

// 方式二:使用箭头函数
delayedName() {
  setTimeout(() => {
    console.log(this.name); // 箭头函数的 this 继承 delayedName 的 this
  }, 100);
}

// 方式三:使用 bind
delayedName() {
  setTimeout(function() {
    console.log(this.name);
  }.bind(this), 100);
}

问题二:类方法里的 this

在 class 语法里,方法的 this 也是动态绑定的:

class Button {
  constructor() {
    this.text = 'Click me';
  }

  handleClick() {
    console.log(this.text);
  }
}

const button = new Button();
const handler = button.handleClick;
handler(); // TypeError!因为 handler 调用时 this 是 undefined

常见解决方式是在构造函数里用 bind 绑定,或者在调用时用箭头函数:

// 方式一:bind
this.handleClick = this.handleClick.bind(this);

// 方式二:箭头函数定义
handleClick = () => {
  console.log(this.text);
};

// 方式三:调用时用箭头函数
<button onClick={() => this.handleClick()}>Click</button>

this 的判定优先级

如果一个调用同时满足多条规则,按优先级从高到低是:

  1. new 绑定(new Foo()
  2. 显式绑定(callapplybind
  3. 隐式绑定(obj.method()
  4. 默认绑定(独立调用)
function foo() {
  console.log(this);
}

const obj = { foo };

obj.foo();            // 隐式绑定,this = obj
obj.foo.call(obj2);   // 显式绑定优先级高于隐式,this = obj2
new obj.foo();        // new 绑定优先级最高,this = 新创建的对象

这一章想说的

this 的值取决于函数被调用的方式,不取决于定义位置。四种绑定规则决定了 this 的值:默认绑定(独立调用)、隐式绑定(对象方法调用)、显式绑定(call/apply/bind)、new 绑定。

箭头函数的 this 是词法绑定的,继承定义时的外层 this,不能被显式方法重新绑定。

理解 this 的关键是分析函数调用时的上下文,而不是盯着函数定义看。