JavaScript this 指南
深入理解 JavaScript 中的 this
从调用点出发,讲清楚 JavaScript 如何决定 this 指向——默认绑定、隐式绑定、显式绑定与 new 绑定,配有可运行示例。
this 不取决于函数写在哪里。它在每次调用时都会重新决定,完全取决于这次调用所用的语法。同一个函数每次运行都可能得到不同的 this。
这篇文章按照你应该检查的顺序,依次讲解各条绑定规则,最后讲箭头函数——唯一一种 this 表现完全不同的情况。
一句话回答
- 每次调用时只问一个问题:函数名前面紧跟着的是什么?这个答案就决定了谁绑定了 this。
- call 和 apply 会立即以显式指定的 this 调用函数;bind 不会调用任何东西,只是返回一个永久绑定了该 this 的新函数。
- new 绑定的优先级高于以上所有情况:用 new 构造总会创建一个全新对象并把 this 绑定到它上面。
1. 默认绑定:函数名前面没有任何对象
像 greet() 这样的裸调用不提供任何接收者。非严格模式下 this 会回退到全局对象;严格模式下则保持 undefined。这是最容易让人意外的一条规则,因为即使函数原本是从某个对象上取出来的,这条规则依然生效。
裸调用前面什么都没有,因此默认绑定生效
function greet() {
console.log(this);
}
greet(); // 非严格模式下是全局对象,严格模式下是 undefined2. 隐式绑定:调用前面紧跟着一个对象
obj.method() 会把 obj 作为 this 提供出来——这就是隐式绑定,也是日常代码最依赖的一条规则。
一旦这个方法被复制到一个裸变量里、并在没有对象在前的情况下被调用,隐式绑定就不再适用,取而代之的是默认绑定。
同一个 greet 函数,被拆下来后就丢失了接收者
const person = {
name: "Ada",
greet() {
console.log(this.name);
},
};
person.greet(); // "Ada" —— 隐式绑定
const greet = person.greet;
greet(); // undefined —— 默认绑定,接收者已经丢失3. 显式绑定:call、apply 和 bind
call 和 apply 都可以让你直接指定 this 并立即调用函数;它们的区别只在于剩余参数的传递方式(逐个传入 vs 以数组形式传入)。
bind 不会调用任何东西。它会返回一个全新的函数,并把 this 永久锁定为你提供的值——之后即使用不同的接收者调用这个返回的函数也不会有任何效果。
call 立即调用;bind 只返回一个新函数
function show() {
console.log(this.id);
}
const target = { id: 42 };
show.call(target); // 42
const bound = show.bind(target);
bound(); // 42 —— 已经锁定,之后无论怎么调用 bound 都不会变4. new 绑定的优先级高于以上所有规则
用 new 调用一个函数会触发 Construct 操作:创建一个全新对象,把它链接到函数的 prototype 上,并在这次调用中把它绑定为 this——无论这个函数在其他情况下本来会如何解析 this。
new 总会把 this 绑定到新创建的实例上
function Person(name) {
this.name = name;
}
const ada = new Person("Ada");
console.log(ada.name); // "Ada"5. 箭头函数不遵循以上任何一条规则
箭头函数没有属于自己的绑定规则。它会在创建时永久捕获外层作用域当时已经存在的 this——call、apply、bind,甚至 new 都无法覆盖它。
这让箭头函数的表现更像是“捕获了 this 的闭包”,而不是“拥有自己绑定规则的函数”——具体是如何体现的,可以参考闭包指南以及 this + 闭包可视化器。