淺談javascript中的的閉包
閉包可以說是javascript中最令人迷惑的概念了。需要我們在實踐中去慢慢理解,在實際編碼中,由于閉包的效率和會產生大量無法銷毀的內存,所以原則是盡量少使用閉包,但是作為javascript中的一個特別的概念,理解閉包是很重要的。閉包像是一種突破javascript中作用域限制的利劍。下面我們就從javascript中的作用域鏈談起,簡單講講閉包的概念和理解。
作用域鏈
javascript中沒有大括號級的作用域,但是javascript中擁有函數作用域。在某函數內部定義的變量,在函數外部是不可見的。
如下面這段代碼:
var a=1;
function f() {
var b = 1;
return a;
}
f();
b
這段代碼在函數外部訪問了函數f中定義的變量的b,但是是不行的,會報如下錯誤:
ReferenceError: b is not defined
data:,/* 通過 Firebug 命令行執行的表達式: */%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
下面這個例子,我們在outer中定義了另一個函數inner,那么在inner中可以訪問的變量即來自他自己的作用域,也來自他的父親作用域,也就函數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();
當我們訪問執行outer函數的時候,返回的結果是6.
所以在這個例子中,可以通過inner訪問到outer和global的變量,這就是作用域鏈。
引出閉包
我們看下面這段代碼
var a = 'global variable';
var F = function () {
var b = 'local variable';
var N = function () {
var c = 'inner local';
};
};
我們定義了一個全局變量a,一個函數F,函數內部定義了變量b,以及一個私有函數N,私有函數內部定義了變量c。
我們通過圖展示這些變量作用域之間的關系。
全局變量:
closure1.png
全部的變量:
closure.png
這個圖不太標準。但我們可以理解一下:
如果我們是a,那么我們就在全局作用域中,而如果是b,我們就位于函數f的作用域內,在這個作用域里,我們可以訪問函數f中的變量也可以訪問函數f外的全局作用域的變量,這就形成了一個作用域鏈,同樣對于c點,是位于函數n中的變量,在c點的作用域我們可以訪問圖中所有的變量。根據圖中,我們大致可以把整個空間分為 全局空間 ,F空間,和N空間。顯然,a與b是不連通的,也就是說我們在a點是無法訪問到b的,同理,顯然a也是無法訪問c點的。
這時候,通過閉包的話,我們可以把N與b連通起來。將N的空間擴展到F以外,并止步于全局空間。這就是 閉包 !
closure2.png
使用閉包后的結果就跟上圖一樣。
如果變成上圖的這樣的話,這樣N就位于全局空間和a是在同一空間的,但是由于函數N還記得被定義時,所處的環境,因此他依然可以訪問F空間并使用b,這有很有趣,因為這個時候,N與a處于同一空間,N可以訪問b,而a卻不能,這就是閉包的神奇作用。這就是讓N突破了作用域鏈,跳到了全局空間,那么我們想象這是如何做到的,其實很簡單,只要通過F將N返回出來,到全局空間就可以了。
利用閉包突破作用域鏈的三種方法
下面我們具體講解三種使用閉包突破作用域鏈的方法。
閉包1
首先,我們對上面那個函數做一些修改。
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();
我們再函數F中返回了函數N,并在函數N中返回b。
函數N有自己的私有空間,同時也可以訪問f空間和全局空間,所以b對他來說是可見的。因為F是可以在全局空間中被調用的。所以我們可以將它的返回值富裕另外一個全局變量inner,這樣就生成了一個可以訪問F私有空間的新的全局函數。
閉包2
第二種方法與第一種實現的方式不同,整體的思想還是一樣的。
我們在全局聲明一個變量inner,然后再在F中給他賦值,這樣,相當于將N保存到全局作用域了。
var inner; // placeholder
var F = function () {
var b = 'local variable';
var N = function () {
return b;
};
inner = N;
};
F();
inner();
閉包3
這次我們與前兩個不同,使用函數的參數,該參數與函數的局部變量沒什么不同,但他們是隱式創建的,我們讓函數里的子函數返回函數的參數。這樣成為全局作用域里的子函數,就可以訪問到函數內部的參數了。
function F(param) {
var N = function () {
return param;
};
param++;
return N;
}
我們像如下這樣調用函數
> var inner = F(123);
> inner();
124
函數綁定的是作用域本身,而不是在函數定義時該作用域的變量或變量當前的返回值。
小結
看完上面三種創建閉包的方式,我們是不是對閉包有了一定的模糊的認識或者印象。
事實上每個函數都可以認為是閉包,因為每個函數都在其所在的作用域內維護了某種私有關系的聯系。但大部分時候,該作用域在函數執行完之后就自行銷毀了,除非像我們上面三種情況一樣使用了閉包,返回了一個內部函數,導致作用域被保持。
現在我們可以說,如果一個函數會在其父級作用域返回之后留住對父級作用域的連接的話,相關的閉包就會被創建起來。
來自:http://www.jianshu.com/p/cff198092a4a