题目重点:普通函数 this 取决于调用方式;箭头函数捕获创建时的词法 this。用方法提取和 call 说明差异。
题意与假设
这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。
- 训练代码为 ES module 严格模式;普通方法脱离对象直接调用时 this 为 undefined,访问 this.name 抛 TypeError。浏览器非严格全局调用可能表现不同。
- 箭头函数在 `person.arrowFactory()` 调用时创建并捕获工厂方法的 this,即 person;之后 `call` 不能改写该箭头函数的 this。
解题路线
- 先定位函数是普通 function/method 还是 arrow。普通函数看调用点:`obj.method()` 由 obj 提供 this;`const f=obj.method;f()` 已失去对象调用上下文;`f.call(other)` 明确指定 other。
- 箭头函数没有自己的动态 this,要向外找创建它的词法作用域;若箭头在普通方法中创建,普通方法那次调用的 this 会被捕获。
- 框架事件处理、定时器回调和类方法传参时,用显式 bind/箭头包装避免意外脱离,但别盲目把所有方法改箭头:原型共享、测试替换和内存成本不同。
手动走一遍
训练代码中 `person.regular()` 返回 Ada,提取后直接调用抛 TypeError,`extracted.call({name:'Grace'})` 返回 Grace;工厂产生的箭头即使 `.call({name:'Grace'})` 仍返回 Ada。
JavaScript 参考实现
下面的代码按上文假设实现,适合本地运行并追加自己的测试用例。
代码区可横向滚动;使用键盘时,先聚焦代码区再按方向键查看。
export function compareThisBinding() {
const person = {
name: "Ada",
regular() { return this.name; },
arrowFactory() { return () => this.name; },
};
const extracted = person.regular;
const arrow = person.arrowFactory();
let extractedError;
try { extracted(); } catch (error) { extractedError = error.name; }
return {
method: person.regular(),
explicit: extracted.call({ name: "Grace" }),
extractedError,
arrow: arrow.call({ name: "Grace" }),
};
}
复杂度
单次绑定判断 O(1);若每个实例都创建新的箭头函数,内存随实例数增长,原型方法通常可共享一份函数对象。
易错点与追问
容易失分的边界
- 说 this 永远指向函数定义处的对象。
- 忽略解构/回调传递造成方法脱离。
- 以为 call 或 bind 能改写箭头函数捕获的 this。
- 不声明 strict/module 环境就给脱离方法的固定输出。
继续深挖
- 把方法传给 setTimeout 会怎样?
- `bind` 后再 `call` 哪个 this 生效?
- 类实例方法传给 React 事件处理器如何绑定?
