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
2
3
4
5
6
7
8
9
10
11
12
13
14
// -----透過函式宣告
// 會回傳 5
console.log(isTotal(5));
function isTotal(num) {
return num + num;
}

// -----函式運算子 Function expression
// 會顯示錯誤訊息 Uncaught ReferenceError
console.log(isCount(5));
var isCount= function (num) {
return num + num;
}

創造環境中變數與函式差異

函式陳述式 Function statement :

在創造階段,會把記憶體空間與整個函式的內容都準備好並載入進來。所以函式在創造階段就已經可以運行。範例

1
2
3
4
5
6
callName();
function callName() {
console.log('呼叫小明');
}

// ============= 印出 呼叫小明

變數 :

  • 在創造階段變數是不會賦予值,只會先有一個記憶體空間。
  • 函式表達式 ( Funciton expression ) 創造階段與變數一樣會先提出變數給予記憶體空間,下方有範例。

❗注意 : 這邊 let 宣告的變數不會有變數提升的特性,有變數提升特性的只有 var 宣告的變數。( 詳細解說請看 「 Kuro - 008 天重新認識 JavaScript 」 2-38 ~ 2-40 頁 )

1
2
3
4
5
console.log(Ming);
var Ming; // 創造階段
Ming = '小明'; // 執行階段

// ============= undefined
1
2
3
4
5
console.log(Ming);
let Ming;
Ming = '小明';

// ============= let宣告變數沒有提升特性,所以顯示錯誤訊息 ReferenceError
1
2
3
4
5
6
7
8
9
10
11
12
13
// 函式表達式( Funciton expression )
var callName = function () {
console.log('呼叫小明');
}
// =========== 拆解
// 創造階段
var callName;
// 執行階段
callName = function () {
console.log('呼叫小明');
}
callName();

❐ Hoisting 分兩階段

  1. 創造環境。
    • 在創造環境把記憶體空間準備好,此流程稱為提升 Hoisting。
  2. 執行。

創造環境

創造環境階段,會把程式碼裡面所有變數挑出來,在記憶體上面分別給它們空間,一直到執行階段才會把這些程式碼依序執行,並且賦予它值。

範例 1

出處:六角學院

  • 記憶體是一對的,左方是 key 右方是。在 hoisting 創造環境這階段時,記憶體會先放入 key 。
  • 宣告變數時會先把 a 放到記憶體左邊位置,但還不會給它值。所以這時去檢查它時值會出現 undefined,直到執行時才會把值套用上來。
    出處:六角學院

/

1
2
3
4
5
6
7
var Ming = '小明';
console.log(Ming);

// ========== 拆解
var Ming; // 創造階段
Ming = '小明'; // 執行階段
console.log(Ming);

上方程式碼執行時會分為兩個步驟 :

  1. 會先宣告這個變數 var Ming;
  2. 把小明的值賦予到 Ming
  3. 此現象稱為提升。

範例 2

出處:六角學院

創造階段

  1. 準備好所有變數的記憶體空間,因為未到執行階段所以未賦予值,值目前呈現 undefined
  2. 在創造環境把記憶體空間準備好,此流程稱為提升 Hoisting。

➋ 創造階段

圖解案例 ▲

  • 函式陳述式 ( Function statement ) 在創造階段就會優先載入。與變數宣告 ( 像 Function expression ) 有些不同。
  • 上方圖解案例中,創造階段變數與函式差異 :
    • 變數 : 在創造階段變數 a 是不會賦予值,只會先有一個記憶體空間。
    • 函式 : 函式在創在階段,會把記憶體空間與整個函式的內容都準備好並載入進來。所以函式在創造階段就已經可以運行

函式陳述式範例

1
2
3
4
callName();
function callName() {
console.log('呼叫小明');
}

拆解:

  1. 函式在創造階段就已經把記憶體和函式準備好了,所以 callName(); 移到最上方是不會出錯的。
  2. function callName() 會被移至上方。
1
2
3
4
5
6
// 創造階段
function callName() {
console.log('呼叫小明');
}
// 執行
callName();

答案: 會印出 呼叫小明

函式表達式 範例

1
2
3
4
5
6
7
8
9
10
11
// 可正確呼叫
var callName = function () {
console.log('呼叫小明');
}
callName();

// undefined
callName();
var callName = function () {
console.log('呼叫小明');
}

解說 :

  • 函式表達式創造階段與變數一樣會先提出變數給予記憶體空間。

  • 在執行階段,才把 function 賦予到變數 callName 上。要運行這段函式必須在函式賦予到值上才能運行此段函式。

    1
    2
    3
    4
    5
    6
    7
    8
    // 創造階段
    var callName;

    // 執行階段
    callName = function () {
    console.log('呼叫小明');
    }
    callName();

函式表達式 + 函式陳述式- 範例 1

1
2
3
4
5
6
7
8
// 題目
function callName() {
console.log('呼叫小明 1');
}
var callName = function () {
console.log('呼叫小明 2');
}
callName();

解說 :

  • 在創造階段函式優先,所以函式先被提至最上面 → 才換變數往前移 → 並賦予值的動作。

    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
2
3
4
5
6
7
8
9
// 題目
function callName() {
console.log('呼叫小明 1');
}
callName();
var callName = function () {
console.log('呼叫小明 2');
}

拆解 :

  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');
    }
  2. 創造階段先宣告函式,定義變數 callName 為空值。執行 callName(); 這個函式取得 console.log('呼叫小明 1');

答案:呼叫小明 1'

函式陳述式- 範例 3

1
2
3
4
5
callName();
function callName() {
console.log(Ming);
}
var Ming = '小明';
  • 答案

    答案為 undefined

    • 先把創造階段與執行階段分開。提出函式 → 提出變數 → 賦予值。

      1
      2
      3
      4
      5
      6
      7
      8
      // 創造
      function callName() {
      console.log(Ming);
      }
      var Ming;
      // 執行
      callName();
      Ming = '小明';
    • 創造階段就先宣告函式,定義這個變數為空值。呼叫 callName(); 這個函式目前所取的值是全域的 Ming,目前這個變數 Ming 還未被賦予值,答案就為 undefined

參考資訊

  • 六角學院 - JavaScript 核心篇