摘要:此時(shí)閉包函數(shù)的作用域鏈得以保存,不會(huì)被垃圾回收機(jī)制所回收。執(zhí)行執(zhí)行完畢,返回總結(jié)閉包的原理,就是把閉包函數(shù)的作用域鏈保存了下來(lái)。
原文:搞懂閉包 | AlloyTeam
作者:TAT.yaoyao
閉包這個(gè)概念是前端工程師必須要深刻理解的,但是網(wǎng)上確實(shí)有一些文章會(huì)讓初學(xué)者覺(jué)得晦澀難懂,而且閉包的文章描述不一。
本文面向初級(jí)的程序員,聊一聊我對(duì)閉包的理解。當(dāng)然如果你看到閉包聯(lián)想不到作用域鏈與垃圾回收也不妨看一眼。希望讀了它之后你不再對(duì)閉包蒙圈。
先體驗(yàn)一下閉包這里有個(gè)需求,即寫一個(gè)計(jì)數(shù)器的函數(shù),每調(diào)用一次計(jì)數(shù)器返回值加一:
counter() // 1 counter() // 2 counter() // 3 ......
要想函數(shù)每次執(zhí)行的返回不一樣,怎么搞呢? 先簡(jiǎn)單的寫一下:
var index = 1; function counter() { return index ++; }
這樣做的確每次返回一個(gè)遞增的數(shù)。但是,它有以下三個(gè)問(wèn)題:
這個(gè)index放在全局,其他代碼可能會(huì)對(duì)他進(jìn)行修改
如果我需要同時(shí)用兩個(gè)計(jì)數(shù)器,但這種寫法只能滿足一個(gè)使用,另一個(gè)還想用的話就要再寫個(gè)counter2函數(shù),再定義一個(gè)index2的全局變量。
計(jì)數(shù)器是一個(gè)功能,我只希望我的代碼里有個(gè) counter函數(shù)就好,其他的最好不要出現(xiàn)。這是稍微有點(diǎn)代碼潔癖的都會(huì)覺(jué)得不爽的。
三個(gè)痛點(diǎn),讓閉包來(lái)一次性優(yōu)雅解決:
function counterCreator() { var index = 1; function counter() { return index ++; } return counter; } // test var counterA = counterCreator(); var counterB = counterCreator(); counterA(); // 1 counterA(); // 2 counterB(); // 1 counterB(); // 2
我的counterCreator函數(shù)只是把上面的幾行代碼包起來(lái),然后返回了里面的 counter 函數(shù)而已。卻能同時(shí)解決這么多問(wèn)題,這就是閉包的魅力! 6不6???
鋪墊知識(shí)鋪墊一些知識(shí)點(diǎn),不展開(kāi)講。
執(zhí)行上下文函數(shù)每次執(zhí)行,都會(huì)生成一個(gè)會(huì)創(chuàng)建一個(gè)稱為執(zhí)行上下文的內(nèi)部對(duì)象(AO對(duì)象,可理解為函數(shù)作用域),這個(gè)AO對(duì)象會(huì)保存這個(gè)函數(shù)中所有的變量值和該函數(shù)內(nèi)部定義的函數(shù)的引用。函數(shù)每次執(zhí)行時(shí)對(duì)應(yīng)的執(zhí)行上下文都是獨(dú)一無(wú)二的,正常情況下函數(shù)執(zhí)行完畢執(zhí)行上下文就會(huì)被銷毀。
作用域鏈在函數(shù)定義的時(shí)候,他還獲得[[scope]]。這個(gè)是里面包含該函數(shù)的作用域鏈,初始值為引用著上一層作用域鏈里面所有的作用域,后面執(zhí)行的時(shí)候還會(huì)將AO對(duì)象添加進(jìn)去 。作用域鏈就是執(zhí)行上下文對(duì)象的集合,這個(gè)集合是鏈條狀的。
function a () { // (1)創(chuàng)建 a函數(shù)的AO對(duì)象:{ x: undfind, b: function(){...} , 作用域鏈上層:window的AO對(duì)象} var x = 1; function b () { // (3)創(chuàng)建 b函數(shù)的AO對(duì)象:{ y: undfind , 作用域鏈上層:a函數(shù)AO對(duì)象} var y = 2; // (4)b函數(shù)的AO對(duì)象:{ y: 3 , 作用域鏈上層:a函數(shù)AO對(duì)象} console.log(x, y); // 在 b函數(shù)的AO對(duì)象中沒(méi)有找到x, 會(huì)到a函數(shù)AO對(duì)象中查找 } //(2)此時(shí) a函數(shù)的AO對(duì)象:{ x: 1, b: function(){...} , 作用域鏈上層:window的AO對(duì)象} b(); } a();
正常情況函數(shù)每次執(zhí)行后AO對(duì)象都被銷毀,且每次執(zhí)行時(shí)都是生成新的AO對(duì)象。我們得出這個(gè)結(jié)論: 只要是這個(gè)函數(shù)每次調(diào)用的結(jié)果不一樣,那么這個(gè)函數(shù)內(nèi)部一定是使用了函數(shù)外部的變量。
垃圾回收如何確定哪些內(nèi)存需要回收,哪些內(nèi)存不需要回收,這依賴于活對(duì)象這個(gè)概念。我們可以這樣假定:一個(gè)對(duì)象為活對(duì)象當(dāng)且僅當(dāng)它被一個(gè)根對(duì)象 或另一個(gè)活對(duì)象指向。根對(duì)象永遠(yuǎn)是活對(duì)象。
function a () { var x = 1; function b () { var y = 2; // b函數(shù)執(zhí)行完了,b函數(shù)AO被銷毀,y 被回收 } b(); //a 函數(shù)執(zhí)行完了,a函數(shù)AO被銷毀, x 和 b 都被回收 } a(); // 這里是在全局下,window中的 a 直到頁(yè)面關(guān)閉才被回收。分析閉包結(jié)構(gòu)
// 生成閉包的函數(shù) function counterCreator() { // 被返回函數(shù)所依賴的變量 var index = 1; // 被返回的函數(shù) function counter() { return index ++; } return counter; } // 被賦值為閉包函數(shù) var counterA = counterCreator(); // 使用 counterA();
閉包的創(chuàng)造函數(shù)必定包含兩部分:
一些閉包函數(shù)執(zhí)行時(shí)依賴的變量,每次執(zhí)行閉包函數(shù)時(shí)都能訪問(wèn)和修改
返回的函數(shù),這個(gè)函數(shù)中必定使用到上面所說(shuō)的那些變量
// 被賦值的閉包函數(shù) var counterA = counterCreator(); var counterB = counterCreator();
而上面這兩句代碼很重要,它其實(shí)是把閉包函數(shù)賦值給了一個(gè)變量,這個(gè)變量是一個(gè)活對(duì)象,這活對(duì)象引用了閉包函數(shù),閉包函數(shù)又引用了AO對(duì)象,所以這個(gè)時(shí)候AO對(duì)象也是一個(gè)活對(duì)象。此時(shí)閉包函數(shù)的作用域鏈得以保存,不會(huì)被垃圾回收機(jī)制所回收。
當(dāng)我們想重新創(chuàng)建一個(gè)新的計(jì)數(shù)器時(shí),只需要重新再調(diào)用一次 counterCreator, 他會(huì)新生成了一個(gè)新的執(zhí)行期上下文,所以counterB與counterA是互不干擾的。
counterCreator 執(zhí)行
counterCreator 執(zhí)行完畢,返回counter
閉包的原理,就是把閉包函數(shù)的作用域鏈保存了下來(lái)。
使用閉包帶你手寫一個(gè)簡(jiǎn)單的防抖函數(shù),趁熱打鐵。
第一步,先把閉包的架子搭起來(lái),因?yàn)槲覀円呀?jīng)分析了閉包生成函數(shù)內(nèi)部一定有的兩部分內(nèi)容。
function debunce(func, timeout) { // 閉包函數(shù)執(zhí)行時(shí)依賴的變量,每次執(zhí)行閉包函數(shù)時(shí)都能訪問(wèn)和修改 return function() { // 這個(gè)函數(shù)最終會(huì)被賦值給一個(gè)變量 } }
第二步: 把閉包第一次執(zhí)行的情況寫出來(lái)
function debunce(func, timeout) { timeout = timeout || 300; return function(...args) { var _this = this; setTimeout(function () { func.apply(_this, args); }, timeout); } }
第三步: 加上一些判斷條件。就像我們最開(kāi)始寫計(jì)數(shù)器的index一樣,不過(guò)這一次你不是把變量寫在全局下,而是寫在閉包生成器的內(nèi)部。
function debunce(func, timeout) { timeout = timeout || 300; var timer = null; // 被閉包函數(shù)使用 return function(...args) { var _this = this; clearTimeout(timer); // 做一些邏輯讓每次執(zhí)行效果可不一致 timer = setTimeout(function () { func.apply(_this, args); }, timeout); } } // 測(cè)試: function log(...args) { console.log("log: ", args); } var d_log = debunce(log, 1000); d_log(1); // 預(yù)期:不輸出 d_log(2); // 預(yù)期:1s后輸出 setTimeout( function () { d_log(3); // 預(yù)期:不輸出 d_log(4); // 預(yù)期:1s后輸出 }, 1500)閉包運(yùn)用
閉包用到的真的是太多了,再舉幾個(gè)例子再來(lái)鞏固一下:
模塊化例NodeJS模塊化原理:
NodeJS 會(huì)給每個(gè)文件包上這樣一層函數(shù),引入模塊使用require,導(dǎo)出使用exports,而那些文件中定義的變量也將留在這個(gè)閉包中,不會(huì)污染到其他地方。
(funciton(exports, require, module, __filename, __dirname) { /* 自己寫的代碼 */ })();高階函數(shù)
一些使用閉包的經(jīng)典例子:
節(jié)流函數(shù)
柯里化(Currying)
組合(Composing)
bind的實(shí)現(xiàn)
最后,如果你對(duì)閉包有更好的理解或者我文章里寫的不好的地方,還請(qǐng)指教。
AlloyTeam 歡迎優(yōu)秀的小伙伴加入。
簡(jiǎn)歷投遞: [email protected]
詳情可點(diǎn)擊 騰訊AlloyTeam招募Web前端工程師(社招)
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/105833.html
摘要:如果非要用一句話定義閉包我更加認(rèn)同你不知道的作者的一句話當(dāng)函數(shù)可以記住并訪問(wèn)所在的詞法作用域時(shí),就產(chǎn)生了閉包。所以本文將會(huì)從閉包的應(yīng)用場(chǎng)景入手,來(lái)印證的這句話。總結(jié)閉包的應(yīng)用場(chǎng)景還有很多,可以說(shuō)是隨處可見(jiàn)了。 直接進(jìn)入主題,閉包是什么? 閉包是寫代碼過(guò)程產(chǎn)生的一種自然結(jié)果,而不是一種概念。 相比于一些概念性的解釋,更重要的是熟悉它的應(yīng)用場(chǎng)景、及常見(jiàn)寫法。 如果非要用一句話定義閉包:我...
摘要:同步一次執(zhí)行一件事,同步引擎一次只執(zhí)行一行,是同步的。調(diào)用函數(shù)將其推入堆棧并從函數(shù)返回將其彈出堆棧。執(zhí)行上下文當(dāng)函數(shù)放入到調(diào)用堆棧時(shí)由創(chuàng)建的環(huán)境。執(zhí)行結(jié)果它會(huì)立即被推到回調(diào)隊(duì)列,但它仍然會(huì)等待調(diào)用堆棧為空才會(huì)執(zhí)行。 為了保證可讀性,本文采用意譯而非直譯。 想閱讀更多優(yōu)質(zhì)文章請(qǐng)猛戳GitHub博客,一年百來(lái)篇優(yōu)質(zhì)文章等著你! 一些名詞 JS引擎 — 一個(gè)讀取代碼并運(yùn)行的引擎,沒(méi)有單一的J...
摘要:曾幾何時(shí),閉包好像就是一個(gè)十分難以捉摸透的東西,看了很多文章,對(duì)閉包都各有說(shuō)法,以致讓我十分暈,什么內(nèi)部變量外部變量的,而且大多數(shù)都只描述一個(gè)過(guò)程,沒(méi)有給閉包的定義,最后,舉幾個(gè)例子,告訴你這就是閉包。 曾幾何時(shí),閉包好像就是一個(gè)十分難以捉摸透的東西,看了很多文章,對(duì)閉包都各有說(shuō)法,以致讓我十分暈,什么內(nèi)部變量、外部變量的,而且大多數(shù)都只描述一個(gè)過(guò)程,沒(méi)有給閉包的定義,最后,舉幾個(gè)例子...
摘要:雙向數(shù)據(jù)綁定的核心和基礎(chǔ)是其內(nèi)部真正參與數(shù)據(jù)雙向綁定流程的主要有和基于和發(fā)布者訂閱者模式,最終實(shí)現(xiàn)數(shù)據(jù)的雙向綁定。在這里把雙向數(shù)據(jù)綁定分為兩個(gè)流程收集依賴流程依賴收集會(huì)經(jīng)過(guò)以上流程,最終數(shù)組中存放列表,數(shù)組中存放列表。 Vue雙向數(shù)據(jù)綁定的核心和基礎(chǔ)api是Object.defineProperty,其內(nèi)部真正參與數(shù)據(jù)雙向綁定流程的主要有Obderver、Dep和Watcher,基于d...
摘要:引擎的運(yùn)行原理引擎也是程序,是屬于瀏覽器的一部分,由瀏覽器廠商自行開(kāi)發(fā)。為了提高運(yùn)行速度,現(xiàn)代瀏覽器一般采用即時(shí)編譯即字節(jié)碼只在運(yùn)行時(shí)編譯,用到哪一行就編譯哪一行,并且把編譯結(jié)果緩存這樣整個(gè)程序的運(yùn)行速度能得到顯著提升。 相信大家在面試的過(guò)程中經(jīng)常遇到查看執(zhí)行順序的問(wèn)題,如setTimeout,promise,async await等等,各種組合,是不是感覺(jué)頭都要暈掉了,其實(shí)這些問(wèn)題最...
閱讀 2973·2021-10-28 09:32
閱讀 3017·2021-10-11 10:57
閱讀 3183·2021-10-08 10:05
閱讀 2666·2021-09-28 09:36
閱讀 2258·2019-08-30 15:55
閱讀 2298·2019-08-30 15:44
閱讀 2423·2019-08-30 14:02
閱讀 3102·2019-08-29 17:16