###### tags: `學習` `JS` `變數` {%hackmd BJrTq20hE %} # var、let、const的差別 ## 1.為什麼要宣告變數?-全域區域汙染 ### 1.先了解什麼是全域屬性 宣告一個全域屬性,範例如下 ```javascript= a = 1 ; ``` 沒有看錯,就是這麼簡單就完成了全域屬性的宣告。 但是全域屬性有個問題 ```javascript= function fna(){ a = 1; } function fna1(){ a = 2; } console.log(a);//2 ``` console.log(a) 最後會顯示a為2,也就是說後面的所宣告的a = 2;全域屬性把前面宣告a = 1;的全域屬性給覆蓋了。 ```javascript= a = 1; function fna(){ a = 2; } console.log(a);//2 ``` 這次我們試著在function外宣告全域屬性,但是可以看到依然被 function a(){}內部的 a = 2; 給覆蓋掉。 ## 2.變數宣告-作用域 為了處理全域屬性覆蓋的問題,需要宣告變數,變數宣告之後會有作用域 ```javascript= function fna(){ var a = 1; } function fna1(){ var a = 2; } console.log(a);// a is not defined ``` 根據上面的範例會看到a is not defined 因為在function內宣告a為變數的時候,a的做用範圍只會存在在function內,兩function的a不會互相影響。 ## 3.屬性可以被刪除,變數不能被刪除 ```javascript= //宣告全域屬性 a = 0 ; delete window.a console.log(window)//會找不到a //宣告變數 var b = 1 ; delete window.b console.log(window)// b屬性存在 console.log(b)//1 ``` ## 4.var的特性 ### 1.詞法做用域 ```javascript= var a = 0; function fna (){ console.log(a)//0 } function fna1 (){ var a = 1 ; console.log(a)//1 } console.log(a)//0 ``` 從上的範例可以看到 function fna會向外查找var a = 0;而且function fna1內部宣告的var a = 1不會影響外面的var a = 0 ### 2.var的作用域在程式碼撰寫完得當下就已經確定了 ```javascript= var a = 0; function fna (){ console.log(a); } function fnb (){ var a = 1; console.log(a) } fnb()//1 fna()//0 ``` 可以看到fna執行後向外查找var a = 0;而且fnb的執行順序也不會影響var a = 0; ```javascript= var a = 0; function fna (){ console.log(a);//0 } function fnb (){ var a = 1; fna() } fnb() ``` 無論fna()在哪裡執行(被包在fnb內)最後還是會回到從第2行開始執行fna,一樣向外查找var a = 0;而不是fnb內所宣告的變數var a= 1; ### 3.var的其他特性 1.重複宣告也沒問題。 ```javascript= var a = 1; var a = 0; var a = 5; console.log(a)//5 ``` 2.從外面是找不到function內部宣告的變數 ```javascript= function fna(){ var a = 1; } console.log(a)// a is not defind ``` 3.可以從外向內找區塊內所宣告的變數 ```javascript= { var a = 1; } console.log(a)//1 ``` 4.在for迴圈中用var宣告的i如果會洩漏出來 for迴圈屬於block ```javascript= for(var i = 0 ; i<10;i++){ console.log(i)//0 1 2 ...9 } console.log(i)//10 ``` 從上面的例子中看到for迴圈內i= 10的後就該停下來,不會顯示,但是因為用var宣告的變數是可以由外面往區塊內讀取所以才會讀到i = 10 5.在for迴圈中用setTimeout ```javascript= for(var i = 0 ; i<10;i++){ setTimeout(function(){ console.log(i)//10 ... 10 出現10次10 }) } ``` 上述範例中會出現10個10的原因,是因為setTimeout執行的位置都會是在javascript的最後,所以會取到i值為10 ### 4.hoisting 在還沒宣告變數以前常試取得變數就是hoisting ```javascript= console.log(a)//a is not definded hoisting var a = 1; console.log(a)//1 ``` ## let與var的差異 1.先來看看let在區塊內宣告後再重區塊外取得用let宣告的變數會怎麼樣? ```javascript= { let a = 1; } console.log(a)// a is not definded ``` 與用var不同會取不到區塊內所宣告的變數,只有let會被區塊限制。 2.再來試function內會怎麼樣 ```javascript= function fna(){ let a = 0; } console.log(a)// a is not definded ``` 這一點與var是相同的,作用域都會被function限制,從外面取不到function的變數 3.這時候先看看for迴圈中i用var宣告時setTimeout的情況 ```javascript= for(var i = 0 ; i<10;i++){ setTimeout(function(){ console.log(i)//10...10 }) } ``` 會出現上面的情況是因為var在迴圈中會洩漏到全域變數,i=10賦值然後再繼續跑setTimeout,再來看看for迴圈中i用let宣告時setTimeout的情況 ```javascript= for(let i = 0 ; i<10;i++){ setTimeout(function(){ console.log(i)//0 ... 9 }) } ``` 這個時候就能正常運作了,因為i不會從區塊洩漏出來 4.使用let所宣告的變數,不會出現在windo上 ```javascript= let a = 0; var b =1; console.log(windo);//會找不到a 但是會找到b:1 ``` 5.不能使用let重複宣告相同的變數 ```javascript= let a = 0; let a =1; // 'a' has already been declared ``` 與用var可以重複宣告相同的變數不同,let不能在同區塊重複宣告相同的變數。 6.暫時性死區(TDZ) ```javascript= function fna(a){ console.log(a) var a = 2; } fna(1)//1 ``` 使用var 宣告變數結果是1 ```javascript= function fna(a){ console.log(a) let a = 2; } fna(1)// 'a' has already been declared ``` 不允許let 或 const 宣告值之前取得值,這樣子會造成暫時性死亡。 原因在準備階段時原本使用var宣告的變數是undefined,但在使用const、let時則是什麼都沒有(不是null),所以就無法取得執行之後的變數賦值。 ```javascript= function fn(){ console.log(a) let a = 'a' } fn() // Uncaught ReferenceError: Cannot access 'a' before initialization // 準備階段 let a ; // 但是這個時候 a什麼都沒有 // 執行階段 console.log(a) // 因為準備階段a什麼都沒有,自然取不到值而報錯 a = 'a' // 因為上一行報錯所以這行不會執行 ``` ### let與var的差異簡易對照表 | 情況 | var | let、const| |------|-------|------| | 從外取得區塊內所宣告的值 | 可 | 不可| | 從外取得function內所宣告的值 | 不可 | 不可| | 重複宣告變數 | 可 | 不可| | 存在window上 | 存在 | 不存在| | 暫時性死亡 | 否 | 是| ## let與const的差異 1.重複賦值 ```javascript= let a = 0; a = 5; a = 'string'; a = true; console.log(a)//true ``` 使用let 宣告的變數可以重複賦值 ```javascript= const a = 0; a = 5; a = 'string'; a = true; console.log(a)//Assignment to constant variable ``` 會報錯不能重複賦值 2.物件傳參考 ```javascript= const a = { name:'Joy' } a.name = 'Mary' a.job = 'teacher' console.log(a)//{ name:'Mary job:'teacher' } ``` 因為物件傳參考的特性所以使用const宣告的物件可以修改與增加屬性值 ```javascript= const a = { name:'Joy' } a = { name:'Mary' } console.log(a)//Assignment to constant variable } ``` 因為 a ={ name:'Mary'}等於又賦值了一個新的物件,因為const不能重複宣告所以報錯 如果物件的指向會改變(再次賦值),則使用let宣告物件,如果物件不會改變指向則推薦使用const ## let與const的差異總結 | 情況 | const | let| |------|-------|------| | 變數重複賦值 | 不可 | 可| ## 補充說明let 的作用在function的作用域 範例1 ```javascript= let a = 2; function num(){ let a = 5; console.log(a) } num()//5 console.log(a)//2 ``` num函式印出5 console.log印出2 ```javascript= let a = 2; function num(){ a = 5; console.log(a) } num()//5 console.log(a)//5 ``` 這裡num函式與console.log皆印出5 原因是因為在function內沒有宣告a的變數,所以會"向外"找變數,所以全域變數的a會被重新賦值 ```javascript= let a = 2; function num(){ let a =3; a = 5; console.log(a) } num()//5 console.log(a)//2 ``` num函式印出5 console.log印出2 因為function內有宣告a變數所以在function內,對a賦值指會對function內的a影響,對全域變數的a沒有影響。 --- 參考資料 [JavaScript 那個 let, const, var 到底差在哪?](https://www.youtube.com/watch?v=FGdKdn_CnWo&t=4s)