今天來聊聊變數的作用範圍。
在其他程式語言像是 C 語系中,變數的活動範圍(scope)為區塊(block),也就是在每個 { }
中,都屬於一個新的活動範圍。
例如下面的範例中,依序輸出 1、0,而不會提示說重複宣告。
int main() {
int myVar = 0;
{
int myVar = 1;
printf("inner scope: %d\n", myVar); // 1
}
printf("main scope: %d\n", myVar); // 0
return 0;
}
在 ES6 之前,只有兩種 scope
也就是活動範圍不是 block,而是 function。
帶入剛剛的例子:
var myVar = 0;
{
var myVar = 1;
console.log("inner scope: %d\n", myVar); // 1
}
console.log("main scope: %d\n", myVar); // 1
注意到了嗎?
在 block({ }
) 中的變數直接修改到了外面的變數,前後印出都是 1。
每定義一個函數,就會建立一個屬於這個函數的活動範圍,不在函數內的變數就屬於全域活動範圍。
ES6 以前,只能使用 var 來宣告變數,在函數中以 var 宣告的變數,其活動範圍僅在函數內,外部無法使用,而沒有用 var 或是在函數外宣告的變數,就屬於全域範圍。
var 是看函數,而不是區塊
但是從 ES6 開始,多了兩種宣告變數的方式: let
、const
。
let 不同於 var,它是以區塊為活動範圍,在 ES6 之後,你可以在 for 區塊、if 區塊、或是純區塊中,使用 let 宣告以區塊為活動範圍的變數。
let myVar = 0;
{
let myVar = 1;
console.log("inner scope: %d\n", myVar); // 1
}
console.log("main scope: %d\n", myVar); // 0
let
禁止在同一活動範圍中再次宣告相同名稱的變數。
var
會無視第二次宣告,直接指派變數值。let
禁止在宣告變數之前就使用它。let
宣告的變數,不會成為全域個體(global object)的屬性。但以 var 宣告的變數同時也會是全域個體的屬性,因此 let 變數是真正的區域變數。
let 是看區塊,而不是函數。
使用 const 宣告的變數,顧名思義就是無法再更動其值,也是常數的概念。若試圖改變 const 宣告之變數,都是語法錯誤。
除此之外,const
的語法限制和 let
相同:
const 是看區塊,而不是函數
最後用這個例子統整一下上方講的,可以看到在 function varScope
的 for 區塊中,可以使用區塊外 var
、let
宣告之變數;在區塊外可以使用區塊內 var
宣告之變數,但卻無法使用 let
宣告之變數。
function varScope(){
var outside_var = "outside_var";
let outside_let = "outside_let";
for(let i = 0; i < 1; i++){
var inner_var = "inner_var";
let inner_let = "inner_let";
console.log(outside_var); // outside_var
console.log(outside_let); // outside_let
}
console.log(inner_var); // inner_var
console.log(inner_let); // error: inner_let is not defined
}
varScope();
那今天的分享就到這邊,明日會聊聊 hoisting
。
等等,再補充一個東西好了,如果有一個物件長這樣
const myObj = {
url: "http://123.com"
}
myObj.url = "changed";
console.log(myObj.url); // changed
咦! const
宣告的變數不是不能夠修改了嗎?
之所以不會有錯誤,是因為在 JS 中陣列和物件都是屬於 reference type,在修改時並沒有把這個常數指向其他地方。預計之後 傳值(value)傳址(reference)
單元會再解釋。
但如果真的不想被修改,可以用這個方法
Object.freeze(myObj);
myObj.url = "changed";
console.log(myObj.url); // "http://123.com"
好啦,明天見!