ES 函数基础 —— IIFE
ES2015 中定义的块级作用域,使应用广泛的 IIFE 变得不再必要了。
什么是 IIFE?
经常能看到如下的函数调用方式:
(function () {
console.log("test")
})()
(function () {
console.log("test");
}())
社区对这种形式的函数表达式调用的称呼不尽相同,其中包括:
- 自执行匿名函数(self-executing anonymous function)
- 立即执行函数表达式(Immediately-Invoked Function Expression,简称 IIFE)
IIFE 的基本形式是这样的:
(function () {
// code
})()
以上代码定义并立即调用了一个匿名函数。代码中有两个重要的括号:
- 包含函数定义的圆括号,表示它是一个函数表达式,而不是一个函数声明
- 末尾的括号,表示立即调用这个函数
为什么第一对括号要这么用? 默认情况下,解释器会把遇到的
function
关键字当作是函数声明语句来进行解释。由于()
之间只能包含表达式,所以解释器会把其中的内容当作表达式而不是声明语句来执行。
()
并不是唯一能创建函数表达式的方式,还有:
// 如果本身就是 expression,那么根本不需要做任何处理
var i = function(){ return 10; }();
true && function(){ /* code */ }();
0, function(){ /* code */ }();
// 如果你不在乎返回值,可以这么做
!function(){ /* code */ }();
~function(){ /* code */ }();
-function(){ /* code */ }();
+function(){ /* code */ }();
// 还有更奇葩的方式,但性能不好
// 来自 http://twitter.com/kuvos/status/18209252090847232
new function(){ /* code */ }
new function(){ /* code */ }()
IIFE 的用途
模拟块级作用域
众所周知,在 ES2015 之前,ES 并没有 C 或 Java 中的块级作用域,只有函数级作用域。
function outputNumbers(count) {
for (var i=0; i < count; i++) {
console.log(i)
}
console.log(i) // 计数
}
这个函数创建了一个 for 循环,变量 i
初始值被设置为 0 。在 Java 等语言中,变量 i
只会在 for 循环的代码块中有定义,循环一旦结束,变量 i
就会被销毁。可是在 ES2015 之前,由于没有块级作用域,变量 i
是定义在 outputNumbers()
的 Variable Object 中的,因此从它有定义开始,就可以在函数内部的任何地方访问它。这是缺少块级作用域而引发的问题。不过可以使用 IIFE 的私有作用域来模拟块级作用域:
function outputNumbers(count) {
(function () {
for (var i=0; i < count; i++) {
console.log(i)
}
})()
console.log(i) // 错误
}
IIFE 中的任何变量,都会在执行结束时被销毁。因此,变量 i
只能在循环中使用,使用后就被销毁。另外由于这个 IIFE 是一个闭包,它能够访问外部作用域中的所有变量,包括变量 count
。 这种用法经常用在全局作用域中的函数上,从而限制向全局作用域中添加过多的变量和函数。
应该尽量减少向全局作用域中添加变量和函数,因为过多的全局变量和全局函数很容易导致变量冲突。
通过使用 IIFE 模拟块级作用域,每个开发者都可以使用自己的变量,又不必担心搞乱全局作用域。比如:
(function () {
var now = new Date()
if (now.getMonth() === 0 && now.getDate() === 1) {
console.log('Happy new year!')
} else {
console.log('Go to school!')
}
})()
模拟单例模式
var counter = (function () {
var i = 0
return {
get: function () {
return i
},
set: function (value) {
i = value
},
increment: function () {
return ++i
},
show: function () {
console.log(i)
}
}
})()
counter.get()
counter.show() // 0
counter.set(3)
counter.show() // 3
counter.increment()
counter.show() // 4
counter.increment()
counter.show() // 5
解决闭包中的变量冲突
var f = function() {
var res = []
for(var i = 0; i < 10; i++) {
var func = function () {
console.log(i)
}
res.push(func)
}
return res
}
// 会输出 10 个 10,而不是预期的 0 1 2 3 4 5 6 7 8 9
var res = f()
for(var i = 0; i < res.length; i++) {
res[i]()
}
修改后:
var f = function() {
var res = []
for(var i = 0; i < 10; i++) {
// 添加一个 IIFE
(function (index) {
var func = function () {
console.log(index)
}
res.push(func)
})(i)
}
return res
}
// 输出预期的 0 1 2 3 4 5 6 7 8 9
var res = f()
for(var i = 0; i < res.length; i++) {
res[i]()
}