JS 閉包

來源:互聯網
上載者:User

標籤:style   blog   http   color   io   使用   ar   strong   for   

 

  閉包是很多語言都具備的特性,在js中,閉包主要涉及到js的幾個其他的特性:範圍鏈,垃圾(記憶體)回收機制,函數嵌套,等等.

  在理解閉包以前.最好能先理解一下範圍鏈的含義,簡單來說,範圍鏈就是函數在定義的時候建立的,用於尋找使用到的變數的值的一個索引,而他內部的規則是,把函數自身的本地變數放在最前面,把自身的父級函數中的變數放在其次,把再高一級函數中的變數放在更後面,以此類推直至全域對象為止.當函數中需要查詢一個變數的值的時候,js解譯器會去範圍鏈去尋找,從最前面的本地變數中先找,如果沒有找到對應的變數,則到下一級的鏈上找,一旦找到了變數,則不再繼續.如果找到最後也沒找到需要的變數,則解譯器返回undefined.

  瞭解了範圍鏈,我們再來看看js的記憶體回收機制,一般來說,一個函數在執行開始的時候,會給其中定義的變數劃分記憶體空間儲存,以備後面的語句所用,等到函數執行完畢返回了,這些變數就被認為是無用的了.對應的記憶體空間也就被回收了.下次再執行此函數的時候,所有的變數又回到最初的狀態,重新賦值使用.但是如果這個函數內部又嵌套了另一個函數,而這個函數是有可能在外部被調用到的.並且這個內建函式又使用了外部函數的某些變數的話.這種記憶體回收機制就會出現問題.如果在外部函數返回後,又直接調用了內建函式,那麼內建函式就無法讀取到他所需要的外部函數中變數的值了.所以js解譯器在遇到函數定義的時候,會自動把函數和他可能使用的變數(包括本地變數和父級和祖先級函數的變數(自由變數))一起儲存起來.也就是構建一個閉包,這些變數將不會被記憶體回收器所回收,只有當內部的函數不可能被調用以後(例如被刪除了,或者沒有了指標),才會銷毀這個閉包,而沒有任何一個閉包引用的變數才會被下一次記憶體回收啟動時所回收.

也就是說,有了閉包,嵌套的函數結構才可以運作,這也是符合我們的預期的.然後,閉包還有一些特性,卻往往讓程式員覺得很難理解.

看看下面一段代碼.

var result=[];function foo(){    var i= 0;    for (;i<3;i=i+1){        result[i]=function(){            alert(i)        }    }};foo();result[0](); // 3result[1](); // 3result[2](); // 3

這段代碼中,程式員希望foo函數中的變數i被內部迴圈的函數使用,並且能分別獲得他們的索引,而實際上,只能獲得該變數最後保留的值,也就是說.閉包中所記錄的自由變數,只是對這個變數的一個引用,而非變數的值,當這個變數被改變了,閉包裡擷取到的變數值,也會被改變.

解決的方法之一,是讓內建函式在迴圈建立的時候立即執行,並且捕捉當前的索引值,然後記錄在自己的一個本地變數裡.然後利用返回函數的方法,重寫內建函式,讓下一次調用的時候,返回本地變數的值,改進後的代碼:

var result=[];function foo(){    var i= 0;    for (;i<3;i=i+1){        result[i]=(function(j){            return function(){                alert(j);            };        })(i);    }};foo();result[0](); // 0result[1](); // 1result[2](); // 2

在這裡我再解釋一下.這裡用到了另外2個技術,立即調用的匿名函數和返回函數

 

但是你只需要知道應用的兩種情況即可——函數作為傳回值,函數作為參數傳遞。

第一,函數作為傳回值

如上代碼,bar函數作為傳回值,賦值給f1變數。執行f1(15)時,用到了fn範圍下的max變數的值。至於如何跨範圍取值,可以參考上一節。

 

第二,函數作為參數被傳遞

如上代碼中,fn函數作為一個參數被傳遞進入另一個函數,賦值給f參數。執行f(15)時,max變數的取值是10,而不是100。

 

上一節講到自由變數跨範圍取值時,曾經強調過:要去建立這個函數的範圍取值,而不是“父範圍”。理解了這一點,以上兩端代碼中,自由變數如何取值應該比較簡單。(不明白的朋友一定要去上一節看看,這個很重要!)

 

另外,講到閉包,除了結合著範圍之外,還需要結合著執行內容棧來說一下。

在前面講執行內容棧時(http://www.cnblogs.com/wangfupeng1988/p/3989357.html),我們提到當一個函數被調用完成之後,其執行內容環境將被銷毀,其中的變數也會被同時銷毀。

但是在當時那篇文章中留了一個問號——有些情況下,函數調用完成之後,其執行內容環境不會接著被銷毀。這就是需要理解閉包的核心內容。

咱們可以拿本文的第一段代碼(稍作修改)來分析一下。

 

第一步,代碼執行前產生全域上下文環境,並在執行時對其中的變數進行賦值。此時全域上下文環境是活動狀態。

 

第二步,執行第17行代碼時,調用fn(),產生fn()執行內容環境,壓棧,並設定為活動狀態。

 

第三步,執行完第17行,fn()調用完成。按理說應該銷毀掉fn()的執行內容環境,但是這裡不能這麼做。注意,重點來了:因為執行fn()時,返回的是一個函數。函數的特別之處在於可以建立一個獨立的範圍。而正巧合的是,返回的這個函數體中,還有一個自由變數max要引用fn範圍下的fn()上下文環境中的max。因此,這個max不能被銷毀,銷毀了之後bar函數中的max就找不到值了。

因此,這裡的fn()上下文環境不能被銷毀,還依然存在與執行內容棧中。

——即,執行到第18行時,全域上下文環境將變為活動狀態,但是fn()上下文環境依然會在執行內容棧中。另外,執行完第18行,全域上下文環境中的max被賦值為100。如:

 

第四步,執行到第20行,執行f1(15),即執行bar(15),建立bar(15)上下文環境,並將其設定為活動狀態。

執行bar(15)時,max是自由變數,需要向建立bar函數的範圍中尋找,找到了max的值為10。這個過程在範圍鏈一節已經講過。

這裡的重點就在於,建立bar函數是在執行fn()時建立的。fn()早就執行結束了,但是fn()執行內容環境還存在與棧中,因此bar(15)時,max可以尋找到。如果fn()上下文環境銷毀了,那麼max就找不到了。

使用閉包會增加內容開銷,現在很明顯了吧!

 

第五步,執行完20行就是上下文環境的銷毀過程,這裡就不再贅述了。

 

閉包和範圍、上下文環境有著密不可分的關係,真的是“想說愛你不容易”!

另外,閉包在jQuery中的應用非常多,在這裡就不一一舉例子了。所以,無論你是想瞭解一個經典的架構/類庫,還是想自己開發一個外掛程式或者類庫,像閉包、原型這些基本的理論,是一定要知道的。否則,到時候出了BUG你都不知道為什麼,因為這些BUG可能完全在你的知識範圍之外。

JS 閉包

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.