Hoisting


Posted by chihyu on 2021-01-25

Hoisting

提升:寫在後面的會被裝有寫在前面

  • 變數宣告可以提升,賦值的部分沒有
  • function 可以提升
    console.log(a) // undefined
    var a = 10 
    ====
    假想的流程:
    var a // 只提升了宣告 a 的部分,後面賦值的部分不提升
    console.log(a)
    a = 10
    
    test() // function 自動被假想成已經出現在前面了
    function test() {
      console.log(10)
    }
    

順序

  • 參數的優先權大於變數宣告
  • function 的優先權大於變數宣告、參數
  • 兩個相同名稱的 function,後面定義的為優先
  • 已經宣告且賦值的變數如果再宣告但不賦值會被忽略
    function > 參數 > 變數

補充:ECMAScript 是 JS 遵照的一個標準

執行環境 Execution contexts

當 control(這個 control 不知道是甚麼) 進入一個 execution code(假想成一個個 function) 就會進入一個新的 execution contexts(EC),EC 會形成一疊(stack),最上層的 EC 是正在執行的 EC,執行完才會清掉(pop)
一開始的時候會有一個 Global execution contexts

變數初始化 variable instantiation

  • 每個 EC 都有一個 variable object(gloabal 的,function 的是 activation object),在 source text(不知道指甚麼)裡宣告的 variable 和 function 會做為 variable object 的 properties, function 的參數也會

初始化順序:

  1. 找參數 (formal parameter):如果寫入的參數比原本的參數少,多於的參數會初始化成 undefined,如果有相同的參數名稱,value 是屬於最新的參數
  2. 找 function 的宣告(FunctionDeclaration):如果 variable object 裡面發現有同名的 properties,那他的 value 跟 attirbute 就會被取代(被取代的依據是甚麼:source text order)
  3. 找 variable 的宣告(VariableDeclaration):如果有同名的,並不會影響到任何事情,如果沒有同名的,就初始化成 undefined

variable object (vo):想像成一個 js 的物件

vo: {
    a:1
    b:1
    c:undefined
}
function text (b, c) {
    var a = 1
}
text(1)

範例執行步驟:

  1. 進入 global EC (只看 global 的東西,function 裡面的東西先不看)

    global EC
    global VO:{
    }
    
  2. 先看有沒有 parameter, 沒有 parameter 再看有沒有 function,發現一個 function test()

    global VO:{
     test: function
    }
    
  3. 找完 function 找 variable

    global VO:{
     test: function 
     a: undefined // 還沒開始執行,只是先找名字
    }
    
  4. 開始執行程式碼,從 global 的第一行開始看
  5. 第一行 a 被宣告成 1
    global VO:{
     test: function 
     a: 1 
    }
    ===
    var a = 1
    
  6. text() 被呼叫,進去一個新的 EC
    test EC
    test VO:{
    }
    
  7. 先看有沒有 parameter, 沒有 parameter 再看有沒有 function,發現一個 function inner()
    test VO:{
     inner:function
    }
    
  8. 找完 function 找 variable
    test VO:{
     inner:function
     a:undefined // 找到變數 a, 先初始化成 undefined
    }
    
  9. 開始執行程式碼,從 test 裡的第一行開始看
  10. console.log('1', a),對照 test VO 裡的 a
    test VO:{
    inner:function
    a:undefined // 找到變數 a, 先初始化成 undefined
    }
    ===
    console.log('1', a) // undefined
    
  11. var a = 7,a 被宣告成 7,把 test VO 的 a 改成 7
    test VO:{
    inner:function
    a:7 // 找到變數 a, 先初始化成 undefined
    }
    ===
    var a = 7
    
  12. console.log('2', a),對照 test VO 裡的 a
    test VO:{
    inner:function
    a:7 // 找到變數 a, 先初始化成 undefined
    }
    ===
    console.log('1', a) // 7
    
  13. a++,把 test VO 裡 a 的值 + 1
    test VO {
    inner:function
    a:8
    }
    a++
    
  14. var a,因為 a 已存在,所以不會有任何變化
  15. inner() 被呼叫,進去一個新的 EC
    inner EC
    inner VO:{
    }
    
  16. 找看看有沒有 parameter、function、variable,發現都沒有
  17. 開始執行程式碼,從 inner 裡的第一行開始看
  18. console.log('3', a),對照 inner VO 裡的 a,發現沒東西再往 test VO 去找
    test VO {
    inner:function
    a:8
    }
    ===
    console.log('3', a) // 8
    
  19. a = 30,inner VO 裡面沒有 a,再往 test VO 找,找到 a,把 a 改成 30
    test VO {
    inner:function
    a:30
    }
    
  20. b = 200,inner VO 裡面沒有 b,再往 test VO 找,test VO 裡面沒有 b,往 global VO 找,發現沒有 b,直接把 b 寫入 global
    global VO:{
    test: function 
    a: 1 
    b:200
    }
    
  21. inner 執行完畢,清掉 inner VO
  22. 回到 test EC,console.log('4', a),對照 test VO 裡的 a
    test VO {
    inner:function
    a:30
    }
    ===
    console.log('4', a) // 30
    
  23. test 執行完畢,清掉 test VO
  24. 回到 global EC,console.log('5', a),對照 global VO 裡的 a
    global VO:{
    test: function 
    a: 1 
    b:200
    }
    ===
    console.log('5', a) // 1
    
  25. a = 70,把 global VO 裡的 a 改成 70
    global VO:{
    test: function 
    a: 70
    b:200
    }
    a = 70
    
  26. console.log('6', a),對照 global VO 裡的 a
    global VO:{
    test: function 
    a: 70
    b:200
    }
    consle.log('6', a) // 70
    
  27. console.log('7', b),對照 global VO 裡的 b
    global VO:{
    test: function 
    a: 70
    b:200
    }
    consle.log('7', b) // 200
    
  28. global 的東西執行完畢,清掉 global VO
  29. 結束

整體流程概要:

  • 進入 EC,先初始化 VO,依序找 parameter、function、variable
  • 接著執行程式碼,由上到下執行
  • 碰到呼叫 function,進入一個新的 EC,初始化新的 VO
  • 當這個 function 執行完畢,把這個 function 的 VO 清掉

補充:
let、const 也有 hoisting,但跟 var 的運作方式不太一樣,是在賦值之前不會被存取,如果沒有賦值就存取不會被當作 undefined,而是會出現錯誤
(TDZ) Temporal dead zone:在宣告後跟賦值前的那個區域,在這個區域裡面不能存取

let a = 10 
function test() {
console.log(a)
let a = 30
}
test()

JS 的作用域

靜態作用域 (Static scope/lexical scope):在 function 宣告時就已經決定,跟執行沒關係,Javascript 是採用此

動態作用域 (Dynamic scope):在 function 執行時才會決定


#Web #javascript #hoisting







Related Posts

Git 狀況劇_忘了開新分支寫作業

Git 狀況劇_忘了開新分支寫作業

JS102 如何輸出和使用模組?

JS102 如何輸出和使用模組?

Lidemy HTTP Challenge

Lidemy HTTP Challenge


Comments