天天看點

3.0 利用閉包模仿塊級作用域

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現在是匿名函數中的局部變量,避免了在全局變量中建立。