圖解JS閉包造成的緣由

前言

什麼是閉包,其實閉包是能夠重用一個對象,又保護對象不被篡改的一種機制。什麼是重用一個對象又保護其不被篡改呢?請看下面的詳解。windows

做用域和做用域鏈

注意
理解做用域和做用域鏈對理解閉包有很是大的幫助,因此咱們先說一下做用域和做用域鏈閉包

什麼是做用域
做用域表示的是一個變量的可用範圍、其實它是一個保存變量的對象
爲何要使用做用域
避免不一樣範圍的變量互相干擾函數

做用域包含了哪兩種
一、全局做用域
在JavaScript中的全局做用域其實就是windows
優勢:可重複使用,隨處可用
缺點:會形成全局污染
clipboard.png學習

二、函數做用域
臨時建立的活動對象AO(Activation Object)、該對象包含了函數的全部局部變量、命名參數、參數集合以及this,當運行時上下文被銷燬、活動也會被銷燬(閉包造成的緣由其實由於就是由於活動對象被引用着沒法被銷燬而致使的,詳細的請繼續往下看)
優勢:不污染全局
缺點:不可重複使用、僅在函數內可使用this

程序執行的四個階段
我如下面一段代碼解釋一下程序執行的幾個階段spa

var age = "21";
    function myAge(){
        var age = 0;
        age++;
        console.log(age);
    }
    myAge();
    console.log(age);

第一階段:在內存中建立執行執行環境棧、把全局對象window壓入棧底、在window中聲明變量
圖片描述code

第二階段:函數調用時
在執行環境中添加當前函數調用、爲本次函數調用建立活動對象AO、根據scope指定運行期活動對象AO的上下文內部對象
圖片描述對象

第三階段:函數調用後
函數調用從執行環境棧中出棧、函數做用域AO釋放、函數做用域AO中的局部變量也一同被釋放
圖片描述圖片

由上面能夠看出當一個函數調用完畢它的局部變量就會被釋放,下次再次調用時會建立新的局部變量。這就是函數中的局部變量不可重用的緣由。ip

爲何要使用閉包

先介紹一下全局變量和局部變量的優缺點
全局變量:能夠重用、可是會形成全局污染並且容易被篡改
局部變量:僅函數內使用不會形成全局污染也不會被篡改、不能夠重用
從上面能夠看出全局變量和局部變量的優缺點恰好是相對的。閉包的出現正好結合了全局變量和局部變量的優勢。

什麼時候使用閉包
但願重用一個對象,又保護對象不被污染篡改時

閉包的實現原理

弄清楚了做用域和做用域鏈、閉包實現的原理也就很容易弄懂了。
下面請看一段代碼和幾張圖^_^
這是一段閉包的代碼,咱們又這段代碼講講閉包的原理

function addAge(){
        var age = 21;
        return function(){
            age++;
            console.log(age);
        }
    }
    var clourse = addAge();
    clourse();
    clourse();
    clourse();

第一階段:在內存中建立執行執行環境棧、把全局對象window壓入棧底、在window中聲明變量(和前面是類似的)
圖片描述

第二階段:
一、在棧中添加addAge的函數調用
二、爲addAge函數建立活動對象AO、根據addAge函數的scope能夠知道其活動對象指向window
三、window對象中的clourse變量記錄着addAge()返回的匿名函數的地址[如今addAge()和clourse變量均可以找到匿名函數和addAge()產生的AO]
圖片描述

第三階段:
addAge()調用完畢出棧、其對活動對象AO的引用也隨之消失。
在做用域和做用域鏈中舉的例子中,活動對象AO會被JS中的垃圾回收機制回收你們還記得嘛^_^,
可是這裏和前面是不同的哦!注意了:匿名函數中的scope引用着活動對象AO、匿名函數的地址也被clourse變量記錄着。所以,addAge()雖然出棧了,對它的活動對象的引用也消失了,可是其活動對象被匿名函數的scope拽着、因此沒法釋放不會被回收。
你們觀看藍色的箭頭,其實能夠發現、藍色的箭頭已經造成了一個閉環了。
此時,由圖也能夠看出,活動對象AO只能經過clourse變量來找到。這裏造成了一個閉包。保存了addAge()函數中的局部變量,使其能夠重複使用,可是又不會形成全局污染。這就是閉包的一個使用場景:保存現場。
至於怎麼調用重複使用局部變量,具體過程請看下面兩幅圖。
圖片描述

第四階段:
clourse()進棧,產生clouse()的活動對象AO,根據它的scope能夠知道它的__parent__指向addAge()產生的活動對象AO。
clouse()執行age++,因爲在它本身的做用域裏面沒有age、因而它會到上一級做用域查找age,它在它的上一級做用域中找到了age,因而對其進行了age++,age從21變成了22。執行console.log(age)輸出22。
圖片描述

第五階段:
clourse()出棧,由於clourse產生的AO沒有scope拽着它,所以clourse的AO是能夠正常釋放的。函數出棧,其AO被JS的垃圾回收機制回收。
clourse變量中的匿名函數中的scope依舊拽着addAge()產生的活動對象AO,因而這個活動對象依舊沒法被釋放[並且這個AO如今只能被clourse找到、clourse能夠重複使用這個AO裏面的局部變量age、又不會形成全局污染]
圖片描述

剩下階段:
代碼中還有兩個函數還有執行

clourse()
clourse()

它的執行和第四階段和第五階段的原理是同樣的。因此在這裏我就不重複畫圖了。
執行clourse():看第四階段的圖,age從22變成了23,執行console.log(age)輸出2三、看第五階段的圖
執行clourse(): 看第四階段的圖,age從23變成了24,執行console.log(age)輸出2四、看第五階段的圖

總結

以上就是對閉包造成過程的圖解。也說明了閉包保存現場的做用場景。閉包結合了局部變量和全局變量的優勢。可使變量不污染全局,可是又能對變量進行重用。可是,其實閉包也有有缺點的,它比起普通函數會佔用更多的內存。總的來講,以上就是我對閉包的理解。若是你們發現了什麼錯誤歡迎評論指出,一塊兒交流一塊兒學習一塊兒進步!^_^

相關文章
相關標籤/搜索