一文讀懂JS中的var/let/const和暫時(shí)性死區(qū)
js中變量的特征
js的變量是松散類(lèi)型的。變量可以用于保存任何類(lèi)型的數(shù)據(jù)。所以js也被稱為弱類(lèi)型語(yǔ)言。
變量的定義與訪問(wèn)
簡(jiǎn)單說(shuō)下作用域
什么是作用域,簡(jiǎn)單來(lái)說(shuō)就是這個(gè)變量起作用,能被訪問(wèn)到、使用到的區(qū)域。
語(yǔ)法
定義
變量的值的存儲(chǔ)位置
變量聲明時(shí)從棧中申請(qǐng)空間來(lái)存儲(chǔ)變量的值。
使用關(guān)鍵字
可以使用var、let、const三個(gè)關(guān)鍵字來(lái)定義變量,后兩個(gè)關(guān)鍵字是自ES6才擁有的,推薦只使用后兩個(gè),不用第一個(gè)。當(dāng)然考慮兼容性的話另說(shuō)。
定義變量的關(guān)鍵詞 變量的標(biāo)識(shí)符名稱[[ = 初始值], 第二個(gè)變量名稱[ = 初始值2], 變量3[ = 初始值3], ...];
其中中括號(hào)括住的內(nèi)容為選寫(xiě)內(nèi)容,...
表示重復(fù)語(yǔ)法。
使用逗號(hào)間隔可以同時(shí)聲明多個(gè)變量。
變量 = 初始值
相當(dāng)于給變量賦予一個(gè)初始的值。當(dāng)?shù)谝淮卧L問(wèn)該變量時(shí),若先前沒(méi)有對(duì)變量進(jìn)行改變的話,那么獲取到的就是這個(gè)初始值。這樣的操作我們稱為初始化。
若是省略初始化這一步,那么獲取到的值就是undefined,該值表示該變量沒(méi)有進(jìn)行初始化,值未定義。
例如
let a = 3, b, c = 4;
這就生成了3個(gè)變量a,b,c
,初始值分別為3,undefined,4。
不用關(guān)鍵字
直接給一個(gè)之前沒(méi)定義成變量的或是定義后失效的標(biāo)識(shí)符賦予一個(gè)值。那么在執(zhí)行完該賦值語(yǔ)句時(shí),該標(biāo)識(shí)符將代表一個(gè)全局變量【可以先知道這么叫,具體理解需要作用域知識(shí)】。
從此刻位置開(kāi)始,向下任何位置都可以訪問(wèn)到該變量,哪怕你變量定義在一個(gè)塊中,我也可以在塊外訪問(wèn)到。
但是若是在賦值語(yǔ)句前對(duì)該變量進(jìn)行訪問(wèn),會(huì)報(bào)錯(cuò)。
此時(shí)該變量的作用域稱為全局作用域
console.log(m);//訪問(wèn)不到,報(bào)錯(cuò)導(dǎo)致整個(gè)程序終止,下面的語(yǔ)句不執(zhí)行 m = 109; console.log(m);//刪除上面報(bào)錯(cuò)的語(yǔ)句刪除后,該句會(huì)執(zhí)行,且訪問(wèn)到
注意:
當(dāng)這個(gè)語(yǔ)法放在函數(shù)中時(shí),仍會(huì)創(chuàng)建出一個(gè)全局變量而不是局部變量。只不過(guò)當(dāng)這個(gè)函數(shù)在真正執(zhí)行之前,該變量都不會(huì)被創(chuàng)建。當(dāng)函數(shù)執(zhí)行之后,才會(huì)被創(chuàng)建。后面的代碼才能訪問(wèn)到該變量。
這是因?yàn)閖s為解釋型語(yǔ)言。可以近似看作解析一句代碼執(zhí)行一句,當(dāng)然這么說(shuō)不準(zhǔn)確,但可以簡(jiǎn)單這樣子理解。所以函數(shù)未被執(zhí)行前,該全局變量無(wú)法被瀏覽器獲知,也就沒(méi)有創(chuàng)建。
function test() { m = 10; } //此行以及以上的代碼,除了test函數(shù)內(nèi)部外,都無(wú)法訪問(wèn)m,m未被創(chuàng)建 test();//此行代碼執(zhí)行完畢后,可以訪問(wèn)m
var
使用該關(guān)鍵字聲明的變量擁有的特點(diǎn):
聲明的變量為局部或是全局變量:
若是在一個(gè)函數(shù)中或是在一個(gè)塊中定義該變量,那么這個(gè)變量將會(huì)是屬于該塊【或函數(shù)】的局部變量。在塊或函數(shù)的內(nèi)部是可以訪問(wèn)到的。但是在外部就不行了。同時(shí)也意味著,在出了這個(gè)塊的時(shí)候,該變量就會(huì)被銷(xiāo)毀。下次在訪問(wèn)到該塊的時(shí)候,會(huì)創(chuàng)建一個(gè)同名的新的變量。而不是原來(lái)的。 不過(guò)定義在最外面的全局作用域中,它就變成了個(gè)全局變量。
function a() { var x = 1; console.log(x);//當(dāng)執(zhí)行函數(shù)時(shí),該語(yǔ)句會(huì)輸出x } a();//輸出x的值,也就是1.說(shuō)明x在a函數(shù)內(nèi)部可以訪問(wèn) console.log(x);//報(bào)錯(cuò)
作用域提升:
我們知道當(dāng)我們?cè)L問(wèn)一個(gè)未定義的變量時(shí),他會(huì)報(bào)錯(cuò)。但是下方代碼卻好像違背了常識(shí)。
console.log(a);//不報(bào)錯(cuò)且輸出undefined var a = 1; console.log(a);//1
按理說(shuō),解釋一句代碼,執(zhí)行一句代碼的話,只能在定義了變量之后,我才知道這個(gè)變量。那為什么使用了var定義后就可以在前面訪問(wèn)呢?
這是因?yàn)閖s會(huì)將使用var
所定義的變量的作用域提升,將var a = 1;
分成了兩個(gè)部分:var a;
與a = 1;
。并且將var a;
放到a定義位置所處的那個(gè)塊中的最前面【最外層的代碼我們說(shuō)他處于一個(gè)同步代碼塊中,雖然沒(méi)有花括號(hào),但是也看做一個(gè)塊】。并將a = 1;
留在了原地。
所以上面的代碼可以認(rèn)為等同于下方代碼。
注意:變量提升【作用域提升】,僅僅會(huì)將變量的聲明提升,初始化的賦值語(yǔ)句則會(huì)留在原地。所以塊開(kāi)頭到聲明部分的中間那段區(qū)域中,變量的值為undefined
var a; console.log(a);//不報(bào)錯(cuò)且輸出undefined a = 1; console.log(a);//1
同樣下方兩個(gè)代碼效果相同:
function a() { console.log(x); var x = 1; console.log(x); }
function a() { var x; console.log(x); x = 1; console.log(x); }
定義的變量的作用域?yàn)楹瘮?shù)作用域或全局作用域
當(dāng)var定義的變量是函數(shù)作用域時(shí),var是在塊中定義的變量。從塊的開(kāi)始到塊的結(jié)束都能訪問(wèn)到他所定義的變量。
當(dāng)var定義的變量為全局作用域時(shí),var是在最外層的同步代碼塊定義的變量。在代碼中的任何一個(gè)位置都可訪問(wèn)到。
聲明的變量若是在最外層同步代碼塊【也叫全局作用域】中會(huì)作為window對(duì)象的屬性
字面意思。當(dāng)定義變量語(yǔ)句放在全局作用域中,那么它就會(huì)被掛載在全局上下文的變量對(duì)象身上【這里以后講上下文就懂了】,而全局上下文的變量對(duì)象可以通過(guò)window訪問(wèn)。
所以會(huì)作為window的屬性。
允許冗余聲明
即允許var定義多個(gè)同名的變量。
你可以將它理解為:var定義的語(yǔ)句都被分為var 標(biāo)識(shí)符;
與標(biāo)識(shí)符 = 初始值;
兩部分。第一部分都被提到塊的最前面,重合的都被合并。第二部分留在原位。
所以相當(dāng)于一個(gè)變量在不同的位置被不停地變換值而已。
var a = 1, a = 2; console.log("結(jié)果"); console.log(a);//輸出2 var a = 3;
var a; a = 1; var a; console.log(a)//輸出1
for循環(huán)頭部定義的變量不會(huì)滲透在循環(huán)外部
比如說(shuō):
for(var i = 0; i < 5; i++) { console.log(i); } console.log(i);
在輸出1,2,3,4,5后,仍然會(huì)輸出5,而不是報(bào)錯(cuò)。
其實(shí)就是相當(dāng)于下面的代碼:
var i; for(i = 0; i < 5; i++) { console.log(i); } console.log(i);
let與暫時(shí)性死區(qū)
使用let命名的變量所擁有的特點(diǎn)與var差不多。但是【下面就是5個(gè)區(qū)別了,唯2的共同點(diǎn)可以說(shuō)是定義的語(yǔ)法和定義出來(lái)的變量可以為局部或是全局變量吧】
不具作用域提升這個(gè)特點(diǎn)
而從塊的開(kāi)頭到定義這個(gè)變量的位置之間的區(qū)域,我們叫做暫時(shí)性死區(qū)。
let定義的變量為塊級(jí)作用域
根據(jù)作用域的概念來(lái)講。由于var與let所定義的變量的可訪問(wèn)的區(qū)域不同。
所以let定義的變量的作用域自然與var所定義的作用域不同。
塊級(jí)作用域:變量從定義處到塊結(jié)束都可以被訪問(wèn)到,其他地方不行。
不允許同一個(gè)塊中冗余聲明
同字面意思。
首先,不允許let在同一個(gè)塊中定義多個(gè)標(biāo)識(shí)符相同的變量。
而且,若是有不用標(biāo)識(shí)符定義的全局變量或是var定義的變量,與let定義的變量的標(biāo)識(shí)符相同,且處于同一個(gè)塊中,那么,會(huì)報(bào)錯(cuò)。
但是嵌套重復(fù)定義是允許的?!緅s中聲明和定義是一回事】【以下理論出自紅寶書(shū)】
由于js引擎【就是負(fù)責(zé)執(zhí)行js代碼的那個(gè)東東】會(huì)記錄:聲明或使用的標(biāo)識(shí)符和該標(biāo)識(shí)符所在的塊作用域。
并且進(jìn)行對(duì)比查重。而查重過(guò)程中只有塊作用域不同而標(biāo)識(shí)符相同時(shí),才會(huì)報(bào)冗余定義的錯(cuò)。
像是let a = 1; a = 3;
這段代碼中,這兩個(gè)a是同一個(gè)塊作用域。
而let a = 2,a = 3;
這段代碼中,這兩個(gè)a是不同的塊作用域,因?yàn)槁暶鞯奈恢貌煌?,所以該變量的塊作用域的起始位置不同,塊作用域本身自然不同。所以會(huì)報(bào)錯(cuò)。
所以若是在不同的塊中定義相同的變量是可以的,不會(huì)報(bào)錯(cuò)。
但是多層嵌套重復(fù)定義的變量在使用時(shí)究竟用哪一個(gè)呢?
這種現(xiàn)象我們叫做作用域覆蓋。其實(shí)聽(tīng)名字都能大概猜出最終會(huì)用哪個(gè)了。
外層的作用域被內(nèi)層的作用域覆蓋掉。而當(dāng)前重復(fù)的區(qū)域的歸屬權(quán)自然不是被覆蓋掉的作用域,而是覆蓋者。而變量使用誰(shuí)自然是看當(dāng)前位置是處于哪個(gè)作用域中。
function a() { let a = 1; //下方對(duì)a訪問(wèn)是訪問(wèn)a變量而不是a函數(shù),也算是一個(gè)作用域覆蓋。 console.log(a);//輸出1 function b() {//若是改成a也會(huì)報(bào)錯(cuò)。同樣冗余定義 //console.log(a); 會(huì)報(bào)錯(cuò),也是冗余問(wèn)題。使用的也會(huì)被記錄 let a = "s"; console.log(a); } b();//輸出s } a();//執(zhí)行的是上面的函數(shù)。
在全局作用域中定義變量,變量不會(huì)作為window的屬性
字面意思,let定義的變量他不會(huì)被掛載在全局上下文的變量對(duì)象上。但是它仍然是在全局作用域中被定義的,所以在全局的定義位置到全局結(jié)束都可訪問(wèn)到該變量。
for循環(huán)頭部定義的變量會(huì)滲透到循環(huán)外部
與var不同,let在for頭部定義的變量并不會(huì)滲透出去。它相當(dāng)于定義在了for循環(huán)的循環(huán)體的那個(gè)代碼塊內(nèi)部。
for (let i = 0; i < 5; i++) { console.log(i); } console.log(i);//報(bào)錯(cuò) //和下方的代碼效果等價(jià) while(true) { let i; if (i < 5) { console.log(i); } else break; i++; } console.log(i);
像這種的區(qū)別要是單純使用for循環(huán)那么不足一提。但是若是在for循環(huán)使用閉包函數(shù)引用這個(gè)迭代變量i,就會(huì)出問(wèn)題了。
最典型的例子是這個(gè):【不懂閉包的就光看結(jié)果吧,以后介紹閉包的時(shí)候會(huì)舊事重提的?!?/p>
for(var i = 0; i < 5; i++) { setTimeout(()=>console.log(i), 4); } //最終輸出5個(gè)5,等同于下面代碼 var i; for(i = 0; i < 5; i++) { setTimeout(()=>console.log(i), 4); }
setTimeout
是延時(shí)函數(shù),第一個(gè)參數(shù)是回調(diào)函數(shù)【要執(zhí)行的函數(shù)】,第二個(gè)參數(shù)是延遲的時(shí)間【單位ms】。表示延遲多長(zhǎng)時(shí)間執(zhí)行該函數(shù)。
由于閉包函數(shù)會(huì)延長(zhǎng)變量的生命周期。所以i的生命周期會(huì)被延長(zhǎng)。當(dāng)執(zhí)行setTimeout
函數(shù)的回調(diào)【就是第一個(gè)參數(shù)】時(shí),i
仍然是當(dāng)時(shí)調(diào)用setTimeout
函數(shù)時(shí)的那層for
循環(huán)的i
。但是由于每一層for
循環(huán)使用的其實(shí)是同一個(gè)變量。而回調(diào)又是延時(shí)后執(zhí)行的,for
循環(huán)在回調(diào)執(zhí)行時(shí)早就運(yùn)行完了。所以最終輸出的會(huì)是i
這個(gè)唯一的迭代變量在經(jīng)過(guò)5次循環(huán)后的值——5.
而若是let則沒(méi)這個(gè)問(wèn)題了。
for (let i = 0; i < 5; i++) { setTimeout(()=>console.log(i), 4); } //和下方的代碼效果等價(jià) while(true) { let i; if (i < 5) { setTimeout(()=>console.log(i), 4); } else break; i++; }
由于每一層循環(huán)都是一個(gè)新的變量。所以回調(diào)所引用的是不同的變量。輸出的自然是我們期望的0,1,2,3,4
const
和let的效果、特點(diǎn)近乎一樣。
唯一的不同就是:const變量在定義的同時(shí)必須初始化。再者定義完成后變量對(duì)應(yīng)的那個(gè)棧中的存儲(chǔ)空間的值就不允許被改變了,否則會(huì)報(bào)錯(cuò)?!咀兞柯暶鲿r(shí)從棧中申請(qǐng)空間來(lái)存儲(chǔ)變量的值】
透露一點(diǎn)。若是const變量被賦值為一個(gè)js對(duì)象【準(zhǔn)確的來(lái)說(shuō)是一個(gè)引用類(lèi)型的數(shù)據(jù)】時(shí),對(duì)象的屬性是允許被改變的。因?yàn)閏onst僅僅限制了棧中值不允許被改變。而對(duì)象在給變量時(shí),是將這個(gè)對(duì)象本身的地址賦給了棧中的存儲(chǔ)空間。而他自身的數(shù)據(jù)存儲(chǔ)在堆之中。所以堆中的數(shù)據(jù)如何修改不關(guān)const的事。
不同<script>中聲明的變量能否使用
可以使用,無(wú)論是內(nèi)嵌代碼塊還是外部引用的代碼塊,但凡是該頁(yè)面中的代碼。只要是已經(jīng)聲明且聲明在全局作用域中,那么在定義的<script>
后面的<script>
中就可以訪問(wèn)到該變量。
但是有一個(gè)要注意的點(diǎn)是:一定要保證一個(gè)標(biāo)識(shí)符在之前的代碼塊中沒(méi)有被定義過(guò),才能再次定義,否則會(huì)報(bào)錯(cuò)。
COOKBOOK
- 推薦使用關(guān)鍵字來(lái)創(chuàng)建變量。因?yàn)樵趬K中定義的全局變量很難維護(hù),容易造成困惑。且在嚴(yán)格模式下不允許不使用關(guān)鍵字就創(chuàng)建變量。
- 當(dāng)要考慮兼容不允許ES6的瀏覽器時(shí),請(qǐng)全部使用var定義變量。
- 當(dāng)不用考慮ES6以前標(biāo)準(zhǔn)的兼容時(shí),請(qǐng)盡量全部使用const和var。其中不改變的一些內(nèi)容,要使用const定義。最好做到const優(yōu)先使用。
到此這篇關(guān)于一文讀懂JS中的var/let/const和暫時(shí)性死區(qū)的文章就介紹到這了,更多相關(guān)JS var let const內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JavaScript判斷數(shù)組的方法總結(jié)與推薦
這篇文章主要給大家介紹了關(guān)于JavaScript判斷數(shù)組方法的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2022-02-02JS判斷瀏覽器類(lèi)型與版本的實(shí)現(xiàn)代碼
在JS中判斷瀏覽器的類(lèi)型,估計(jì)是每個(gè)編輯過(guò)頁(yè)面的開(kāi)發(fā)人員都遇到過(guò)的問(wèn)題2012-10-10three.js中文文檔學(xué)習(xí)之通過(guò)模塊導(dǎo)入
這篇文章主要給大家介紹了關(guān)于three.js中文文檔學(xué)習(xí)之通過(guò)模塊導(dǎo)入的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或使用three.js具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。2017-11-11JavaScript模擬實(shí)現(xiàn)繼承的方法
這篇文章主要介紹了JavaScript模擬實(shí)現(xiàn)繼承的方法,實(shí)例分析了javascript類(lèi)的操作與模擬實(shí)現(xiàn)繼承的技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-03-03JavaScript實(shí)現(xiàn)QQ聊天消息展示和評(píng)論提交功能
這篇文章主要為大家詳細(xì)介紹了JavaScript實(shí)現(xiàn)QQ聊天消息展示和評(píng)論提交功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-05-05淺析JavaScript回調(diào)函數(shù)應(yīng)用
這篇文章主要為大家詳細(xì)介紹了JavaScript回調(diào)函數(shù)應(yīng)用,感興趣的朋友可以參考一下2016-05-05js實(shí)現(xiàn)帶三角符的手風(fēng)琴效果
本文主要介紹了js實(shí)現(xiàn)帶三角符手風(fēng)琴效果的實(shí)例。具有很好的參考價(jià)值,下面跟著小編一起來(lái)看下吧2017-03-03原生JS版和jquery版實(shí)現(xiàn)checkbox的全選/全不選/點(diǎn)選/行內(nèi)點(diǎn)選(Mr.Think)
腳本之家小編之前整理不少checkbox全選全不選這方便的文章,但看了這篇以后發(fā)現(xiàn)實(shí)現(xiàn)方法更好2016-10-10JavaScript html5利用FileReader實(shí)現(xiàn)上傳功能
這篇文章主要為大家詳細(xì)介紹了JavaScript html5利用FileReader實(shí)現(xiàn)上傳功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-03-03