天天看點

關于閉包了解

對閉包的了解:

閉包是由函數以及建立該函數的詞法環境組成,形式上看就是函數内的子函數。

因為js有自動的垃圾回收機制,當變量不被引用時就會通過特殊的算法回收(一般是标記清除和引用計數兩種方法),當一個函數執行完後作用域會被銷毀,而閉包可以通路父級作用域,父級函數中的變量在閉包中被引用,那麼就不會被垃圾回收;是以閉包可以實作在函數外部通路内部的局部變量,并且可以使讓這些變量的值始終保持在記憶體中。

從記憶體來看閉包:

當JS引擎判斷形成閉包,就會在堆記憶體中開辟一塊空間,裡面有一個閉包對象closure,這個對象裡包含閉包用到的外部變量,之後要用這些變量的時候,就會去到堆裡面的closure對象裡面找。

關于閉包了解

了解練習:

例一:

var x = 10;

function fn(){

console.log(x);

}

function show(f){

var x = 20;       //因為閉包是由函數及建立該函數的詞法環境組成,而fn建立的詞法環境中x=10

(function(){

f();

})()                       

}

show(fn); //10

例二:

function a() {
    var i = 0;
    function b() {
        alert(++i);
    }
    return b;
}
var c = a();
c(); //1   i=0,第一次調用c 執行++i,輸出1
c(); //2   因為是閉包,i變量在b()中引用不會在c()執行完後釋放記憶體,是以再次c(),++i就是1+1=2
      

例三:

var add = function(x) {
    var sum = 1;
    var tmp = function(x) {
        sum = sum + x;
        return tmp;
    }
    tmp.toString = function() {
        return sum;
    }
    return tmp;
    }
alert(add(1)(2)(3))    // 6
      
第一次調用傳入了參數1,沒有使用,此時sum的值是1;然後傳回tmp函數
      
第二次調用tmp函數傳入 2,sum的值是1+2=3;
      
第三次重複調用tmp函數傳入3,sum的值是3+3=6;隐式自動調用tmp的toString()彈出6
      

例四:

var i = 0;

function outerFn(){

 function innerFn(){

  i++;

  console.log(i);

  }

 return innerFn

}

var inner1 = outerFn();

var inner2 = outerFn();

inner1();//1

inner2();//1

inner1();//2

inner1();//3

inner2();//2

inner2();//3

因為閉包找到的是同一位址中父級函數中對應變量最終的值。

例五:

function fun(){

   //1. 找受保護的變量: 外層函數的局部變量

   var n=999;

   //2. 外層函數共抛出哪些内層函數

   nAdd=function(){n++};

   return function(){ console.log(n) };

  }

  var get=fun();//fun的AO(n=999)

  //var nAdd: function(){n++}

  //get: function(){ console.log(n) };

  get();//999

  nAdd();//AO(n=1000)

  get();//1000

因為nAdd沒有被定義過,強行指派會使JS将nAdd定義為全局變量,是以他也會被傳回到外部,故也将執行

例六:

for(var i = 0;i < 5;i++){

    setTimeout(function (){

        console.log(i++);

    },4000)

}

console.log(i);

for循環并不是一個函數,是以沒有函數作用域,var聲明的變量也不存在塊級作用域,是以i就是個全局變量。

順序執行,先執行for,setTimeout異步宏任務放任務隊列等待執行

當for循環完i=5,執行console.log(i)輸出5

然後執行任務隊列的五個setTimeout宏任務,因為是閉包i的值儲存不被釋放,是以接下來輸出5,6,7,8,9

正常實作從0增加輸出:

1.立即執行函數,每一次都會建立函數執行環境得到對應的結果

for(var i = 0;i < 5;i++){

            (function (x) {

                setTimeout(function (){

                    console.log(x++);

                },4000)

            })(i);

        }   

2.setTimeout 的第三個參數

for ( var i=1; i<=5; i++) {

    setTimeout( function timer(j) {

        console.log( j );

    }, i*1000, i);

}

3.使用 let 定義 i 

for ( let i=1; i<=5; i++) {

    setTimeout( function timer() {

        console.log( i );

    }, i*1000 );

}

記憶體溢出和記憶體洩漏:

記憶體溢出:當程式運作需要的記憶體超過了剩餘的記憶體時, 就出抛出記憶體溢出的錯誤

記憶體洩露:占用的記憶體沒有及時釋放

常見的記憶體洩漏:

1.沒有及時清理的計時器或回調函數

原因:定時器中有dom的引用,即使dom删除了,但是定時器還在,是以記憶體中還是有這個dom。

解決:手動删除定時器和dom。

2.意外的全局變量。

原因:全局變量,不會被回收。

解決:使用嚴格模式避免。

function fn () {
  b = new Array[1000000]
  a = [] //不小心沒有var定義,這時候a變量是全局的
}
fn()      
function foo() {
    this.variable = "potential accidental global";
}
// foo 調用自己,this 指向了全局對象(window)
foo();
           

3.閉包,函數執行完後, 函數内的局部變量沒有釋放, 占用記憶體時間會變長,容易造成記憶體洩露。

原因:閉包可以維持函數内局部變量,使其得不到釋放。

解決:将事件處理函數定義在外部,解除閉包,或者在定義事件處理函數的外部函數中,删除對dom的引用。

4.dom清空或删除時,事件未清除導緻的記憶體洩漏。

原因:雖然别的地方删除了,但是對象中還存在對dom的引用

解決:手動删除。

關于閉包了解

5.子元素存在引用引起的記憶體洩漏

原因:div中的ul li  得到這個div,會間接引用某個得到的li,那麼此時因為div間接引用li,即使li被清空,也還是在記憶體中,并且隻要li不被删除,他的父元素都不會被删除。

解決:手動删除清空。

怎樣避免記憶體洩露:

1)減少不必要的全局變量,或者生命周期較長的對象,及時對無用的資料進行垃圾回收;

2)注意程式邏輯,避免“死循環”之類的 ;

3)避免建立過多的對象 原則:不用了的東西要及時歸還。

關于垃圾回收機制:

垃圾收集器會定期(周期性)找出那些不在繼續使用的變量,然後釋放其記憶體。

通常情況下有兩種實作方式:标記清除,引用計數。

标記清除:

工作原理:是當變量進入環境時,将這個變量标記為“進入環境”。當變量離開環境時,則将其标記為“離開環境”。标記“離開環境”的就回收記憶體。

工作流程:

1.    垃圾回收器,在運作的時候會給存儲在記憶體中的所有變量都加上标記。

2.    去掉環境中的變量以及被環境中的變量引用的變量的标記。

3.    再被加上标記的會被視為準備删除的變量。

4.    垃圾回收器完成記憶體清除工作,銷毀那些帶标記的值并回收他們所占用的記憶體空間。

引用計數:

工作原理:跟蹤記錄每個值被引用的次數。

工作流程:

1.    聲明了一個變量并将一個引用類型的值指派給這個變量,這個引用類型值的引用次數就是1。

2.    同一個值又被指派給另一個變量,這個引用類型值的引用次數加1.

3.    當包含這個引用類型值的變量又被指派成另一個值了,那麼這個引用類型值的引用次數減1.

4.    當引用次數變成0時,說明沒辦法通路這個值了。

5.    當垃圾收集器下一次運作時,它就會釋放引用次數是0的值所占的記憶體。

繼續閱讀