Photo by Mohammad Rahmani on Unsplash
hoisting 提升邏輯
變數宣告
跟函式宣告
都會提升- 只有
宣告會提升
,賦值不會提升 函式宣告
提升高度高於變數宣告
- 函式裡面傳進來的參數
何謂 hoisting 提升呢?
在變數 還沒被宣告
之前就使用,沒有執行錯誤,表示此變數被 hoisting 提升
了
// undefined
console.log(a)
var a = 10
原本預期是 ReferenceError: a is not defined
出錯,但是卻顯示 undefined
所以程式碼被 JavaScript 核心編譯處理後會變成這樣,變數 a
被 hoisting 提升
了
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()
參考資料
- 我知道你懂 hoisting,可是你了解到多深?
- You-Dont-Know-JS/scope-closures at 2nd-ed · getify/You-Dont-Know-JS · GitHub
- You-Dont-Know-JS/ch5.md at 2nd-ed · getify/You-Dont-Know-JS · GitHub
- 一次說清楚 JavaScript 中宣告的各種提升行為(var、function、let/const) | by realdennis | Medium
- [第十七週] JavaScript 進階:從 EC 來理解 Hoisting | Yakim shu
Donate KJ 贊助作者喝咖啡
如果這篇文章對你有幫助的話,可以透過下面支付方式贊助作者喝咖啡,如果有什麼建議或想說的話可以贊助並留言給我
If this article has been helpful to you, you can support the author by treating them to a coffee through the payment options below. If you have any suggestions or comments, feel free to sponsor and leave a message for me!
方式 Method | 贊助 Donate |
PayPal | https://paypal.me/kejyun |
綠界 ECPay | https://p.ecpay.com.tw/AC218F1 |
歐付寶 OPay | https://payment.opay.tw/Broadcaster/Donate/BD2BD896029F2155041C8C8FAED3A6F8 |