💎 變數的演進
JavaScript 過去只有 var 一種變數宣告方式,直到 2015 年發表的 ECMAScript 2015(ES2015),第 6 版,又被稱為 ECMAScript 6(通常簡稱為 ES6 ),才有 let 和 const 兩種新的變數宣告方式,這個版本有相當多的革新,現在常用的箭頭函式、樣板字串...等都包含在其中。
參考資料:
Wiki - ECMAScript
💎 Can I Use?
雖然 2015 年距今已經超過 6 年了,但是仍有些企業或公司在使用 IE 這類過時的瀏覽器,let 和 const 在 IE 11 只有部分支援、 IE 10 以下版本則是完全不支援,使用前需要確認用戶端環境是否支援。
參考資料:
Can I Use
💎 變數特性
var的特色是就是沒有規矩,因此需要小心使用避免踩坑,let 和 const 有了進一步的規範,可以針對不同需求場景運用,使用上較為安全穩定,接下來對各種特性逐一說明。
🔸 重複宣告
var可以重複宣告同一個變數名稱,後者會覆蓋前者,而且程式不會報錯。var a = 1; var a = 2; console.log(a); // 2let和const則不允許重複宣告(會報錯),可以避免變數名稱衝突導致 debug 困難的情況。let b = 1; let b = 2; // Uncaught SyntaxError: Identifier 'b' has already been declared
🔸 重新賦值
摘錄維基百科:
常數,又稱定數(Constant),是指一個數值固定不變的常量,例如圓周率,與之相反的是變數。參考資料:
Wiki - 常數
過去只有
var時,如果想要宣告一個不能被變更的常數,通常會用全部大寫,加上_連接單字來標示,例如:var DO_NOT_TOUCH_ME = 'X';- 但是名稱只是幫助辨識,不會真的具備常數的特性。
var DO_NOT_TOUCH_ME = 'X'; DO_NOT_TOUCH_ME = '我偏要改'; console.log(DO_NOT_TOUCH_ME); // 我偏要改 const可以宣告『真正』的常數,有了const之後也比較不會特別要求使用大寫格式的變數名稱來區別(視團隊開發時的規範而定)。const doNotTouchMe = 'X'; doNotTouchMe = '我偏要碰'; // Uncaught TypeError: Assignment to constant variable.const的使用時機
對 JS 新手來說,大概只知道圓周率、太陽...等明確固定數值要用常數,但是下列的資料都應盡量使用const宣告:- DOM:使用
querySelector這類方法取得的 DOM 通常不會再改變。 - 陣列:使用陣列方法來操作,避免被
=砍掉重練。 - 物件:使用物件方法來操作,避免被
=砍掉重練。 - 函式:避免匿名函式被蓋掉,功能全失。
- DOM:使用
🔸 初始值
- 常數(const)除了無法變更外還有另一個特性,宣告的時候一定要賦予值,
var和let則無此限制。const a; // Uncaught SyntaxError: Missing initializer in const declaration - 既然是後續不能更動的值,在一開始就設定好是很合理的事情吧!
🔸 作用域
作用域(Scope)是變數可以被使用的區域,也代表著在相同的作用域才會有重複命名的衝突問題。
📗 全域作用域(Global Scope)
當我們在一個 JS 檔案直接撰寫變數宣告時,變數存在於最外層,無論是 var、let、const 都屬於全域變數,無論寫了多少層的 {} 和 函式,都可以向外找到並使用全域變數。
```
let s;
function t() {
if(true){{{{{{s=666}}}}}}
};
t();
console.log(s); // 666
```
不同的變數宣告方式,在運作機制上會有所不同,當變數跑到 window 底下會被視為一種『污染』,應盡量避免這種狀況。
var宣告全域變數時會成為 window 物件底下的一個屬性let、const不會被放到 window 底下直接用變數名稱宣告(不加前綴詞),無論放在
{}或函式內都會成為 window 物件底下的一個屬性。dirtyA = 1; console.log(window.dirtyA); // 1 var dirtyB = 2; console.log(window.dirtyB); // 2 let c = 3; console.log(window.c); // undefined const d = 4; console.log(window.d); // undefined function dirtyVariable() { dirtyE = 5; } dirtyVariable(); console.log(window.dirtyE); // 5直接宣告在
window底下的變數可以使用delete物件方法刪除;而使用var宣告的全域變數無法使用delete刪除(介於屬性與非屬性之間)。x = 1; delete window.x; // true console.log(x); // x is not defined var y = 2; delete window.y; // false console.log(y); // 2在 ES5 時可以透過
'use strict'(嚴格模式)來避免這些不好的操作,使用 ES6 時應只用let和const就好。延伸閱讀:
1、深入JavaScript系列(一):詞法環境
2、Javascript 的嚴格模式 (Strict Mode)
📗 函式作用域(Function Scope)
var 的作用域除了最外層的全域,還會在函式 (function) 內區隔。
var a = 1;
function foo() {
var a = 2;
var b = 3;
}
foo();
console.log(a); // 1
console.log(b); // b is not defined
上面的範例可以看到宣告在函式內的兩個變數,是無法從全域取得的。
for (var i = 1; i < 5; i++) {
setTimeout(()=>
console.log(i), 500);
}
// 5
// 5
// 5
// 5
function 的範圍較大,撰寫時很難把程式切分成一堆 function 來區隔作用域,上面的程式碼在處理非同步時就出現不如預期的結果。
延伸閱讀
1、JavaScript 核心觀念(36)-函式以及 This 的運作-立即函式
2、閉包
📗 區塊作用域(Block Scope)
let 和 const 的作用域除了最外層的全域,會在大括號 {} 內區隔:
{
const a = 1;
{
let b = 2;
var c = 3;
}
console.log(a); // 1
console.log(b); // b is not defined
console.log(c); // 3
}
console.log(a); // a is not defined
console.log(b); // b is not defined
console.log(c); // 3
上面的範例可以看到 var 無論在哪個大括號內都可以取用,但 let 和 const 無法在自己存在的大括號外被取用。
常用的 if-else、for等方法都會使用大括號,相對於 function,可以把作用域區分的更細。
前個段落 setTimeout 範例內的 var 改成 let,就能如預期的執行了。
for (let i = 1; i < 5; i++) {
setTimeout(()=>
console.log(i), 500);
}
// 1
// 2
// 3
// 4
🔸 提昇(Hoisting)
JavaScript 是一種直譯式語言,程式是由上到下逐行執行,理所當然,還沒宣告變數時沒辦法取得值:
console.log(a); // a is not defined
但是在執行前實際上還有一個執行環境建置的階段,會把程式碼中所有變數名稱、函式先完成宣告,再逐行執行,因此,下面的程式碼中並不會出現任何錯誤:
console.log(a); // undefined
console.log(foo); // foo(){...}
var a;
function foo() {
return 'Hello';
}
實際執行順序如下:
var a; // 提昇
function foo() { // 提昇
return 'Hello';
}
console.log(a); // undefined
console.log(foo); // foo(){...}
再看下面這段程式碼,想想看結果是什麼:
var b = 1;
var b;
console.log(b);
你可能會認為答案是 undefined,因為重新宣告但是沒有賦予值,但答案其實是 1,經過提昇後實際執行順序如下:
var b;
var b;
b = 1;
console.log(b);
這樣的機制似乎讓這個語言又多了一個特性要記下來,為了讓事情變得簡單,let 和 const 有不一樣的機制,稱為 Temporal Dead Zone(TDZ),暫時性死區。
使用 let 和 const 宣告的變數名稱從建立到賦值(包含賦予 undefined)的期間會無法被取用,這段期間就是 TDZ;換句話說,變數提昇後會被鎖定,等到程式碼執行到他實際宣告的那一行才可取用。
這種把變數名稱給保留起來的方法,讓 JS 撰寫上更加嚴謹、有保障。
console.log(x); // x is not defined
let x = 1;
console.log(x);
參考文章
1、我知道你懂 hoisting,可是你了解到多深?
2、理解ES6中的暫時死區(TDZ)
💎 總結
前面一共針對變數的5種特性說明,整理後的列表如下:
| var |
let |
const |
|
|---|---|---|---|
| 重複宣告 | ✅ | ❌ | ❌ |
| 重新賦值 | ✅ | ✅ | ❌ |
| 強制賦予初始值 | ❌ | ❌ | ✅ |
| 作用域 | function | { } | { } |
| 提升機制 | 可被取用 | TDZ | TDZ |
以上是我對變數型別的一點認知,如有錯誤或是補充的知識點,也歡迎大家不吝指教,謝謝!

![[ 筆記 ] 後端基礎 - PHP](https://static.coderbridge.com/img/krebikshaw/5a69960d129648c6a2cd6ebdbd7d2889.jpg)
![[Note] React: Performance & Patterns](https://static.coderbridge.com/images/covers/default-post-cover-3.jpg)