1. 程式人生 > >JavaScript閉包--模仿塊級作用域

JavaScript閉包--模仿塊級作用域

正如閉包的定義一樣:“閉包指的是有權訪問另一個函式作用域中的變數的函式”, 閉包最大的意義就在於閉包可以對另一個函式作用域的變數進行訪問,由此,閉包可以延伸出一系列的用法。

模仿塊級作用域

JavaScript沒有塊級作用域的概念。這意味著在塊語句中定義的變數,實際上是包含在函式中而非語句中建立的。從作用域鏈的角度來理解是,所有在函式內定義的變數(所有,也就是說塊語句中定義的變數也包含在內)都會在這個函式執行時所建立的函式的活動物件中,因此從函式內的所有變數定義開始,就可以在函式內部隨處訪問它,閉包也可以通過作用域鏈訪問它。
例子:

function outputNumbers(count){
    for
(var i = 0; i < count; i++){ console.log(i); // 0, 1, ... count - 1 } console.log(i); // count }

C++, JAVA等語言中,變數i只會在for迴圈的語句塊(block)中有定義,迴圈一旦結束,變數i就會被銷燬。可是在JavaScript中,變數i是定義在outputNumbers()的活動物件中,因此從函式內的所有變數定義開始,就可以在函式內部隨處訪問它,閉包也可以通過作用域鏈訪問它。即使像下面這樣重新宣告同一個變數,也不會改變它的值。

function outputNumbers(count){
    for
(var i = 0; i < count; i++){ console.log(i); // 0, 1, ... count - 1 } var i; // redeclare i console.log(i); // count }

JavaScript從來不管是否多次聲明瞭同一個變數;遇到這種情況,JavaScript只會對後續的宣告視而不見(不過會執行後續宣告中的變數初始化),將其當成一個賦值語句。

函式包裝器可以用來模仿塊作用域並避免這個問題。
函式包裝器就是建立並立即呼叫一個函式。

(function(){
    console.log("Hello World!"
)
; })
();

這段程式碼直接輸出”Hello World”, 這就是一個函式包裝器。
函式包裝器的作用:

  1. 立即執行函式中的程式碼,又不會再記憶體中留下對該函式的引用;
  2. 函式內部的所有變數都會被立即銷燬(除非將這些變數賦值給了包含作用域中的變數)。

當在函式內部使用函式包裝器的時候,此時函式包裝器就是一個閉包,有權訪問外部環境中的所有變數。

function outputNumbers(count){
    (function(){
        //塊級作用域
        for(var i = 0; i < count; i++){
            console.log(i); // 0, 1, ... count - 1
        }
    })();
    console.log(i); // error
}

在函式包裝器中可以訪問外部環境outputNumbers()的變數count,列印0, 1, … count - 1,但是在函式包裝器執行完畢之後,再訪問變數i就會丟擲錯誤,因為i是在函式包裝器中定義的,outputNumbers()函式無法訪問。

無論在什麼地方,如果只需要一些臨時變數,就可以使用塊級作用域!

使用函式包裝器這種閉包可以減少閉包過多佔用記憶體的問題。因為沒有指向匿名函式的引用, 所以只要函式包裝器執行完畢,就可以立即銷燬其作用域鏈了。

函式包裝器這種技術經常在全域性作用域中被用在函式外部,從而限制想全域性作用域中新增過多的變數和函式。一般來說,我們都應該儘量少向全域性作用域中新增變數和函式。過多的全域性變數和函式很容易導致命名衝突。通過建立塊級作用域,每個開發人員既可以使用自己的變數,有不必擔心搞亂全域性作用域。例如:

(function(){
    var now = new Date();
    if (now.getMonth() == 0 && now.getDate() == 1) {
        console.log("Happy new year");
    }
})();

將這段程式碼放在全域性作用域中,可以用來確定哪天是一月一日。其中變數now現在是匿名函式中的區域性變數,避免了在全域性變數中建立。