var
- 函式作用域let
、const
- 區塊作用域JavaScript 宣告變數的方式有三種:var、let、const。let
是 ES6 出現後,被用來改善現有語法的宣告方式,在 ES6 未出現之前,網頁不存在「區塊域」的概念,因此大多數都使用 var
的方式宣告變數,不過這樣有個問題,就是用 var
宣告變數會污染到全域變數,使用 let
可以只在部分區域運作。
var 與 let 宣告變數範例:
var a = '無敵哆啦A夢'; // 這裡的 a 為全域變數
function doraemon () {
let a = '無敵大雄'; // 這裡的 a 為區域變數
a = '變身哆啦美';
console.log(a); // 變身哆啦美
}
doraemon();
console.log(a) // 無敵哆啦A夢
從範例來看,結果是「變身哆啦美」與「無敵哆啦A夢」,這裡的觀念在於只存在大括號裡這件事。
第一個 console.log(a)
是「變身哆啦美」,原因是因為在裡面我們重新給 a
賦予了一個「變身哆啦美」的值,而到了第二個 console.log(a)
的時候,答案卻變成「無敵哆啦A夢」,這是因為function doraemon
裡面的 a
只會存活在 {}
這個大括號裡,因此裡面我們替 a
賦予的值,沒有更改到外面。
接著實際操作 let
與 var
,看看區塊作用域與函式作用域的差別:
// var 宣告
// 宣告變數 a
var a = 10;
if(true){
var b = 20;
}
// 輸出: a = 10
console.log(a)
// 輸出: b = 20
console.log(b)
// let 宣告
// 宣告變數 a
var a = 10;
if(true){
let b = 20;
}
// 輸出: a = 10
console.log(a)
// 因為變數 b 使用 let 宣告,離開 if 區域便無法被存取
console.log(b) //會顯示沒有定義
上面的例子可以看到 var
與 let
的差異在於區塊的概念,let
在 if
區域裡面宣告的變數只留在 if
括號裡,並且在同一個區塊內,let
與 const
不能重複宣告變數,var
則可以重複宣告。
var
區域污染的現象在迴圈尤其明顯,這是一個 var
與 for
的例子:
<ul class="list">
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
const list = document.querySelectorAll('.list li').length;
for(var i = 0; i < list; i++){
document.querySelectorAll('.list li')[i].addEventListener('click', function(){
alert(i+1);
})
}
以上範例程式碼運作方式為,透過點擊了解我們點擊到的是哪一個按鈕,也就是當我們點擊 <li>1</li>
的時候,我們預期他會顯示 1,點擊 <li>2</li>
的時候預期會顯示 2 。
但在這個範例中會發現,不管我們點擊哪一個,alert
都會顯示 4,這是因為值被「全域變數」影響。for
迴圈跑完以後,會直接顯示全部跑完的結果,而不是個別處理。在這個過程中,var
已經直接將 i
宣告為「全域變數」,並不斷透過 for
進行累加,因此全部跑完的結果是 4,但這不是我們預期的結果。
那要如何讓以上程式範例碼成為我們預期的呢?這時候使用 let
便可以達到個別處理的結果。
const list = document.querySelectorAll('.list li').length;
for(let i = 0; i < list; i++){
document.querySelectorAll('.list li')[i].addEventListener('click', function(){
alert(i+1);
})
}
在這邊我們把 var
改成 let
,上面說到 let
有區域特性,它只會在大括號裡運作,因此每一次的執行結果都會顯示一次。
當我們點擊 <li>1</li>
的時候,就會執行一次括號內的程式碼,在 let i = 0
的作用域裡面(括號內)執行 i++
,因此 alert
此時會跳出 1,當我們點擊 <li>2</li>
的時候,就會再執行一次括號內的程式碼,在 let i = 1
的作用域裡面執行 i++
,alert
於是跳出 2,然後結束。依此類推,最後即是我們所預期的呈現。
const
是宣告常數,常用在一些不能被變更的變數,譬如:url 網址。確定不再做更動的時候,可以使用 const
方法。
嘗試執行以下程式碼呈現的結果:
const me = '齊天大聖孫悟空';
let me = '豬八戒';
// Uncaught SyntaxError: Identifier 'me' has already been declared
在這邊會顯示上面這個 me,已經有被宣告變數,由上面的範例碼可以知道,const
不能再被重新宣告變數覆蓋。
另外,let
可以直接宣告變數,const
則是一定要有值。
let a;
// undefined
const b;
// Uncaught SyntaxError: Missing initializer in const declaration
let
直接宣告變數,只是顯示沒有東西,而直接宣告變數的 const
,會顯示缺少值。
雖然使用 const
宣告變數就無法更動,但如果使用 const
宣告物件,裡面的屬性仍然是可以更動的,因為物件有傳參考的特性,因此依然可以修改。
const color = {
light: 'white',
dark: 'black'
};
color.midtones = 'grey';
// {light: "white", dark: "black", midtones: "grey"}
最後的結果,midtones: "grey"
是能夠被加上去的。
有一點要注意,已經宣告的物件則無法再被重新宣告。
let newColor = {
light: 'yellow',
dark: 'crimson'
};
color = newColor // 錯誤
如果不希望 const
宣告的物件被修改,還有另一項法寶 freeze()
。freeze()
的功能是防止新增屬性或是屬性遭到修改,就像它英文的意思一樣,「凍結」屬性。
const color = {
light: 'white',
dark: 'black'
};
Object.freeze(color);
color.midtones = 'grey';
// {light: "white", dark: "black"}
同樣的範例,可以看到與上面的結果不同,在物件下方添加 freeze()
,屬性 midtones
則無法被加入,於是可以防止物件被添加或修改。