摘要:另外,如果你想跳過這里,你可以直接跳到立即調(diào)用函數(shù)表達式進行閱讀,但是我建議你讀完整篇文章。當圓括號包裹函數(shù)時,它會默認將函數(shù)作為表達式去解析,而不是函數(shù)聲明。
原文:Immediately-Invoked Function Expression (IIFE) by Ben Alman
原譯:立即執(zhí)行函數(shù) by Murphywuwu
改增內(nèi)容: by blanu
也許你沒有注意到,我是一個對于專業(yè)術語有一點強迫癥的人。所以,當我多次聽到流行卻易產(chǎn)生誤解的術語「自執(zhí)行匿名函數(shù)」,我最終決定將我的想法寫進這篇文章里。
更進一步地說,除了提供關于該模式究竟是如何工作的全面信息,事實上我還建議了我們應該怎樣稱呼這種模式。另外,如果你想跳過這里,你可以直接跳到立即調(diào)用函數(shù)表達式進行閱讀,但是我建議你讀完整篇文章。
請理解這篇文章不是想說「我對了,你錯了」。我發(fā)自真心地想幫助人們理解看似復雜的概念,并且我認為使用前后一致的精確術語是有助于人們理解的最簡單的方式之一。
它是什么在JavaScript里,每個函數(shù),當被調(diào)用時,都會創(chuàng)建一個新的執(zhí)行上下文。因為在函數(shù)里定義的變量和函數(shù)只能在函數(shù)內(nèi)部被訪問,外部無法獲取;當調(diào)用函數(shù)時,函數(shù)提供的上下文就提供了一個非常簡單的方法創(chuàng)建私有變量。
//因為這個函數(shù)的返回值是另一個能訪問私有變量i的函數(shù),因此返回的函數(shù)實際上被提權(privileged)了 function makeCounter() { //i只能從`makeConuter`內(nèi)部訪問 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)用一次得到一個單一的值,在其他一些情況里,你甚至不需要明確的知道返回值。
它的核心現(xiàn)在,無論你定義一個函數(shù)像這樣function foo(){}或者var foo = function(){},調(diào)用時,你都需要在后面加上一對圓括號,像這樣foo()
//向下面這樣定義的函數(shù)可以通過在函數(shù)名后加一對括號進行調(diào)用,像這樣`foo()`,因為foo相對于函數(shù)表達式`function(){/* code */}`只是一個引用變量 var foo = function(){/* code */} //那這可以說明函數(shù)表達式可以通過在其后加上一對括號自己調(diào)用自己嗎? function(){ /* code */}();//SyntaxError: Unexpected token (
正如你所看到的,這里捕獲了一個錯誤。當圓括號為了調(diào)用函數(shù)出現(xiàn)在函數(shù)后面時,無論在全局環(huán)境或者局部環(huán)境里遇到了這樣的function關鍵字,默認的,它會將它當作是一個函數(shù)聲明,而不是函數(shù)表達式,如果你不明確的告訴圓括號它是一個表達式,它會將其當作沒有名字的函數(shù)聲明并且拋出一個錯誤,因為函數(shù)聲明需要一個名字。
問題1:這里我么可以思考一個問題,我們是不是也可以像這樣直接調(diào)用函數(shù)var foo = function(){console.log(1)}(),答案是可以的。
問題2:同樣的,我們還可以思考一個問題,像這樣的函數(shù)聲明在后面加上圓括號被直接調(diào)用,又會出現(xiàn)什么情況呢?請看下面的解答。
有趣的是,如果你為一個函數(shù)指定一個名字并在它后面放一對圓括號,同樣的也會拋出錯誤,但這次是因為另外一個原因。當圓括號放在一個函數(shù)表達式后面指明了這是一個被調(diào)用的函數(shù),而圓括號放在一個聲明后面便意味著完全的和前面的函數(shù)聲明分開了,此時圓括號只是一個簡單的代表一個括號(用來控制運算優(yōu)先的括號)。
//然而函數(shù)聲明語法上是無效的,它仍然是一個聲明,緊跟著的圓括號是無效的,因為圓括號里需要包含表達式 function foo(){ /* code */ }();//SyntaxError: Unexpected token //現(xiàn)在,你把一個表達式放在圓括號里,沒有拋出錯誤...但是函數(shù)也并沒有執(zhí)行,因為: function foo(){/* code */}(1) //它等同于如下,一個函數(shù)聲明跟著一個完全沒有關系的表達式: function foo(){/* code */} (1);
立即執(zhí)行函數(shù)表達式(IIFE)關于這個細節(jié),你可以閱讀Dmitry A. Soshnikov的文章:ECMA-262-3 in detail. Chapter 5. Functions (中文版本)
幸運的是,修正語法錯誤很簡單。最流行的也最被接受的方法是將函數(shù)聲明包裹在圓括號里來告訴語法分析器去表達一個函數(shù)表達式,因為在JavaScript里,圓括號不能包含聲明。因為這點,當圓括號為了包裹函數(shù)碰上了 function關鍵詞,它便知道將它作為一個函數(shù)表達式去解析而不是函數(shù)聲明。注意理解這里的圓括號和上面的圓括號遇到函數(shù)時的表現(xiàn)是不一樣的,也就是說。
當圓括號出現(xiàn)在匿名函數(shù)的末尾想要調(diào)用函數(shù)時,它會默認將函數(shù)當成是函數(shù)聲明。
當圓括號包裹函數(shù)時,它會默認將函數(shù)作為表達式去解析,而不是函數(shù)聲明。
//這兩種模式都可以被用來立即調(diào)用一個函數(shù)表達式,利用函數(shù)的執(zhí)行來創(chuàng)造私有變量 (function(){/* code */}());//Crockford recommends this one (function(){/* code */})();//But this one works just as well // 因為括號的作用就是為了消除函數(shù)表達式和函數(shù)聲明之間的差異 // 如果解釋器能預料到這是一個表達式,括號可以被省略 // 不過請參見下面的「重要筆記」 var i = function(){return 10;}(); true && function(){/*code*/}(); 0,function(){}(); //如果你并不關心返回值,或者讓你的代碼盡可能的易讀,你可以通過在你的函數(shù)前面帶上一個一元操作符來存儲字節(jié) !function(){/* code */}(); ~function(){/* code */}(); -function(){/* code */}(); +function(){/* code */}(); // 這里是另外一種方法 // 我(原文作者)不清楚new方法是否會影響性能 // 但它卻是奏效,參見http://twitter.com/kuvos/status/18209252090847232 new function(){ /* code */ } new function(){ /* code */ }() // 只有當傳入?yún)?shù)時才需要加括號關于括號的重要筆記
在一些情況下,當額外的帶著歧義的括號圍繞在函數(shù)表達式周圍是沒有必要的(因為這時候的括號已經(jīng)將其作為一個表達式去表達),但當括號用于調(diào)用函數(shù)表達式時,這仍然是一個好主意。
這樣的括號指明函數(shù)表達式將會被立即調(diào)用,并且變量將會儲存函數(shù)的結(jié)果,而不是函數(shù)本身。當這是一個非常長的函數(shù)表達式時,這可以節(jié)約其他人閱讀你代碼的時間,不用滾到頁面底部去看這個函數(shù)是否被調(diào)用。
作為規(guī)則,當你書寫清楚明晰的代碼時,有必要阻止JavaScript拋出錯誤的,同樣也有必要阻止其他開發(fā)者對你拋出錯誤WTFError!
保存閉包的狀態(tài)就像當函數(shù)通過他們的名字被調(diào)用時,參數(shù)會被傳遞,而當函數(shù)表達式被立即調(diào)用時,參數(shù)也會被傳遞。一個立即調(diào)用的函數(shù)表達式可以用來鎖定值并且有效的保存此時的狀態(tài),因為任何定義在一個函數(shù)內(nèi)的函數(shù)都可以使用外面函數(shù)傳遞進來的參數(shù)和變量(這種關系被叫做閉包)。
關于閉包的更多信息,參見 Closures explained with JavaScript
//它的運行原理可能并不像你想的那樣,因為`i`的值從來沒有被鎖定。相反的,每個鏈接,當被點擊時(循環(huán)已經(jīng)被很好的執(zhí)行完畢),因此會彈出所有元素的總數(shù),因為這是`i`此時的真實值。 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); } //而像下面這樣改寫,便可以了,因為在IIFE里,`i`值被鎖定在了`lockedInIndex`里。在循環(huán)結(jié)束執(zhí)行時,盡管`i`值的數(shù)值是所有元素的總和,但每一次函數(shù)表達式被調(diào)用時,IIFE里的`lockedInIndex`值都是`i`傳給它的值,所以當鏈接被點擊時,正確的值被彈出。 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,僅僅只用括號包裹點擊處理函數(shù),并不包含整個`addEventListener`。無論用哪種方式,這兩個例子都可以用IIFE將值鎖定,不過我發(fā)現(xiàn)前面一個例子更可讀 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); }
記住,在這最后兩個例子里,lockedInIndex可以沒有任何問題的訪問i,但是作為函數(shù)的參數(shù)使用一個不同的命名標識符可以使概念更加容易的被解釋。
立即執(zhí)行函數(shù)一個最顯著的優(yōu)勢是就算它沒有命名或者說是匿名,函數(shù)表達式也可以在沒有使用標識符的情況下被立即調(diào)用,一個閉包也可以在沒有當前變量污染的情況下被使用。
「自執(zhí)行匿名函數(shù)(Self-executing anonymous function)」有什么問題呢?你看到它已經(jīng)被提到好幾次了,但它仍未被清楚地解釋,我提議將術語改成"Immediately-Invoked Function Expression",或者,IIFE,如果你喜歡縮寫的話(發(fā)音類似“iffy”)。
什么是Immediately-Invoked Function Expression呢?顧名思義,它就是一個被立即調(diào)用的函數(shù)表達式。
我想JavaScript社區(qū)的成員應該可以在他們的文章里或者陳述里接受術語Immediately-Invoked Function Expression和IIFE,因為我感覺這樣更容易讓這個概念被理解,并且術語"self-executing anonymous function"真的也不夠精確。
//下面是個自執(zhí)行函數(shù),遞歸的調(diào)用自己本身 function foo(){foo();}; //這是一個自執(zhí)行匿名函數(shù)。因為它沒有標識符,它必須是使用`arguments.callee`屬性來調(diào)用它自己 var foo = function(){arguments.callee();}; //這也許算是一個自執(zhí)行匿名函數(shù),但是僅僅當`foo`標識符作為它的引用時,如果你將它換成用`foo`來調(diào)用同樣可行 var foo = function(){foo();}; //有些人像這樣叫"self-executing anonymous function"下面的函數(shù),即使它不是自執(zhí)行的,因為它并沒有調(diào)用它自己。然后,它只是被立即調(diào)用了而已。 (function(){ /*code*/ }()); //為函數(shù)表達式增加標識符(也就是說創(chuàng)造一個命名函數(shù))對我們的調(diào)試會有很大幫助。一旦命名,函數(shù)將不再匿名。 (function foo(){/* code */}()); //IIFEs同樣也可以自執(zhí)行,盡管,也許他不是最有用的模式 (function(){arguments.callee();}()) (function foo(){foo();}()) // 另外,下面這個表達式竟會在黑莓5上拋出錯誤,在一個被命名的函數(shù)中,該函數(shù)名是undefined。很奇妙吧… (function foo(){ foo(); }());
希望上面的例子可以讓你更加清楚的知道術語"self-executing"是有一些誤導的,因為他并不是執(zhí)行自己的函數(shù),盡管函數(shù)已經(jīng)被執(zhí)行。同樣的,匿名函數(shù)也沒用必要特別指出,因為,Immediately Invoked Function Expression,既可以是命名函數(shù)也可以匿名函數(shù)。
最后:模塊模式有趣的是:因為arguments.callee在ECMAScript 5 strict mode中被deprecated了,所以在ES5的strict mode中實際上不可能創(chuàng)建一個self-executing anonymous function
當我調(diào)用函數(shù)表達式時,如果我不至少一次的提醒我自己關于模塊模式,我便很可能會忽略它。如果你并不熟悉JavaScript里的模塊模式,它和我第一個例子很像,但是返回值用對象代替了函數(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)
模塊模式方法不僅相當?shù)膮柡Χ液唵巍7浅I俚拇a,你可以有效的利用與方法和屬性相關的命名,在一個對象里,組織全部的模塊代碼即最小化了全局變量的污染也創(chuàng)造了私人變量。
延伸閱讀希望這篇文章可以為你答疑解惑。當然,如果你產(chǎn)生了更多疑惑,你可以閱讀下面這些關于函數(shù)和模塊模式的文章。
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 Module Pattern: In-Depth - Ben Cherry
Closures explained with JavaScript - Nick Morgan
文章版權歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/81004.html
摘要:將匿名函數(shù)賦予一個變量,叫函數(shù)表達式,這是最常見的函數(shù)表達式語法形式。組成這是一個被稱為自執(zhí)行匿名函數(shù)的設計模式,主要包含兩部分。 一、函數(shù)聲明&函數(shù)表達式 1.1 函數(shù)聲明 (函數(shù)語句) showImg(https://segmentfault.com/img/bVbbqvT?w=278&h=166); (1)使用 function 關鍵字聲明一個函數(shù),再指定一個函數(shù)名,叫函數(shù)聲明。...
摘要:所以那些匿名函數(shù)附近使用括號或一些一元運算符的慣用法,就是來引導解析器,指明運算符附近是一個表達式。 Immediately-invoked Function Expression(IIFE,立即調(diào)用函數(shù)),簡單的理解就是定義完成函數(shù)之后立即執(zhí)行。因此有時候也會被稱為自執(zhí)行的匿名函數(shù)(self-executing anonymous function)。 IIFE的叫法最早見于Ben...
摘要:而且,如果你想跳過這里,你可以直接跳到立即調(diào)用函數(shù)表達式進行閱讀,但是我建議你讀完整篇文章。當圓括號包裹函數(shù)時,它會默認將函數(shù)作為表達式去解析,而不是函數(shù)聲明。什么是呢它使一個被立即調(diào)用的函數(shù)表達式。一旦命名,函數(shù)將不再匿名。 原文:http://benalman.com/news/2010/11/immediately-invoked-function-expression/#iif...
IIFE(Immediately Invoked Function Expressions) 叫做立即執(zhí)行表達式,顧名思義,該表達式一被創(chuàng)建就立即執(zhí)行。 1.對返回結(jié)果不進行處理 (function(形參){ 函數(shù)體內(nèi)容 })(實參); 2.對返回結(jié)果不進行處理 (function(形參){ 函數(shù)體內(nèi)容 }(實參)); 3.返回的是一個布爾值,然后進行取反 !function(形...
閱讀 1707·2023-04-26 02:30
閱讀 1033·2021-11-10 11:36
閱讀 1380·2021-10-08 10:14
閱讀 3496·2021-09-28 09:35
閱讀 1552·2021-08-23 09:47
閱讀 2544·2019-08-30 15:56
閱讀 1469·2019-08-30 15:44
閱讀 1751·2019-08-30 13:59