Javascript 閉包

閉包的作用域鏈

閉包是有權(quán)訪(fǎng)問(wèn)另一個(gè)函數(shù)作用域中的變量的函數(shù),比如:

function createFunc(words) {
    return function() {
        return words;
    }
}
var func = createFunc("Hello World!")
func();
// "Hello World!"

上述例子中createFunc的返回值是一個(gè)函數(shù)(閉包),這個(gè)返回值在調(diào)用時(shí)仍然可以訪(fǎng)問(wèn)createFuncwords屬性,這是為什么呢?還記得在之前的文章Javascript 變量、作用域和內(nèi)存問(wèn)題中提到的,一個(gè)函數(shù)在創(chuàng)建時(shí),會(huì)生成一個(gè)內(nèi)部屬性[[scope]],這個(gè)屬性包含函數(shù)被創(chuàng)建的作用域中對(duì)象的集合,也就包括了createFunc的活動(dòng)對(duì)象,而如果沒(méi)有閉包,createFunc的活動(dòng)對(duì)象在調(diào)用結(jié)束時(shí)就可以進(jìn)入GC序列,只有銷(xiāo)毀對(duì)閉包的引用,即func = null,才會(huì)使createFunc的活動(dòng)對(duì)象被GC。
總結(jié)閉包的作用域鏈如下圖:

通過(guò)上述分析我們還可以看出,閉包有一個(gè)不同于普通函數(shù)的特性,就是它會(huì)攜帶包含它的函數(shù)的作用域,因此會(huì)占用更多內(nèi)存。

閉包與變量

閉包的作用域鏈決定了閉包只能包含外部函數(shù)中任何變量的最終值,舉個(gè)例子:

function createFunctions(){
    var result = new Array();
    for (var i=0; i < 10; i++){
        result[i] = function(){
            return i;
        };
    }
    return result;
}
var result = createFunctions();
result[3](); // 10

上述例子中,我們?cè)?code>createFunctions函數(shù)中,通過(guò)for循環(huán),創(chuàng)建了多個(gè)函數(shù),并期望每一個(gè)都能返回創(chuàng)建它時(shí)的索引值,但結(jié)果發(fā)現(xiàn),每一個(gè)函數(shù)都只能返回i的最終值(由于ECMAScript中沒(méi)有塊級(jí)作用域,因此icreateFunctions中的局部變量),之所以是這樣的結(jié)果,是因?yàn)槊恳粋€(gè)閉包能夠訪(fǎng)問(wèn)到的i都是對(duì)局部變量i的引用,由于這種情況的存在,因此我們發(fā)現(xiàn)很多閉包都是這樣寫(xiě)的:

function createFunctions(){
    var result = new Array();
    for (var i=0; i < 10; i++){
        result[i] = function(num){
            return function() {
                return num;
            }    
        }(i);
    }
    return result;
}
var result = createFunctions();
result[3](); // 3

原理就是將動(dòng)態(tài)的局部變量參數(shù)化,這樣每一個(gè)閉包都保存了該局部變量某個(gè)時(shí)刻的副本。

在閉包中使用this

this是基于執(zhí)行環(huán)境綁定的,如果是全局函數(shù),那么thiswindow,如果是某個(gè)對(duì)象的函數(shù),那么this指這個(gè)對(duì)象,而匿名函數(shù)的執(zhí)行環(huán)境具有全局性,因此閉包中的this一般指window,比如:

var name = "The Window";
var object = {
    name : "My Object",
    getNameFunc : function(){
        return function(){
            return this.name;
        };
    }
};
console.log(object.getNameFunc()()); //"The Window"

上述例子中,閉包是在全局環(huán)境中執(zhí)行的,而我們知道,每個(gè)函數(shù)在執(zhí)行時(shí)會(huì)基于執(zhí)行環(huán)境自動(dòng)獲得this,因此this指向了window。
上述例子中,如何使閉包可以訪(fǎng)問(wèn)object呢,可以做如下修改:

var name = "The Window";
var object = {
    name : "My Object",
    getNameFunc : function(){
        var that = this;
        return function(){
            return that.name;
        };
    }
};
object.getNameFunc()()
// "My Object"

內(nèi)存泄漏

在之前的文章Javascript 變量、作用域和內(nèi)存問(wèn)題提到過(guò)IE在版本9之前,ECMAScript對(duì)象和DOM對(duì)象的GC機(jī)制不同,循環(huán)引用會(huì)導(dǎo)致DOM對(duì)象永遠(yuǎn)不能被回收,學(xué)習(xí)完這一章節(jié)后才發(fā)現(xiàn)自己經(jīng)常寫(xiě)的一段代碼就存在這樣的問(wèn)題!-_-,書(shū)中也提到了這個(gè)例子:

function assignHandler(){
    var element = document.getElementById("someElement");
    element.onclick = function(){
        console.log(element.id);
    };
}

上面的代碼就創(chuàng)建了一個(gè)閉包作為element的事件處理程序,這里的循環(huán)引用體現(xiàn)在element的屬性onclick的值中存在對(duì)element的引用,即使退出assignHandler,element這個(gè)DOM對(duì)象也不會(huì)被引用計(jì)數(shù)機(jī)制GC,那么不在閉包中顯式地引用element,總可以了吧,就比如:

function assignHandler(){
    var element = document.getElementById("someElement");
    var id = element.id;
    element.onclick = function(){
        console.log(id);
    };
}

答案是不行的,因?yàn)殚]包中無(wú)論如何都是要保存一份對(duì) assignHandler活動(dòng)對(duì)象的引用的,自然包含element。
之前提到過(guò),由于閉包中保存的只是函數(shù)活動(dòng)對(duì)象的引用,那么閉包中能夠訪(fǎng)問(wèn)的變量就具有動(dòng)態(tài)性,上個(gè)例子中,閉包由于引用了assignHandler的活動(dòng)對(duì)象,就引用了element,而element引用了一個(gè)DOM對(duì)象,那么,如果element不引用DOM對(duì)象,而是其他對(duì)象,比如null,那么element就可以被標(biāo)記清楚機(jī)制GC。

function assignHandler(){
    var element = document.getElementById("someElement");
    var id = element.id;
    element.onclick = function(){
        console.log(id);
    };
    element = null;
}
?著作權(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)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • 閉包(closure)是Javascript語(yǔ)言的一個(gè)難點(diǎn),也是它的特色,很多高級(jí)應(yīng)用都要依靠閉包實(shí)現(xiàn)。 一、變量...
    zouCode閱讀 1,363評(píng)論 0 13
  • 前言 總括 :這篇文章使用有效的javascript代碼向程序員們解釋了閉包,大牛和功能型程序員請(qǐng)自行忽略。 譯者...
    KX九五閱讀 329評(píng)論 0 1
  • 前言 這篇文章使用有效的javascript代碼向程序員們解釋了閉包,大牛和功能型程序員請(qǐng)自行忽略。 基礎(chǔ)篇 閉包...
    kiaizi閱讀 410評(píng)論 0 7
  • 前言 總括 :這篇文章使用有效的javascript代碼向程序員們解釋了閉包,大牛和功能型程序員請(qǐng)自行忽略。 譯者...
    秦至閱讀 796評(píng)論 0 19
  • 制茶和做人一樣,也是需擇其善者而從之,但是在學(xué)習(xí)借鑒的過(guò)程當(dāng)中,不能把自己的特點(diǎn)丟掉。紅茶的香甜、綠茶的鮮爽、烏龍...
    小志de私人茶舍閱讀 391評(píng)論 0 1

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