閉包 美過一切

寫這篇文章時(shí)的心情是十分忐忑的雏门,因?yàn)閷?duì)于我們今天的主角:閉包,很多小伙伴都寫過關(guān)于它的文章掸掏,相信大家也讀過不少茁影,那些文章到底有沒有把JS中這個(gè)近似神話的東西講清楚,說實(shí)心里丧凤,真的有募闲,但為數(shù)不多。


寫這篇文章的初衷:讓所有看到這篇文章的小伙伴都徹徹底底的理解閉包=> 提高JS水平 => 能夠?qū)懗龈哔|(zhì)量的JS代碼愿待。


開文之所以說心情是忐忑的蝇更,就是怕達(dá)不到我寫該文的初衷,但是我有信心同時(shí)我也會(huì)努力的完成我的目標(biāo)呼盆。如行文中有絲毫誤人子弟的陳述年扩,歡迎大家指正,在這感激不盡访圃。


我們開始吧:


相信眾多JS的lovers都聽說過這句話:閉包很重要但是很難理解厨幻。


我起初也是這么覺得,但是當(dāng)我努力學(xué)習(xí)了JS的一些深層的原理以后我倒覺得閉包并不是那么不好理解腿时,反倒是讓我感覺出一種很美的感覺况脆。當(dāng)我徹底理解閉包的那一剎那,心中油然產(chǎn)生一種十分愉悅感覺批糟,就像**"酒酣尚醉格了,花未全開"**那種美景一樣。


撥開閉包神秘的面紗

[if !supportLineBreakNewLine]

[endif]

我們先看一個(gè)閉包的例子:


function foo() {

? ? let a = 2;


? ? function

bar() {


console.log( a );

? ? }


? ? return bar;

}


let baz = foo();


baz();

[if !supportLineBreakNewLine]

[endif]

大家肯定都寫過類似的代碼徽鼎,相信很多小伙伴也知道這段代碼應(yīng)用了閉包盛末,but,

Why does the closure be generated and Where is closure?


來弹惦,我們慢慢分析:


首先必須先知道閉包是什么,才能分析出閉包為什么產(chǎn)生和閉包到底在哪悄但?




寫這篇文章時(shí)的心情是十分忐忑的棠隐,因?yàn)閷?duì)于我們今天的主角:閉包,很多小伙伴都寫過關(guān)于它的文章檐嚣,相信大家也讀過不少助泽,那些文章到底有沒有把JS中這個(gè)近似神話的東西講清楚,說實(shí)心里嚎京,真的有嗡贺,但為數(shù)不多。


寫這篇文章的初衷:讓所有看到這篇文章的小伙伴都徹徹底底的理解閉包=> 提高JS水平 => 能夠?qū)懗龈哔|(zhì)量的JS代碼鞍帝。


開文之所以說心情是忐忑的诫睬,就是怕達(dá)不到我寫該文的初衷,但是我有信心同時(shí)我也會(huì)努力的完成我的目標(biāo)膜眠。如行文中有絲毫誤人子弟的陳述,歡迎大家指正溜嗜,在這感激不盡宵膨。


我們開始吧:


相信眾多JS的lovers都聽說過這句話:閉包很重要但是很難理解。


我起初也是這么覺得炸宵,但是當(dāng)我努力學(xué)習(xí)了JS的一些深層的原理以后我倒覺得閉包并不是那么不好理解辟躏,反倒是讓我感覺出一種很美的感覺。當(dāng)我徹底理解閉包的那一剎那土全,心中油然產(chǎn)生一種十分愉悅感覺捎琐,就像**"酒酣尚醉,花未全開"**那種美景一樣裹匙。


撥開閉包神秘的面紗

?

我們先看一個(gè)閉包的例子:

function foo() {

? ? let a = 2;


? ? function

bar() {


console.log( a );

? ? }


? ? return bar;

}


let baz = foo();


baz();


大家肯定都寫過類似的代碼瑞凑,相信很多小伙伴也知道這段代碼應(yīng)用了閉包,but, Why does the closure be generated and Where is closure?


來概页,我們慢慢分析:


首先必須先知道閉包是什么籽御,才能分析出閉包為什么產(chǎn)生和閉包到底在哪?


當(dāng)一個(gè)函數(shù)能夠記住并訪問到其所在的詞法作用域及作用域鏈惰匙,特別強(qiáng)調(diào)是在其定義的作用域外進(jìn)行的訪問技掏,此時(shí)該函數(shù)和其上層執(zhí)行上下文共同構(gòu)成閉包。


需要明確的幾點(diǎn):


[if !supportLists]1.????? [endif]閉包一定是函數(shù)對(duì)象(wintercn大大的閉包考證)

[if !supportLists]2.????? [endif]閉包和詞法作用域项鬼,作用域鏈哑梳,垃圾回收機(jī)制息息相關(guān)

[if !supportLists]3.????? [endif]當(dāng)函數(shù)一定是在其定義的作用域外進(jìn)行的訪問時(shí),才產(chǎn)生閉包

[if !supportLists]4.????? [endif]閉包是由該函數(shù)和其上層執(zhí)行上下文共同構(gòu)成(這點(diǎn)稍后我會(huì)說明)


閉包是什么绘盟,我們說清楚了鸠真,下面我們看下閉包是如何產(chǎn)生的悯仙。


接下來,我默認(rèn)你已經(jīng)讀過我之前的兩篇文章 原來JavaScript內(nèi)部是這樣運(yùn)行的 和 徹底搞懂JavaScript作用域 , 建議先進(jìn)行閱讀理解JS執(zhí)行機(jī)制和作用域等相關(guān)知識(shí)弧哎,再理解閉包雁比,否則可能會(huì)理解的不透徹。


現(xiàn)在我假設(shè)JS引擎執(zhí)行到這行代碼

let baz = foo();



此時(shí)撤嫩,JS的作用域氣泡是這樣的:


這個(gè)時(shí)候foo函數(shù)已經(jīng)執(zhí)行完偎捎,JS的垃圾回收機(jī)制應(yīng)該會(huì)自動(dòng)將其標(biāo)記為"離開環(huán)境",等待回收機(jī)制下次執(zhí)行,將其內(nèi)存進(jìn)行釋放(標(biāo)記清除)序攘。


但是茴她,我們仔細(xì)看圖中粉色的箭頭,我們將bar的引用指向baz程奠,正是這種引用賦值丈牢,阻止了垃圾回收機(jī)制將foo進(jìn)行回收,從而導(dǎo)致bar的整條作用域鏈都被保存下來瞄沙。


接下來己沛,baz()執(zhí)行,bar進(jìn)入執(zhí)行棧距境,閉包(foo)形成申尼,此時(shí)bar中依舊可以訪問到其父作用域氣泡中的變量a。


這樣說可能不是很清晰垫桂,接下來我們借助chrome的調(diào)試工具看下閉包產(chǎn)生的過程师幕。


當(dāng)JS引擎執(zhí)行到這行代碼let baz = foo();時(shí):


圖中所示,let baz = foo();已經(jīng)執(zhí)行完诬滩,即將執(zhí)行baz();霹粥,此時(shí)Call Stack中只有全局上下文。


接下來baz();執(zhí)行:


我們可以看到疼鸟,此時(shí)bar進(jìn)入Call Stack中后控,并且Closure(foo)形成。


針對(duì)上面我提到的幾點(diǎn)進(jìn)行下說明:


[if !supportLists]1.????? [endif]上述第二點(diǎn)(閉包和詞法作用域空镜,作用域鏈忆蚀,垃圾回收機(jī)制息息相關(guān))大家應(yīng)該都清楚了

[if !supportLists]2.????? [endif]上述第三點(diǎn),當(dāng)函數(shù)baz執(zhí)行時(shí)姑裂,閉包才生成

[if !supportLists]3.????? [endif]上述第四點(diǎn)馋袜,閉包是foo,并不是bar舶斧,很多書(《you dont know JavaScript》《JavaScript高級(jí)程序設(shè)計(jì)》)中欣鳖,都強(qiáng)調(diào)保存下來的引用,即上例中的bar是閉包茴厉,而chrome認(rèn)為被保存下來的封閉空間foo是閉包泽台,針對(duì)這點(diǎn)我贊同chrome的判斷(僅為自己的理解什荣,如有不同意見,歡迎來討論)


閉包的藝術(shù)性

[if !supportLineBreakNewLine]

[endif]

我相信這個(gè)世界上最美的事物往往就存在我們身邊怀酷,通常它并不是那么神秘稻爬,那么不可見,只是我們?nèi)鄙倭艘浑p發(fā)現(xiàn)美的眼睛蜕依。


生活中桅锄,我們抽出一段時(shí)間放慢腳步,細(xì)細(xì)品味我們所過的每一分每一秒样眠,會(huì)收獲到生活給我們的另一層樂趣友瘤。


閉包也一樣紫谷,它不是很神秘需纳,反而是在我們的程序中隨處可見,當(dāng)我們靜下心來妻率,品味閉包的味道被丧,發(fā)現(xiàn)它散發(fā)出一種藝術(shù)的美盟戏,樸實(shí)、精巧又不失優(yōu)雅甥桂。


細(xì)想柿究,在我們作用域氣泡模型中,作用域鏈讓我們的內(nèi)部bar氣泡能夠"看到"外面的世界格嘁,而閉包則讓我們的外部作用域能夠"關(guān)注到"內(nèi)部的情況成為可能笛求±纫疲可見糕簿,只要我們?cè)敢猓瑑?nèi)心世界和外面世界是可以相通的狡孔。


閉包的應(yīng)用的注意事項(xiàng)

[if !supportLineBreakNewLine]

[endif]

閉包懂诗,在JS中絕對(duì)是一個(gè)高貴的存在,它讓很多不可能實(shí)現(xiàn)的代碼成為可能苗膝,但是物雖好殃恒,也要合理使用,不然不但不能達(dá)到我們想要的效果辱揭,有的時(shí)候可能還會(huì)適得其反离唐。



內(nèi)存泄漏(Memory Leak)


JavaScript分配給Web瀏覽器的可用內(nèi)存數(shù)量通常比分配給桌面應(yīng)用程序的少,這樣做主要是防止JavaScript的網(wǎng)頁耗盡全部系統(tǒng)內(nèi)存而導(dǎo)致系統(tǒng)崩潰问窃。


因此亥鬓,要想使頁面具有更好的性能,就必須確保頁面占用最少的內(nèi)存資源域庇,也就是說嵌戈,我們應(yīng)該保證執(zhí)行代碼只保存有用的數(shù)據(jù)覆积,一旦數(shù)據(jù)不再有用,我們就應(yīng)該讓垃圾回收機(jī)制對(duì)其進(jìn)行回收熟呛,釋放內(nèi)存宽档。


我們現(xiàn)在都知道了閉包阻止了垃圾回收機(jī)制對(duì)變量進(jìn)行回收,因此變量會(huì)永遠(yuǎn)存在內(nèi)存中庵朝,即使當(dāng)變量不再被使用時(shí)吗冤,這樣會(huì)造成內(nèi)存泄漏,會(huì)嚴(yán)重影響頁面的性能偿短。因此當(dāng)變量對(duì)象不再適用時(shí)欣孤,我們要將其釋放。


我們拿上面代碼舉例:


?function foo() {

? ? ?let a =

2;



?function bar() {


?console.log( a );

? ? ?}


? ? ?return

bar;

?}


?let baz = foo();


?baz(); //baz指向的對(duì)象會(huì)永遠(yuǎn)存在堆內(nèi)存中


?baz = null; //如果baz不再使用昔逗,將其指向的對(duì)象釋放


閉包的應(yīng)用

[if !supportLineBreakNewLine]

[endif]

1.模塊


一個(gè)模塊應(yīng)該具有私有屬性降传、私有方法和公有屬性、公有方法勾怒。


而閉包能很好的將模塊的公有屬性婆排、方法暴露出來。



ar myModule = (function (window, undefined) {

??????????? let name = "echo";


??????????? function getName() {

??????????? ??????????? returnname;

??????????? }


??????????? return {

??????????? ??????????? name,

??????????? ??????????? getName

??????????? }

})(window);


console.log( myModule.name

); // echo

console.log(

myModule.getName() ); // echo



"return"關(guān)鍵字將對(duì)象引用導(dǎo)出賦值給myModule笔链,從而應(yīng)用到閉包段只。


2.延時(shí)器(setTimeout)、計(jì)數(shù)器(setInterval)


這里簡單寫一個(gè)常見的關(guān)于閉包的面試題鉴扫。

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

??????????? setTimeout(() => {

??????????? ??????????? console.log(i );

??????????? }, 1000 * i)

}

答案大家都知道:每秒鐘輸出一個(gè)5赞枕,一共輸出5次。


那么如何做到每秒鐘輸出一個(gè)數(shù)坪创,以此為0炕婶,1,2莱预,3柠掂,4呢?


我們這里只介紹閉包的解決方法依沮,其他類似塊作用域等等的解決方法涯贞,我們這里不討論。

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

??????????? ((j) => {

??????????? ??????????? setTimeout(()=> {

?????????????????????? ??????????? console.log(j );

??????????? ??????????? },1000 * j)

??????????? })(i)????

}

"setTimeout"方法里應(yīng)用了閉包危喉,使其內(nèi)部能夠記住每次循環(huán)所在的詞法作用域和作用域鏈宋渔。


由于setTimeout中的回調(diào)函數(shù)會(huì)在當(dāng)前任務(wù)隊(duì)列的尾部進(jìn)行執(zhí)行,因此上面第一個(gè)例子中每次循環(huán)中的setTimeout回調(diào)函數(shù)記住的i的值是for循環(huán)作用域中的值辜限,此時(shí)都是5皇拣,而第二個(gè)例子記住的i的數(shù)為setTimeout的父級(jí)作用域自執(zhí)行函數(shù)中的j的值,依次為0列粪,1审磁,2谈飒,3,4


3.監(jiān)聽器


var oDiv =

document.querySeletor("#div");


oDiv.addEventListener('click',

function() {

??????????? console.log( oDiv.id );

})

3.監(jiān)聽器


var oDiv =

document.querySeletor("#div");


oDiv.addEventListener('click',

function() {

??????????? console.log( oDiv.id );

})

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末态蒂,一起剝皮案震驚了整個(gè)濱河市杭措,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌钾恢,老刑警劉巖手素,帶你破解...
    沈念sama閱讀 218,386評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異瘩蚪,居然都是意外死亡泉懦,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門疹瘦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來崩哩,“玉大人,你說我怎么就攤上這事言沐〉肃冢” “怎么了?”我有些...
    開封第一講書人閱讀 164,704評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵险胰,是天一觀的道長汹押。 經(jīng)常有香客問我,道長起便,這世上最難降的妖魔是什么棚贾? 我笑而不...
    開封第一講書人閱讀 58,702評(píng)論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮榆综,結(jié)果婚禮上妙痹,老公的妹妹穿的比我還像新娘。我一直安慰自己奖年,他們只是感情好细诸,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,716評(píng)論 6 392
  • 文/花漫 我一把揭開白布沛贪。 她就那樣靜靜地躺著陋守,像睡著了一般。 火紅的嫁衣襯著肌膚如雪利赋。 梳的紋絲不亂的頭發(fā)上水评,一...
    開封第一講書人閱讀 51,573評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音媚送,去河邊找鬼中燥。 笑死,一個(gè)胖子當(dāng)著我的面吹牛塘偎,可吹牛的內(nèi)容都是我干的疗涉。 我是一名探鬼主播拿霉,決...
    沈念sama閱讀 40,314評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼咱扣!你這毒婦竟也來了绽淘?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,230評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤闹伪,失蹤者是張志新(化名)和其女友劉穎沪铭,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體偏瓤,經(jīng)...
    沈念sama閱讀 45,680評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡杀怠,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,873評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了厅克。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片赔退。...
    茶點(diǎn)故事閱讀 39,991評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖证舟,靈堂內(nèi)的尸體忽然破棺而出离钝,到底是詐尸還是另有隱情,我是刑警寧澤褪储,帶...
    沈念sama閱讀 35,706評(píng)論 5 346
  • 正文 年R本政府宣布卵渴,位于F島的核電站,受9級(jí)特大地震影響鲤竹,放射性物質(zhì)發(fā)生泄漏浪读。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,329評(píng)論 3 330
  • 文/蒙蒙 一辛藻、第九天 我趴在偏房一處隱蔽的房頂上張望碘橘。 院中可真熱鬧,春花似錦吱肌、人聲如沸痘拆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽纺蛆。三九已至,卻和暖如春规揪,著一層夾襖步出監(jiān)牢的瞬間桥氏,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評(píng)論 1 270
  • 我被黑心中介騙來泰國打工猛铅, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留字支,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,158評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像堕伪,于是被迫代替她去往敵國和親揖庄。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,941評(píng)論 2 355

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

  • 特別說明欠雌,為便于查閱抠艾,文章轉(zhuǎn)自https://github.com/getify/You-Dont-Know-JS...
    殺破狼real閱讀 485評(píng)論 0 0
  • BY 張建成(prettyEcho@github)除非另行注明检号,頁面上所有內(nèi)容采用知識(shí)共享-署名(CC BY 2....
    echo_me閱讀 308評(píng)論 0 0
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)蛙酪,斷路器齐苛,智...
    卡卡羅2017閱讀 134,657評(píng)論 18 139
  • 官方中文版原文鏈接 感謝社區(qū)中各位的大力支持,譯者再次奉上一點(diǎn)點(diǎn)福利:阿里云產(chǎn)品券桂塞,享受所有官網(wǎng)優(yōu)惠凹蜂,并抽取幸運(yùn)大...
    HetfieldJoe閱讀 5,605評(píng)論 16 88
  • 翻看以前的書,今天收拾東西的時(shí)候發(fā)現(xiàn)的阁危,感覺芒果可能不適合看這種小寶寶的書了玛痊,正在郁悶書太多都忘了看,打算今天晚上...
    愛跳舞的加菲慫閱讀 148評(píng)論 0 0