1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
// makeCounter函數返回的是一個新的函數,該函數對makeCounter裏的局部變量i享有使用權 function makeCounter() { // i只是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; // 報錯,i沒有定義,它只是makeCounter內部的局部變量 |
- 問題的核心
1 2 |
var foo = function(){ /* code */ }; foo(); |
1 | function(){ /* code */ }(); // SyntaxError: Unexpected token ( |
- 一波未平一波又起
1 | function foo(){ /* code */ }(); // SyntaxError: Unexpected token ) |
1 2 |
function foo(){ /* code */ } (); // SyntaxError: Unexpected token ) |
1 | (function(){ /* code */ }()); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
// 最常用的兩種寫法 (function(){ /* code */ }()); // 老道推薦寫法 (function(){ /* code */ })(); // 當然這種也可以 // 括號和JS的一些操作符(如 = && || ,等)可以在函數表達式和函數聲明上消除歧義 // 如下代碼中,解析器已經知道一個是表達式了,於是也會把另一個默認爲表達式 // 但是兩者交換則會報錯 var i = function(){ return 10; }(); true && function(){ /* code */ }(); 0, function(){ /* code */ }(); // 如果你不怕代碼晦澀難讀,也可以選擇一元運算符 !function(){ /* code */ }(); ~function(){ /* code */ }(); -function(){ /* code */ }(); +function(){ /* code */ }(); // 你也可以這樣 new function(){ /* code */ } new function(){ /* code */ }() // 帶參數 |
- 無論何時,給立即執行函數加上括號是個好習慣
1 | var i = function(){ return 10; }(); |
1 | var i = (function(){ return 10; }()); |
- 立即執行函數與閉包的曖昧關係
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
// 並不會像你想象那樣的執行,因爲i的值沒有被鎖住 // 當我們點擊鏈接的時候,其實for循環已經執行完了 // 於是在點擊的時候i的值其實已經是elems.length了 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的值傳給了lockedIndex,並且被鎖在內存中 // 儘管for循環結束後i的值已經改變,但是立即執行函數內部lockedIndex的值並不會改變 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 ); } // 你也可以這樣,但是毫無疑問上面的代碼更具有可讀性 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' ); } |
- 我爲什麼更願意稱它是“立即執行函數”而不是“自執行函數”
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
// 這是一個自執行函數,函數內部執行的是自己,遞歸調用 function foo() { foo(); } // 這是一個自執行匿名函數,因爲它沒有函數名 // 所以如果要遞歸調用自己的話必須用arguments.callee var foo = function() { arguments.callee(); }; // 這可能也算是個自執行匿名函數,但僅僅是foo標誌引用它自身 // 如果你將foo改變成其它的,你將得到一個used-to-self-execute匿名函數 var foo = function() { foo(); }; // 有些人叫它自執行匿名函數,儘管它沒有執行自己,只是立即執行而已 (function(){ /* code */ }()); // 給函數表達式添加了標誌名稱,可以方便debug // 但是一旦添加了標誌名稱,這個函數就不再是匿名的了 (function foo(){ /* code */ }()); // 立即執行函數也可以自執行,不過不常用罷了 (function(){ arguments.callee(); }()); (function foo(){ foo(); }()); |
- 最後的旁白:模塊模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
// 創建一個立即執行的匿名函數 // 該函數返回一個對象,包含你要暴露的屬性 // 如下代碼如果不使用立即執行函數,就會多一個屬性i // 如果有了屬性i,我們就能調用counter.i改變i的值 // 對我們來說這種不確定的因素越少越好 var counter = (function(){ var i = 0; return { get: function(){ return i; }, set: function( val ){ i = val; }, increment: function() { return ++i; } }; }()); // counter其實是一個對象 counter.get(); // 0 counter.set( 3 ); counter.increment(); // 4 counter.increment(); // 5 counter.i; // undefined i並不是counter的屬性 i; // ReferenceError: i is not defined (函數內部的是局部變量) |
- 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