JavaScript閉包

javascript之閉包

閉包的概念

閉包(closure)是 JavaScript 的一種語法特性。javascript

關於閉包,有一種經典的提法——「閉包是代碼塊和建立該代碼塊的上下文(環境)中數據的結合」。java

閉包就是在函數內部定義函數,內部的函數可訪問其外部函數的做用域。下面是在程序中實現閉包的例子。編程

閉包是指有權訪問另外一個函數做用域中的變量的函數。(Javascript高級編程 P178)數組

function outer(name){ // 外部的函數
  var msg="hello";
  function inner(){ // 內部函數
    alert(msg+" "+name);
  }
  return inner(); // 返回內部函數
}
var clos=outer("WANGERN");
clos();
複製代碼

執行代碼,將彈出警告「hello WANGERN」。閉包

前提

要理解閉包,還有一個很關鍵性概念—— JavaScript 的做用域規則。先解釋一下做用域(scope)。在運行函數都會建立屬於函數的上下文環境(context)及做用域,做用域即當前環境範圍內的變量。JavaScript 中最外圍的環境爲 window 對象,也就是全局做用域所在的環境。當執行到下一級環境時,下一級環境會主動包含上一級的做用域,最終造成一級一級關聯的做用域鏈(對象的 [Scope] 屬性指向該做用域鏈)。當有下一級環境生成時,上一級環境會失活,但不會自動銷燬而保存在一種「棧」式結構中,這樣能夠保證做用域鏈的延續性,也能夠環境回退時再次激活。當前環境可訪問當前做用域鏈中的所有變量,好比上面代碼中的 inner() 函數可訪問 outer() 函數中的 msg 和 name 變量。函數

閉包就是藉助這種做用域鏈,一方面可以使內部函數可訪問外部函數的變量;另外一方面,閉包還能夠抑制外部函數環境的銷燬,使其變量始終保存在內存中,直至不須要時再銷燬。ui


閉包的實例

先看下面的例子,當function裏嵌套function時,內部的function能夠訪問外部function裏的變量。spa

function foo(x) {
    var tmp = 3;
    function bar(y) {
      alert(x + y + (++tmp));
    }
    bar(10);
}
foo(2)
複製代碼

無論執行多少次,都會alert 16,由於bar能訪問foo的參數x,也能訪問foo的變量tmp。設計

但,這還不是閉包。當你return的是內部function時,就是一個閉包。內部function會close-over外部function的變量直到內部function結束。code

function foo(x) {
    var tmp = 3;
    return function (y) {
        alert(x + y + (++tmp));
    }
}
var bar = foo(2); // bar 如今是一個閉包
bar(10);
複製代碼

上面的腳本最終也會alert 16,由於雖然bar不直接處於foo的內部做用域,但bar仍是能訪問x和tmp。

可是,因爲tmp仍存在於bar閉包的內部,因此它仍是會自加1,並且你每次調用bar時它都會自加1.

(咱們其實能夠創建不止一個閉包方法,好比return它們的數組,也能夠把它們設置爲全局變量。它們全都指向相同的x和相同的tmp,而不是各自有一份副本。)

上面的x是一個字面值(值傳遞),和JS裏其餘的字面值同樣,當調用foo時,實參x的值被複制了一份,複製的那一份做爲了foo的參數x。

JS裏處理object時是用到引用傳遞的,那麼,你調用foo時傳遞一個object,foo函數return的閉包也會引用最初那個object!

function foo(x) {
  var tmp = 3;
  return function (y) {
      alert(x + y + tmp);
      x.memb = x.memb ? x.memb + 1 : 1;
      alert(x.memb);
  }
}
var age = new Number(2);
var bar = foo(age); // bar 如今是一個引用了age的閉包
bar(10);
複製代碼

不出咱們意料,每次運行bar(10),x.memb都會自加1。但須要注意的是x每次都指向同一個object變量——age,運行兩次bar(10)後,age.memb會變成2.

這和HTML對象的內存泄漏有關,呃,不過貌似超出了答題的範圍。

這裏有一個不用return關鍵字的閉包例子:

function closureExample(objID, text, timedelay) {
    setTimeout(function() {
        document.getElementById(objID).innerHTML = text;
    }, timedelay);
}
closureExample(‘myDiv’, ‘Closure is created’, 500);
複製代碼

JS裏的function能訪問它們的:

  1. 參數
  2. 局部變量或函數
  3. 外部變量(環境變量?),包括
    3.1 全局變量,包括DOM。
    3.2 外部函數的變量或函數。

若是一個函數訪問了它的外部變量,那麼它就是一個閉包。

注意,外部函數不是必需的。經過訪問外部變量,一個閉包能夠維持(keep alive)這些變量。在內部函數和外部函數的例子中,外部函數能夠建立局部變量,而且最終退出;可是,若是任何一個或多個內部函數在它退出後卻沒有退出,那麼內部函數就維持了外部函數的局部數據。

一個典型的例子就是全局變量的使用。

從技術上來說,在JS中,每一個function都是閉包,由於它老是能訪問在它外部定義的數據。

閉包常常用於建立含有隱藏數據的函數(但並不老是這樣)。

var db = (function() {
  // 建立一個隱藏的object, 這個object持有一些數據
  // 從外部是不能訪問這個object的
  var data = {};
  // 建立一個函數, 這個函數提供一些訪問data的數據的方法
  return function(key, val) {
    if (val === undefined) { return data[key] } // get
    else { return data[key] = val } // set
  }
  // 咱們能夠調用這個匿名方法
  // 返回這個內部函數,它是一個閉包
})();

db('x'); // 返回 undefined
db('x', 1); // 設置data['x']爲1
db('x'); // 返回 1
// 咱們不可能訪問data這個object自己
// 可是咱們能夠設置它的成員
複製代碼

閉包的總結

(1) 閉包是一種設計原則,它經過分析上下文,來簡化用戶的調用,讓用戶在不知曉的狀況下,達到他的目的; (2) 網上主流的對閉包剖析的文章其實是和閉包原則反向而馳的,若是須要知道閉包細節才能用好的話,這個閉包是設計失敗的; (3) 因爲閉包會攜帶包含它的函數的做用域,所以會比其餘函數佔用更多的內存。過分使用閉包可能會致使內存佔用過多。

相關文章
相關標籤/搜索