8艺谆、函數(shù)表達(dá)式1(《JS高級》筆記)

定義函數(shù)的方式有兩種:一種是函數(shù)聲明榨惰,另一種是函數(shù)表達(dá)式。函數(shù)聲明的語法是這樣的静汤。

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

說明:Firefox琅催、Safari居凶、ChromeOpera都給函數(shù)定義了一個(gè)非標(biāo)準(zhǔn)的name屬性,通過這個(gè)屬性可以訪問到給定函數(shù)指定的名字藤抡。

console.log(functionName.name);//"functionName"

關(guān)于函數(shù)聲明侠碧,它的一個(gè)重要特征就是函數(shù)聲明提升,意思是在執(zhí)行代碼之前會先讀取函數(shù)聲明缠黍。這意味著可以版函數(shù)聲明放在調(diào)用它的語句后面弄兜。

sayHi();
function sayHi(){
    console.log("Hi");
}

第二種創(chuàng)建函數(shù)的方式是使用函數(shù)表達(dá)式。函數(shù)表達(dá)式有幾種不同的語法形式瓷式,下面是最常見的一種替饿。

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

說明:這種情況下創(chuàng)建的函數(shù)叫做匿名函數(shù),因?yàn)?code>function關(guān)鍵字后面沒有標(biāo)識符贸典。匿名函數(shù)的name屬性是空字符串视卢。函數(shù)表達(dá)式與其他表達(dá)式一樣,在使用前必須先賦值廊驼。

理解函數(shù)提升的關(guān)鍵据过,就是理解函數(shù)聲明與函數(shù)表達(dá)式之間的區(qū)別,如:

var condition = true;

if(condition){
    function sayHi(){
        alert("Hi!");
    }
} else {
    function sayHi(){
        alert("Yo!");
    }
}

sayHi();

說明:表面上看以上代碼表示在conditiontrue時(shí)妒挎,使用一個(gè)sayHi()的定義喧伞;否則使用另一個(gè)定義汉嗽。實(shí)際上悼沿,這是一個(gè)無效語法丧叽,JS引擎會嘗試修正錯(cuò)誤,將其轉(zhuǎn)換為合理的狀態(tài)庸队。但是各瀏覽器修正錯(cuò)誤的做法并不一致积蜻,大多數(shù)瀏覽器返回第二個(gè)聲明,忽略condition彻消。因?yàn)檫@種使用方式很危險(xiǎn)竿拆,不應(yīng)該出現(xiàn)。不過宾尚,如果使用函數(shù)表達(dá)式就沒有什么問題了丙笋。

var condition = true;
var sayHi;
//never do this!
if(condition){
    sayHi = function(){
        alert("Hi!");
    };
} else {
    sayHi = function(){
        alert("Yo!");
    };
}

sayHi();

一、遞歸

遞歸函數(shù)是在一個(gè)函數(shù)通過名字調(diào)用自身的情況下構(gòu)成煌贴,如下所示:

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

這是一個(gè)經(jīng)典的遞歸階乘函數(shù)御板。雖然表面上看沒什么問題,但是下面的代碼卻可能導(dǎo)致其出錯(cuò)牛郑。

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

說明:雖然我們先將factorial()函數(shù)賦給了anotherFactorial怠肋,但是此后將factorial()置為空,當(dāng)調(diào)用anotherFactorial()時(shí)淹朋,其內(nèi)部需要調(diào)用factorial()笙各,而此時(shí)卻為null钉答,于是出錯(cuò)。這種情況下杈抢,使用arguments.callee可以解決問題数尿。

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

說明:arguments.callee是一個(gè)指向正在執(zhí)行的函數(shù)的指針,因此可以用它來實(shí)現(xiàn)對函數(shù)的遞歸調(diào)用惶楼。但是在嚴(yán)格模式下右蹦,不能這樣使用,可以使用命名函數(shù)表達(dá)式來達(dá)到相同的結(jié)果:

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

以上代碼創(chuàng)建了一個(gè)名為f的命名函數(shù)歼捐,然后將其賦值給factorical何陆,即便把函數(shù)賦值給了另一個(gè)變量,函數(shù)名f仍然有效窥岩。

二甲献、閉包

閉包是指有權(quán)訪問另一個(gè)函數(shù)作用域中的變量的函數(shù)宰缤。有關(guān)如何創(chuàng)建作用域鏈以及作用域鏈有什么用處的細(xì)節(jié)颂翼,對徹底理解閉包至關(guān)重要。當(dāng)某個(gè)函數(shù)被調(diào)用時(shí)慨灭,會創(chuàng)建一個(gè)執(zhí)行環(huán)境及相應(yīng)的作用域鏈朦乏。然后,使用arguments和其他命名參數(shù)的值來初始化函數(shù)的活動對象(就是函數(shù)體中定義的變量)氧骤。但在作用域鏈中呻疹,外部函數(shù)的活動對象始終處于第二位,外部函數(shù)的外部函數(shù)的活動對象處于第三位筹陵,一直到作用域鏈終點(diǎn)的全局執(zhí)行環(huán)境刽锤。舉例說明:

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的活動對象并思。全局執(zhí)行環(huán)境對象(包含resultcompare)在compa()執(zhí)行環(huán)境的作用域鏈中則處于第二位。如圖所示:

1

說明:

  • 其實(shí)很好理解语稠,就是在調(diào)用compare()函數(shù)時(shí)宋彼,會創(chuàng)建一個(gè)作用域鏈,這個(gè)作用域鏈像一個(gè)指針數(shù)組仙畦,首個(gè)元素都是指向其本身的活動對象(就是其本身函數(shù)體中定義的一些變量)输涕,其次就是包含函數(shù)(即本函數(shù)的上一層或叫包含本函數(shù)的函數(shù)或環(huán)境,此處沒有慨畸,則上一層就是全局變量對象)的活動對象莱坎,此處為全局變量對象。

  • 在創(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è)活動對象(在此作為變量對象使用)被創(chuàng)建并被推入執(zhí)行環(huán)境作用域鏈的前端烫葬。對于這個(gè)例子中compare()函數(shù)的執(zhí)行環(huán)境而言界弧,其作用域鏈中包含兩個(gè)變量對象:本地活動對象和全局變量對象。顯然搭综,作用域鏈本質(zhì)上是一個(gè)指向變量對象的指針列表垢箕,它只引用但不實(shí)際包含變量對象

  • 無論什么時(shí)候在函數(shù)中訪問一個(gè)變量兑巾,就會從作用域鏈中搜索具有相應(yīng)名字的變量条获。一般來講,當(dāng)函數(shù)執(zhí)行完畢之后蒋歌,局部活動對象就會被銷毀帅掘,內(nèi)存中僅保存全局作用域。但是閉包的情況又有所不同堂油。

在另一個(gè)函數(shù)內(nèi)部定義的函數(shù)會將包含函數(shù)(即外部函數(shù))的活動對象添加到它的作用域鏈中修档。如:

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

如上,在createComparisonFunction()函數(shù)內(nèi)部定義的匿名函數(shù)的作用域鏈中府框,實(shí)際上會包含外部函數(shù)createComparisonFunction()的活動對象吱窝,如圖所示:

2

代碼如下所示:

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

說明:在匿名函數(shù)從createComparisonFunction()中被返回后,它的作用域鏈被初始化為包含createComparisonFunction()函數(shù)的活動對象和全局變量(從匿名函數(shù)的作用域鏈的后面兩個(gè)位置可以看到)迫靖。這樣院峡,匿名函數(shù)就可以訪問在createComparisonFunction()函數(shù)中定義的所有變量。更為重要的是系宜,createComparisonFunction()函數(shù)在執(zhí)行完畢后照激,其活動對象也不會被銷毀,因?yàn)槟涿瘮?shù)的作用域鏈仍然在引用這個(gè)活動對象蜈首,而直到匿名函數(shù)被銷毀后实抡,createComparisonFunction()的活動對象才會被銷毀。如:

var compareNames = createComparisonFunction("name");
var result = compareNames({name: "Nicholas"}, {name: "Greg"});
compareNames = null;

最后一行是將匿名函數(shù)銷毀欢策。

2.1 閉包與變量

作用域鏈的這種配置機(jī)制引出了一個(gè)值得注意的副作用吆寨,即閉包只能取得包含函數(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ù)的活動對象想帅,所以它們引用的都是同一個(gè)變量i。當(dāng)createFunctions()函數(shù)返回后啡莉,變量i的值是10港准,于是每個(gè)函數(shù)都返回10這里數(shù)組中的每個(gè)匿名函數(shù)就是一個(gè)閉包咧欣∏掣祝可以通過創(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;
}
//換一種寫法
function createFunctions(){
    var result = new Array();
    function indexFunc(num){
        return function(){
            return num;
        }
    }
    for (var i=0; i < 10; i++){
        result[i] = indexFunc(i);
    }
    return result;
}

說明:以上代碼中魄咕,沒有直接把閉包賦值給數(shù)組衩椒,而是定義了一個(gè)匿名函數(shù),并將立即執(zhí)行該匿名函數(shù)的結(jié)果賦給數(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變量副本荔睹,因此就可以返回各自的索引了狸演。

2.2 關(guān)于this變量

我們知道,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(當(dāng)然劝篷,在通過call()applay()改變函數(shù)執(zhí)行環(huán)境的情況下哨鸭,this就會指向其他對象)。但有時(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)格模式下)

說明:這里的this是指window,那為什么不是取得其包含作用域(或外部作用域)的this對象呢哈恰?這是因?yàn)橹还溃總€(gè)函數(shù)在被調(diào)用時(shí)都會自動取得兩個(gè)特殊的變量:thisarguments志群。內(nèi)部函數(shù)在搜索這兩個(gè)變量時(shí),只會搜索到其活動對象為止(就是從全局活動對象中開始一層一層向內(nèi)部搜索蛔钙,只要搜索到this變量锌云,則停止搜索),因此永遠(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()());  //"MyObject"

說明:this的指向在函數(shù)定義的時(shí)候是不能確定的石洗,只有在函數(shù)執(zhí)行的時(shí)候才能確定,實(shí)際上紧显,this的最終指向的是那個(gè)調(diào)用它的對象讲衫。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市孵班,隨后出現(xiàn)的幾起案子涉兽,更是在濱河造成了極大的恐慌,老刑警劉巖篙程,帶你破解...
    沈念sama閱讀 212,383評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件枷畏,死亡現(xiàn)場離奇詭異,居然都是意外死亡虱饿,警方通過查閱死者的電腦和手機(jī)拥诡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來氮发,“玉大人渴肉,你說我怎么就攤上這事∷幔” “怎么了仇祭?”我有些...
    開封第一講書人閱讀 157,852評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長颈畸。 經(jīng)常有香客問我乌奇,道長,這世上最難降的妖魔是什么眯娱? 我笑而不...
    開封第一講書人閱讀 56,621評論 1 284
  • 正文 為了忘掉前任礁苗,我火速辦了婚禮,結(jié)果婚禮上困乒,老公的妹妹穿的比我還像新娘寂屏。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,741評論 6 386
  • 文/花漫 我一把揭開白布迁霎。 她就那樣靜靜地躺著吱抚,像睡著了一般。 火紅的嫁衣襯著肌膚如雪考廉。 梳的紋絲不亂的頭發(fā)上秘豹,一...
    開封第一講書人閱讀 49,929評論 1 290
  • 那天,我揣著相機(jī)與錄音昌粤,去河邊找鬼既绕。 笑死,一個(gè)胖子當(dāng)著我的面吹牛涮坐,可吹牛的內(nèi)容都是我干的凄贩。 我是一名探鬼主播,決...
    沈念sama閱讀 39,076評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼袱讹,長吁一口氣:“原來是場噩夢啊……” “哼疲扎!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起捷雕,我...
    開封第一講書人閱讀 37,803評論 0 268
  • 序言:老撾萬榮一對情侶失蹤椒丧,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后救巷,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體壶熏,經(jīng)...
    沈念sama閱讀 44,265評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,582評論 2 327
  • 正文 我和宋清朗相戀三年浦译,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了棒假。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,716評論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡管怠,死狀恐怖淆衷,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情渤弛,我是刑警寧澤,帶...
    沈念sama閱讀 34,395評論 4 333
  • 正文 年R本政府宣布甚带,位于F島的核電站她肯,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏鹰贵。R本人自食惡果不足惜晴氨,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,039評論 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望碉输。 院中可真熱鬧籽前,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至挠锥,卻和暖如春众羡,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蓖租。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評論 1 266
  • 我被黑心中介騙來泰國打工粱侣, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蓖宦。 一個(gè)月前我還...
    沈念sama閱讀 46,488評論 2 361
  • 正文 我出身青樓齐婴,卻偏偏與公主長得像,于是被迫代替她去往敵國和親稠茂。 傳聞我的和親對象是個(gè)殘疾皇子柠偶,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,612評論 2 350

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

  • 定義函數(shù)的方式有兩種:函數(shù)聲明和函數(shù)表達(dá)式。 函數(shù)聲明的一個(gè)重要特征就是函數(shù)聲明提升主慰,意思是在執(zhí)行代碼前會先讀取函...
    oWSQo閱讀 661評論 0 0
  • 繼承 一嚣州、混入式繼承 二、原型繼承 利用原型中的成員可以被和其相關(guān)的對象共享這一特性共螺,可以實(shí)現(xiàn)繼承该肴,這種實(shí)現(xiàn)繼承的...
    magic_pill閱讀 1,054評論 0 3
  • 定義函數(shù)的方法有兩種:函數(shù)聲明和函數(shù)表達(dá)式。 函數(shù)聲明 使用函數(shù)聲明時(shí)藐不,函數(shù)聲明會被提升至當(dāng)前作用域最前面匀哄。 但是...
    exialym閱讀 368評論 0 3
  • 寒雪梅中盡,春風(fēng)柳上歸 鶯鶯嬌語醉雏蛮,燕燕雙于飛 風(fēng)光今日好涎嚼,徘徊不肯回 西冷松柏下,小小待相隨
    徐不二閱讀 355評論 0 3
  • 我是薛姑娘的丫頭鶯兒。 我原叫黃金鶯的犀概,因姑娘覺得這名字太拗口立哑,便改叫了鶯兒。改的很好姻灶,連我都覺得原來的名字不僅難...
    愛逛大觀園的劉姥姥閱讀 1,007評論 6 12