最難的 JavaScript 面試題解析

王大冶發表於2024-11-29
  • CSS技巧與案例詳解
  • vue2與vue3技巧合集
  • VueUse原始碼解讀

圖片

覺得自己的 JavaScript 功底還不錯?那來試試這道複雜的面試題吧!

下面是一段程式碼,請分析每一行的輸出,並解釋其背後的原因。


問題描述

以下是程式碼,預測輸出並說明邏輯:

function Foo() {
  this.value = 42;
}

Foo.prototype.getValue = function() {
  return this.value;
};

const obj1 = new Foo();
const obj2 = {
  value: 24,
  getValue: obj1.getValue
};

console.log(obj1.getValue());      // A
console.log(obj2.getValue());      // B

setTimeout(function() {
  console.log(obj1.getValue());    // C
  obj2.value = 100;
  console.log(obj2.getValue());    // D
}, 0);

Promise.resolve().then(() => {
  obj1.value = 84;
  console.log(obj1.getValue());    // E
});

console.log(obj1.getValue());      // F

分析與輸出

A:obj1.getValue()
console.log(obj1.getValue()); // A

解釋

  • obj1Foo 的例項,obj1.getValue() 呼叫的是原型上的 getValue 方法。
  • 方法中的 this 指向 obj1,返回 this.value
  • obj1.value 初始化為 42,因此輸出:

輸出42


B:obj2.getValue()
console.log(obj2.getValue()); // B

解釋

  • obj2.getValue 是直接引用了 obj1.getValue,但呼叫時透過 obj2.getValue()
  • 在 JavaScript 中,this 的繫結依賴呼叫的物件。在這裡,this 指向 obj2
  • obj2.value24,因此輸出:

輸出24


F:同步執行的 obj1.getValue()
console.log(obj1.getValue()); // F

解釋

  • 此處仍是 obj1.getValue() 的呼叫,且 obj1.value 尚未被非同步程式碼修改。
  • 因此輸出和 A 一樣,為:

輸出42


E:Promise 中的 obj1.getValue()
Promise.resolve().then(() => {
  obj1.value = 84;
  console.log(obj1.getValue()); // E
});

解釋

  • Promise 的回撥是微任務,在同步程式碼執行完後立即執行。
  • 回撥中將 obj1.value 修改為 84,隨後呼叫 obj1.getValue()
  • 因此此處返回的是更新後的值:

輸出84


C:setTimeout 中的 obj1.getValue()
setTimeout(function() {
  console.log(obj1.getValue()); // C
  obj2.value = 100;
  console.log(obj2.getValue()); // D
}, 0);

解釋

  • setTimeout 的回撥是宏任務,在同步程式碼和微任務都執行完後才會執行。
  • 此時,obj1.value 已被微任務修改為 84,呼叫 obj1.getValue() 返回的是修改後的值:

輸出84


D:setTimeout 中的 obj2.getValue()
console.log(obj2.getValue()); // D

解釋

  • setTimeout 的回撥中,obj2.value 被修改為 100
  • 呼叫 obj2.getValue()this 仍指向 obj2,因此返回的是更新後的值:

輸出100


完整輸出順序

  1. A42
  2. B24
  3. F42
  4. E84
  5. C84
  6. D100

背後的知識點

這道題涉及了 JavaScript 中多個高階概念,是對語言機制的一次全面考察:

  1. 原型繼承

    • obj1 呼叫了 Foo 建構函式,透過原型鏈繼承了 getValue 方法。
  2. 動態繫結的 this

    • this 的指向取決於函式的呼叫方式,而不是定義時的上下文。
  3. 事件迴圈與任務佇列

    • 同步程式碼優先執行,Promise 的微任務佇列緊隨其後,而 setTimeout 的回撥則在最後的宏任務佇列中執行。
  4. 值的動態修改

    • 不同任務(同步、微任務、宏任務)對變數的修改會影響之後的結果。

總結

這道題表面看起來是簡單的輸出預測,但實際上需要對 JavaScript 的事件迴圈、this 繫結規則和原型鏈有全面的理解。透過這類問題的深入分析,不僅可以提升程式碼閱讀能力,也能更自信地處理實際開發中的複雜場景。

首發於公眾號 大遷世界,歡迎關注。📝 每週一篇實用的前端文章 🛠️ 分享值得關注的開發工具 ❓ 有疑問?我來回答

本文 GitHub https://github.com/qq449245884/xiaozhi 已收錄,有一線大廠面試完整考點、資料以及我的系列文章。

相關文章