JavaScript的閉包究竟是什麼?

引言

看以下例子,注意內部函數form是如何引用定義定義在外部函數formFamily內的requiredWho變量的?咱們須要知道JavaScript容許你引用當前函數之外定義的變量javascript

function formFamily (){
    var requiredWho = "Father and Mother ";
    function form (who){
        return requiredWho + "need " + who ;
    }
    return form ("YingBao and MaoTan");
}
formFamily(); // "Father and Mother need YingBao and MaoTan"複製代碼

例子演進,這個例子和上面幾乎徹底相同,惟一區別是,不是在外部函數formFamily中當即調用f("YingBao and MaoTan"),而是返回form函數自己。所以f的值爲內部的函數form,調用f實際是調用form函數。但即便formFamily函數已經返回,form仍能記住requiredWho的值。java

這裏咱們須要知道,即便外部函數返回,當前函數仍然能夠引用在外部函數所定義的變量。這就意味着,你能夠返回一個內部函數,並在稍後調用它。web

function formFamily (){
    var requiredWho = "Father and Mother ";
    function form (who){
        return requiredWho + "need " + who ;
    }
    return form;
}
var f = formFamily(); 
f("YingBao"); // "Father and Mother need YingBao"
f("MaoTan"); // "Father and Mother need MaoTan"
f("YingBao and MaoTan"); // "Father and Mother need YingBao and MaoTan"複製代碼

這是如何工做的?編程

在外部函數 formFamily 中定義內部函數 form,而 form 又引用了 formFamily 做用域內的倆個變量requiredWhowho。每當form函數被調用時,其代碼都能引用到這倆個變量,由於該閉包存儲了這個倆個變量。form函數就是一個閉包。bash

在外部函數 formFamily 以外使用內部 form 函數而且引用外部函數的變量,則造成了閉包。閉包

定義

JavaScript的函數值包含了比調用它們時執行所須要的代碼還要多的信息。並且JavaScript函數值會在內部存儲它們可能會引用的定義在其封閉做用域的變量。那些在其所涵蓋的做用域內跟蹤變量的函數被稱爲閉包。app

tips1:函數能夠引用定義在其外部做用域的變量。tips2:閉包比建立它們的函數有更長的生命週期。函數式編程

優化

函數能夠引用其做用域內的任何變量,包括參數和外部函數變量。利用這點咱們編寫更加通用的formFamily函數。函數

function formFamily (requiredWho){
    function form (who){
        return requiredWho + "need " + who ;
    }
    return form;
}
var f = formFamily("Father "); 
f("YingBao and MaoTan"); // "Father need YingBao and MaoTan"
var m = formFamily("Mother "); 
m("YingBao and MaoTan"); // "Mother need YingBao and MaoTan"複製代碼

該例子建立了fm倆個徹底不一樣的函數。儘管它們都是由相同的form函數定義的,可是它們時倆個大相徑庭的對象。優化

閉包是JavaScript最優雅、最有表現力的特性之一。JavaScript甚至還提供了一種更爲方便的構建閉包字面量語法——函數表達式。

function formFamily (requiredWho){
    return function(who){
        return requiredWho + "need " + who ;
    }
}複製代碼

請注意,該函數表達式是匿名的。咱們只需其能產生一個新的函數值,沒打算在局部調用它,所以根本不必給該函數命名。

擴展

閉包能夠更新外部變量的值。實際上,閉包存儲的是外部變量的引用,而不是它們的副本。所以,對於任何具備訪問這些外部變量的閉包,均可以進行更新。以下例子。

function box(){
    var val = undefined;
    return {
        set: function(newVal) { val = newVal; },
        get: function() { return val; },
        type: function() { return typeof val; }
    };
}
var b = box();
b.type();  // "undefined"
b.set(13.2);
b.get();  // 13.2.6
b.type(); // "number"複製代碼

box函數存儲了一個可讀寫的內部值。該例子產生了一個包含三個閉包的對象,這三個閉包是setgettype屬性。它們都共享訪問val變量。set閉包更新val的值,隨後調用gettype查看更新的結果。

知識補充

function outter(){   
    var n = 0; 
    return function (){
        return n++;
    } 
}   

var o1 = outter();  
o1();//n == 0 
o1();//n == 1 
o1();//n == 2 

var o2 = outter();  
o2();//n == 0 
o2();//n == 1複製代碼

外部函數outter執行後返回的是一個function,賦值給了 o1o1 能夠訪問 nreturn 回來的function能夠訪問裏面的變量,就會致使 n 不能被回收,由於有o1有用到它。

匿名函數 function(){return n++;} 中包含對外部函數 outter 的局部變量 n 的引用,所以當外部函數 outter 返回時,n 的值被保留 ( 不會被垃圾回收機制回收 ),持續調用 o1(),將會改變 n 的值。而 o2 的值並不會隨着 o1() 被調用而改變,第一次調用 o2 會獲得 n==0 的結果,用面向對象的術語來講,就是 o1o2 爲不一樣的實例,互不干涉。

須要注意閉包的一個缺陷:閉包會致使內存泄漏。

參考資料

  1. 書籍:《Effective JavaScript 編寫高質量JavaScript代碼的68個優效方法》
  2. 博客:《JavaScript 中的函數式編程實踐》
相關文章
相關標籤/搜索