閉包裏的微觀世界

本文旨在解釋閉包裏的微觀世界javascript

內容包含:值類型做用域閉包html

JS當中全部的function都是閉包,通常說來,嵌套的function的閉包性更強。這也是咱們平時接觸和研究比較多的地方。java

在進入本文的核心部分之前,首先來理解幾個概念:閉包

  • 值類型函數

    聲明一個值類型變量,編譯器會在棧上分配一個空間,這個空間對應着該值的類型變量,空間存儲的就是這個變量的值。存儲在棧(stack)中的簡單數據段,也就是說,它們的值直接存儲在變量訪問的位置。spa

  • 引用類型.net

    引用類型的實例分配在堆(heap)上,新建一個引用類型的實例,獲得的變量值對應的是該實例的內存分配地址。存儲在堆(heap)中的對象,也就是說,存儲在變量中的值是一個指針(point),其指向存儲對象的位置。3d

    javascript//值類型
    var a="xl";
    var b=a;
    a="XL";
    console.log(b); //輸出  "xl"
    
    //引用類型
    var a={name:"xl"};
    var b=a;
    a.name="XL";
    console.log(b.name);//輸出 "XL"

區別就是值類型變量是能夠直接訪問棧(stack)中的值:指針

  • 在第一段代碼中,將變量"a"賦值給"b",至關於在stack中也爲"b"開闢了一個存儲其值的空間,與存儲變量"a"的存儲空間是相互獨立的,所以修改"a"的值,不會影響到「b」的值。
  • 在第二段代碼中,"a","b"都得到的是對於存儲在heap當中實例的引用,當「a」對其進行修改的時候,「b」的引用也會受到影響。

接下來的內容就是關於閉包的微觀世界code

javascriptfunction a(){
        var i=0;
        function b(){
            console.log(++i);
        }
        return b;
    }

    var c=a(); //函數a執行後返回函數b,並將函數b賦給c
    c();//輸出 1

原本這個地方變量i是定義在函數a中,並不能被函數a的外部所訪問,可是這個地方由於在a中定義了一個函數b,函數b中有對變量i的引用,所以當b被a返回後,變量c得到了對函數a中函數b的引用,所以i不會被GC回收,而是存在內存當中。

當在一個函數a裏面定義另一個函數b,函數b有對函數a中變量的引用,當函數a執行並返回函數b,將b賦給變量c時,這樣就存在相互之間的引用關係,並造成了你們常常見到的閉包

咱們進一步的分析:這一部分的內容包含了做用域做用域鏈部分的內容.

依然拿上面的例子來分析:

  • 當定義函數a的時候,js解釋器會將函數a的做用域鏈(scope chain)設置爲定義a時所在的「環境」,若是a是一個全局函數,那麼scope chain中只有window對象。

  • 當執行函數a的時候,a會進入相應的執行環境(excution context).

  • 在建立執行環境的過程當中,首先會爲a添加scope屬性,即a的做用域,其值就爲第一步的scope chain.即a.scope=a的做用域鏈。

  • 而後執行環境會建立一個活動對象(call object).活動對象也是一個擁有屬性的對象。但它不具備原型並且不能直接經過javascript代碼訪問。建立完活動對象後,把活動對象添加到a的做用域的最頂端,此時a的做用域鏈包含2個對象:a的活動對象和window對象。

  • 下一步是在活動對象上添加一個arguments屬性,它保存着調用a時所傳遞的參數。最後把全部函數a的形參以及定義的內部函數b添加到a的活動對象上。在這一步中,完成了函數b的定義,正如第一步,函數b的做用域鏈被設置爲b被定義時所處的環境,即a的做用域
    到此,整個函數a從定義到執行的過程就完成了。此時a返回函數b的引用給c,又函數b的做用域鏈包含了對函數a的活動對象的引用,也就是說b能夠訪問到a中定義的全部變量和函數。函數b被c引用,函數b又依賴函數的a,所以函數a在返回的時候不會被gc收回。

  • 當函數b執行的時候,一樣會按上述步驟同樣。執行時b的做用域裏包含了3個對象:{b的活動對象}、{a的活動對象}、{window對象}

下面用2張圖來表示整個過程:

圖一展現了函數a定義過程是如何建立做用域鏈的

圖片描述

圖二展現了函數a執行過程產生的活動對象(call object)

圖片描述

在這其中有個很是重要的內容就是函數的做用域是在定義函數的時候就已經肯定,而不是在執行的時候肯定。

具體內容參見:鳥哥:Javascript做用域和做用域鏈

再來看看咱們在平時常常遇到的一段代碼:

javascriptHTML部分:
        <div id="example">
            <span>1</span>
            <span>2</span>
            <span>3</span>
        </div>

    JS:

    var spanArr=document.getElementById("example").getElementsByTagName("span");
    for(var i=0;i<3;i++){
        spanArr[i].onclick=function(){
            console.log(i);
        }
    }
    //無論點擊哪一個<span>都會輸出3
    //這是由於在內部的匿名函數中i是對於外部的i的引用。當for循環結束之後,i的值變爲了3.那麼匿名函數相應得到的引用值夜都變爲了3.因此最後無論點擊哪一個<span>最後都會輸出3.
    //因此遇到這種狀況的時候通常處理方法是
    1.將變量i保存在每一個span對象上。
    for(var i=0;i<3;i++){
        spanArr[i].i=i;
        spanArr[i].onclick=function(){
            console.log(i);
        }
    }
    2.加一層閉包
    for(var i=0;i<3;i++){
        (function(i){
            spanArr[i].onclick=function(){
                console.log(i);
            }
        })(i)
    }
    //固然還有其餘的方法,這裏很少述。

參考文章:

  1. 理解javascript的做用域和做用域鏈
  2. javascript閉包深刻理解
  3. 理解javascript閉包
相關文章
相關標籤/搜索