閉包可以說(shuō)是javascript中最令人迷惑的概念了。需要我們?cè)趯?shí)踐中去慢慢理解,在實(shí)際編碼中,由于閉包的效率和會(huì)產(chǎn)生大量無(wú)法銷毀的內(nèi)存,所以原則是盡量少使用閉包,但是作為javascript中的一個(gè)特別的概念,理解閉包是很重要的。閉包像是一種突破javascript中作用域限制的利劍。下面我們就從javascript中的作用域鏈談起,簡(jiǎn)單講講閉包的概念和理解。
作用域鏈
javascript中沒(méi)有大括號(hào)級(jí)的作用域,但是javascript中擁有函數(shù)作用域。在某函數(shù)內(nèi)部定義的變量,在函數(shù)外部是不可見(jiàn)的。
如下面這段代碼:
var a=1;
function f() {
var b = 1;
return a;
}
f();
b
這段代碼在函數(shù)外部訪問(wèn)了函數(shù)f中定義的變量的b,但是是不行的,會(huì)報(bào)如下錯(cuò)誤:
ReferenceError: b is not defined
data:,/* 通過(guò) Firebug 命令行執(zhí)行的表達(dá)式: */%0Avar%20a%3D1%3B%0Afunction%20f()%20%7B%0A%20%20var%20b%20%3D%201%3B%0A%20%20return%20a%3B%0A%7D%0Af()%3B%0Ab
Line 8
下面這個(gè)例子,我們?cè)趏uter中定義了另一個(gè)函數(shù)inner,那么在inner中可以訪問(wèn)的變量即來(lái)自他自己的作用域,也來(lái)自他的父親作用域,也就函數(shù)outer,所以這樣就形成了一條作用域鏈。
var global = 1;
function outer() {
var outer_local = 2;
function inner() {
var inner_local = 3;
return inner_local + outer_local + global;
}
return inner();
}
outer();
當(dāng)我們?cè)L問(wèn)執(zhí)行outer函數(shù)的時(shí)候,返回的結(jié)果是6.
所以在這個(gè)例子中,可以通過(guò)inner訪問(wèn)到outer和global的變量,這就是作用域鏈。
引出閉包
我們看下面這段代碼
var a = 'global variable';
var F = function () {
var b = 'local variable';
var N = function () {
var c = 'inner local';
};
};
我們定義了一個(gè)全局變量a,一個(gè)函數(shù)F,函數(shù)內(nèi)部定義了變量b,以及一個(gè)私有函數(shù)N,私有函數(shù)內(nèi)部定義了變量c。
我們通過(guò)圖展示這些變量作用域之間的關(guān)系。
全局變量:

全部的變量:

這個(gè)圖不太標(biāo)準(zhǔn)。但我們可以理解一下:
如果我們是a,那么我們就在全局作用域中,而如果是b,我們就位于函數(shù)f的作用域內(nèi),在這個(gè)作用域里,我們可以訪問(wèn)函數(shù)f中的變量也可以訪問(wèn)函數(shù)f外的全局作用域的變量,這就形成了一個(gè)作用域鏈,同樣對(duì)于c點(diǎn),是位于函數(shù)n中的變量,在c點(diǎn)的作用域我們可以訪問(wèn)圖中所有的變量。根據(jù)圖中,我們大致可以把整個(gè)空間分為 全局空間 ,F(xiàn)空間,和N空間。顯然,a與b是不連通的,也就是說(shuō)我們?cè)赼點(diǎn)是無(wú)法訪問(wèn)到b的,同理,顯然a也是無(wú)法訪問(wèn)c點(diǎn)的。
這時(shí)候,通過(guò)閉包的話,我們可以把N與b連通起來(lái)。將N的空間擴(kuò)展到F以外,并止步于全局空間。這就是** 閉包 **!

使用閉包后的結(jié)果就跟上圖一樣。
如果變成上圖的這樣的話,這樣N就位于全局空間和a是在同一空間的,但是由于函數(shù)N還記得被定義時(shí),所處的環(huán)境,因此他依然可以訪問(wèn)F空間并使用b,這有很有趣,因?yàn)檫@個(gè)時(shí)候,N與a處于同一空間,N可以訪問(wèn)b,而a卻不能,這就是閉包的神奇作用。這就是讓N突破了作用域鏈,跳到了全局空間,那么我們想象這是如何做到的,其實(shí)很簡(jiǎn)單,只要通過(guò)F將N返回出來(lái),到全局空間就可以了。
利用閉包突破作用域鏈的三種方法
下面我們具體講解三種使用閉包突破作用域鏈的方法。
閉包1
首先,我們對(duì)上面那個(gè)函數(shù)做一些修改。
var a = 'global variable';
var F = function () {
var b = 'local variable';
var N = function () {
var c = 'inner local';
return b;
};
return N;
};
var inner = F();
inner();
我們?cè)俸瘮?shù)F中返回了函數(shù)N,并在函數(shù)N中返回b。
函數(shù)N有自己的私有空間,同時(shí)也可以訪問(wèn)f空間和全局空間,所以b對(duì)他來(lái)說(shuō)是可見(jiàn)的。因?yàn)镕是可以在全局空間中被調(diào)用的。所以我們可以將它的返回值富裕另外一個(gè)全局變量inner,這樣就生成了一個(gè)可以訪問(wèn)F私有空間的新的全局函數(shù)。
閉包2
第二種方法與第一種實(shí)現(xiàn)的方式不同,整體的思想還是一樣的。
我們?cè)谌致暶饕粋€(gè)變量inner,然后再在F中給他賦值,這樣,相當(dāng)于將N保存到全局作用域了。
var inner; // placeholder
var F = function () {
var b = 'local variable';
var N = function () {
return b;
};
inner = N;
};
F();
inner();
閉包3
這次我們與前兩個(gè)不同,使用函數(shù)的參數(shù),該參數(shù)與函數(shù)的局部變量沒(méi)什么不同,但他們是隱式創(chuàng)建的,我們讓函數(shù)里的子函數(shù)返回函數(shù)的參數(shù)。這樣成為全局作用域里的子函數(shù),就可以訪問(wèn)到函數(shù)內(nèi)部的參數(shù)了。
function F(param) {
var N = function () {
return param;
};
param++;
return N;
}
我們像如下這樣調(diào)用函數(shù)
> var inner = F(123);
> inner();
124
函數(shù)綁定的是作用域本身,而不是在函數(shù)定義時(shí)該作用域的變量或變量當(dāng)前的返回值。
小結(jié)
看完上面三種創(chuàng)建閉包的方式,我們是不是對(duì)閉包有了一定的模糊的認(rèn)識(shí)或者印象。
事實(shí)上每個(gè)函數(shù)都可以認(rèn)為是閉包,因?yàn)槊總€(gè)函數(shù)都在其所在的作用域內(nèi)維護(hù)了某種私有關(guān)系的聯(lián)系。但大部分時(shí)候,該作用域在函數(shù)執(zhí)行完之后就自行銷毀了,除非像我們上面三種情況一樣使用了閉包,返回了一個(gè)內(nèi)部函數(shù),導(dǎo)致作用域被保持。
現(xiàn)在我們可以說(shuō),如果一個(gè)函數(shù)會(huì)在其父級(jí)作用域返回之后留住對(duì)父級(jí)作用域的連接的話,相關(guān)的閉包就會(huì)被創(chuàng)建起來(lái)。