JS完美收官之——閉包

       在上一篇JS完美收官之做用域中,咱們已經知道當函數執行完畢後,它所產生的執行期上下文會被銷燬,被世人稱之爲渣男類型的,用完就丟掉,而今天咱們探究的是閉包卻與之相反,能夠將閉包理解爲"癡情的男孩",就是無論怎麼打,怎麼罵,都牢牢拽着你衣角的那種,不禁想起曾小賢那句「好男人就是我,我就是閉包」。好了,咱們一塊兒來看看到底什麼是閉包?爲什麼被稱爲好男人呢?編程

 直接上代碼:數組

function a(){
    function b(){
        var bbb = 234;
        console.log(aaa);
     }
     var aaa = 123;
     return b;
}
var glob = 100;
var demo = a();
demo();

當a函數被定義的時候會生成全局的執行期上下文GO(global object)放在做用域鏈的第0位,緊接着在a函數執行的前一刻會生成局部的執行期上下文AO(activation object)放在做用域鏈最頂端(第0位是最頂端,1是次頂端,查找順序是從最頂端往下查)。看圖:瀏覽器

然而在上面代碼中a函數的執行產生b函數的定義,而且b函數被return保存了出來,b函數的定義是站在a函數的勞動成果之上,因此看圖:微信

當a函數執行完畢以後會銷燬本身的執行期上下文,咣噹一下把自身做用域鏈上那條線剪斷了,a銷燬後回到被定義的狀態等待下次被執行,但並不表明a函數裏面的執行期上下文跟着消失了,由於b還存着a函數執行期上下文的引用,牢牢拽着它呢。b被保存全局demo中,當執行demo()的時候,會生成一個獨一無二的執行期上下文放到做用域鏈的最頂端,隨後執行console.log(aaa),因爲自身上沒有aaa,那麼它會順着做用域鏈自上而下一次查找,此時做用域鏈上綁着三條引用,如圖: 閉包

以上過程其實就是閉包,但凡內部函數被保存到了外部,它必定生成閉包閉包是JavaScript最強大的特性之一,由於它容許函數能夠訪問除局部做用域以外的數據。函數

同時也解釋下文章開頭說的"癡男",這裏的b函數就是好男人,可是它有個前提——就是被保存到外部的時候。癡情的人雖然頗有魅力,但請不要貪杯哦,它也有缺陷...... spa

閉包的弊端:當內部函數被保存到外部的時候必定生成閉包,閉包會致使原有的做用域鏈不釋放形成內存泄漏。3d

那啥叫做用域鏈不釋放啊,原本a函數執行完畢後要銷燬自身的執行期上下文從而羽化登仙,可是因爲b函數死活拽着a函數的腳丫子不讓走,時間久了,天乾物燥,b函數身體水分蒸發過快,形成缺水。(水分從毛孔蒸發的過程就是內存泄漏,內存用的越多剩的越少,就像泄漏了同樣),要是b函數牛勁犯了,拽着好幾百個死活不放手,就會致使系統空間過多被佔用,會影響執行速度,在腳本編程中,必定要很是當心地使用閉包,由於它同時關係到內存和執行速度,咱們一般把跨做用域變量存儲在局部變量中,而後直接訪問局部變量。以此來減輕閉包對執行速度的影響。 code

閉包的小例子:blog

1.實現公有變量(寫個不依賴外部變量的累加器)

function add(){
    var count = 0;
    function demo(){
        count ++;
        console.log(count);
    }
    return demo; 
}
var counter = add();
counter();       
counter();

執行結果:

咱們能夠用閉包來作一個不依賴外部變量的累加器,調用多少次就會加多少次,這樣的好處能夠把局部變量變成私有狀態,減小了全局變量的使用,全局變量處在做用域鏈的最底層,位置越深執行速度就會越緩慢,具體慢多少還得取決於瀏覽器,因此咱們在寫程序的時候儘可能使用局部變量

⚠️全局變量還有一個很是重要的問題:那就是會發生命名衝突,好比說第三方庫裏面定義了一個全局變量global,而後又在函數裏面定義了一個全局變量global,這樣就會出現問題,後面的global覆蓋前面那個global,那第三方庫可能就失效了。在下一篇文章中,咱們能夠一塊兒探討下最小化全局變量的方法,好比說當即執行函數、命名空間的模式、用var聲明變量.......

2.寫一個打印0~9數字

依舊先看代碼:

function test(){
    var arr = [];
     for(var i = 0;i<10; i++){
        arr[i] = function (){
            console.log(i);
        }
    }
    return arr;
}
var myArr = test();
for(var j = 0;j<10; j++){
    myArr[j]();
}

執行結果

看到這結果是否是跟咱們想的不同!那爲何會打印10個10呢?第一點咱們要注意的是執行語句並非必定義就執行的,console.log(i)裏面的i的值不是當即打印的,而是要等被保存到外部函數的執行纔打印,這段代碼建立了10個閉包,並將它們存儲在一個數組中,數組中的10個函數分別與test函數造成閉包而且共享test函數生成的執行期上下文,也就是共享變量 i ,當test()返回時,變量 i 的值爲10,因此閉包都共享這個值,所以數組中的函數的返回值都是同一個值。

那有什麼辦法能夠解決這個問題嗎?咱們就是想讓它打印0~9,該如何處理呢?

咱們能夠用當即執行函數解決,看以下代碼:

function test() {
        var arr = [];
            for (var i = 0; i < 10; i++) {
                (function (j) {
                    arr[j] = function () {
                        console.log(j);
                    }
                }(i))
            }
            return arr;
        }
        var myArr = test();
        for (var j = 0; j < 10; j++) {
            myArr[j](); 
        }

加了當即執行函數以後,能夠實現的的效果就是讓 i 執行到第6行的時候當即打印出來 ,把 i 當作實參傳給形參 j ,當下面代碼

arr[j] = function () {
           console.log(j);
    }

被保存到外部時,拿到的是當即執行函數的所產生的執行期上下文,與當即執行函數造成閉包,因爲在for循環中,會產生10個獨一無二的當即執行函數,當即執行函數裏面的函數分別保存了各自的當即執行函數的執行期上下文,因此當裏面的函數被保存到外部執行的時候就會打印各自保存的值。

執行結果

『 好啦,以上呢,就是這期給你們的分享啦,謝謝支持~』

「歡迎各位大佬關注個人微信公衆號,掃二維碼便可」

相關文章
相關標籤/搜索