ECMAScript6 入門:var、let、const 差異

本篇文章介紹 JavaScript ES6 之中,let 與 const 的重要觀念與使用上的注意事項。

GITHUB

過去常用的 var、window

之前寫 JavaScript 總是用 var 宣告變數,但使用 var 宣告的變數,會汙染全域變數。

舉例:

1
2
3
4
5
var a = 1;
console.log(a);
for (var i = 0; i < 3; i++) {
console.log(i);
}

我們可以使用「開發人員工具」輸入 window 去搜尋,會發現最上方出現 a 這個全域變數。
而在 ES6 推出後,有了「區塊域」的概念與 let 的寫法,就能避免掉使用 var 會汙染全域變數的副作用了!

ES6 優缺點概述

  • 優點
    • 解決 ES5 的 BUG 與不便之處
    • 盡量避免汙染全域變數:維護性較高、不會污染其他開發者
  • 小問題
    • 舊版瀏覽器之問題:使用 Babel + Gulp 可解決

ES6 - let 的特性

letconst 用來宣告區塊裡的變數,即「區域變數」,而所謂的「區塊」就是指這個 { 大括號 } 裡面的東西。

我們直接舉個例子來瞭解 let 的特性:

1
2
3
4
5
6
7
8
var a = 0; // 這個 a 是全域變數
function changeA() {
let a = 0; // 這個 a 是區域變數
a = 1;
console.log(a); // 結果為 1
}
changeA();
console.log(a); // 結果為 0

上述例子中,呈現在 console 的結果為 1 與 0。

為什麼?

首先,第五行 console.log(a) 的結果為 1,因為 a 在第四行的時候,被重新賦予值為 1。

那第二個 console.log(a) 的答案怎麼不是 1 呢?

因為 function changeA(){...} 裡面的 a 只會存活在那個區塊 { } 裡面,所以 function changeA(){...} 裡面的 a = 1 並沒有變更到外面的 a。
因此第二個 console.log(a) 就會回傳 0 的結果。

註:ES6 建議 JavaScript 都寫成 “函數式”
盡量以函式呼叫的方式去做設計,不要使用到 var

let 的 for 用法

我們透過 let 使用於 for 迴圈的時候常見的小問題,來進一步解釋 let 的特性。

下方程式碼當中,我們希望上方的列表被點擊時,會跳出 alert 來通知我們是點到哪一個列表。
因此,我們先監聽 li 並加上 JavaScript 的 click 事件:

1
2
3
4
5
6
<!-- HTML -->
<ul class="list">
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
1
2
3
4
5
6
7
8
9
// Javascript
const listLength = document.querySelectorAll('.list li').length;
for (var i = 0; i < listLength; i++) {
document
.querySelectorAll('.list li')
[i].addEventListener('click', function () {
alert(i + 1);
});
}

完成之後,雖然點擊 li 確實會彈出 alert,但是我們卻發現不管點擊的是哪一個 li,彈跳出來的 alert 的內容都是 4。

這就是因為它的值被「全域變數」所影響。
for 迴圈跑完之後,i 的值已經等於 i++ 完的結果,汙染了全域變數。

改善方式:使用 let

1
2
3
4
5
6
7
8
const listLength = document.querySelectorAll('.list li').length;
for (let i = 0; i < listLength; i++) {
document
.querySelectorAll('.list li')
[i].addEventListener('click', function () {
alert(i + 1); // 用 let 的話,會在大括號區塊內重新綁定
});
}

重點就在於 for(let i=0; i<listLength; i++){ } 右邊的大括號區塊。

因為 let 的功能就是在 for 執行「每一次」的時候,裡面的 i 都可以存活在個別的大括號作用域裡面。
就是因為這樣,彼此執行的內容才不會像是使用全域變數一樣,會個別干擾!

以白話一點的方式說明:
let i = 0 時,i 存活在 i 為 0 的 { } 作用域裡面,執行 function (){ alert(i+1)},變成 alert(1)
let i = 1 時,i 存活在 i 為 1 的 { } 作用域裡面,執行 function (){ alert(i+1)},變成 alert(2)
… 之後以此類推。

ES 6 - const 的特性

const 是唯讀變數(不能去做修改),常用在一些不能被變更的變數。
例如: url 網址、圓周率 ( PI = 3.14159 )。

不過有個例外,就是 { 物件 } 跟 [ 陣列 ] 還是會被變更,這方面可以用 freeze() 方法解決。

Object.freeze()

freeze() 的功能顧名思義,就是「凍結」一個物件,用於防止物件新增屬性,或是防止原有的屬性被刪除。

1
2
3
4
5
6
const obj = {
url: 'https://sealman.com',
};
Object.freeze(obj); // 使用 freeze 就不能修正了
obj.url = '30';
console.log(obj.url); // 30 -> https://sealman.com

然而,在哪些情況下我們會要使用 freeze() 呢?
通常是有些預設的東西不想被干擾或更改的時候,可以使用 constfreeze (物件或陣列)的特性!

let 與 const 的注意事項

Hoisting (向上提升)

什麼是 Hoisting ?
我們舉個例子來解釋吧:

1
2
3
4
// var a; // 相當於有這一行存在
console.log(a); // undefined
var a = 1;
console.log(a); // 1

上方程式碼中,第一次的 console.log(a) 為何不是顯示找不到 a 這個變數,而是顯示 undefined 呢?

這是因為 JavaScript 在編譯時,預設會將 var 做「向上提升」,當建立變數或函式時,會自動提升到最上方。

但是,這裡如果將 var 改為 let,變成 let a = 1; 的話,第一次的 console.log(a) 就會抓不到值囉!

1
2
3
4
console.log(a); // 抓不到值
let a = 1;
// const a = 1;
console.log(a); // 1

整理: var 有向上提升的特性, letconst 沒有

在同個區塊 { } 上不能重複命名

  • var 可以重新賦予其值
1
2
var a = 1;
var a = 2; // var 可以重新賦予
  • letconst 不能重新賦予值
1
2
3
4
let a = 1;
let a = 2; // a 已被賦予值
const b = 1;
const b = 2; // b 已被賦予值

const 與 let 不會在全域變數 (window)裡面

這裡可以使用開發人員工具,在 console 輸入 window 檢查。

1
2
3
let a = 1;
const b = 1;
var c = 1;

我們會發現 a 與 b 都不會出現在 window 裡,但是 c 會出現在 window 裡,這是因為 c 是全域變數。
這邊就能回到我們一開始所說的,使用 letconst 能避免掉使用 var 會汙染全域變數的副作用囉!

總結

在 ES6 推出後,寫 JavaScript 時可以多使用 letconst,少一點使用 var
這麼做除了可以避免使用 var 可能出現的錯誤,也能增加程式碼的可讀性,像是閱讀程式碼的人看到使用 const 所宣告的變數,就會知道這個變數是不能做改變的。

以上資源是我自己整理過後的筆記,若有錯誤歡迎隨時和我聯繫。