原文:http://benalman.com/news/2010/11/immediately-invoked-function-expression/#iife
by Murphywuwugit
博客同步地址 当即执行函数github
可能你并无注意到,我是一个对于专业术语有一点坚持细节人。全部,当我听到流行的可是还存在误解的术语“自执行匿名函数”屡次时,我最终决定将个人想法写进这篇文章里。express
除了提供关于这种模式事实上是如何工做的一些全面的信息,更进一步的,实际上我建议咱们应该知道咱们应该叫它什么。并且,若是你想跳过这里,你能够直接跳到当即调用函数表达式进行阅读,可是我建议你读完整篇文章。闭包
在 JavaScript 里,每一个函数,当被调用时,都会建立一个新的执行上下文。由于在函数里定义的变量和函数是惟一在内部被访问的变量,而不是在外部被访问的变量,当调用函数时,函数提供的上下文提供了一个很是简单的方法建立私有变量。ide
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
这样的函数返回屡次累加值,而且能够只调用一次获得一个单一的值,在其余一些状况里,你甚至不须要明确的知道返回值。函数
如今,不管你定义一个函数像这样function foo(){}
或者var foo = function(){}
,调用时,你都须要在后面加上一对圆括号,像这样foo()
。this
//向下面这样定义的函数能够经过在函数名后加一对括号进行调用,像这样`foo()`, //由于foo相对于函数表达式`function(){/* code */}`只是一个引用变量 var foo = function(){/* code */} //那这能够说明函数表达式能够经过在其后加上一对括号本身调用本身吗? function(){ /* code */}(); //SyntaxError: Unexpected token (
正如你所看到的,这里捕获了一个错误。当圆括号为了调用函数出如今函数后面时,不管在全局环境或者局部环境里遇到了这样的function
关键字,默认的,它会将它看成是一个函数声明,而不是函数表达式,若是你不明确的告诉圆括号它是一个表达式,它会将其看成没有名字的函数声明而且抛出一个错误,由于函数声明须要一个名字。调试
问题1:这里我么能够思考一个问题,咱们是否是也能够像这样直接调用函数 var foo = function(){console.log(1)}()
,答案是能够的。
问题2:一样的,咱们还能够思考一个问题,像这样的函数声明在后面加上圆括号被直接调用,又会出现什么状况呢?请看下面的解答。code
有趣的是,若是你为一个函数指定一个名字并在它后面放一对圆括号,一样的也会抛出错误,但此次是由于另一个缘由。当圆括号放在一个函数表达式后面指明了这是一个被调用的函数,而圆括号放在一个声明后面便意味着彻底的和前面的函数声明分开了,此时圆括号只是一个简单的表明一个括号(用来控制运算优先的括号)。orm
//然而函数声明语法上是无效的,它仍然是一个声明,紧跟着的圆括号是无效的,由于圆括号里须要包含表达式 function foo(){ /* code */ }();//SyntaxError: Unexpected token //如今,你把一个表达式放在圆括号里,没有抛出错误...,可是函数也并无执行,由于: function foo(){/* code */}(1) //它等同于以下,一个函数声明跟着一个彻底没有关系的表达式: function foo(){/* code */} (1);
幸运的是,修正语法错误很简单。最流行的也最被接受的方法是将函数声明包裹在圆括号里来告诉语法分析器去表达一个函数表达式,由于在Javascript里,圆括号不能包含声明。由于这点,当圆括号为了包裹函数碰上了 function
关键词,它便知道将它做为一个函数表达式去解析而不是函数声明。注意理解这里的圆括号和上面的圆括号遇到函数时的表现是不同的,也就是说。
当圆括号出如今匿名函数的末尾想要调用函数时,它会默认将函数当成是函数声明。
当圆括号包裹函数时,它会默认将函数做为表达式去解析,而不是函数声明。
//这两种模式均可以被用来当即调用一个函数表达式,利用函数的执行来创造私有变量 (function(){/* code */}());//Crockford recommends this one,括号内的表达式表明函数当即调用表达式 (function(){/* code */})();//But this one works just as well,括号内的表达式表明函数表达式 // 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(){}(); //若是你并不关心返回值,或者让你的代码尽量的易读,你能够经过在你的函数前面带上一个一元操做符来存储字节 !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
在一些状况下,当额外的带着歧义的括号围绕在函数表达式周围是没有必要的(由于这时候的括号已经将其做为一个表达式去表达),但当括号用于调用函数表达式时,这仍然是一个好主意。
这样的括号指明函数表达式将会被当即调用,而且变量将会储存函数的结果,而不是函数自己。当这是一个很是长的函数表达式时,这能够节约比人阅读你代码的时间,不用滚到页面底部去看这个函数是否被调用。
做为规则,当你书写清楚明晰的代码时,有必要阻止 JavaScript 抛出错误的,一样也有必要阻止其余开发者对你抛出错误 WTFError
!
就像当函数经过他们的名字被调用时,参数会被传递,而当函数表达式被当即调用时,参数也会被传递。一个当即调用的函数表达式能够用来锁定值而且有效的保存此时的状态,由于任何定义在一个函数内的函数均可以使用外面函数传递进来的参数和变量(这种关系被叫作闭包)。
// 它的运行原理可能并不像你想的那样,由于`i`的值历来没有被锁定。 // 相反的,每一个连接,当被点击时(循环已经被很好的执行完毕),所以会弹出全部元素的总数, // 由于这是 `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`里。 // 在循环结束执行时,尽管`i`值的数值是全部元素的总和,但每一次函数表达式被调用时, // 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,仅仅只用括号包括点击处理函数,并不包含整个`addEventListener`。 //不管用哪一种方式,这两个例子均可以用IIFE将值锁定,不过我发现前面一个例子更可读 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
,可是做为函数的参数使用一个不一样的命名标识符可使概念更加容易的被解释。
当即执行函数一个最显著的优点是就算它没有命名或者说是匿名,函数表达式也能够在没有使用标识符的状况下被当即调用,一个闭包也能够在没有当前变量污染的状况下被使用。
你看到它已经被提到好几回了,可是它仍然不是那么清楚的被解释,我提议将术语改为"Immediately-Invoked Function Expression",或者,IIFE,若是你喜欢缩写的话。
什么是Immediately-Invoked Function Expression呢?它使一个被当即调用的函数表达式。就像引导你去调用的函数表达式。
我想Javascript社区的成员应该能够在他们的文章里或者陈述里接受术语,Immediately-Invoked Function Expression和 IIFE,由于我感受这样更容易让这个概念被理解,而且术语"self-executing anonymous function"真的也不够精确。
//下面是个自执行函数,递归的调用本身自己 function foo(){foo();}; //这是一个自执行匿名函数。由于它没有标识符,它必须是使用`arguments.callee`属性来调用它本身 var foo = function(){arguments.callee();}; //这也许算是一个自执行匿名函数,可是仅仅当`foo`标识符做为它的引用时,若是你将它换成用`foo`来调用一样可行 var foo = function(){foo();}; //有些人像这样叫'self-executing anonymous function'下面的函数,即便它不是自执行的,由于它并无调用它本身。而后,它只是被当即调用了而已。 (function(){ /*code*/ }()); //为函数表达式增长标识符(也就是说创造一个命名函数)对咱们的调试会有很大帮助。一旦命名,函数将再也不匿名。 (function foo(){/* code */}()); //IIFEs一样也能够自执行,尽管,也许他不是最有用的模式 (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(); }());
但愿上面的例子可让你更加清楚的知道术语'self-executing'是有一些误导的,由于他并非执行本身的函数,尽管函数已经被执行。一样的,匿名函数也没用必要特别指出,由于,Immediately Invoked Function Expression,既能够是命名函数也能够匿名函数。
当我调用函数表达式时,若是我不至少一次的提醒我本身关于模块模式,我便极可能会忽略它。若是你并不属性 JavaScript 里的模块模式,它和我下面的例子很像,可是返回值用对象代替了函数。
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)
模块模式方法不只至关的厉害并且简单。很是少的代码,你能够有效的利用与方法和属性相关的命名,在一个对象里,组织所有的模块代码即最小化了全局变量的污染也创造了使用变量。