JavaScript 閉包系列一

一. 閉包的概念 

閉包是有權訪問另外一個函數做用域中的變量的函數javascript

以下代碼:根據變量做用域,函數outer中全部的局部變量對函數inner都是可見的。可是反過來不行,inner內部的局部變量對outer是不可見的。這就是JavaScript語言特有的鏈式做用域結構(chain scope),子對象會一級一級向上尋找全部父對象的變量。因此父對象的全部變量對子對象都是可見的,反之則不成立。html

function outer() {
     var i = 100;
     function inner() {
        console.log(i);    //100
    }
    inner();
}
outer();

既然函數inner能讀取函數outer的局部變量,那麼只要將inner做爲返回值,就能夠直接在outer外部讀取它內部的變量。java

function outer() {
     var i = 100;
     function inner() {
        console.log(i);
    }
     return inner;
}
var res = outer();
res();    // 100

這個函數有兩個特色:數組

1)函數inner嵌套在函數outer內部閉包

2)函數outer返回函數inner函數

執行完var rs = outer()後,實際rs指向函數inner。這段代碼其實就是一個閉包。也就是說當函數outer內的函數inner函數被函數outer外的一個變量引用的時候,就建立了一個閉包。this

 

二. 閉包的做用

function outer() {
     var i = 100;
     function inner() {
        console.log(i++);
    }
     return inner;
}
var rs = outer();
rs();    // 100
rs();    // 101
rs();    // 102

上面的代碼中,rs是閉包inner函數。rs共運行了3次,第一次100,第二次101,第三次102,這說明函數outer中的局部變量i一直保存在內存中,並無在調用後清除。spa

閉包的做用:在outer執行完畢並返回後,閉包是JavaScript的垃圾回收機制(garbage collection)不會回收outer所佔的內存,由於outer函數內部的inner函數執行要依賴outer中的變量。(另外一種解釋:outer是inner的父函數,inner被賦給了一個全局變量,致使inner會一直在內存中,而inner的存在依賴於outer,所以outer也一直存在於內存中,不會在調用結束後被垃圾回收機制(garbage collection)回收。) code

由上述可得知:htm

1)閉包有權訪問函數內部的全部變量;

2)當函數返回一個閉包時,這個函數的做用域會一直在內存中保存直到閉包不存在爲止。 

 

三. 閉包與變量 

做用鏈機制,閉包容許內層函數引用父函數中的變量,但該變量是最終值

var f =  function() {
     var rs = [];
     for( var i=0; i<10; i++) {
        rs[i] =  function() {
             return i;
        }
    }
     return rs;
};
var fn = f();
for( var i=0; i<fn.length; i++) {
    console.log("函數fn(" + i + ")返回值是:" + fn[i]());
}

上述代碼執行後,fn爲一個數組,表面上看,每一個函數執行後應該返回本身的索引值,但實際上,每一個函數都返回10。fn數組中每一個函數的做用域鏈上都保存着函數f的活動對象,它們引用的是同一變量i。當函數f返回後,i的值爲10。

強制建立另外一個閉包讓函數的行爲符合預期,以下代碼所示。

var f =  function() {
     var rs = [];
     for( var i=0; i<10; i++) {
        rs[i] = ( function(num) {
             return  function() {
                 return num;
            }
        })(i);
    }
     return rs;
};
var fn = f();
for( var i=0; i<fn.length; i++) {
    console.log("函數fn(" + i + ")返回值是:" + fn[i]());
}

這個版本中,沒有直接將閉包賦值給數組,而是定義了一個匿名函數,並將當即執行匿名函數的結果賦值給數組。這裏的匿名函數有一個參數num,在調用每一個函數時,傳入變量i,因爲參數是按值傳遞的,所以將變量i複製給參數num。而在這個匿名函數內部又建立並返回了一個訪問num的閉包,所以rs數組中每一個函數包含本身的num變量,所以就能夠返回不一樣的數值。

 

四. 閉包中this對象

var name = "Jack";
var o = {
    name: "Ting",
    getName:  function() {
         return  function() {
             return  this.name;
        }
    }
};
console.log(o.getName()());    // Jack

var name = "Jack";
var o = {
    name: "Ting",
    getName:  function() {
         var self =  this;
         return  function() {
             return self.name;
        }
    }
};
console.log(o.getName()());    // Ting

 

五. 內存泄露

以下代碼:建立了做爲el元素事件處理程序的閉包,而這個閉包又建立了循環引用,只要匿名函數存在,el的引用數至少爲1,所以它所佔用的內存就永遠不會被回收。

function assignHandler() {
     var el = document.getElementById('demo');
    el.onclick =  function() {
        console.log(el.id);
    }
}
assignHandler();

以下代碼:把變量el設置爲null,可以解除對DOM對象的引用,確保正常回收其佔用內存。

function assignHandler() {
     var el = document.getElementById('demo');
     var id = el.id;
    el.onclick =  function() {
        console.log(id);
    }
    el =  null;
}
assignHandler();

 

六. 模仿塊級做用域

任何一對花括號中的語句集都屬於一個塊,在這之中定義的全部變量在代碼塊外都是不可見的,稱之爲塊級做用域。

( function() {
     // 塊級做用域
})();


七. 閉包的應用

1)讀取函數內部的變量

如上述例子中, 函數inner可以訪問函數outer中的變量,將函數inner做爲返回值,就能直接在outer外部讀取它的變量。

2)在內存中維持變量

如上述例子中,因爲閉包,函數outer一直存在於內存中,所以每次執行rs(),都會給i加1。

 

 

 

時間:2014-10-23

地點:合肥

引用:http://wlog.cn/javascript/javascript-closures.html

相關文章
相關標籤/搜索