update:
2017-10-23 更新了文中一些表達以及添加了JS編譯部分的理解。
2018-06-06 這篇文章能更好的理解ES6的let
和const
理解ES6中的暫時死區(TDZ)
先前看到方方老師最新的文章《我用了兩個月的時間才理解 let》,以下是對該文章的學習結合自己理解總結的筆記。
由于自己對JavaScript的編譯和運行理解的不是非常深,文中對于JavaScript引擎的編譯部分的理解可能并不準確,如有問題歡迎提出!
var
let
function
const
聲明的過程
首先,需要明白,JavaScript在準備階段會由JavsScript引擎進行編譯的,編譯的過程包括進行詞法分析(詞法作用域)及代碼生成。當前面的準備完成后通常會立即執行代碼。
對于變量聲明來說,一般可以簡單的分為三個過程:
- create創建 (編譯)
- initialize初始化 (編譯)
- assign賦值 (執行代碼)
需要注意,初始化當中也可以進行賦值。
var
// 通過fn限定作用域
function fn(){
console.log(x) // 結果為undefined
var x = 1
var y = 2
}
fn()
// 1. 找到fn作用域中所有var聲明的變量,create變量(x和y)
// 2. initialize變量x,y為undefined
// 3. 開始執行代碼 x = 1 ,將變量 x assign為 1
// 4. y = 2 將變量 y assign為 2
// 可以理解為下面的情況
var x
var y
x = 1
y = 2
基本過程:
1.找到所有var
,「創建」變量
2.「初始化」變量并賦值undefined
3.「執行代碼」,根據執行的代碼開始為變量「賦值」。
在這個過程中產生了一個像是聲明提升到作用域頂部的現象,因此會出現在var x = 1
之前console.log(x)
。
ps:從文章中學習到,聲明提升其實并非是一個官方說法。
function
// 假設全局作用域下
fn2 ()
function fn2(){
console.log(2)
}
// 1. 找到當前作用域中所有的function聲明的變量,為當前環境create這些變量(聲明fn2)。
// 2. 初始化這些變量(fn2)并賦值為 function(){ console.log(2) }
// 3. 執行代碼fn2()
基本過程:
- 會先找到所有
function
關鍵字的函數聲明進行「創建」 - 進行「初始化并賦值」的操作
- 「執行」代碼
fn2()
,因此產生了函數提升的過程
需要注意的是,這里特指函數聲明。
let
function fn3(){
let x = 1
x = 2
}
fn3()
// 1. fn3()作用域中找到所有的let聲明的變量并創建它
// 2. 開始執行代碼 (與var 不同,這里還沒初始化)
// 3. 執行x = 1 ,x 「初始化」為 1(這并不是一次賦值,如果代碼是 let x,就將 x 初始化為 undefined)
// 4. 執行 x = 2,為x進行賦值
根據這個區別,let的初始化是在執行代碼的過程中,而不是先初始化:
- 找到所有
let
「創建」變量。 - 開始「執行」代碼,使變量根據執行的代碼「初始化并賦值」,此時如果沒有賦值(比如:
let x
)則「初始化」為undefined
。 - 繼續執行代碼。
由于let
的初始化和賦值操作是在代碼執行階段,所以對let
聲明的變量的使用必須在聲明后,也就沒有出現聲明提升的現象。
const
function fn4(){
const x = 1
x = 3 // Uncaught TypeError: Assignment to constant variable.
}
fn4()
// 1. fn4的作用域中找到所有const聲明的變量,并創建它(創建x)
// 2. 為變量初始化 (變量x 初始化為3)
// 3. 執行代碼x = 3 ,報錯
const和let 主要區別在于const聲明的變量無法賦值,同時const必須在執行初始化的時候賦值,否則會拋出錯誤Uncaught SyntaxError: Missing initializer in const declaration
let
與var
區別:
- var 的創建與初始化都提升了
- let 的創建提升了,但是初始化沒有提升
- function 的創建、初始化、賦值都被提升了
因此在let
初始化之前使用其聲明的變量,就會出現變量沒有定義的錯誤:
function fn5(){
console.log(x) // Uncaught ReferenceError: x is not defined
let x = 1
}
fn5()
小結
對于上面的結論,我們對JavaScript變量聲明做一些總結。
創建:在編譯階段通過詞法分析,對關鍵字(var
,let
,const
,function
)進行變量創建
表現為變量提升到作用域頂端進行創建
初始化:仍在編譯階段,不同關鍵字初始化方式不同,主要操作是為變量賦值:
var:創建后初始化為undefined
let:執行代碼階段進行初始化,如果沒有賦值則初始化為undefined
function:函數聲明表達式為var的形式提升到作用域頂端創建,并同時初始化,賦值為一個函數對象
const:創建后,執行代碼階段進行初始化,同時必須賦值
執行代碼階段:
var:創建變量時已進行了第一次初始化
let:關鍵字后為第一次執行,第一次執行時才進行初始化,可以不賦值,不賦值則為初始化為undefined
function:
const:關鍵字后第一次執行代碼,必須賦值
- 所有變量聲明方式都會將變量提升到作用域頂端,并進行創建
- 初始化的表現使得變量聲明不同,產生了變量提升的現象
其他相關
參考文章和知乎上的知識,各瀏覽器在console上表現并不是一致的
比如上圖中的問題,在chrome的console中,有以下的情況:
- 當 let聲明變量x如果初始化失敗了,就會處于一種已創建的狀態
- 此時無法再次對x進行初始化,即初始化機會只有一次
- 此時x將處于暫時死區(temporal dead zone)
- 于是,此狀態下的x將是不可用的
但是對于其他瀏覽器可能報錯信息將不一致,具體可以參照問題中賀老的回答。
參考:
https://zhuanlan.zhihu.com/p/28140450
https://www.zhihu.com/question/62966713