Day16 | JS的Hoisting提升與執行環境
❐ 提升 Hoisting
關於提升
- 在創造環境把記憶體空間準備好,此流程稱為提升 Hoisting。
- 透過
let
宣告的變數不會有 hoisting 變數提升的特性只有類似的概念,var
宣告的變數才會,下方會有範例。let
一樣有創造階段和執行階段,類似 Hoisting 提升的概念,只是它在提升時不會賦予變數undefined
的值,而是出現「 暫時性死區 TDZ 」,這個暫時性死區無法存取這個變數而出現錯誤訊息Uncaught ReferenceError: Cannot access 'Ming' before initialization
。
- 在創造階段函式優先。( 優先只適用於透過函式宣告方式定義的函式,如下述函數陳述式 Function statement )。
- ( 詳細解說請看 「 Kuro - 008 天重新認識 JavaScript 」2-42 頁 )
1 | // -----透過函式宣告 |
創造環境中變數與函式差異
➊ 函式陳述式 Function statement :
在創造階段,會把記憶體空間與整個函式的內容都準備好並載入進來。所以函式在創造階段就已經可以運行。範例
1 | callName(); |
➋ 變數 :
- 在創造階段變數是不會賦予值,只會先有一個記憶體空間。
- 函式表達式 ( Funciton expression ) 創造階段與變數一樣會先提出變數給予記憶體空間,下方有範例。
❗注意 : 這邊 let
宣告的變數不會有變數提升的特性,有變數提升特性的只有 var
宣告的變數。( 詳細解說請看 「 Kuro - 008 天重新認識 JavaScript 」 2-38 ~ 2-40 頁 )
1 | console.log(Ming); |
1 | console.log(Ming); |
1 | // 函式表達式( Funciton expression ) |
❐ Hoisting 分兩階段
- 創造環境。
- 在創造環境把記憶體空間準備好,此流程稱為提升 Hoisting。
- 執行。
➊ 創造環境
創造環境階段,會把程式碼裡面所有變數挑出來,在記憶體上面分別給它們空間,一直到執行階段才會把這些程式碼依序執行,並且賦予它值。
範例 1
- 記憶體是一對的,左方是
key
右方是值
。在 hoisting 創造環境這階段時,記憶體會先放入 key 。 - 宣告變數時會先把 a 放到記憶體左邊位置,但還不會給它值。所以這時去檢查它時值會出現
undefined
,直到執行時才會把值套用上來。
/
1 | var Ming = '小明'; |
上方程式碼執行時會分為兩個步驟 :
- 會先宣告這個變數
var Ming;
。 - 把小明的值賦予到
Ming
。 - 此現象稱為提升。
範例 2
創造階段
- 準備好所有變數的記憶體空間,因為未到執行階段所以未賦予值,值目前呈現
undefined
。 - 在創造環境把記憶體空間準備好,此流程稱為提升 Hoisting。
➋ 創造階段
- 函式陳述式 ( Function statement ) 在創造階段就會優先載入。與變數宣告 ( 像 Function expression ) 有些不同。
- 上方圖解案例中,創造階段變數與函式差異 :
- 變數 : 在創造階段變數 a 是不會賦予值,只會先有一個記憶體空間。
- 函式 : 函式在創在階段,會把記憶體空間與整個函式的內容都準備好並載入進來。所以函式在創造階段就已經可以運行。
函式陳述式範例
1 | callName(); |
拆解:
- 函式在創造階段就已經把記憶體和函式準備好了,所以
callName();
移到最上方是不會出錯的。 function callName()
會被移至上方。
1 | // 創造階段 |
答案: 會印出 呼叫小明
函式表達式 範例
1 | // 可正確呼叫 |
解說 :
函式表達式創造階段與變數一樣會先提出變數給予記憶體空間。
在執行階段,才把 function 賦予到變數 callName 上。要運行這段函式必須在函式賦予到值上才能運行此段函式。
1
2
3
4
5
6
7
8// 創造階段
var callName;
// 執行階段
callName = function () {
console.log('呼叫小明');
}
callName();
函式表達式 + 函式陳述式- 範例 1
1 | // 題目 |
解說 :
在創造階段函式優先,所以函式先被提至最上面 → 才換變數往前移 → 並賦予值的動作。
1
2
3
4
5
6
7
8
9
10
11// 創造階段
function callName() {
console.log('呼叫小明 1');
}
var callName;
// 執行階段
callName = function () {
console.log('呼叫小明 2');
}
callName();在執行階段,變數
callName
重新被另一個函式function() { console.log('呼叫小明 2');}
給覆蓋掉,所以在執行callName();
會出現呼叫小明 2
。- 無論是函式表達式
var callName = function(){}
在前或是函式陳述式function callName()
在前,都會印出呼叫小明 2
。
- 無論是函式表達式
答案:呼叫小明 2
函式表達式 + 函式陳述式- 範例 2 ( 延續 1 )
1 | // 題目 |
拆解 :
提升順序 : 函式 → 變數 → 賦予值。
1
2
3
4
5
6
7
8
9
10
11// 創造階段
function callName() {
console.log('呼叫小明 1');
}
var callName;
// 執行階段
callName();
callName = function () {
console.log('呼叫小明 2');
}創造階段先宣告函式,定義變數 callName 為空值。執行
callName();
這個函式取得console.log('呼叫小明 1');
。
答案:呼叫小明 1'
函式陳述式- 範例 3
1 | callName(); |
答案
答案為
undefined
。先把創造階段與執行階段分開。提出函式 → 提出變數 → 賦予值。
1
2
3
4
5
6
7
8// 創造
function callName() {
console.log(Ming);
}
var Ming;
// 執行
callName();
Ming = '小明';創造階段就先宣告函式,定義這個變數為空值。呼叫
callName();
這個函式目前所取的值是全域的Ming
,目前這個變數Ming
還未被賦予值,答案就為undefined
。
參考資訊
- 六角學院 - JavaScript 核心篇