iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 17
0

源自函式的範疇

前一章的內容回顧,a 泡泡包著 b 泡泡

你在 a 裡面,會看到 b 但是不能拿 b 裡面的東西。

function b(aa){
 var cc = 3;
 console.log( aa + cc );
}

foo( 2 ); // 5
cc; // ReferenceError

隱藏在普通範疇中

Tony 要開車,只需要知道方向盤、油門、煞車、打檔。所以你不會提供 Tony 引擎室裡面的配置。

這是最小權限原則 ( Principle of Least Privilege ) 的軟體設計原理。
或 最小授權 ( Least Authority ) 或 最小曝露 ( Least Exposure )

在軟體設計中,你應該只透露所需要的最少東西,並把其他的東西都「隱藏」起來。

function doSomething(a) {
    b = a + doSomethingElse( a * 2 );
    console.log( b * 3 );
}

function doSomethingElse(a) {
    return a - 1;
}

var b;
doSomething( 2 ); // 15

這裡有兩個併排的泡泡。全域可以取用到 b 和 doSomething,但沒必要。

讓泡泡包著泡泡吧。

function doSomething(a) {
    function doSomethingElse(a) { // 寫在內部。
        return a - 1;
    }
    var b; // 保持私有。
    
    b = a + doSomethingElse( a * 2 );
    console.log( b * 3 );
}
doSomething( 2 ); // 15

這樣開放的只有 doSomething。

避免衝突

產生衝突的例子。

function foo(){                 // step2
    function bar(a) {           // step5 bar(a = 0); step10 bar(a = 4)
        i = 3;                  // step6 i = 3;      step11 i = 3 重複了
        console.log( a + i );   // step7 ( 0 + 3);
    }
    for (var i = 0; i<10; i++){ // step3 i = 0;      step8 i++ = 4;(永遠小於10)
        bar( i * 2);            // step4 bar(0);     step9 bar(4)
    }
}
foo();                          // step1

照著 step 執行後,就會造成 step6~9 重複執行。不斷得到 11 的結果。( 金恐怖 )

要怎麼避免呢?

  1. 請在他所屬的範疇裡宣告 var i = 3;
  2. 用不同的變數名稱j

全域命名空間 ( Global namespaces )

這邊還沒辦法意會。
(如果載入許多程式庫,這些程式庫如果沒有規範,就會互相使用到全域函數。所以,在製作函式庫之前會先做個命名空間。)

var MyReallyCollLibrary = {
    awesome: "stuff",
    doSomething: function(){
        // ...
    },
    doAnotherThing: function(){
        // ...
    }
};

模組管理 ( Module management )

一樣要避免衝突。但是是用模組的方式達成。
這邊 Tony 等級不夠,等到第五章在看。

函式作為範疇

如果我們想對原本的變數,切割出範疇。可以用函式。

var a = 2;
console.log(a);

現在要多一個範疇,裡面的 a = 3; 要怎麼做?

加入以下程式碼。

function foo(){
    var a = 3;
    console.log( a ); // 3 
}
foo()

但原本的全域裡面只有 a。增加後的的全域裡面不只 a 還有 foo()。 這樣就「污染」了全域。
而且還要呼叫,才能使用。

就產生了 IIFE (Immediately Invoked Function Expression)立即執行函數。

不汙染馬上執行

(function foo(){
    var a = 3;
    console.log( a );
})();

如果 function 前面東西,就是函式宣告。
如果 function 前面東西,就是函式運算式。

關鍵差異在於有沒有被繫結 ( bound )。小括號會先結算繫結。

匿名 VS 具名

函式運算式有匿名和具名。

比較熟悉的匿名是做為 回呼 ( call back ) 的參數。

setTimeout( function(){
    console.log("I waited 1 second!");
}, 1000);

裡面的 function 沒有名稱,就是匿名。
匿名的缺點

  1. 不好除錯,沒名稱顯示
  2. 重複用有困難
  3. 沒有可讀性

行內函式運算式 ( Inline function expressions )強大且實用沒有明顯壞處。

setTimeout( function timeoutHandler() {
    console.log("I waited 1 second!");
}, 1000);

請讓你的函式運算式具名

即刻調用函式運算式

前面有提到過,這邊再一次列出來。
兩種寫法都可以

(function(){..}()); // 在內執行
(function(){..})(); // 在外執行
  1. IIFE 也可以傳入參數
var a = 2;
(funciton IIFE( global ){
    var a = 3;
    console.log( a ); // 3
    console.log( global.a ); // 2
})( window );
console.log( a ); // 2

這樣可以在同一個範疇同時使用範疇內和全域的參考。

  1. 確保 undefined 的值是正確的
undefined = true; // 這是假設,別用
(function IIFE( undefined ){
    var a;
    if ( a === undefined ){
        console.log("Undefined is safe here!");
    }
})();

給了 IIFE undefined 的參數,但沒有傳給他任何值。透過判斷,這個範疇下的 undefined 是安全的。(利基微小)

  1. 傳參數的另外一個變體。(還不太瞭解好懂的定義在哪)
var a = 2;
(function IIFE( def ){
    def( window );
})(function def(global){
    var a = 3;
    console.log( a ); // 3
    console.log( global.a ); // 2
})

def() 作為參數傳入 IIFE(),window 再以參數的形式傳入 def()。

參考資料

你所不知道的JS


上一篇
Day16 - 語彙範疇
下一篇
Day18 - 區塊範疇
系列文
你為什麼不問問神奇 JavaScript 呢?30

尚未有邦友留言

立即登入留言