一、立即执行函数(IIFE)
在JavaScript基础—函数中介绍了函数作用域的概念:在函数中声明的变量在整个函数体内都是可见的,在函数的外部是不可见的。
不在任何函数内部声明的变量是全局变量,在JavaScript程序中都是可见的。ES6新增了块级作用域。
那么,在ES6之前我们能否弥补整个作用域的缺陷呢。那就是立即执行函数。
IIFE:全拼Imdiately Invoked Function Expression,是一个在定义的时候就立即执行的JavaScript函数。
像如下的代码所示,就是一个匿名立即执行函数:
(function(window, undefined){
// 代码...
})(window);
注意:上述代码中最外层有两个圆括号,function关键字前面对应的为第一个括号,最后(window)的为第二个括号
二、IIFE中括号的含义
2.1 第二个括号的意义
在JavaScript中,圆括号()是一种运算符,它跟在函数名之后,表示函数的调用,比如console.log(),调用的是console对象的log方法。因此就有了最后的这个括号,加粗标黄的括号:
函数部分();
因此第二个括号表示函数的调用,同样,这个括号中的参数便是函数调用时传入的实参。
2.2 第一个括号的意义
在JavaScript中,什么才能执行,当然是函数或者函数表达式啦。但是我们能不能直接用function打头,定义一个函数呢,像下面这样:
function (){
console.log(1)
}();
显然不行了,直接运行报错:
为什么呢?这跟浏览器的JavaScript引擎解析和function这两个东东有关。
function,这个关键字既可以当作语句,也可以当作表达式。当你以function打头的时候,浏览器蒙圈了,不知道该怎么解析了。这里就存在了歧义。
为了避免解析上的歧义,JavaScript引擎规定:如果关键字function出现在行首,一律解析为函数声明语句。因此,JavaScript引擎看到行首是function关键字之后,认为这一段都是函数的定义,不应该以圆括号结尾,所以就报错了。
那么为了这个东东能够执行,所以要让它转换成一个表达式,所以最简单的处理就是不要让function出现在行首,让JavaScript引擎把它看作是一个表达式,所以自然的就用括号把它报过了起来了。
因此,第一个括号的意义:把function(){}转化成一个可执行的函数表达式
2.3 第一个括号不是必须是括号
像一些库的源码,喜欢用如下的方式代替:
~function(){
// 代码...
}();
或者这种方式:
+function(){
// 代码...
}();
但是其作用都是一样:把function(){}
转化成一个可执行的表达式,方便执行。尽管有各种写法,但是我们还是推荐用圆括号包裹的方式,因为这样代码看起来更清晰,更明了,更有层次整体感
三、IIFE的目的
在第一部分中提到IIFE实际是为了解决作用域缺陷而诞生的,那么它的诞生给我们带来了哪些好处呢?
-
不必为函数命名,避免了污染全局变量;
-
IIFE内部形成了单独的作用域,可以封住一些外部无法读取的私有变量
以下是一个常规的写法:
function myModule(){
//模块代码
//这个模块所使用的所有变量都是局部变量
//而不是污染全局命名空间
}
myModule(); // 但是我们不能忘记还要调用它
这种常规的写法依然定义了一个全局的变量myModule,麻烦并且污染来的全局变量。
以下是IIFE的写法:
// 匿名函数立即执行
(function(){
//模块代码
//这个模块所使用的所有变量都是局部变量
//而不是污染全局命名空间
})(); // 函数结束定义并立即调用它
四、IIFE的参数
在前面提了一下IIFE的参数传递,直接上代码:
var mymodule= {};
(function(window, MyModule, undefined){
//代码
})(window, mymodule);
参数分为形参和实参。function(window, MyModule, undefined)
三个参数为形参,第二个括号(window, mymodule)
的两个参数为实参。也即可以理解为 window == window
,MyModule== mymodule
。
4.1 普通形参
普通形参是指由window
和wall
这样的实际变量传入指定,可以为任何类型的变量。一个形参就对应一个实参
4.2 特殊形参undefined
大家都知道,IE是个神奇的浏览器,尤其是早期版本。比如说IE6这玩意儿,它居然功能强大到可以修改undefined,如果undefined被修改以,那么下面这类代码就玩不转了:
if(mymodule == undefined){
//永远也进不来了
}
所以这个地方多加一个形参,就可以避免这个坑,在IIFE作用域中就能正常的获取到undefined了。
同时,它也有助于代码的压缩,减小文件的大小。
五、写法解析
最后给大家一个用IIFE写模块的一个代码模板:
// 定义
(function(window, MyModule, undefined){
MyModule.name = 'mymodule'
// 给wall命名空间绑定方法say
MyModule.say = function(){
console.log('hello');
}
MyModule.MyName = function(){
console.log(this.name);
}
})(window, window.mymodule || (window.mymodule = {}));
// 调用
console.log(mymodule.name);
mymodule.say();
mymodule.MyName();
关键点在于传参部分:window.mymodule || (window.mymodule = {}),这里巧妙的运用了或运算的短路原则
-
如果window.mymodule 是已经实例化的,非not defined。则直接返回window.mymodule的引用,赋值给形MyModule。不会执行||运算符后面的内容。
-
如果window.mymodule还未实例化,则进行实例化。这里要注意的点是实例化是一个赋值操作,需要用括号包起来,变成表达式去执行,才不会报错。
-
表达式(window.mymodule = {})执行完毕后,会返回新对象window.mymodule的引用。
以上是一些立即执行函数的IIFE的一些基础知识,希望能对你有所帮助。欢迎关注同步微信公众号:前端小菜的进阶之路