第七章 函數(shù)表達式

本章內(nèi)容

  • 函數(shù)表達式的特征
  • 使用函數(shù)實現(xiàn)遞歸
  • 使用閉包定義私有變量

第五章曾介紹過臀脏,定義函數(shù)的方式有兩種:一種是函數(shù)聲明擦剑,另一種是函數(shù)表達式
關(guān)于函數(shù)聲明,它的一個重要特征是函數(shù)提升存谎,這就意味著可以把函數(shù)聲明放在調(diào)用它的語句后面返顺。

sayHi();//不會拋出錯誤。
function sayHi(){
    alert("hi");
}

關(guān)于函數(shù)表達式站绪,它有幾種不同的語法形式遭铺。

var functionName = function(arg0, arg1, arg2){
    //函數(shù)體
};

這種形式好像常規(guī)的變量賦值語句恢准,即創(chuàng)建一個函數(shù)并將它賦值給變量魂挂。functionName.這種情況下創(chuàng)建的函數(shù)叫做匿名函數(shù)。匿名函數(shù)的name屬性是空字符串馁筐。
函數(shù)表達式與其他表達式一樣涂召,在使用前必須先賦值。以下代碼會導致錯誤敏沉。

sayHi();//錯誤果正,函數(shù)還不存在
var sayHi = function(){
    alert("hi");
}

理解函數(shù)提升的關(guān)鍵,就是理解函數(shù)聲明與函數(shù)表達式的區(qū)別盟迟。
能夠創(chuàng)建函數(shù)再賦值給變量秋泳,也能夠把函數(shù)作為其他函數(shù)的值返回。

一攒菠、遞歸

遞歸函數(shù)是指一個函數(shù)通過名字調(diào)用自身迫皱,如下是一個經(jīng)典的階乘函數(shù)。

function factorial(num){
    if(num <= 1){
        return 1;
    } else {
        return num *  factorial(num -1);
    }
}
//下面代碼可能導致階乘函數(shù)出錯
var anotherFactorial = factorial;
factorial = null;
alert(anotherFactorial (4));//出錯

上面出錯的原因是將factorial變量置為null辖众,結(jié)果指向原始函數(shù)的引用只剩下一個卓起。但在調(diào)用anotherFactorial 時,在函數(shù)內(nèi)部必須執(zhí)行factorial(num -1)凹炸,而factorial已不是函數(shù)戏阅,導致出錯。有兩種方法解決這個問題啤它。

//1.arguments.callee是指向正在執(zhí)行的函數(shù)的指針
function factorial(num){
    if(num <= 1){
        return 1;
    } else {
        return num *  arguments.callee(num -1);
    }
}
//2. 若在嚴格模式下饲握,不能通過腳本訪問arguments.callee私杜。使用命名函數(shù)表達式。
//就算將factorial賦值給另外一個變量救欧,函數(shù)f仍然有效。
var factorial = (function f(num){
      if(num <= 1){
        return 1;
    } else {
        return num * f(num -1)
})

二锣光、閉包

要區(qū)分匿名函數(shù)與閉包的區(qū)別笆怠。閉包是指有權(quán)訪問另一個函數(shù)作用域中變量的函數(shù)。創(chuàng)建閉包的常見方式誊爹,就是在一個函數(shù)內(nèi)部創(chuàng)建另一個函數(shù)蹬刷。如下:

function createComparionFunction(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;
        }
    };
}

當某個函數(shù)被調(diào)用時,會創(chuàng)建一個執(zhí)行環(huán)境及相應(yīng)的作用域鏈频丘。然后通過arguments和其他命名參數(shù)的值來初始化函數(shù)的活動對象办成。后臺的每個執(zhí)行環(huán)境都有一個表示變量的對象---變量對象。全局環(huán)境的變量對象始終存在搂漠,而局部環(huán)境的變量對象則只在函數(shù)執(zhí)行過程中存在迂卢。作用域鏈的本質(zhì)是一個指向變量對象的指針列表,它只引用但不包含實際的變量對象桐汤。
在上述例子中而克,閉包函數(shù)可以訪問外部函數(shù)中的propertyName。即使這個內(nèi)部函數(shù)被返回了怔毛,而且是在其他地方被調(diào)用了员萍,但它仍然可以訪問變量propertyName。之所以還能夠訪問這個變量是因為內(nèi)部函數(shù)的作用域鏈中包含createComparsionFunction()的作用域拣度。
在匿名函數(shù)從createComparisonFunction()中返回以后碎绎,它的作用域鏈被初始化為包含createComparsionFunction()函數(shù)的活動對象和全局變量對象。這樣匿名函數(shù)就可以訪問createComparsionFunction()中定義的變量抗果。更為重要的是createComparsionFunction()函數(shù)在執(zhí)行完畢之后筋帖,其活動對象也不會被銷毀,因為匿名函數(shù)的作用域鏈仍然在引用這個活動對象窖张。換句話說幕随,當createComparsionFunction()函數(shù)返回后,其執(zhí)行環(huán)境的作用域會被銷毀宿接,但它的活動對象仍然會留在內(nèi)存中赘淮,直到匿名函數(shù)被銷毀后,createComparsionFunction()函數(shù)的活動對象才會被銷毀睦霎。例如:

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

2.1 閉包與變量

作用域鏈的這種配置機制梢卸,引出了一個值得注意的副作用,即閉包只能取得包含函數(shù)中任何變量的最后一個值副女。

function createFunctions(){
    var result = new Array();
    for(var i=0; i<10蛤高; i++){
        result[i] = function(){
             return i; //直接把閉包賦值給數(shù)組
         };
    }
    return result;
}

上述每個函數(shù)都返回10,因為每個函數(shù)作用域鏈中都保存著createFunctions()的活動對象,所以他們引用的是同一個變量戴陡。但我們可以用以下匿名函數(shù)強制讓閉包的行為符合預(yù)期塞绿。

function createFunctions(){
    var result = new Array();
    for(var i = 0; i<10; i++){
        result[i] = function(num){
            return function (){ return num; }//按值傳遞num,定義一個匿名函數(shù)返回num
        }(i);
    }
    return result;
}

在重寫了函數(shù)之后恤批,每個函數(shù)就會返回各自不同的索引值了异吻。在這個版本中,我們沒有直接把閉包賦值給數(shù)組喜庞,而是定義了一個匿名函數(shù)诀浪,并將立即執(zhí)行該匿名函數(shù)的結(jié)果賦值給數(shù)組延都。這個匿名函數(shù)有一個參數(shù)num雷猪,并且是按照值傳遞的,所以會將變量i的當前值賦值給num晰房。而在這個匿名函數(shù)內(nèi)部求摇,又創(chuàng)建并返回了一個訪問num的閉包。這樣result數(shù)組中都有自己的num的一個副本嫉你,因此就可以返回各自不同的值了月帝。

2.2 關(guān)于this對象

我們知道,this對象是在運行時基于函數(shù)的執(zhí)行環(huán)境綁定的:在全局函數(shù)中幽污,this等于window嚷辅,而當函數(shù)被作為某個對象的方法調(diào)用時,this等于哪個對象距误。不過匿名函數(shù)的執(zhí)行環(huán)境具有全局性簸搞,因此this通常指向window。

var name = "This window";
var object = {
    name:"My Object",
    getNameFunc:function(){
        return funcion () {
            return this.name;
        }
    }
};
alert(object.getNameFunc()());//"This window"

上述例子調(diào)用匿名函數(shù)并立即執(zhí)行准潭,為什么返回的是全局name變量的值而沒有取得其包含作用域(或外部作用域)的this對象呢趁俊?
每個函數(shù)在被調(diào)用時都會自動取得兩個特殊的變量:this和arguments。內(nèi)部函數(shù)在搜索這兩個變量時刑然,只會搜索到其活動對象為止寺擂,因此永遠不可能直接訪問外部函數(shù)中的這兩個變量。不過把外部作用域中的this對象保存在一個閉包能夠訪問的變量里泼掠,就可以讓閉包訪問該對象了怔软。如下所示

var name = "This window";
var object = {
    name:"My object";
    getNameFunc: function(){
        var that = this;
        return function(){
            return that.name;
        }
    }
};
alert(object.getNameFunc()()); //"My object"

2.3 內(nèi)存泄漏

閉包有時會導致一些特殊的問題。具體來說择镇,如果閉包的作用域鏈中保存著一個HTML元素挡逼,那么就意味著該元素將無法被銷毀。

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

以上代碼創(chuàng)建了一個作為element元素事件處理程序的閉包腻豌,而這個閉包又創(chuàng)建了一個循環(huán)引用家坎。由于匿名函數(shù)中保存了一個對assignHandler()活動對象的引用嘱能,因此就導致無法減少element的引用數(shù)。只要匿名函數(shù)存在虱疏,element的引用數(shù)至少也是1惹骂,因此它的內(nèi)存將永遠無法被回收《┛颍可以通過稍微改寫一下代碼來解決析苫。

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

通過把element.id的一個副本保存在變量中,并且在閉包中引用該變量消除了循環(huán)引用穿扳。但這并不足以解決內(nèi)存泄漏的問題。必須要記坠酢:閉包會引用包含函數(shù)的整個活動對象矛物,而其中包含著element。即使閉包不直接引用element跪但,包含函數(shù)的活動對象中也會保存著一個引用履羞。因此有必要把element變量置為null。

三屡久、模仿塊級作用域

Javascript中沒有塊級作用域的概念忆首。這意味著在塊語句中定義的變量爆价,實際上是在函數(shù)中而非語句中定義的汤锨。看下面的例子厌小。

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

這個函數(shù)中定義了一個for循環(huán)筛欢,而變量i的初始值被設(shè)為0浸锨。在java/C++等語言中,變量i只會在for循環(huán)語句中有定義版姑,循環(huán)一旦結(jié)束柱搜,變量i就會被銷毀“眨可是在JavaScript中聪蘸,變量i是定義在outputNumbers()的活動對象中的。因此函數(shù)中可以隨處訪問這個變量表制。即使重新聲明健爬,Javascript也只會對后續(xù)的聲明視而不見。
用作塊級作用域(通常稱作私有作用域)的匿名函數(shù)語法如下所示:

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

將匿名函數(shù)的函數(shù)聲明包含在圓括號中表示它實際上是一個函數(shù)表達式浑劳。而緊隨其后的另一對圓括號表示立即調(diào)用這個函數(shù)。在匿名函數(shù)中定義的任何變量夭拌,都會在執(zhí)行結(jié)束時被銷毀魔熏。
無論在什么地方衷咽,只要臨時需要一些變量,就可以使用私有作用域蒜绽。這種技術(shù)經(jīng)常在全局作用域中被用在函數(shù)外部镶骗,從而限制向全局作用域中添加過多的變量和函數(shù)。通過創(chuàng)建私有作用域躲雅,每個開發(fā)人員既可以使用自己的變量鼎姊,又不必擔心搞亂全局作用域。

四相赁、私有變量

嚴格來講相寇,Javascript沒有私有成員的概念,所有的對象屬性都是公有的钮科。但有一個私有變量的概念唤衫。任何在函數(shù)中定義的變量,都可以認為是私有變量绵脯,因為不能在函數(shù)外部訪問這些變量佳励。私有變量包括函數(shù)的參數(shù)、局部變量和在函數(shù)中定義的其他函數(shù)蛆挫。
在函數(shù)內(nèi)部定義的私有變量和私有函數(shù)赃承,在函數(shù)外部不能訪問他們。如果在函數(shù)內(nèi)部創(chuàng)建一個閉包悴侵,那么閉包通過自己的作用域鏈可以訪問他們瞧剖。
我們把有權(quán)訪問私有變量和私有函數(shù)的方法稱為特權(quán)方法。有兩種在函數(shù)中創(chuàng)建特權(quán)函數(shù)的方法畜挨。第一種是在構(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ù)內(nèi)部定義了所有私有變量和函數(shù)。然后又繼續(xù)創(chuàng)建了能夠訪問這些私有成員的特權(quán)方法巴元。能夠在構(gòu)造函數(shù)中定義特權(quán)方法毡咏,是因為特權(quán)方法作為閉包有權(quán)訪問在構(gòu)造函數(shù)中定義的所有變量和函數(shù)。
除了利用特權(quán)方法訪問私有變量逮刨。利用私有和特權(quán)成員呕缭,還可以隱藏哪些不應(yīng)該被直接修改的數(shù)據(jù)。
在構(gòu)造函數(shù)中定義特權(quán)方法也有一個缺點修己,即每個實例都會創(chuàng)建同一組新方法恢总。使用靜態(tài)私有變量可以避免這個問題。

4.1 靜態(tài)私有變量

通過在私有作用域中定義私有變量或函數(shù)睬愤,同樣也可以創(chuàng)建特權(quán)方法片仿,其基本模式如下所示。

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

這個模式創(chuàng)建了一個私有作用域尤辱,并在其中封裝了一個構(gòu)造函數(shù)及其相應(yīng)的方法砂豌。在私有作用域中首先定義了私有變量和私有函數(shù)厢岂,然后又定義了構(gòu)造函數(shù)和公有方法,公有方法是在原型上定義的阳距。在聲明MyObject時沒有使用var關(guān)鍵字塔粒。記住:初始化未經(jīng)聲明的變量筐摘,總是會創(chuàng)建一個全局變量卒茬。因此MyObject就成了一個全局變量,能夠在私有作用域之外被訪問到咖熟。
這個模式與在構(gòu)造函數(shù)中定義特權(quán)方法的主要區(qū)別在于:私有變量和函數(shù)是由實例共享的圃酵。特權(quán)方法是在原型上定義的,因此所有實例都使用同一個函數(shù)馍管。
看下面具體一個例子辜昵。

(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(person2.getName()); //"Michael"
alert(person2.getName()); //"Michael"

可以看出,在上述模式下咽斧,變量name變成了一個靜態(tài)的,由所有實例共享的屬性躬存。也就是說张惹,調(diào)用setName會影響所有實例。不過岭洲,到底是使用實例變量還是使用靜態(tài)私有變量需要視情況而定宛逗。

4.2 模塊模式

模塊模式是指為單例創(chuàng)建私有變量和特權(quán)方法。所謂單例盾剩,指的是只有一個實例的對象雷激。按照慣例,Javascript利用對象字面量的方式來創(chuàng)建對象.
模塊模式通過為單例添加私有變量和特權(quán)方法使其功能得到增強告私。

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

這個模塊模式使用了一個返回對象的匿名函數(shù)屎暇。在這個匿名函數(shù)內(nèi)部,首先定義了私有變量和函數(shù)驻粟。然后根悼,將一個對象字面量作為函數(shù)的值返回。返回的對象字面量中值包含可以公開的屬性和方法蜀撑。由于這個對象字面量是在匿名函數(shù)中定義的挤巡,因此它的公有方法有權(quán)訪問私有變量和函數(shù)。從本質(zhì)上講酷麦,這個對象字面量定義的是單例的公共接口矿卑。這種模式在需要對單例進行某些初始化,同時又需要維護其私有變量時是非常有用的沃饶。

4.3增強的模塊模式

這種增強的模塊模式適合那些單例必須是某種類型的實例母廷,同時還必須添加某些屬性和方法以對其實現(xiàn)增強的情況轻黑。如下application對象必須是BaseComponent類型的實例。

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

上述例子的不同之處在于命名變量app的創(chuàng)建過程徘意,因為它必須是BaseComponent的實例苔悦。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市椎咧,隨后出現(xiàn)的幾起案子玖详,更是在濱河造成了極大的恐慌,老刑警劉巖勤讽,帶你破解...
    沈念sama閱讀 221,695評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蟋座,死亡現(xiàn)場離奇詭異,居然都是意外死亡脚牍,警方通過查閱死者的電腦和手機向臀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來诸狭,“玉大人券膀,你說我怎么就攤上這事⊙庇觯” “怎么了芹彬?”我有些...
    開封第一講書人閱讀 168,130評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長叉庐。 經(jīng)常有香客問我舒帮,道長,這世上最難降的妖魔是什么陡叠? 我笑而不...
    開封第一講書人閱讀 59,648評論 1 297
  • 正文 為了忘掉前任玩郊,我火速辦了婚禮,結(jié)果婚禮上枉阵,老公的妹妹穿的比我還像新娘译红。我一直安慰自己,他們只是感情好岭妖,可當我...
    茶點故事閱讀 68,655評論 6 397
  • 文/花漫 我一把揭開白布临庇。 她就那樣靜靜地躺著,像睡著了一般昵慌。 火紅的嫁衣襯著肌膚如雪假夺。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,268評論 1 309
  • 那天斋攀,我揣著相機與錄音已卷,去河邊找鬼。 笑死淳蔼,一個胖子當著我的面吹牛侧蘸,可吹牛的內(nèi)容都是我干的裁眯。 我是一名探鬼主播,決...
    沈念sama閱讀 40,835評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼讳癌,長吁一口氣:“原來是場噩夢啊……” “哼穿稳!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起晌坤,我...
    開封第一講書人閱讀 39,740評論 0 276
  • 序言:老撾萬榮一對情侶失蹤逢艘,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后骤菠,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體它改,經(jīng)...
    沈念sama閱讀 46,286評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,375評論 3 340
  • 正文 我和宋清朗相戀三年商乎,在試婚紗的時候發(fā)現(xiàn)自己被綠了央拖。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,505評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡鹉戚,死狀恐怖鲜戒,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情抹凳,我是刑警寧澤袍啡,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站却桶,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏蔗牡。R本人自食惡果不足惜颖系,卻給世界環(huán)境...
    茶點故事閱讀 41,873評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望辩越。 院中可真熱鬧嘁扼,春花似錦、人聲如沸黔攒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽督惰。三九已至不傅,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間赏胚,已是汗流浹背访娶。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留觉阅,地道東北人崖疤。 一個月前我還...
    沈念sama閱讀 48,921評論 3 376
  • 正文 我出身青樓秘车,卻偏偏與公主長得像,于是被迫代替她去往敵國和親劫哼。 傳聞我的和親對象是個殘疾皇子叮趴,可洞房花燭夜當晚...
    茶點故事閱讀 45,515評論 2 359

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

  • 夜鶯2517閱讀 127,724評論 1 9
  • 版本:ios 1.2.1 亮點: 1.app角標可以實時更新天氣溫度或選擇空氣質(zhì)量,建議處女座就不要選了权烧,不然老想...
    我就是沉沉閱讀 6,899評論 1 6
  • 我是黑夜里大雨紛飛的人啊 1 “又到一年六月眯亦,有人笑有人哭,有人歡樂有人憂愁豪嚎,有人驚喜有人失落搔驼,有的覺得收獲滿滿有...
    陌忘宇閱讀 8,543評論 28 53
  • 兔子雖然是枚小碩 但學校的碩士四人寢不夠 就被分到了博士樓里 兩人一間 在學校的最西邊 靠山 兔子的室友身體不好 ...
    待業(yè)的兔子閱讀 2,608評論 2 9