在JavaScript中定义一个函数后,声明一个函数并马上执行(调用)这个函数,这样的函数就是立即执行函数(Immediately-Invoked Function Expression 即IIFE),也称为即时函数(Immediately Function)。
典型样式:
(function(形参){
//执行语句
//......
})(实参)
也可以是:
(function (形参) {
//执行语句
//......
}(实参))
推荐是第一种的写法,因为清楚明了,不容易产生歧义。
有的习惯性在前面加;,比如;(function(){})();,这个是为了避免错误的习惯性写法(前面的语句最后没有加;)。
如果在函数内没有对外变量的引用,也即没有闭包存在,那么这个函数执行完就作为垃圾回收了。
多数情况下,我们应用都是没有实参和形参的。
这里有一些的例子:
(function func(){ console.log('B')})();//输出B
console.log(!!function func(){ return 'C'; }());//输出true
console.log(!(function(){ return true})());//输出false
console.log(1 + function func(){ return 1; }());//输出2
-(function func(){ console.log('D');})();//输出D
在JavaScript语言当中要了解立即执行函数,先了解()的作用。
()可以是求值。
那么圆括号里面的就是表达式。
比如,可以写:
a=(1+2);
console.log(a);//输出3
但是写成a=(1+2;)则报错,因为括号里面的是语句不是表达式。
又例如:
function a(){console.log('1')}();
这个是报错的,因为JavaScript解释器遇到以function开头的都会以语句来解释。
(function a(){console.log('1')}());
这个则可以正确输出,因为以()包裹,则JavaScript解释器会认为里面的是可以执行的表达式。
()也可以是执行操作符。
我的理解就是{}与执行()一一对应,想着是剥洋葱,{}就是封一层洋葱皮,而()则是剥一层洋葱皮。
var K=function a(){
return function b(){
return function c(){
return function d(){
return 'JavaScript';
}
}
}
};
console.log(K()()()()); //输出JavaScript
在JavaScript语言当中,()也是一个很神奇的存在,试看下面的代码,只是()的位置不一样导致结果也是不一样的。
var a=( function(){
var count=0;
return function (){
count=count+1;
return count;
}
})();
console.log(typeof a); //输出function
console.log(a());//输出1
console.log(a());//输出2
console.log(a());//输出3
上面的这个例子利用结合闭包来实现特定的效果。
下面只是改变()的位置:
var a=( function(){
var count=0;
return function (){
count=count+1;
return count;
}
})()();
console.log(typeof a);//输出number
console.log(a);//输出1
console.log(a);//输出1
console.log(a);//输出1
又或者写成下面的样式:
var a=( function(){
var count=0;
return function (){
count=count+1;
return count;
}
});
console.log(typeof a);//输出function
console.log(a()());//输出1
console.log(a()());//输出1
console.log(a()());//输出1
下面是一个简单的计数功能的例子。
;(function (){
var count=0;
function add(){//增加计数
count=count+1;
return count;
}
function reduce(){//减少计数
count=count-1;
return count;
}
window.JS=function(){ return { add:add,reduce:reduce } }
}
)();
console.log(window.JS().add());//输出1
console.log(window.JS().add());//输出2
console.log(window.JS().add());//输出3
console.log(window.JS().reduce());//输出2
console.log(window.JS().reduce());//输出1
下面是一道面试题,也是即时函数与闭包结合比较典型的试题:
for(var i=0;i<5;i++){
setTimeout( function(){
i=i+1;
console.log(i);
},5000);
};
console.log('i:'+i);
上面的代码段输出是什么?
setTimeout(表达式,延时时间),意思是在载入后,在延迟时间后执行一次表达式。
setInterval(表达式,交互时间),意思是在载入后,每隔交互时间就执行一次表达式。
实际输出:
如果要输出1、2、3、4、5,则应该改写代码:
for(var i=0;i<5;i++){
(function(x){
setTimeout( function(){
x=x+1;
console.log(x);
},5000)
})(i);
}
console.log('i:'+i);
实际输出:
上面的这道题目和下面的类似:
function func(){
for(var i=0,arr=[];i<5;i++){
arr[i]=function(){ console.log(i) }
}
return arr;
}
var funs=func();
funs[0]();//输出5
funs[1]();//输出5
funs[2]();//输出5
funs[3]();//输出5
funs[4]();//输出5
而要想输出0、1、2、3、4,则可以使用即时函数来完成:
function func(){
for(var i=0,arr=[];i<5;i++){
(function(i){ arr[i]=function(){ console.log(i) }} )(i)
}
return arr;
}
var funs=func();
funs[0]();//输出0
funs[1]();//输出1
funs[2]();//输出2
funs[3]();//输出3
funs[4]();//输出4
因为是作用域的问题,在ES6中可以使用let赋值来解决就简单多了:
function func(){
let arr=[];
for(let i=0;i<5;i++){
arr[i]=function(){ console.log(i) }
}
return arr;
}
var funs=func();
funs[0]();//输出0
funs[1]();//输出1
funs[2]();//输出2
funs[3]();//输出3
funs[4]();//输出4
在实际的应用中,我们常见即时函数的应用,比如事件挂载与绑定、初始化应用等等,即时函数经常与闭包联系在一起使用,比如在变量声明与引用、模块化开发等方面。