原文:http://benalman.com/news/2010/11/immediately-invoked-function-expression/#iife
by Murphywuwu

1.它是什么

在 JavaScript 里,每个函数,当被调用时,都会创建一个新的执行上下文。因为在函数里定义的变量和函数是唯一在内部被访问的变量,而不是在外部被访问的变量,当调用函数时,函数提供的上下文提供了一个非常简单的方法创建私有变量。

  1. function makeCounter() {
  2. var i = 0;
  3. return function(){
  4. console.log(++i);
  5. };
  6. }
  7. //记住:`counter`和`counter2`都有他们自己的变量 `i`
  8. var counter = makeCounter();
  9. counter();//1
  10. counter();//2
  11. var counter2 = makeCounter();
  12. counter2();//1
  13. counter2();//2
  14. i;//ReferenceError: i is not defined(它只存在于makeCounter里)

在许多情况下,你可能并不需要makeWhatever这样的函数返回多次累加值,并且可以只调用一次得到一个单一的值,在其他一些情况里,你甚至不需要明确的知道返回值。

它的核心

现在,无论你定义一个函数像这样function foo(){}或者var foo = function(){},调用时,你都需要在后面加上一对圆括号,像这样foo()。

  1. //向下面这样定义的函数可以通过在函数名后加一对括号进行调用,像这样`foo()`,
  2. //因为foo相对于函数表达式`function(){/* code */}`只是一个引用变量
  3. var foo = function(){/* code */}
  4. //那这可以说明函数表达式可以通过在其后加上一对括号自己调用自己吗?
  5. function(){ /* code */}(); //SyntaxError: Unexpected token (

正如你所看到的,这里捕获了一个错误。当圆括号为了调用函数出现在函数后面时,无论在全局环境或者局部环境里遇到了这样的function关键字,默认的,它会将它当作是一个函数声明,而不是函数表达式,如果你不明确的告诉圆括号它是一个表达式,它会将其当作没有名字的函数声明并且抛出一个错误,因为函数声明需要一个名字。
问题1:这里我么可以思考一个问题,我们是不是也可以像这样直接调用函数 var foo = function(){console.log(1)}(),答案是可以的。
问题2:同样的,我们还可以思考一个问题,像这样的函数声明在后面加上圆括号被直接调用,又会出现什么情况呢?请看下面的解答。

函数,圆括号,错误

有趣的是,如果你为一个函数指定一个名字并在它后面放一对圆括号,同样的也会抛出错误,但这次是因为另外一个原因。当圆括号放在一个函数表达式后面指明了这是一个被调用的函数,而圆括号放在一个声明后面便意味着完全的和前面的函数声明分开了,此时圆括号只是一个简单的代表一个括号(用来控制运算优先的括号)。

  1. //然而函数声明语法上是无效的,它仍然是一个声明,紧跟着的圆括号是无效的,因为圆括号里需要包含表达式
  2. function foo(){ /* code */ }();//SyntaxError: Unexpected token
  3. //现在,你把一个表达式放在圆括号里,没有抛出错误...,但是函数也并没有执行,因为:
  4. function foo(){/* code */}(1)
  5. //它等同于如下,一个函数声明跟着一个完全没有关系的表达式:
  6. function foo(){/* code */}
  7. (1);

2.立即执行函数表达式(IIFE)

幸运的是,修正语法错误很简单。最流行的也最被接受的方法是将函数声明包裹在圆括号里来告诉语法分析器去表达一个函数表达式,因为在Javascript里,圆括号不能包含声明。因为这点,当圆括号为了包裹函数碰上了 function关键词,它便知道将它作为一个函数表达式去解析而不是函数声明。注意理解这里的圆括号和上面的圆括号遇到函数时的表现是不一样的,也就是说。

  • 当圆括号出现在匿名函数的末尾想要调用函数时,它会默认将函数当成是函数声明。
  • 当圆括号包裹函数时,它会默认将函数作为表达式去解析,而不是函数声明。 ```typescript (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

  1. <a name="ohSqm"></a>
  2. ## 保存闭包的状态
  3. 就像当函数通过他们的名字被调用时,参数会被传递,而当函数表达式被立即调用时,_参数也会被传递。_一个立即调用的函数表达式可以用来锁定值并且有效地保存此时的状态,因为任何定义在一个函数内的函数都可以出使用外面函数传递进来的参量和变量(这种关系称为闭包)。
  4. ```jsx
  5. // 它的运行原理可能并不像你想的那样,因为`i`的值从来没有被锁定。
  6. // 相反的,每个链接,当被点击时(循环已经被很好的执行完毕),因此会弹出所有元素的总数,
  7. // 因为这是 `i` 此时的真实值。
  8. var elems = document.getElementsByTagName('a');
  9. for(var i = 0;i < elems.length; i++ ) {
  10. elems[i].addEventListener('click',function(e){
  11. e.preventDefault();
  12. alert('I am link #' + i)
  13. },false);
  14. }
  15. // 而像下面这样改写,便可以了,因为在IIFE里,`i`值被锁定在了`lockedInIndex`里。
  16. // 在循环结束执行时,尽管`i`值的数值是所有元素的总和,但每一次函数表达式被调用时,
  17. // IIFE 里的 `lockedInIndex` 值都是`i`传给它的值,所以当链接被点击时,正确的值被弹出。
  18. var elems = document.getElementsByTagName('a');
  19. for(var i = 0;i < elems.length;i++) {
  20. (function(lockedInIndex){
  21. elems[i].addEventListener('click',function(e){
  22. e.preventDefault();
  23. alert('I am link #' + lockedInIndex);
  24. },false)
  25. })(i);
  26. }
  27. //你同样可以像下面这样使用IIFE,仅仅只用括号包括点击处理函数,并不包含整个`addEventListener`。
  28. //无论用哪种方式,这两个例子都可以用IIFE将值锁定,不过我发现前面一个例子更可读
  29. var elems = document.getElementsByTagName( 'a' );
  30. for ( var i = 0; i < elems.length; i++ ) {
  31. elems[ i ].addEventListener( 'click', (function( lockedInIndex ){
  32. return function(e){
  33. e.preventDefault();
  34. alert( 'I am link #' + lockedInIndex );
  35. };
  36. })( i ),false);
  37. }

立即执行函数一个最显著的优势是就算它没有命名或者说是匿名,函数表达式也可以在没有使用标识符的情况下被立即调用,一个闭包也可以在没有当前变量污染的情况下被使用。

3.模块模式

模块模式方法不仅相当的厉害而且简单。非常少的代码,你可以有效的利用与方法和属性相关的命名,在一个对象里,组织全部的模块代码既最小化了全局变量的污染,又创造了使用变量

  1. var counter = (function(){
  2. var i = 0;
  3. return {
  4. get: function(){
  5. return i;
  6. },
  7. set: function(val){
  8. i = val;
  9. },
  10. increment: function(){
  11. return ++i;
  12. }
  13. }
  14. }());
  15. counter.get();//0
  16. counter.set(3);
  17. counter.increment();//4
  18. counter.increment();//5

模块模式中,无法访问立即执行函数内部的变量。

  1. conuter.i; //undefined (`i` is not a property of the returned object)
  2. i; //ReferenceError: i is not defined (it only exists inside the closure)