“立即执行函数表达式 (Immediately Invoked Function Expression)” 简称 IIFE
,是一个在定义时就会立即执行的 JavaScript 函数。
“立即执行函数表达式”被用于弥补 JS 在作用域方面的不足,在早期的 JS 中只有“全局作用域(global scope)” 与 “函数作用域(function scope)”,并没有更具体的其它局部作用域(例如 ES6 才具有的“块级作用域 (block scope)”),因此当我们需要进行作用域隔离时,往往都会在上下文代码中采用“函数作用域”来模拟所需的局部作用域,而这样的函数也只会被执行一次,所以函数名以及调用语句都可以被省略掉。
(function() {
console.log("IIFE");
})();
整体看上去就是一个立即调用的匿名函数,下面是推荐使用的格式:
;(function() {
//独立的作用域
console.log("IIFE");
}());
前面的分号
;
可以有效解决代码合并时可能产生的语法错误,同样的运用例如 webpack 的打包编译。
“立即执行函数表达式”的立即执行原理就在于 “表达式(expression)”。 对于“表达式”的定义,通常指的是用运算符号将操作数链接起来可能会产生运算结果的式子。 在 JS 引擎中我们可以立即执行一个“函数表达式”,但是不能立即执行一个“函数声明语句”,如下所示:
function test(){}(); //Uncaught SyntaxError: Unexpected token ')'
执行上面的代码,我们会抛出一个标记异常的语法错误,下面让我们一步一步来验证问题所在,继续执行下面的代码:
(1); //1
function test(){}(1); //1,但并没有执行函数
() // Uncaught SyntaxError: Unexpected token ')'
根据上面的执行结果,我们可以得出一个结论,那就是在直观上我们所认为的 function test(){}();
是一个整体,但对于 JS 引擎而言则是毫无关联的两个部分,一个是“函数声明语句”,另一则是“圆括号运算符(用来控制运算优先级)”,就连抛出的语法错误,也是因为圆括号运算符的操作数不能为空的缘故。
下面是 JS 引擎对 function test(){}();
的理解:
function test() {}; //函数声明语句。
(); //圆括号运算符,但操作数为空,所以会报错
我们必须考虑 JS 语法允许我们在一些场景中省略分号
;
的使用。
所以使用一对圆括符 ()
就可以执行一条函数声明语句的美丽错觉,就源于这个被隐藏掉的分号;
。
那么我们能否实现立即执行一条“函数声明语句”呢?答案是可以的!从源头出发,既然这个分号对于开发者而言是不可见的,那么我们完全也可以让这个分号对于 JS 引擎而言也是不可见的,只要让这个分号消失,那么这条函数声明语句与后面紧随的圆括号运算符符就会被视为一个整体,从而能被正确的识别和执行。
在此之前,先加强下我们的基础理论的认知水平:
- 函数声明语句:用于创建函数的语句,返回值是一个引用类型的“函数值”。
- 函数表达式:根据一般“表达式”的定义,这里指的就是用操作符将“函数值”链接其来的式子。
- 语句(statement):简单理解上,就是“表达式”与分号
;
的组合
function test(){}; //函数声明语句,创建一个 `test`函数。
var f = function(){}; //函数表达式(函数赋值表达式)。
既然“函数声明语句”是用来创建一个引用类型的函数(值),因此我们有理由可以认为函数声明类似于一种占位符的性质,实际上所代表的是引用类型的函数(值)。此时,如果我们用一个“运算符”来与函数声明语句进行运算,是否就相当于与这个函数值进行运算呢?实际测试的反馈就是如此,这样,当我们用一个运算符来与一个函数声明语句进行运算时,将会重新创建一条函数表达式,而表达式中的操作数就是函数声明语句所代表的函数。
例如下面的赋值表达式:
var f = function test() {
console.log("IIFE");
}
在赋值的过程中,右边的函数声明语句会将创建的函数赋值给左边的变量 f
,所以右边的函数声明本质上就是一个函数值操作数,那么此时如果我们在函数声明后面添上一对圆括号运算符又会怎么样呢?
var f = function test() { console.log("IIFE"); }(); //IIFE
f; //undefined
var f = function test() { reutn 'IIFE' }();
f; //IIFE
我们可以发现函数声明语句创建的函数被执行了,并且将执行的结果赋值给了变量 f,其中第一个是默认值 undefined
,而第二个则是 IIFE
,当然这里还牵涉到了运算符的结合性,例如赋值运算符是左结合性,所以优先执行右边的函数值(此时函数被立即调用)。
同样的道理,我们完全可以使用 ()
圆括符运算符来与一条函数声明组建一个“函数表达式”。然后执行这个表达式中的函数值操作数,也或者是执行表达式的运算结果(圆括号运算符会返回操作数自身,这里就是要立即执行的函数自身)
//ƒ IIFE() { console.log('IIFE') }
(function IIFE() {
console.log("IIFE");
});
//IIFE
(function IIFE() {
console.log("IIFE");
})();
//IIFE
(function IIFE() {
console.log("IIFE");
})();
上面便是常用的“立即执行函数表达式”的格式由来。
总的来说,我们无法立即执行“函数声明语句”,因为有一个看不见的分号,将函数与表示调用的“圆括符”进行了分离,而“函数表达式”便没有这个困扰,因此要立即执行某条“函数声明语句”,我们必须要将一个“函数声明语句”通过与“运算符”再结合,将其转换为“函数表达式”,这便是“立即执行函数表达式”的名称由来。
同样的思路,我们完全可以使用其它“运算符”来与“函数声明语句”组建一个临时的“函数表达式”,然后我们再立即执行这个函数表达式,以实现立即执行。
0,function () { console.log('IIEF') }();
+ function () { console.log('IIEF') }();
- function () { console.log('IIEF') }();
! function () { console.log('IIEF') }();
void function () { console.log('IIEF') }();
typeof function () { console.log('IIEF') }();
new function () { console.log('IIEF') }();
但通常上面的写法很少会被使用到,但原理都是想通的。
“立即执行函数表达式”被用于模拟局部作用域,以实现作用域隔离。而“闭包”、与“立即执行的模块模式” 都是对其的延伸使用。
http://softlab.sdut.edu.cn/blog/subaochen/2016/02/%E8%AF%B4%E4%B8%80%E8%AF%B4js%E7%9A%84iife/