您好,登錄后才能下訂單哦!
本篇內容介紹了“JS中的var/let/const和暫時性死區實例分析”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
js的變量是松散類型的。變量可以用于保存任何類型的數據。所以js也被稱為弱類型語言。
什么是作用域,簡單來說就是這個變量起作用,能被訪問到、使用到的區域。
定義
變量的值的存儲位置
變量聲明時從棧中申請空間來存儲變量的值。
使用關鍵字
可以使用var、let、const三個關鍵字來定義變量,后兩個關鍵字是自ES6才擁有的,推薦只使用后兩個,不用第一個。當然考慮兼容性的話另說。
定義變量的關鍵詞 變量的標識符名稱[[ = 初始值], 第二個變量名稱[ = 初始值2], 變量3[ = 初始值3], ...];
其中中括號括住的內容為選寫內容,...
表示重復語法。
使用逗號間隔可以同時聲明多個變量。
變量 = 初始值
相當于給變量賦予一個初始的值。當第一次訪問該變量時,若先前沒有對變量進行改變的話,那么獲取到的就是這個初始值。這樣的操作我們稱為初始化。
若是省略初始化這一步,那么獲取到的值就是undefined,該值表示該變量沒有進行初始化,值未定義。
例如
let a = 3, b, c = 4;
這就生成了3個變量a,b,c
,初始值分別為3,undefined,4。
不用關鍵字
直接給一個之前沒定義成變量的或是定義后失效的標識符賦予一個值。那么在執行完該賦值語句時,該標識符將代表一個全局變量【可以先知道這么叫,具體理解需要作用域知識】。
從此刻位置開始,向下任何位置都可以訪問到該變量,哪怕你變量定義在一個塊中,我也可以在塊外訪問到。
但是若是在賦值語句前對該變量進行訪問,會報錯。
此時該變量的作用域稱為全局作用域
console.log(m);//訪問不到,報錯導致整個程序終止,下面的語句不執行 m = 109; console.log(m);//刪除上面報錯的語句刪除后,該句會執行,且訪問到
注意:
當這個語法放在函數中時,仍會創建出一個全局變量而不是局部變量。只不過當這個函數在真正執行之前,該變量都不會被創建。當函數執行之后,才會被創建。后面的代碼才能訪問到該變量。
這是因為js為解釋型語言。可以近似看作解析一句代碼執行一句,當然這么說不準確,但可以簡單這樣子理解。所以函數未被執行前,該全局變量無法被瀏覽器獲知,也就沒有創建。
function test() { m = 10; } //此行以及以上的代碼,除了test函數內部外,都無法訪問m,m未被創建 test();//此行代碼執行完畢后,可以訪問m
使用該關鍵字聲明的變量擁有的特點:
聲明的變量為局部或是全局變量:
若是在一個函數中或是在一個塊中定義該變量,那么這個變量將會是屬于該塊【或函數】的局部變量。在塊或函數的內部是可以訪問到的。但是在外部就不行了。同時也意味著,在出了這個塊的時候,該變量就會被銷毀。下次在訪問到該塊的時候,會創建一個同名的新的變量。而不是原來的。 不過定義在最外面的全局作用域中,它就變成了個全局變量。
function a() { var x = 1; console.log(x);//當執行函數時,該語句會輸出x } a();//輸出x的值,也就是1.說明x在a函數內部可以訪問 console.log(x);//報錯
作用域提升:
我們知道當我們訪問一個未定義的變量時,他會報錯。但是下方代碼卻好像違背了常識。
console.log(a);//不報錯且輸出undefined var a = 1; console.log(a);//1
按理說,解釋一句代碼,執行一句代碼的話,只能在定義了變量之后,我才知道這個變量。那為什么使用了var定義后就可以在前面訪問呢?
這是因為js會將使用var
所定義的變量的作用域提升,將var a = 1;
分成了兩個部分:var a;
與a = 1;
。并且將var a;
放到a定義位置所處的那個塊中的最前面【最外層的代碼我們說他處于一個同步代碼塊中,雖然沒有花括號,但是也看做一個塊】。并將a = 1;
留在了原地。
所以上面的代碼可以認為等同于下方代碼。
注意:變量提升【作用域提升】,僅僅會將變量的聲明提升,初始化的賦值語句則會留在原地。所以塊開頭到聲明部分的中間那段區域中,變量的值為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); }
定義的變量的作用域為函數作用域或全局作用域
當var定義的變量是函數作用域時,var是在塊中定義的變量。從塊的開始到塊的結束都能訪問到他所定義的變量。
當var定義的變量為全局作用域時,var是在最外層的同步代碼塊定義的變量。在代碼中的任何一個位置都可訪問到。
聲明的變量若是在最外層同步代碼塊【也叫全局作用域】中會作為window對象的屬性
字面意思。當定義變量語句放在全局作用域中,那么它就會被掛載在全局上下文的變量對象身上【這里以后講上下文就懂了】,而全局上下文的變量對象可以通過window訪問。
所以會作為window的屬性。
允許冗余聲明
即允許var定義多個同名的變量。
你可以將它理解為:var定義的語句都被分為var 標識符;
與標識符 = 初始值;
兩部分。第一部分都被提到塊的最前面,重合的都被合并。第二部分留在原位。
所以相當于一個變量在不同的位置被不停地變換值而已。
var a = 1, a = 2; console.log("結果"); console.log(a);//輸出2 var a = 3;
var a; a = 1; var a; console.log(a)//輸出1
for循環頭部定義的變量不會滲透在循環外部
比如說:
for(var i = 0; i < 5; i++) { console.log(i); } console.log(i);
在輸出1,2,3,4,5后,仍然會輸出5,而不是報錯。
其實就是相當于下面的代碼:
var i; for(i = 0; i < 5; i++) { console.log(i); } console.log(i);
使用let命名的變量所擁有的特點與var差不多。但是【下面就是5個區別了,唯2的共同點可以說是定義的語法和定義出來的變量可以為局部或是全局變量吧】
不具作用域提升這個特點
而從塊的開頭到定義這個變量的位置之間的區域,我們叫做暫時性死區。
let定義的變量為塊級作用域
根據作用域的概念來講。由于var與let所定義的變量的可訪問的區域不同。
所以let定義的變量的作用域自然與var所定義的作用域不同。
塊級作用域:變量從定義處到塊結束都可以被訪問到,其他地方不行。
不允許同一個塊中冗余聲明
同字面意思。
首先,不允許let在同一個塊中定義多個標識符相同的變量。
而且,若是有不用標識符定義的全局變量或是var定義的變量,與let定義的變量的標識符相同,且處于同一個塊中,那么,會報錯。
但是嵌套重復定義是允許的。【js中聲明和定義是一回事】【以下理論出自紅寶書】
由于js引擎【就是負責執行js代碼的那個東東】會記錄:聲明或使用的標識符和該標識符所在的塊作用域。
并且進行對比查重。而查重過程中只有塊作用域不同而標識符相同時,才會報冗余定義的錯。
像是let a = 1; a = 3;
這段代碼中,這兩個a是同一個塊作用域。
而let a = 2,a = 3;
這段代碼中,這兩個a是不同的塊作用域,因為聲明的位置不同,所以該變量的塊作用域的起始位置不同,塊作用域本身自然不同。所以會報錯。
所以若是在不同的塊中定義相同的變量是可以的,不會報錯。
但是多層嵌套重復定義的變量在使用時究竟用哪一個呢?
這種現象我們叫做作用域覆蓋。其實聽名字都能大概猜出最終會用哪個了。
外層的作用域被內層的作用域覆蓋掉。而當前重復的區域的歸屬權自然不是被覆蓋掉的作用域,而是覆蓋者。而變量使用誰自然是看當前位置是處于哪個作用域中。
function a() { let a = 1; //下方對a訪問是訪問a變量而不是a函數,也算是一個作用域覆蓋。 console.log(a);//輸出1 function b() {//若是改成a也會報錯。同樣冗余定義 //console.log(a); 會報錯,也是冗余問題。使用的也會被記錄 let a = "s"; console.log(a); } b();//輸出s } a();//執行的是上面的函數。
在全局作用域中定義變量,變量不會作為window的屬性
字面意思,let定義的變量他不會被掛載在全局上下文的變量對象上。但是它仍然是在全局作用域中被定義的,所以在全局的定義位置到全局結束都可訪問到該變量。
for循環頭部定義的變量會滲透到循環外部
與var不同,let在for頭部定義的變量并不會滲透出去。它相當于定義在了for循環的循環體的那個代碼塊內部。
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);
像這種的區別要是單純使用for循環那么不足一提。但是若是在for循環使用閉包函數引用這個迭代變量i,就會出問題了。
最典型的例子是這個:【不懂閉包的就光看結果吧,以后介紹閉包的時候會舊事重提的。】
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
是延時函數,第一個參數是回調函數【要執行的函數】,第二個參數是延遲的時間【單位ms】。表示延遲多長時間執行該函數。
由于閉包函數會延長變量的生命周期。所以i的生命周期會被延長。當執行setTimeout
函數的回調【就是第一個參數】時,i
仍然是當時調用setTimeout
函數時的那層for
循環的i
。但是由于每一層for
循環使用的其實是同一個變量。而回調又是延時后執行的,for
循環在回調執行時早就運行完了。所以最終輸出的會是i
這個唯一的迭代變量在經過5次循環后的值——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++; }
由于每一層循環都是一個新的變量。所以回調所引用的是不同的變量。輸出的自然是我們期望的0,1,2,3,4
和let的效果、特點近乎一樣。
唯一的不同就是:const變量在定義的同時必須初始化。再者定義完成后變量對應的那個棧中的存儲空間的值就不允許被改變了,否則會報錯。【變量聲明時從棧中申請空間來存儲變量的值】
透露一點。若是const變量被賦值為一個js對象【準確的來說是一個引用類型的數據】時,對象的屬性是允許被改變的。因為const僅僅限制了棧中值不允許被改變。而對象在給變量時,是將這個對象本身的地址賦給了棧中的存儲空間。而他自身的數據存儲在堆之中。所以堆中的數據如何修改不關const的事。
不同<script>中聲明的變量能否使用
可以使用,無論是內嵌代碼塊還是外部引用的代碼塊,但凡是該頁面中的代碼。只要是已經聲明且聲明在全局作用域中,那么在定義的<script>
后面的<script>
中就可以訪問到該變量。
但是有一個要注意的點是:一定要保證一個標識符在之前的代碼塊中沒有被定義過,才能再次定義,否則會報錯。
COOKBOOK
推薦使用關鍵字來創建變量。因為在塊中定義的全局變量很難維護,容易造成困惑。且在嚴格模式下不允許不使用關鍵字就創建變量。
當要考慮兼容不允許ES6的瀏覽器時,請全部使用var定義變量。
當不用考慮ES6以前標準的兼容時,請盡量全部使用const和var。其中不改變的一些內容,要使用const定義。最好做到const優先使用。
“JS中的var/let/const和暫時性死區實例分析”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。