【题目】普通函数和箭头函数的this指向的问题

22 阅读4分钟

题目:请说出以下代码的输出结果,并解释原因。

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();

期望的回答与解析

候选人应该回答出的预期输出:

  1. person.showHobbiesRegular(); 输出:

    undefined likes Reading
    undefined likes Coding
    

    (或者在非严格模式下,可能会输出 Global likes ...,这取决于环境。关键点是 this.name 不是 'Alice'

  2. person.showHobbiesArrow(); 输出:

    Alice likes Reading
    Alice likes Coding
    
  3. 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 机制的理解深度,特别是对两种函数区别的掌握程度。