Full Stack Intern · Frontend Onsite

Rippling 前端:对象方法脱离后 this 指向哪里

普通函数 this 取决于调用方式;箭头函数捕获创建时的词法 this。用方法提取和 call 说明差异。

题目重点:普通函数 this 取决于调用方式;箭头函数捕获创建时的词法 this。用方法提取和 call 说明差异。

题意与假设

这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。

  • 训练代码为 ES module 严格模式;普通方法脱离对象直接调用时 this 为 undefined,访问 this.name 抛 TypeError。浏览器非严格全局调用可能表现不同。
  • 箭头函数在 `person.arrowFactory()` 调用时创建并捕获工厂方法的 this,即 person;之后 `call` 不能改写该箭头函数的 this。

解题路线

  1. 先定位函数是普通 function/method 还是 arrow。普通函数看调用点:`obj.method()` 由 obj 提供 this;`const f=obj.method;f()` 已失去对象调用上下文;`f.call(other)` 明确指定 other。
  2. 箭头函数没有自己的动态 this,要向外找创建它的词法作用域;若箭头在普通方法中创建,普通方法那次调用的 this 会被捕获。
  3. 框架事件处理、定时器回调和类方法传参时,用显式 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 事件处理器如何绑定?
✓已复制微信号!