(Day 22) ES6 的 let 、const

前言

在 ES6 新增兩種變數方法 letconst ,不過我們比較常把 const 叫做常數,主要是因為 var 的特性 容易觸發 Bug , 這邊與 var 的差異主要有:

  • 作用域範圍不同
  • 提升特性不同
  • 全域不掛在 window 下

在提及這些不同特性之前,先大致介紹一下這兩個用法的差別。

let 、 const 基本介紹

let 能夠重新賦值,但不能重複宣告

1
2
3
let String1 = 'test1'
String1 = 'test2'
let String1 = 'test3' // 重複宣告錯誤, Uncaught SyntaxError: Identifier 'String1' has already been declared

const 不能重新賦值,也不能重新宣告。

1
2
3
const String1 = 'test1'
const String1 = 'test2' // 重複宣告錯誤, Uncaught SyntaxError: Identifier 'String1' has already been declared
String1 = 'test3' // 賦值錯誤, Uncaught TypeError: invalid assignment to const 'String1'

要補充一點如果 const 的值是物件,對物件底下的屬性賦值, const 則能接受這種操作繼續使用。

1
2
3
4
5
6
7
const obj = {}
obj.name = 'Ryder'
console.log(obj) // { name: 'Ryder' }

const array = []
array.push(1)
console.log(array) // [1]

作用域不同

在過去 var 作用域是根據函式作用域,而 letconst 則是以 { ... } Block 區塊做為作用域,來看看以下範例:

1
2
3
4
5
6
7
8
9
var name1 = 'Ryder'
let name2 = 'Ryder'

function test(){
var name1 = 'Jack'
let name2 = 'Jack'
}
test()
console.log(name1, name2) // ?

結果是 Ryder, Ryder ,這範例很好理解,不論是 var 還是 letname = 'Jack' 的作用範圍都只在 test() 這個函式中,console 的位置則是在全域,自然都會是 Ryder ,那麼再來看看這個範例:

1
2
3
4
5
6
7
8
9
10
11
var name1 = 'Ryder'
let name2 = 'Ryder'

{
var name1 = 'Jack'
}

{
let name2 = 'Jack'
}
console.log(name1 , name2) // ?

結果會是 Jack, Ryder ,這是因為上面提到的, let 是以 { ... } 區塊做為作用域,因此 let name1 = 'Jack' 這個語法的有效範圍只會存在於 { ... } 之中,而 var 則會被 { ... } 中的 var name1 = 'Jack' ,直接做替換,因此 name1 會是 'Jack'

這邊要補充一下, 這邊提到的 { ... } 並不是物件,而是一個作用域範圍,主要是為了搭配 letconst 特性 ES6 才引入的,不過實做中通常不會刻意使用 { ... } 去區分作用域。

提升特性不同

在提升章節我們有提到,JavaScript 在編譯程式碼時,會分為兩個階段:
1.創造階段
2.執行階段
var 變數會先在 創造階段 被建立,進入執行階段才會實際賦值,而在創造階段中的 var 變數,他的值會是 undefined ,如這個範例:

1
2
console.log(name1)  // undefined
var name1 = 'Ryder'

let 雖然也有提升概念,也同樣分成:
1.創造階段
2.執行階段
但在創造階段和 var 不同,let 在創造階段不是直接顯示 undefined ,他是進入一個 暫時性死區 (TDZ) 的狀況,MDN 文件是這麼描述的:

The variable is in a “temporal dead zone” from the start of the block until the initialization is processed

如果我們在 暫時性死區 的狀態去取得 let 變數的值,瀏覽器會跳錯,要注意的是,不同瀏覽器跳出的錯誤訊息會不同,如下範例:

FireFox 版本

1
2
console.log(name2)  // Uncaught ReferenceError: can't access lexical declaration 'name1' before initialization
let name2 = 'Ryder'

Chrome 版本

1
2
console.log(name2)  //Uncaught ReferenceError: name1 is not defined
let name2 = 'Ryder'

全域建立變數

上面有提到到 letconst 是根據 { ... } 來區分作用域的,這邊要提一點的是 var 全域建立時,會是掛在 window 下。

而使用 letconst 在全域建立變數時,他並不會掛在 window 下,但我們若直接呼叫變數,他也會正確顯示,如範例

1
2
3
4
5
6
7
8
var name1= 'Ryder'
let name2 = 'Ryder'

window.name1 // 'Ryder'
window.name2 // undefined

name1 // 'Ryder'
name2 // 'Ryder'

這個原因是出在全域執行環境(Global space) 上面,首先這個全域執行環境其實是由兩個環境所組成的

  • 全域物件 - Object Env
  • 宣告環境 - Declare Env

因此全域執行環境(Global space) 其實是一個由雙環境組成的東西,一般來說我們是看不到 Declare Env 的。

所以 var 其實是基於 ObjectEnv 宣告並加入到 Declare Env,而 letconst 則是只會宣告在 Declare Env 中,這也就是為什麼我們無法在 Window 上面看到由 letconst 宣告的變數但卻又可以正常取得到值的原因。

參考文獻