【讀書筆記】你唔知JS 函式作用域和塊作用域
函式中的作用域
1.無論識別符號宣告出現在作用域中的何處, 這個識別符號所代表的變數或函式都將附屬於所處作用域的氣泡。
2.這些識別符號全都無法從全域性作用域中進行訪問, 因此會導致ReferenceError 錯誤。
隱藏內部實現
1.從所寫的程式碼中挑選出一個任意的片段, 然後用函式宣告對它進行包裝,實際上就是把這些程式碼“隱藏” 起來了。
function doSomething(a) { b = a + doSomethisEles(a * 2); console.log(b* 3); } function doSomethiingElse(a) { return a - 1; } var b; doSomethis(2); // 15
function doSomethis(a) { function doSomethisElse(a) { return a - 1; } var b; b = a + doSomethingElse(a * 2); console.log(b * 3); } doSomethis(2); // 15
2.為什麼“隱藏” 變數和函式是一個有用的技術?
它們大都是從最小特權原則中引申出來的, 也叫最小授權或最小暴露原則。 這個原則是指在軟體設計中, 應該最小限度地暴露必要內容, 而將其他內容都“隱藏” 起來, 比如某個模組或物件的 API 設計。
規避衝突
function foo() { function bar(a) { i = 3; // 修改 for 迴圈所屬作用域中的 i console.log( a + i ); } for (var i=0; i<10; i++) { bar( i * 2 ); //糟糕, 無限迴圈了! } } foo();
1.“隱藏” 作用域中的變數和函式所帶來的另一個好處, 是可以避免同名識別符號之間的衝突,兩個識別符號可能具有相同的名字但用途卻不一樣, 無意間可能造成命名衝突。
衝突會導致變數的值被意外覆蓋。
2.函式內部的賦值操作需要宣告一個本地變數來使用, 採用任何名字都可以 。
但是軟體設計在某種情況下可能自然而然地要求使用同樣的識別符號名稱, 因此在這種情況下使用作用域來“隱藏” 內部宣告是唯一的最佳選擇。
全域性名稱空間
1.庫通常會在全域性作用域中宣告一個名字足夠獨特的變數, 通常是一個物件。
這個物件被用作庫的名稱空間, 所有需要暴露給外界的功能都會成為這個物件(名稱空間) 的屬性,而不是將自己的識別符號暴漏在頂級的詞法作用域中。
函式作用域
1.首先,必須宣告一個具名函式 foo(), 意味著 foo 這個名稱本身“汙染” 了所在作用域(在這個例子中是全域性作用域)。
其次, 必須顯式地通過函式名(foo()) 呼叫這個函式才能執行其中的程式碼。
2.如果函式不需要函式名(或者至少函式名可以不汙染所在作用域), 並且能夠自動執行,這將會更加理想。
3.如果 function 是宣告中的第一個詞, 那麼就是一個函式宣告, 否則就是一個函式表示式。
(function foo() { var a = 3; console.log(a); // 3 })(); console.log(a); // 2
4.foo 被繫結在函式表示式自身的函式中而不是所在作用域中。
立即執行函式表示式
1.由於函式被包含在一對 ( ) 括號內部, 因此成為了一個表示式, 通過在末尾加上另外一個( ) 可以立即執行這個函式, 比如 (function foo(){ .. })()。
第一個 ( ) 將函式變成表示式, 第二個 ( ) 執行了這個函式。
2.相較於傳統的 IIFE 形式, 很多人都更喜歡另一個改進的形式: (function(){ .. }())。
var a = 2; (function IIFE(global) { var a = 3; console.log(a); // 3 console.log(global.a); // 2 })(window); console.log(a); // 2
3.IIFE 的另一個非常普遍的進階用法是把它們當作函式呼叫並傳遞引數進去。
try/catch
try { undefined(); // 執行一個非法操作來強制製造一個異常 } catch (err) { console.log( err ); // 能夠正常執行! } console.log( err ); // ReferenceError: err not found
1.非常少有人會注意到 JavaScript 的 ES3 規範中規定 try/catch 的 catch 分句會建立一個塊作用域, 其中宣告的變數僅在 catch 內部有效。