Day18 | JS 關於 let、 const、var
ES6 的 let、const 是為了改變 var 在宣告變數上的一些問題。
❐ var 宣告可能會產生的問題
var具函式作用域,所以不受區塊限制,但會受到函式範圍限制。var也是一種宣告變數的方式,可覆寫值,與 let 類似但相對較不嚴謹,但目前已經很少使用var因為較容易發生奇怪的問題 (var會汙染全域變數,容易造成不可預期的錯誤 )。
範例 1
程式量大的時候你可能會忘記取過什麼變數,所以會出現重複宣告的情況,蓋掉之前寫的變數的值。
1
2
3var name = 'abc';
var name = 'ccc';
// var 是允許重複宣告變數的,且 console 不會丟任何錯誤提示給你,而 let 和 const 會。var在函式外宣告屬於 global variables(全域變數,意即 JS 任何地方都可以使用),在函式內則為該函式整個區域都可以使用 → 這是屬於 scope(作用域的範疇)。主要都是圍繞在 Redeclartion(重新宣告)、Scope(作用域)、Hoisiting(提升)、TDZ 這幾個主題,有興趣的話也可以查查這些關鍵字。1
2
3
4
5
6
7
8
9
10
11
12function sayHi() {
var name = "andy";
for (var i = 1; i<=3; i++) {
var num = 0;
if (i = 3) {
num = i;
}
}
console.log(`${name}的座號是 ${num} 號`);
// 可以取得 for 迴圈的變數 num,若是改用 let 宣告 num,它是取不到 for 迴圈內的 num 的,會報錯
}
sayHi();
範例 2. for 迴圈
1 | for (var i = 0; i < 10; i++){ |
for 迴圈使用 var 宣告 i 會污染全域:
- for 迴圈中,
var i = 0;中的i因為是用var宣告,所以為全域變數,在 for 迴圈外也取得到,所以如果想要把 i 控制在 for 迴圈內就會產生一些問題。- 這邊可透過
window.i來查看並找到i的值。
- 這邊可透過
範例 2. 出處:六角學院 JavaScript 核心篇 / Let, Const 基本概念
範例 3. 判斷式
1 | var answer = true; |
除了上方的 for 迴圈,在判斷式也會有相同問題 → 污染全域
❐ let
let宣告的變數可重新賦予新的值。- 可取出
let宣告過的變數,並重新賦予新的值。但不可使用let再重新宣告相同的變數,會出現錯誤訊息Uncaught SyntaxError: Identifier 'myName' has already been declared,這樣就可以避免同一個作用域下使用 let 做重覆宣告。
1 | // 正確用法:宣告過的變數重新賦予新的值 |
❐ const
const是宣告一個常數,所以基本上使用const宣告的變數是沒辦法被調整的。( cosnt 在原始型別難以被覆寫 )隨時需要做調整的變數值的話可使用
let,不會去更改值的話可使用const。const在 object{}與 Array[]中使用是可被修改的。除非使用Object.freeze(變數);會凍結裡面的內容無法做修改。
![在 object `{}` 與 Array `[]` 中使用是可被修改的](https://ithelp.ithome.com.tw/upload/images/20221003/20119743Udt3GrDNYJ.png)

❐ var、let、const 作用域
- 所謂作用域即「變數有效的作用範圍」,最大為全域作用域範圍,指變數有效範圍是全部範圍;區塊作用域指的是
{}大括號的範圍。 - ES6 前,沒有區塊作用域概念 ( block funciton ),僅有全域 ( global scope ) 與函式作用域 ( function scope ),
var宣告的變數具有函式作用域的特性,代表**切分變數有效範圍的最小單位是function**。 - ES6 後,新增區塊作用域概念 ( block funciton ),let / const 宣告的變數才具有區塊作用域的特性,切分變數最小單位的有效範圍式是
{}block。
➊ var 具函式作用域
var具函式作用域,所以不受區塊{}限制,但會受到函式function範圍限制。
因為 var 具函式作用域,所以上方「var 可能會產生的問題 」中範例 1、2、3 方式都會污染全域,除非用 function 包住。可見 function 外就讀取不到 i 的變數 ( i is not defined )。
範例1.
1 | // 這邊使用立即函式包覆 |
範例 2.
var 具函式作用域,所以不受區塊限制,但會受到函式範圍限制
1 | // --- var不受區塊限制,但會受到函式範圍限制 |
➋ const、let 具區塊作用域
範例 1.
const、let 具區塊作用域,所以有效作用域範圍會被限制在該區域中
1 | // --範例一 |
❐ let 、const 實作技巧
實作 1. for 迴圈使用 var 宣告
1 | for (var i = 0; i < 10; i++) { |
解析:
for 迴圈中使用 var 宣告變數 i 是全域變數,所以 for 迴圈外 console.log(i); 中的 i 會是執行到最後的結果 10 ( 為 0 到 9 個執行一次的狀態 )。
setTimeout為非同步的程式, JS 會放到事件緒列內,等到所有程式都執行完才回來執行這個非同步程式。所以setTimeout中的i會是全域變數的i並不是 for 迴圈內的i。
答案:
- 所以無法如預期中依依印出 0 到 9
console.log(i);會印出10、setTimeout會印出 10 次這執行第 10 次。
實作 2. for 迴圈使用 let 宣告
1 | for (let i = 0; i < 10; i++) { |
解析:
- for 迴圈中使用
let宣告變數i就不會是全域變數,因為let為區塊作用域只會在區塊{}內產生作用
答案:
setTimeout會依序印出這執行第 0 次~這執行第 9 次。console.log(i);會印出Uncaught ReferenceError: i is not defined。- 因為使用
let宣告的關係,所以i並非全域變數。
- 因為使用
❐ Let 有沒有 Hoisting?暫時性死區介紹
Hoisting 分創造與執行兩階段,下方實作範例中來看看 var 與 let 宣告會有什麼不同處。
實作1. var
1 | console.log(Ming); |
解析:
Hoisting 分創造與執行兩階段,以上方程式碼來說會拆分為下面形式:
- 宣告的變數會先被移至創造階段 →
var Ming; - 執行階段再賦予值 →
Ming = '小明'; - 在創在階段
var Ming;為undefined,如果在賦予值前就先console.log(Ming);要去取Ming的值就會印出undefined。
1 | //----- 創造階段 |
答案:
印出 undefined
實作 2. let
1 | console.log(Ming); |
解析:
let在創造階段會產生「 暫時性死區 TDZ 」,在此區域是無法取得值的。- 所以
let類似 Hoisting 提升的概念,只是它在提升時不會賦予變數undefined的值,而是出現「 暫時性死區 TDZ 」,這個暫時性死區無法存取這個變數。
1 | //----- 創造階段 |
答案:
會顯示錯誤訊息 Uncaught ReferenceError: Cannot access 'Ming' before initialization ( 無法在初始化前去取得此變數 )。
實作 3. let
1 | console.log(typeof a); |
解析:
let一樣有創造階段,但 let 在創造階段會產生「 暫時性死區 TDZ 」,在此區域是無法取得值的,所以會顯示錯誤訊息Uncaught ReferenceError: Cannot access 'Ming' before initialization
答案:
console.log(typeof a);印出undefined。console.log(typeof myName);印出錯誤訊息Uncaught ReferenceError: Cannot access 'Ming' before initialization( 無法在初始化前去取得此變數 )。
❐ Let 及 Const - 課後練習
課後練習 1
1 | a(); |
答案:
1 | //--創造 |
印出:Uncaught ReferenceError: Cannot access 'a' before initialization
課後練習 2. 課程上我們了解到 let 會有暫時性死區問題,所以不能在變數宣告建立之前使用該變數,那 const 呢?
1 | console.log('a'); |
答案:
印出:Uncaught ReferenceError: Cannot access 'a' before initialization
課後練習 3. 同前面幾題,如果我們接下來將 console.log() 移至後方會得什麼?
1 | let a; |
答案:
undefined
1 | //--創造 |
課後練習 4. 同上題,若改成 const 呢?
1 | const a; |
答案:
Uncaught SyntaxError: Missing initializer in const declaration ( const 宣告缺少初始化 )
課後練習 5. 請問 console.log() 將會出現什麼?
1 | const array = []; |
答案:
[’Casper’]
課後練習 6. 請問 console.log() 結果是什麼?
1 | let a = 10; |
答案:
1 | //創造 |
- fu() 內
let在創造階段為暫時性死區,1
2
3
4
5
6
7function fu() {
// 創造
let a; //暫時性死區
// 執行
console.log(a);
a = 20;
}另外
let為區塊作用域,在{}執行完記憶體就會釋放掉。所以答案為Uncaught ReferenceError: Cannot access 'a' before initialization。
課後練習 7. 請問以下 console.log() 將會出現什麼?
1 | function fu() { |
答案:
- 答案為
Casper。 - 函式為物件型別,所以是可以新增屬性的。後面的
a.fu會覆蓋掉前面的fu.fu。
參考資訊
- 六角學院 - JavaScript 核心篇
- JS 宣告變數, var 與 let / const 差異