想要理解立即执行函数,先了解函数声明与函数表达式的区别:
函数声明
1 |
function ([param,[, param,[..., param]]]) { |
函数表达式
1 |
let function_expression = function [name]([param1[, param2[, ..., paramN]]]) { |
name可以省略,这种情况下的函数时匿名函数。
两者的不同之处
- 函数声明可在当前作用域下提前调用执行,函数表达式需等执行到该函数后,方可执行,不可提前调用。
- 函数表达式可直接在函数后加括号调用。而函数声明不能再后面加括号直接调用。
问题的核心
问题的核心就是函数声明和函数表达式通过()
调用的问题。
请看下面的代码:
1 |
var foo = function(){}(); |
这行代码执行起来是没有问题的,因为函数表达式可以在函数后面加括号直接调用。
1 |
function () {} (); |
报错是因为在javascript代码解释时,当遇到function关键字时,会默认把它当做是一个函数声明,而不是函数表达式,如果没有把它显视地表达成函数表达式,就报错了,因为函数声明需要一个函数名,而上面的代码中函数没有函数名,以上代码在执行到第一个左括号的时候报错。所以,Firefox和Chrome本质上报的是同一个错误。
1 |
function foo() {} (); |
在一个表达式后面加括号表示该表达式立即执行,但是在一个函数声明后面加括号,括号并不会对前面的函数声明产生影响,以上代码等同于:
1 |
function foo() {}; |
()
内的表达式不能为空,所以报错。
立即执行函数(IIFE)
上面我们发现了问题的核心,就是()
前面必须是函数表达式。
下面看立即执行函数的两种写法:
1 |
(function(){ |
1 |
(function(){ |
这两种写法都是为了将匿名的函数声明转换为函数表达式。在JavaScript中,()
是一种运算符,()
内部不能包含声明,只能包含表达式,当解析器对代码进行解释的时候,会自动将()
里面的代码识别为表达式,而不是函数声明。
所以,从理论上来讲,除了()运算符
,其他任何运算符都可以达到类似的效果,而之所以使用括号,是因为括号相对其他运算符会更安全。
IIFE的使用
与闭包配合使用来锁住状态
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
大专栏 全面理解JavaScript立即执行函数e">18
19
20
21
22
23
24
25
26
27
28
29
30
31// 并不会像你想象那样的执行,因为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 );
}module模式
IIFE内部形成一个单独是作用域,封装一下外部无法读取的私有变量,通过
return
来返回外部可以访问的数据。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// 创建一个立即调用的匿名函数表达式
// return一个变量,其中这个变量里包含你要暴露的东西
// 返回的这个变量将赋值给counter,而不是外面声明的function自身
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不是返回对象的属性
i; // 引用错误: i 没有定义(因为i只存在于闭包)