本文首發(fā)于個(gè)人網(wǎng)站:let關(guān)鍵字:ES6新增的var關(guān)鍵字加強(qiáng)版
你好锌仅,今天大叔想和你嘮扯嘮扯 ES6 新增的關(guān)鍵字 —— let
献雅。再說 let
的具體用法之前,大叔想先和你說說大叔自己對(duì) let
的感受 —— let
其實(shí)就是加強(qiáng)版的 var
。為啥這么說呢孟岛?別急,且聽大叔慢慢道來。
首先,let
和 var
的作用是一樣一樣滴轩性,都是用來聲明變量『菰В看到這兒揣苏,你可能會(huì)有個(gè)問題啦,既然作用一樣碰煌,為啥還要再搞個(gè)什么新特性出來舒岸?
想要回答這個(gè)問題绅作,就要說到 let
和 var
的不同之處了芦圾。比方說 var
聲明的全局變量會(huì)自動(dòng)添加到頂級(jí)對(duì)象中作為屬性,而 let
就不會(huì)俄认。再比方說 var
允許聲明提升或者重復(fù)聲明个少,而 let
就不允許這樣做洪乍。當(dāng)然了,它們之間的不同可不止這些夜焦,大叔也只是舉個(gè)栗子而已壳澳。
如果你沒了解過 ES6 的內(nèi)容,看到這兒可能有點(diǎn)懵茫经。沒關(guān)系啊~ 別往心里去巷波,因?yàn)榻酉聛泶笫寰褪且湍銍Z扯嘮扯 let
的具體用法。
聲明的全局變量不是頂級(jí)對(duì)象的屬性
在整明白 let
和 var
第一點(diǎn)不同之前卸伞,大叔要先和你嘮扯嘮扯 var
這個(gè)關(guān)鍵字的一些用法抹镊。為啥?荤傲!var
你要是都整不明白的話垮耳,你還想整明白 let
,那就是一個(gè)美麗的扯遂黍!
首先终佛,咱們都知道其實(shí)聲明一個(gè)全局變量,是既可以使用 var
進(jìn)行聲明雾家,也可以不使用 var
進(jìn)行聲明的铃彰。比方說像下面這段代碼一樣:
var a = 'a'
console.log(a)
b = 'b'
console.log(b)
上面這段代碼不用大叔多扯,想必你也知道打印的結(jié)果是個(gè)啥 —— 打印 a 和 b 嘛芯咧。別急豌研,這才是個(gè)開始,咱不點(diǎn)慢慢來不是~
接下來呢唬党,大叔要用 delete
這個(gè)運(yùn)算符來做個(gè)騷操作了 —— 先用 delete
刪除上面的兩個(gè)變量 a
和 b
鹃共,然后呢再分別打印這兩個(gè)變量的值。
你尋思一下這個(gè)時(shí)候應(yīng)該打印的結(jié)果是啥呢驶拱?對(duì)啦霜浴!變量 a
的值會(huì)正常輸出 a,但變量 b
會(huì)報(bào)錯(cuò) b is not defined
蓝纲。那為啥又是這樣一個(gè)結(jié)果吶阴孟?
大叔覺得你應(yīng)該知道 delete
運(yùn)算符的作用是用來刪除對(duì)象的屬性,但是 delete
是無法刪除變量的税迷。對(duì)啦永丝!你想的沒錯(cuò),這就說明上面聲明的 a
是變量但不是對(duì)象的屬性箭养,而是 b
是對(duì)象的屬性但不是變量慕嚷。
大叔這話說的有點(diǎn)繞,給你帶入一個(gè)場景吧。比如上面這段代碼是在一個(gè) HTML 頁面中定義的 JavaScript 代碼喝检,那 a
就是一個(gè)全局變量嗅辣,b
就是向 window
對(duì)象添加了一個(gè)屬性。所以挠说,delete
運(yùn)算符可以刪除 b
澡谭,但不能刪除 a
的原因了。
那也就是說使用 var
關(guān)鍵字聲明的是變量损俭,不使用 var
關(guān)鍵字聲明的是 window
對(duì)象的屬性唄蛙奖。話嘮叨這兒,大叔還得來個(gè)騷操作杆兵。咱再看一段代碼:
var a = 'a'
console.log(window.a)
var b = 'b'
console.log(window.b)
這段代碼如果按照上面的結(jié)論外永,打印的結(jié)果就應(yīng)該是 undefined 和 b。但是~ 你真實(shí)運(yùn)行一下這段代碼拧咳,就應(yīng)該知道實(shí)際上打印的結(jié)果是 a 和 b伯顶!
這咋和上面的結(jié)論不一樣呢?骆膝!是不是又有點(diǎn)懵祭衩?哈哈~ 別先急著懵逼,這個(gè)問題實(shí)際上是 JavaScript 的作者 Brendan Eich 當(dāng)年在設(shè)計(jì) JavaScript 這門語言時(shí)的一個(gè)小失誤:在全局作用域中聲明的變量同時(shí)會(huì)被作為屬性添加到頂級(jí)對(duì)象中阅签。
可能嘮扯到這兒掐暮,你會(huì)滿屏的吐槽彈幕:這尼瑪誰不知道?政钟!但大叔真正想和你嘮扯的就是這一點(diǎn)路克,這個(gè)小小的失誤,就導(dǎo)致了使用 var
關(guān)鍵字聲明的全局變量會(huì)污染全局對(duì)象的問題养交。
而 ES6 新增的 let
就很好滴彌補(bǔ)了這個(gè)問題精算!也就是說,使用 let
關(guān)鍵字聲明的全局變量不會(huì)污染全局對(duì)象碎连。不信咱可以來試試嘛~ 還是剛才那個(gè)場景灰羽,在一個(gè) HTML 頁面中定義 JavaScript 代碼,僅僅把 var
改成 let
:
let a = 'a'
console.log(a)
console.log(window.a)
這段代碼實(shí)際的運(yùn)行結(jié)果就是 a 和 undefined鱼辙。事實(shí)證明 let
有效滴解決了 var
的問題廉嚼,所以你知道為啥 ES6 要新增一個(gè)關(guān)鍵字來完成和 var
一樣的事兒了吧?倒戏!
不允許重復(fù)聲明
但是怠噪,但可是,可但是~ let
就這么一點(diǎn)點(diǎn)和 var
的區(qū)別嗎杜跷?答案肯定不是滴傍念。咱們還是先來嘮扯嘮扯 var
關(guān)鍵字矫夷,使用 var
聲明的變量是允許反復(fù)滴重復(fù)聲明的,就像下面這段代碼:
var a = 'a'
var a = 'aa'
console.log(a)
這段代碼最終打印的結(jié)果是 aa捂寿,原因就在于 var
聲明的變量是允許重復(fù)聲明的》踉耍可能這會(huì)兒你又會(huì)問了秦陋,這我也知道啊,有啥子問題嗎治笨?
問題肯定是有滴驳概,要是沒有大叔花這么多口舌和你在這兒叨逼叨干啥啊~ 大叔還是給你帶入一個(gè)場景,比方說你定義了一個(gè) JS 文件是需要被其他小伙伴導(dǎo)入使用滴旷赖,那你在這個(gè)文件里面聲明的變量在人家那分分鐘被重新聲明了顺又,你內(nèi)心是個(gè)啥感受?
當(dāng)然了等孵,大叔就是舉個(gè)栗子稚照,你也別太當(dāng)真啦~ 總而言之,就是說咱們在真實(shí)開發(fā)時(shí)對(duì)變量的命名肯定是有規(guī)劃的俯萌,不能隨意就被重新聲明使用果录,這樣會(huì)讓命名空間很亂很亂滴。
你可能有想問了咐熙,這個(gè)問題要怎么解決呢弱恒?答案其實(shí)很簡單,就是使用 ES6 新增的這個(gè) let
關(guān)鍵字棋恼。因?yàn)?let
關(guān)鍵字聲明的變量是不允許被重復(fù)聲明返弹,否則會(huì)報(bào)錯(cuò)滴。不信你也可以看看嘛:
let a = 'a'
let a = 'aa'
console.log(a)
僅僅只是把 var
改成 let
爪飘,這個(gè)結(jié)果就是報(bào)錯(cuò)了义起,報(bào)錯(cuò)的內(nèi)容是:SyntaxError: Identifier 'a' has already been declared
,大概的意思就是變量 a 已經(jīng)被聲明過了师崎。
所以并扇,你看,let
可不是僅僅那么一點(diǎn)點(diǎn)的區(qū)別呢抡诞!
不允許聲明提前
這會(huì)兒你是不是又想問 let
和 var
之間還有沒有其他區(qū)別扒钣肌?大叔也不藏著掖著了昼汗,干脆一口氣都和你說了吧肴熏!你知道使用 var
關(guān)鍵字聲明的變量是允許聲明提前的嗎?啥顷窒?不知道蛙吏!沒事兒源哩,這個(gè)簡單,啥叫聲明提前鸦做,來看段代碼:
console.log(a)
var a = 'a'
你運(yùn)行一下這段代碼励烦,看看打印的結(jié)果是啥?沒錯(cuò)~ 結(jié)果就是 undefined泼诱。為啥不是報(bào)錯(cuò)呢坛掠?原因就是使用 var
關(guān)鍵字聲明的變量允許聲明提前。還是說人話吧治筒,也就是說屉栓,上面這段代碼和下面這段代碼本質(zhì)上是沒區(qū)別的:
var a
console.log(a)
a = 'a'
這樣?jì)饍簩懩憧赡芫兔靼琢藶樯洞蛴〉慕Y(jié)果是 undefined 而不是報(bào)錯(cuò)了吧!但是耸袜,嘿嘿~ 咱們又得嘮扯嘮扯 let
了友多,因?yàn)?let
聲明的變量就不允許聲明提前。不信的話還是給你看段代碼先:
console.log(a)
let a = 'a'
這段代碼運(yùn)行之后打印的結(jié)果就是報(bào)錯(cuò)堤框,報(bào)錯(cuò)的內(nèi)容是:ReferenceError: Cannot access 'c' before initialization
域滥,大概的意思就是無法在聲明變量 c
之前訪問變量 c
。
暫時(shí)性死區(qū)(TDZ)
let
是不是挺屌的吧蜈抓?骗绕!那你想不想知道 let
聲明的變量又為啥不允許聲明提前呢?嘿嘿~ 這是因?yàn)槭褂?let
聲明變量的過程中存在一個(gè)叫做暫時(shí)性死區(qū)(Temporal dead zone资昧,簡稱 TDZ)的概念酬土。
是不是覺得挺高深的?哈哈~ 其實(shí)沒啥高深的格带,大叔就給你嘮扯明白這個(gè)事兒撤缴。規(guī)矩不變,咱還是先看段代碼再說:
if (true) {
console.log(a)
let a;
console.log(a)
a = "a";
console.log(a)
}
大叔想先問問你這段代碼里面三處打印的結(jié)果分別是啥叽唱?你得認(rèn)真的尋思尋思哈~ 這可都是大叔剛和你嘮過的內(nèi)容屈呕。
- 第一處打印的結(jié)果是報(bào)錯(cuò),報(bào)錯(cuò)內(nèi)容就是
ReferenceError: Cannot access 'c' before initialization
- 第二處打印的結(jié)果是 undefined
- 第三處打印的結(jié)果是 b
對(duì)于這樣的結(jié)果棺亭,大叔估計(jì)你應(yīng)該會(huì)明白虎眨,畢竟都是剛嘮過的內(nèi)容。接下來镶摘,你得認(rèn)真的看了嗽桩,因?yàn)榇笫逡湍銇韲Z扯有關(guān)暫時(shí)性死區(qū)的概念了~
所謂的暫時(shí)性死區(qū),就是說使用 let
關(guān)鍵字聲明的變量直到執(zhí)行定義語句時(shí)才會(huì)被初始化凄敢。也就是說碌冶,從代碼從頂部開始執(zhí)行直到變量的定義語句執(zhí)行,這個(gè)過程中這個(gè)變量都是不能被訪問的涝缝,而這個(gè)過程就被叫做暫時(shí)性死區(qū)扑庞。
具體到上面這段代碼的話譬重,實(shí)際上暫時(shí)性死區(qū)的開始和結(jié)束位置就像下面這段代碼標(biāo)注的一樣?jì)饍海?/p>
if (true) {
// 暫時(shí)性死區(qū)開始
console.log(a); // 報(bào)錯(cuò),ReferenceError: Cannot access 'a' before initialization
let a;
// 暫時(shí)性死區(qū)結(jié)束
console.log(a); // 輸出undefined
a = "a";
console.log(a); // 輸出a
}
撈到這會(huì)兒罐氨,大叔相信你應(yīng)該可以明白啥子是暫時(shí)性死區(qū)了臀规。其實(shí)啊,一些新的概念也沒啥難理解的栅隐,主要是你理解的角度和方式的問題塔嬉。
typeof
運(yùn)算符也不再安全
總體上來說,let
關(guān)鍵字要比 var
關(guān)鍵字嚴(yán)格了許多约啊,導(dǎo)致我們開發(fā)時(shí)遇到的問題相應(yīng)會(huì)減少許多邑遏。但 let
就沒有任何問題了嗎佣赖?答案顯然不是滴恰矩,大叔一直信奉一句話:任何技術(shù)都沒有最優(yōu),只有最適合憎蛤。
ES6 新增的 let
關(guān)鍵字也是如此外傅,就比方說剛才咱們撈的暫時(shí)性死區(qū)的內(nèi)容,其實(shí)就有問題俩檬。啥問題呢萎胰?你還記得 JS 里面有個(gè)運(yùn)算符叫做 typeof
吧,就是用來判斷原始數(shù)據(jù)類型的棚辽。這個(gè)運(yùn)算符在 let
出現(xiàn)之前相對(duì)是比較安全的技竟,說白了就是不容易報(bào)錯(cuò)。但在 let
出現(xiàn)之后就不一定了屈藐,比方說如果你把它用在剛才說的暫時(shí)性死區(qū)里面榔组,它就會(huì)報(bào)錯(cuò)了:
if (true) {
console.log(typeof c)
let c;
}
這段代碼最終打印的結(jié)果同樣是報(bào)錯(cuò),報(bào)錯(cuò)內(nèi)容同樣是:ReferenceError: Cannot access 'c' before initialization
联逻。
塊級(jí)作用域
關(guān)于 let
關(guān)鍵字咱們撈到這會(huì)兒搓扯,其實(shí)基本上已經(jīng)嘮完了。但是包归,但可是锨推,可但是~ 嘿嘿~ let
還有一個(gè)最重要的特性大叔還沒和你嘮呢,這重量級(jí)的都得最后出場不是公壤?换可!
那這個(gè)最重要的特性就是啥呢?叫做塊級(jí)作用域厦幅。嘮到作用域想必你應(yīng)該知道在 ES5 中存在兩個(gè):全局作用域和函數(shù)作用域锦担,但在 ES6 中又新增了一個(gè)塊級(jí)作用域。
為什么需要塊級(jí)作用域
想嘮明白什么是塊級(jí)作用域慨削,咱就得從為啥需要塊級(jí)作用域嘮起啊~ 規(guī)矩不變洞渔,還是先看段代碼:
var a = "a"
function fn() {
console.log(a)
if (false) {
var a = "b"
}
}
fn()
你覺得這段代碼運(yùn)行之后打印的結(jié)果應(yīng)該是啥套媚?是 a?是 b磁椒?還是... ...堤瘤?其實(shí)結(jié)果是 undefined。當(dāng)然了浆熔,這個(gè)結(jié)果不難得出本辐,你運(yùn)行一下就能看到。關(guān)鍵在于医增,為啥是這么個(gè)結(jié)果慎皱?!
因?yàn)榫驮谟?ES5 只有全局作用域和函數(shù)作用域叶骨,而上面這段代碼的結(jié)果產(chǎn)生的原因就在于局部變量覆蓋了全局變量茫多。當(dāng)然了,還有比這更麻煩的問題呢忽刽,比方說咱們再看下面這段代碼:
for (var i = 0; i < 5; i++) {
console.log("循環(huán)內(nèi):" + i)
}
console.log("循環(huán)外:" + i)
是不是無比地熟悉吧天揖?!不就是個(gè) for
循環(huán)嘛跪帝!關(guān)鍵在哪今膊?關(guān)鍵在于 for
循環(huán)結(jié)束之后,你會(huì)發(fā)現(xiàn)依舊能訪問到變量 i
伞剑。這說明啥斑唬?說明變量 i
現(xiàn)在是一個(gè)全局變量。當(dāng)然了黎泣,你可能會(huì)說這沒啥問題恕刘,畢竟之前一直不都是這個(gè)樣子的嘛。
什么是塊級(jí)作用域
但是聘裁,大叔要和你說的是雪营,現(xiàn)在不一樣了啊,現(xiàn)在有塊級(jí)作用域啦衡便!啥是塊級(jí)作用域献起?還是看段代碼先:
if (true) {
let b = "b"
}
console.log(b)
這段代碼運(yùn)行之后打印的結(jié)果是報(bào)錯(cuò),報(bào)錯(cuò)的內(nèi)容是:SyntaxError: Lexical declaration cannot appear in a single-statement context
镣陕。
這說明啥谴餐?這就說明現(xiàn)在你使用 let
聲明的變量在全局作用域中訪問不到了,原因就是因?yàn)槭褂?let
聲明的變量具有塊級(jí)作用域呆抑。
接下來你的問題可能就是這個(gè)塊級(jí)作用域在哪呢吧岂嗓?其實(shí)這個(gè)塊級(jí)作用域就是在花括號(hào)({}
)里面。比方說鹊碍,咱們現(xiàn)在把上面那個(gè) for
循環(huán)的代碼用 let
改造一下再看看:
for (let i = 0; i < 5; i++) {
console.log("循環(huán)內(nèi):" + i)
}
console.log("循環(huán)外:" + i)
改造完的這段代碼運(yùn)行之后的結(jié)果就是在循環(huán)結(jié)束后的打印結(jié)果是報(bào)錯(cuò)厌殉,報(bào)錯(cuò)內(nèi)容大叔就不說了食绿,因?yàn)槎家粋€(gè)樣。
塊級(jí)作用域的注意事項(xiàng)
整明白了啥是塊級(jí)作用域公罕,接下來大叔就得和你嘮叨嘮叨需要注意的事兒了器紧。就是在使用 let
關(guān)鍵字聲明塊級(jí)作用域的變量時(shí)可必須在這對(duì) {}
里面啊,不然同樣也會(huì)報(bào)錯(cuò)滴楼眷。
比方說铲汪,咱們經(jīng)常在使用 if
語句時(shí)愛把 {}
省略,但是如果 if
語句里面是使用 let
聲明變量的話就不行了罐柳。不信來看段代碼吧:
if (true) let c = 'c'
這段代碼的運(yùn)行結(jié)果同樣是報(bào)錯(cuò)掌腰,而且報(bào)錯(cuò)內(nèi)容都是一樣的≌偶可是不能忘記哦~
塊級(jí)作用域的作用
好了齿梁,整明白啥是塊級(jí)作用域了,也嘮清楚需要注意的了芦拿,你是不是想問問這塊級(jí)作用域有啥子用處笆快查邢?大叔都想你心里面去了蔗崎,嘿嘿~
你知道匿名自調(diào)函數(shù)吧?還記得怎么寫一個(gè)匿名自調(diào)函數(shù)嗎扰藕?是不是這樣?jì)饍旱模?/p>
(function(){
var msg = 'this is IIFE.'
console.log(msg)
})()
還記得匿名自調(diào)函數(shù)的作用不缓苛?是不是就是為了定義的變量和函數(shù)不污染全局命名空間?邓深!有了 let
未桥,有了塊級(jí)作用域,上面這段匿名自調(diào)函數(shù)就可以寫成這樣?jì)饍旱模?/p>
{
let msg = 'this is IIFE.'
console.log(msg)
}
簡化了不少吧芥备?冬耿!
寫在最后的話
好了,整到這兒萌壳,ES6 新增的 let
關(guān)鍵字所有大叔想和你嘮扯的內(nèi)容都嘮扯完了亦镶,也希望能對(duì)你有所幫助。最后再說一句:我是不想成熟的大叔袱瓮,為前端學(xué)習(xí)不再枯燥缤骨、困難和迷茫而努力。你覺得這樣學(xué)習(xí)前端技術(shù)有趣嗎尺借?有什么感受绊起、想法,和好的建議可以在下面給大叔留言哦~