鐵人賽
因為 JavaScript engine 執行時,沒有東西真正被 「Hoist」
Hoisting 是一種 隱喻
,或是說對某一種「狀況」,我們慣例上用 Hoisting 這個字來稱呼它,
但其實, Hoisting 就是在探討 lexical scope 的問題。
不要忘記,我們之前了解 有 compile time 和 Run time
line 3, line 4 有 Variable Declaration:
Execution Context(global) | Memory |
---|---|
student |
|
teacher |
line 1, line 2 有 expression statement(source reference):
Execution Context(global) | Memory |
---|---|
studnet; // ?? |
student |
teacher; // ?? |
teacher |
我們把 expression 丟到 Execution Context,然後執行它之後:
Execution Context(global) | Memory |
---|---|
studnet; // undefined |
student |
teacher; // undefined |
teacher |
因為還沒有賦值,所以是 undefined
大家可以回去看 day06 :
undefined 表示我們有宣告這個變數,不過在這時候還 沒有賦值(no value).
line 3, line 4 有 expression statement(targent reference):
把值寫進 identifer
Execution Context(global) | Memory |
---|---|
student : "you" |
|
teacher : "Kyle" |
寫進去以後再執行,就會印出值了:
studnet;
teacher;
Execution Context(global) | Memory |
---|---|
studnet; // "you" |
student : "you" |
teacher; // "Kyle" |
teacher : "Kyle" |
以上的步驟應該沒有問題,我們現在都知道 JavaScript 是兩步驟,
以上的步驟叫做有 lexical scope
的語言。
註: 上面的步驟我有做成投影片,這次換成表格來試著說明看看。
投影片在這邊,都沒有什麼人點擊 QQ
JavaScript 是直譯,但不會報錯
的問題?就出現這張圖,也是最多人 強行解釋
JavaScript 執行的方法 :
但事實上,你知道,我知道,獨眼龍也知道:「code 沒有被動過,沒有任何一行 code 受到傷害。」
那只是 JavaScript 的 Lexical Scope 而已。
這也就是我們上面表格的狀態。
但是會讓更多人忽略 JavaScript 本質還是做 parsing, 然後 executing。
當你遇到一個「Hoisting」的問題,然後去 Stack Overflow 上面找答案,你得到的答案還是 「Hoisting」。 You learn nothing!
line 3 有 Function Declaration, line 8 有 Variable Declaration
Execution Context(global) | Memory |
---|---|
teacher (function) |
|
otherTeacher (Variable) |
宣告一個 Function Declaration :
宣告一個 Function Declaration時,identifier 會是一個 function name,
沒有帶括號()
。
其意義是這個 identifier 的值是你的 function Code
。
所以你今天在 console.log 一個 identifier (其中 identifier 儲存 function) ,
這時候會出現真的 code
,而不是執行
code。
換句話說,你用 Function Declaration 寫個無窮迴圈,只要不執行,
JavaScript 只是把你的 無窮迴圈code 放在記憶體,然後透過你指定的 identifier 當作便簽(label),方便後續使用。
如果你的 Function Declaration 內還有其他 Declaration (無論是變數還是其他函式),compiler 的階段也只是單純紀錄並規劃 scope ,所以還是不會執行
。
執行
一個 Function Expression :
你必須很顯性的使用 expression , JavaScript 才會知道你執行它。
執行時,會依據規劃書建立一個 Execution Context,也就是我們的 Scope a.k.a. 不同顏色的木桶。
執行時,Function Expression 的 return
還是一個 JavaScript 定義的值
,所以顯性的使用 identifer()
就是代表 一個 JavaScript 定義的值
(對比 identifer
代表一堆 真實的code
)。
line 1 : teacher()
Execution Context(global) | Memory |
---|---|
teacher() | teacher (function) |
otherTeacher (Variable) |
teacher() 會把 function teacher(){...}
拿出來執行,
這時候會創造一個 function teacher 的 Local Execution Context
function teacher() 的 Execution Context | Memory |
---|---|
return "Kyle"; |
(沒有parameter) |
執行之後知道, teacher() => "Kyle"
所以 line 1 會印出 "Kyle"
line 2 : otherTeacher()
otherTeacher() 會把 function otherTeacher(){...}
拿出來執行,
但是這時候發現找不到一個 function 叫做 otherTeacher
[day16] YDKJS (Scope) : Lexical Scope Review,Error 種類
有找到 variable
如果 variable 有找到,但你嘗試做些不可能的操作,這時候的 Error throw 結果稱作 TypeError
很顯然,對 Variable 當作 function 存取,就是一個 不可能的操作
,所以此時會出現 TypeError
。
遇到 Error ,就跳出了,程式 GG。
我不知道用哪種會比較好懂,但至少我現在看到 code 被移動感覺很怪。
不過 Kyle 想強調, line 9
的 Function Expression。
很多人喜歡用這種方式宣告,後面是一個 arrow function,
這時候你就無法享受 function hoisting
的好處。
因為 Kyle 都把 Function Declaration 放在 code 的最下面,
這樣可以不用每次看 code 都要一直滾動。
(我:????)
Execution Context(global) | Memory |
---|---|
teacher (Variable) |
|
otherTeacher (Function) |
function otherTeacher() 的 Execution Context | Memory |
---|---|
teacher (Variable) |
Run time:
line 5 : console.log(teacher) 裡面的 teacher 會先找自己的 Scope ,
這時候有變數,但沒有賦值,所以是 undefined
大家可以回去看 day06 :
undefined 表示我們有宣告這個變數,不過在這時候還 沒有賦值(no value).
let 不會 hoist !?
很顯然,這一定是錯的,let 會。但是 為什麼 ?
如果 let 不會 hoist ,那應該是出現 reference Error ,但是出現的還是 TDZ (temporal dead zone) error.
[day16] YDKJS (Scope) : Lexical Scope Review,Error 種類
沒有找到 variable
如果 RHS (常用 source position ) 沒有找到 variable,特別強調找到 global Scope 都失敗,這樣的 Error throw 結果稱作ReferenceError .
這時候,靈魂的審問再次出現 : 「 var , let(const)有什麼不一樣?」
答案是
uninitialized
(尚未初始化) ,你還是嘗試接觸它,那就會有一個錯誤: TDZ (temporal dead zone)很早以前藏下去的圖:
[day06] YDKJS (Type) : 特殊值:undefined / undeclared / TDZ ? , NaN , 負數 0
現在我們都知道,let 與 const 也有 hoisting。
但他們不會初始化為 undefined,而且在賦值之前試圖取值會發生錯誤(TDZ error)。
但是,
TC39 強迫讓你不要在宣告以前使用變數
以下是 Kyle 說的,我覺得比較有說服力:
學術上,真正開發語言上的原因:
1. 其實和 let 沒有任何關係
2. TDZ error 是為了實作 const
你使用一個 const 在 block scope 內部,
如果這時候沒有限制它不能初始化 undefined,你在真正 const 賦予值以前,
你可以 console.log 它,他會是 undefined (就像 var 的 hoisting)
這就違背了 const 在學術上(Academically)的想法
, const 只能有一個值。
所以為了避免這個問題,所有在真正 assign 以前的接觸都直接給 Error,以保護 const 的學術性。
然後 const 和 let 一起推出的,他們就想 「let 也這樣做好了。」
That is the fact.
Kyle 的建議是
先宣告,再使用
的習慣。可以使用
。
有時候放到整份 code 最下面可以減少找 main code 的時間。
先宣告,再使用
的習慣,這樣就不會碰到 TDZ 。can't hoist
就是 can't happen at compile time
expression 是 run time 的東西,當然不會在 compile time 執行,也就不會有 expression hoisting。
還好沒有寫的和 huli 那篇 我知道你懂 hoisting,可是你了解到多深?
一樣。
但那篇文章也有同樣引用到 Kyle,所以下半部可能有些雷同(特別是有對話那邊 XD)。
huli 有問 Kyle 一些問題,如果好奇可以看一下 Kyle 的回答:
https://github.com/getify/You-Dont-Know-JS/issues/1375
其實 hoist 很多人都寫過,但是如果還是「想成把 code 提前」難免有些可惜,
下次試著畫畫看兩個階段的表格,然後解釋 hoisting ,就會慢慢體驗『hoist 是 compile time 的事』
明天開始 closure !!!
這篇寫得不錯,特別喜歡解釋 TDZ 那邊,因為要實作 const 然後順便把 let 帶進來,聽起來十分合理
然後這邊稍微解釋一下我問的那個問題,我想表達的其實是:「當我們說一個程式語言是 compiled language 的時候,不是表示『它一定要是 compiled language』,而只是在說『大多數的實作(或是比較合理的實作)是 compiled 的』」
因為語言本身不會規範你一定要 compiled 阿,所以聽起來應該合理吧?不過對 Kyle 來說他好像覺得不合理,因為 ECMAScript 的 spec 「暗示」了應該要有 compiled 這件事,所以 compiled 才是唯一合理的結果。
不過我後來覺得我們不是在討論同一個問題就是了,畢竟英文不是母語所以我問起來可能沒有到位,要再來來往往我也沒有把握能問得好,搞不好看起來會像跳針XD
我提的其實比較像是「文字上」或是「語言上」的定義,是跟實作完全無關的。舉例來說,你可以自己寫個 C++ 直譯器,也可以寫 JS 直譯器,也可以寫 JS 編譯器,JS 本身沒有限制你一定要用直譯還是編譯。
但 Kyle 提的是「在實作上」JS 如果沒有經過編譯這個階段根本不合理,因為有編譯的效率一定高於沒編譯,所以編譯是必須的,因此 JS 就是一定會有編譯。
只是這兩種答案其實可以相容啦,你可以認同文字上的定義就是這樣,也可以認同在實作上必須得這樣,所以我覺得兩種都對。而且後來我覺得光是編譯跟直譯的定義就可以弄的很複雜了,深究下去也沒什麼意義,所以重點還是要知道說 JS 執行前到底做了什麼處理。
哦哦,那應該是我誤會了,
我修改內文~