說了這麼多,到底為什麼要使用作用域呢?
說到底,列出一系列對變數存取的規則,目的就是要製造出一個個作用域泡泡,只有在合乎規定的情況下,才能夠使用泡泡內部的變數,在泡泡外的世界,內部一切東西都是隱藏的。
這麼做的好處包括:
最小權限原則(principle of least privilege):又稱為「最小授權(least authority)」、「最小暴露(least exposure)」,意指程式執行時只允許訪問當下必要的信息或資源,避免受到錯誤或者惡意行為的破壞。
以下進行更詳細的解釋:
function calculate(a) {
b = a + calculateMore( a * 2 );
console.log( b * 3 );
}
function calculateMore(a) {
return a - 1;
}
var b;
calculate( 2 ); // 15
在上面這段程式碼中, b
和 calculateMore
都只在 calculate
內部使用,卻暴露在全域變數中,有可能會被意外更改或覆蓋。更好的做法是把它們都藏入 calculate
內部,僅提供 calculate
的函式作用域存取:
function calculate(a) {
function calculateMore(a) {
return a - 1;
}
var b;
b = a + calculateMore( a * 2 );
console.log( b * 3 );
}
calculate( 2 ); // 15
如此一來,b
和 calculateMore
從外部都無法存取,在保有同樣功能的條件下,卻更不容易出錯。
因此彼此將私有資源暴露在外,將這些細節隱藏起來被認為是更好的程式寫法。
function foo() {
function bar(a) {
i = 3; // 改變外層作用域的 i
console.log( a + i );
}
for (var i=0; i<10; i++) {
bar( i * 2 ); // 因為 i 永遠是 3 所以陷入無限循環
}
}
foo();
以上範例中,在 for
循環執行 bar()
時,由於 bar()
內部改變了屬於外層作用域變數 i
的值,i
的值永遠停留在 3 無法遞增,因此造成無限迴圈。
命名空間(namespace)基本上可以理解為擁有名稱的作用域,或者說,命名作用域以便存取指定作用域內部的某個變數。
一般的作用域除了global
以外通常難以獨立調用,而將作用域命名,便可靈活地存取這些作用域。
一般的函式庫基本上都會隱藏自己內部使用的變數和函式,以避免與全域作用域中的變數產生衝突。
在 JS 中,命名空間通常以物件形式存在,函式庫的功能則都作為屬性掛在這個物件底下,避免直接暴露在全域作用域,對其他引用的函式庫彼此影響。
以下是一個作為命名空間的物件範例:
var MyReallyCoolLibrary = {
awesome: "stuff",
doSomething: function() {
// ...
},
doAnotherThing: function() {
// ...
}
};
另外一種避免汙染全域作用域的方式則是使用模組(Module),利用模組管理器將各個函式庫作為模組導入程式。
這些管理器會要求明確地引入某個函式庫的識別字,有效避免了在全域作用域新增變數。
關於模組,後面會進行更詳細的說明。