在javaScript中,每個函數被調用時,都會建立一個新的運行上下文。因爲在一個函數裏面定義的變量和函數僅僅能在裏面訪問。在外面是不行的。上下文提供了一種很是easy的方法來建立私有性。javascript
//makeCounter函數返回另一個匿名函數,這個匿名函數能夠訪問到「私有」變量i, 好像有一點「特權」性。 function makeCounter() { // i僅僅能在makeCounter的裏面被訪問到 var i = 0; return function() { console.log( ++i ); }; } // 注意'counter'和'counter2'有他們本身的做用域'i' var counter = makeCounter(); counter(); // logs: 1 counter(); // logs: 2 var counter2 = makeCounter(); counter2(); // logs: 1 counter2(); // logs: 2 i; // ReferenceError: i沒有被定義(它僅僅存在於makeCounter的內部)
在很是多狀況下,你不需要函數返回多個「實例」。你只需要一個單例。又或者在其它狀況下,你可能連一個返回值都不需要。html
現在你定義了像function foo(){}或var foo = function(){}這種函數,這樣你獲得了一個函數的標識符。你可以經過()來調用它。如foo()。java
//像這樣定義的函數可以經過後面的()來調用,如foo(),因爲foo不過一個 //函數表達式'function(){/*...*/}'的引用 var foo = function(){ /* ... */ } //只能事後面的() ,函數表達式就能被運行,這是不合常理的。 function(){ /* ... */ }(); // SyntaxError: Unexpected token (
正如你以上所看見的,出現了異常。閉包
當JS解釋器在全局做用域或函數的裏面遇到functionkeyword時,會默以爲是一個函數聲明,而不是一個函數表達式。假設你不確切地告訴解釋器是一個表達式,它會以爲是一個沒有名字的函數聲明,因此致使語法錯誤異常,因爲函數聲明是需要名字的。ecmascript
有趣的是,假設你爲函數指定了一個名字,並且把括號放在它後面,出於不一樣的緣由。解析器也會拋出一個語法錯誤。當括號放在表達式的的後面意味着表達式是一個將被調用的函數,括號放在聲明的後面,會與前面的聲明全然分開。只表明一個分組操做符(做爲一種控制優先級的一種控制方式)。函數
<span style="font-weight: normal;">//這個函數在語法聲明上是有效的。它不過一個聲明,緊跟着的括號是無效的, //因爲分組操做符需要包含一個表達式 function foo(){ /* ... */ }(); // SyntaxError: Unexpected token ) //現在假設你在括號裏放一個表達式,沒有異常拋出 //但是函數仍然沒有運行 function foo(){ /* ... */ }( 1 ); //事實上上面的至關於下面兩個:一個是函數聲明,一個是不相關的表達式 function foo(){ /* ... */ } ( 1 )</span>
想要了解不少其它。請訪問, ECMA-262-3 in detail. Chapter 5. Functions性能
幸運的是以上語法錯誤可以簡單地被修復。最普遍的作法是告訴解析器函數表達式不過用括號括起來的,因爲在javaScript中,括號不能包含聲明。依據這一點,當解析器遇到functionkeyword時,會把它解析成一個表達式而不是函數聲明。學習
//下面兩種都可以被用來立刻運行一個函數表達式,用函數的運行上下文去建立 //私有性 (function(){ /* ... */ }()); // Crockford推薦使用這個 (function(){ /* ... */ })(); // 但是這一個也工做得很是好 //從括號或強制操做符來看,消除了函數聲明和函數表達式的歧義, //當解析器已經預期是一個表達式時,他們也可以被忽略,請看下面的樣例 var i = function(){ return 10; }(); true && function(){ /* ...*/ }(); 0, function(){ /* ... */ }(); //假設你不關心函數的返回值或你的代碼可讀性 //你可以節省一個字節。經過在函數前面加一個一元操做符 !function(){ /* code */ }(); ~function(){ /* code */ }(); -function(){ /* code */ }(); +function(){ /* code */ }(); //還有另外的版本號,但我不清楚它的實現性能會如何,用'new'keyword實現。 //運行是正常的 new function(){ /* code */ } new function(){ /* code */ }()
特別在「消除歧義」的括號(即包着函數表達式的括號),是不需要的(因爲解析器已經以爲它是表達式了)。對於用在賦值中仍是一個不錯的想法。spa
這種括號很是明顯暗示了函數表達式可以立刻被調用,並且這個變量將包含函數的返回結果。不是函數自己。這可以免去一些人閱讀你代碼的麻煩,因爲假設你的函數聲明很是長時。可能要滾動到如下才知道你這個函數有沒有被調用。code
通常來講。想寫出清晰的代碼。從技術上說要阻止解析器拋出語法錯誤異常,編寫清晰的代碼也需要阻止其它開發人員把錯誤異常拋向你。
經過傳遞參數來調用立刻函數。調用具備名稱標識的函數並同一時候向它傳遞參數,這二者的調用方式是很是像的。
不論什麼定義在一個函數裏面的函數都能訪問它外面函數的所傳遞進來的參數以及外層函數的變量(這樣的關係被稱爲閉包),一個立刻調用函數表達式可以被用來「鎖住」值和保存狀態值。
假設你想學習不少其它關於閉包的內容,請閱讀Closures explained with JavaScript.
如下例2和例3會依照你指望的運行,立刻運行函數表達式有一個很是大的缺點是它是匿名或沒有命名的。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title></title> </head> <body> <a href="###">點我</a> <a href="###">點我</a> <a href="###">點我</a> <a href="###">點我</a> <a href="###">點我</a> <script type="text/javascript"> var elems = document.getElementsByTagName( 'a' ); //例1 for ( var i = 0; i < elems.length; i++ ) { console.log(i); elems[ i ].addEventListener( 'click', function(e){ e.preventDefault(); alert( 'I am link #' + i ); }, 'false' ); } //運行結果: //I am link #5 //I am link #5 //I am link #5 //I am link #5 //I am link #5 //例2 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 ); } //運行結果: //I am link #0 //I am link #1 //I am link #2 //I am link #3 //I am link #4 //例3 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' ); } //運行結果: //I am link #0 //I am link #1 //I am link #2 //I am link #3 //I am link #4 </script> </body> </html>