javascript中聲明變量的關鍵字是var、let和const。var聲明的變量可以用來保存任何類型的值,聲明的范圍是函數作用域;let聲明的范圍是塊作用域;而const聲明變量時必須同時初始化變量,且初始化后值不可再修改。
聲明變量關鍵字var、let和const
ECMAScript變量是松散類型的,即變量可以用於保存任何類型的數據,每個變量只不過是一個用於保存任意值的命名占位符。
1.var關鍵字
var聲明的變量可以用來保存任何類型的值(在不初始化的情況下會保存一個特殊值undefined),像其他語言一樣在javascript在定義變量的同時還可以對變量進行賦值,該變量被定義為一個保存所賦值的值的變量,因為javascript是動態語言,在初始化變量的時候不會將它標識為所賦值的數據類型,只是一個簡單的賦值而已。隨后不僅可以改變保存的值,還可以改變值的類型:
1
2
var message = "hi";
message = 100;
1.1.var聲明作用域
使用var操作符定義的變量會成為包含它的函數的局部變量。比如,使用var在函數內部定義一個變量,就意味這該變量將在函數退出時被銷毀,我覺得這就是所說的垃圾回收:
1
2
3
4
5
function test( ) {
vart message = "hi"; //局部變量
}
test( );
console.log(message); //報錯!
函數調用之后變量會隨機被銷毀,因此最后一行會報錯。不過,在函數定義變量時省略var操作符時可以創建一個全局變量:
1
2
3
4
5
function test( ) {
message = "hi"; //全局變量
}
test( );
console.log(message); //"hi"
只要調用一次函數test( )就會定義message這一全局變量,並且可以在函數外部訪問。但是由於在局部作用域中定義的全局變量很難維護,所以一般不推薦這樣做。
1.2.var聲明提升
使用var關鍵字聲明的變量會自動提升函數作用域頂部,即所謂的“提升”(hoist),也就是把所有變量聲明都拉到函數作用域的頂部:
1
2
3
4
5
function fool( ) {
console.log(age);
var age = 28;
}
fool( ); //undefined
這里是不會報錯的,而是顯示undefined,ECMAScript在運行是會把它看成等價於如下的代碼:
1
2
3
4
5
6
function fool( ) {
var age;
console.log(age);
age = 28;
}
fool( ); //undefined
2.let聲明
let跟var的作用差不多,但有着非常重要的區別。最明顯的區別是let聲明的范圍是塊作用域,而var聲明的范圍是函數作用域:
1
2
3
4
5
if (true) {
let age = 26;
console.log(age); //26
}
console.log(age); //ReferceError:age沒有定義
age變量的作用域僅限於該塊內部,所以不能在if塊外部被引用。塊作用域是函數作用域的子集,所以適用於var的作用域限制也同樣適用於let。
let也不允許同一塊作用域中出現冗余聲明(var可以):
1
2
3
4
var name;
var name;
let age;
let age; //SyntaxError;標識符age已經聲明過了
此外,對聲明冗余報錯不會因混用var和let而受影響。這兩個關鍵字聲明的並不是不同類型的變量,他們只是指出變量在相關作用域如何存在。
2.1.暫時性死區
let與var的另一個重要區別是let聲明的變量不會在作用域中被提升:
1
2
3
4
5
6
7
//name會提升
console.log(name); //undefined
var name = 'matt';
//name不會提升
console.log(name); //ReferenceError:name沒有定義
let name = 'matt';
2.2.全局聲明
與var不同,使用let在全局作用域中聲明的變量不會成為window對象的屬性(var聲明的變量則會):
1
2
3
4
5
var name = 'matt';
console.log(window.name); //'matt'
let name = 'matt';
console.log(window.name); //undefined
不過,let聲明仍然是在全局作用域中發生的,相應變量會在頁面的聲明周期內存續。
2.3.條件聲明
let的作用域是塊,所以不可能檢查前面是否已經使用let聲明過同名變量,同時也就不可能在沒有聲明的情況下聲明它。使用try/catch或typeof操作符也不能解決,因為條件塊中let聲明的作用域僅限於該塊。為此,對於let這個新的ES6聲明關鍵字不能依賴條件聲明模式。
2.4.for循環中的let聲明
在使用var的時候,最常見的問題就是對迭代變量的奇特聲明和修改:
1
2
3
4
5
for(var i = 0; i < 5; ++i) {
setTimeout( () => console.log(i) ,0)
}
//你可能以為會輸出0、1、2、3、4
//實際上輸出的是5、5、5、5、5
在退出循環的時候迭代變量保存的是導致循環退出的值:5。在之后執行setTimeout超時邏輯時,所以i都是同一個變量,因而最終輸出的都是同一個值。
使用let聲明迭代變量時,JavaScript引擎在后台會為每個迭代循環聲明一個新的迭代變量,每個setTimeout引用的都是不同的變量實例:
1
2
3
4
for(let i = 0; i < 5; ++i) {
setTimeout( () => console.log(i) ,0)
}
//會輸出0、1、2、3、4
3.const聲明
const的行為與let基本相同,唯一一個重要區別是它聲明變量時必須同時初始化變量,且嘗試修改const聲明的變量會導致運行錯誤。
const聲明的限制只適用於它指向的變量的引用。如果const變量引用的是一個對象,那么修改這個對象內部的屬性並不違反const的限制:
1
2
const person = { };
person.name = 'matt';
4.使用建議
let和const是ES6中新增的,從客觀上為JavaScript更精確地聲明作用域和語義提供更好的支持。
4.1.不使用var
限制自己只使用let和const有助於提升代碼質量,因為變量有了明確的作用域、聲明位置,以及不變的值。
4.2.const優先,let次之
使用const聲明可以讓瀏覽器運行時強制保持變量不變,也可以讓靜態代碼分析工具提前發現不合法的賦值操作。因此,我們應該優先使用const來聲明變量,只有在提前知道未來會有修改時再使用let。