题目:请说出以下代码的输出结果,并解释原因。
var name = 'Global';
const person = {
name: 'Alice',
hobbies: ['Reading', 'Coding'],
showHobbiesRegular: function() {
this.hobbies.forEach(function(hobby) {
console.log(this.name + ' likes ' + hobby);
});
},
showHobbiesArrow: function() {
this.hobbies.forEach((hobby) => {
console.log(this.name + ' likes ' + hobby);
});
},
showThis: () => {
console.log('This in showThis is: ', this);
}
};
// 执行以下代码,输出结果是什么?
person.showHobbiesRegular();
console.log('---');
person.showHobbiesArrow();
console.log('---');
person.showThis();
期望的回答与解析
候选人应该回答出的预期输出:
-
person.showHobbiesRegular();输出:undefined likes Reading undefined likes Coding(或者在非严格模式下,可能会输出
Global likes ...,这取决于环境。关键点是this.name不是'Alice') -
person.showHobbiesArrow();输出:Alice likes Reading Alice likes Coding -
person.showThis();输出:This in showThis is: { ... } // 指向全局对象(浏览器中是Window)或全局作用域(Node.js中)
深入解析与评分标准
一个优秀的候选人应该能清晰地解释 “this”的绑定规则 和 箭头函数的特点。
1. 对 showHobbiesRegular 方法的解析
-
关键点:
forEach方法中使用的回调函数是一个普通函数。 -
规则:普通函数的
this指向取决于它的调用方式。当一个普通函数被作为回调函数传递,而不是作为对象的方法直接调用时,它的this会丢失原本的绑定。 -
具体分析:
person.showHobbiesRegular()是通过person对象调用的,所以在这个方法内部,this正确指向person对象。- 但是,在
forEach循环内部,传入了一个普通的匿名函数function(hobby) { ... }。这个函数是被 JavaScript 引擎直接调用的,而不是通过任何对象调用的(即obj.func()的形式)。 - 在这种“丢失绑定”的默认调用中,普通函数的
this在非严格模式下会指向全局对象(浏览器中为window,Node.js 中为global),在严格模式下则为undefined。 - 题目最外层使用
var声明了var name = 'Global';,var声明的全局变量会成为全局对象的属性。所以在非严格模式下,this.name会找到全局对象的name属性,即'Global'。但在很多现代环境(如 ES模块、Class 或使用了‘use strict’的代码块)中,默认是严格模式,this为undefined,访问undefined.name会抛出错误。题目中为了不复杂化,输出undefined是可接受的答案。
-
加分项:候选人可以提到如何修复普通函数的问题(例如使用
.bind(this),或者将this保存在一个变量里(通常叫that或self))。
2. 对 showHobbiesArrow 方法的解析
-
关键点:
forEach方法中使用的回调函数是一个箭头函数。 -
规则:箭头函数没有自己的
this绑定。它的this值由外层(函数或全局)作用域决定。 -
具体分析:
- 箭头函数
(hobby) => { ... }被定义在showHobbiesArrow这个普通函数内部。 showHobbiesArrow是作为person对象的方法被调用的(person.showHobbiesArrow()),所以它的this正确指向person对象。- 因此,箭头函数回调体内的
this就继承自它的外层作用域(即showHobbiesArrow函数)的this,也就是person对象。所以this.name正确地为'Alice'。
- 箭头函数
-
加分项:候选人可以强调箭头函数的
this是“词法作用域”的,它在定义时就已经确定了,而不是在调用时确定的。
3. 对 showThis 方法的解析
-
关键点:
showThis本身就是一个箭头函数,并且它是作为对象person的方法定义的。 -
规则:箭头函数的
this继承自它的外层作用域。 -
具体分析:
person对象是在全局作用域中定义的。- 因此,箭头函数
showThis的外层作用域就是全局作用域。 - 所以,无论你如何调用
person.showThis()(即使是用person这个对象去调用),它的this都指向全局作用域的this(浏览器中是window,Node.js 中是global或一个空对象{},取决于版本和模块类型)。
-
陷阱:这道题是为了考察候选人是否理解对象字面量中的箭头函数并不会因为它是对象的方法而自动绑定该对象。这是一个非常常见的误区。
总结性提问(如果候选人回答得很好,可以进一步追问)
“所以你能否总结一下,在哪些场景下使用箭头函数更合适,哪些场景下使用普通函数更合适?”
-
期望回答:
- 箭头函数适用:需要固定
this指向的场景,如回调函数(定时器、事件处理器、map/forEach等)、需要继承外层this时。 - 普通函数适用:需要动态
this的场景(如作为对象的方法)、需要使用new关键字作为构造函数时、需要使用arguments对象时。
- 箭头函数适用:需要固定
通过这道题,可以很好地考察面试者对 JavaScript 中 this 机制的理解深度,特别是对两种函数区别的掌握程度。