var, let, 以及 const 的區別
var
, let
, 以及 const
都是在 JavaScript 用來做變數宣告的保留字,在 JavaScript 早期只有 var
,直到 ES2015 (ES6) 時才加入了 let
與 const
。
他們之間主要有 4 個不同:
- 作用域 (Scope) 不同: 在作用域上,
var
可以是全域、也可以是以函式作為範圍;let
與const
則是以區塊作為範圍。 - 重複宣告 (Declaration) 的差異: 在宣告上,
var
可以被重複宣告,但是let
與const
則不行。let
與const
在絕多數面向都是一樣,兩者的一大區別在於,用let
宣告的變數可以重新賦值,但是用const
的不行。 - 變量提升 (Hoisting) 不同: 在提升上,
var
宣告的變數會自動初始化值為undefined
,因此在宣告前就使用變數,不會出現錯誤,而會是undefined
;但是let
與const
宣告的變數則不會自動初始化,而是會進到暫時死區 (TDZ),因此在let
與const
宣告變數前使用該變數,會出現錯誤。 - for 迴圈的綁定 (Bind) 差異:
var
在宣告變數前,不會綁定到for
迴圈的上下文,而let
和const
會綁定到for
迴圈的上下文。
作用域 (Scope) 不同
var
是函式作用域,let / const
是區塊作用域。var
具有「函式作用域」,亦即在函式中宣告的變數,有效作用範圍會被限制在該函式中。但不具有區塊作用域,所以在區塊中宣告的變數,依然會作用到區塊之外,並不會被區塊限制住。let / const
具有「區塊作用域」,亦即在區塊中宣告的變數,有效作用範圍會被限制在該區塊中。
在 ES6 前,並沒有區塊作用域(block scope)的概念,僅有全域(global scope)與函式作用域(function scope),所以 var
宣告的變數,具有函式作用域(function scope)的性質,代表 切分最小單位的有效範圍是 function。
直到 ES6 後,新增區塊切分的概念,區塊是指 { }
大括號範圍。 let / const
宣告的變數,才具有區塊作用域(block scope)的性質,切分最小單位的有效範圍是 block。
而所謂的作用域就是「變數有效的作用範圍」,最大為全域作用範圍,意指變數有效範圍是全部範圍。
在區塊{ }
中,用 var / let
,宣告學生名為 Yuji 來看看結果。
/// 「var」 不受區塊限制,區塊外變數存取成功。///
{
var studentName = "Yuji";
}
console.log(studentName);
//Yuji
///「let」會受區塊限制,區塊外變數存取失敗。///
{
let studentName = "Yuji";
}
console.log(studentName);
//ReferenceError: corgiName is not defined
雖然 var
不受區塊限制,但會受到函式範圍限制,如下:
/// 「var」 受函式限制,函式外變數存取失敗。///
function callYuji() {
var studentName = "Yuji";
}
console.log(studentName);
//ReferenceError: studentName is not defined
/// 即使用 var 宣告相同的變數名稱 studentName,但因為「 函式作用域 」,所以不會讓同名變數衝突。 ///
//將學生命名為Yuji,之後用 callYuji() 呼叫
function callYuji() {
var studentName = "Yuji";
console.log(studentName);
}
//將學生命名為Megumi,之後用 callMegumi() 呼叫
function callMegumi() {
var studentName = "Megumi";
console.log(studentName);
}
callYuji(); //Yuji
callMegumi(); //Megumi
由上述範例,可以看出限定作用域範圍有兩個好處:
- 避免同名變數的衝突。
- 能維持最小權限的原則,以避免變數資料被不當存取。
而過往用 var
宣告,僅有 function 函式範圍有這樣的好處,而如今用 let / const
宣告,就能讓上述好處發生在 if、for 等「 區塊作用域 」當中,藉此降低多人協作或大型專案的衝突情況。
重複宣告 (Declaration) 的差異
var
允許重複宣告,let / const
會出錯
var studentName = "Megumi";
var studentName = "Yuji";
console.log(studentName);
//Yuji
let studentName = "Megumi";
let studentName = "Yuji";
console.log(studentName);
//SyntaxError: Identifier 'studentName' has already been declared
const studentName = "Megumi";
const studentName = "Yuji";
console.log(studentName);
//SyntaxError: Identifier 'studentName' has already been declared
變量提升 (Hoisting) 不同
undefined
代表找不到值,已經被宣告,但尚未賦值。這邊結果為undefined
,即是代表:雖然我們看不見,但其實在 console.log(i)
之前,i
就已經被宣告了,只是尚未賦值。
///由於 var 直接變量提升,所以上面程式碼等同下面程式碼///
console.log(i);
var i = 5;
//undefined
var i;
console.log(i);
i = 5;
//undefined
這其中是因為 var
有著「提升(hoisting)」的特性,其實不僅是 var
,function 也有這個特性。以 var
宣告變數而言,「 變數提升 」簡而言之是: 在執行任何程式碼前,會把變數放進記憶體中,這樣的特點是,可以在程式碼宣告該變數之前使用它。
/// 因為 hoisting,所以可以在宣告變數前,就預先使用變數,所以可以寫完後一起宣告///
i = 2;
n = 3;
console.log(i + n);
var i;
var n;
// 5
在這樣的情況下,只需要有賦值就不會出錯,即使尚未宣告變數也不會出錯。
這樣會造成什麼問題?當養成了「 後宣告 」的習慣,如果最後忘了用 var
宣告呢?並不會出錯,只是變數會跑到全域中,變成全域變數,可能造成些 bug,像是:
/// 函式中沒有用 var 宣告,導致污染到全域 ///
var x = 1;
function addFunc(y) {
x = 100;
x = x + y;
}
addFunc(50);
console.log(x);
// 150,預期應該要是 1,但函式中的 x 跑出來了
但是 let
與 const
則不會,而是會進到暫時死區 (TDZ),因此在 let
與 const
宣告變數前使用該變數,會出現錯誤:
/// 先使用變數,後宣告會直接「出錯」,更嚴謹的養成先宣告,後使用///
console.log(i);
let i = 5;
//ReferenceError
i = 5;
console.log(i);
let i;
//ReferenceError
所以 習慣用 let
的開發者,通常會先宣告變數,而不會習慣先運算變數後宣告的情況,藉此降低開發出錯的機率 。
for 迴圈的綁定 (Bind) 差異
// 使用 var 宣告迭代變數
console.log("使用 var:");
for (var i = 0; i < 3; i++) {
setTimeout(function () {
console.log("var 迴圈內的 i:", i);
}, 1000);
}
console.log("var 迴圈外的 i:", i); // 可以存取 i,因為它被提升到外部作用域
// 使用 let 宣告迭代變數
console.log("使用 let:");
for (let j = 0; j < 3; j++) {
setTimeout(function () {
console.log("let 迴圈內的 j:", j);
}, 1000);
}
// console.log("let 迴圈外的 j:", j); // 此行會拋出 ReferenceError,因為 j 只在迴圈內可見
// 使用 const 宣告迭代變數
console.log("使用 const:");
for (const k = 0; k < 3; k++) {
setTimeout(function () {
console.log("const 迴圈內的 k:", k);
}, 1000);
}
// console.log("const 迴圈外的 k:", k); // 此行會拋出 TypeError,因為 k 只在迴圈內可見且不可重新賦值
- 在這個範例中,使用
var
宣告的變數i
被提升到外部作用域,因此迴圈外部仍然可以存取它。 - 使用
let
宣告的變數j
只在迴圈內部可見,迴圈外部無法存取,這符合區塊作用域的規則。 - 而使用
const
宣告的變數k
也只在迴圈內部可見,且由於const
宣告的特性,它無法重新賦值,因此無法在迴圈外部存取或修改。
結論:避免使用 var
,使用 let
或 const
宣告變數。