在 JS 最早的版本中,只有函式能夠創造自己的作用域。
接著 ES3 新增了 try/catch
,其中的 catch
擁有自己的作用域。
try {
// do something
}
catch (err) {
console.log( err );
}
console.log( err ); // ReferenceError: `err` not found
來到 ES6 時,JS 新增了 let
、const
這兩個宣告變數的關鍵字,開始能夠使用「區塊(Block)」也就是 {}
內部的範圍來劃定作用域。
能劃定區塊作用域的關鍵字:
let
:宣告可重複修改的變數。const
:宣告常數(constant),也就是無法修改的變數。宣告時就必須賦值,之後只能讀取(RHS),不可修改其值(LHS)。// 使用 var
for (var i = 0; i < 10; i++) {
console.log( i );
}
console.log( `當前 i 等於 ${i}` );
// 使用 let
for (let j = 0; j < 10; j++) {
console.log( j );
}
console.log( `當前 j 等於 ${j}` );
// ReferenceError: j is not defined
跟 var
不同的其中一點是,let
禁止重複宣告,比如以下程式碼是合法的:
var a = "Hi!";
console.log(a); // Hi!
var a = "Hello!";
console.log(a); // Hello!
但這樣就出錯了:
let a = "Hi!";
console.log(a); // Hi!
let a = "Hello!";
console.log(a);
// Uncaught SyntaxError: Identifier 'a' has already been declared
const
的特性是只能在宣告時賦值,且之後無法改變其值。
只能在宣告時賦值:
const a;
a = "JavaScript";
// SyntaxError: Missing initializer in const declaration
宣告後無法改變其值:
const a = "Java";
a = "JavaScript";
// TypeError: Assignment to constant variable.
以下來做個 var
、let
和 const
的比較:
var foo = true;
if (foo) {
var a = 1;
let b = 2; // 屬於 if 的區塊作用域
const c = 3; // 屬於 if 的區塊作用域
a = 2; // OK!
b = 3; // OK!
c = 4; // 重複賦值而報錯──TypeError: Assignment to constant variable.
}
console.log( a ); // 2
console.log( b ); // ReferenceError!
console.log( c ); // ReferenceError!
使用區塊作用域可明確告知引擎,執行完區塊後可清空整個區塊的資料。
function process(data) {
// do something
}
{
let someReallyBigData = { ... };
process( someReallyBigData );
// someReallyBigData 存在到這裡為止
}
// someReallyBigData 已被回收,從記憶體消失
console.log(someReallyBigData) // ReferenceError
迭代的變數被鎖在區塊作用域內部,不會汙染全域作用域。
範例一
for (let i = 0; i < 10; i++) {
console.log( i );
}
console.log( i ); // ReferenceError
範例二
{
let j;
for (j = 0; j < 10; j++) {
let i = j + 1; // 每次迭代都重新绑定
console.log( i, j );
}
console.log( i ); // ReferenceError
}
console.log( j ); // ReferenceError
所謂的填補,是指使用新版本撰寫的代碼,需要在舊環境運行的狀況下,由於舊版本不支援新版本某些功能,於是使用舊版本擁有的技術實現新版本功能,以「填補」該功能的缺失。
如 Babel 就是一個有名的 JS 編譯器,另外 Google 也開發並維護了一個以 Node.js 開發的,名為 Traceur 的專案,以上兩者都可以將 ES6 以上的功能轉換為舊版可運行的代碼。
以下就舉例一些在舊版本達成區塊作用域效果的方法:
try/catch
(ES3+)範例一
a 是屬於全域變數,而非 if
區塊自己的變數
if (true) {
var a = 1;
console.log(a) // 1
}
console.log(a) // 1
使用 try/catch
改寫
try{
throw 2
}catch(a){
console.log( a ); // 2
var b = "2";
console.log( b ); // "2"
}
console.log( b ); // undefined
console.log( a ); // ReferenceError
從上面範例可以看出,catch 的作用域和函式作用域的不同點在於:函式作用域的提升僅提升到函式內部最上面,而 catch 作用域使用
var
宣告的變數,會提升到外部區塊。
(function foo(){
var a = 1;
console.log( a ); // 1
})();
console.log( a ); // ReferenceError