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(變數);
會凍結裡面的內容無法做修改。
❐ 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 差異