JavaScript 進階 02:Hoisting


什麼是 Hoisting

會輸出 undefined 而不是 ReferenceError: b is not defined,這種現象就叫做 Hoisting,變數的宣告被「提升」到最上面去了。

  • 先來個報錯案例
console.log(b)
// b is not defined
  • Hoisting (提升)
console.log(b)
var b = 10
// 印出 b 是 undefined,而非報錯
// 這個現象,稱之為 Hoisting(提升)
var b
console.log(b)
b = 10
// 我們可以想像成,把變數拉上去
// 只有宣告會提升,賦值不會
  • 宣告與賦值的提升關係
test()
var test = function() {
    console.log(123)
}
// test is not a function
var test  // undefined
test()
test = function() {
    console.log(123)
}
// 宣告會被提升,賦值不會
  • 宣告 function 與 呼叫 function
test()
function test() {
    console.log(123)
}
// 我們可以在宣告 function 之前,先呼叫,這也是 Hoisting
// function 整體被提升
// JS 做得到,但其他程式語言不一定
function test() {
    console.log(123)
}
test()

Hoisting 的順序問題

  • Hoisting 與變數有關
    Hoisting 只會發生在變數的 Scope 裡面
var a = 'global'
function test() {
    console.log(a)
    var a = 'local'
}
test()
// 印出 undefined
var a = 'global'
function test() {
    var a  // 變數在這個作用域內,被提升
    console.log(a)
    a = 'local'
}
test()
  • 來談談提升(Hoisting)的順序:變數 v.s. function
var a = 'global'
function test() {
    console.log(a)
    var a = 'local'
    function a() {
    }
}
test()
// 印出 [function:a]
===
var a = 'global'
function test() {
    console.log(a)
    function a() {
    }
    var a = 'local' 
}
test()
// 更動 var a = 'local' 位置,仍會印出 [function:a]
// 因為 function 會有優先權
function test() {
    function a() {
    }
    console.log(a)
    a = 'local' // var a 可以不寫出來,因為在 function 那裡已有宣告
}
// 提升的運作可以以此為想像
// function 的優先度,會比宣告變數高
  • 來談談提升(Hoisting)的順序:function v.s. function
function test() {
    console.log(a)
    a()
    function a() {
        console.log(1)
    }
    function a() {
        console.log(2)
    }
    var a = 'local'
}
// 印出 [function:a], 2
// 宣告兩個同名的 function,後面宣告的 function 蓋掉 之前的 function
  • 來談談提升(Hoisting)的順序:參數與變數
function test(a) {
    var a // 只是宣告一個變數 a
    console.log(a)
    a = 456
}
test(123)
// 印出 123
function test(a) {
    var a = undefined
    console.log(a)
    a = 456
}
test(123)
// 印出 undefined
  • 來談談提升(Hoisting)的順序:參數與 function
function test(a) {
    console.log(a)
    function a() {
    }
}
test(123)
// 印出 [function:a]
  • 需要釐清的地方
function test() {
    var a = 'test'
    var a // 我要宣告一個變數 a
    console.log(a)
}
test()
  • 更進一步
function test(a) {
    console.log(a)
    var a = 456
    console.log(a)
}
test(123)
印出 123, 456

提升(Hoisting)的結論,提升的優先順序

  1. function
  2. arguments (傳進函式的參數)
  3. var

Hoisting 與變數大解密

var a = 1; 
function test(){
    console.log('1.', a);
    var a = 7;
    console.log('2.', a);
    a++;
    var a;
    inner();
    console.log('4.', a);
    function inner(){
    console.log('3.', a);
    a = 30;
    b = 200;
    }
}
test();
console.log('5.', a);
a = 70;
console.log('6.', a);
console.log('7.', b);
  • 一步一步解析
  1. 宣告一個全域變數
  2. 宣告一個 function 叫 test
  3. 然後執行函式 test
  4. 執行函式 test,我們來到第三行
  5. 第三行要印出 a , 先看下面有沒有宣告 a
  6. 看到第四行和第七行,有宣告變數也有賦值 (宣告會被提升,賦值不會)
  7. console.log('1.', a),a 印出 undefined
  8. 第四行宣告 a ,並賦值 7
  9. console.log('2.', a),a 印出 7
  10. 第六行 a++,這時數字為 8
  11. 第七行 var a ,沒有作用,因為第四行已經有宣告 a
  12. 第八行,執行 inner()
  13. inner(),執行 console.log('3.', a)
  14. 會先在 inner(),有沒有宣告變數 a,沒有再往上一層找
  15. 接著 碰到 a = 30,並非變數宣告,往上一層找變數宣告,然後去改變 a 值
  16. 接下來 碰到 b = 200,global variable 也找不到的話,就會直接在宣告一個全域變數 var b
  17. console.log('5.', a),看到全域變數 a = 1
  18. console.log('6.', a),前一行改變 a 的賦值 = 70
  19. console.log('7.', b),b = 200

hoisting 到底是怎麼運作的?

來看看 ECMAScript:JS 界的聖經

以下摘文來自 Huli 文章:https://blog.huli.tw/2018/11/10/javascript-hoisting-and-tdz/

  • Execution Contexts
    這邊先非常簡單介紹一下什麼是 Execution Contexts(以下簡稱 EC),每當你進入一個 function 的時候,就會產生一個 EC,裡面儲存跟這個 function 有關的一些資訊,並且把這個 EC 放到 stack 裡面,當 function 執行完以後,就會把 EC 給 pop 出來。

  • Variable object
    每個 EC 都會有相對應的 variable object(以下簡稱 VO),在裡面宣告的變數跟函式都會被加進 VO 裡面,如果是 function,那參數也會被加到 VO 裡。

參考資料:https://blog.huli.tw/2018/11/10/javascript-hoisting-and-tdz/








你可能感興趣的文章

Day 74

Day 74

PS學務處|製作火焰效果

PS學務處|製作火焰效果

Don’t break the Web:以 SmooshGate 以及 keygen 為例

Don’t break the Web:以 SmooshGate 以及 keygen 為例






留言討論