Javascript 閉包

如果要了解閉包想诅,我們需要先了解閉包的由來召庞,閉包的產(chǎn)生岛心,源于JS的詞法作用域

詞法作用域

作用域是指一個 變量能夠訪問到的區(qū)域 例如我們設(shè)置一個var n=0;實際分兩步篮灼,聲明和賦值忘古,var n;n=1诅诱;在js中只有函數(shù)能夠限定作用域髓堪,除此之外聲明的都是全局作用域,在ES6的新標(biāo)準(zhǔn)中娘荡,采用了let和const來限定局部變量干旁,注意局部變量的優(yōu)先級是高于全局變量的

執(zhí)行環(huán)境是JavaScript中最為重要的一個概念,在js代碼開始執(zhí)行的時候炮沐,會創(chuàng)建一個匿名函數(shù)的執(zhí)行環(huán)境争群,執(zhí)行環(huán)境定義了變量或函數(shù)有權(quán)訪問的其它數(shù)據(jù),每個執(zhí)行環(huán)境都有一個與之相關(guān)的變量對象央拖,環(huán)境中定義的所有變量和函數(shù)都保存在這個對象中,雖然我們編寫的代碼無法訪問這個對象鹉戚,但解析器咋及處理數(shù)據(jù)的時候會在后臺使用它

變量的作用域是在定義的時候決定的而不是在執(zhí)行的時候決定的鲜戒,變量只能在本層作用域和上層作用域生效,不能在下層作用域生效

舉一個栗子來加深理解

    var num;
    function f1(){
        num=3;
    }
    f1();
    console.log(num);//3

在這個栗子中抹凳,num的值在全局最開始設(shè)置的是undefined遏餐,但是在函數(shù)f1中改變了該值,這個值變?yōu)?赢底,在函數(shù)f1執(zhí)行了一次之后我們再打印num的值失都,得到的是num=3,通過解讀JavaScript是詞法作用域我們可以知道幸冻,num這個變量的作用域是在它定義的時候決定了它是一個全局變量粹庞,所以即使在函數(shù)中執(zhí)行num的操作,我們在函數(shù)外部依然可以獲取到這個值

注意一個細(xì)節(jié)洽损,獲取全局變量比獲取局變量要耗費性能庞溜,舉個例子:

    function (obj){
        var i=0,
            l=obj.length;
        for(;i<l;i++){}
    }

在這段代碼里,我們獲取設(shè)置局部變量obj.length用來進(jìn)行循環(huán)條件判斷碑定,使用i<l是一個比i<obj.length更佳的選擇流码,因為如果obj包含大量的代碼,我們在循環(huán)時每一次都要重新獲取obj的值延刘,而設(shè)置l則可以降低這部分的性能損耗

閉包

在JS中只有function能形成塊級作用域漫试,在函數(shù)內(nèi)部的變量外部是無法獲取的,而在函數(shù)中嵌套函數(shù)碘赖,被嵌套的函數(shù)則可以拿到外層函數(shù)的變量的值驾荣,所以有的時候我們需要拿到一個函數(shù)的內(nèi)部數(shù)據(jù)時外构,可以采用如下的方法:

    function A(){
        var name="tom"

        function B(){

            return name
        }

        return B();

    }

    console.log(A());//tom

以上的代碼就是我們常說的閉包,官方的閉包的解釋十分的概念化秘车,我對閉包的理解就是:

  1. 初步的理解:能夠獲取其他函數(shù)的內(nèi)部數(shù)據(jù)的函數(shù)
  2. 深入的理解:函數(shù)記住并訪問其所在的詞法作用域典勇,叫作閉包現(xiàn)象,而此時函數(shù)對作用域的引用叫作閉包(閉包就是引用叮趴,維基上有一段對閉包的引用解釋的我覺的是比較好:引用了自由變量的函數(shù)割笙,自由變量和函數(shù)將一同存在),如果想要理解這一塊眯亦,我們需要對JavaScript中的垃圾回收機(jī)制JavaScript的存儲機(jī)制做一些了解
JavaScript的垃圾回收機(jī)制

垃圾回收的主要工作是跟蹤內(nèi)存的分配和使用伤溉,在內(nèi)存不再工作時,將其進(jìn)行釋放

在垃圾回收機(jī)制中妻率,涉及到的算法比較多乱顾,所以也不打算深入,只做簡單的了解即可宫静,我們只需要知道:

在js中走净,在創(chuàng)建變量或函數(shù)時,會開辟空間孤里,有一個屬性來標(biāo)注它們是否被其它變量伏伯,被引用則計數(shù)器++,

  1. 如果函數(shù)被調(diào)用過了捌袜,并且以后不會再用到说搅,此時判斷計數(shù)器為0,那么垃圾回收機(jī)制就會將其作用域進(jìn)行銷毀
  2. 全局變量是不會被銷毀的
JavaScript的存儲機(jī)制

我們都知道虏等,在JS的存儲中是分為堆存儲和棧存儲的弄唧,堆存儲是用來存儲對象,也就是引用數(shù)據(jù)類型霍衫,而棧存儲是用來存儲簡單數(shù)據(jù)類型候引,在這里我們需要了解的是:

  1. 對象的引用是通過地址來傳遞的,一個對象應(yīng)用另一個對象后只是它的地址指向發(fā)生了改變敦跌,而它原來的值是依然存在的
  2. 簡單數(shù)據(jù)類型的引用是直接在棧中把原先數(shù)據(jù)進(jìn)行替換的

了解了以上兩個概念我們可以再回頭看一下關(guān)于深入的理解閉包背伴,如果一個函數(shù)被引用,那么它的作用域就不會被垃圾回收機(jī)制進(jìn)行銷毀峰髓,這就可以理解為記住了這個作用域傻寂,如果對其內(nèi)部的變量進(jìn)行訪問,那么稱為訪問

<script type="text/javascript" language="javascript">
    function a(){
        var i=0;
        return function b() {
            alert(++i);//記住并訪問
        }
    }
    c=a();//引用
    c();
</script>

由于比包內(nèi)的函數(shù)對外部函數(shù)的變量進(jìn)行了引用携兵,在垃圾回收機(jī)制看來疾掰,引用計數(shù)不為0,所以在函數(shù)的作用域被銷毀后徐紧,閉包內(nèi)的變量會一直存在静檬,這也是閉包的缺點炭懊,大量的閉包引用如果在引用后沒有賦值為null會占用大量的內(nèi)存空間,在IE低版本中會導(dǎo)致內(nèi)存泄漏

閉包還有一個特性是閉包只能氣的包含函數(shù)中任何變量的最后一個值拂檩,例如一些常見的面試題中經(jīng)常出現(xiàn)這個問題

    function person(){

        var arr=[];

        for(var i=0;i<10;i++){
           arr[i]=function (){
                return i;
            };
        };
        return arr;
    }
    
    console.log(person()[1]());//10

在上面的代碼中arr的每個位置存儲的都是10侮腹,如果我們想要使每個返回不同的數(shù)字那么我們需要對代碼進(jìn)行改進(jìn)

    function person(){

        var arr=[];

        for(var i=0;i<10;i++){
           arr[i]=function (num){
                return function(){//再次添加一個閉包,在這個閉包內(nèi)將每次i的值進(jìn)行存儲
                    return num;
                };
            }(i)
        };
        return arr;
    }
    console.log(person()[1]());//10

閉包的用途

  1. 獲取其它函數(shù)內(nèi)部的變量的值

  2. 緩存變量的值

        function A(){
            var i=1;
            return function(){
                i++;
                console.log(i);
            }
        }
        var a=A();
        a();
        a();
        a();
      //這里我們要探討一個問題稻励,閉包緩存數(shù)據(jù)的形式是什么父阻,之前一直對閉包的概念有誤解,一直以為A事必報望抽,但是實際上
      // return function(){
      //          i++;
      //          console.log(i);
      //     }
      //這部分才是真正的閉包加矛,所以能夠進(jìn)行緩存的是這一部分,這也就解決了之前的一個疑惑煤篙,為什么需要寫
      // var a=A();這一步再調(diào)用a()斟览;才能看到閉包緩存的數(shù)據(jù)
      //我們可以根據(jù)這個特性座椅計時器,來記錄一個構(gòu)造函數(shù)創(chuàng)建了多少個對象
       var C=function(){
    
            var i=0;
    
            return function(){
    
                return ++i
            }
        }();//讓函數(shù)自調(diào)用自身
    
        function fn(name){
    
            if(this==window){//判斷當(dāng)前調(diào)用的是不是window對象
                return 
            }
    
            this.name=name;
    
            fn.cn=C();//注意辑奈,在這里實際上已經(jīng)相當(dāng)于直接使用++i為fn.cn賦值了
        }
    
        var a=new fn("tom");
        var b=new fn("son");
        var c=new fn("xm");
    
        console.log(fn.cn)//3
    
  3. 設(shè)置變量的可讀可寫(封裝)

        function A(_name){
            var name=_name;
    
            return {
                getName:function(){//可讀
                    return name;
                },
                setName:function(newName){//可寫
                    name=newName; 
                }   
    
            }
        }
    
        var a=new A("tom");
        console.log(a.getName())//tom
        a.setName("xm");
        console.log(a.getName())//xm
      
      //還有一個寫法
      
        function A(_name,_age){
            var name=_name,
                age=_age;
    
            return {
                name:function(value){
                    if(value===undefined) return name;
                    else name=value;
                },
                age:function(value){
                    if(value===undefined) return age;
                    else age=value;
                }
            } 
        }
        
        var a=new A("tom",18);
        console.log(a.name());//tom 
        a.name("xm");
        console.log(a.name());//xm
    
  4. 沙箱模式

    (function(){})();//防止代碼污染
    //這里我們需要提到一個概念苛茂,也就是JS中的依賴注入,和AngularJS中的類似的概念鸠窗,JS中的依賴注入也就是表示妓羊,在JS中如果需要調(diào)用某種我們已經(jīng)封裝好的,或者外部的JS庫塌鸯,那么我們可以提前聲明
    1.在JS中侍瑟,如果讓JS自己解析唐片,尋找我們定義的變量丙猬,這個過程的效率很低,無形中會損耗一部分性能费韭,如果我們以實參的方式傳入茧球,那么會減少這部分額性能損耗
    (function(window){
    
      })(window)
    2.提前聲明我們使用了什么庫,便于代碼的維護(hù)和可讀性星持,保障代碼的健壯性
    (function($){
    
      })($)
    
  5. 提前返回

      var addEventlistner=function (){
       if(window.addEventlistner){
    
        return function(elem,type,calback,bool){
    
          elem.addEventlistner("type",calback抢埋,bool);
        };
      }else{
          return function(elem,type,calback){
    
            elem.attachEvent("on"+type, calback)
        };
      }
    }();
     //在之前我們進(jìn)行能力檢測的時候,每次調(diào)用都會進(jìn)行if..else..的判斷督暂,其實在我們進(jìn)入瀏覽器的時候揪垄,瀏覽器的能力已經(jīng)確定了,所以我們可以通過利用閉包的提前返回的特性逻翁,把能力檢測的結(jié)果保存下來饥努,不需要每次都進(jìn)行判斷
    
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市八回,隨后出現(xiàn)的幾起案子酷愧,更是在濱河造成了極大的恐慌驾诈,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件溶浴,死亡現(xiàn)場離奇詭異乍迄,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)士败,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進(jìn)店門闯两,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人拱烁,你說我怎么就攤上這事生蚁。” “怎么了戏自?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵邦投,是天一觀的道長。 經(jīng)常有香客問我擅笔,道長志衣,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任猛们,我火速辦了婚禮念脯,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘弯淘。我一直安慰自己绿店,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布庐橙。 她就那樣靜靜地躺著假勿,像睡著了一般。 火紅的嫁衣襯著肌膚如雪态鳖。 梳的紋絲不亂的頭發(fā)上转培,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天,我揣著相機(jī)與錄音浆竭,去河邊找鬼浸须。 笑死,一個胖子當(dāng)著我的面吹牛邦泄,可吹牛的內(nèi)容都是我干的删窒。 我是一名探鬼主播,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼顺囊,長吁一口氣:“原來是場噩夢啊……” “哼肌索!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起包蓝,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤驶社,失蹤者是張志新(化名)和其女友劉穎企量,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體亡电,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡届巩,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了份乒。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片恕汇。...
    茶點故事閱讀 39,841評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖或辖,靈堂內(nèi)的尸體忽然破棺而出瘾英,到底是詐尸還是另有隱情,我是刑警寧澤颂暇,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布缺谴,位于F島的核電站,受9級特大地震影響耳鸯,放射性物質(zhì)發(fā)生泄漏湿蛔。R本人自食惡果不足惜堡妒,卻給世界環(huán)境...
    茶點故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一呼股、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧刷喜,春花似錦财喳、人聲如沸察迟。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽扎瓶。三九已至,卻和暖如春祝高,著一層夾襖步出監(jiān)牢的瞬間栗弟,已是汗流浹背污筷。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工工闺, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人瓣蛀。 一個月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓陆蟆,卻偏偏與公主長得像,于是被迫代替她去往敵國和親惋增。 傳聞我的和親對象是個殘疾皇子叠殷,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,781評論 2 354

推薦閱讀更多精彩內(nèi)容