天天看點

閉包(Closure)

一.什麼是閉包(Closure)

在計算機科學中,閉包(英語:Closure),又稱詞法閉包(Lexical Closure)或函數閉包(function closures),是引用了自由變量的函數。這個被引用的自由變量将和這個函數一同存在,即使已經離開了創造它的環境也不例外。是以,有另一種說法認為閉包是由函數和與其相關的引用環境組合而成的實體。閉包在運作時可以有多個執行個體,不同的引用環境和相同的函數組合可以産生不同的執行個體。

….說的這是啥?

簡單來說,閉包就是 : 閉包就是能夠讀取其他函數内部變量的函數

二.閉包什麼時候産生

當你 定義 某個函數的時候,其實這個函數就已經産生了一個閉包.

當這個函數被執行的時候,他們的閉包能夠讓他們通路他們作用域内的資料。

三.閉包與函數的作用域之間的差別

  • 産生的時間不同:

    閉包是當你定義一個函數時建立的,而作用域是函數被執行的的時候建立的;

  • 消失的時間不同:

    函數執行完畢,它的作用域就會被清除,下次函數被執行的時候是一個全新的作用域.

    而閉包即使函數被執行完畢也不會消失.

// scope: global
var a = ;
void function one() {
  // scope: one
  // closure: [one, global]
  var b = ;

  void function two() {
    // scope: two
    // closure: [two, one, global]
    var c = ;

    void function three() {
      // scope: three
      // closure: [three, two, one, global]
      var d = ;
      console.log(a + b + c + d); // prints 10
    }();
  }();
}();

在上面的簡單例子中,我們定義并立即調用了三個函數,是以他們都建立了作用域和閉包。

函數one()的作用域就是它自己,它的閉包讓我們有通路它和全局作用域的權利。

函數two()的作用域就是它自己,它的閉包讓我們有通路它和函數one(),還有全局作用域的權利。

同樣,函數three()的閉包給我們通路所有作用域的權力。這就是為什麼我們可以在函數three()中通路所有變量的原因。
           

總之,記住一句話 : 當函數被建立的時候,它的閉包就已經産生,相應的它所能夠通路的作用域也已經确定

var v = ;

var f1 = function () {
  console.log(v);
}

var f2 = function() {
  var v = ;
  f1(); // Will this print 1 or 2?
};

f2();
           

如果你想說2,那麼你将會感到驚訝,這段代碼實際上會列印1。原因是作用域和閉包并不相同。console.log方法會使用當我們定義f1()時所建立的f1()閉包,這意味着f1()的閉包值允許我們通路f1()和全局的作用域。

我們執行f1()的地方的作用域并不會影響閉包。實際上,f1()的閉包并不會給我們通路函數f2()作用域的權力。如果你删除全局變量v,然後執行這段代碼,你将會得到錯誤消息:

var f1 = function () {
  console.log(v);
}

var f2 = function() {
  var v = ;
  f1(); // ReferenceError: v is not defined
};

f2();
           

四.閉包的用途

閉包可以用在許多地方。它的最大用處有兩個。

1.擷取函數内部的變量

function f1(){

    var n=;

    function f2(){
      alert(n); 
    }

    return f2;

  }

  var result=f1();

  result(); // 999
           

函數f2就被包括在函數f1内部,這時f1内部的所有局部變量,對f2都是可見的。但是反過來就不行,f2内部的局部變量,對f1就是不可見的。

2.讓變量的值始終保持在記憶體中。

function f1(){

    var n=;

    nAdd=function(){n+=}

    function f2(){
      alert(n);
    }

    return f2;

  }

  var result=f1();

  result(); // 999

  nAdd();

  result(); // 1000
           

result實際上就是閉包f2函數。它一共運作了兩次,第一次的值是999,第二次的值是1000。這證明了,函數f1中的局部變量n一直儲存在記憶體中,并沒有在f1調用後被自動清除。

為什麼會這樣呢?原因就在于f1是f2的父函數,而f2被賦給了一個全局變量,這導緻f2始終在記憶體中,而f2的存在依賴于f1,是以f1也始終在記憶體中,不會在調用結束後,被垃圾回收機制(garbage collection)回收。

這段代碼中另一個值得注意的地方,就是”nAdd=function(){n+=1}”這一行,首先在nAdd前面沒有使用var關鍵字,是以nAdd是一個全局變量,而不是局部變量。其次,nAdd的值是一個匿名函數(anonymous function),而這個匿名函數本身也是一個閉包,是以nAdd相當于是一個setter,可以在函數外部對函數内部的局部變量進行操作。

五.使用閉包的注意點

1.由于閉包會使得函數中的變量都被儲存在記憶體中,記憶體消耗很大,是以不能濫用閉包,否則會造成網頁的性能問題,在IE中可能導緻記憶體洩露。解決方法是,在退出函數之前,将不使用的局部變量全部删除。

2.閉包會在父函數外部,改變父函數内部變量的值。是以,如果你把父函數當作對象(object)使用,把閉包當作它的公用方法(Public Method),把内部變量當作它的私有屬性(private value),這時一定要小心,不要随便改變父函數内部變量的值。

繼續閱讀