閉包的定義
學(xué)術(shù)來說,閉包是指在 JavaScript 中捐凭,內(nèi)部函數(shù)總是可以訪問其所在的外部函數(shù)中聲明的參數(shù)和變量,即使在其外部函數(shù)被返回(壽命終結(jié))了之后。
就我個人而言的理解御板,從形式來看,閉包就是在函數(shù)里面定義一個函數(shù)牛郑,從特點來說怠肋,子函數(shù)能夠讀寫父函數(shù)的局部變量。
function parent() {
var count = 0;
return function children(){
count++;
console.log(count);
}
}
var children = parent();
children(); // 1
children(); // 2
如何辨別閉包
雖然生產(chǎn)中淹朋,我們一直極力回避復(fù)雜的閉包笙各,閉包容易降低代碼的閱讀性,但是大家卻非常喜歡考閉包础芍,知乎也有相關(guān)討論:
https://www.zhihu.com/question/30861304
就讓我們來看看那些面試中的閉包杈抢。
案例1:
function f1() {
var n = 999;
nAdd = function () { n += 1;}
function f2() {
console.log(n);
}
}
var result = f1();
result(); // 999
nAdd(); //
result(); // 1000
- 執(zhí)行f1返回得是一個f2的function。
- 執(zhí)行f2 打印999仑性,其中n是f1的局部變量惶楼,作為閉包的f2能訪問父函數(shù)的局部變量。
- 執(zhí)行nAdd方法,實際上歼捐,nAdd是一個匿名函數(shù)何陆,n作為引用傳入到匿名函數(shù)中,將匿名函數(shù)傳給nAdd豹储,因為nAdd方法沒有做
var
聲明贷盲,nAdd是一個全局函數(shù),執(zhí)行全局函數(shù)剥扣。調(diào)用nAdd的時候?qū)蛴?000
案例2
var tasks = [];
for (var i=0; i<3; i++) {
tasks.push(function() {
console.log('>>> ' + i);
});
}
相信大多數(shù)人都會回答晃洒,打印陸續(xù)打印1,2朦乏,3球及,而實際情況打印的是3,3呻疹,3 因為匿名函數(shù)保存的是i的引用吃引,當(dāng)for循環(huán)結(jié)束的時候,i已經(jīng)變成3了刽锤,所以打印的時候變成3镊尺,相對其他題目的考察方向,這個例子的考察的方向是如何利用閉包解決問題
for (var i=0; i<3; i++) {
(function(n) {
tasks.push(function() {
console.log('>>> ' + n);
});
})(i);
}
閉包里的匿名函數(shù)并思,讀取變量的順序是庐氮,先讀取本地變量,再讀取父函數(shù)的局部變量宋彼,如果找不到到全局里面搜索弄砍,i作為局部變量存到閉包里面,所以調(diào)整后的代碼可以能正常打印输涕。
案例3:
是不是覺得有點感覺了音婶,看看以下案例?
function fun(n,o){
console.log(o);
return {
fun:function(m){//[2]
return fun(m,n);//[1]
}
}
}
var a=fun(0);
a.fun(1);
a.fun(2);
a.fun(3);
var b=fun(0).fun(1).fun(2).fun(3);
var c=fun(0).fun(1);
c.fun(2);
c.fun(3);
鑒于分析篇幅過大莱坎,就不做分析了衣式,可以參考:
http://segmentfault.com/a/1190000004187681
閉包與內(nèi)存泄漏
javascript的主要通過計數(shù)器的方式方式回收內(nèi)存,假設(shè)有A,B,C三個對象檐什,當(dāng)a引用b的時候碴卧,那么b的引用計數(shù)器增加1,同時b引用c的時候乃正,c計數(shù)器增加1住册,當(dāng)a被釋放的時候,b的計數(shù)器減少1烫葬,變成0被釋放界弧,c計數(shù)器變成0凡蜻,被釋放,然而垢箕,當(dāng)遇到b和c之間存在相互引用的時候划栓,就無法通過計數(shù)器方式釋放內(nèi)存,而必包恰好是導(dǎo)致這種情況發(fā)生的溫床条获。閉包不代表一定會帶來內(nèi)存泄漏忠荞,良好的閉包設(shè)計是正常內(nèi)存使用。
function parent() {
var childrenVar = {a: 1};
return function() {
console.log(childrenVar);
}
}
var children = parent();
children(); // {a: 1}
當(dāng)parent函數(shù)結(jié)束時發(fā)現(xiàn)childrenVar 變量被匿名函數(shù)占用帅掘,所以parent無法釋放委煤,導(dǎo)致內(nèi)存泄漏。
翻閱了不少資料修档,有人已經(jīng)對js的閉包做過不少測試碧绞,具體可以參考
http://justjavac.iteye.com/blog/1465169
閉包的應(yīng)用
說了那么多閉包的壞處,難道閉包就一無是處么吱窝?實際上讥邻,只要保證使用閉包的適合,不要重復(fù)創(chuàng)建院峡,不斷創(chuàng)建兴使,無休止創(chuàng)建,使用閉包并沒有想象中那么嚴重照激,例如发魄,當(dāng)我們需要利用閉包來實現(xiàn)統(tǒng)計,那是內(nèi)存使用俩垃,不叫內(nèi)存泄漏励幼。
這里列舉兩個例子
案例1
var middleware = function (s) {
return function (req, res, next) {
console.log(s); // hello world
return next();
}
}
app.use(middleware('hello world'));
使用閉包能夠為 express中間件,傳遞參數(shù)到中間件當(dāng)中吆寨。
案例2
var request = require('request');
var _ = require('underscore');
var middleware = function (req, res, next) {
req.request = function (options, callback) {
_.extend(options, {headers: req.headers});
request(options, callback);
}
}
app.use(middleware);
app.get(function (req, res, next) {
req.request('/api', function () {
res.send('ok');
})
})
現(xiàn)在有這么一個業(yè)務(wù)需求赏淌,做一個api代理,代理需要把原來api請求的header也帶上啄清,如果直接使用request模塊做轉(zhuǎn)發(fā),時間久了俺孙,很容易會導(dǎo)致忘記帶上原來請求的header辣卒,所以寫了一個request方法綁定到req上。
最后
閉包睛榄,并不是什么壞東西荣茫,不要每次提到閉包都與內(nèi)存泄漏掛鉤,正常使用閉包场靴,是使用內(nèi)存啡莉,不是內(nèi)存泄漏港准,請充分利用好閉包的特性。