在 JavaScript 中,IIFE 被广泛应用。

比如,我们经常能在一些 JavaScript 项目中,看到所有代码被包裹在一个 IIFE 中

1
2
3
(function(){
  // 项目实现代码
}());

IIFE 的使用理由

使用 IIFE 的一个重要原因是,可以避免对外层作用域的污染。

因为IIFE的本质就是一个函数,我们在函数内部定义的所有变量、函数,均在函数本地生效,避免了跟其他 JavaScript 冲突。

声明变量务必使用 var(ES6 之后支持 let,const)。 因为非 strict mode 中,不使用 var 声明的变量,实际上是定义在全局作用域的。

此外还有一些有用的特性也是使用 IIFE 的理由之一,例如提供正确的 undefined 值:

1
2
3
4
5
6
(function(undefined){
  // undefined 可能被重新赋值
  // 无传递实参,确保了
  // 形参 `undefined` 是
  // 真正的 `undefined`
}());

当然利用这种模式,也可以往 IIFE 里面注入一些其他的依赖,比如全局对象等等。

IIFE 的执行原理

奥妙就在于 () 上面,因为()运算符内部必须是一个表达式。 所以,() 内部的函数不是一个函数声明,而是一个函数表达式,表达式的计算结果返回一个函数,然后立即被调用。

这跟下面的函数表达式一样道理(= 运算符右侧接受一个表达式):

1
2
3
4
var exprVal = function() {
  return 'haha~'
}()
// exprVal === 'haha~'

IIFE 的其他写法

既然明白了 IIFE 的原理就是利用的函数表达式的性质,那么我们就可以有很多种写法了,例如

1
2
3
4
(function(){ /* 代码 */ })();
!function(){ /* 代码 */ }();
+function(){ /* 代码 */ }();
void function(){ /* 代码 */ }();

选择哪种写法基本取决于个人审美,不过惯例上使用括号的比较多。

但,不同的写法仍有一些其他的细微影响。

比如分号的使用,比如括号的方式,因为括号的多义性,如果前面还有内容,那么括号会被当作是一个函数调用,导致非期望的结果出现。

一个好的实践方式是,在括号、中括号等前面随手加上分号:

1
;(function(){ /* 代码 */ }())

这样可以避免 IIFE 前面的代码忘记加分号导致的问题。