我也來說說啥是閉包关拒,本來很簡單的事佃蚜,別人怎么都講那么復(fù)雜

為啥js中有閉包這個東西,其他后臺語言里沒有着绊?

當(dāng)一個函數(shù)調(diào)用時谐算,內(nèi)部變量的查找會按作用域鏈條來查找,按理說不會有啥特殊情況出現(xiàn)归露,js之所以會出現(xiàn)閉包這個現(xiàn)象洲脂,原因就是你調(diào)用的這個函數(shù)是另外一個函數(shù)的返回值。

說到函數(shù)能作為返回值,這是跟js中函數(shù)類型是第一類對象這種語言設(shè)計方式有關(guān)恐锦,我會先介紹第一類對象對js的編碼風(fēng)格的影響往果。當(dāng)然你直接可以看第二部分,關(guān)于閉包的說明一铅。

第一部分

啥是第一類對象呢陕贮,我?guī)湍惆俣攘恕?/p>

第一類對象不一定是面向?qū)ο蟪绦蛟O(shè)計所指的物件,而可以指任何程序中的實體潘飘。一般第一類對象所特有的特性為:

可以被存入變量或其他結(jié)構(gòu)

可以被作為參數(shù)傳遞給其他函數(shù)

可以被作為函數(shù)的返回值

可以在執(zhí)行期創(chuàng)造肮之,而無需完全在設(shè)計期全部寫出

即使沒有被系結(jié)至某一名稱,也可以存在

在js中object類型就是第一類對象卜录。你能怎么使用object類型戈擒。就怎么能使用function類型。

這里先說一下js中函數(shù)的四大作用

1.可以調(diào)用執(zhí)行艰毒。程序世界里筐高,函數(shù)的基本作用就是可復(fù)用代碼段的封裝,可以直接調(diào)用丑瞧。

2.可以按照對象的使用方式來使用凯傲。原因就是js中函數(shù)類型是第一類對象。準確的來說js中函數(shù)本身就是對象嗦篱。對象能做啥,他當(dāng)然也能做幌缝。

3.可以提供作用域灸促。js中沒有塊級作用域的概念。函數(shù)是提供作用域的最小單位涵卵。

4.可以作為構(gòu)造函數(shù)浴栽。這一作用算是函數(shù)的特殊作用〗钨耍可以作用生成其他對象的模板典鸡。也就是可以通過函數(shù)來模擬類的實現(xiàn)。

在講閉包之前坏晦,先大致對函數(shù)的使用方式與對象和數(shù)組(數(shù)組本來就是對象萝玷,當(dāng)然函數(shù)也是)做個對比。

1.關(guān)于字面量

對象的字面量"{}"

[code]var a = new Object();

a.b = "xxx";

//等同于

var a = {};

a.b = "xxx";

//或者

var a = {

b : "xxx"

};

[/code]數(shù)組的字面量"[]"

[code]var a = new Array('a','b','c');

//等同于

var a = ['a','b','c'];[/code]

那么函數(shù)呢昆婿,我們平常聲明函數(shù)的方式球碉,可以理解成是一種字面量(我沒有說是)

[code]function a(x,y){

return x + y;

}

//相當(dāng)于如下對象的字面量

var a = new Function(x,y,"return x + y;");[/code]

注意:上面所有a 都是對象的引用

2.關(guān)于匿名函數(shù)

同樣也有匿名對象和匿名數(shù)組,我們先看看他們是怎么使用的

[code]var b = ({

a : "xxx"

}).a

alert(b) // "xxx"

for(var i = 0; i < [1,2,3].length; i++){

console.log(i);

}[/code]

同樣函數(shù)也有匿名的

[code]funcion(){

alert("11");

}

//因為函數(shù)的最基本功能是調(diào)用仓蛆,匿名函數(shù)也可以調(diào)用(我習(xí)慣稱呼為函數(shù)自執(zhí)行睁冬,一般書上都叫函數(shù)立即調(diào)用表達式)

(function(){alert(11)})();[/code]

3.可以存進變量或者其他結(jié)構(gòu)。

因為數(shù)組元素中可以存入數(shù)組看疙,當(dāng)然也可以存入函數(shù)豆拨。對象也是直奋,鍵值對的值可以存入任何東西,當(dāng)然也可以存入函數(shù)施禾,這時我們一般都用匿名函數(shù)脚线,例如

[code]var a = function(){};//這種聲明函數(shù)的方式也叫函數(shù)直接量。

var a = {

say : function(){//....}

};[/code]

4.可以做為參數(shù),傳入函數(shù)也就是平常我們說的回調(diào)函數(shù)拾积。

眾所周知函數(shù)有參數(shù)和返回值

對象和數(shù)組作為參數(shù)沒得說殉挽,寫下函數(shù)相關(guān)的例子

[code]var a = function(b){

b();

};

// 可以傳入匿名函數(shù),jquery中各種回調(diào)都是匿名的

a(function(){alert("123");});

//傳入有名字的函數(shù),跟c聲明的位置無關(guān)拓巧,這里涉及到變量提升的問題以及函數(shù)優(yōu)先初始化的問題斯碌。

a(c);

function c(){

alert("222")

} [/code]

廣義的講,當(dāng)然了肛度,回調(diào)函數(shù)傻唾,傳入?yún)?shù)不一定非得函數(shù)變量,但是一定要包含函數(shù)的結(jié)構(gòu)(例如數(shù)組承耿、object對象冠骄、自定義對象),如下

[code]var a = function(object)

object.say();

}

a({x :"2222",say :function(){alert("xxxx")}});[/code]

5.作為返回值

對象和數(shù)組作為函數(shù)的返回值沒得說加袋,寫下函數(shù)相關(guān)的例子

[code]function a(){

return function(){

alert("22222");

};

}

(a())();//alert "22222";[/code]

[attachimg][attachimg][attachimg]

第二部分

現(xiàn)在還是說說為啥出了個閉包這個東西凛辣,原因[color=Orange]就是你調(diào)用的那個函數(shù)是另一個函數(shù)的返回值,當(dāng)外部調(diào)用時,會沿著這個返回值的函數(shù)作用域鏈條來找其內(nèi)部相關(guān)變量的职烧。[/color]

先大致說下作用域鏈條的問題扁誓。

函數(shù)中識別變量,是一層層向外找的蚀之,首先在函數(shù)內(nèi)部找蝗敢,看看是不是內(nèi)部聲明的,然后再到上一層找足删,沒找到寿谴,再往上,直到全局作用域失受。如果全局頁面都沒聲明讶泰,那瀏覽器就報錯了。這一層層中的層是什么東西呢拂到,就是函數(shù)峻厚,因為函數(shù)提供最小的作用域∽缓福看個例子

[code]var a = 3;

var b = 4;

function outer(){

var a = 5;

var c = 7;

var d = 8;

console.log(a);//5惠桃,outer內(nèi)部的

console.log(b);//4,全局的

var inner = function(){

var c = 6;

console.log(b);//4,全局的

console.log(c);//6辜王,inner內(nèi)部的

console.log(d);//8劈狐,outer內(nèi)部的

//console.log(e); //報錯,沒找到

b = 0 //找到全局的

d = "xxx";

}

inner();

console.log(b);//0呐馆,找全局的b

console.log(d);//"xxx", outer內(nèi)部的

}

outer();[/code]

作用域鏈條我們明白了肥缔,然后咱再來看看閉包的情形

[code]//代碼1

function a(){

var x = 0;

return function(){

x++;//此函數(shù)的作用域鏈能看到x

console.log(x);

}

}

var fun = a();//a返回的是個函數(shù)汹来,保存起來沒問題续膳。

fun()//打印1

fun()//打印2[/code]

為啥打印2而不是1呢,原因是

[color=Orange]因為a中返回個函數(shù)收班,我們要調(diào)用這個函數(shù)坟岔,瀏覽器一看,你要運行的是函數(shù)摔桦,函數(shù)是有作用域鏈條的社付,哦,x我能找到邻耕,保證不報錯的鸥咖。里面的x當(dāng)然也能自增加了[/color]

說的直白點就像如下代碼一樣

[code]//代碼2

var x = 0;

var fun = function(){

x++;

console.log(x);

}

fun();//打印1

fun();//打印2[/code]

[color=Orange]補充:[/color]經(jīng)網(wǎng)友提醒,閉包有占用內(nèi)存的問題兄世,這里說下啼辣,因為代碼1中fun是一個函數(shù)的引用,瀏覽器對應(yīng)的會對其作用域鏈條中的變量x做了保存御滩,因而會占用內(nèi)存熙兔。達到的效果就跟代碼2中的x一樣。

要釋放其內(nèi)存可以把其引用置空艾恼,使a返回的那個匿名函數(shù)無引用指向它,自然垃圾回收器會回收的麸锉。代碼如下

[code]//代碼3

function a(){

var x = 0;

return function(){

x++钠绍;//此函數(shù)的作用域鏈能看到x

console.log(x);

}

}

var fun = a();//a返回的是個函數(shù),保存起來沒問題花沉。

fun()//打印1

fun()//打印2

//以后不再使用了柳爽,注意要釋放內(nèi)存

fun = null;

[/code]

注意:

[color=Orange]如果我換種調(diào)用方法呢

(a())();//打印1

(a())();//打印1

誒,為啥第二次不打印2了呢碱屁。原因很簡單磷脯,因為兩次調(diào)用返回的不是同一個函數(shù)引用,因此是兩條作用域鏈條娩脾。[/color]

說的直白點就像如下的代碼

[code]var x1 = 0;

(function(){

x1++;

console.log(x1);

})();//打印1

var x2 = 0;

(function(){

x2++;

console.log(x2);

})();//打印1[/code]

這種使用方式赵誓,就不會有出現(xiàn)閉包常駐內(nèi)存的情況,因為每次使用都匿名的,當(dāng)然了俩功,也失去了閉包的意義幻枉。

大體閉包這種現(xiàn)象我是解釋明白了。我沒有給閉包下明確的定義诡蜓,不同的書有不同的說法熬甫。

有的說,返回的那個函數(shù)是閉包蔓罚,有的說返回的函數(shù)提供的作用域鏈條是閉包椿肩。有的甚至把其得到效果說是閉包,

大體是這么說的豺谈,通過這種方式郑象,能訪問某個部函數(shù)內(nèi)部的私有變量,這種方式稱為閉包核无。

不管怎么說都是跟函數(shù)的作用域鏈條相關(guān)的扣唱。更有甚者也有說所有函數(shù)都是閉包。

我個人覺得會出現(xiàn)閉包這個東西团南,主要原因就是跟js中函數(shù)是第一類對象有關(guān)噪沙,因為你調(diào)用的一個函數(shù)可能不是直接聲明的,而是其他函數(shù)直接return的函數(shù)或者return某種結(jié)構(gòu)中的一個函數(shù)吐根。

關(guān)于是返回某種結(jié)構(gòu)的中函數(shù)正歼,舉例如下

[code]function a(){

var x = 0;

var y = {name :"張三"};

var f1 = function(){

x ++;

}

var f2 = function(name){

y.name = name;

}

return [f1,f2];

}

var b= a()

b[0]();

b0;[/code]

再寫個

[code]function a(){

var name = null;

var f1 = function(n){

name = n;

};

var f2 = function(){

return name;

};

return {

setName : f1,

getName : f2

}

}

var o =a();

o.setName("老姚");

var myName = o.getName();

console.log(myName);[/code]

如果在講上述例子改寫新的形式,把函數(shù)改成匿名的(有的人甚至覺得匿名函數(shù)是閉包拷橘,那樣我會說局义,看來所有函數(shù)都是閉包了),就是一種設(shè)計模式:模塊模式冗疮。

[code]var person = (function(){

var name = null;

var f1 = function(n){

name = n;

};

var f2 = function(){

return name;

};

return {

setName : f1,

getName : f2

}

}

)();

person.setName("老姚");

console.log(person.getName());[/code]

由此可以看出來應(yīng)用閉包不只是簡單的寫個計數(shù)器啥的萄唇。

第三部分

?下面也是一種閉包的應(yīng)用。

原先是閉包术幔,改后也也是閉包另萤。達到的效果是,添加一層隔絕诅挑,來滿足我們需要的結(jié)果四敞。

有時我們本意不想用閉包的,

如下拔妥,我想彈出0忿危,1,2的没龙,結(jié)果都會彈出3.

[code]var fun = function(){

var a = [];

for(var i = 0;i<3;i++){

a.push(function(){

return i;

})

}

//console.log(i);//因為js中沒有塊級作用域铺厨,i最后變成3,而不是報錯

return a;

}

var a = fun();

alert(a[0]())缎玫;//3

alert(a[1]());//3

alert(a[2]())努释;//3[/code]

可以改成

[code]var fun = function(){

var a = [];

for(var i = 0;i<3;i++){

a.push(function(j){

return function(){

return j;

};

}(i))

}

return a;

}

var a = fun();

alert(a[0]())//0

alert(a[1]())//1

alert(a[2]())//2[/code]

最開始的那個例子也可以避免閉包碘梢,改成

[code]function a(){

var x = 0;

return function(){

(function(y){//y寫x也沒問題,變量首先會在此作用找的

y++;

console.log(y);

})(x)

};

}

var fun = a();

fun();//輸出1

fun();//輸出1[/code]

[color=Red]注意:經(jīng)網(wǎng)友指正伐蒂,原文中的代碼如下煞躬,是有問題的,原因就是第二個return逸邦,又創(chuàng)建了閉包恩沛。代碼敲順手了,還得細心驗證才行缕减。[/color]

此處要改成雷客,“方式二”

[code]function a(){

var x = 0;

return function(x){

return function(){

x++;

console.log(x);

}

}(x);

}[/code]

還有一種情況也會出現(xiàn)閉包現(xiàn)象桥狡,把內(nèi)部函數(shù)綁定了dom節(jié)點某種操作(onclick)的回調(diào)函數(shù)搅裙,沒有寫在return語句里。道理是一樣的裹芝。寫在return里部逮,是return后調(diào)用,綁定到dom上嫂易,比如說觸發(fā)點擊事件后再調(diào)用兄朋,其道理是一樣的,作用域鏈條該怎么找就怎么找怜械。

閉包最起碼的應(yīng)用颅和,就是我們可以把一些全局變量封裝起來,通過這種方式來不污染全局缕允,例如上面的模塊模式例子峡扩。

最后:

“相信有很多想學(xué)前端的小伙伴,今年年初我花了一個月整理了一份最適合2018年學(xué)習(xí)的web前端干貨障本,從最基礎(chǔ)的HTML+CSS+JS到移動端HTML5等都有整理教届,送給每一位前端小伙伴,53763彼绷,1707這里是小白聚集地,歡迎初學(xué)和進階中的小伙伴茴迁〖拿酰”

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市堕义,隨后出現(xiàn)的幾起案子猜旬,更是在濱河造成了極大的恐慌脆栋,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件洒擦,死亡現(xiàn)場離奇詭異椿争,居然都是意外死亡,警方通過查閱死者的電腦和手機熟嫩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進店門秦踪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人掸茅,你說我怎么就攤上這事椅邓。” “怎么了昧狮?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵景馁,是天一觀的道長。 經(jīng)常有香客問我逗鸣,道長合住,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任撒璧,我火速辦了婚禮透葛,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘沪悲。我一直安慰自己获洲,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布殿如。 她就那樣靜靜地躺著贡珊,像睡著了一般。 火紅的嫁衣襯著肌膚如雪涉馁。 梳的紋絲不亂的頭發(fā)上门岔,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天,我揣著相機與錄音烤送,去河邊找鬼寒随。 笑死,一個胖子當(dāng)著我的面吹牛帮坚,可吹牛的內(nèi)容都是我干的妻往。 我是一名探鬼主播,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼试和,長吁一口氣:“原來是場噩夢啊……” “哼讯泣!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起阅悍,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤好渠,失蹤者是張志新(化名)和其女友劉穎昨稼,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體拳锚,經(jīng)...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡假栓,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了霍掺。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片匾荆。...
    茶點故事閱讀 40,115評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖抗楔,靈堂內(nèi)的尸體忽然破棺而出棋凳,到底是詐尸還是另有隱情,我是刑警寧澤连躏,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布剩岳,位于F島的核電站,受9級特大地震影響入热,放射性物質(zhì)發(fā)生泄漏拍棕。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一勺良、第九天 我趴在偏房一處隱蔽的房頂上張望绰播。 院中可真熱鬧,春花似錦尚困、人聲如沸蠢箩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽谬泌。三九已至,卻和暖如春逻谦,著一層夾襖步出監(jiān)牢的瞬間掌实,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工邦马, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留贱鼻,地道東北人。 一個月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓滋将,卻偏偏與公主長得像邻悬,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子随闽,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,055評論 2 355

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

  • 第2章 基本語法 2.1 概述 基本句法和變量 語句 JavaScript程序的執(zhí)行單位為行(line)父丰,也就是一...
    悟名先生閱讀 4,149評論 0 13
  • "use strict";function _classCallCheck(e,t){if(!(e instanc...
    久些閱讀 2,031評論 0 2
  • Lua 5.1 參考手冊 by Roberto Ierusalimschy, Luiz Henrique de F...
    蘇黎九歌閱讀 13,810評論 0 38
  • --- 學(xué)習(xí)目標: - 掌握編程的基本思維 - 掌握編程的基本語法 typora-copy-images-to: ...
    YFBigHeart閱讀 1,053評論 0 2
  • 不管你是成功達人亦或是名利皆無础米,幸福和煩惱總是不分等級悄然而至,你的開心的不開心的所有不愿意跟其他人分享的都可以找...
    bailu89閱讀 254評論 0 0