關於 hoisting
這篇針對常見跟面試可能會出現的問題紀錄一下 JavaScript 的特產 hoisting。
最基本的,在 JavaScript 裡如果試圖對一個尚未宣告的變數取值,那麼你會得到以下錯誤:
console.log(x); // ReferenceError: x is not defined
var
但如果你這樣寫:
console.log(x); // undefined
var x;
得到的卻是 undefined
。
程式是一行一行由上往下執行的,出現這樣的結果應該挺令人驚訝的,但簡單來講這就是 hoisting。
可以把上面那段程式碼想像成這樣:
var x;
console.log(x); // undefined
我們只能用想像的,因為程式碼本身並不會直接被移動。
- 只有變數的宣告會提升,賦值不會:
console.log(x); // undefined
var x = 100;
這段程式碼我們可以想像成這樣:
var x;
console.log(x); // undefined
x = 100;
JavaScript 實際在執行程式時,會把 var x = 100;
分成兩個步驟
宣告變數
x
賦值
100
給變數x
- 重複宣告:
var x = 100;
var x;
console.log(x); // 100
對於重複宣告,x
的值依然是 100
,我們可以這樣想像:
var x;
var x;
x = 100;
console.log(x); // 100
這樣就很好懂了,先別管為什麼 JavaScript 這麼機車,但你總要瞭解的對吧。
- 變數與函數的優先級
console.log(x); // ƒ x() {}
var x;
function x() {}
console.log(x); // ƒ x() {}
function x() {}
var x;
最後一個,無論上面的程式碼順序怎麼擺,你可以看到 function
的宣告提升會優先於變數的宣告提升。
let & const
在 ES6 多了兩個新的變數宣告的關鍵字,let
與 const
,彼此的行為大同小異,我們以 let
為例:
console.log(x); // undefined
console.log(y); // Cannot access 'y' before initialization
var x;
let y;
與 var
不同在於:
let
與const
屬於區塊作用域;var
則是函數作用域在
let
與const
宣告前就對變數進行訪問的話會拋出ReferenceError
錯誤;var
則是會得到undefined
由 let
或 const
宣告的變數,當它們的詞法環境被實例化時會被創建,但只有在變數的詞法綁定已經被求值運算後才能夠被訪問。
也就是說當程式進入新的作用域(function,block,module)時,作用域內如果有經由 let
或 const
宣告的變數,該變數會先在作用域中被創建,但此時還未對宣告語句進行求值運算,所以該變數不得被訪問,硬要訪問就會像上面一樣拋出錯誤。
所以 let
與 const
也有提升,只是拋出的訊息與 var
不同,var
還會給你一個 undefined
的值,但 let
與 const
很明顯的直接給你一個錯誤訊息,且這是一個在運行期間才會得到的錯誤訊息。
重點在於以 let
或 const
宣告的變數或常數,必須經過對宣告的賦值語句進行求值後,才算初始化完成,創建期間並不算初始化。
我們看看這個例子:
let x = 'outer';
(function() {
// 在這裡 x 的詞法環境被創建,但還未對賦值語句進行求值所以不算初始化完成
console.log(x); // Cannot access 'x' before initialization
let x = 'inner';
})();
很明顯與 var
不同在於,let
與 const
直接不允許你在宣告變數前訪問該變數,如果你這麼做那麼在 run time 會直接噴錯,如果上面的 let
改成 var
那麼 console
出來的就是 undefined
。也算是養成一個良好的寫 code 習慣。