###### 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)