hoisting 提升

Node.js hoisting 提升

hoisting 提升邏輯

  • 變數宣告函式宣告 都會提升
  • 只有宣告會提升賦值不會提升
  • 函式宣告 提升高度高於 變數宣告
  • 函式裡面傳進來的參數

何謂 hoisting 提升呢?

在變數 還沒被宣告 之前就使用,沒有執行錯誤,表示此變數被 hoisting 提升

// undefined
console.log(a)
var a = 10

原本預期是 ReferenceError: a is not defined 出錯,但是卻顯示 undefined

所以程式碼被 JavaScript 核心編譯處理後會變成這樣,變數 ahoisting 提升

var a
// undefined
console.log(a)
a = 10

為什麼我們需要 hoisting?

如果我們沒有 hoisting 會怎樣?

1. 先宣告變數才可以使用

變數使用前先宣告是個好習慣

2. 先宣告函式才可以使用

每個檔案你都必須把 function 宣告放到最上面去,才能保證你底下的程式碼都可以 call 到這些 function

3. 沒有辦法達成 function 互相呼叫

盡量不要做到 function 彼此互相 call,這樣很容易一寫不好導致無窮迴圈

function loop(n){
    if (n>1) {
        // 呼叫紀錄奇數偶數函數
        logEvenOrOdd(--n)
    }
}

function logEvenOrOdd(n) {
    console.log(n, n % 2 ? 'Odd' : 'Even');
    // 呼叫  loop 函數
    loop(n);
}

// 9 Odd
// 8 Even
// 7 Odd
// 6 Even
// 5 Odd
// 4 Even
// 3 Odd
// 2 Even
// 1 Odd
loop(10)

所以為了能夠達成上面的結果,所以才需要 hoisting 提升

hoisting 提升範例

變數在 function 參數

function test(v){
    //  10
    console.log(v);
    var v = 3
}
test(10)

變數 v 印出來的是 10 而不是 undefined

因為變數 v 從外部傳入數值,所以表示此變數已經預先定義,所以 hoisting 提升 會變成

function test(v){
  // 因為下面呼叫 test(10)
  var v = 10
  var v
  console.log(v)
  v = 3
}
test(10)

let 宣告變數 hoisting 提升

let 變數宣告 不會進行 hoisting 提升

// ReferenceError: a is not defined
console.log(a)
let a

let 變數宣告 導致函數內部原本 global 變數 a 變成 local 變數 a,而 let 變數宣告 不會進行 hoisting 提升到使用出錯

var a = 10
function test(){
  // ReferenceError: Cannot access 'a' before initialization
  console.log(a)
  let a
}
test()

function 變數提升

function 也會被 hoisting 提升

greeting('KJ');
function greeting(name){
    // Hello!  KJ
    console.log('Hello! ',name)
}

使用 var 定義變數實則會無法提升

//  TypeError: greeting is not a function
greeting('KJ');
var greeting = function(name){
    console.log('Hello! ',name)
}
// ReferenceError: greeting is not defined
greeting('KJ');
greeting = (name) => {
    console.log('Hello! ',name)
}

實際上提升的方式結果會變成 undefined 的變數值,所以在執行的時候會出錯

var greeting;
// 這時 greeting 是 undefined
greeting('KJ');
greeting = function(name){
  console.log('Hello! ',name)
}

function 與變數相同同步提升

var a = 0;
function a() {};
// TypeError: a is not a function
a();

function 會優先提升,再來是變數,所以提升完後會長這樣

function a() {};
var a;
a = 0
a();

這樣導致變數後來被賦值,變成不是 function 而導致無法呼叫

JavaScript 引擎操作

var foo = "bar"
var a = 1
function bar() {
    foo = "inside bar"
    var a = 2
    c = 3
    console.log(c)
    console.log(d)
}
bar()

執行完變數的 scope 及設定會長這樣,會將需要的變數及函式進行 hoisting 提升

globalScope = {
  foo: undefined,
  a: undefined,
  bar: function(){}
}

barScope: {
  a: undefined
}

LHS(Left hand side) 與 RHS(Right hand side)

名詞 說明 範例
LHS 請幫我去查這個變數的 位置 在哪裡,因為 我要對它賦值 let a = 1
RHS 請幫我查詢這個變數的 值是什麼,因為 我要用 這個值 console.log(a)

變數定義步驟

var foo = "bar"
var a = 1
function bar() {
    foo = "inside bar"
    var a = 2
    c = 3
    console.log(c)
    console.log(d)
}
bar()

Step 1. var foo = “bar”

步驟 程式 處理 結果
1 var foo = "bar" 問 global scope,我要對 foo 變數 LHS 你有看過嗎? global scope 成功找到 foo 並且賦值
globalScope : {
  foo: "bar",
  a: undefined,
  bar: function(){}
}

barScope: {
  a: undefined
}

Step 2. var a = 1

步驟 程式 處理 結果
2 var a = 1 問 global scope,我要對 a 變數 LHS 你有看過嗎? global scope 成功找到 a 並且賦值
globalScope : {
  foo: "bar",
  a: 1,
  bar: function(){}
}

barScope: {
  a: undefined
}

Step 3. bar()

步驟 程式 處理 結果
3 bar() 問 global scope,我要對 bar 變數 LHS 你有看過嗎? global scope 成功找到 bar 並且執行 function
globalScope : {
  foo: "bar",
  a: 1,
  bar: function(){}
}

barScope: {
  a: undefined
}

Step 4. foo = “inside bar”

步驟 程式 處理 結果
4 foo = "inside bar" 依序問 bar scope 及 global scope,我要對 bar 變數 LHS 你有看過嗎? bar scope 沒有找到,但 global 成功找到 foo 並且賦值
globalScope : {
  foo: "inside bar",
  a: 1,
  bar: function(){}
}

barScope: {
  a: undefined
}

Step 5. var a = 2

步驟 程式 處理 結果
5 var a = 2" 問 bar scope,我要對 a 變數 LHS 你有看過嗎? bar scope 成功找到 a 並且賦值
globalScope : {
  foo: "inside bar",
  a: 1,
  bar: function(){}
}

barScope: {
  a: 2
}

Step 6. c = 3

步驟 程式 處理 結果
6 c = 3" 依序問 bar scope 及 global scope,我要對 c 變數 LHS 你有看過嗎? bar scope 及 global scope 都沒有找到

嚴格模式(use strict) 下會回傳 ReferenceError: c is not defined

不是在嚴格模式,則會宣告變數 c 到 global scope

globalScope : {
  foo: "inside bar",
  a: 1,
  bar: function(){},
  // 非嚴格模式
  c: 3
}

barScope: {
  a: 2
}

Step 7. console.log(c)

步驟 程式 處理 結果
7 console.log(c)" 依序問 bar scope 及 global scope,我要對 c 變數 RHS 你有看過嗎? bar scope 沒有找到,但 global 成功找到 c 並且呼叫 console.log
globalScope : {
  foo: "inside bar",
  a: 1,
  bar: function(){},
  // 非嚴格模式
  c: 3
}

barScope: {
  a: 2
}

Step 8. console.log(d)

步驟 程式 處理 結果
8 console.log(d)" 依序問 bar scope 及 global scope,我要對 d 變數 RHS 你有看過嗎? bar scope 及 global scope 都沒有找到,回傳 ReferenceError: d is not defined
globalScope : {
  foo: "inside bar",
  a: 1,
  bar: function(){},
  // 非嚴格模式
  c: 3
}

barScope: {
  a: 2
}

Temporal Dead Zone (TDZ)

var & let & const 都有 hoisting

  • var 提升後變數會初始化為 undefined
  • let 與 const 不會初始化,所以在取用前必須要賦值,不然就會出錯

所以 let 與 const 未被賦值前,會進入一個 Temporal Dead Zone (TDZ) 無法存取

function test() {
    var a = 1; // c 的 TDZ 開始
    var b = 2;
    // ReferenceError: Cannot access 'c' before initialization
    console.log(c) // 錯誤
    if (a > 1) {
        console.log(a)
    }
    let c = 10 // c 的 TDZ 結束
}
test()

參考資料