考慮下面的這三句代碼和對應的報錯信息:node
假設寫這個代碼的人一開始不知道 ES6 裏新增的構造函數不能省略 new,因而第一行寫錯了。而後第二行嘗試從新聲明一次,結果又報錯說重複聲明瞭。那乾脆不聲明,直接賦值總行吧,結果又報錯說 map 未定義。git
這三個報錯直接對應規範裏的下面三條規則(並附通俗解釋):github
23.1.1.1 Map()shell
1. If NewTarget is undefined, throw a TypeError exception.函數
解釋:Map() 不帶 new 不能被調用。blog
15.1.11 GlobalDeclarationInstantiation()get
5.b. If envRec.HasLexicalDeclaration(name) is true, throw a SyntaxError exception.it
解釋:map 已經被聲明過,不能重複聲明。io
8.1.1.1.5 SetMutableBinding()table
4. If the binding for N in envRec has not yet been initialized, throw a ReferenceError exception.
解釋:map 處於已經聲明但未初始化的狀態,這種狀態不能經過 = 賦值。
一段代碼在被真正的執行前,會有個專門用來聲明變量的過程,俗語常把這個過程稱爲預解析/預處理。不管是用 var 仍是用 let/const 聲明的變量,都是在這個過程裏被提早聲明好的,俗語常把這種表現稱爲 hoisting。只是 var 和 let/const 有個區別,var 變量被聲明的同時,就會被初始化成 undefined,然後二者不會。
首先說 「map 未定義」是 V8 的報錯信息不友好,正確的報錯信息應該是 「map 未初始化」。
規範規定一個已經聲明但未初始化的變量不能被賦值,甚至不能被引用,代碼示例裏第三句即使只寫一個 map 也會報同樣的錯。規範裏用來聲明 var/let 變量的內部方法是 CreateMutableBinding(),初始化變量用 InitializeBinding(),爲變量賦值用 SetMutableBinding(),引用一個變量用 GetBindingValue()。在執行完 CreateMutableBinding() 後沒有執行 InitializeBinding() 就執行 SetMutableBinding() 或者 GetBindingValue() 是會報錯的,這種表現有個專門的術語(非規範術語)叫 TDZ(Temporal Dead Zone),通俗點說就是一個變量在聲明後且初始化前是完徹底全不能被使用的。
由於 var 變量的聲明和初始化(成 undefined )都是在「預處理」過程當中同時進行的,因此永遠不會觸發 TDZ 錯誤。let 的話,聲明和初始化是分開的,只有真正執行到 let 語句的時候,纔會被初始化。若是隻聲明不賦值,好比 let foo,foo 會被初始化成 undefined,若是有賦值的話,只有等號右側的表達式求值成功(不報錯),纔會初始化成功。一旦錯過了初始化的機會,後面再沒有彌補的機會。這是由於賦值運算符 = 只會執行 SetMutableBinding(),並不會執行 InitializeBinding(),因此例子中的 map 變量被永遠困在了 TDZ 裏。
其實我舉的這個例子已經在 Firefox、Chrome、Node 的 bug 平臺上都被反應過了。Firefox 的 JS 引擎爲了消除這種奇怪的表現,專門針對 shell 環境(包括 Firefox 中的控制檯)作了特殊處理,當 let/const 語句等號右側的表達式求值發生錯誤後,引擎會把它初始化成 undefined:
若是是 js shell 的話,還能看到一段解釋信息,代表這樣作實際上是違反規範的:
讀到如今,有同窗就問了:「就由於這個就不讓在控制檯裏用 let/const?我之後記得加 new 不就得了」。等號右邊的表達式報錯其實有不少種狀況,好比某個屬性意外成了 undefined,好比右側的函數調用自己報錯了,都有可能,出錯其實挺常見的。
並且除了這種因報錯致使你不得不從新聲明一次的狀況,還有一些狀況是你主動想重複聲明的。好比咱們常常在控制檯裏寫代碼都是想最終產出一段代碼的,但你寫的時候是一句是一句寫的,寫一句回車執行,沒問題的話,按下上箭頭,而後按 shift+enter,換行後寫第二句,可能最終完成須要十來句。若是其中某一句用到了 let/const,第二次執行的時候就會報錯,而後你只能刷新頁面了。
不用 let/const 那用啥呢?用 var 或者直接用賦值語句均可以,依狀況而定。並且本文的觀點並非絕對的,不少狀況下是能夠用 let/const 的,好比你的聲明語句是寫在一個函數裏的,好比你從別的地方複製了一個腳本(搶月餅?),只須要在控制檯粘貼執行一次,不用修改,這些狀況用什麼均可以。