先记住这个答案
两种 IIFE 都是「定义完立刻调用」,函数体都是独立作用域,所以隔离变量的效果完全一样。区别不在 IIFE 这个写法,而在函数种类:箭头函数没有自己的 this 和 arguments,在 IIFE 里读到的是外层函数的;普通函数 IIFE 是一次普通调用,严格模式下 this 是 undefined,非严格模式下是全局对象,arguments 是这次调用自己的。所以只要 IIFE 内部用到了 this 或 arguments,两种写法的结果就会不一样。
- 两种写法隔离变量的效果一样
- 箭头 IIFE 里的
this、arguments来自外层 - 普通 IIFE 直接调用,严格模式下
this是undefined - 箭头 IIFE 的括号只能包住函数,不能包到调用后面
在对象方法里对比两种 IIFE
最能看出差别的场景是在对象方法里写 IIFE,因为外层有一个明确的 this 和 arguments。
'use strict'
const user = {
name: 'Ada',
greet() {
const fromArrow = (() => this.name)() // 用的是 greet 的 this
const fromFunction = (function () {
return this // 普通调用,严格模式下是 undefined
})()
console.log(fromArrow, fromFunction)
;(() => console.log(arguments[0]))('arrow-arg') // 读的是 greet 的 arguments
;(function () {
console.log(arguments[0]) // 读的是自己的 arguments
})('function-arg')
}
}
user.greet('greet-arg')查看输出与解释
Ada undefined
greet-arg
function-arg箭头 IIFE 读 this.name 拿到 'Ada',因为它没有自己的 this,往外找到了 greet 的 this,也就是 user。普通函数 IIFE 是被直接调用的,前面没有对象,严格模式下 this 就是 undefined。
arguments 同理。箭头 IIFE 明明传了 'arrow-arg',打印出来却是 'greet-arg',因为它读的是外层 greet 的 arguments,自己传进去的参数被忽略了。普通函数 IIFE 读到的是自己收到的 'function-arg'。
- 普通函数 IIFE 想用外层
this怎么办 - 在 ES6 之前,常见做法是在外面先写
var self = this,IIFE 里用self。也可以写成(function () { ... }).call(this),把外层的this显式传进去。有了箭头函数以后,这两种写法基本都可以省掉。 反过来,箭头 IIFE 没办法换this。对箭头函数调用.call(obj)不会报错,但传进去的obj会被忽略,this还是外层的。
写法上的两个细节
普通函数 IIFE 的括号位置比较随意,(function () {})() 和 (function () {}()) 都能跑。箭头函数不行,只能写成 (() => {})(),把括号包到调用后面的 (() => {}()) 是语法错误,因为箭头函数体后面不能直接接调用。
另一个细节是 async 版本。(async () => { await init() })() 是现在最常见的 IIFE 写法之一,用来在不支持顶层 await 的地方开一个异步入口。写成普通函数 (async function () { ... })() 也可以,区别仍然只在 this 和 arguments。不管哪种,返回的都是 Promise,记得用 .catch() 处理错误。
容易答错的地方
- 以为箭头 IIFE 执行时会得到一个新的
this - 箭头函数任何时候都不创建
this,立即执行也一样。它的this在定义时就由外层决定了。 - 以为普通函数 IIFE 的
this会是外层对象 - IIFE 写在对象方法里,不等于它被这个对象调用。它是直接调用的,前面没有
obj.,所以this走默认规则,和写在哪个方法里无关。
面试官还会怎么问?
箭头 IIFE 能被 new 调用吗?
不能。箭头函数没有 prototype,也不能作为构造函数,写 new (() => {})() 会抛 TypeError。普通函数 IIFE 理论上可以 new,但实际没人这么用。
箭头函数里想拿到所有参数怎么办?
用剩余参数,写成 ((...args) => console.log(args))(1, 2)。args 是真正的数组,比 arguments 更好用,这也是新代码基本不再用 arguments 的原因。
顶层代码里的两种 IIFE,this 有区别吗?
在 ES 模块顶层,外层 this 是 undefined,箭头 IIFE 读到 undefined;普通函数 IIFE 在模块里默认是严格模式,也是 undefined,结果一样。在非严格的浏览器脚本里就不同了:箭头 IIFE 读到的是顶层的 window,普通函数 IIFE 被直接调用,this 也会回退成 window,结果碰巧相同,但原因不一样。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。