JavaScript閉包

阮一峰的博客:http://www.ruanyifeng.com/blog/2009/08/learning_javascript_closures.html

創(chuàng)建匿名函數(shù)并立即執(zhí)行

理論上講,創(chuàng)建一個(gè)匿名函數(shù)并立刻執(zhí)行可以這么寫(xiě):

function (x) { return x * x;}(3); // 9

由于JavaScript語(yǔ)法解析的問(wèn)題坐榆,會(huì)報(bào)SyntaxError錯(cuò)誤解总,因此需要用括號(hào)把整個(gè)函數(shù)定義括起來(lái):

(function (x) { return x * x }) (3); //9

高階函數(shù)

一個(gè)函數(shù)就可以接收另一個(gè)函數(shù)作為參數(shù),這種函數(shù)就稱之為高階函數(shù)挨厚。如map, reduce,sort,filter。通常情況下,求和的函數(shù)是這樣定義的

var arr = [1, 2, 3, 4, 5]; 
function sum(arr){
    return arr.reduce(function(x,y){
        return x+y;
    })
}
sum(arr);
console.log(sum(arr)); // 15

函數(shù)作為返回值

高階函數(shù)除了可以接受函數(shù)作為參數(shù)外芬首,還可以把函數(shù)作為結(jié)果值返回悦即。某些時(shí)候我們不想立刻求結(jié)果吮成,而是后面根據(jù)需要再求怎么辦?可以不返回求和的結(jié)果盐欺,而是返回求和的函數(shù)赁豆!

function sum(arr){
     return function(){
        return arr.reduce(function(x,y){
            return x+y;
        })
     }
}
sum(arr)(); //15
console.log(sum(arr)());

實(shí)際上,我們就是在原來(lái)立刻返回結(jié)果的語(yǔ)句的外層又套了一層匿名函數(shù)并作為返回值冗美,這樣sum返回的是這個(gè)匿名函數(shù)魔种,當(dāng)再次調(diào)用這個(gè)匿名函數(shù)的時(shí)候,才是返回剛才想立刻返回的結(jié)果粉洼。我們也可以把這個(gè)匿名函數(shù)賦給一個(gè)變量來(lái)實(shí)現(xiàn)

function sum(arr){
var result = function(){
return arr.reduce(function(x,y){
return x+y;
})
}
return result;
}
sum(arr)(); //15

## 閉包
在上面例子中节预,我們?cè)诤瘮?shù)`sum`中又定義了函數(shù)result,并且属韧,內(nèi)部函數(shù)result可以引用外部函數(shù)sum
的參數(shù)和局部變量安拟,當(dāng)sum返回函數(shù)result時(shí),相關(guān)參數(shù)和變量都保存在返回的函數(shù)中宵喂,這中程序結(jié)構(gòu)就稱為**閉包(Closure)**糠赦。

function count() {
var arr = [];
for (var i=1; i<=3; i++) {
arr.push(function () {
return i * i;
});
}
return arr;
}

var results = count();
var f1 = results[0];
var f2 = results[1];
var f3 = results[2];

f1(); // 16
f2(); // 16
f3(); // 16

**返回閉包時(shí)牢記的一點(diǎn)就是:返回函數(shù)不要引用任何循環(huán)變量,或者后續(xù)會(huì)發(fā)生變化的變量锅棕。**

如果一定要引用循環(huán)變量怎么辦拙泽?方法是**再套一層匿名函數(shù)并立即執(zhí)行,用該函數(shù)的參數(shù)綁定循環(huán)變量當(dāng)前的值裸燎,無(wú)論該循環(huán)變量后續(xù)如何更改顾瞻,已綁定到函數(shù)參數(shù)的值不變**:

//方法是再創(chuàng)建一個(gè)函數(shù),用該函數(shù)的參數(shù)綁定循環(huán)變量當(dāng)前的值德绿,
//無(wú)論該循環(huán)變量后續(xù)如何更改荷荤,已綁定到函數(shù)參數(shù)的值不變:
function count() {
var arr = [];
for (var i=1; i<=3; i++) {
arr.push((function (n) {
return function(){
return n*n
}
})(i));
}
return arr;
}

var results = count();
var f1 = results[0];
var f2 = results[1];
var f3 = results[2];

console.log(f1()) //1
console.log(f2()) //4
console.log(f3()) //9

這里外層套了一層匿名函數(shù),立即執(zhí)行則相當(dāng)于又把這層函數(shù)給抵消了移稳,那么push的仍然是像我們?cè)谠瓉?lái)那個(gè)函數(shù)中push的函數(shù)蕴纳,只不過(guò)這時(shí)push的那些函數(shù)的參數(shù)是我們創(chuàng)建的匿名函數(shù)帶來(lái)的參數(shù)了。
我們這個(gè)立即執(zhí)行的匿名函數(shù)就好像在原來(lái)的for循環(huán)和push的function之間加入了一層作用域个粱,這個(gè)作用域變成了function () { return i \\* i; }的爹古毛,使得里面的function () { return i * i; }的變量要先去找爹,再去找爺爺几蜻,爹中有參數(shù)喇潘,就不去找爺爺了?体斩。
再來(lái)個(gè)例子鞏固一下:
實(shí)現(xiàn)函數(shù) makeClosures,調(diào)用之后滿足如下條件:
1颖低、返回一個(gè)函數(shù)數(shù)組 result絮吵,長(zhǎng)度與 arr 相同
2、運(yùn)行 result 中第 i 個(gè)函數(shù)忱屑,即 `result[i]()`蹬敲,結(jié)果與 `fn(arr[i])` 相同 

var arr = [1, 2, 3, 4, 5];
var square = function (x) { return x * x; };
function makeClosures(arr, fn) {
var result = [];
for(var i = 0; i < arr.length; i++){

}
return result;

}
var funcs = makeClosures(arr, square);
console.log(funcs1); // answer: 4

首先我們肯定是要在for循環(huán)中添加內(nèi)容。嘗試1:

for(var i = 0; i < arr.length; i++){
result.push(fn(arr[i]));
}

這肯定是不對(duì)的莺戒,因?yàn)椴环项}目要求伴嗡,人家要返回函數(shù)啊,你這樣直接result中存儲(chǔ)的都是值了从铲,還怎么能調(diào)用呢瘪校?雖然值是變了的,那是因?yàn)閒n(arr[i])立即執(zhí)行了(fn已經(jīng)是寫(xiě)好的函數(shù)square了)名段。

那好吧阱扬,那我們外面加一層函數(shù),然后把這個(gè)值通過(guò)這個(gè)函數(shù)返回不就得了么伸辟。嘗試2:

for(var i = 0; i < arr.length; i++){
var tmp = arr[i]
result.push(function(){
return fn(tmp);
});
);
}
var funcs = makeClosures(arr, square);
// console.log(funcs)
console.log(funcs0); //25
console.log(funcs1); //25
console.log(funcs2); //25

這里就犯了和上面sum的例子的同樣的錯(cuò)誤麻惶,因?yàn)楫?dāng)我需要調(diào)用的時(shí)候,我的i循環(huán)變量已經(jīng)結(jié)束循環(huán)信夫,此時(shí)i=5, 所以輸出的都是25了窃蹋。
疑問(wèn):不寫(xiě)`var tmp = arr[i]`直接寫(xiě)成`return fn(arr[i])`為什么不行?静稻?警没??

解決方法同理姊扔,就是再套一層函數(shù)惠奸,綁定參數(shù)梅誓,然后立即執(zhí)行恰梢。這樣就把作用域給變了。嘗試3:
for(var i = 0; i < arr.length; i++){
     result.push((function(e){
        return function(){
            return fn(e);
        }
     })(arr[i]))
}

var funcs = makeClosures(arr, square);
// console.log(funcs)
console.log(funcs0);
console.log(funcs1);
console.log(funcs2);

這樣就搞定了梗掰。嵌言。。


## 關(guān)于作用域
Q1:全局變量會(huì)綁定到window上及穗,不同的JavaScript文件如果使用了相同的全局變量摧茴,或者定義了相同名字的頂層函數(shù),都會(huì)造成命名沖突埂陆,并且很難被發(fā)現(xiàn)苛白。這與模塊的區(qū)別娃豹??购裙?
## 關(guān)于參數(shù)的作用域懂版?
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市躏率,隨后出現(xiàn)的幾起案子躯畴,更是在濱河造成了極大的恐慌,老刑警劉巖薇芝,帶你破解...
    沈念sama閱讀 222,729評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蓬抄,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡夯到,警方通過(guò)查閱死者的電腦和手機(jī)嚷缭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,226評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)耍贾,“玉大人峭状,你說(shuō)我怎么就攤上這事”普” “怎么了优床?”我有些...
    開(kāi)封第一講書(shū)人閱讀 169,461評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)誓焦。 經(jīng)常有香客問(wèn)我胆敞,道長(zhǎng),這世上最難降的妖魔是什么杂伟? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 60,135評(píng)論 1 300
  • 正文 為了忘掉前任移层,我火速辦了婚禮,結(jié)果婚禮上赫粥,老公的妹妹穿的比我還像新娘观话。我一直安慰自己,他們只是感情好越平,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,130評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布频蛔。 她就那樣靜靜地躺著,像睡著了一般秦叛。 火紅的嫁衣襯著肌膚如雪晦溪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,736評(píng)論 1 312
  • 那天挣跋,我揣著相機(jī)與錄音三圆,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛舟肉,可吹牛的內(nèi)容都是我干的修噪。 我是一名探鬼主播,決...
    沈念sama閱讀 41,179評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼路媚,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼割按!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起磷籍,我...
    開(kāi)封第一講書(shū)人閱讀 40,124評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤适荣,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后院领,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體弛矛,經(jīng)...
    沈念sama閱讀 46,657評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,723評(píng)論 3 342
  • 正文 我和宋清朗相戀三年比然,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了丈氓。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,872評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡强法,死狀恐怖万俗,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情饮怯,我是刑警寧澤闰歪,帶...
    沈念sama閱讀 36,533評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站蓖墅,受9級(jí)特大地震影響库倘,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜论矾,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,213評(píng)論 3 336
  • 文/蒙蒙 一教翩、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧贪壳,春花似錦饱亿、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,700評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至传黄,卻和暖如春杰扫,著一層夾襖步出監(jiān)牢的瞬間队寇,已是汗流浹背膘掰。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,819評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人识埋。 一個(gè)月前我還...
    沈念sama閱讀 49,304評(píng)論 3 379
  • 正文 我出身青樓凡伊,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親窒舟。 傳聞我的和親對(duì)象是個(gè)殘疾皇子系忙,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,876評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容

  • 前言 總括 :這篇文章使用有效的javascript代碼向程序員們解釋了閉包,大牛和功能型程序員請(qǐng)自行忽略惠豺。 譯者...
    KX九五閱讀 279評(píng)論 0 1
  • 目錄 1.執(zhí)行環(huán)境與作用域鏈 2. 立即執(zhí)行函數(shù) 3. 閉包知識(shí)點(diǎn) 3.1 什么是閉包 3.2 使用閉包的意義與注...
    犯迷糊的小羊閱讀 639評(píng)論 0 11
  • 前言 這篇文章使用有效的javascript代碼向程序員們解釋了閉包银还,大牛和功能型程序員請(qǐng)自行忽略。 基礎(chǔ)篇 閉包...
    kiaizi閱讀 365評(píng)論 0 7
  • 前言 總括 :這篇文章使用有效的javascript代碼向程序員們解釋了閉包洁墙,大牛和功能型程序員請(qǐng)自行忽略蛹疯。 譯者...
    秦至閱讀 746評(píng)論 0 19
  • 畢業(yè),工作热监,結(jié)婚捺弦,生子,繼續(xù)工作孝扛。一路走來(lái)列吼,這個(gè)陌生的都市見(jiàn)證了我在這里的努力,彷徨苦始,歡笑和淚水寞钥。從那個(gè)長(zhǎng)時(shí)間漂在...
    張小棠的簡(jiǎn)書(shū)閱讀 342評(píng)論 0 1