深入理解閉包(六)——閉包

原文地址:深入理解閉包(六)——閉包

終于講到閉包了,這一路走來(lái)不容易。
從前面的博文中我們知道,js的垃圾回收機(jī)制會(huì)在某個(gè)函數(shù)的執(zhí)行上下文生命周期結(jié)束后將其回收,釋放內(nèi)存,但是閉包的存在會(huì)阻止這一過(guò)程。

概念

js高程對(duì)閉包的定義是:閉包是指有權(quán)訪問(wèn)另一個(gè)函數(shù)作用域中的變量的函數(shù)。 創(chuàng)建閉包的常見方式就是在一個(gè)函數(shù)內(nèi)部創(chuàng)建另一個(gè)函數(shù),作為返回值或參數(shù)傳遞到函數(shù)外部。但是根據(jù)我的經(jīng)驗(yàn),只要在一個(gè)函數(shù)內(nèi)部使用了關(guān)鍵字function,一個(gè)閉包就被創(chuàng)建了。

實(shí)例

任何書面上的解釋都不如一個(gè)實(shí)例來(lái)的有效。

        var a = 1;
        function test() {
            var b = 2;
            return function () {
                var c = 3;
                console.log(a+b+c);
            }
        }
        var result=test();
        result();   //6

按照我們之前的說(shuō)法,函數(shù)外部是不能訪問(wèn)函數(shù)內(nèi)部的變量的,代碼執(zhí)行到var result=test()這句時(shí)調(diào)用test函數(shù),執(zhí)行完畢后應(yīng)該銷毀執(zhí)行上下文,其中的變量都不能再訪問(wèn),但是執(zhí)行result()時(shí)卻仍然調(diào)用了test中的變量,這是因?yàn)槲覀冊(cè)趖est函數(shù)中返回了一個(gè)匿名函數(shù)。
test函數(shù)中返回的不僅僅是一個(gè)函數(shù),還有它的執(zhí)行上下文環(huán)境,將其賦值給全局變量result后,那么result其實(shí)就等同于那個(gè)返回的匿名函數(shù),當(dāng)它被調(diào)用時(shí)可以訪問(wèn)匿名函數(shù)作用域鏈中指向的變量對(duì)象(并不是把變量對(duì)象復(fù)制給了result,只是可以被引用),這個(gè)返回的匿名函數(shù)就是閉包。
引用阮一峰老師對(duì)閉包的解釋:閉包本質(zhì)上是將函數(shù)內(nèi)部和外部連接起來(lái)的橋梁。

再看一個(gè)栗子:

    function out() {
        var a = 1;
        var inner=function () {
            console.log(a);
        }
        a++;
        return inner;
    }
    var b = out();
    b();        //2

肯定會(huì)有人覺得輸出的應(yīng)該是1,想著a++在console.log(a)后面,這是一種常見的錯(cuò)覺,代碼創(chuàng)建的位置和執(zhí)行的順序是不一樣的,這點(diǎn)要謹(jǐn)記。這段代碼里首先執(zhí)行的是調(diào)用out函數(shù),out函數(shù)執(zhí)行代碼,最后一步返回inner,要知道a++是在return之前執(zhí)行的,a=2已經(jīng)保存到了變量對(duì)象中,接下來(lái)調(diào)用b函數(shù)(等同于調(diào)用inner函數(shù)),輸出的a自然就是2.

復(fù)雜一點(diǎn)的栗子:

    var fun1,fun2,fun3;
    function test() {
        var a=10;
        fun1=function () {console.log(a);};
        fun2=function () {a++;};
        fun3=function (x){a=x;};
    }
    
    test();
    fun2();
    fun1();   //11
    fun3(5);
    fun1();   //5
    var fun4=fun1;
    test();
    fun1();   //10
    fun4();   //5

如果上一個(gè)栗子你已經(jīng)理解,那么理解這個(gè)栗子前兩次輸出的11和5也不是什么難事,但后面的兩個(gè)輸出10和5你可能會(huì)有點(diǎn)疑惑。

  • 我們第二次調(diào)用test函數(shù)時(shí)一個(gè)新的閉包被創(chuàng)建,它能訪問(wèn)到的變量也是重新創(chuàng)建的,跟前面的沒(méi)有關(guān)系,因此再調(diào)用fun1時(shí)輸出10。我們要記住這句話:如果你在一個(gè)函數(shù)內(nèi)部聲明了另一個(gè)函數(shù),那么這個(gè)外部函數(shù)每次被調(diào)用都會(huì)產(chǎn)生一個(gè)閉包,創(chuàng)建嶄新的執(zhí)行上下文環(huán)境。
  • 那么調(diào)用fun4怎么又輸出了5呢,這是因?yàn)関ar fun4=fun1是在第一次調(diào)用test發(fā)生的,那么fun4可以訪問(wèn)的變量也是第一次調(diào)用test時(shí)創(chuàng)建的變量對(duì)象,即使在別的地方被調(diào)用,它的作用域鏈也就是可訪問(wèn)的變量是不變的。我們要記住這句話:一個(gè)函數(shù)可以訪問(wèn)的變量對(duì)象要到創(chuàng)建這個(gè)函數(shù)的執(zhí)行環(huán)境中去找而不是調(diào)用這個(gè)函數(shù)的執(zhí)行環(huán)境。

還有一個(gè)非常常見的栗子,那就是閉包對(duì)循環(huán)的影響:

    var arr=[];
    function test(){
        for (i= 0;i<3;i=i+1){
            arr[i]=function(){
                console.log(i);
            };
        }
    }
    test();
    arr[0](); // 3
    arr[1](); // 3
    arr[2](); // 3

函數(shù)運(yùn)行之后會(huì)得到一個(gè)函數(shù)數(shù)組,你本想讓每個(gè)函數(shù)都返回自己的索引值,比如運(yùn)行arr[0]()時(shí)得到0,運(yùn)行arr[1]()時(shí)得到1。但實(shí)際上,每個(gè)函數(shù)都輸出3。這是因?yàn)?strong>閉包只能取得包含函數(shù)中任何變量的最后一個(gè)值,每個(gè)函數(shù)的作用域鏈中都保存著test函數(shù)中的變量對(duì)象,所以它們引用的是同一個(gè)變量i,而i的最后一個(gè)值是3,因此每個(gè)函數(shù)都輸出3。我們可以通過(guò)創(chuàng)建一個(gè)自執(zhí)行匿名函數(shù)來(lái)讓閉包符合預(yù)期:

    var arr=[];
    function test(){
        for (i= 0;i<3;i=i+1){
            arr[i]=(function(num){
                console.log(num);
            })(i);
        }
    }
    test();   //0,1,2

此處用到了自執(zhí)行匿名函數(shù)(function(){···})(),它的寫法是,在函數(shù)體外面加一對(duì)圓括號(hào),形成一個(gè)表達(dá)式,在圓括號(hào)后面再加一個(gè)圓括號(hào),里面可傳入?yún)?shù)。由于函數(shù)參數(shù)是按值傳遞的,所以就可以把變量i的當(dāng)前值賦值給匿名函數(shù)的參數(shù)num,而自執(zhí)行匿名函數(shù)可以不用調(diào)用,自己執(zhí)行自己,因此只要檢測(cè)到參數(shù)i就會(huì)立即執(zhí)行并把結(jié)果傳遞給arr數(shù)組。這樣一來(lái)我們就能得到預(yù)期的結(jié)果了。

缺點(diǎn)

最后一個(gè)栗子告訴我們,閉包有時(shí)也會(huì)給我們的工作帶來(lái)負(fù)面效果。除此之外,還有兩個(gè)缺點(diǎn)我們需要注意一下。

  1. 內(nèi)存泄漏: 在ie9之前的版本中,如果閉包的作用域鏈中保存著一個(gè)html元素,那么就意味著該元素將無(wú)法銷毀。
  2. 占用內(nèi)存:由于閉包會(huì)攜帶包含它的函數(shù)的作用域,因此會(huì)比其他函數(shù)占用更多的內(nèi)存。過(guò)度使用閉包可能會(huì)導(dǎo)致內(nèi)存占用過(guò)多,所以我們最好只在絕對(duì)必要時(shí)再考慮使用閉包。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容