'js难点之IIFE'

IIFE

据说这是js里面的一个难点,在过了第一遍《JavaScript高级程序设计第三版》之后,开始解决一下这个难点。

IIFE几种写法

方法1:最前最后加括号

1
(function(){console.log('IIFE');}());

方法2:function外面加括号

1
(function(){console.log('IIFE');})();

方法3:function前面加其他运算符,常见的是!与void 。

1
2
!function(){alert(1);}();
void function(){alert(2);}();

看上去,其中方法2和方法3的结构一样。以上说是三种方法,其实浏览器解析的时候都是一种写法。只是大部分人经常用前俩种方式,所以才区分了一下。

IIFE有什么用

1、既然俗称’自执行函数’,那么作用肯定是让函数自动执行了。比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
(function(){
/* module */
angular.module('app', ['ngRoute', 'ngDialog']);

/* route */
angular.module('app').config(function($routeProvider){
//...
});

/* factory */
angular.module('app').factory('global', function(){
//...
});

/* init */
angular.init();
})();

我截取了部分之前写的ng1代码,可以看到这个IIFE的目的是用来加载初始化ng1里面的一下模块、路由、服务等等。
2、通过结合函数闭包,保存私有变量。比如经典的for循环中使用异步的setTimeout:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var arr = [];
for(var i=0;i<10;i++){

setTimeout(function(){
console.log(i)
},1000)
}
// 输出结果是10个10

var arr = [];
for(var i=0;i<10;i++){

(function(i) {
setTimeout( function timer() {
console.log(i);
}, 1000 );
})(i)
}
// 输出结果是0,1,2,3,4,5,6,7,8,9

结构类似下面

1
2
3
(function(arr) {
//...
})(arr)

其它作用还没有见过。以后碰到了,再补上

IIFE原理 摘自本文

要理解立即执行函数,需要先理解一些函数的基本概念。

函数声明:function fnName () {…};使用function关键字声明一个函数,再指定一个函数名,叫函数声明。

函数表达式 var fnName = function () {…};使用function关键字声明一个函数,但未给函数命名,最后将匿名函数赋予一个变量,叫函数表达式,这是最常见的函数表达式语法形式。

匿名函数:function () {}; 使用function关键字声明一个函数,但未给函数命名,所以叫匿名函数,匿名函数属于函数表达式,匿名函数有很多作用,赋予一个变量则创建函数,赋予一个事件则成为事件处理程序或创建闭包等等。


重点内容
函数声明和函数表达式不同之处在于,一、Javascript引擎在解析javascript代码时会‘函数声明提升’(Function declaration Hoisting)当前执行环境(作用域)上的函数声明,而函数表达式必须等到Javascirtp引擎执行到它所在行时,才会从上而下一行一行地解析函数表达式,二、函数表达式后面可以加括号立即调用该函数,函数声明不可以,只能以fnName()形式调用。


以上内容可以具体查看本文进行详细理解。

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

1
2
3
4
5
6
7
8
//向下面这样定义的函数可以通过在函数名后加一对括号进行调用,像这样foo(),
//因为foo相对于函数表达式`function(){/* code */}`只是一个引用变量

var foo = function(){/* code */}

//那这可以说明函数表达式可以通过在其后加上一对括号自己调用自己吗?

function(){ /* code */}(); //SyntaxError: Unexpected token (

正如你所看到的,这里捕获了一个错误。当圆括号为了调用函数出现在函数后面时,无论在全局环境或者局部环境里遇到了这样的function关键字,默认的,它会将它当作是一个函数声明,而不是函数表达式,如果你不明确的告诉圆括号它是一个表达式,它会将其当作没有名字的函数声明并且抛出一个错误,因为函数声明需要一个名字。

问题1 这里我么可以思考一个问题,我们是不是也可以像这样直接调用函数 var foo = function(){console.log(1)}(),答案是可以的。
问题2 同样的,我们还可以思考一个问题,像这样的函数声明在后面加上圆括号被直接调用,又会出现什么情况呢?请看下面的解答。

继续看下面代码:

1
2
3
4
5
6
7
8
9
10
11
12
//然而函数声明语法上是无效的,它仍然是一个声明,紧跟着的圆括号是无效的,因为圆括号里需要包含表达式

function foo(){ /* code */ }();//SyntaxError: Unexpected token

//现在,你把一个表达式放在圆括号里,没有抛出错误...,但是函数也并没有执行,因为:

function foo(){/* code */}(1)

//它等同于如下,一个函数声明跟着一个完全没有关系的表达式:

function foo(){/* code */}
(1);

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

总得俩说,俩句话:

  • 当圆括号出现在匿名函数的末尾想要调用函数时,它会默认将函数当成是函数声明。
  • 当圆括号包裹函数时,它会默认将函数作为表达式去解析,而不是函数声明。
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
//这两种模式都可以被用来立即调用一个函数表达式,利用函数的执行来创造私有变量

(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:立即执行函数表达式(IIFE)
JS中小括号的用法总结(自执行函数)