update:
2017-10-23 更新了文中一些表達(dá)以及添加了JS編譯部分的理解。
2018-06-06 這篇文章能更好的理解ES6的let
和const
理解ES6中的暫時(shí)死區(qū)(TDZ)
先前看到方方老師最新的文章《我用了兩個(gè)月的時(shí)間才理解 let》蜕乡,以下是對(duì)該文章的學(xué)習(xí)結(jié)合自己理解總結(jié)的筆記缠沈。
由于自己對(duì)JavaScript的編譯和運(yùn)行理解的不是非常深偶洋,文中對(duì)于JavaScript引擎的編譯部分的理解可能并不準(zhǔn)確饲趋,如有問(wèn)題歡迎提出幽邓!
var
let
function
const
聲明的過(guò)程
首先炮温,需要明白,JavaScript在準(zhǔn)備階段會(huì)由JavsScript引擎進(jìn)行編譯的牵舵,編譯的過(guò)程包括進(jìn)行詞法分析(詞法作用域)及代碼生成柒啤。當(dāng)前面的準(zhǔn)備完成后通常會(huì)立即執(zhí)行代碼。
對(duì)于變量聲明來(lái)說(shuō)畸颅,一般可以簡(jiǎn)單的分為三個(gè)過(guò)程:
- create創(chuàng)建 (編譯)
- initialize初始化 (編譯)
- assign賦值 (執(zhí)行代碼)
需要注意担巩,初始化當(dāng)中也可以進(jìn)行賦值。
var
// 通過(guò)fn限定作用域
function fn(){
console.log(x) // 結(jié)果為undefined
var x = 1
var y = 2
}
fn()
// 1. 找到fn作用域中所有var聲明的變量没炒,create變量(x和y)
// 2. initialize變量x,y為undefined
// 3. 開(kāi)始執(zhí)行代碼 x = 1 涛癌,將變量 x assign為 1
// 4. y = 2 將變量 y assign為 2
// 可以理解為下面的情況
var x
var y
x = 1
y = 2
基本過(guò)程:
1.找到所有var
,「創(chuàng)建」變量
2.「初始化」變量并賦值undefined
3.「執(zhí)行代碼」送火,根據(jù)執(zhí)行的代碼開(kāi)始為變量「賦值」拳话。
在這個(gè)過(guò)程中產(chǎn)生了一個(gè)像是聲明提升到作用域頂部的現(xiàn)象,因此會(huì)出現(xiàn)在var x = 1
之前console.log(x)
漾脂。
ps:從文章中學(xué)習(xí)到假颇,聲明提升其實(shí)并非是一個(gè)官方說(shuō)法。
function
// 假設(shè)全局作用域下
fn2 ()
function fn2(){
console.log(2)
}
// 1. 找到當(dāng)前作用域中所有的function聲明的變量骨稿,為當(dāng)前環(huán)境create這些變量(聲明fn2)笨鸡。
// 2. 初始化這些變量(fn2)并賦值為 function(){ console.log(2) }
// 3. 執(zhí)行代碼fn2()
基本過(guò)程:
- 會(huì)先找到所有
function
關(guān)鍵字的函數(shù)聲明進(jìn)行「創(chuàng)建」 - 進(jìn)行「初始化并賦值」的操作
- 「執(zhí)行」代碼
fn2()
姜钳,因此產(chǎn)生了函數(shù)提升的過(guò)程
需要注意的是,這里特指函數(shù)聲明形耗。
let
function fn3(){
let x = 1
x = 2
}
fn3()
// 1. fn3()作用域中找到所有的let聲明的變量并創(chuàng)建它
// 2. 開(kāi)始執(zhí)行代碼 (與var 不同哥桥,這里還沒(méi)初始化)
// 3. 執(zhí)行x = 1 ,x 「初始化」為 1(這并不是一次賦值激涤,如果代碼是 let x拟糕,就將 x 初始化為 undefined)
// 4. 執(zhí)行 x = 2,為x進(jìn)行賦值
根據(jù)這個(gè)區(qū)別倦踢,let的初始化是在執(zhí)行代碼的過(guò)程中送滞,而不是先初始化:
- 找到所有
let
「創(chuàng)建」變量。 - 開(kāi)始「執(zhí)行」代碼辱挥,使變量根據(jù)執(zhí)行的代碼「初始化并賦值」犁嗅,此時(shí)如果沒(méi)有賦值(比如:
let x
)則「初始化」為undefined
。 - 繼續(xù)執(zhí)行代碼晤碘。
由于let
的初始化和賦值操作是在代碼執(zhí)行階段褂微,所以對(duì)let
聲明的變量的使用必須在聲明后,也就沒(méi)有出現(xiàn)聲明提升的現(xiàn)象园爷。
const
function fn4(){
const x = 1
x = 3 // Uncaught TypeError: Assignment to constant variable.
}
fn4()
// 1. fn4的作用域中找到所有const聲明的變量宠蚂,并創(chuàng)建它(創(chuàng)建x)
// 2. 為變量初始化 (變量x 初始化為3)
// 3. 執(zhí)行代碼x = 3 ,報(bào)錯(cuò)
const和let 主要區(qū)別在于const聲明的變量無(wú)法賦值童社,同時(shí)const必須在執(zhí)行初始化的時(shí)候賦值求厕,否則會(huì)拋出錯(cuò)誤Uncaught SyntaxError: Missing initializer in const declaration
let
與var
區(qū)別:
- var 的創(chuàng)建與初始化都提升了
- let 的創(chuàng)建提升了,但是初始化沒(méi)有提升
- function 的創(chuàng)建叠洗、初始化甘改、賦值都被提升了
因此在let
初始化之前使用其聲明的變量,就會(huì)出現(xiàn)變量沒(méi)有定義的錯(cuò)誤:
function fn5(){
console.log(x) // Uncaught ReferenceError: x is not defined
let x = 1
}
fn5()
小結(jié)
對(duì)于上面的結(jié)論灭抑,我們對(duì)JavaScript變量聲明做一些總結(jié)十艾。
創(chuàng)建:在編譯階段通過(guò)詞法分析,對(duì)關(guān)鍵字(var
,let
,const
,function
)進(jìn)行變量創(chuàng)建
表現(xiàn)為變量提升到作用域頂端進(jìn)行創(chuàng)建
初始化:仍在編譯階段腾节,不同關(guān)鍵字初始化方式不同忘嫉,主要操作是為變量賦值:
var:創(chuàng)建后初始化為undefined
let:執(zhí)行代碼階段進(jìn)行初始化,如果沒(méi)有賦值則初始化為undefined
function:函數(shù)聲明表達(dá)式為var的形式提升到作用域頂端創(chuàng)建案腺,并同時(shí)初始化庆冕,賦值為一個(gè)函數(shù)對(duì)象
const:創(chuàng)建后,執(zhí)行代碼階段進(jìn)行初始化劈榨,同時(shí)必須賦值
執(zhí)行代碼階段:
var:創(chuàng)建變量時(shí)已進(jìn)行了第一次初始化
let:關(guān)鍵字后為第一次執(zhí)行访递,第一次執(zhí)行時(shí)才進(jìn)行初始化,可以不賦值同辣,不賦值則為初始化為undefined
function:
const:關(guān)鍵字后第一次執(zhí)行代碼拷姿,必須賦值
- 所有變量聲明方式都會(huì)將變量提升到作用域頂端惭载,并進(jìn)行創(chuàng)建
- 初始化的表現(xiàn)使得變量聲明不同,產(chǎn)生了變量提升的現(xiàn)象
其他相關(guān)
參考文章和知乎上的知識(shí)响巢,各瀏覽器在console上表現(xiàn)并不是一致的
比如上圖中的問(wèn)題描滔,在chrome的console中,有以下的情況:
- 當(dāng) let聲明變量x如果初始化失敗了踪古,就會(huì)處于一種已創(chuàng)建的狀態(tài)
- 此時(shí)無(wú)法再次對(duì)x進(jìn)行初始化含长,即初始化機(jī)會(huì)只有一次
- 此時(shí)x將處于暫時(shí)死區(qū)(temporal dead zone)
- 于是,此狀態(tài)下的x將是不可用的
但是對(duì)于其他瀏覽器可能報(bào)錯(cuò)信息將不一致伏穆,具體可以參照問(wèn)題中賀老的回答拘泞。
參考:
https://zhuanlan.zhihu.com/p/28140450
https://www.zhihu.com/question/62966713