题目 28 / 31

这四种 this 里,.call() 到底能覆盖哪几种?

知识点: this 绑定难度: 高级

普通方法、对象字面量里的箭头属性、以及两个"返回函数的函数",各自解析 this 的方式都不一样——其中一个甚至从来没有真正执行过 console.log。

使用执行可视化器解题在下方逐步观察每个绑定、函数调用和控制台输出。

预测输出结果

这段代码通过 person1 的四个方法一共发起了十次调用,但只有七次真正打印了内容。请按顺序预测这七行输出,并找出哪三次调用是"哑"的。

var name = "Window";

var person1 = {
    name: "Person1",
    foo1: function () {
        console.log(this.name);
    },
    foo2: () => console.log(this.name),
    foo3: function () {
        return function () {
            console.log(this.name)
        }
    },
    foo4: function () {
        return () => this.name
    }
}

var person2 = { name: "Person2" };

person1.foo1();
person1.foo1.call(person2);

person1.foo2();
person1.foo2.call(person2);

person1.foo3()();
person1.foo3.call(person2)();
person1.foo3().call(person2);

person1.foo4()();
person1.foo4.call(person2)();
person1.foo4().call(person2);
查看答案与完整解析

正确输出

Person1
Person2
Window
Window
Window
Window
Person2

为什么会这样

foo1 是一个普通方法。person1.foo1() 走隐式绑定(this = person1,输出 "Person1");person1.foo1.call(person2) 显式覆盖了接收者(输出 "Person2")。

foo2 是直接写在对象字面量属性值位置的箭头函数——它的 this 在对象字面量本身被求值的那一刻(也就是顶层作用域)就已经确定下来,此时 this 是 window。person1.foo2() 和 person1.foo2.call(person2) 都输出 "Window":箭头函数的 this 永远不能被覆盖,而且它一开始捕获的也根本不是 person1 的 this——箭头函数不是方法。

foo3 是一个普通方法,每次调用都返回一个全新的普通函数。person1.foo3()() 和 person1.foo3.call(person2)() 都是"裸调用"这个返回的函数,因此无论 foo3 本身是怎么被调用的,都走默认绑定——都输出 "Window"。person1.foo3().call(person2) 则是拿到返回的函数后,显式用 person2 调用它,输出 "Person2"。

foo4 看起来和 foo3 一样,但它返回的是一个箭头函数 —— () => this.name ——只是返回这个值而不是打印它。foo4 的三次调用(person1.foo4()()、.call(person2)()、.call(person2))都只是求出一个值然后立刻丢弃,因此没有任何一次会打印内容。

这道题考察什么

  • 普通方法的 this 是在每次调用时独立决定的——同一个方法在不同调用中可以绑定到不同的 this
  • 写在对象字面量属性值位置的箭头函数并不是"方法":它的 this 取决于对象字面量被求值时所在的作用域,而不是它最终挂在哪个对象上
  • call()/apply() 只对普通函数的 this 起作用;把它们用在箭头函数上语法上不会报错,但完全不起效果
  • 一个只 return 值而不调用 console.log 的函数不会产生任何可见输出,即便它确实执行了,its this 也确实被解析了

运行环境前提

假设是经典的非严格模式脚本,此时一次没有接收者的调用其 this 默认是全局对象(window),且对象字面量是在顶层作用域求值的。

规范参考

JavaScript 代码编辑器支持直接编辑或粘贴自定义代码
第 0 步 / 共 0 步
单步Space播放
按 → 单步 或 Space 自动播放
阶段 1: 编译预解析(变量提升与内存分配)
阶段 2: 代码执行(逐行赋值与调用求值)
单步执行原理解析

按键盘右方向键 “→” 进行单步调试,或按空格键 “Space” / 点击 “▶ 自动播放” 进行连续执行。