JavaScript(10):var、let、const之間的差異解析

Blog背景圖

作用域(Scope)的差別

作用域(Scope)是指在程式碼中變數和函式的可訪問範圍

var 函數作用域(function scope)

  • 變數在函數中的任何地方都是可讀取的。
  • 如果在函數之外的地方嘗試訪問變數,將會顯示該變數不存在ReferenceError的錯誤

  • 變數作用域說明
    • 第一個 var a = 20(作用域:1-3行)
    • 第二個 var a = 30(作用域:4-6行)

var作用域

  • 範例如下
1
2
3
4
5
6
7
function funcA() {
var a = 20
}
function funcB() {
var a = 30
}
console.log(a)

顯示如下

1
Uncaught ReferenceError: a is not defined

全域變數擁有全域作用域(Global Scope)

  • var變數在函式(Function)外宣告時,該變數會自動成為全域物件屬性。
  • let宣告的變數不會成為全域物件屬性。其作用域僅限於宣告它的區塊(block scope)或函式內部。

範例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Test</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/1.6.2/axios.min.js"></script>
</head>
<body>
<script>
var a = 10
let b = 20
console.log(window)
</script>
</body>
</html>
  • 將全域物件window印出,可以看到剛剛設定的a已經變成window的一種屬性

Window全局屬性


若跳過宣告的流程,宣告的變數也會變成全域物件屬性

範例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Test</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/1.6.2/axios.min.js"></script>
</head>
<body>
<script>
function test(){
a = 100
}
test()
console.log(window)
</script>
</body>
</html>

跳過宣告的情形下,變成Window全局屬性

Window全局屬性(跳過宣告的情形)

let/const 塊級作用域(block scope)

  • 變數在聲明的**區塊{}**(例如,if 語句、迴圈)內可讀取的
  • 如果在區塊之外的地方嘗試訪問變數,將會顯示該變數不存在ReferenceError的錯誤

最簡單的定義方式就是單純{}

範例如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Test</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/1.6.2/axios.min.js"></script>
</head>
<body>
<script>
{
const a = 100
console.log(a)
}
</script>
</body>
</html>

顯示如下

塊級作用域範例印出


搭配以下範例說明let/const 變數作用域

  • const Title = “數字太大了”(作用域:2-5行)

塊級作用域(IF範例作用域)

  • 範例如下
1
2
3
4
5
6
7
8
function funcA(Value) {
if (Value > 100) {
const Title = "數字太大了"
console.log("if區域", Title)
}
console.log("我是funcA的最下面", Title)
}
funcA(120)

顯示如下

1
2
if區域 數字太大了
Uncaught ReferenceError: Title is not defined
  • Title作用域在if的括號之內(紅色框框),因此 if 外層的console.log(“我是funcA的最下面”, Title)無法正確讀取到Title變數,出現ReferenceError錯誤

塊級作用域(解釋外層讀取內層變數錯誤方式)

const & let 差異

let與const重新賦值差別

  • let可以重新賦值,案例如下
1
2
let x = 10; // 宣告一個let變數
x = 20 // 合法
  • const不可以重新賦值,案例如下
1
2
const x = 10 // 宣告一個const變數
x = 20 // 不合法

顯示如下

1
TypeError: Assignment to constant variable.

const可針對物件型別內容物修改

  • const宣告的變數,無法更改原始型別,但可以修改物件型別的屬性

範例如下(物件型別屬性修改)

1
2
3
4
5
6
const a = {
name: "hello"
}
a.name = "Antonio"

console.log(a)

顯示如下

1
{ name: 'Antonio' }

const常數變數宣告就必須賦值

範例如下(無賦值發生錯誤)

1
2
const a 
console.log(a)

顯示如下

1
SyntaxError: Missing initializer in const declaration

作用鍊(Scope chain)

當使用變數時,依序從當前作用域開始,一層一層往外尋找是否有符合其作用域的變數。這個搜尋的過程形成了作用鍊

作用鍊(Scope chain)


試著將上圖的程式碼改成console.log(a + b + c + d)顯示如下

  • 因為已經到最外層還是找不到宣告變數d的地方,因此出現ReferenceError錯誤
1
Uncaught ReferenceError: d is not defined

for迴圈的使用差異

  • 情境:使用setTimeout定時器,模擬異步事件。查看透過var及let變數在迴圈中的變化

var定義的index,會彼此共用影響

  • var結束的時候,index = 10(全部的index都會被影響)
1
2
3
4
5
for (var index = 0; index < 10; index++) {
setTimeout(()=>{
console.log("var index", index)
}, 0)
}

顯示如下
for迴圈(var迴圈案例)

let是重新宣告出來,彼此互不影響(只在單一區塊作用)

1
2
3
4
5
for (let index = 0; index < 10; index++) {
setTimeout(()=>{
console.log("let index", index)
}, 0)
}

顯示如下
for迴圈(let迴圈案例)

提升(Hoisting)的差別

JavaScript編譯階段將變數和函式的宣告存入記憶體的概念,使函式和變量的宣告看起來好像被提升到作用域的頂部(實際程式碼的順序不變),但賦值的動作並沒有提升

var變數宣告時會自動存入記憶體並初始化定義undefined

範例如下:

1
2
console.log(x)
var x = 100

顯示如下

1
undefined

實際上瀏覽器看到的是類似以下的順序(並不會真的改變程式碼順序):

1
2
3
var x
console.log(x)
x = 100
  • 執行順序上,var變數會優先提升至作用域的頂部並初始化定義為undefined

const/let變數提升時會陷入暫時性死區(Temporal Dead Zone, TDZ)

範例如下:

1
2
console.log(x)
let x = 100

顯示如下

1
ReferenceError: Cannot access 'x' before initialization

透過不同方式更確定let的區塊性提升

範例如下

1
2
3
4
5
6
var a = 10
function test(){
console.log(a)
let a = 20
}
test()
  • 因為let的a變數提升,導致console的結果並未顯示10

顯示如下

1
ReferenceError: Cannot access 'a' before initialization

重複宣告

var變數可以重複宣告

範例如下

1
2
3
var a = 10
var a = 20
console.log(a)

顯示如下

1
20
  • 同樣變數的宣告會被替代,因此最後印出來的是最後一個20

let變數不可以重複宣告

範例如下

1
2
3
let a = 10
let a = 20
console.log(a)

顯示如下

1
SyntaxError: Identifier 'a' has already been declared
  • 同一區塊內不可以宣告同樣的變數