ES6 章節:Let 及 Const

Vicky
宅宅薇琪 [前端學習筆記]
12 min readFeb 24, 2021

今天來介紹 ES6 的兩大主角 Let 及 Const,看看他們究竟有什麼本事,可以取代 var 的變數正宮地位。

Let、Const 和 Var 一樣都是宣告變數使用的,Let、Const 也是為了改變 Var 在宣告變數上的問題而產生。 傳統var 到底有什麼缺點,才需演變成導入 letconst,這樣對程式又能有什麼幫助呢 ? 讓我們從淺談 var 特性說起。

Var 基本概念

先來看看使用 Var 來宣告變數會遇到甚麼樣的問題~

  1. 先使用 var 來宣告變數。
  2. 假設後面不曉得前面有使用 var 宣告過變數了。後面又使用 var 來宣告相同的變數名稱。
  3. 當我們宣告兩個同名的變數時,在原本 var 這種宣告的方式是沒有問題的,後者會覆蓋前面的變數。但在開發時並不是有意的要把後面變數覆蓋前面的,其實是希望另外宣告一個變數,在這時候就會出現一些問題。
var Casper = '卡斯伯';
var Casper = '卡伯'; // 宣告兩個同名的變數時,後者會覆蓋前面的變數
console.log(Casper); // 卡伯

再來試著在 for 迴圈裡使用 Var 來宣告變數會遇到甚麼樣的問題~

for 迴圈裡的 i 其實是「全域變數」

  • 在 for 迴圈內可以取到 i 的值
  • 在 for 迴圈外也可以取到 i 的值

當我們希望把 i 的值控制在 for 迴圈內的話,就會有問題。

  • i 會汙染我們所在的作用域
  • 我們可以透過 window.i 找到 i 的值
for (var i = 0; i < 10; i++) {   // i 屬於「全域變數」,會汙染整個作用域
console.log(i) // ( 顯示 0 ~ 9 )
}
console.log(i); // 10 (在外層依然可以取到此「for 迴圈」裡的值)
console.log(window.i); // 10

相同的問題也會出現在「判斷式」裡~
在「判斷式」裡宣告一個變數,變數也是屬於「全域變數」依然會汙染整個作用域,所以在外層依然可以取到此「判斷式」裡的值。

var answer = true;
if (answer) {
var myFeedback = '同意'; // 屬於「全域變數」,會汙染整個作用域
console.log(myFeedback); // 同意
}
console.log(myFeedback); // 同意 (在外層依然可以取到此「判斷式」裡的值)

var 的作用域是在「函式」的區塊內,所以若要限制此作用域,就必須使用「立即函式」將他包起來。

最直接的方式就是使用「立即函式」

透過這種方式可以把 for 迴圈包在「立即函式」裡,就可避免在外層還取到 for 迴圈裡面的值。

(function(){       // 使用「立即函式」把 for 迴圈包起來
for (var i = 0; i < 10; i++) {
console.log(i) // ( 正確顯示 0 ~ 9 )
}
}());
console.log(i);// i is not defined (在「立即函式」外層無法取到 i 這個變數)

Let, Const 基本概念

使用 let 所宣告的變數,可以修改他的值

  • 可以對 let 所宣告的變數重新賦予值

使用 const 所宣告的變數,不能修改他的值

  • 若使用 const 所宣告的變數又重新賦予值會跳錯 (你無法對一個常數重新賦予新值)

使用 var 所宣告的變數,可以修改他的值

let Casper = '卡斯伯'; 
Casper = '卡伯'; // let 可以重新賦予值
console.log(Casper); // 卡伯

const Casper = '卡斯伯';
Casper = '卡伯'; // const 無法重新再賦予值
console.log(Casper); // 跳錯 (無法對一個常數重新賦予新值)

Let, Const 無法在「相同作用域」下再重新宣告重複的變數

var 可以重新宣告重複的變數名稱

這種方式可避免在同個作用域下使用 Let, Const 重複宣告

let Casper = '卡斯伯';
let Casper = '卡伯'; // Let 不可重複宣告相同的變數名稱
console.log(Casper); // 跳錯 ('Casper' has already been declared)

const Casper = '卡斯伯';
const Casper = '卡伯'; // Const 不可重複宣告相同的變數名稱
console.log(Casper); // 跳錯 ('Casper' has already been declared)

若 Let, Const 的變數所在作用域不同,就不算重複宣告

let Casper = '卡斯伯';   // 作用域在全域
{
let Casper = '卡伯'; // 作用域在 block 內
console.log(Casper); // 卡伯
}
console.log(Casper); // 卡斯伯

Let, Const 的作用域在 block 內

var 的作用域是「函式作用域」

Let, Const 在 for 迴圈的作用域在 block 內,外層取不到 i 值

for (let i = 0; i < 10; i++) {
console.log(i) // ( 顯示 0 ~ 9 )
}
console.log(i); // i is not defined

Let, Const 的作用域在 block 內,外層取不到 myFeedback 值

var answer = true;
if (answer) {
let myFeedback = '同意'; // let 作用域只在 block 內,外層存取不到
console.log(myFeedback); // 同意
}
console.log(myFeedback); // myFeedback is not defined

若使用 var 宣告變數,必須使用「立即函式」限制 i 的作用域

(function(){       // 使用「立即函式」把 for 迴圈包起來
for (var i = 0; i < 10; i++) {
console.log(i) // ( 顯示 0 ~ 9 )
}
}());
console.log(i);// i is not defined (在「立即函式」外層無法取到 i 這個變數)

若使用 let 宣告變數就不須使用「立即函式」來限制 i 的作用域

for (let i = 0; i < 10; i++) {
console.log(i) // ( 顯示 0 ~ 9 )
}
console.log(i); // i is not defined

若使用 let 宣告變數,也可直接使用「block 區塊」來限制 i 的作用域

  • 外層無法存取「block 區塊」內的變數
{
let i = 0
console.log(i) // 0 (限制變數 i 只存活在 block 的作用域內)
}
console.log(i); // i is not defined

const 宣告時候需要賦予值,否則會出現語法錯誤

const a;
console.log(a);
// 跳錯,SyntaxError: Missing initializer in const declaratio

let 則不會跳錯

let a;
console.log(a); // undefined

📝 注意 : 變數無法使用 delete 刪除

在一般狀況下使用 var 與沒有使用 var 最大差別在於「可不可以被 delete 刪除」。

首先 delete 是一個刪除物件屬性的語法。

如果沒有使用 var 宣告的變數「會被當作物件屬性新增」的方式新增,因此就會強烈建議你變數一定要使用 var 宣告,否則可以被刪除的變數是很容易出現系統上的 bug。

var a = 'a';
delete a; // false

b = 'b';
delete b; // ture

📝 注意 : let、const 無法從 window 中找到

使用 var 宣告變數後都可以在 window 下找到相關變數

var myName = 'Tom';
console.log(window.myName); // Tom

使用 let、const 宣告變數無法從 window 下找到相關變數

  • 使用 let、const 宣告的變數無法在 window 底下找到,但可以正確的呼叫取得該變數的值。
  • 也無法使用 delete 刪除此變數 myName。
let myName = 'Tom';
console.log(window.myName); // undefined
console.log(myName); // Tom

delete myName;
console.log(myName); // Tom (無法使用 delete 刪除此變數)

回顧比較 var 、let、const 的特性

Var 基本概念

  • 可以對 var 所宣告的變數重新賦予值 : 可以宣告兩個同名的變數,後者會覆蓋前面的變數。
  • var 的作用域是在「函式」的區塊內 : 若要限制此作用域,就必須使用「立即函式」將他包起來。就可避免在外層還取到 Var宣告變數的值。

Let, Const 基本概念

  • 可以對 let 所宣告的變數重新賦予值
  • 不能對 const 所宣告的變數重新賦予值
  • let、const 無法從 window 中找到
  • Let, Const 的作用域在 block 內
  • Let, Const 無法在「相同作用域」下再重新宣告重複的變數
  • 若 Let, Const 的變數所在作用域不同,就不算重複宣告
  • const 宣告時候需要賦予值,否則會出現語法錯誤 (let 則不會跳錯)

Let, Const 實戰運用技巧

✏️ 範例一

setTimeout 會重複執行 console.log 的結果,預期會將變數 i 從 0–9 都執行一次。

用 var 宣告的變數 i 屬於「全域變數」,所以在外層的變數 i 是執行到最後的結果。

for (var i = 0; i < 10; i++) {  // 使用 var 宣告的變數為「全域變數」

// setTimeout 是「非同步」的函式,會先到「事件柱列」裡,等到所有程式碼都執行
完後,才會執行這段
setTimeout(function () {
console.log('這執行第' + i + '次'); // 取到「全域變數」裡的 i
}, 0);
}
console.log(i); // 10

結果 setTimeout 無法符合預期顯示 0–9,而是顯示 “這執行第 10 次”。

  • setTimeout 是「非同步」的函式,setTimeout 會先到「事件柱列」裡,等到所有程式碼都執行完後,才會執行這段。此時 setTimeout 函式裡取到的變數 i 是「全域變數」裡的 i,非 for 迴圈裡的 i。

解決方法 : 使用 let 宣告變數

let 不會產生「全域變數」,且作用域只會在「block區塊」裡產生作用。所以 setTimeout 在執行時就會取到正確數值的變數 i。

for (let i = 0; i < 10; i++) {  
setTimeout(function () {
console.log('這執行第' + i + '次'); // 可以正常取到 i 並顯示 0-9
}, 0);
}
console.log(i); // i is not defined

✏️ 範例二

當使用 var 宣告變數時

使用 Object.freeze(物件) 的語法,只有凍結內層的屬性 ,外層的物件是可以被修改的,這時就無法維持他固定的狀態。

var person = {  // 宣告一個物件
name: '小明',
money: 500
}
person.name = '杰倫'; // 調整物件裡的屬性值
console.log(person); // {name: "杰倫", money: 500}

Object.freeze(person); // 當此物件被凍結後,就無法調整他的內層屬性
person.money = 1000;
console.log(person); // {name: "杰倫", money: 500}

person = {};
console.log(person); // { } (變空物件,外層物件可以被重新賦予值)

解決上例問題 : 外層物件被修改的方法 (使用 const 宣告變數)

前面有提到 const 是宣告一個常數,使用 const 所宣告的變數是無法再做調整的。

  • 由 person.name = ‘杰倫’ 得知, 基本上並沒有把此物件直接替換掉。物件是傳參考的特性,只要不直接替換掉此物件,是可以修改裡面的屬性值,依然維持常數的特性。
  • 由 person = {} 得知,除非把物件 person 又賦予到另一個物件上,就會跳錯。

使用 Object.freeze(物件) 的語法,只有凍結內層的屬性 ,外層的物件是可以被修改的。

  • 但若配合 const 宣告變數,外層的物件也無法被修改 (無法被另外賦值)。
const person = {       // 宣告一個物件
name: '小明',
money: 500
}
person.name = '杰倫'; // 調整物件裡的屬性值
console.log(person); // {name: "杰倫", money: 500}
Object.freeze(person); // 當此物件被凍結後,就無法調整他的內層屬性
person.money = 1000;
person = {}; // 跳錯,Assignment to constant variable

--

--