函數(shù)表達(dá)式

定義函數(shù)的方式有兩種:函數(shù)聲明和函數(shù)表達(dá)式却音。

// 函數(shù)聲明
function fn(){}

函數(shù)聲明的一個(gè)重要特征就是函數(shù)聲明提升,意思是在執(zhí)行代碼前會先讀取函數(shù)聲明淌喻。這就意味著可以把函數(shù)聲明放在調(diào)用它的語句后面僧家。
第二種創(chuàng)建函數(shù)的方式是使用函數(shù)表達(dá)式。這種情況下創(chuàng)建的函數(shù)叫匿名函數(shù)裸删。

var fn=function(){}

函數(shù)表達(dá)式與其他表達(dá)式一樣,在使用前必須先賦值阵赠。以下代碼會導(dǎo)致錯(cuò)誤涯塔。

sayHi();    //錯(cuò)誤:函數(shù)還不存在 
var sayHi = function(){ 
    alert("Hi!");
};

能夠創(chuàng)建函數(shù)再賦值給變量肌稻,也就能夠把函數(shù)作為其他函數(shù)的值返回。

function demo(propertyName) {
    return function(object1, object2){ 
        var value1 = object1[propertyName]; 
        var value2 = object2[propertyName];
        if (value1 < value2){ 
            return -1;
        } else if (value1 > value2){ 
            return 1;
        } else {
            return 0;
        }
    };
}

demo()就返回了一個(gè)匿名函數(shù)匕荸。返回的函數(shù)可能會被賦值給一個(gè)變量爹谭,或者以其他方式被調(diào)用;不過榛搔,在demo()函數(shù)內(nèi)部诺凡,它是匿名的。在把函數(shù)當(dāng)成值來使用的情況下践惑,都可以使用匿名函數(shù)腹泌。不過,這并不是匿名函數(shù)唯一的用途尔觉。

遞歸

遞歸函數(shù)是在一個(gè)函數(shù)內(nèi)通過名字調(diào)用自身的情況下構(gòu)成的凉袱。

function factorial(num) {
    if(num<=1){
        return 1;
    }else{
        return num*factorial(num-1);
    }
}

下面的代碼卻可能導(dǎo)致它出錯(cuò)。

var anotherFactorial = factorial ;
factorial = null;
alert(anotherFactorial(4)); // 出錯(cuò)

以上代碼先把factorial()函數(shù)保存在變量anotherFactorial中侦铜,然后將factorial變量設(shè)置為null专甩,結(jié)果指向原始函數(shù)的引用只剩下一個(gè)。但在接下來調(diào)用anotherFactorial()時(shí)钉稍,由于必須執(zhí)行factorial()涤躲,而factorial已經(jīng)不再是函數(shù),所以就會導(dǎo)致錯(cuò)誤贡未。在這種情況下篓叶,使用arguments.callee可以解決這個(gè)問題。
arguments.callee是一個(gè)指向正在執(zhí)行的函數(shù)的指針羞秤,因此可以用它來實(shí)現(xiàn)對函數(shù)的遞歸調(diào)用缸托。

function factorial(num) {
    if(num <= 1) {
        return 1;
    } else {
        return num*arguments.callee(num-1);
    }
}

通過使用arguments.callee代替函數(shù)名,可以確保無論怎樣調(diào)用函數(shù)都不會出問題瘾蛋。因此俐镐,在編寫遞歸函數(shù)時(shí),使用arguments.callee總比使用函數(shù)名更保險(xiǎn)哺哼。
但在嚴(yán)格模式下佩抹,不能通過腳本訪問arguments.callee。訪問這個(gè)屬性會導(dǎo)致錯(cuò)誤取董。不過棍苹,可以使用命名函數(shù)表達(dá)式來達(dá)成相同的結(jié)果。

var factorial = (function f (num) {
    if (num <= 1 ) {
        return 1;
    } else {
        return num*f(num-1);
    }
});

閉包

閉包是指有權(quán)訪問另一個(gè)函數(shù)作用域中的變量的函數(shù)茵汰。創(chuàng)建閉包的常見方式就是在一個(gè)函數(shù)內(nèi)部創(chuàng)建另一個(gè)函數(shù)枢里。

function demo(propertyName) {
    return function(object1, object2){ 
        var value1 = object1[propertyName]; 
        var value2 = object2[propertyName];
        if (value1 < value2){ 
            return -1;
        } else if (value1 > value2){ 
            return 1;
        } else {
            return 0;
        }
    };
}

當(dāng)某個(gè)函數(shù)被調(diào)用時(shí),會創(chuàng)建一個(gè)執(zhí)行環(huán)境及相應(yīng)的作用域鏈。然后栏豺,使用arguments和其他命名參數(shù)的值來初始化函數(shù)的活動(dòng)對象彬碱。但在作用域鏈中,外部函數(shù)的活動(dòng)對象始終處于第二位奥洼,外部函數(shù)的外部函數(shù)的活動(dòng)對象處于第三位巷疼,.....直至作為作用域鏈終點(diǎn)的全局執(zhí)行環(huán)境。
在函數(shù)執(zhí)行過程中灵奖,為讀取和寫入變量的值嚼沿,就需要在作用域鏈中查找變量。

function compare(value1, value2){ 
    if (value1 < value2){ 
        return -1;
    } else if (value1 > value2){ 
        return 1;
    } else {
        return 0;
    }
}
var result = compare(5, 10);

當(dāng)調(diào)用compare()時(shí)瓷患,會創(chuàng)建一個(gè)包含arguments骡尽、value1value2的活動(dòng)對象。全局執(zhí)行環(huán)境的變量對象(包含resultcompare)在compare()執(zhí)行環(huán)境的作用域鏈中則處于第二位尉尾。


后臺的每個(gè)執(zhí)行環(huán)境都有一個(gè)表示變量的對象——變量對象爆阶。全局環(huán)境的變量對象始終存在,而像compare()函數(shù)這樣的局部環(huán)境的變量對象沙咏,則只在函數(shù)執(zhí)行的過程中存在辨图。在創(chuàng)建compare()函數(shù)時(shí),會創(chuàng)建一個(gè)預(yù)先包含全局變量對象的作用域鏈肢藐,這個(gè)作用域鏈被保存在內(nèi)部的[[Scope]]屬性中故河。 當(dāng)調(diào)用compare()函數(shù)時(shí),會為函數(shù)創(chuàng)建一個(gè)執(zhí)行環(huán)境吆豹,然后通過復(fù)制函數(shù)的[[Scope]]屬性中的對象構(gòu)建起執(zhí)行環(huán)境的作用域鏈鱼的。此后,又有一個(gè)活動(dòng)對象(在此作為變量對象使用)被創(chuàng)建并被推入執(zhí)行環(huán)境作用域鏈的前端痘煤。對于這個(gè)例子中compare()函數(shù)的執(zhí)行環(huán)境而言凑阶,其作用域鏈中包含兩個(gè)變量對象:本地活動(dòng)對象和全局變量對象。顯然衷快,作用域鏈本質(zhì)上是一個(gè)指向變量對象的指針列表宙橱,它只引用但不實(shí)際包含變量對象。
無論什么時(shí)候在函數(shù)中訪問一個(gè)變量時(shí)蘸拔,就會從作用域鏈中搜索具有相應(yīng)名字的變量师郑。一般來講, 當(dāng)函數(shù)執(zhí)行完畢后调窍,局部活動(dòng)對象就會被銷毀宝冕,內(nèi)存中僅保存全局作用域(全局執(zhí)行環(huán)境的變量對象)。但是邓萨,閉包的情況又有所不同地梨。
在另一個(gè)函數(shù)內(nèi)部定義的函數(shù)會將包含函數(shù)(即外部函數(shù))的活動(dòng)對象添加到它的作用域鏈中菊卷。因此,在demo()函數(shù)內(nèi)部定義的匿名函數(shù)的作用域鏈中湿刽,實(shí)際上將會包含外部函數(shù) demo()的活動(dòng)對象的烁。

var compare = demo("name");
var result = compare({ name: "Nicholas" }, { name: "Greg" });

在匿名函數(shù)從demo()中被返回后褐耳,它的作用域鏈被初始化為包含demo()函數(shù)的活動(dòng)對象和全局變量對象诈闺。這樣,匿名函數(shù)就可以訪問在demo()中定義的所有變量铃芦。更為重要的是雅镊,demo()函數(shù)在執(zhí)行完畢后,其活動(dòng)對象也不會被銷毀刃滓,因?yàn)槟涿瘮?shù)的作用域鏈仍然在引用這個(gè)活動(dòng)對象仁烹。換句話說,當(dāng)demo()函數(shù)返回后咧虎,其執(zhí)行環(huán)境的作用域鏈會被銷毀卓缰,但它的活 動(dòng)對象仍然會留在內(nèi)存中;直到匿名函數(shù)被銷毀后征唬,demo()的活動(dòng)對象才會被銷毀。

//創(chuàng)建函數(shù)
var compareNames = demo("name");
//調(diào)用函數(shù)
var result = compareNames({ name: "Nicholas" }, { name: "Greg" });
//解除對匿名函數(shù)的引用(以便釋放內(nèi)存) 
compareNames = null;

首先茁彭,創(chuàng)建的比較函數(shù)被保存在變量compareNames中总寒。而通過將compareNames設(shè)置為等于null解除該函數(shù)的引用,就等于通知垃圾回收例程將其清除理肺。隨著匿名函數(shù)的作用域鏈被銷毀摄闸,其他作用域 (除了全局作用域)也都可以安全地銷毀了。

調(diào)用compareNames()的過程中產(chǎn)生的作用域鏈之間的關(guān)系

由于閉包會攜帶包含它的函數(shù)的作用域妹萨,因此會比其他函數(shù)占用更多的內(nèi)存年枕。過度使用閉包可能會導(dǎo)致內(nèi)存占用過多。

閉包與變量

閉包只能取得包含函數(shù)中任何變量的最后一個(gè)值乎完。閉包所保存的是整個(gè)變量對象熏兄,而不是某個(gè)特殊的變量。

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

這個(gè)函數(shù)會返回一個(gè)函數(shù)數(shù)組囱怕。表面上看霍弹,似乎每個(gè)函數(shù)都應(yīng)該返自己的索引值,即位置0的函數(shù)返回0娃弓,位置1的函數(shù)返回1典格,以此類推。但實(shí)際上台丛,每個(gè)函數(shù)都返回10耍缴。因?yàn)槊總€(gè)函數(shù)的作用域鏈中都保存createFunctions()函數(shù)的活動(dòng)對象 砾肺, 所以它們引用的都是同一個(gè)變量i。 當(dāng)createFunctions()函數(shù)返回后防嗡,變量i的值是10变汪,此時(shí)每個(gè)函數(shù)都引用著保存變量i的同一個(gè)變量 對象,所以在每個(gè)函數(shù)內(nèi)部i的值都是10蚁趁。但是裙盾,我們可以通過創(chuàng)建另一個(gè)匿名函數(shù)強(qiáng)制讓閉包的行為符合預(yù)期。

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

在重寫了前面的createFunctions()函數(shù)后他嫡,每個(gè)函數(shù)就會返回各自不同的索引值了番官。在這個(gè)版本中,我們沒有直接把閉包賦值給數(shù)組钢属,而是定義了一個(gè)匿名函數(shù)徘熔,并將立即執(zhí)行該匿名函數(shù)的結(jié)果賦給數(shù)組。這里的匿名函數(shù)有一個(gè)參數(shù)num淆党,也就是最終的函數(shù)要返回的值酷师。在調(diào)用每個(gè)匿名函數(shù)時(shí),我們傳入了變量i染乌。由于函數(shù)參數(shù)是按值傳遞的山孔,所以就會將變量 i 的當(dāng)前值復(fù)制給參數(shù)num。而在這個(gè)匿名函數(shù)內(nèi)部慕匠,又創(chuàng)建并返回了一個(gè)訪問num的閉包饱须。這樣一來,result數(shù)組中的每個(gè)函數(shù)都有自己num變量的一個(gè)副本台谊,因此就可以返回各自不同的數(shù)值了蓉媳。

關(guān)于this對象

在閉包中使用this對象也可能會導(dǎo)致一些問題。我們知道锅铅,this對象是在運(yùn)行時(shí)基于函數(shù)的執(zhí) 行環(huán)境綁定的:在全局函數(shù)中酪呻,this等于window,而當(dāng)函數(shù)被作為某個(gè)對象的方法調(diào)用時(shí)盐须,this等于那個(gè)對象玩荠。不過,匿名函數(shù)的執(zhí)行環(huán)境具有全局性贼邓,因此其this對象通常指向window阶冈。但有時(shí)候由于編寫閉包的方式不同,這一點(diǎn)可能不會那么明顯塑径。

var name = "The Window";
var object = {
    name : "My Object",
    getNameFunc : function(){ 
        return function(){ 
            return this.name; 
        };
    }
};
alert(object.getNameFunc()());  //"The Window"(在非嚴(yán)格模式下)

每個(gè)函數(shù)在被調(diào)用時(shí)都會自動(dòng)取得兩個(gè)特殊變量:thisarguments女坑。內(nèi)部函數(shù)在搜索這兩個(gè)變量時(shí),只會搜索到其活動(dòng)對象為止统舀,因此永遠(yuǎn)不可能直接訪問外部函數(shù)中的這兩個(gè)變量匆骗。不過劳景,把外部作用域中的this對象保存在一個(gè)閉包能夠訪問到的變量里,就可以讓閉包訪問該對象了碉就。

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

thisarguments也存在同樣的問題盟广。如果想訪問作用域中的arguments對象,必須將對該對象的引用保存到另一個(gè)閉包能夠訪問的變量中瓮钥。
在幾種特殊情況下筋量,this的值可能會意外地改變。

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

這里的getName()方法只簡單地返回this.name的值骏庸。以下是幾種調(diào)用object.getName()的方式以及各自的結(jié)果毛甲。

object.getName();   //"My Object"
(object.getName)(); //"My Object"
(object.getName = object.getName)(); //"The Window"年叮,在非嚴(yán)格模式下

內(nèi)存泄漏

由于IE9之前的版本對JScript對象和COM對象使用不同的垃圾收集例程具被,因此閉包在IE的這些版本中會導(dǎo)致一些特殊的問題。具體來說只损,如果閉包的作用域鏈中保存著一個(gè)HTML元素一姿,那么就意味著該元素將無法被銷毀。

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è)對assignHandler()的活動(dòng)對象的引用,因此就會導(dǎo)致無法減少element的引用數(shù)爆存。只要匿名函數(shù)存在蛉顽,element的引用數(shù)至少也是1,因此它所占用的內(nèi)存就永遠(yuǎn)不會被回收先较。不過携冤,這個(gè)問題可以通過稍微改寫一下代碼來解決。

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

在上面的代碼中闲勺,通過把element.id的一個(gè)副本保存在一個(gè)變量中曾棕,并且在閉包中引用該變量消除了循環(huán)引用。但僅僅做到這一步菜循,還是不能解決內(nèi)存泄漏的問題翘地。必須要記住:閉包會引用包含函數(shù)的整個(gè)活動(dòng)對象癌幕,而其中包含著element衙耕。即使閉包不直接引用element,包含函數(shù)的活動(dòng)對象中也仍然會保存一個(gè)引用勺远。因此橙喘,有必要把element變量設(shè)置為null。這樣就能夠解除對DOM對象的引用谚中,順利地減少其引用數(shù)渴杆,確保正沉戎Γ回收其占用的內(nèi)存。

模仿塊級作用域

JS沒有塊級作用域的概念磁奖。這意味著在塊語句中定義的變量囊拜,實(shí)際上是在包含函數(shù)中而非語句中創(chuàng)建的。

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

在JS中比搭,變量i是定義在ouputNumbers()的活動(dòng)對象中的冠跷,因此從它有定義開始,就可以在函數(shù)內(nèi)部隨處訪問它身诺。即使像下面這樣錯(cuò)誤地重新聲明同一個(gè)變量蜜托,也不會改變它的值。

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

JS從來不會告訴你是否多次聲明了同一個(gè)變量霉赡;遇到這種情況橄务,它只會對后續(xù)的聲明視而不見(不過,它會執(zhí)行后續(xù)聲明中的變量初始化)穴亏。匿名函數(shù)可以用來模仿塊級作用域并避免這個(gè)問題蜂挪。
用作塊級作用域(通常稱為私有作用域)的匿名函數(shù)的語法如下所示钟沛。

(function(){
    //這里是塊級作用域
})();

以上代碼定義并立即調(diào)用了一個(gè)匿名函數(shù)榆骚。將函數(shù)聲明包含在一對圓括號中,表示它實(shí)際上是一個(gè)函數(shù)表達(dá)式比吭。而緊隨其后的另一對圓括號會立即調(diào)用這個(gè)函數(shù)刺覆。
無論在什么地方严肪,只要臨時(shí)需要一些變量,就可以使用私有作用域谦屑,例如:

function outputNumbers(count){ 
    (function () {
        for (var i=0; i < count; i++){ 
            alert(i);
        }
    })();

    alert(i);   //導(dǎo)致一個(gè)錯(cuò)誤驳糯! 
}

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

私有變量

嚴(yán)格來講充蓝,JS中沒有私有成員的概念隧枫;所有對象屬性都是公有的。不過谓苟,倒是有一個(gè)私有變量的概念官脓。任何在函數(shù)中定義的變量,都可以認(rèn)為是私有變量涝焙,因?yàn)椴荒茉诤瘮?shù)的外部訪問這些變量卑笨。私有變量包括函數(shù)的參數(shù)、局部變量和在函數(shù)內(nèi)部定義的其他函數(shù)仑撞。

function add (num1 , num2 ) {
    var sum = num1 + num2;
    return sum;
}

在這個(gè)函數(shù)內(nèi)部有3個(gè)私有變量:num1赤兴,num2sum妖滔。在函數(shù)內(nèi)部可以訪問這幾個(gè)變量,但在函數(shù)外部則不能訪問它們桶良。如果在這個(gè)函數(shù)內(nèi)部創(chuàng)建一個(gè)閉包座舍,那么閉包通過自己的作用域鏈也可以訪問這些變量。而利用這一點(diǎn)陨帆,就可以創(chuàng)建用于訪問私有變量的公有方法曲秉。
我們把有權(quán)訪問私有變量和私有函數(shù)的公有方法稱為特權(quán)方法。有兩種在對象上創(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è)模式在構(gòu)造函數(shù)內(nèi)部定義了所有私有變量和函數(shù)亥鸠。然后,又繼續(xù)創(chuàng)建了能夠訪問這些私有成員的特權(quán)方法缩焦。能夠在構(gòu)造函數(shù)重定義特權(quán)方法读虏,是因?yàn)樘貦?quán)方法作為閉包有權(quán)訪問在構(gòu)造函數(shù)中定義的所有變量和函數(shù)。對這個(gè)例子而言袁滥,變量privateVariable和函數(shù)privateFunction()只能通過特權(quán)方法publicMethod()來訪問。在創(chuàng)建MyObject的實(shí)例后灾螃,除了使用publicMethod()這一個(gè)途徑外题翻,沒有任何辦法可以直接訪問privateVariableprivateFunction()
利用私有和特權(quán)成員腰鬼, 可以隱藏那些不應(yīng)該被直接修改的數(shù)據(jù):

function Person(name){
    this.getName = function(){ 
        return name;
    };
    this.setName = function (value) { 
        name = value;
    };
}
var person = new Person("Nicholas"); 
alert(person.getName());   //"Nicholas" 
person.setName("Greg"); 
alert(person.getName());   //"Greg"

在構(gòu)造函數(shù)中定義特權(quán)方法也有一個(gè)缺點(diǎn)嵌赠,那就是你必須使用構(gòu)造函數(shù)模式來達(dá)到這個(gè)目的。構(gòu)造函數(shù)模式的缺點(diǎn)是針對每個(gè)實(shí)例都會創(chuàng)建同樣一組新方法熄赡,而使用靜態(tài)私有變量來實(shí)現(xiàn)特權(quán)方法就可以避免這個(gè)問題姜挺。

靜態(tài)私有變量

通過在私有作用域中定義私有變量或函數(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è)模式創(chuàng)建了一個(gè)私有作用域炊豪,并在其中封裝了一個(gè)構(gòu)造函數(shù)及相應(yīng)的方法。在私有作用域中拧篮, 首先定義了私有變量和私有函數(shù)词渤,然后又定義了構(gòu)造函數(shù)及其公有方法。公有方法是在原型上定義的串绩, 這一點(diǎn)體現(xiàn)了典型的原型模式缺虐。需要注意的是,這個(gè)模式在定義構(gòu)造函數(shù)時(shí)并沒有使用函數(shù)聲明礁凡,而是 使用了函數(shù)表達(dá)式高氮。函數(shù)聲明只能創(chuàng)建局部函數(shù)慧妄,但那并不是我們想要的。出于同樣的原因剪芍,我們也沒 有在聲明MyObject時(shí)使用var關(guān)鍵字腰涧。記住:初始化未經(jīng)聲明的變量紊浩,總是會創(chuàng)建一個(gè)全局變量窖铡。 因此,MyObject就成了一個(gè)全局變量坊谁,能夠在私有作用域之外被訪問到费彼。但也要知道,在嚴(yán)格模式下給未經(jīng)聲明的變量賦值會導(dǎo)致錯(cuò)誤口芍。
這個(gè)模式與在構(gòu)造函數(shù)中定義特權(quán)方法的主要區(qū)別箍铲,就在于私有變量和函數(shù)是由實(shí)例共享的。由于特權(quán)方法是在原型上定義的鬓椭,因此所有實(shí)例都使用同一個(gè)函數(shù)颠猴。而這個(gè)特權(quán)方法,作為一個(gè)閉包小染,總是保存著對包含作用域的引用翘瓮。

(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"

這個(gè)例子中的Person構(gòu)造函數(shù)與getName()setName()方法一樣,都有權(quán)訪問私有變量name裤翩。
在這種模式下资盅,變量name就變成了一個(gè)靜態(tài)的、由所有實(shí)例共享的屬性踊赠。也就是說呵扛,在一個(gè)實(shí)例上調(diào)用setName()會影響所有實(shí)例。而調(diào)用setName()或新建一個(gè)Person實(shí)例都會賦予name屬性一個(gè)新值筐带。結(jié)果就是所有實(shí)例都會返回相同的值今穿。
以這種方式創(chuàng)建靜態(tài)私有變量會因?yàn)槭褂迷投鲞M(jìn)代碼復(fù)用,但每個(gè)實(shí)例都沒有自己的私有變量伦籍。到底是使用實(shí)例變量蓝晒,還是靜態(tài)私有變量,最終還是要視你的具體需求而定鸽斟。
多查找作用域鏈中的一個(gè)層次拔创,就會在一定程度上影響查找速度。而這正是使用閉包和私有變量的一個(gè)顯明的不足之處富蓄。

模塊模式

模塊模式是為單例創(chuàng)建私有變量和特權(quán)方法剩燥。所謂單例,指的就是只有一個(gè)實(shí)例的對象。 按照慣例灭红,JS是以對象字面量的方式來創(chuàng)建單例對象的侣滩。

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

模塊模式通過為單例添加私有變量和特權(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è)返回對象的匿名函數(shù)变擒。在這個(gè)匿名函數(shù)內(nèi)部君珠,首先定義了私有變量和函數(shù)。 然后娇斑,將一個(gè)對象字面量作為函數(shù)的值返回策添。返回的對象字面量中只包含可以公開的屬性和方法。由于這個(gè)對象是在匿名函數(shù)內(nèi)部定義的毫缆,因此它的公有方法有權(quán)訪問私有變量和函數(shù)唯竹。從本質(zhì)上來講,這個(gè)對象字面量定義的是單例的公共接口苦丁。這種模式在需要對單例進(jìn)行某些初始化浸颓,同時(shí)又需要維護(hù)其私有變量時(shí)是非常有用的。

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);
            }
        }
    };
}();

在 Web 應(yīng)用程序中旺拉,經(jīng)常需要使用一個(gè)單例來管理應(yīng)用程序級的信息产上。這個(gè)簡單的例子創(chuàng)建了一個(gè)用于管理組件的application對象。在創(chuàng)建這個(gè)對象的過程中蛾狗,首先聲明了一個(gè)私有的components數(shù)組晋涣,并向數(shù)組中添加了一個(gè)BaseComponent的新實(shí)例(在這里不需要關(guān)心BaseComponent的代碼,我們只是用它來展示初始化操作)淘太。而返回對象的getComponentCount()registerComponent()方法姻僧,都是有權(quán)訪問數(shù)組components的特權(quán)方法。前者只是返回已注冊的組件數(shù)目蒲牧,后者用于注冊新組件。
簡言之赌莺,如果必須創(chuàng)建一個(gè)對象并以某些數(shù)據(jù)對其進(jìn)行初始化冰抢,同時(shí)還要公開一些能夠訪問這些私有數(shù)據(jù)的方法,那么就可以使用模塊模式艘狭。以這種模式創(chuàng)建的每個(gè)單例都是Object的實(shí)例挎扰,因?yàn)樽罱K要通過一個(gè)對象字面量來表示它。事實(shí)上巢音,這也沒有什么遵倦;畢竟,單例通常都是作為全局對象存在的官撼,我們不會將它傳遞給一個(gè)函數(shù)梧躺。因此,也就沒有什么必要使用instanceof操作符來檢查其對象類型了。

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

在返回對象之前加入對其增強(qiáng)的代碼掠哥。這種增強(qiáng)的模塊模式適合那些單例必須是某種類型的實(shí)例巩踏,同時(shí)還必須添加某些屬性和方法對其加以增強(qiáng)的情況。

var singleton = function(){
    //私有變量和私有函數(shù)
    var privateVariable = 10;
    function privateFunction(){ 
        return false;
    }
    //創(chuàng)建對象
    var object = new CustomType();
    //添加特權(quán)/公有屬性和方法 
    object.publicProperty = true;
    object.publicMethod = function(){ 
        privateVariable++; 
        return privateFunction(); 
    };
    //返回這個(gè)對象 
    return object; 
}();

如果前面演示模塊模式的例子中的application對象必須是BaseComponent的實(shí)例续搀,那么就可以使用以下代碼塞琼。

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è)重寫后的應(yīng)用程序(application)單例中,首先也是像前面例子中一樣定義了私有變量禁舷。主要的不同之處在于命名變量app的創(chuàng)建過程彪杉,因?yàn)樗仨毷?code>BaseComponent的實(shí)例。這個(gè)實(shí)例實(shí)際上是application對象的局部變量版牵咙。此后派近,我們又為app對象添加了能夠訪問私有變量的公有方法。 最后一步是返回app對象霜大,結(jié)果仍然是將它賦值給全局變量application构哺。

小結(jié)

在JS編程中,函數(shù)表達(dá)式是一種非常有用的技術(shù)战坤。使用函數(shù)表達(dá)式可以無須對函數(shù)命名曙强, 從而實(shí)現(xiàn)動(dòng)態(tài)編程。匿名函數(shù)途茫,也稱為拉姆達(dá)函數(shù)碟嘴,是一種使用JS函數(shù)的強(qiáng)大方式。以下總結(jié)了函數(shù)表達(dá)式的特點(diǎn)囊卜。

  • 函數(shù)表達(dá)式不同于函數(shù)聲明娜扇。函數(shù)聲明要求有名字,但函數(shù)表達(dá)式不需要栅组。沒有名字的函數(shù)表達(dá)式也叫做匿名函數(shù)雀瓢。
  • 在無法確定如何引用函數(shù)的情況下,遞歸函數(shù)就會變得比較復(fù)雜玉掸;
  • 遞歸函數(shù)應(yīng)該始終使用arguments.callee來遞歸地調(diào)用自身刃麸,不要使用函數(shù)名,函數(shù)名可能會發(fā)生變化司浪。

當(dāng)在函數(shù)內(nèi)部定義了其他函數(shù)時(shí)泊业,就創(chuàng)建了閉包。閉包有權(quán)訪問包含函數(shù)內(nèi)部的所有變量啊易。

  • 在后臺執(zhí)行環(huán)境中吁伺,閉包的作用域鏈包含著它自己的作用域、包含函數(shù)的作用域和全局作用域租谈。
  • 通常篮奄,函數(shù)的作用域及其所有變量都會在函數(shù)執(zhí)行結(jié)束后被銷毀。
  • 但是,當(dāng)函數(shù)返回了一個(gè)閉包時(shí)宦搬,這個(gè)函數(shù)的作用域?qū)恢痹趦?nèi)存中保存到閉包不存在為止牙瓢。

使用閉包可以在JS中模仿塊級作用域(JS本身沒有塊級作用域的概念)。

  • 創(chuàng)建并立即調(diào)用一個(gè)函數(shù)间校,這樣既可以執(zhí)行其中的代碼矾克,又不會在內(nèi)存中留下對該函數(shù)的引用。
  • 結(jié)果就是函數(shù)內(nèi)部的所有變量都會被立即銷毀——除非將某些變量賦值給了包含作用域(即外部作用域)中的變量憔足。

閉包還可以用于在對象中創(chuàng)建私有變量胁附。

  • 即使JS中沒有正式的私有對象屬性的概念,但可以使用閉包來實(shí)現(xiàn)公有方法滓彰,而通過公有方法可以訪問在包含作用域中定義的變量控妻。
  • 有權(quán)訪問私有變量的公有方法叫做特權(quán)方法。
  • 可以使用構(gòu)造函數(shù)模式揭绑、原型模式來實(shí)現(xiàn)自定義類型的特權(quán)方法弓候,也可以使用模塊模式、增強(qiáng)的模塊模式來實(shí)現(xiàn)單例的特權(quán)方法他匪。

JS中的函數(shù)表達(dá)式和閉包都是極其有用的特性菇存,利用它們可以實(shí)現(xiàn)很多功能。不過邦蜜,因?yàn)閯?chuàng)建閉包必須維護(hù)額外的作用域依鸥,所以過度使用它們可能會占用大量內(nèi)存。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末悼沈,一起剝皮案震驚了整個(gè)濱河市贱迟,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌絮供,老刑警劉巖衣吠,帶你破解...
    沈念sama閱讀 222,000評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異壤靶,居然都是意外死亡蒸播,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評論 3 399
  • 文/潘曉璐 我一進(jìn)店門萍肆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人胀屿,你說我怎么就攤上這事塘揣。” “怎么了宿崭?”我有些...
    開封第一講書人閱讀 168,561評論 0 360
  • 文/不壞的土叔 我叫張陵亲铡,是天一觀的道長。 經(jīng)常有香客問我,道長奖蔓,這世上最難降的妖魔是什么赞草? 我笑而不...
    開封第一講書人閱讀 59,782評論 1 298
  • 正文 為了忘掉前任,我火速辦了婚禮吆鹤,結(jié)果婚禮上厨疙,老公的妹妹穿的比我還像新娘。我一直安慰自己疑务,他們只是感情好沾凄,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,798評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著知允,像睡著了一般撒蟀。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上温鸽,一...
    開封第一講書人閱讀 52,394評論 1 310
  • 那天保屯,我揣著相機(jī)與錄音,去河邊找鬼涤垫。 笑死姑尺,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的雹姊。 我是一名探鬼主播股缸,決...
    沈念sama閱讀 40,952評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼吱雏!你這毒婦竟也來了敦姻?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,852評論 0 276
  • 序言:老撾萬榮一對情侶失蹤歧杏,失蹤者是張志新(化名)和其女友劉穎镰惦,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體犬绒,經(jīng)...
    沈念sama閱讀 46,409評論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡旺入,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,483評論 3 341
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了凯力。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片茵瘾。...
    茶點(diǎn)故事閱讀 40,615評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖咐鹤,靈堂內(nèi)的尸體忽然破棺而出拗秘,到底是詐尸還是另有隱情,我是刑警寧澤祈惶,帶...
    沈念sama閱讀 36,303評論 5 350
  • 正文 年R本政府宣布雕旨,位于F島的核電站扮匠,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏凡涩。R本人自食惡果不足惜棒搜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,979評論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望活箕。 院中可真熱鬧力麸,春花似錦、人聲如沸讹蘑。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽座慰。三九已至陨舱,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間版仔,已是汗流浹背游盲。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蛮粮,地道東北人益缎。 一個(gè)月前我還...
    沈念sama閱讀 49,041評論 3 377
  • 正文 我出身青樓,卻偏偏與公主長得像然想,于是被迫代替她去往敵國和親莺奔。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,630評論 2 359

推薦閱讀更多精彩內(nèi)容

  • 本章內(nèi)容 函數(shù)表達(dá)式的特征 使用函數(shù)實(shí)現(xiàn)遞歸 使用閉包定義私有變量 定義函數(shù)的方式有兩種:一種是函數(shù)聲明变泄,另一種就...
    悶油瓶小張閱讀 362評論 0 0
  • 定義函數(shù)的方法有兩種:函數(shù)聲明和函數(shù)表達(dá)式令哟。 函數(shù)聲明 使用函數(shù)聲明時(shí),函數(shù)聲明會被提升至當(dāng)前作用域最前面妨蛹。 但是...
    exialym閱讀 374評論 0 3
  • 常規(guī)方式定義函數(shù) 定義函數(shù)有兩種方式屏富,第一種方式為常規(guī)聲明方式,該方式下函數(shù)可以先使用蛙卤,后聲明狠半,即"函數(shù)聲明提升"...
    勤勞的悄悄閱讀 259評論 0 0
  • 子曰:吾日三省吾省。 做好時(shí)間管理的首要是做重要的事颤难,利用早起的時(shí)間神年, 在晨間日記中快速列出當(dāng)天重點(diǎn)要做的三件事;...
    MASTERJIANG閱讀 126評論 0 0
  • 1行嗤、和大家分享“狂野想象”這個(gè)工具的第三種用法瘤袖,一起自在人生。 2昂验、“狂野想象”這個(gè)工具有三種用法捂敌。第一種,宇宙訂...
    自在人生心想生閱讀 1,347評論 0 5