1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        js--閉包與垃圾回收機(jī)制

        共 12275字,需瀏覽 25分鐘

         ·

        2021-03-03 10:59

        點(diǎn)擊上方藍(lán)色字體,選擇“標(biāo)星公眾號(hào)”

        優(yōu)質(zhì)文章,第一時(shí)間送達(dá)

          作者 |  丶Serendipity丶

        來(lái)源 |  urlify.cn/Jj2EJz

        76套java從入門到精通實(shí)戰(zhàn)課程分享

        前言

          閉包和垃圾回收機(jī)制常常作為前端學(xué)習(xí)開(kāi)發(fā)中的難點(diǎn),也經(jīng)常在面試中遇到這樣的問(wèn)題,本文記錄一下在學(xué)習(xí)工作中關(guān)于這方面的筆記。

        正文

         1.閉包

          閉包(closure)是Javascript語(yǔ)言的一個(gè)難點(diǎn),也是它的特色,很多高級(jí)應(yīng)用都要依靠閉包實(shí)現(xiàn)。作為一個(gè)JavaScript開(kāi)發(fā)者,理解閉包十分重要。

          1.1閉包是什么?

          閉包就是一個(gè)函數(shù)引用另一個(gè)函數(shù)的變量,內(nèi)部函數(shù)被返回到外部并保存時(shí)產(chǎn)生,(內(nèi)部函數(shù)的作用域鏈AO使用了外層函數(shù)的AO)

               因?yàn)樽兞勘灰弥圆粫?huì)被回收,因此可以用來(lái)封裝一個(gè)私有變量,但是不必要的閉包只會(huì)增加內(nèi)存消耗。

          閉包是一種保護(hù)私有變量的機(jī)制,在函數(shù)執(zhí)行時(shí)形成私有的作用域,保護(hù)里面的私有變量不受外界干擾?;蛘哒f(shuō)閉包就是子函數(shù)可以使用父函數(shù)的局部變量,還有父函數(shù)的參數(shù)。

          1.2閉包的特性

           ①函數(shù)嵌套函數(shù)

         ?、诤瘮?shù)內(nèi)部可以引用函數(shù)外部的參數(shù)和變量

         ?、蹍?shù)和變量不會(huì)被垃圾回收機(jī)制回收

          1.3理解閉包

          基于我們所熟悉的作用域鏈相關(guān)知識(shí),我們來(lái)看下關(guān)于計(jì)數(shù)器的問(wèn)題,如何實(shí)現(xiàn)一個(gè)函數(shù),每次調(diào)用該函數(shù)時(shí)候計(jì)數(shù)器加一。

        var counter=0;
            function demo3(){
                console.log(counter+=1);       
            }
            demo3();//1
            demo3();//2
            var counter=5;
            demo3();  //6

          上面的方法,如果在任何一個(gè)地方改變counter的值 計(jì)數(shù)器都會(huì)失效,javascript解決這種問(wèn)題用到閉包,就是函數(shù)內(nèi)部?jī)?nèi)嵌函數(shù),再來(lái)看下利用閉包如何實(shí)現(xiàn)。

        function add() {
                    var counter = 0;
                    return function plus() {
                        counter += 1;
                        return counter
                    }      
                }
                var count=add()
                console.log(count())//1
                var counter=100
                console.log(count())//2

          上面就是一個(gè)閉包使用的實(shí)例 ,函數(shù)add內(nèi)部?jī)?nèi)嵌一個(gè)plus函數(shù),count變量引用該返回的函數(shù),每次外部函數(shù)add執(zhí)行的時(shí)候都會(huì)開(kāi)辟一塊內(nèi)存空間,外部函數(shù)的地址不同,都會(huì)重新創(chuàng)建一個(gè)新的地址,把plus函數(shù)嵌套在add函數(shù)內(nèi)部,這樣就產(chǎn)生了counter這個(gè)局部變量,內(nèi)次調(diào)用count函數(shù),該局部變量值加一,從而實(shí)現(xiàn)了真正的計(jì)數(shù)器問(wèn)題。

          1.4閉包的主要實(shí)現(xiàn)形式

          這里主要通過(guò)兩種形式來(lái)學(xué)習(xí)閉包:

         ?、俸瘮?shù)作為返回值,也就是上面的例子中用到的。

        function showName(){
                        var name="xiaoming"
                        return function(){
                            return name
                        }
                    }
                    var name1=showName()
                    console.log(name1())

          閉包就是能夠讀取其他函數(shù)內(nèi)部變量的函數(shù)。閉包就是將函數(shù)內(nèi)部和函數(shù)外部連接起來(lái)的一座橋梁。

         ?、陂]包作為參數(shù)傳遞

        var num = 15
                    var foo = function(x){
                        if(x>num){
                            console.log(x)
                        }  
                    }
                    function foo2(fnc){
                        var num=30
                        fnc(25)
                    }
                    foo2(foo)//25

        上面這段代碼中,函數(shù)foo作為參數(shù)傳入到函數(shù)foo2中,在執(zhí)行foo2的時(shí)候,25作為參數(shù)傳入foo中,這時(shí)判斷的x>num的num取值是創(chuàng)建函數(shù)的作用域中的num,即全局的num,而不是foo2內(nèi)部的num,因此打印出了25。

          1.5閉包的優(yōu)缺點(diǎn)

          優(yōu)點(diǎn):

          ①保護(hù)函數(shù)內(nèi)的變量安全 ,實(shí)現(xiàn)封裝,防止變量流入其他環(huán)境發(fā)生命名沖突

         ?、谠趦?nèi)存中維持一個(gè)變量,可以做緩存(但使用多了同時(shí)也是一項(xiàng)缺點(diǎn),消耗內(nèi)存)

         ?、勰涿詧?zhí)行函數(shù)可以減少內(nèi)存消耗

          缺點(diǎn):

         ?、倨渲幸稽c(diǎn)上面已經(jīng)有體現(xiàn)了,就是被引用的私有變量不能被銷毀,增大了內(nèi)存消耗,造成內(nèi)存泄漏,解決方法是可以在使用完變量后手動(dòng)為它賦值為null;

         ?、谄浯斡捎陂]包涉及跨域訪問(wèn),所以會(huì)導(dǎo)致性能損失,我們可以通過(guò)把跨作用域變量存儲(chǔ)在局部變量中,然后直接訪問(wèn)局部變量,來(lái)減輕對(duì)執(zhí)行速度的影響。

          1.6閉包的使用

        for (var i = 0; i < 5; i++) {
              setTimeout(function() {
                  console.log( i);
              }, 1000);
          }

          console.log(i);

          我們來(lái)看上面的問(wèn)題,這是一道很常見(jiàn)的題,可這道題會(huì)輸出什么,一般人都知道輸出結(jié)果是 5,5,5,5,5,5,你仔細(xì)觀察可能會(huì)發(fā)現(xiàn)這道題還有很多巧妙之處,這6個(gè)5的輸出順序具體是怎樣的?5 -> 5,5,5,5,5 ,了解同步異步的人也不難理解這種情況,基于上面的問(wèn)題,接下來(lái)思考如何實(shí)現(xiàn)5 -> 0,1,2,3,4這樣的順序輸出呢?

        for (var i = 0; i < 5; i++) {
                (function(j) {  // j = i
                    setTimeout(function() {
                        console.log( j);
                    }, 1000);
                })(i);
            }
            console.log( i);
        //5 -> 0,1,2,3,4

          這樣在for循環(huán)種加入匿名函數(shù),匿名函數(shù)入?yún)⑹敲看蔚膇的值,在同步函數(shù)輸出5的一秒之后,繼續(xù)輸出01234。

        for (var i = 0; i < 5; i++) {
                setTimeout(function(j) {
                    console.log(j);
                }, 1000, i);
            }
            console.log( i);
            //5 -> 0,1,2,3,4

          仔細(xì)查看setTimeout的api你會(huì)發(fā)現(xiàn)它還有第三個(gè)參數(shù),這樣就省去了通過(guò)匿名函數(shù)傳入i的問(wèn)題。

        var output = function (i) {
              setTimeout(function() {
                  console.log(i);
              }, 1000);
          };

          for (var i = 0; i < 5; i++) {
              output(i);  // 這里傳過(guò)去的 i 值被復(fù)制了
          }

          console.log(i);
          //5 -> 0,1,2,3,4

          這里就是利用閉包將函數(shù)表達(dá)式作為參數(shù)傳遞到for循環(huán)中,同樣實(shí)現(xiàn)了上述效果。

        for (let i = 0; i < 5; i++) {
              setTimeout(function() {
                  console.log(new Date, i);
              }, 1000);
          }
          console.log(new Date, i);
          //5 -> 0,1,2,3,4

          知道let塊級(jí)作用域的人會(huì)想到上面的方法。但是如果要實(shí)現(xiàn)0 -> 1 -> 2 -> 3 -> 4 -> 5這樣的效果呢。

        for (var i = 0; i < 5; i++) {
              (function(j) {
                  setTimeout(function() {
                      console.log(new Date, j);
                  }, 1000 * j);  // 這里修改 0~4 的定時(shí)器時(shí)間
              })(i);
          }

          setTimeout(function() { // 這里增加定時(shí)器,超時(shí)設(shè)置為 5 秒
              console.log(new Date, i);
          }, 1000 * i);
          //0 -> 1 -> 2 -> 3 -> 4 -> 5

          還有下面的代碼,通過(guò)promise來(lái)實(shí)現(xiàn)。

        const tasks = [];
          for (var i = 0; i < 5; i++) {   // 這里 i 的聲明不能改成 let,如果要改該怎么做?
              ((j) => {
                  tasks.push(new Promise((resolve) => {
                      setTimeout(() => {
                          console.log(new Date, j);
                          resolve();  // 這里一定要 resolve,否則代碼不會(huì)按預(yù)期 work
                      }, 1000 * j);   // 定時(shí)器的超時(shí)時(shí)間逐步增加
                  }));
              })(i);
          }

          Promise.all(tasks).then(() => {
              setTimeout(() => {
                  console.log(new Date, i);
              }, 1000);   // 注意這里只需要把超時(shí)設(shè)置為 1 秒
          });
          //0 -> 1 -> 2 -> 3 -> 4 -> 5
        const tasks = []; // 這里存放異步操作的 Promise
          const output = (i) => new Promise((resolve) => {
              setTimeout(() => {
                  console.log(new Date, i);
                  resolve();
              }, 1000 * i);
          });

          // 生成全部的異步操作
          for (var i = 0; i < 5; i++) {
              tasks.push(output(i));
          }

          // 異步操作完成之后,輸出最后的 i
          Promise.all(tasks).then(() => {
              setTimeout(() => {
                  console.log(new Date, i);
              }, 1000);
          });
          //0 -> 1 -> 2 -> 3 -> 4 -> 5
        // 模擬其他語(yǔ)言中的 sleep,實(shí)際上可以是任何異步操作
        const sleep = (timeountMS) => new Promise((resolve) => {
            setTimeout(resolve, timeountMS);
        });

        (async () => {  // 聲明即執(zhí)行的 async 函數(shù)表達(dá)式
            for (var i = 0; i < 5; i++) {
                if (i > 0) {
                    await sleep(1000);
                }
                console.log(new Date, i);
            }

            await sleep(1000);
            console.log(new Date, i);
        })();
        //0 -> 1 -> 2 -> 3 -> 4 -> 5

          上面的代碼中都用到了閉包,總之,閉包找到的是同一地址中父級(jí)函數(shù)中對(duì)應(yīng)變量最終的值。

          2.垃圾回收機(jī)制

          JavaScript 中的內(nèi)存管理是自動(dòng)執(zhí)行的,而且是不可見(jiàn)的。我們創(chuàng)建基本類型、對(duì)象、函數(shù)……所有這些都需要內(nèi)存。

          通常用采用的垃圾回收有兩種方法:標(biāo)記清除、引用計(jì)數(shù)。

          1、標(biāo)記清除

          垃圾收集器在運(yùn)行的時(shí)候會(huì)給存儲(chǔ)在內(nèi)存中的所有變量都加上標(biāo)記。然后,它會(huì)去掉環(huán)境中的變量以及被環(huán)境中的變量引用的標(biāo)記。

          而在此之后再被加上標(biāo)記的變量將被視為準(zhǔn)備刪除的變量,原因是環(huán)境中的變量已經(jīng)無(wú)法訪問(wèn)到這些變量了。

          最后。垃圾收集器完成內(nèi)存清除工作,銷毀那些帶標(biāo)記的值,并回收他們所占用的內(nèi)存空間

          2.引用計(jì)數(shù)

          引用計(jì)數(shù)的含義是跟蹤記錄每個(gè)值被引用的次數(shù)。當(dāng)聲明了一個(gè)變量并將一個(gè)引用類型賦值給該變量時(shí),則這個(gè)值的引用次數(shù)就是1。

          相反,如果包含對(duì)這個(gè)值引用的變量又取得了另外一個(gè)值,則這個(gè)值的引用次數(shù)就減1。當(dāng)這個(gè)引用次數(shù)變成0時(shí),

          則說(shuō)明沒(méi)有辦法再訪問(wèn)這個(gè)值了,因而就可以將其所占的內(nèi)存空間給收回來(lái)。這樣,垃圾收集器下次再運(yùn)行時(shí),

          它就會(huì)釋放那些引用次數(shù)為0的值所占的內(nèi)存。

         

        總結(jié)

          以上就是本文的全部?jī)?nèi)容,希望給讀者帶來(lái)些許的幫助和進(jìn)步,方便的話點(diǎn)個(gè)關(guān)注,小白的成長(zhǎng)之路會(huì)持續(xù)更新一些工作中常見(jiàn)的問(wèn)題和技術(shù)點(diǎn)。

         








        粉絲福利:Java從入門到入土學(xué)習(xí)路線圖

        ??????

        ??長(zhǎng)按上方微信二維碼 2 秒


        感謝點(diǎn)贊支持下哈 

        瀏覽 45
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            欧美精品一区二区三区高清在线 | 探花无码| 欧美最爽乱淫视频免费观看 | 性xxxx欧美老肥妇牲乱 | 中文字幕无码专区 | 逼com| 久久强奸视频 | 欧美自拍第一页 | 玩弄少妇一级艳片 | 国产91精品一区二区三区竹菊影视 |