题目 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),且对象字面量是在顶层作用域求值的。