---
# System prepended metadata

title: var、let、const的差別
tags: [學習, JS, 變數]

---

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