深入解析JavaScript中的立即執(zhí)行函數(shù)
來(lái)源:易賢網(wǎng) 閱讀:779 次 日期:2016-06-25 13:30:31
溫馨提示:易賢網(wǎng)小編為您整理了“深入解析JavaScript中的立即執(zhí)行函數(shù)”,方便廣大網(wǎng)友查閱!

立即執(zhí)行函數(shù)模式在JavaScript中可以讓你的函數(shù)在定義后立即被執(zhí)行,下面我們就來(lái)深入解析JavaScript中的立即執(zhí)行函數(shù),需要的朋友可以參考下

它是什么

在 JavaScript 里,每個(gè)函數(shù),當(dāng)被調(diào)用時(shí),都會(huì)創(chuàng)建一個(gè)新的執(zhí)行上下文。因?yàn)樵诤瘮?shù)里定義的變量和函數(shù)是唯一在內(nèi)部被訪(fǎng)問(wèn)的變量,而不是在外部被訪(fǎng)問(wèn)的變量,當(dāng)調(diào)用函數(shù)時(shí),函數(shù)提供的上下文提供了一個(gè)非常簡(jiǎn)單的方法創(chuàng)建私有變量。

function makeCounter() {

  var i = 0;

  return function(){

    console.log(++i);

  };  

}

//記住:`counter`和`counter2`都有他們自己的變量 `i`

var counter = makeCounter();

counter();//1

counter();//2

var counter2 = makeCounter();

counter2();//1

counter2();//2

i;//ReferenceError: i is not defined(它只存在于makeCounter里)

在許多情況下,你可能并不需要makeWhatever這樣的函數(shù)返回多次累加值,并且可以只調(diào)用一次得到一個(gè)單一的值,在其他一些情況里,你甚至不需要明確的知道返回值。

它的核心

現(xiàn)在,無(wú)論你定義一個(gè)函數(shù)像這樣function foo(){}或者var foo = function(){},調(diào)用時(shí),你都需要在后面加上一對(duì)圓括號(hào),像這樣foo()。

//向下面這樣定義的函數(shù)可以通過(guò)在函數(shù)名后加一對(duì)括號(hào)進(jìn)行調(diào)用,像這樣`foo()`,

//因?yàn)閒oo相對(duì)于函數(shù)表達(dá)式`function(){/* code */}`只是一個(gè)引用變量

var foo = function(){/* code */}

//那這可以說(shuō)明函數(shù)表達(dá)式可以通過(guò)在其后加上一對(duì)括號(hào)自己調(diào)用自己?jiǎn)幔?/P>

function(){ /* code */}(); //SyntaxError: Unexpected token (

正如你所看到的,這里捕獲了一個(gè)錯(cuò)誤。當(dāng)圓括號(hào)為了調(diào)用函數(shù)出現(xiàn)在函數(shù)后面時(shí),無(wú)論在全局環(huán)境或者局部環(huán)境里遇到了這樣的function關(guān)鍵字,默認(rèn)的,它會(huì)將它當(dāng)作是一個(gè)函數(shù)聲明,而不是函數(shù)表達(dá)式,如果你不明確的告訴圓括號(hào)它是一個(gè)表達(dá)式,它會(huì)將其當(dāng)作沒(méi)有名字的函數(shù)聲明并且拋出一個(gè)錯(cuò)誤,因?yàn)楹瘮?shù)聲明需要一個(gè)名字。

問(wèn)題1:這里我么可以思考一個(gè)問(wèn)題,我們是不是也可以像這樣直接調(diào)用函數(shù) var foo = function(){console.log(1)}(),答案是可以的。

問(wèn)題2:同樣的,我們還可以思考一個(gè)問(wèn)題,像這樣的函數(shù)聲明在后面加上圓括號(hào)被直接調(diào)用,又會(huì)出現(xiàn)什么情況呢?請(qǐng)看下面的解答。

函數(shù),圓括號(hào),錯(cuò)誤

有趣的是,如果你為一個(gè)函數(shù)指定一個(gè)名字并在它后面放一對(duì)圓括號(hào),同樣的也會(huì)拋出錯(cuò)誤,但這次是因?yàn)榱硗庖粋€(gè)原因。當(dāng)圓括號(hào)放在一個(gè)函數(shù)表達(dá)式后面指明了這是一個(gè)被調(diào)用的函數(shù),而圓括號(hào)放在一個(gè)聲明后面便意味著完全的和前面的函數(shù)聲明分開(kāi)了,此時(shí)圓括號(hào)只是一個(gè)簡(jiǎn)單的代表一個(gè)括號(hào)(用來(lái)控制運(yùn)算優(yōu)先的括號(hào))。

//然而函數(shù)聲明語(yǔ)法上是無(wú)效的,它仍然是一個(gè)聲明,緊跟著的圓括號(hào)是無(wú)效的,因?yàn)閳A括號(hào)里需要包含表達(dá)式

function foo(){ /* code */ }();//SyntaxError: Unexpected token

//現(xiàn)在,你把一個(gè)表達(dá)式放在圓括號(hào)里,沒(méi)有拋出錯(cuò)誤...,但是函數(shù)也并沒(méi)有執(zhí)行,因?yàn)椋?/P>

function foo(){/* code */}(1)

//它等同于如下,一個(gè)函數(shù)聲明跟著一個(gè)完全沒(méi)有關(guān)系的表達(dá)式:

function foo(){/* code */}

(1);

立即執(zhí)行函數(shù)表達(dá)式(IIFE)

幸運(yùn)的是,修正語(yǔ)法錯(cuò)誤很簡(jiǎn)單。最流行的也最被接受的方法是將函數(shù)聲明包裹在圓括號(hào)里來(lái)告訴語(yǔ)法分析器去表達(dá)一個(gè)函數(shù)表達(dá)式,因?yàn)樵贘avascript里,圓括號(hào)不能包含聲明。因?yàn)檫@點(diǎn),當(dāng)圓括號(hào)為了包裹函數(shù)碰上了 function關(guān)鍵詞,它便知道將它作為一個(gè)函數(shù)表達(dá)式去解析而不是函數(shù)聲明。注意理解這里的圓括號(hào)和上面的圓括號(hào)遇到函數(shù)時(shí)的表現(xiàn)是不一樣的,也就是說(shuō)。

當(dāng)圓括號(hào)出現(xiàn)在匿名函數(shù)的末尾想要調(diào)用函數(shù)時(shí),它會(huì)默認(rèn)將函數(shù)當(dāng)成是函數(shù)聲明。

當(dāng)圓括號(hào)包裹函數(shù)時(shí),它會(huì)默認(rèn)將函數(shù)作為表達(dá)式去解析,而不是函數(shù)聲明。

//這兩種模式都可以被用來(lái)立即調(diào)用一個(gè)函數(shù)表達(dá)式,利用函數(shù)的執(zhí)行來(lái)創(chuàng)造私有變量

(function(){/* code */}());//Crockford recommends this one,括號(hào)內(nèi)的表達(dá)式代表函數(shù)立即調(diào)用表達(dá)式

(function(){/* code */})();//But this one works just as well,括號(hào)內(nèi)的表達(dá)式代表函數(shù)表達(dá)式

// Because the point of the parens or coercing operators is to disambiguate

// between function expressions and function declarations, they can be

// omitted when the parser already expects an expression (but please see the

// "important note" below).

var i = function(){return 10;}();

true && function(){/*code*/}();

0,function(){}();

//如果你并不關(guān)心返回值,或者讓你的代碼盡可能的易讀,你可以通過(guò)在你的函數(shù)前面帶上一個(gè)一元操作符來(lái)存儲(chǔ)字節(jié)

!function(){/* code */}();

~function(){/* code */}();

-function(){/* code */}();

+function(){/* code */}();

// Here's another variation, from @kuvos - I'm not sure of the performance

// implications, if any, of using the `new` keyword, but it works.

// http://twitter.com/kuvos/status/18209252090847232

new function(){ /* code */ }

new function(){ /* code */ }() // Only need parens if passing arguments

關(guān)于括號(hào)的重要筆記

在一些情況下,當(dāng)額外的帶著歧義的括號(hào)圍繞在函數(shù)表達(dá)式周?chē)菦](méi)有必要的(因?yàn)檫@時(shí)候的括號(hào)已經(jīng)將其作為一個(gè)表達(dá)式去表達(dá)),但當(dāng)括號(hào)用于調(diào)用函數(shù)表達(dá)式時(shí),這仍然是一個(gè)好主意。

這樣的括號(hào)指明函數(shù)表達(dá)式將會(huì)被立即調(diào)用,并且變量將會(huì)儲(chǔ)存函數(shù)的結(jié)果,而不是函數(shù)本身。當(dāng)這是一個(gè)非常長(zhǎng)的函數(shù)表達(dá)式時(shí),這可以節(jié)約比人閱讀你代碼的時(shí)間,不用滾到頁(yè)面底部去看這個(gè)函數(shù)是否被調(diào)用。

作為規(guī)則,當(dāng)你書(shū)寫(xiě)清楚明晰的代碼時(shí),有必要阻止 JavaScript 拋出錯(cuò)誤的,同樣也有必要阻止其他開(kāi)發(fā)者對(duì)你拋出錯(cuò)誤 WTFError!

保存閉包的狀態(tài)

就像當(dāng)函數(shù)通過(guò)他們的名字被調(diào)用時(shí),參數(shù)會(huì)被傳遞,而當(dāng)函數(shù)表達(dá)式被立即調(diào)用時(shí),參數(shù)也會(huì)被傳遞。一個(gè)立即調(diào)用的函數(shù)表達(dá)式可以用來(lái)鎖定值并且有效的保存此時(shí)的狀態(tài),因?yàn)槿魏味x在一個(gè)函數(shù)內(nèi)的函數(shù)都可以使用外面函數(shù)傳遞進(jìn)來(lái)的參數(shù)和變量(這種關(guān)系被叫做閉包)。

// 它的運(yùn)行原理可能并不像你想的那樣,因?yàn)閌i`的值從來(lái)沒(méi)有被鎖定。

// 相反的,每個(gè)鏈接,當(dāng)被點(diǎn)擊時(shí)(循環(huán)已經(jīng)被很好的執(zhí)行完畢),因此會(huì)彈出所有元素的總數(shù),

// 因?yàn)檫@是 `i` 此時(shí)的真實(shí)值。

var elems = document.getElementsByTagName('a');

for(var i = 0;i < elems.length; i++ ) {

  elems[i].addEventListener('click',function(e){

    e.preventDefault();

    alert('I am link #' + i)

    },false);

}

// 而像下面這樣改寫(xiě),便可以了,因?yàn)樵贗IFE里,`i`值被鎖定在了`lockedInIndex`里。

// 在循環(huán)結(jié)束執(zhí)行時(shí),盡管`i`值的數(shù)值是所有元素的總和,但每一次函數(shù)表達(dá)式被調(diào)用時(shí),

// IIFE 里的 `lockedInIndex` 值都是`i`傳給它的值,所以當(dāng)鏈接被點(diǎn)擊時(shí),正確的值被彈出。

var elems = document.getElementsByTagName('a');

for(var i = 0;i < elems.length;i++) {

  (function(lockedInIndex){

    elems[i].addEventListener('click',function(e){

      e.preventDefault();

      alert('I am link #' + lockedInIndex);

      },false)

  })(i);

}

//你同樣可以像下面這樣使用IIFE,僅僅只用括號(hào)包括點(diǎn)擊處理函數(shù),并不包含整個(gè)`addEventListener`。

//無(wú)論用哪種方式,這兩個(gè)例子都可以用IIFE將值鎖定,不過(guò)我發(fā)現(xiàn)前面一個(gè)例子更可讀

var elems = document.getElementsByTagName( 'a' );

for ( var i = 0; i < elems.length; i++ ) {

  elems[ i ].addEventListener( 'click', (function( lockedInIndex ){

    return function(e){

      e.preventDefault();

      alert( 'I am link #' + lockedInIndex );

    };

    })( i ),false);

  }

記住,在這最后兩個(gè)例子里,lockedInIndex可以沒(méi)有任何問(wèn)題的訪(fǎng)問(wèn)i,但是作為函數(shù)的參數(shù)使用一個(gè)不同的命名標(biāo)識(shí)符可以使概念更加容易的被解釋。

立即執(zhí)行函數(shù)一個(gè)最顯著的優(yōu)勢(shì)是就算它沒(méi)有命名或者說(shuō)是匿名,函數(shù)表達(dá)式也可以在沒(méi)有使用標(biāo)識(shí)符的情況下被立即調(diào)用,一個(gè)閉包也可以在沒(méi)有當(dāng)前變量污染的情況下被使用。

自執(zhí)行匿名函數(shù)(“Self-executing anonymous function”)有什么問(wèn)題呢?

你看到它已經(jīng)被提到好幾次了,但是它仍然不是那么清楚的被解釋?zhuān)姨嶙h將術(shù)語(yǔ)改成"Immediately-Invoked Function Expression",或者,IIFE,如果你喜歡縮寫(xiě)的話(huà)。

什么是Immediately-Invoked Function Expression呢?它使一個(gè)被立即調(diào)用的函數(shù)表達(dá)式。就像引導(dǎo)你去調(diào)用的函數(shù)表達(dá)式。

我想Javascript社區(qū)的成員應(yīng)該可以在他們的文章里或者陳述里接受術(shù)語(yǔ),Immediately-Invoked Function Expression和 IIFE,因?yàn)槲腋杏X(jué)這樣更容易讓這個(gè)概念被理解,并且術(shù)語(yǔ)"self-executing anonymous function"真的也不夠精確。

//下面是個(gè)自執(zhí)行函數(shù),遞歸的調(diào)用自己本身

function foo(){foo();};

//這是一個(gè)自執(zhí)行匿名函數(shù)。因?yàn)樗鼪](méi)有標(biāo)識(shí)符,它必須是使用`arguments.callee`屬性來(lái)調(diào)用它自己

var foo = function(){arguments.callee();};

//這也許算是一個(gè)自執(zhí)行匿名函數(shù),但是僅僅當(dāng)`foo`標(biāo)識(shí)符作為它的引用時(shí),如果你將它換成用`foo`來(lái)調(diào)用同樣可行

var foo = function(){foo();};

//有些人像這樣叫'self-executing anonymous function'下面的函數(shù),即使它不是自執(zhí)行的,因?yàn)樗](méi)有調(diào)用它自己。然后,它只是被立即調(diào)用了而已。

(function(){ /*code*/ }());

//為函數(shù)表達(dá)式增加標(biāo)識(shí)符(也就是說(shuō)創(chuàng)造一個(gè)命名函數(shù))對(duì)我們的調(diào)試會(huì)有很大幫助。一旦命名,函數(shù)將不再匿名。

(function foo(){/* code */}());

//IIFEs同樣也可以自執(zhí)行,盡管,也許他不是最有用的模式

(function(){arguments.callee();}())

(function foo(){foo();}())

// One last thing to note: this will cause an error in BlackBerry 5, because

// inside a named function expression, that name is undefined. Awesome, huh?

(function foo(){ foo(); }());

希望上面的例子可以讓你更加清楚的知道術(shù)語(yǔ)'self-executing'是有一些誤導(dǎo)的,因?yàn)樗⒉皇菆?zhí)行自己的函數(shù),盡管函數(shù)已經(jīng)被執(zhí)行。同樣的,匿名函數(shù)也沒(méi)用必要特別指出,因?yàn)?,Immediately Invoked Function Expression,既可以是命名函數(shù)也可以匿名函數(shù)。

最后:模塊模式

當(dāng)我調(diào)用函數(shù)表達(dá)式時(shí),如果我不至少一次的提醒我自己關(guān)于模塊模式,我便很可能會(huì)忽略它。如果你并不屬性 JavaScript 里的模塊模式,它和我下面的例子很像,但是返回值用對(duì)象代替了函數(shù)。

var counter = (function(){

  var i = 0;

  return {

    get: function(){

      return i;

    },

    set: function(val){

      i = val;

    },

    increment: function(){

      return ++i;

    }

  }

  }());

  counter.get();//0

  counter.set(3);

  counter.increment();//4

  counter.increment();//5

  conuter.i;//undefined (`i` is not a property of the returned object)

  i;//ReferenceError: i is not defined (it only exists inside the closure)

模塊模式方法不僅相當(dāng)?shù)膮柡Χ液?jiǎn)單。非常少的代碼,你可以有效的利用與方法和屬性相關(guān)的命名,在一個(gè)對(duì)象里,組織全部的模塊代碼即最小化了全局變量的污染也創(chuàng)造了使用變量。

更多信息請(qǐng)查看網(wǎng)絡(luò)編程
易賢網(wǎng)手機(jī)網(wǎng)站地址:深入解析JavaScript中的立即執(zhí)行函數(shù)
由于各方面情況的不斷調(diào)整與變化,易賢網(wǎng)提供的所有考試信息和咨詢(xún)回復(fù)僅供參考,敬請(qǐng)考生以權(quán)威部門(mén)公布的正式信息和咨詢(xún)?yōu)闇?zhǔn)!

2025國(guó)考·省考課程試聽(tīng)報(bào)名

  • 報(bào)班類(lèi)型
  • 姓名
  • 手機(jī)號(hào)
  • 驗(yàn)證碼
關(guān)于我們 | 聯(lián)系我們 | 人才招聘 | 網(wǎng)站聲明 | 網(wǎng)站幫助 | 非正式的簡(jiǎn)要咨詢(xún) | 簡(jiǎn)要咨詢(xún)須知 | 新媒體/短視頻平臺(tái) | 手機(jī)站點(diǎn) | 投訴建議
工業(yè)和信息化部備案號(hào):滇ICP備2023014141號(hào)-1 云南省教育廳備案號(hào):云教ICP備0901021 滇公網(wǎng)安備53010202001879號(hào) 人力資源服務(wù)許可證:(云)人服證字(2023)第0102001523號(hào)
云南網(wǎng)警備案專(zhuān)用圖標(biāo)
聯(lián)系電話(huà):0871-65099533/13759567129 獲取招聘考試信息及咨詢(xún)關(guān)注公眾號(hào):hfpxwx
咨詢(xún)QQ:1093837350(9:00—18:00)版權(quán)所有:易賢網(wǎng)
云南網(wǎng)警報(bào)警專(zhuān)用圖標(biāo)