摘要:離開閉包的泥淖,給這個例子一個較為合理的寫法總結理解閉包的概念是重要的,但我們不應當過多的使用閉包,它有優(yōu)點,也優(yōu)缺點,是一把雙刃劍。
閉包
關于閉包,目前有如下說法:
閉包是函數和聲明該函數的詞法環(huán)境的組合(MDN)
函數對象可以通過作用域鏈相互關聯(lián)起來,函數體內部的變量都可以保存在函數作用域內。這種特性在計算機科學文獻中被稱為閉包(JavaScript權威指南)
閉包,指的是詞法表示包括不被計算的變量的函數,也就是說,函數可以使用函數之外定義的變量(W3school)
閉包是指有權訪問另一個函數作用域中的變量的函數(JavaScript高級程序設計)
根據排列順序也可以看出,我個人對這些說法的認同程度。其實大家說的都是同一個東西,只是描述是否精確的問題。
為了充分理解以上的說法,要先理解一些術語:
簡單來說,詞法作用域就是:根據變量定義時所處的位置,來確定變量的作用范圍。(詞法解析,通過閱讀包含變量定義在內的數行源碼就能知道變量的作用域)
舉例而言,定義在全局的變量,它的作用范圍是全局的,所以被稱為全局變量;定義在函數內部的變量,它的作用范圍是局部的,所以被稱為局部變量。
函數在創(chuàng)建時,會同時保存它的作用域鏈。——這個保存的作用域鏈包含了該函數所處的作用域對象的集合。因為所有函數都在全局作用域下聲明,所以這個保存的作用域鏈一定包含全局作用域對象(global)。此外,如果函數是在其他函數內部聲明的,那它保存的作用域鏈中除了global之外,還包含它創(chuàng)建時所處的局部作用域對象。(在chrome中直接標識為closure,在firefox中則標識為塊)。顯然,這個作用域鏈實際上是一個指向作用域對象集合的指針列表。
函數在執(zhí)行時,會創(chuàng)建一個執(zhí)行環(huán)境、執(zhí)行時作用域鏈以及活動對象?!顒訉ο?activation object)是指當前作用域對象(處于活動狀態(tài)的,它包含arguments、this以及所有局部變量)。執(zhí)行時作用域鏈實際上是函數創(chuàng)建時保存的作用域鏈的一個復制,但它更長,因為活動對象被推入了執(zhí)行時作用域鏈的前端。每次函數在執(zhí)行時都會創(chuàng)建一個新的執(zhí)行環(huán)境(execution context),它對應著一個全新的執(zhí)行時作用域鏈。
根據JavaScript的垃圾回收機制:一般情況下,函數在執(zhí)行完畢后,執(zhí)行環(huán)境(包括執(zhí)行時作用域鏈)將自動被銷毀,占用的內存將被釋放。
垃圾回收機制JavaScript 是一門具有自動垃圾回收機制的語言。
這種機制的原理是找出那些不再繼續(xù)使用的變量,然后釋放其占用的內存。目前,找出不再繼續(xù)使用的變量的策略有兩種:標記清除(主流瀏覽器)和引用計數(IE8及以下)。
標記清除:垃圾收集器在運行的時候會給存儲在內存中的所有變量都加上標記;然后,它會去掉環(huán)境中的變量以及被環(huán)境中的變量引用的變量的標記;最后,垃圾收集器銷毀那些帶標記的值并回收它們所占用的內存空間。垃圾收集器會按照固定的時間間隔周期性地執(zhí)行這一操作。
引用計數:當聲明了一個變量并將一個引用類型值賦給該變量時,則這個值的引用次數就是 1。如果同一個值又被賦給另一個變量,則該值的引用次數加 1。相反,如果包含對這個值引用的變量又取得了另外一個值,則這個值的引用次數減 1。當這個值的引用次數變成 0 時,則說明沒有辦法再訪問這個值了,因而就可以將其占用的內存空間回收回來。這樣,當垃圾收集器下次再運行時,它就會釋放那些引用次數為零的值所占用的內存。(引用計數的失敗之處在于它無法處理循環(huán)引用)
現在,什么是閉包呢?
——“閉包是函數和聲明該函數的詞法環(huán)境的組合”(MDN)
function a(){ console.log("1"); } a();
以上例子:函數a,和它創(chuàng)建時所在的全局作用域,構成一個閉包。于是有人說每個函數實際上都是一個閉包,但準確來講,應該是每個函數和它創(chuàng)建時所處的作用域構成一個閉包。
但這個閉包叫什么名字呢?
在chrome和firefox調試中,將函數a所在作用域的名字,作為閉包的名字;在JavaScript高級程序設計中則將函數a的名字,作為閉包的名字。這樣一來,每個函數都是一個閉包的說法似乎又“準確”了一些。
其實我們書寫的所有js代碼,都處在全局作用域這個大大的閉包之中,只是我們意識不到它作為一個閉包存在著。
function a(){ var b = 1; function c(){ console.log(b); } return c } var d = a(); d(); // 1
以上例子:除了函數a和全局作用域構成一個閉包以外,函數c和局部作用域(函數a的作用域)也構成一個閉包。
先不關注這些函數內部的邏輯,我們只看結構:
函數a聲明了,然后在var d = a();這一句執(zhí)行。通過以上對詞法作用域、作用域鏈以及垃圾回收機制的理解,我們可以得出以下結論:
函數a在聲明時保存了一個作用域鏈,在它執(zhí)行時又創(chuàng)建了一個執(zhí)行環(huán)境(以及執(zhí)行時作用域鏈)。一般情況下,當函數a執(zhí)行完畢,它的執(zhí)行環(huán)境將被銷毀。但在這個例子里,函數a中的變量c,被return突破作用域的限制賦值給了變量d,而變量c是一個函數,它使用了它創(chuàng)建時所處的作用域(函數a的作用域)中的變量b,這意味著,在函數d執(zhí)行完畢之前,函數c以及它創(chuàng)建時所處的作用域中變量(變量b)不可以被銷毀。
這打斷了函數a執(zhí)行環(huán)境的銷毀進程,它被保存了下來,以備函數d調用時使用??纯幢槐4娴氖鞘裁??一個函數c和它創(chuàng)建時所在的作用域。一個閉包。
function a(){ var b = 1; function c(){ b++; console.log(b); } return c } var d = a(); d(); // 2 d(); // 3 var e = a(); e(); // 2 e(); // 3
以上例子,函數a被執(zhí)行了兩次并分別賦值給了d、e,顯然,函數a的兩次執(zhí)行創(chuàng)建了兩個執(zhí)行環(huán)境,它們本該被銷毀,但由于函數c的存在(有權訪問另一個函數內部變量的函數),它們被保存下來。函數d的兩次執(zhí)行,使用同一個執(zhí)行環(huán)境中的變量b,所以b遞增了;由于函數e使用的是另一個執(zhí)行環(huán)境中的變量b,所以它重新開始遞增。
所以,什么是閉包呢?
閉包是一個函數和它創(chuàng)建時所在作用域的組合。在我們日常應用中,通常是將一個函數定義在另一個函數的內部并從中返回,以使它成為一個在函數外部仍有權限訪問函數內部作用域的函數。
jQuery就是定義在一個匿名自執(zhí)行函數內部的函數,當它被賦值給全局作用域變量$和jQuery時,在全局作用域使用$和jQuery方法,就能夠訪問到那個匿名自執(zhí)行函數的內部作用域(其中包含的變量等)。在jQuery這個例子中,內部函數jQuery和其所在的匿名自執(zhí)行函數作用域就構成一個閉包。
一個經典的例子:
// html
為頁面上的所有l(wèi)i標簽綁定點擊函數,點擊后輸出自身的序號。在以上例子中,顯然將輸出 3, 3, 3;而非 0, 1, 2;
一個通俗的解釋是,當點擊li標簽時,for循環(huán)已經執(zhí)行完畢,i的值已經確定。所以三個li標簽點擊輸出同一個i的值。
我們稍微改動一下代碼:
// html
以上例子,當點擊li標簽時,for循環(huán)已經執(zhí)行完畢,i的值已經確定,可為什么結果會輸出 0, 1, 2 呢?
實際上,這是閉包在作怪:
click事件的匿名函數 跟外層自執(zhí)行匿名函數的作用域構成了一個閉包。在循環(huán)中,外層匿名自執(zhí)行函數本該在執(zhí)行結束后銷毀它的執(zhí)行環(huán)境,釋放其內存,但由于它的參數(變量)i 還被事件監(jiān)聽函數引用著,所以這個執(zhí)行環(huán)境無法被銷毀,它將被保存著。每一次的循環(huán),匿名自執(zhí)行函數都將執(zhí)行一次,并保存一個執(zhí)行環(huán)境;當循環(huán)結束,類似的執(zhí)行環(huán)境共有三個,每一個里面的變量i的值都是不同的。
回到第一個例子,匿名事件函數實際上和聲明它的全局作用域也構成了一個閉包,但在三次循環(huán)中,i 都未曾離開這個閉包,它一直遞增直至3,三個點擊事件函數引用同一個執(zhí)行環(huán)境中的變量i,它們的值必然是相同的。
離開閉包的泥淖,給這個例子一個較為合理的寫法:
// html
總結:理解閉包的概念是重要的,但我們不應當過多的使用閉包,它有優(yōu)點,也優(yōu)缺點,是一把雙刃劍。使用閉包可以創(chuàng)建一個封閉的環(huán)境,使得我們可以保存私有變量,避免全局作用域命名沖突,加強了封裝性;但它常駐內存的特性也對網頁的性能造成了比較大的影響,在引用計數的垃圾回收策略下更容易造成內存泄漏。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/100191.html
摘要:閉包引起的內存泄漏總結從理論的角度將由于作用域鏈的特性中所有函數都是閉包但是從應用的角度來說只有當函數以返回值返回或者當函數以參數形式使用或者當函數中自由變量在函數外被引用時才能成為明確意義上的閉包。 文章同步到github js的閉包概念幾乎是任何面試官都會問的問題,最近把閉包這塊的概念梳理了一下,記錄成以下文章。 什么是閉包 我先列出一些官方及經典書籍等書中給出的概念,這些概念雖然...
摘要:閉包可以用來在一個函數與一組私有變量之間創(chuàng)建關聯(lián)關系。夾帶私貨外部變量返回的是函數,帶私貨的函數支持將函數當成對象使用的編程語言,一般都支持閉包。所以說當你的裝飾器需要自定義參數時,一般都會形成閉包。 Python中的閉包不是一個一說就能明白的概念,但是隨著你往學習的深入,無論如何你都需要去了解這么一個東西。 閉包的概念 我們嘗試從概念上去理解一下閉包。 在一些語言中,在函數中可以(嵌...
摘要:下面我們就羅列閉包的幾個常見問題,從回答問題的角度來理解和定義你們心中的閉包。函數可以通過作用域鏈相互關聯(lián)起來,函數內部的變量可以保存在其他函數作用域內,這種特性在計算機科學文獻中稱為閉包。 寫這篇文章之前,我對閉包的概念及原理模糊不清,一直以來都是以通俗的外層函數包裹內層....來欺騙自己。并沒有說這種說法的對與錯,我只是不想擁有從眾心理或者也可以說如果我們說出更好更低層的東西,逼格...
摘要:中所有的事件綁定都是異步編程當前這件事件沒有徹底完成,不再等待,繼續(xù)執(zhí)行下面的任務當綁定事件后,不需要等待執(zhí)行,繼續(xù)執(zhí)行下一個循環(huán)任務,所以當我們點擊執(zhí)行方法的時候,循環(huán)早已結束即是最后。 概念 閉包就是指有權訪問另一個函數作用域中的變量的函數 點擊li標簽彈出對應數字 0 1...
閱讀 3731·2021-11-24 10:23
閱讀 2775·2021-09-06 15:02
閱讀 1278·2021-08-23 09:43
閱讀 2358·2019-08-30 15:44
閱讀 3052·2019-08-30 13:18
閱讀 785·2019-08-23 16:56
閱讀 1750·2019-08-23 16:10
閱讀 543·2019-08-23 15:08