程式設計題:為什麼最後一個a是1不是5

山頭人漢波發表於2022-04-15

前段時間有人在知乎上邀請我回答一個問題:為什麼最後一個 a 是 1 不是 5?

題目如下:

console.log(a)
if (true) {
  a = 1
  function a() {}
  a = 5
  console.log(a)
}
console.log(a)

結果

我的第一反應是:undefined,5,5。估計和題主想的一樣

分析一波

假設沒有 if(true),即如下程式碼:

console.log(a)
a = 1
function a() {}
a = 5
console.log(a)
console.log(a)

那麼答案什麼?

a()、5、5

這解釋了兩個特性

  1. 變數、函式提升且函式的權重大於變數
  2. 在 a 沒有用 var 宣告時,a=XX 預設是用 var 來宣告

變數、函式提升方面的知識點在於:

變數會提升,函式也會提升,並且函式提升的優先順序大於變數,如下例:

console.log(a)
console.log(a())
var a = 1
function a() {
  console.log(2)
}
console.log(a)
var a = 3
a = 4
console.log(a)
console.log(a())

a()、2、1、4、a is not a function

回過頭來看這道題目

console.log(a)
if (true) {
  a = 1
  function a() {}
  a = 5
  console.log(a)
}
console.log(a)

if (ture) {} ,形成了作用域,鎖住了這片變數,function a(){} 無法逃逸。換句話說,只有 {} 塊級識別符號在,function a() {} 就被所在塊級作用域中,也就說在 if (ture) {} 這片塊級作用域下,它不會提升到全域性頂層,而是在 if(true){} 下,即程式碼執行時是這樣:

console.log(a)
if (true) {
  +function a() {};
  a = 1;
  -function a() {};
  a = 5;
  console.log(a);
}
console.log(a)

如果你在 a = 1 前列印 a,a 的值就是 function a(){}

所以這道題全域性環境下,沒有變數提升,寫在第一行的 console.log(a) 因為找不到 a,所以值為 undefined

進入 if(true) {} 中,function a(){} 函式提升,且權重最高,所以賦值之前的塊級作用域中的 a 為 function a() {}window.aundefined

程式碼執行到 function a() {} 後,塊級作用域中的 a 還是為 1,但是全域性變數 a 被賦值為 1

執行到 a = 5,傳統賦值,影響的是塊級作用域中的 a,而不會影響全域性變數 a,所以列印的第二個 console.log(a) 為 5,第三個 console.log(a) 為 1

那麼問題來了,為什麼一執行 function a(){},全域性變數 a 就被賦值為 1?

我陷入的沉思,後來在回答中發現雲補斷山回答了,說是

歷史原因,為了相容之前的 ES5 的語法,所以在規範規定了塊級作用域內函式宣告的一些行為,各個瀏覽器實現可能不一樣

簡單來說,在塊級作用域內的函式函式宣告,行為類似於 var ,都會在全域性作用域宣告一個同名變數(也就是 window 上掛一個同名的屬性,預設值是 undefined),因為 ES6 遇到塊級作用域,會基於塊級作用域建立 environment record,存放當前塊級作用域內的變數,所以這個函式宣告會提升到塊級作用域頂部(而非全域性作用域頂部)

ECMA262 目錄 B

我們學的 JavaScript 是 ECMAScript,但是我們把程式碼執行在瀏覽器上時就要按照瀏覽器的標準,瀏覽器裡會有一些私貨在,最經典的是 __proto__ ,倒逼 ECMA 採納。話說回來,按照這位仁兄的意思

// 因為 function a() 宣告過,所以全域性有個 window.a
console.log(a)
if (true) {
  // 宣告歸宣告,但是函式提升提升與作用域相關,所以提升至此塊級作用域頂部
  a = 1
  // 塊級作用域中的 a 被賦值為 1
  function a() {}
  // 原地爆炸,執行函式後,全域性 window.a 被賦值為塊級作用域中的 a
  a = 5
  // 塊級作用域中的 a 又被賦值為 5
  console.log(a)
}
console.log(a)

最詭異的是執行 function a() {} 後,全域性 window.a 被賦值且為塊級作用域中的 a

這個事情沒完!!

等等,我就說的玩玩的,如果工作中或面試中真遇到這類問題,我也許還是不會解。

太詭異了,這不是考題範圍(塊級作用域、函式提升、變數提升)

就這樣先吧

本文參與了 SegmentFault 思否徵文「如何“反殺”面試官?」,歡迎正在閱讀的你也加入。

相關文章