http://lipeng1667.github.io/2016/12/20/IIFE-in-js/
立即执行函数表达式(Immediately-Invoked Function Expression), 还有其他的名字:自执行匿名函数(self-executing anonymous function)。 接触到这个IIFE,最早就是为了解决闭包时造成的问题。
还记得我们上一篇Blog, JavaScript中的闭包(closure)中的例子么?
function buildList(list) {
var result = [];
for (var i = 0; i < list.length; i++) {
var a_item = 'item' + i;
result.push( function() {console.log(a_item + ' ' + list[i])} );
}
return result;
}
function testList() {
var fnlist = buildList([1,2,3]);
// Using j only to help prevent confusion -- could use i.
for (var j = 0; j < fnlist.length; j++) {
fnlist[j]();
}
}
testList() //logs "item2 undefined" 3 times
这个示例最终会输出3遍item2 undefined
。
我想,我们已经理解了这个问题是如何造成的对吧,如果你还不理解,强烈建议读一遍之前blog中的所有例子。
那么这篇blog我们就来研究下如何解决这个问题。
我们首先需要理解这两个概念:
// 函数声明
function f() {}
// 表达式(非匿名)
var f1 = function f() {}
// 表达式(匿名)
var f2 = function() {}
function f() {console.log('test');} ();
对于函数表达式,即可以是匿名的,也可以是非匿名的,不过对于非匿名的函数,其实这个函数也不能直接使用,必须通过等号左边的变量名称(f1, f2) 来调用。
在Javascript中,一对圆括号()
是一种运算符,跟在函数名之后,表示调用该函数,所以我们可以通过函数表达式后面跟上()表示调用该函数。
那么问题来了,我们是否可以在函数声明之后立即调用呢? 就像这样:
答案是NO。
原因是:JavaScript引擎规定,如果function关键字出现在行首,一律解释成语句
。因此,JavaScript引擎看到行首是function关键字之后,认为这一段都是函数的定义,不应该以圆括号结尾,所以就报错了。
那我们想要让函数声明之后立即被调用,只需要不让function出现在行首就行了,这就是IIFE是怎么出现的了。
我们其实能经常在各种库中看到IIFE的用法,我们来看看jQuery3.1.1中开头的代码:
( function( global, factory ) {
"use strict";
if ( typeof module === "object" && typeof module.exports === "object" ) {
// For CommonJS and CommonJS-like environments where a proper `window`
// is present, execute the factory and get jQuery.
// For environments that do not have a `window` with a `document`
// (such as Node.js), expose a factory as module.exports.
// This accentuates the need for the creation of a real `window`.
// e.g. var jQuery = require("jquery")(window);
// See ticket #14549 for more info.
module.exports = global.document ?
factory( global, true ) :
function( w ) {
if ( !w.document ) {
throw new Error( "jQuery requires a window with a document" );
}
return factory( w );
};
} else {
factory( global );
}
// Pass this if window is not defined yet
} )( typeof window !== "undefined" ? window : this, function( window, noGlobal )
{...}
()
还有很多方法可以达到同样的目的:是不是同样看到,用一对
()
将函数声明包围起来之后,直接在后面跟上
()
表示立即执行这个函数,只不过上面的例子中是带有两个参数的,直接传递到了当前的函数体中,这是IIFE的另一个特性,防止作用域污染。
!function(){ /* code */ }();
~function(){ /* code */ }();
-function(){ /* code */ }();
+function(){ /* code */ }();
void function(){ /* code */ }();
// new关键字也能达到这个效果
new function(){ /* code */ }
new function(){ /* code */ }()
// 只有传递参数时,才需要最后那个圆括号
解决闭包中的循环问题有人做过测试,使用()
和void
时,效率是最高的,所以推荐大家尽量使用这两种方式。
那么我们究竟如何来解决开篇提到的问题呢??如何才能得到下面我们想要的答案呢?
item0 1
item1 2
item2 3
function buildList(list) {
for (var i = 0; i < list.length; i++) {
var a_item = 'item' + i;
(function(index) {console.log(a_item + ' ' + list[index])})(i);
}
}
buildList([1,2,3]);
答案就是用IIFE,我们在执行循环时,用立即执行的函数表达式,这样就能读取到正确的index值了。
上面的方法我们把每次循环的i
作为参数传递给index
,这样就可以形成正确的函数表达式了。
或者我们还可以不用传参数,直接这样写也可以:
function buildList(list) {
for (var i = 0; i < list.length; i++) {
var a_item = 'item' + i;
(function() {console.log(a_item + ' ' + list[i])})();
}
}
buildList([1,2,3]);
IIFE的其他用处这种写法虽然简单,但是可读性没有上一种方法高,具体使用哪一种,看个人习惯了。
如果我们在页面中同时引用了多个js文件,如果这两个文件中有相同的变量定义,那么就会被覆盖掉。试想下面的例子:
var num = 1;
// code....
lib_a.js
var num = 2;
// code....
lib_b.js
如果在页面中同时引用lib_a.js和lib_a.js两个库,必然导致num变量被覆盖,为了解决这个问题,可以通过IIFE来解决:
lib_a.js
(function() {
var num = 1;
// code....
})();
(function() {
var num = 2;
// code....
})();
lib_b.js
参考&感谢: