- 究竟什么是閉包?
- 閉包在什么場(chǎng)景下使用?
- 寫前端程序需要用到閉包嗎?我用jQuery也能寫的好好滴呀?
對(duì)于初學(xué)者來說,常常會(huì)覺得閉包是個(gè)很難理解的概念,我認(rèn)為之所以覺得難以理解,是因?yàn)闆]有了解到閉包的用途以及它通常的使用場(chǎng)景,實(shí)際開發(fā)中,閉包的運(yùn)用非常廣泛。
如果知道了使用閉包可以解決哪些問題,使用閉包會(huì)帶來哪些好處,完全掌握并熟練使用閉包就不再是一個(gè)難題了。還是那句話:學(xué)以致用,如果不知道如何使用,就是沒有學(xué)會(huì)。do it, make your hand dirty!
“閉包”的概念 ——《百度百科》
閉包是指可以包含自由(未綁定到特定對(duì)象)變量的代碼塊;這些變量不是在這個(gè)代碼塊內(nèi)或者任何全局上下文中定義的,而是在定義代碼塊的環(huán)境中定義(局部變量)?!伴]包” 一詞來源于以下兩者的結(jié)合:要執(zhí)行的代碼塊(由于自由變量被包含在代碼塊中,這些自由變量以及它們引用的對(duì)象沒有被釋放)和為自由變量提供綁定的計(jì)算環(huán)境(作用域)。在PHP、Scala、Scheme、Common Lisp、Smalltalk、Groovy、JavaScript、Ruby、 Python、Go、Lua、objective c、swift 以及Java(Java8及以上)等語言中都能找到對(duì)閉包不同程度的支持。--百度百科
從“閉包”的概念上可以知道,閉包其實(shí)是一個(gè)通用的概念,在很多領(lǐng)域中(比如“離散數(shù)學(xué)”,“計(jì)算機(jī)”等領(lǐng)域都有使用)。
“閉包”的概念 ——《javascript權(quán)威指南 第6版》
Like most modern programming languages, JavaScript uses lexical scoping. This means that functions are executed using the variable scope that was in effect when they were defined, not the variable scope that is in effect when they are invoked. In order to implement lexical scoping, the internal state of a JavaScript function object must include not only the code of the function but also a reference to the current scope chain.This combination of a function object and a scope (a set of variable bindings) in which the function’s variables are resolved is called a closure in the computer science literature. ——引用自“javascript權(quán)威指南 第6版”
在javascript語言中,閉包就是函數(shù)和該函數(shù)作用域的組合。從這個(gè)概念上來講,在js中,所有函數(shù)都是閉包(函數(shù)都是對(duì)象并且函數(shù)都有和他們相關(guān)聯(lián)的作用域鏈scope chain)。
既然所有函數(shù)都是閉包,還有必要專門提這個(gè)概念嗎?
大多數(shù)函數(shù)被調(diào)用時(shí)(invoked),使用的作用域和他們被定義時(shí)(defined)使用的作用域是同一個(gè)作用域,這種情況下,閉包神馬的,無關(guān)緊要。但是,當(dāng)他們被invoked的時(shí)候,使用的作用域不同于他們定義時(shí)使用的作用域的時(shí)候,閉包就會(huì)變的非常有趣,并且開始有了很多的使用場(chǎng)景,這就是你之所以要掌握閉包的原因。
理解“閉包” step 1:掌握嵌套函數(shù)的詞法作用域規(guī)則(lexical scoping rules)
var scope = "global scope";
function checkScope() {
var scope = "local scope";
function f() {
return scope;
}
return f();
}
checkScope(); //=> "local scope"
分析一下上面的代碼,該代碼定義了一個(gè)全局變量 scope,以及一個(gè)函數(shù)checkScope,在函數(shù)checkScope中,定一個(gè)一個(gè)局部變量,同樣命名為scope,以及一個(gè)函數(shù)f(嵌套函數(shù))。
- 在js中,函數(shù)可以用來創(chuàng)建函數(shù)作用域;
- 函數(shù)就像一層半透明玻璃,在函數(shù)內(nèi)部可以看到函數(shù)外部的變量,但是在函數(shù)外部,看不到函數(shù)內(nèi)部的變量。
- 變量的搜索是從內(nèi)向外而不是從外向內(nèi)搜索的。
代碼執(zhí)行過程分析:

checkScope被invoke時(shí),return f(),運(yùn)行內(nèi)部嵌套函數(shù)f,f沿著作用域鏈從內(nèi)向外尋找變量scope,找到“l(fā)ocal scope”,停止尋找,因此,函數(shù)返回 “l(fā)ocal scope”;
接下來,代碼稍作修改:
var scope = "global scope";
function checkScope() {
var scope = "local scope";
function f() {
return scope;
}
return f;
}
checkScope()(); //=> "這次返回什么?"
代碼執(zhí)行過程分析:
checkScope被invoke時(shí),將內(nèi)部嵌套的函數(shù)f返回,因此checkScope()()這句執(zhí)行時(shí),其實(shí)運(yùn)行的是f(),f函數(shù)返回scope變量,在這種情況下,f會(huì)從哪個(gè)作用域里去尋找變量scope呢?
remember 詞法作用域的基礎(chǔ)規(guī)則:函數(shù)被執(zhí)行時(shí)(executed)使用的作用域鏈(scope chain)是被定義時(shí)的scope chain,而不是執(zhí)行時(shí)的scope chain
嵌套函數(shù)f(), 被定義時(shí),所在的作用域鏈中,變量scope是被綁定的值是“l(fā)ocal scope”,而不是"global scope",因此,以上代碼的結(jié)果是啥?沒錯(cuò),是"local scope"!
這就是閉包的神奇特性:閉包可以捕獲到局部變量和參數(shù)的外部函數(shù)綁定,即便外部函數(shù)的調(diào)用已經(jīng)結(jié)束。
只要記住一點(diǎn):詞法作用域的規(guī)則,即函數(shù)被執(zhí)行時(shí)(executed)使用的作用域鏈(scope chain)是被定義時(shí)的scope chain,而不是執(zhí)行時(shí)的scope chain,就可以很容易的理解閉包的行為了。
理解“閉包” step 2:掌握閉包的使用場(chǎng)景
在js版本的設(shè)計(jì)模式中,很多模式的實(shí)現(xiàn)都需要借助于閉包,因此,掌握閉包的使用場(chǎng)景,可以結(jié)合設(shè)計(jì)模式一起理解學(xué)習(xí)。這里引用了《JavaScript設(shè)計(jì)模式與開發(fā)實(shí)踐》中的很多例子,書很好,推薦大家閱讀學(xué)習(xí)。
閉包經(jīng)典使用場(chǎng)景一:通過循環(huán)給頁面上多個(gè)dom節(jié)點(diǎn)綁定事件
場(chǎng)景描述:假如頁面上有5個(gè)button,要給button綁定onclick事件,點(diǎn)擊的時(shí)候,彈出對(duì)應(yīng)button的索引編號(hào)。

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<button>Button0</button>
<button>Button1</button>
<button>Button2</button>
<button>Button3</button>
<button>Button4</button>
</body>
</html>
先隨手來一段for循環(huán):
var btns = document.getElementsByTagName('button');
for(var i = 0, len = btns.length; i < len; i++) {
btns[i].onclick = function() {
alert(i);
}
}
通過執(zhí)行該段代碼,發(fā)現(xiàn)不論點(diǎn)擊哪個(gè)button ,均alert 5;
why?
因?yàn)?,onclick事件是被異步觸發(fā)的,當(dāng)事件被觸發(fā)時(shí),for循環(huán)早已結(jié)束,此時(shí)變量 i 的值已經(jīng)是 5 。所以,當(dāng)onlick事件函數(shù)順著作用域鏈從內(nèi)向外查找變量 i 時(shí),找到的值總是 5 。
那怎么能循環(huán)給button添加事件,并且還能alert出來不同的值呢?答案當(dāng)然是:“閉包”!在閉包的作用下,定義事件函數(shù)的時(shí)候,每次循環(huán)的i值都被封閉起來,這樣在函數(shù)執(zhí)行時(shí),會(huì)查找定義時(shí)的作用域鏈,這個(gè)作用域鏈里的i值是在每次循環(huán)中都被保留的,因此點(diǎn)擊不同的button會(huì)alert出來不同的i。
上代碼:
Tip: 在js中,沒有塊級(jí)作用域 ,只有函數(shù)作用域??梢圆捎谩傲⒓磮?zhí)行函數(shù)Immediately-Invoked Function Expression (IIFE)”的方式創(chuàng)建作用域。
for(var i = 0, len = btns.length; i < len; i++) {
(function(i) {
btns[i].onclick = function() {
alert(i);
}
}(i))
}
運(yùn)行以上代碼,是符合我們需求的。
閉包使用場(chǎng)景二:封裝變量
閉包可以將一些不希望暴露在全局的變量封裝成“私有變量”。
假如有一個(gè)計(jì)算乘積的函數(shù),mult函數(shù)接收一些number類型的參數(shù),并返回乘積結(jié)果。為了提高函數(shù)性能,我們?cè)黾泳彺鏅C(jī)制,將之前計(jì)算過的結(jié)果緩存起來,下次遇到同樣的參數(shù),就可以直接返回結(jié)果,而不需要參與運(yùn)算。這里,存放緩存結(jié)果的變量不需要暴露給外界,并且需要在函數(shù)運(yùn)行結(jié)束后,仍然保存,所以可以采用閉包。
上代碼:
var mult = (function(){
var cache = {};
var calculate = function() {
var a = 1;
for(var i = 0, len = arguments.length; i < len; i++) {
a = a * arguments[i];
}
return a;
}
return function() {
var args = Array.prototype.join.call(arguments, ',');
if(args in cache) {
return cache[args];
}
return cache[args] = calculate.apply(null, arguments);
}
}())
閉包使用場(chǎng)景三:延續(xù)局部變量的壽命
img對(duì)象經(jīng)常用于數(shù)據(jù)上報(bào),如下:
var report = function(src) {
var img = new Image();
img.src = src;
}
report('http://xxx.com/getUserInfo');
這段代碼在運(yùn)行時(shí),發(fā)現(xiàn)在一些低版本瀏覽器上存在bug,會(huì)丟失部分?jǐn)?shù)據(jù)上報(bào),原因是img是report函數(shù)中的局部變量,當(dāng)report函數(shù)調(diào)用結(jié)束后,img對(duì)象隨即被銷毀,而此時(shí)可能還沒來得及發(fā)出http請(qǐng)求,所以此次請(qǐng)求就會(huì)丟失。
因此,我們使用閉包把img對(duì)象封閉起來,就可以解決數(shù)據(jù)丟失的問題:
var report = (function() {
var imgs = [];
return function(src) {
var img = new Image();
imgs.push(img);
img.src = src;
}
}())
閉包+設(shè)計(jì)模式
在諸多設(shè)計(jì)模式中,閉包都有廣泛的應(yīng)用。對(duì)象==數(shù)據(jù)+方法。而閉包是在過程中以環(huán)境的形式包含了數(shù)據(jù)。因此,通常面向?qū)ο竽軐?shí)現(xiàn)的功能,使用閉包也可以實(shí)現(xiàn)。
涉及到設(shè)計(jì)模式,閉包就是一種理所當(dāng)然的存在,必須熟練使用,才可以理解每種設(shè)計(jì)模式的意圖。這里先不引入設(shè)計(jì)模式了,內(nèi)容太多,有空再總結(jié)吧 :)