初識javascript閉包

      做爲第一次接觸閉包,就將本身的理解及查詢相關信息進行總結,在談論閉包以前,先了解幾個相關知識:執行上下文、做用域鏈、垃圾回收機制; javascript

       先從執行上下文開始,什麼是執行上下文呢,每次當控制器轉到ECMAScript可執行代碼的時候,即會進入到一個執行上下文執行上下文(簡稱-EC)是一個抽象概念,ECMA-262標準用這個概念同可執行代碼(executable code)概念進行區分。  html

Javascript中代碼的運行環境分爲如下三種: 前端

  • 全局級別的代碼 – 這個是默認的代碼運行環境,一旦代碼被載入,引擎最早進入的就是這個環境。 java

  • 函數級別的代碼 – 當執行一個函數時,運行函數體中的代碼。 web

  • Eval的代碼 – 在Eval函數內運行的代碼。 瀏覽器

    如: 閉包

    var testvar = "hello world!"; 函數

    function test(){ 性能

         var name = "Tom"; this

        function Rname(){

                    return name;

            }

    }

    其中,全部代碼屬於全局級別代碼,也就是全局性上下文,而test函數中則是函數級別的代碼,即函數的上下文。注意的是,無論什麼狀況下,只存在一個全局的上下文,該上下文能被任何其它的上下文所訪問到。至於函數上下文的個數是沒有任何限制的,每到調用執行一個函數時,引擎就會自動新建出一個函數上下文,換句話說,就是新建一個局部做用域,能夠在該局部做用域中聲明私有變量等,在外部的上下文中是沒法直接訪問到該局部做用域內的元素的。

    當javascript代碼文件被瀏覽器載入後,默認最早進入的是一個全局的執行上下文。當在全局上下文中調用執行一個函數時,程序流就進入該被調用函數內,此時引擎就會爲該函數建立一個新的執行上下文,而且將其壓入到執行上下文堆棧的頂部。瀏覽器老是執行當前在堆棧頂部的上下文,一旦執行完畢,該上下文就會從堆棧頂部被彈出,而後,進入其下的上下文執行代碼。這樣,堆棧中的上下文就會被依次執行而且彈出堆棧,直到回到全局的上下文。

    因而可知 ,對於執行上下文這個抽象的概念,能夠概括爲如下幾點:

    • 單線程

    • 同步執行

    • 惟一的一個全局上下文

    • 函數的執行上下文的個數沒有限制

    • 每次某個函數被調用,就會有個新的執行上下文爲其建立,即便是調用的自身函數,也是如此。

    執行上下文的創建過程

    咱們如今已經知道,每當調用一個函數時,一個新的執行上下文就會被建立出來。然而,在javascript引擎內部,這個上下文的建立過程具體分爲兩個階段:

    1. 創建階段(發生在當調用一個函數時,可是在執行函數體內的具體代碼之前)

      • 創建變量,函數,arguments對象,參數

      • 創建做用域鏈

      • 肯定this的值

    2. 代碼執行階段:

      • 變量賦值,函數引用,執行其它代碼

    實際上,能夠把執行上下文看作一個對象,其下包含了以上3個屬性:

    1
    2
    3
    4
    5
      executionContextObj  =  {
        variableObject :  {  /* 函數中的arguments對象, 參數, 內部的變量以及函數聲明 */  } ,
        scopeChain :  {  /* variableObject 以及全部父執行上下文中的variableObject */  } ,
         this :  { }
       }


    創建階段以及代碼執行階段的詳細分析可查看參考博文源地址: http://www.360weboy.com/frontdev/javascript/execution-context.html

    而在創建上下文中,有創建做用域鏈,那麼什麼又是做用域鏈呢?

    咱們都知道,在JavaScript中,變量的做用域有全局做用域和局部做用域兩種。在JavaScript中,函數也是對象,函數對象和其它對象同樣,擁有能夠經過代碼訪問的屬性和一系列僅供JavaScript引擎訪問的內部屬性。其中一個內部屬性是[[Scope]],由ECMA-262標準第三版定義,該內部屬性包含了函數被建立的做用域中對象的集合,這個集合被稱爲函數的做用域鏈,它決定了哪些數據能被函數訪問。

    當一個函數建立後,它的做用域鏈會被建立此函數的做用域中可訪問的數據對象填充。例如定義下面這樣一個函數:

    ?
    1
    2
    3
    4
    function add(num1,num2) {
        var sum = num1 + num2;
        return sum;
    }

      在函數add建立時,它的做用域鏈中會填入一個全局對象,該全局對象包含了全部全局變量,以下圖所示:




       

     函數add的做用域將會在執行時用到。例如執行以下代碼:

?
1
var total = add(5,10);

  執行此函數時會建立一個稱爲「運行期上下文(execution context)」的內部對象,運行期上下文定義了函數執行時的環境。每一個運行期上下文都有本身的做用域鏈,用於標識符解析,當運行期上下文被建立時,而它的做用域鏈初始化爲當前運行函數的[[Scope]]所包含的對象。

     這些值按照它們出如今函數中的順序被複制到運行期上下文的做用域鏈中。它們共同組成了一個新的對象,叫「活動對象(activation object)」,該對象包含了函數的全部局部變量、命名參數、參數集合以及this,而後此對象會被推入做用域鏈的前端,當運行期上下文被銷燬,活動對象也隨之銷燬。新的做用域鏈以下圖所示:


      在函數執行過程當中,每遇到一個變量,都會經歷一次標識符解析過程以決定從哪裏獲取和存儲數據。該過程從做用域鏈頭部,也就是從活動對象開始搜索,查找同名的標識符,若是找到了就使用這個標識符對應的變量,若是沒找到繼續搜索做用域鏈中的下一個對象,若是搜索完全部對象都未找到,則認爲該標識符未定義。函數執行過程當中,每一個標識符都要經歷這樣的搜索過程。

博文源地址:http://www.cnblogs.com/lhb25/archive/2011/09/06/javascript-scope-chain.html

    還有就是垃圾回收機制(GC:GarbageCollecation),GC在回收內存時,首先會判斷該對象是否被其它對象引用。在肯定沒有其它對象引用便釋放該對象內存區域。所以如何肯定對象再也不被引用是GC的關鍵所在。

.<script>


    functionaa(){
this.rr=「彈窗」;}

    functionbb(){this.rr=「彈窗」;}

    functioncc(){var a1=newaa();var b1=newbb();returnb1;}

    var b1=cc();

    alert(b1.rr);

</script>


    此時cc函數中的a1,b1都是局部變量,但仍然會彈出文字窗口。說明b1並無被GC回收。所以JavaScript中局部變量不是全部時候都被GC回收的。

    而對於閉包(closure),Javascript閉包(closure)深刻淺出,以函數cc爲例變量層級關係爲:
    window<=>cc<=>a1<=>rr2.<=>b1<=>rr
在執行cc()方法時,文字解釋以下:


    .window的活動對象包括cc,假設window是頂級對象(由於運行中不會被回收)

    ·cc的活動對象包括a1和b1,其做用域鏈是window·a1的活動對象包括rr,其做用域鏈是cc

    ·b1的活動對象包括rr,其做用域鏈是cc

    執行cc()時,cc的執行環境會建立一個活動對象和一個做用域鏈。其局部變量a1,b1都會掛在cc的活動對象中。當cc()執行完畢後,執行環境會嘗試回收活動對象佔用的內存。但因局部變量b1經過returnb1,爲其增長了一條做用域鏈:window<=>b1<=>rr,因此GC中止對b1回收。

        咱們都知道,Javascript語言的特殊之處,就在於函數內部能夠直接讀取全局變量在函數外部天然沒法讀取函數內的局部變量。那麼,咱們有時候須要獲得函數內的局部變量,如何能實現呢,就是閉包,個人理解呢,閉包就是可以讀取其餘函數內部變量的函數;以下:

    function f1(){
       function f2(){
           var temp = 100;
           document.write(temp)
        }
        return f2;
    }
        var result = f1();
        result();// 100;

      其中,函數f1中的局部變量temp一直保存在內存中,並無在f1調用後被自動清除爲何呢,緣由就在於f1是f2的父函數,而f2被賦給了一個全局變量,也就是被result所引用,這致使f2始終在內存中,而f2的存在依賴於f1,所以f1也始終在內存中,不會在調用結束後,被垃圾回收機制(garbage collection)回收。這也就是閉包的兩個用途:一個是前面提到的能夠讀取函數內部的變量,另外一個就是讓這些變量的值始終保持在內存中。 

使用閉包的注意點

1)因爲閉包會使得函數中的變量都被保存在內存中,內存消耗很大,因此不能濫用閉包,不然會形成網頁的性能問題,在IE中可能致使內存泄露。解決方法是,在退出函數以前,將不使用的局部變量所有刪除。

2)閉包會在父函數外部,改變父函數內部變量的值。因此,若是你把父函數看成對象(object)使用,把閉包看成它的公用方法(Public Method),把內部變量看成它的私有屬性(private value),這時必定要當心,不要隨便改變父函數內部變量的值。

相關文章
相關標籤/搜索