💎 變數的演進
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); // 2
let
和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 |
以上是我對變數型別的一點認知,如有錯誤或是補充的知識點,也歡迎大家不吝指教,謝謝!