摘要:也正因為這個閉包的特性,閉包函數(shù)可以讓父函數(shù)的數(shù)據(jù)一直駐留在內(nèi)存中保存,從而這也是后來模塊化的基礎(chǔ)。只有閉包函數(shù),可以讓它的父函數(shù)作用域永恒,像全局作用域,一直在內(nèi)存中存在。的本質(zhì)就是如此,每個模塊文件就是一個大閉包。
為什么會有閉包
js之所以會有閉包,是因為js不同于其他規(guī)范的語言,js允許一個函數(shù)中再嵌套子函數(shù),正是因為這種允許函數(shù)嵌套,導(dǎo)致js出現(xiàn)了所謂閉包。
function a(){ function b(){ }; b(); } a();
在js正常的函數(shù)嵌套中,父函數(shù)a調(diào)用時,嵌套的子函數(shù)b的結(jié)構(gòu),在內(nèi)存中產(chǎn)生,然后子函數(shù)又接著調(diào)用了,子函數(shù)b就注銷了,此時父函數(shù)a也就執(zhí)行到尾,父函數(shù)a也會把自己函數(shù)體內(nèi)調(diào)用時生成的數(shù)據(jù)從內(nèi)存都注銷。
function a(){ function b(){ } return b; } var f=a();
這個例子中,父函數(shù)調(diào)用時,函數(shù)體內(nèi)創(chuàng)建了子函數(shù)b,但是子函數(shù)并沒有立即調(diào)用,而是返回了函數(shù)指針,以備“日后再調(diào)用”,因為“準(zhǔn)備日后調(diào)用”,此時父函數(shù)a執(zhí)行完了,就不敢注銷自己的作用域中的數(shù)據(jù)了,因為一旦注銷了,子函數(shù)b日后再調(diào)用時,沿著函數(shù)作用域鏈往上訪問數(shù)據(jù),就沒有數(shù)據(jù)可以訪問了,這就違背了js函數(shù)作用域鏈的機(jī)制。
正因此,子函數(shù)要“日后調(diào)用”,導(dǎo)致父函數(shù)要維持函數(shù)作用域鏈,而不敢注銷自己的作用域,那么這個子函數(shù)就是“閉包函數(shù)”。
閉包函數(shù)在形式上有很多種。
在這個例子中,父函數(shù)v()體內(nèi)定義了好幾種子函數(shù),這些子函數(shù)有的是異步事件的回調(diào)函數(shù),會進(jìn)入瀏覽器的事件循環(huán)池,等主線程工作結(jié)束后日后再調(diào)用這些回調(diào)函數(shù),這些子函數(shù),都導(dǎo)致父函數(shù)調(diào)用完了,不敢注銷自己的作用域,因此這些子函數(shù)都是閉包函數(shù)。
js并不是為了創(chuàng)造閉包而創(chuàng)造,完全只是因為js允許函數(shù)嵌套,js函數(shù)嵌套還有個函數(shù)作用域鏈的機(jī)制,讓父函數(shù)不敢注銷自己作用域中的數(shù)據(jù),才會產(chǎn)生所謂閉包。
也正因為這個閉包的特性,閉包函數(shù)可以讓父函數(shù)的數(shù)據(jù)一直駐留在內(nèi)存中保存,從而這也是后來js模塊化的基礎(chǔ)。
閉包與函數(shù)作用域如果僅僅只是有函數(shù)嵌套,而沒有函數(shù)作用域鏈,也或許不會有閉包。理解js函數(shù)作用域至關(guān)重要。
function a(){ }
函數(shù)的作用域?qū)嶋H上是個動態(tài)概念,上面的代碼,只是定義了一個函數(shù),并沒有調(diào)用函數(shù),函數(shù)的作用域是不存在的。只有函數(shù)a調(diào)用時,才會在內(nèi)存中動態(tài)開辟一個自己的作用域,函數(shù)調(diào)用完了這個作用域又關(guān)閉了,函數(shù)運(yùn)行過程中在內(nèi)存創(chuàng)建的數(shù)據(jù)又被清除了。
function a(){ var n=1; function b(){ n++; console.log(n); } b(); b(); b(); } a();
這個例子中,父函數(shù)a調(diào)用,首先在內(nèi)存中動態(tài)開辟了作用域,然后在運(yùn)算過程中,定義了函數(shù)b,子函數(shù)b()每次調(diào)用,都會開辟自己的作用域,在自己的作用域內(nèi)進(jìn)行運(yùn)算,運(yùn)算過程中訪問了還處于打開狀態(tài)的父函數(shù)作用域中的變量n的值,這個子函數(shù)三次調(diào)用,每次調(diào)用時候自己子作用域,訪問的都是同一個變量n。但是父函數(shù)a總有執(zhí)行完的時刻,總有要關(guān)閉作用域的時候。
var q=""; function a(){ var n=1; q=function b(){ n++; console.log(n); } } a(); q(); q(); q();
這個例子中,運(yùn)行父函數(shù),函數(shù)開啟了作用域,運(yùn)算過程中生成了函數(shù)b,子函數(shù)b賦給了全局變量q,導(dǎo)致父函數(shù)a運(yùn)行完了,不敢關(guān)閉自己的作用域,,讓子函數(shù)b成了閉包函數(shù),全局變量q持有了這個閉包函數(shù)。
這個得到的結(jié)果,和上面例子中常規(guī)函數(shù)嵌套,得到的效果是一樣的。但是區(qū)別在于,前一個例子中,父函數(shù)a即便執(zhí)行萬年,也有結(jié)束要關(guān)閉作用域的時候,而這個閉包,就讓它的父函數(shù)作用域永恒了。
實際上在js的作用域機(jī)制中,有一個作用域是永恒的,就是window全局作用域,只要瀏覽器窗口不關(guān)閉,這個windows全局作用域就是永恒的,在全局作用域中定一個函數(shù),無論調(diào)用幾次,這幾次調(diào)用都可以共享操作同一個全局變量。除了window作用域可以永恒,其他的函數(shù)作用域,總有關(guān)閉的時候而無法永恒。只有閉包函數(shù),可以讓它的父函數(shù)作用域永恒,像windows全局作用域,一直在內(nèi)存中存在。
當(dāng)閉包函數(shù)調(diào)用時,它會動態(tài)開辟出自己的作用域,在它之上的是父函數(shù)的永恒作用域,在父函數(shù)作用域之上的,是window永恒的全局作用域。閉包函數(shù)調(diào)用完了,它自己的作用域關(guān)閉了,從內(nèi)存中消失了,但是父函數(shù)的永恒作用域和window永恒作用域還一直在內(nèi)存是打開的。閉包函數(shù)再次調(diào)用時,還能訪問這兩個作用域,可能還保存了它上次調(diào)用時候產(chǎn)生的數(shù)據(jù)。只有當(dāng)閉包函數(shù)的引用被釋放了,它的父作用域才會最終關(guān)閉(當(dāng)然父函數(shù)可能創(chuàng)建了多個閉包函數(shù),就需要多個閉包函數(shù)全部釋放后,父函數(shù)作用域才會關(guān)閉)。
這個例子是閉包函數(shù)的一個典型應(yīng)用,示例中只有兩個函數(shù)嵌套,但是加上window全局作用于,一共會有三個嵌套作用域。其中for循環(huán)了三次,三次調(diào)用了匿名自執(zhí)行函數(shù),就開了三個函數(shù)作用域,開第一個作用域時保存的i的值是0,開第二個作用域保存的是1,第三個保存的是2。三次調(diào)用父函數(shù),又創(chuàng)建了三個閉包函數(shù),每個閉包函數(shù)沿著它自己的作用域鏈向上訪問,訪問的值就都不相同。三個閉包函數(shù)調(diào)用完了,它們自己的作用域就關(guān)閉了,但是各自的父函數(shù)作用域還一直在內(nèi)存中處于打開狀態(tài),下次閉包函數(shù)再調(diào)用時,再接著訪問它自己的作用域。就像在window全局作用域定義了一個函數(shù),函數(shù)調(diào)用幾次,全局作用域都在,每次調(diào)用都接著訪問全局作用域。
閉包與js模塊化日常編碼中有很多地方會不經(jīng)意用到了閉包只是沒有察覺,使用閉包的作用就是為了兩點:形成命名空間同時保存數(shù)據(jù)。
在HTML中引入多個js文件,瀏覽器會從第一個執(zhí)行到最后一個,這些js文件都共用一個全局作用域,這很多時候就會導(dǎo)致命名沖突。而如果只是為了命名空間,匿名自執(zhí)行函數(shù)也可以實現(xiàn)。
(function(){ var a=1; })() alert(a);//訪問不到變量a的值,會報錯變量a未定義
這個例子中就借助匿名自執(zhí)行函數(shù)實現(xiàn)了命名空間,隔離了數(shù)據(jù),不會產(chǎn)生沖突,但是僅僅只是把數(shù)據(jù)封起來不提供接口有些時候或許也不行,因此這就需要閉包。
這個例子在前一個例子基礎(chǔ)上進(jìn)行了改造,a.js文件中就使用了閉包,無論這個文件引入到哪里,它的數(shù)據(jù)都是隔離的,不會會任何地方的代碼產(chǎn)生沖突,同時它提供了閉包函數(shù)作為API接口,讓其他地方以指定的方式訪問數(shù)據(jù),得到需要的結(jié)果,其他地方也不需要關(guān)心閉包結(jié)構(gòu)里的數(shù)據(jù)是什么或者怎么操作的,也不需要擔(dān)心引入它會與自己的代碼沖突。
require.js的本質(zhì)就是如此,每個模塊文件就是一個大閉包。
是否使用閉包要考慮兩點:隔離和數(shù)據(jù)保存。如果需要隔離數(shù)據(jù)形成命名空間,可以使用匿名自執(zhí)行函數(shù)。如果需要隔離數(shù)據(jù),同時還需要在隔離狀態(tài)保存數(shù)據(jù),保存了后面還可以繼續(xù)使用,那就可以使用閉包。如果都不需要,那就使用普通函數(shù),函數(shù)調(diào)用完作用域就關(guān)閉數(shù)據(jù)就釋放了,沒有保存,數(shù)據(jù)不存在了也不需要隔離了。
一個實際應(yīng)用淘寶的購物車中,一個商品點擊新增數(shù)量或減少數(shù)量,它會往服務(wù)器發(fā)送一個請求保存新數(shù)量,但是如果快速連續(xù)點擊,淘寶的購物車并沒有跟隨快速點擊連續(xù)發(fā)送ajax,而是在連續(xù)點擊的結(jié)束之后才發(fā)送了一個請求,把用戶真正想要的數(shù)量最后才用一個請求發(fā)送了服務(wù)器,這樣就減少了不必要的請求減少服務(wù)器的壓力。
如果只是單純用個click事件處理函數(shù),然后把a(bǔ)jax放到處理函數(shù)中,點一次按鈕就會發(fā)一次請求,連續(xù)點就會連續(xù)發(fā)。而要實現(xiàn)淘寶的這個效果,它要的原理是,定一個延時時間,比方1秒,單擊之后過1秒種才發(fā)請求,而如果單擊了之后還沒有到1秒又連續(xù)單擊了,那么重置這個計時,快速連續(xù)單擊就一直再重置這個計時始終都沒有達(dá)到一秒,就不會因為連續(xù)點擊而發(fā)送請求,直到最后連續(xù)點擊停下來了,過了一秒才發(fā)一個請求。
這個應(yīng)用中就借助了閉包函數(shù),實際click事件真正執(zhí)行的用于發(fā)送請求的也就是里面嵌套的紅框的閉包函數(shù),每一次單擊都會執(zhí)行這個紅框函數(shù),它除了最終發(fā)送ajax,還要做個判斷,如果上一次點擊的時間,到這一次又點擊的時間,這之間的間隔小于了指定的1秒,那么就不會發(fā)送ajax,同時重置這個計時。而在最初第一次單擊的時候,它還需要上一次的時間,這個時間就只能在初始化時候用一個變量保存一個當(dāng)前時間,然后第一次單擊時候的時間與變量保存的時間進(jìn)行一個對比。單擊第二次時,那么該變量又保存了第一次單擊時的時間,然后第二次單擊的時間又與第一次單擊的時間進(jìn)行比較。
關(guān)鍵也就在于需要個變量保存上一次的時間。這時間不借助閉包函數(shù)也完全可以,就把這個變量放在全局環(huán)境下,在全局環(huán)境下定義一個全局變量startTime,反正就是保存一下上一次單擊的時間。但是問題在于,購物車中有多個商品,并不會有只有一個單擊按鈕需要用到這個,多個按鈕要用,給每個按鈕都定義全局變量,startOne,startTwo,startThree...那就很麻煩,并且通過json渲染多個商品時候也不可能手動去定義這么多變量。這就必需借助閉包函數(shù)。
json在渲染多個商品時按鈕時,這個debounce函數(shù)就會被多次調(diào)用,每一次調(diào)用都return返回了一個閉包函數(shù)給每個商品的button按鈕的click作為其處理函數(shù),那么每個處理函數(shù)都有一個專屬的永恒父作用域,并且里面都已經(jīng)自動定義了各自需要使用的startTime變量用于保存每個按鈕自己計算時使用的上一次單擊的時間。通過閉包解決這個問題這就非常方便。
額...上面一個通過for()循環(huán)創(chuàng)建多個閉包函數(shù),內(nèi)存開多個作用域來保存不同的數(shù)據(jù),不一定是最好的實現(xiàn)。這個例子中,同樣是for循環(huán)創(chuàng)建三個了函數(shù),但三個函數(shù)都是普通函數(shù)。由于函數(shù)在js中也是對象,因此給函數(shù)本身創(chuàng)建一個靜態(tài)屬性來保存不同的值,那么for循環(huán)創(chuàng)建的三個普通函數(shù),每個函數(shù)的靜態(tài)屬性都保存了不同的值,而不必借助閉包結(jié)構(gòu)保存不同的值,可以減少內(nèi)存消耗。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/88140.html
摘要:函數(shù)使用函數(shù)的使用主要有兩種閉包閉包的本質(zhì)是對共享變量的操作,典型運(yùn)用是觀察者模式備忘錄模式普通封裝,復(fù)用。參考阿里博客你可能不知道的事基礎(chǔ)篇總結(jié)要寫好一個項目需要兼容,性能,安全等。 一次阿里面試后對函數(shù)本質(zhì)的理解 寫在前面 環(huán)境:阿里的在線編程系統(tǒng)允許面試官在線考察面試者的編程能力. 考點:編程和理論. 編程:分為技術(shù)自驅(qū)力、異步操作、編程風(fēng)格(顆粒小)、變量作用域、DOM操作...
摘要:函數(shù)使用函數(shù)的使用主要有兩種閉包閉包的本質(zhì)是對共享變量的操作,典型運(yùn)用是觀察者模式備忘錄模式普通封裝,復(fù)用。參考阿里博客你可能不知道的事基礎(chǔ)篇總結(jié)要寫好一個項目需要兼容,性能,安全等。 一次阿里面試后對函數(shù)本質(zhì)的理解 寫在前面 環(huán)境:阿里的在線編程系統(tǒng)允許面試官在線考察面試者的編程能力. 考點:編程和理論. 編程:分為技術(shù)自驅(qū)力、異步操作、編程風(fēng)格(顆粒小)、變量作用域、DOM操作...
摘要:函數(shù)使用函數(shù)的使用主要有兩種閉包閉包的本質(zhì)是對共享變量的操作,典型運(yùn)用是觀察者模式備忘錄模式普通封裝,復(fù)用。參考阿里博客你可能不知道的事基礎(chǔ)篇總結(jié)要寫好一個項目需要兼容,性能,安全等。 一次阿里面試后對函數(shù)本質(zhì)的理解 寫在前面 環(huán)境:阿里的在線編程系統(tǒng)允許面試官在線考察面試者的編程能力. 考點:編程和理論. 編程:分為技術(shù)自驅(qū)力、異步操作、編程風(fēng)格(顆粒小)、變量作用域、DOM操作...
摘要:首先變量對于一個程序來說是一個很重要的角色那么問題來了這些變量存在哪里程序用到的時候如何找到變量呢所以需要一套規(guī)則來存儲變量方便之后再找到這套規(guī)則就成為作用域是一門編譯語言對于來說大部分情況下編譯發(fā)生在代碼執(zhí)行前的幾微妙的時間內(nèi)對于參與到一 首先,變量對于一個程序來說是一個很重要的角色, 那么問題來了 這些變量存在哪里,程序用到的時候如何找到變量呢? 所以需要一套規(guī)則來存儲變量方便之后...
閱讀 4186·2021-11-22 13:52
閱讀 2094·2021-09-22 15:12
閱讀 1133·2019-08-30 15:53
閱讀 3467·2019-08-29 17:12
閱讀 2198·2019-08-29 16:23
閱讀 1662·2019-08-26 13:56
閱讀 1778·2019-08-26 13:44
閱讀 1897·2019-08-26 11:56