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 的參數也會
初始化順序:
- 找參數 (formal parameter):如果寫入的參數比原本的參數少,多於的參數會初始化成 undefined,如果有相同的參數名稱,value 是屬於最新的參數
- 找 function 的宣告(FunctionDeclaration):如果 variable object 裡面發現有同名的 properties,那他的 value 跟 attirbute 就會被取代(被取代的依據是甚麼:source text order)
- 找 variable 的宣告(VariableDeclaration):如果有同名的,並不會影響到任何事情,如果沒有同名的,就初始化成 undefined
variable object (vo):想像成一個 js 的物件
vo: {
a:1
b:1
c:undefined
}
function text (b, c) {
var a = 1
}
text(1)
範例執行步驟:
進入 global EC (只看 global 的東西,function 裡面的東西先不看)
global EC global VO:{ }
先看有沒有 parameter, 沒有 parameter 再看有沒有 function,發現一個 function test()
global VO:{ test: function }
找完 function 找 variable
global VO:{ test: function a: undefined // 還沒開始執行,只是先找名字 }
- 開始執行程式碼,從 global 的第一行開始看
- 第一行 a 被宣告成 1
global VO:{ test: function a: 1 } === var a = 1
- text() 被呼叫,進去一個新的 EC
test EC test VO:{ }
- 先看有沒有 parameter, 沒有 parameter 再看有沒有 function,發現一個 function inner()
test VO:{ inner:function }
- 找完 function 找 variable
test VO:{ inner:function a:undefined // 找到變數 a, 先初始化成 undefined }
- 開始執行程式碼,從 test 裡的第一行開始看
- console.log('1', a),對照 test VO 裡的 a
test VO:{ inner:function a:undefined // 找到變數 a, 先初始化成 undefined } === console.log('1', a) // undefined
- var a = 7,a 被宣告成 7,把 test VO 的 a 改成 7
test VO:{ inner:function a:7 // 找到變數 a, 先初始化成 undefined } === var a = 7
- console.log('2', a),對照 test VO 裡的 a
test VO:{ inner:function a:7 // 找到變數 a, 先初始化成 undefined } === console.log('1', a) // 7
- a++,把 test VO 裡 a 的值 + 1
test VO { inner:function a:8 } a++
- var a,因為 a 已存在,所以不會有任何變化
- inner() 被呼叫,進去一個新的 EC
inner EC inner VO:{ }
- 找看看有沒有 parameter、function、variable,發現都沒有
- 開始執行程式碼,從 inner 裡的第一行開始看
- console.log('3', a),對照 inner VO 裡的 a,發現沒東西再往 test VO 去找
test VO { inner:function a:8 } === console.log('3', a) // 8
- a = 30,inner VO 裡面沒有 a,再往 test VO 找,找到 a,把 a 改成 30
test VO { inner:function a:30 }
- b = 200,inner VO 裡面沒有 b,再往 test VO 找,test VO 裡面沒有 b,往 global VO 找,發現沒有 b,直接把 b 寫入 global
global VO:{ test: function a: 1 b:200 }
- inner 執行完畢,清掉 inner VO
- 回到 test EC,console.log('4', a),對照 test VO 裡的 a
test VO { inner:function a:30 } === console.log('4', a) // 30
- test 執行完畢,清掉 test VO
- 回到 global EC,console.log('5', a),對照 global VO 裡的 a
global VO:{ test: function a: 1 b:200 } === console.log('5', a) // 1
- a = 70,把 global VO 裡的 a 改成 70
global VO:{ test: function a: 70 b:200 } a = 70
- console.log('6', a),對照 global VO 裡的 a
global VO:{ test: function a: 70 b:200 } consle.log('6', a) // 70
- console.log('7', b),對照 global VO 裡的 b
global VO:{ test: function a: 70 b:200 } consle.log('7', b) // 200
- global 的東西執行完畢,清掉 global VO
- 結束
整體流程概要:
- 進入 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 執行時才會決定