十一

內(nèi)存泄漏
由于IE9之前的版本對(duì)JScript對(duì)象和COM對(duì)象使用不同的垃圾收集例程,因此閉包在IE的這些版本中會(huì)導(dǎo)致一些特殊的問(wèn)題。具體來(lái)說(shuō),如果閉包的作用域鏈中保存著一個(gè)HTML元素,那么就意味著該元素將無(wú)法被銷(xiāo)毀:

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

以上代碼創(chuàng)建了一個(gè)作為element元素事件處理程序的閉包,而這個(gè)閉包則又創(chuàng)建了一個(gè)循環(huán)引用。由于匿名函數(shù)保存了一個(gè)對(duì)assignHandler()的活動(dòng)對(duì)象的引用,因此就會(huì)導(dǎo)致無(wú)法減少element的引用數(shù)。只要匿名函數(shù)存在,element的引用數(shù)至少也是1,因此它所占用的內(nèi)存就永遠(yuǎn)不會(huì)被回收。不過(guò),這個(gè)問(wèn)題可以通過(guò)稍微改寫(xiě)一下代碼來(lái)解決:

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

在上面的代碼中,通過(guò)把element.id的一個(gè)副本保存在一個(gè)變量中,并且在閉包中引用該變量消除了循環(huán)引用。但僅僅做到這一步,還是不能解決內(nèi)存泄漏的問(wèn)題。必須要記?。洪]包會(huì)引用包含函數(shù)的整個(gè)活動(dòng)對(duì)象,而其中包含著element。即使閉包不直接引用element,包含函數(shù)的活動(dòng)對(duì)象中也仍然會(huì)保存一個(gè)引用。因此,有必要把element變量設(shè)置為null。這樣就能夠接觸對(duì)DOM對(duì)象的引用,順利地減少其引用數(shù),確保正?;厥掌湔加玫膬?nèi)存。

模仿塊級(jí)作用域
JavaScript沒(méi)有塊級(jí)作用域的概念,這意味著在塊語(yǔ)句中定義的變量,實(shí)際上是在包含函數(shù)中而非語(yǔ)句中創(chuàng)建的:

function outputNumbers(count) {
    for (var i = 0; i < count; i++) {
        alert(i);
    }
    alert(i); //計(jì)數(shù)
}

在JavaScript中,變量i是定義在ouputNumbers()的活動(dòng)對(duì)象中的,因此從它有定義開(kāi)始,就可以在函數(shù)內(nèi)部隨處訪(fǎng)問(wèn)它。即使像下面這樣錯(cuò)誤地重新聲明同一個(gè)變量,也不會(huì)改變它的值:

function outputNumbers(count) {
    for (var i = 0; i < count; i++) {
        alert(i);
    }
    var i; //重新聲明變量
    alert(i); //計(jì)數(shù)
}

JavaScript從來(lái)不會(huì)告訴你是否多次聲明了同一個(gè)變量;遇到這種情況,它只會(huì)對(duì)后續(xù)的聲明視而不見(jiàn)(不過(guò),它會(huì)執(zhí)行后續(xù)聲明中的變量初始化)。匿名函數(shù)可以用來(lái)模仿塊作用域并避免這個(gè)問(wèn)題。
用作塊級(jí)作用域(通常稱(chēng)為私有作用域)的匿名函數(shù)的語(yǔ)法如下:

(function(){
    //這里是塊級(jí)作用域
})();
function(){
  //從這里是塊級(jí)作用域
}(); //出錯(cuò)!

上一段代碼會(huì)導(dǎo)致語(yǔ)法錯(cuò)誤,因?yàn)镴avaScript將function關(guān)鍵字當(dāng)做一個(gè)函數(shù)聲明的開(kāi)始,而函數(shù)聲明后面不能跟圓括號(hào)。然而,函數(shù)表達(dá)式的后面可以跟圓括號(hào)。
無(wú)論在什么地方,只要臨時(shí)需要一些變量,就可以使用私有作用域:

function outputNumbers(count) {
    (function () {
        for (var i = 0; i < count; i++) {
            alert(i);
        }
    })();
    alert(i); //導(dǎo)致一個(gè)錯(cuò)誤!
}

這種技術(shù)經(jīng)常在全局作用域中被用在函數(shù)外部,從而限制向全局作用域中添加過(guò)多的變量和函數(shù)。一般來(lái)說(shuō),我們都應(yīng)該盡量少向全局作用域中添加變量和函數(shù)。在一個(gè)由很多開(kāi)發(fā)人員共同參與的大型應(yīng)用程序中,過(guò)多的全局變量和函數(shù)很容易導(dǎo)致命名沖突。而通過(guò)創(chuàng)建私有作用域,每個(gè)開(kāi)發(fā)人員既可以使用自己的變量,又不必?fù)?dān)心搞亂全局作用域。

這種做法可以減少閉包占用的內(nèi)存問(wèn)題,因?yàn)闆](méi)有指向匿名函數(shù)的引用。只要函數(shù)執(zhí)行完畢,就可以立即銷(xiāo)毀其作用域鏈了。

私有變量
嚴(yán)格來(lái)講,JavaScript中沒(méi)有私有成員的概念;所有對(duì)象屬性都是公有的。不過(guò),倒是有一個(gè)私有變量的概念。任何在函數(shù)中定義的變量,都可以認(rèn)為是私有變量,因?yàn)椴荒茉诤瘮?shù)的外部訪(fǎng)問(wèn)這些變量。私有變量包括函數(shù)的參數(shù)、局部變量和在函數(shù)內(nèi)部定義的其他函數(shù)。
私有變量在函數(shù)外部不能訪(fǎng)問(wèn)它們。如果在這個(gè)函數(shù)內(nèi)部創(chuàng)建一個(gè)閉包,那么閉包通過(guò)自己的作用域鏈也可以訪(fǎng)問(wèn)這些變量。而利用這一點(diǎn),就可以創(chuàng)建用于訪(fǎng)問(wèn)私有變量的公有方法。
我們把有權(quán)訪(fǎng)問(wèn)私有變量和私有函數(shù)的公有方法稱(chēng)為特權(quán)方法。有兩種在對(duì)象上創(chuàng)建特權(quán)方法的方式。第一種是在構(gòu)造函數(shù)中定義特權(quán)方法:

function MyObject() {
    //私有變量和私有函數(shù)
    var privateVariable = 10;
    function privateFunction() {
        return false;
    }
    //特權(quán)方法
    this.publicMethod = function () {
        privateVariable++;
        return privateFunction();
    };
}

在構(gòu)造函數(shù)中定義特權(quán)方法有一個(gè)缺點(diǎn),那就是你必須使用函數(shù)模式來(lái)達(dá)到這個(gè)目的。前面已經(jīng)討論過(guò),構(gòu)造函數(shù)模式的缺點(diǎn)是針對(duì)每個(gè)實(shí)例都會(huì)創(chuàng)建同樣一組新方法,而使用靜態(tài)私有變量來(lái)實(shí)現(xiàn)特權(quán)方法可以避免這個(gè)問(wèn)題。

靜態(tài)私有變量
通過(guò)在私有作用域中定義私有變量或函數(shù),同樣也可以創(chuàng)建特權(quán)方法:

(function () {
    //私有變量和私有函數(shù)
    var privateVariable = 10;
    function privateFunction() {
        return false;
    }
    //構(gòu)造函數(shù)
    MyObject = function () {
    };
    //公有/特權(quán)方法
    MyObject.prototype.publicMethod = function () {
        privateVariable++;
        return privateFunction();
    };
})();

需要注意的是,這個(gè)模式在定義構(gòu)造函數(shù)時(shí)并沒(méi)有使用函數(shù)聲明,而是使用了函數(shù)表達(dá)式。函數(shù)聲明只能創(chuàng)建局部函數(shù),但那并不是我們想要的。出于同樣的原因,我們也沒(méi)有在聲明MyObject時(shí)使用var關(guān)鍵字。記?。撼跏蓟唇?jīng)聲明的變量,總是會(huì)創(chuàng)建一個(gè)全局變量。因此,MyObject就成了一個(gè)全局變量,能夠在私有作用域之外被訪(fǎng)問(wèn)到。但也要知道,在嚴(yán)格模式下給未經(jīng)聲明的變量賦值會(huì)導(dǎo)致錯(cuò)誤。
這個(gè)模式與在構(gòu)造函數(shù)中定義特權(quán)方法的主要區(qū)別,就在于私有變量和函數(shù)是由實(shí)例共享的。由于特權(quán)方法是在原型上定義的,因此所有實(shí)例都使用同一個(gè)函數(shù)。而這個(gè)特權(quán)方法,作為一個(gè)閉包,總是保存著對(duì)包含作用域的引用。

(function () {
    var name = "";
    Person = function (value) {
        name = value;
    };
    Person.prototype.getName = function () {
        return name;
    };
    Person.prototype.setName = function (value) {
        name = value;
    };
})();
var person1 = new Person("Nicholas");
alert(person1.getName()); //"Nicholas"
person1.setName("Greg");
alert(person1.getName()); //"Greg"
var person2 = new Person("Michael");
alert(person1.getName()); //"Michael"
alert(person2.getName()); //"Michael"

以這種方式創(chuàng)建靜態(tài)私有變量會(huì)因?yàn)槭褂迷投鲞M(jìn)代碼復(fù)用,但每個(gè)實(shí)例都沒(méi)有自己的私有變量。到底是使用實(shí)例變量,還是靜態(tài)私有變量,最終還是要視你的具體需求而定。

多查找作用域鏈中的一個(gè)層次,就會(huì)在一定程度上影響查找速度。而這正是使用閉包和私有變量的一個(gè)明顯的不足之處。

模塊模式
前面的模式是用于為自定義類(lèi)型創(chuàng)建私有變量和特權(quán)方法的。而道格拉斯所說(shuō)的模塊模式則是為單例創(chuàng)建私有變量和特權(quán)方法。所謂單例,指的就是只有一個(gè)實(shí)例的對(duì)象。按照慣例,JavaScript是以對(duì)象字面量的方式來(lái)創(chuàng)建單例對(duì)象的。

var singleton = {
  name : value,
  method : function () {
    //這里是方法的代碼
  }
};

模塊模式通過(guò)為單例添加私有變量和特權(quán)方法能夠使其得到增強(qiáng):

var singleton = function () {
    //私有變量和私有函數(shù)
    var privateVariable = 10;
    function privateFunction() {
        return false;
    }
    //特權(quán)/公有方法和屬性
    return {
        publicProperty: true,
        publicMethod: function () {
            privateVariable++;
            return privateFunction();
        }
    };
}();

這個(gè)模塊模式使用了一個(gè)返回對(duì)象的匿名函數(shù)。在這個(gè)匿名函數(shù)內(nèi)部,首先定義了私有變量和函數(shù)。然后,將一個(gè)對(duì)象字面量作為函數(shù)的值返回。返回的對(duì)象字面量中只包含可以公開(kāi)的屬性和方法。由于這個(gè)對(duì)象是在匿名函數(shù)內(nèi)部定義的,因此它的公有方法有權(quán)訪(fǎng)問(wèn)私有變量和函數(shù)。從本質(zhì)上來(lái)講,這個(gè)對(duì)象字面量定義的是單例的公共接口。這種模式在需要對(duì)單例進(jìn)行某些初始化,同時(shí)又需要維護(hù)其私有變量時(shí)是非常有用的:
簡(jiǎn)言之,如果必須創(chuàng)建一個(gè)對(duì)象并以某些數(shù)據(jù)對(duì)其進(jìn)行初始化,同時(shí)還要公開(kāi)一些能夠訪(fǎng)問(wèn)這些私有數(shù)據(jù)的方法,那么就可以使用模塊模式。以這種模式創(chuàng)建的每個(gè)單例都是Object的實(shí)例,因?yàn)樽罱K要通過(guò)一個(gè)對(duì)象字面量來(lái)表示它。事實(shí)上,這也沒(méi)有什么;畢竟,單例通常都是作為全局對(duì)象存在的,我們不會(huì)將它傳遞給一個(gè)函數(shù)。因此,也就沒(méi)有什么必要使用instanceof操作符來(lái)檢查其對(duì)象類(lèi)型了。

增強(qiáng)的模塊模式

var application = function () {
    //私有變量和函數(shù)
    var components = new Array();
    //初始化
    components.push(new BaseComponent());
    //公共
    return {
        getComponentCount: function () {
            return components.length;
        },
        registerComponent: function (component) {
            if (typeof component == "object") {
                components.push(component);
            }
        }
    };
}();

變?yōu)椋?/p>

var application = function () {
    //私有變量和函數(shù)
    var components = new Array();
    //初始化
    components.push(new BaseComponent());
    //創(chuàng)建application的一個(gè)局部副本
    var app = new BaseComponent();
    //公共接口
    app.getComponentCount = function () {
        return components.length;
    };
    app.registerComponent = function (component) {
        if (typeof component == "object") {
            components.push(component);
        }
    };
    //返回這個(gè)副本
    return app;
}();

這個(gè)重寫(xiě)后的應(yīng)用程序(application)單例中,首先也是像前面例子中一樣定義了私有變量。主要的不同之處在于命名變量app的創(chuàng)建過(guò)程,因?yàn)樗仨毷荁aseComponent的實(shí)例。這個(gè)實(shí)例實(shí)際上是application對(duì)象的局部變量版。此后,我們又為app對(duì)象添加了能夠訪(fǎng)問(wèn)私有變量的公有方法。最后一步是返回app對(duì)象,結(jié)果仍然是將它賦值給全局變量application。

?著作權(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)容

  • ??函數(shù)表達(dá)式是 JavaScript 中的一個(gè)既強(qiáng)大有容易令人困惑的特性。定義函數(shù)的的方式有兩種: 函數(shù)聲明; ...
    霜天曉閱讀 892評(píng)論 0 1
  • 第3章 基本概念 3.1 語(yǔ)法 3.2 關(guān)鍵字和保留字 3.3 變量 3.4 數(shù)據(jù)類(lèi)型 5種簡(jiǎn)單數(shù)據(jù)類(lèi)型:Unde...
    RickCole閱讀 5,530評(píng)論 0 21
  • 定義函數(shù)的方式有兩種:函數(shù)聲明和函數(shù)表達(dá)式。 函數(shù)聲明的一個(gè)重要特征就是函數(shù)聲明提升,意思是在執(zhí)行代碼前會(huì)先讀取函...
    oWSQo閱讀 746評(píng)論 0 0
  • 我睜開(kāi)雙眼 企圖看到你 我伸出雙手 企圖抓住你 但是 如水中的倒影 有什么是真實(shí)的 如夢(mèng)中的美好 有什么可以留得住...
    零距離_b1a3閱讀 345評(píng)論 2 2
  • 以招商銀行信用卡為例來(lái)描述 Apple Pay 的運(yùn)行機(jī)制:1.用戶(hù)向招商銀行申請(qǐng)一張銀聯(lián)標(biāo)準(zhǔn)信用卡,并獲得批準(zhǔn):...
    AlaricMurray閱讀 4,574評(píng)論 0 2

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