《jquery實戰(zhàn)》javascript 必知必會(2)

A2 一等公民函數(shù)

在傳統(tǒng) OO 語言里杈绸,對象包含數(shù)據(jù)和方法。這些語言里,數(shù)據(jù)和方法通常是不同的概念:javascript另辟蹊徑危彩。

與其他 js 的類型一樣,函數(shù)可以作為對象處理泳桦,如String汤徽、Number、Date等灸撰。
與其他對象類似谒府,函數(shù)可以通過javascript函數(shù)定義 —— 此時函數(shù)可以

  • 賦值給變量;
  • 賦值給對象屬性浮毯;
  • 作為參數(shù)傳遞完疫;
  • 作為函數(shù)結(jié)果返回;
  • 使用文本創(chuàng)建债蓝。

因為這個語言里壳鹤,函數(shù)與其他對象的處理方式類似,所以我們說函數(shù)是一等對象(first-class object)饰迹。
在 js 里芳誓,函數(shù)可以做不同的事余舶,也可以用不同的方式定義。

A2.1 函數(shù)表達式與函數(shù)聲明

函數(shù)不僅可以調(diào)用值锹淌,而且會證明這是正確的匿值。其中一種定義方式就是 函數(shù)聲明(function declaration)。思考下面代碼:

function doSomethingWonderful(){
    alert('Does something wonderful');
}

函數(shù)聲明由關鍵字 function 組成葛圃,接著是函數(shù)的名字千扔,還有參數(shù)或者無參及函數(shù)體。

正確的代碼里库正,定義函數(shù)名字是 doSomethingWonderful曲楚,這是無參的函數(shù)。當調(diào)用時褥符,就會執(zhí)行函數(shù)體龙誊,這里只調(diào)用了 alert() 彈出消息∨玳梗看起來沒有返回值趟大,但是在 js 里,如果沒前面提到的頂級定義的變量會作為 window 對象的屬性铣焊。 Function 對象也不例外逊朽。如果之前函數(shù)聲明在頂級層次,那么可以創(chuàng)建與函數(shù)名同名的window屬性曲伊。因此叽讳,下面的語句是等價的

function hello() { alert('Hi there!'); }
hello = function hello() { alert('Hi there!'); }
window.hello = function hello() { alert('Hi there!'); }

在支持 ECMAScript 6 規(guī)范的瀏覽器里,可以通過名為 name 的函數(shù)屬性名訪問函數(shù)坟募。
在 js 里岛蚤,函數(shù)可以作為代碼的一部分定義,而且被稱為函數(shù)表達式(function expression)懈糯。函數(shù)表達式的值可以作為函數(shù)對象涤妒。

var myFunc = function(){
    alert('this is a function');
}

定義了一個變量myFunc,賦值了一個函數(shù)赚哗。因為這是一條代碼她紫,注意這個函數(shù)沒有名字(name 屬性是空字符串),所以不能使用函數(shù)名調(diào)用它屿储。但是犁苏,因為已經(jīng)賦值給一個變量,所以可以按下面方法執(zhí)行:

myFunc();

這并非函數(shù)聲明與函數(shù)表達式的唯一區(qū)別扩所。另外一個重要的卻別:函數(shù)聲明是升起的(hoisted)(有的地方叫函數(shù)聲明的聲明提前),而函數(shù)表達式不是朴乖。為了實際理解這個概念祖屏,思考下面的例子:

函數(shù)聲明與函數(shù)表達式

例子中定義兩個函數(shù) funcDecl()funcExpr()助赞。但是實際定義之前,我們執(zhí)行了調(diào)用袁勺。首先調(diào)用(funcDecl();)成功雹食,然后調(diào)用(funcExpr();)拋出錯誤。不同的行為是因為 funcDecl() 是升起的期丰,而 funcExpr() 不是群叶。

var 聲明提前:

變量在聲明它們的腳本或函數(shù)中都是有定義的,變量聲明語句會被提前到腳本或函數(shù)的頂部钝荡。但是街立,變量初始化的操作還是在原來var語句的位置執(zhí)行,在聲明語句之前變量的值是undefined埠通。


var 聲明提前

下部分是實際執(zhí)行的過程赎离。

函數(shù)聲明提前

函數(shù)聲明是在預執(zhí)行期執(zhí)行的,就是說函數(shù)聲明是在瀏覽器準備執(zhí)行代碼的時候執(zhí)行的端辱。因為函數(shù)聲明在預執(zhí)行期被執(zhí)行梁剔,所以到了執(zhí)行期,函數(shù)聲明就不再執(zhí)行了(人家都執(zhí)行過了自然就不再執(zhí)行了)舞蔽。
函數(shù)聲明和函數(shù)表達式 - myvin

可以采用同樣的方式給變量賦值函數(shù)表達式荣病,也可以作為屬性賦值給對象:

var myObj = {
        bar: function() {}
    };

A2.2 回調(diào)函數(shù)

處理事件或計時器,或執(zhí)行 AJAX 請求時渗柿,WEB 頁面代碼的本性是異步的个盆。其中異步編程最流行的概念就是 回調(diào)函數(shù)

下面看計時器例子做祝±。可以調(diào)用計時器來觸發(fā) —— 假設5秒鐘,通過傳遞適當?shù)拈g隔時間給 window.setTimeout() 方法混槐。但是這個方法怎么讓等待時間結(jié)束后執(zhí)行想要的方法编兄?也是通過調(diào)用設置的函數(shù)實現(xiàn)的。

function hello(){alert('Hi there!');}
setTimeout(hello, 5000);

第一個參數(shù)設置給 setTimeout() 方法声登,是函數(shù)的引用狠鸳。傳遞函數(shù)作為參數(shù)與傳遞其他值沒有區(qū)別,就好像傳遞一個數(shù)一樣悯嗓。

當計時器結(jié)束時件舵,調(diào)用 hello 函數(shù)。因為 setTimeout() 方法在代碼里發(fā)起一個回調(diào)脯厨,所以函數(shù)被稱為 回調(diào)函數(shù)(callback function)铅祸。

因為只使用一次函數(shù),沒必要創(chuàng)建函數(shù)名 hello。除非在其他地方多次調(diào)用函數(shù)临梗,否則沒必要創(chuàng)建 window 屬性來存儲 hello 函數(shù)并把它傳遞給回調(diào)參數(shù)涡扼。更加優(yōu)美的代碼:

setTimeout(function() {alert('Hi there!');}, 5000);

這里在參數(shù)列表直接定義(實際是內(nèi)聯(lián)匿名函數(shù)),無須生成函數(shù)名字盟庞〕曰Γ可在 jQuery 中經(jīng)常看到這種用法什猖,沒必要賦值給頂級屬性票彪。

在這個例子里創(chuàng)建的函數(shù)或者頂級函數(shù)(作為 window 屬性),或者賦值給函數(shù)調(diào)用不狮。也可以復制 Function 對象 給對象的屬性降铸。

A2.3 尋根求源

OO 語言會自動一共一種方式來引用當前方法內(nèi)對象的實例。在JAVA和C#這種語言中荤傲,this 變量指向當前實例的引用垮耳。在JS中,類似的概念存在遂黍,也使用this關鍵字终佛,還可以訪問與函數(shù)關聯(lián)的對象。但是JS實現(xiàn)的 this 與OO語言的不同雾家。

  • 在OO語言中铃彰,this 通常引用的是聲明方法類的實例。
  • 在JS中芯咧,函數(shù)是一等對象牙捉,不會作為其他東西的一部分,對于通過 this 引用 —— 稱為函數(shù)上下文(function context) —— 不是由函數(shù)聲明來決定而是由函數(shù)調(diào)用(invoked)來確定的敬飒。

這意味著相同的函數(shù)可以有不同的上下文依賴邪铲,取決于如何調(diào)用它。這一點非常詭異无拗,但是非常有用带到。
  默認情況下,函數(shù)調(diào)用的上下文(this)屬性包含了對于調(diào)用函數(shù)引用的對象英染。我們來看看摩托車代碼的例子揽惹,修改對象的創(chuàng)建代碼如下:

    var ride = {
        make: 'Yamaha',
        model: 'XT660R',
        year: 2014,
        purchased: new Date(2015, 7, 21),
        owner: {
            name: 'Tg',
            occupation: 'bounty hunter'
        },
        whatAmI: function() {
            return this.year + ' ' + this.make + ' ' + this.model;
        }
    };

新增了代碼:

whatAmI: function() {
    return this.year + ' ' + this.make + ' ' + this.model;
}

新代碼添加了一個名為 whatAmI 的屬性,它引用了一個函數(shù) Function 實例四康。新的對象層次搪搏,使用 Function 實例賦值給了一個名為 whatAmI 的屬性。

A.2 模型展示了函數(shù)不是對象的一部分闪金,但是可以通過對象的屬性 whatAmI 訪問引用

當函數(shù)通過這個屬性引用調(diào)用時疯溺,代碼如下:

var bike = ride.whatAmI();

函數(shù)上下文(this)設置為 ride 引用的對象實例。結(jié)果變量 bike 設置為 '2014 Ymaha XT660R',因為函數(shù)用過 this 獲得了對象的屬性囱嫩。

對于頂級函數(shù)也一樣嗅辣。記住頂級函數(shù)是 window 對象的屬性,所以調(diào)用函數(shù)上下文是 window 對象挠说。

雖然是通常和隱含的行為,但是JS給了我們現(xiàn)實控制函數(shù)上下文的機會愿题。可以通過 Function 方法 call() 或者 apply() 來設置調(diào)用函數(shù)的上下文损俭。雖然看起有點瘋狂,但是作為一等對象潘酗,函數(shù)有通過 Function 構(gòu)造函數(shù)定義的方法杆兵。

call() 方法調(diào)用函數(shù)指定第一個對象參數(shù)作為上下文,而剩余的參數(shù)作為調(diào)用函數(shù)使用 —— call() 的第二個參數(shù)變成調(diào)用函數(shù)的第一個參數(shù)仔夺,依次類推琐脏。apply() 方法與此方法的工作方式類似,除了第二個參數(shù)是數(shù)組參數(shù)用來調(diào)用函數(shù)使用缸兔。

call() 和 apply()

在 JavaScript 中, 函數(shù)是對象日裙。JavaScript 函數(shù)有它的屬性和方法。
call() 和 apply() 是預定義的函數(shù)方法惰蜜。 兩個方法可用于調(diào)用函數(shù)昂拂,兩個方法的第一個參數(shù)必須是對象本身。
兩者的區(qū)別在于第二個參數(shù): apply傳入的是一個參數(shù)數(shù)組抛猖,也就是將多個參數(shù)組合成為一個數(shù)組傳入格侯,而call則作為call的參數(shù)傳入(從第二個參數(shù)開始)。
在 JavaScript 嚴格模式(strict mode)下, 在調(diào)用函數(shù)時第一個參數(shù)會成為 this 的值财著, 即使該參數(shù)不是一個對象联四。
在 JavaScript 非嚴格模式(non-strict mode)下, 如果第一個參數(shù)的值是 null 或 undefined, 它將使用全局對象替代。

  • 通過 call() 或 apply() 方法你可以設置 this 的值, 且作為已存在對象的新方法調(diào)用撑教。

為了強化概念朝墩,我們看一個例子。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Function Conctext Example</title>
</head>
<body>
    <script type="text/javascript">
    var obj1 = { handle: 'obj1' };
    var obj2 = { handle: 'obj2' };
    var obj3 = { handle: 'obj3' };
    var value = 'test';
    window.handle = 'window';

    function whoAmI(param) {
        return this.handle + '' + param;
    }

    obj1.identifyMe = whoAmI;

    console.log(whoAmI(value)); //windowtest
    console.log(obj1.identifyMe(value));//obj1test
    console.log(whoAmI.call(obj2, value));//obj2test
    console.log(whoAmI.apply(obj3, [value]));//obj3test
    </script>
</body>
</html>
列表A.1 函數(shù)上下文的值取決于函數(shù)調(diào)用

代碼里的定義了三個對象驮履,每個對象使用 handel 屬性來區(qū)分對象的引用①鱼辙。同樣也為 handel 實例添加了屬性,因此它易于辨認玫镐。

然后定義了一個頂級函數(shù)倒戏,它可以返回任意作為任意函數(shù)上下文對象的 handel 屬性的值②,并把同一個函數(shù)賦值給 obj1identifyMe 屬性③恐似《捧危可以說在 obj1 上創(chuàng)建了一個名為 identifyMe 的方法,雖然函數(shù)是和對象獨立聲明的。

obj 作為函數(shù) func 調(diào)用的上下文時葛闷,函數(shù) func 作為對象 obj 的方法憋槐。為了更進一步演示這個概念,思考下面的代碼:

console.log(obj1.identifyMe.call(obj3));  //obj3undefined

雖然作為 obj1 的屬性引用了函數(shù)淑趾,但這時調(diào)用函數(shù)上下文是 obj3,也進一步強調(diào)了函數(shù)聲明無法決定上下文而是取決于如何調(diào)用函數(shù)阳仔。

A2.4 閉包

閉包(closure)指的是 Function 實例,與其執(zhí)行需要的局部變量耦合在一起扣泊。當聲明函數(shù)時近范,它可以引用自己范圍內(nèi)的任意變量。但是對于閉包延蟹,這些變量被函數(shù)攜帶评矩,甚至在聲明點之后,已經(jīng)超出了范圍阱飘,關閉聲明斥杜。

回調(diào)函數(shù)在聲明的時候引用局部變量是一個編寫高效 javascript 代碼的必備工具。使用計時器我們來看一個例子沥匈,列表2蔗喂。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Closure Example</title>
</head>
<body>
    <div id="display"></div>
    <script type="text/javascript" src="jquery.js"></script>
    <script type="text/javascript">
    function timer() {
        var local = 1;
        window.setInterval(
            function() {
                $('#display').append(
                    '<div>At ' + new Date() + ' local=' + local + '</div>'
                );
                local++;
            },
            2000);
    }
    timer();
    </script>
</body>
</html>
列表A.2 閉包允許訪問函數(shù)聲明的范圍

這個例子中,我們創(chuàng)建了一個函數(shù) timer()咐熙,在定義之后執(zhí)行⑤弱恒。在 timer() 函數(shù)內(nèi)聲明了局部變量 local②,并且賦值為1棋恼,然后使用 window.setInterval() 方法來建立計時器返弹,每隔2秒觸發(fā)一次③。作為計時器的回調(diào)函數(shù)爪飘,我們指定了一個內(nèi)斂函數(shù)來引用局部變量 local义起,通過向頁面里名為 display 的元素附加 div 來展示當前時間和 local 變量的值①。作為回調(diào)函數(shù)的一部分师崎,local 變量的值每次遞增1④默终。

如果不了解閉包,也許會認為犁罩,因為回調(diào)函數(shù)會在 timer() 函數(shù)調(diào)用后2秒觸發(fā)齐蔽,local 變量的值在執(zhí)行回調(diào)期間是未定義的。但是床估,加載頁面并運行一小段時間含滴,會看到如圖A.4所示結(jié)果。

A.4 閉包允許回調(diào)函數(shù)訪問自己的環(huán)境丐巫,雖然環(huán)境已經(jīng)超出了范圍

雖然當 ready 處理器已經(jīng)退出谈况,local 變量已經(jīng)超出了范圍勺美,函數(shù)聲明的閉包,包含 local碑韵,仍然存在于函數(shù)的生命周期范圍內(nèi)赡茸。

閉包的另一特性,函數(shù)上下文從來不會作為閉包的一部分祝闻。例如占卧,下面的代碼不會按照我們的預期執(zhí)行。

···js
this.id = 'someID';
$('*').each(function(){
  alert(this.id);
});

記住每個函數(shù)調(diào)用都有自己的函數(shù)上下文联喘,所以屉栓,這個代碼的回調(diào)函數(shù)內(nèi)存底給 each() 函數(shù)上下文是 jQuery 集合中的元素(DOM元素),不是外部函數(shù)設置的 someID 屬性耸袜。每次調(diào)用回調(diào)函數(shù)都會輪流顯示 jQuery 集合中的每個元素的 ID。

當訪問作為函數(shù)上下文的對象時牲平,可以在局部變量里使用常見的版本來創(chuàng)建 this 引用的拷貝堤框,這個局部變量將會包括在閉包里。思考下面的代碼:

    <div id="display"></div>
    <script type="text/javascript" src="jquery.js"></script>
    <script type="text/javascript">
    this.id = 'someID';
    //var outer = this;                 //this 引用 window
    $('#display').each(function() {
        var outer = this;               // this 引用 display
        console.log(outer.id);
    });
    </script>

變量(絕大部分時間命名為 that)纵柿,變成了閉包的一部分蜈抓,因為它已經(jīng)在回調(diào)函數(shù)內(nèi)部被引用了,因此可以被訪問昂儒。outer 賦值給任意上下文沟使,而不是回調(diào)函數(shù)定義的上下文。例如渊跋,前面的代碼包括在名為 foo 的函數(shù)內(nèi)腊嗡,outer 變量會引用 foo 函數(shù)的上下文。如果前面的代碼定義在 HTML 頁面里拾酝,而沒有被函數(shù)包裹燕少,則 outer 變量將會引用 window 對象。

修改后的代碼現(xiàn)在顯示警告框來展示字符串 someID 任意多次蒿囤,只要 jQuery 集合中元素就行客们。

我們發(fā)現(xiàn)閉包在使用 jQuery 異步回調(diào)來創(chuàng)建優(yōu)美代碼時非常重要,尤其是在編寫 Ajax 請求和事件處理代碼時材诽。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末底挫,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子脸侥,更是在濱河造成了極大的恐慌建邓,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,591評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件湿痢,死亡現(xiàn)場離奇詭異涝缝,居然都是意外死亡扑庞,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評論 3 392
  • 文/潘曉璐 我一進店門拒逮,熙熙樓的掌柜王于貴愁眉苦臉地迎上來罐氨,“玉大人,你說我怎么就攤上這事滩援≌ひ” “怎么了?”我有些...
    開封第一講書人閱讀 162,823評論 0 353
  • 文/不壞的土叔 我叫張陵玩徊,是天一觀的道長租悄。 經(jīng)常有香客問我,道長恩袱,這世上最難降的妖魔是什么泣棋? 我笑而不...
    開封第一講書人閱讀 58,204評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮畔塔,結(jié)果婚禮上潭辈,老公的妹妹穿的比我還像新娘。我一直安慰自己澈吨,他們只是感情好把敢,可當我...
    茶點故事閱讀 67,228評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著谅辣,像睡著了一般修赞。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上桑阶,一...
    開封第一講書人閱讀 51,190評論 1 299
  • 那天柏副,我揣著相機與錄音,去河邊找鬼蚣录。 笑死搓扯,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的包归。 我是一名探鬼主播锨推,決...
    沈念sama閱讀 40,078評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼公壤!你這毒婦竟也來了换可?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,923評論 0 274
  • 序言:老撾萬榮一對情侶失蹤厦幅,失蹤者是張志新(化名)和其女友劉穎沾鳄,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體确憨,經(jīng)...
    沈念sama閱讀 45,334評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡译荞,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,550評論 2 333
  • 正文 我和宋清朗相戀三年瓤的,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片吞歼。...
    茶點故事閱讀 39,727評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡圈膏,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出篙骡,到底是詐尸還是另有隱情稽坤,我是刑警寧澤,帶...
    沈念sama閱讀 35,428評論 5 343
  • 正文 年R本政府宣布糯俗,位于F島的核電站尿褪,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏得湘。R本人自食惡果不足惜杖玲,卻給世界環(huán)境...
    茶點故事閱讀 41,022評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望淘正。 院中可真熱鬧天揖,春花似錦、人聲如沸跪帝。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽伞剑。三九已至,卻和暖如春市埋,著一層夾襖步出監(jiān)牢的瞬間黎泣,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評論 1 269
  • 我被黑心中介騙來泰國打工缤谎, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留抒倚,地道東北人。 一個月前我還...
    沈念sama閱讀 47,734評論 2 368
  • 正文 我出身青樓坷澡,卻偏偏與公主長得像托呕,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子频敛,可洞房花燭夜當晚...
    茶點故事閱讀 44,619評論 2 354

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

  • 前言 人生苦多项郊,快來 Kotlin ,快速學習Kotlin斟赚! 什么是Kotlin着降? Kotlin 是種靜態(tài)類型編程...
    任半生囂狂閱讀 26,201評論 9 118
  • 明鏡心明知路何 物仁神明歸事何 心知彼興歲月并 共求此生終克難
    朝如溪水閱讀 234評論 0 0
  • 九江王 英布者,六(今安徽省六安縣北)人也拗军。說起這個人任洞,也是有著傳奇色彩的蓄喇。當他還是無名之士的時候,有一天交掏,他突然...
    小司空閱讀 315評論 21 52
  • 提筆,從何寫起熊尉。 沒有辦法衡量的東西要么是無價之寶罐柳,要么一文不值。畢業(yè)一年的人裸辭狰住,起初他人沒在意张吉,時間久了都會問...
    熊lulu閱讀 252評論 0 0
  • 前幾天肮蛹,樂嘉老師的公眾號上發(fā)布了他在《淡淡》一書中的篇章《祈禱到底有用么》,主要講述了他在蛋碎前后创南,黃國倫與寇乃馨...
    心眸閱讀 691評論 2 3