摘要:前言大家學(xué)的時(shí)候,經(jīng)常遇到自執(zhí)行匿名函數(shù)的代碼,今天我們主要就來想想說一下自執(zhí)行。其實(shí),前面兩個(gè)例子里的變量,也可以換成,因?yàn)楹屯饷娴牟辉谝粋€(gè)作用于,所以不會(huì)出現(xiàn)問題,這也是匿名函數(shù)閉包的威力。
前言
大家學(xué)JavaScript的時(shí)候,經(jīng)常遇到自執(zhí)行匿名函數(shù)的代碼,今天我們主要就來想想說一下自執(zhí)行。
在詳細(xì)了解這個(gè)之前,我們來談了解一下“自執(zhí)行”這個(gè)叫法,本文對(duì)這個(gè)功能的叫法也不一定完全對(duì),主要是看個(gè)人如何理解,因?yàn)橛械娜苏f立即調(diào)用,有的人說自動(dòng)執(zhí)行,所以你完全可以按照你自己的理解來取一個(gè)名字,不過我聽很多人都叫它為“自執(zhí)行”,但作者后面說了很多,來說服大家稱呼為“立即調(diào)用的函數(shù)表達(dá)式”。
本文英文原文地址:http://benalman.com/news/2010/11/immediately-invoked-function-expression/
什么是自執(zhí)行?在JavaScript里,任何function在執(zhí)行的時(shí)候都會(huì)創(chuàng)建一個(gè)執(zhí)行上下文,因?yàn)闉閒unction聲明的變量和function有可能只在該function內(nèi)部,這個(gè)上下文,在調(diào)用function的時(shí)候,提供了一種簡(jiǎn)單的方式來創(chuàng)建自由變量或私有子function。
// 由于該function里返回了另外一個(gè)function,其中這個(gè)function可以訪問自由變量i // 所有說,這個(gè)內(nèi)部的function實(shí)際上是有權(quán)限可以調(diào)用內(nèi)部的對(duì)象。 function makeCounter() { // 只能在makeCounter內(nèi)部訪問i var i = 0; return function () { console.log(++i); }; } // 注意,counter和counter2是不同的實(shí)例,分別有自己范圍內(nèi)的i。 var counter = makeCounter(); counter(); // logs: 1 counter(); // logs: 2 var counter2 = makeCounter(); counter2(); // logs: 1 counter2(); // logs: 2 alert(i); // 引用錯(cuò)誤:i沒有defind(因?yàn)閕是存在于makeCounter內(nèi)部)。
很多情況下,我們不需要makeCounter多個(gè)實(shí)例,甚至某些case下,我們也不需要顯示的返回值,OK,往下看。
問題的核心當(dāng)你聲明類似function foo(){}或var foo = function(){}函數(shù)的時(shí)候,通過在后面加個(gè)括弧就可以實(shí)現(xiàn)自執(zhí)行,例如foo(),看代碼:
// 因?yàn)橄胂旅娴谝粋€(gè)聲明的function可以在后面加一個(gè)括弧()就可以自己執(zhí)行了,比如foo(), // 因?yàn)閒oo僅僅是function() { /* code */ }這個(gè)表達(dá)式的一個(gè)引用 var foo = function(){ /* code */ } // ...是不是意味著后面加個(gè)括弧都可以自動(dòng)執(zhí)行? function(){ /* code */ }(); // SyntaxError: Unexpected token ( //
上述代碼,如果運(yùn)行,第2個(gè)代碼會(huì)出錯(cuò),因?yàn)樵诮馕銎鹘馕鋈值膄unction或者function內(nèi)部function關(guān)鍵字的時(shí)候,默認(rèn)是認(rèn)為function聲明,而不是function表達(dá)式,如果你不顯示告訴編譯器,它默認(rèn)會(huì)聲明成一個(gè)缺少名字的function,并且拋出一個(gè)語法錯(cuò)誤信息,因?yàn)閒unction聲明需要一個(gè)名字。
函數(shù),括弧,語法錯(cuò)誤(SyntaxError)有趣的是,即便你為上面那個(gè)錯(cuò)誤的代碼加上一個(gè)名字,他也會(huì)提示語法錯(cuò)誤,只不過和上面的原因不一樣。在一個(gè)表達(dá)式后面加上括號(hào)(),該表達(dá)式會(huì)立即執(zhí)行,但是在一個(gè)語句后面加上括號(hào)(),是完全不一樣的意思,他的只是分組操作符。
// 下面這個(gè)function在語法上是沒問題的,但是依然只是一個(gè)語句 // 加上括號(hào)()以后依然會(huì)報(bào)錯(cuò),因?yàn)榉纸M操作符需要包含表達(dá)式 function foo(){ /* code */ }(); // SyntaxError: Unexpected token ) // 但是如果你在括弧()里傳入一個(gè)表達(dá)式,將不會(huì)有異常拋出 // 但是foo函數(shù)依然不會(huì)執(zhí)行 function foo(){ /* code */ }(1); // 因?yàn)樗耆葍r(jià)于下面這個(gè)代碼,一個(gè)function聲明后面,又聲明了一個(gè)毫無關(guān)系的表達(dá)式: function foo(){ /* code */ }(1);
你可以訪問ECMA-262-3 in detail. Chapter 5. Functions 獲取進(jìn)一步的信息。
自執(zhí)行函數(shù)表達(dá)式要解決上述問題,非常簡(jiǎn)單,我們只需要用大括弧將代碼的代碼全部括住就行了,因?yàn)镴avaScript里括弧()里面不能包含語句,所以在這一點(diǎn)上,解析器在解析function關(guān)鍵字的時(shí)候,會(huì)將相應(yīng)的代碼解析成function表達(dá)式,而不是function聲明。不明白的,可以看深入理解JavaScript系列2:揭秘命名函數(shù)表達(dá)式中的函數(shù)表達(dá)式和函數(shù)聲明
// 下面2個(gè)括弧()都會(huì)立即執(zhí)行 (function () { /* code */ } ()); // 推薦使用這個(gè) (function () { /* code */ })(); // 但是這個(gè)也是可以用的 // 由于括弧()和JS的&&,異或,逗號(hào)等操作符是在函數(shù)表達(dá)式和函數(shù)聲明上消除歧義的 // 所以一旦解析器知道其中一個(gè)已經(jīng)是表達(dá)式了,其它的也都默認(rèn)為表達(dá)式了 // 不過,請(qǐng)注意下一章節(jié)的內(nèi)容解釋 var i = function () { return 10; } (); true && function () { /* code */ } (); 0, function () { /* code */ } (); // 如果你不在意返回值,或者不怕難以閱讀 // 你甚至可以在function前面加一元操作符號(hào) !function () { /* code */ } (); ~function () { /* code */ } (); -function () { /* code */ } (); +function () { /* code */ } (); // 還有一個(gè)情況,使用new關(guān)鍵字,也可以用,但我不確定它的效率 // http://twitter.com/kuvos/status/18209252090847232 new function () { /* code */ } new function () { /* code */ } () // 如果需要傳遞參數(shù),只需要加上括弧()
上面所說的括弧是消除歧義的,其實(shí)壓根就沒必要,因?yàn)槔ɑ”緛韮?nèi)部本來期望的就是函數(shù)表達(dá)式,但是我們依然用它,主要是為了方便開發(fā)人員閱讀,當(dāng)你讓這些已經(jīng)自動(dòng)執(zhí)行的表達(dá)式賦值給一個(gè)變量的時(shí)候,我們看到開頭有括弧(,很快就能明白,而不需要將代碼拉到最后看看到底有沒有加括弧。
用閉包保存狀態(tài)和普通function執(zhí)行的時(shí)候傳參數(shù)一樣,自執(zhí)行的函數(shù)表達(dá)式也可以這么傳參,因?yàn)殚]包直接可以引用傳入的這些參數(shù),利用這些被lock住的傳入?yún)?shù),自執(zhí)行函數(shù)表達(dá)式可以有效地保存狀態(tài)。
下面是錯(cuò)誤的使用:
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"); }
由于變量i從來就沒背locked住。相反,當(dāng)循環(huán)執(zhí)行以后,我們?cè)邳c(diǎn)擊的時(shí)候i獲得數(shù)值,所以說無論點(diǎn)擊哪個(gè)連接,最終顯示的都是I am link #10(如果有10個(gè)a元素的話)
下面是正確的使用:
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); }
由于在自執(zhí)行函數(shù)表達(dá)式閉包內(nèi)部i的值作為locked的索引存在,在循環(huán)執(zhí)行結(jié)束以后,盡管最后i的值變成了a元素總數(shù)(例如10)但閉包內(nèi)部的lockedInIndex值是沒有改變,因?yàn)樗呀?jīng)執(zhí)行完畢了所以當(dāng)點(diǎn)擊連接的時(shí)候,結(jié)果是正確的。
或者你也可以像這樣使用:
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"); }
上面的代碼在處理函數(shù)那里使用自執(zhí)行函數(shù)表達(dá)式,而不是在addEventListener外部,這樣也可以達(dá)到locked的效果,但是前面的代碼更具有可讀性。
其實(shí),前面兩個(gè)例子里的lockedInIndex變量,也可以換成i,因?yàn)楹屯饷娴膇不在一個(gè)作用于,所以不會(huì)出現(xiàn)問題,這也是匿名函數(shù)+閉包的威力。
自執(zhí)行匿名函數(shù)和立即執(zhí)行的函數(shù)表達(dá)式區(qū)別在這篇文章中,我們一直叫自執(zhí)行函數(shù),確切的說是自執(zhí)行匿名函數(shù)(Self-executing anonymous function),但英文原文作者一直倡議使用立即調(diào)用的函數(shù)表達(dá)式(Immediately-Invoked Function Expression)這一名稱,作者又舉了一堆例子來解釋,好吧,我們來看看:
// 這是一個(gè)自執(zhí)行的函數(shù),函數(shù)內(nèi)部執(zhí)行自身,遞歸 function foo() { foo(); } // 這是一個(gè)自執(zhí)行的匿名函數(shù),因?yàn)闆]有函數(shù)名稱 // 必須使用arguments.callee屬性來執(zhí)行自己 var foo = function () { arguments.callee(); }; // 這可能也是一個(gè)自執(zhí)行的匿名函數(shù),僅僅是foo函數(shù)名引用它自身 // 如果你將foo改變成其它的,你將得到一個(gè)自執(zhí)行(used-to-self-execute)的匿名函數(shù) var foo = function () { foo(); }; // 有些人叫這個(gè)是自執(zhí)行的匿名函數(shù)(即便它不是),因?yàn)樗鼪]有調(diào)用自身,它只是立即執(zhí)行而已。 (function () { /* code */ } ()); // 為函數(shù)表達(dá)式添加一個(gè)函數(shù)名稱,可以方便Debug // 注意:一旦命名為函數(shù)添加了函數(shù)名,這個(gè)函數(shù)就不再是匿名的了 (function foo() { /* code */ } ()); // 立即調(diào)用的函數(shù)表達(dá)式(IIFE)也可以自執(zhí)行,不過可能不常用罷了 (function () { arguments.callee();} ()); (function foo() { foo(); } ());
希望這里的一些例子,可以讓大家明白,什么叫自執(zhí)行,什么叫立即調(diào)用。
Module模式注:arguments.callee在ECMAScript 5 strict mode里被廢棄了,所以在這個(gè)模式下,其實(shí)是不能用的。
在講到這個(gè)立即調(diào)用的函數(shù)表達(dá)式的時(shí)候,我又想起來了Module模式,如果你還不熟悉這個(gè)模式,我們先來看看代碼:
// 創(chuàng)建一個(gè)立即調(diào)用的匿名函數(shù)表達(dá)式 // return一個(gè)變量,其中這個(gè)變量里包含你要暴露的東西 // 返回的這個(gè)變量將賦值給counter,而不是外面聲明的function自身 var counter = (function () { var i = 0; return { get: function () { return i; }, set: function (val) { i = val; }, increment: function () { return ++i; } }; } ()); // counter是一個(gè)帶有多個(gè)屬性的對(duì)象,上面的代碼對(duì)于屬性的體現(xiàn)其實(shí)是方法 counter.get(); // 0 counter.set(3); counter.increment(); // 4 counter.increment(); // 5 counter.i; // undefined 因?yàn)閕不是返回對(duì)象的屬性 i; // 引用錯(cuò)誤: i 沒有定義(因?yàn)閕只存在于閉包)
關(guān)于更多Module模式的介紹,請(qǐng)?jiān)L問我的上一篇文章:深入理解JavaScript系列2:揭秘命名函數(shù)表達(dá)式
更多閱讀希望上面的一些例子,能讓你對(duì)立即調(diào)用的函數(shù)表達(dá)(也就是我們所說的自執(zhí)行函數(shù))有所了解,如果你想了解更多關(guān)于function和Module模式的信息,請(qǐng)繼續(xù)訪問下面列出的網(wǎng)站:
ECMA-262-3 in detail. Chapter 5. Functions. - Dmitry A. Soshnikov
Functions and function scope - Mozilla Developer Network
Named function expressions - Juriy “kangax” Zaytsev
深入理解JavaScript系列3:全面解析Module模式 - hiyangguo
Closures explained with JavaScript - Nick Morgan
關(guān)于本文本文轉(zhuǎn)自TOM大叔的深入理解JavaScript系列
【深入理解JavaScript系列】文章,包括了原創(chuàng),翻譯,轉(zhuǎn)載,整理等各類型文章,原文是TOM大叔的一個(gè)非常不錯(cuò)的專題,現(xiàn)將其重新整理發(fā)布。謝謝大叔。如果你覺得本文不錯(cuò),請(qǐng)幫忙點(diǎn)個(gè)推薦,支持一把,感激不盡。
更多優(yōu)秀文章歡迎關(guān)注我的專欄
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/78428.html
摘要:將匿名函數(shù)賦予一個(gè)變量,叫函數(shù)表達(dá)式,這是最常見的函數(shù)表達(dá)式語法形式。組成這是一個(gè)被稱為自執(zhí)行匿名函數(shù)的設(shè)計(jì)模式,主要包含兩部分。 一、函數(shù)聲明&函數(shù)表達(dá)式 1.1 函數(shù)聲明 (函數(shù)語句) showImg(https://segmentfault.com/img/bVbbqvT?w=278&h=166); (1)使用 function 關(guān)鍵字聲明一個(gè)函數(shù),再指定一個(gè)函數(shù)名,叫函數(shù)聲明。...
摘要:,對(duì)外公開的接口。更需要注意立即執(zhí)行函數(shù),返回的是一個(gè)匿名函數(shù),也是一個(gè)閉包,在這里一定要注意一個(gè)問題是在進(jìn)入可執(zhí)行上下文時(shí)創(chuàng)建的。三在方法中,注意如下代碼省略代碼的實(shí)際參數(shù)是一個(gè)自執(zhí)行匿名函數(shù),這個(gè)匿名函數(shù)接受了兩個(gè)參數(shù),但只返回了。 function DemoFunction(){ this.init = function(){ var func = (fu...
摘要:圖片轉(zhuǎn)引自的演講和兩個(gè)定時(shí)器中回調(diào)的執(zhí)行邏輯便是典型的機(jī)制。異步編程關(guān)于異步編程我的理解是,在執(zhí)行環(huán)境所提供的異步機(jī)制之上,在應(yīng)用編碼層面上實(shí)現(xiàn)整體流程控制的異步風(fēng)格。 問題背景 在一次開發(fā)任務(wù)中,需要實(shí)現(xiàn)如下一個(gè)餅狀圖動(dòng)畫,基于canvas進(jìn)行繪圖,但由于對(duì)于JS運(yùn)行環(huán)境中異步機(jī)制的不了解,所以遇到了一個(gè)棘手的問題,始終無法解決,之后在與同事交流之后才恍然大悟。問題的根節(jié)在于經(jīng)典的J...
摘要:函數(shù)名可以省略省略函數(shù)名的話該函數(shù)就成為了匿名函數(shù)被傳入函數(shù)的參數(shù)的名稱一個(gè)函數(shù)最多可以有個(gè)參數(shù)這些語句組成了函數(shù)的函數(shù)體。使用那我們通常為什么使用函數(shù)立即表達(dá)式呢,以及我如何使用呢通常情況下,只對(duì)匿名函數(shù)使用這種立即執(zhí)行的函數(shù)表達(dá)式。 注:此文只在理解立即執(zhí)行函數(shù),不在所謂原創(chuàng),文中大量引用阮一峰的JavaScript標(biāo)準(zhǔn)參考教程、MDN的JavaScript 參考文檔和深入理解Ja...
摘要:要理解立即執(zhí)行函數(shù),需要先理解一些函數(shù)的基本概念。函數(shù)表達(dá)式使用關(guān)鍵字聲明一個(gè)函數(shù),但未給函數(shù)命名,最后將匿名函數(shù)賦予一個(gè)變量,叫函數(shù)表達(dá)式,這是最常見的函數(shù)表達(dá)式語法形式。 javascript和其他編程語言相比比較隨意,所以javascript代碼中充滿各種奇葩的寫法,有時(shí)霧里看花,當(dāng)然,能理解各型各色的寫法也是對(duì)javascript語言特性更進(jìn)一步的深入理解。 ( functio...
閱讀 1724·2021-11-11 10:58
閱讀 4210·2021-09-09 09:33
閱讀 1267·2021-08-18 10:23
閱讀 1555·2019-08-30 15:52
閱讀 1632·2019-08-30 11:06
閱讀 1877·2019-08-29 14:03
閱讀 1513·2019-08-26 14:06
閱讀 2960·2019-08-26 10:39