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