源自函式的範疇
JS 具有以函式為基礎的範疇。其內的識別字只能在函式的範疇內被取用。
隱藏在普通範疇中
函式的相反思維:當把程式碼或變數放在函式中,其效用等於「隱藏」了程式碼。
最小權限原則 (principle of least privilege) / 最小授權 (least authority) / 最小暴露 (least exposure):在模組或物件 API 中,應該只暴露最少的東西。
參考 P.31 程式碼,私有變數或函式等細節,應被隱藏起來,以避免非預期的使用。
你會隱藏嗎?或是覺得什麼情況可以隱藏,請舉出一些例子。
避免衝突
隱藏可以避免同名但用途不同的識別字產生衝突或複寫。
全域命名空間
載入的程式庫如果沒有隱藏私有函式、變數,容易汙染全域空間,彼此發生衝突。
可以建立一個物件作為命名空間來使用,將對外的函式或變數設定為該物件的屬性。
模組管理
另一個方法是使用模組 (module),藉由工具產生相依性管理,不新增識別字到全域範疇。
模組管理機制並沒有逃離範疇規則的掌控,而是利用規則將識別字放置於私有的、不易發生衝突的範疇中。
函式作為範疇
包在函式中的確能夠隱藏裡面的細節,但有一些問題:
- 必須宣告一個函式名稱,如
foo
。 - 要明確地呼叫函式才能夠執行。
JS 針對這兩個問題提供了解法:IIFE。
函式宣告 vs. 函式運算式
函式宣告:利用 function
為關鍵字來宣告。
函式運算式:將函式指定給特定變數,如 var foo = function () { ... }
或 (function foo() { ... })();
。
(function foo() { ... })
代表 foo 只能在 … 所標示的範疇中所找到,不會汙染包含它的範疇。
考慮以下兩個函式運算式。
var foo = function(){ console.log(foo); } (function foo(){ console.log(foo); })()
匿名 vs. 具名
函式運算式可以是匿名的。缺點:
- 除錯困難:在堆疊中沒有名稱。
- 要使用時可能被棄用:需要參考或遞迴時,或是要解除事件繫結 (unbind event) 的時候。
- 可讀性降低:可識別的名稱能讓程式碼較容易理解。
最佳的實務做法是永遠讓函式運算式帶有自己的名稱。
即刻調用函式運算式 (IIFE)
IIFE 是可立即執行的函式運算式,不會汙染到全域範圍,並且匿名或具名皆合法。
各種變體 (P.37):
- 將 window 重新指名為 global。
- 確保 undefined 正確性。
- 反轉順序,不傳變數而是傳函式。
區塊作為範疇
盡可能在最近的地方宣告變數。
for (var i = 0; i<10; i++) { console.log(i); } console.log(i);
例如預期 i 只在 for 中被使用,但這並不是真正的區塊範疇,而是仰賴風格和自我約束偽造出來的範疇。
with
with 透過物件建立出來的範疇,實際上是一種區塊範疇的實例。這個範疇只會在 with 的生命週期中存在,而且不在包含它的範疇中。
try/catch
在 try/catch 中的變數宣告,會以 catch 區塊為範疇。
let
ES6 改變世界~
let 會隱含地將變數宣告接附到它所在的區塊 (通常是一對 { … })。
可以在程式碼中放入明確的一對 { … } ,以製作一個明確的區塊。
let 不會被拉升。
垃圾回收
宣告明確的區塊,可以向 Engine 清楚地表示範圍中的識別字不需要再被保留了。(參考 P.43)
let 迴圈
for (let i = 0; i < 10; i++) { console.log(i); } console.log(i); // ReferenceError
在 for 迴圈中使用,不只將 i 限制於 for 的區塊範疇中,還會每次都重新綁定 (rebind),將前一次迭代的結果重新賦值。
如果現有程式碼對變數宣告有範疇依賴性,重構或移動時要特別注意 (參考 P.45)。
const
與 let 雷同,但其值是固定不可變的。嘗試改變會有 ReferenceError。
複習
- 宣告在函式內的識別字會被隱藏起來。
- 除了函式範疇,也有區塊範疇 (ES3 catch、ES6 let, const)。
附錄 B
使用 try/catch 來達成區塊範疇實作。
let 述句,透過 let 述句建立一個明確的區塊,並將宣告至於區塊的頂端。