【前端面试小册】JS-第5节:IIFE 立即调用函数表达式
一、什么是 IIFE
IIFE(Immediately Invoked Function Expression,立即调用函数表达式)是指定义后立即执行的函数表达式。
为什么需要 IIFE?
在 ES6 之前,JavaScript 没有块级作用域,只有函数作用域。IIFE 通过立即执行函数来创建独立的作用域,避免了变量污染全局作用域。
类比理解:就像快递员把包裹放在快递柜里,每个快递柜都是独立的空间,互不干扰。IIFE 就是这样的"函数快递柜",每次调用都会创建一个新的独立作用域。
二、IIFE 的两种写法
写法一:括号包裹整个函数表达式
(function() {
// 函数体
})();
写法二:括号包裹函数体和调用
(function() {
// 函数体
}());
注意:两种写法功能完全相同,选择哪种取决于团队代码规范。写法一更常见,写法二更紧凑。
为什么需要括号?
// ❌ 这样会报错:SyntaxError
function() {
console.log('Hello');
}();
// ✅ 正确:用括号包裹函数表达式
(function() {
console.log('Hello');
})();
原理说明:JavaScript 引擎遇到 function 关键字时会解析为函数声明,而函数声明不能立即调用。用括号包裹后,函数被解析为函数表达式,表达式可以立即执行。
三、IIFE 的核心作用
3.1 模拟块级作用域
在 ES6 之前,var 声明的变量是函数级作用域,没有块级作用域。使用 IIFE 可以创建独立的作用域。
(function() {
// 块级作用域
for (var i = 0; i < 5; i++) {
console.log(i);
}
})();
// ReferenceError: i is not defined
console.log(i);
执行流程:
graph TD
A[IIFE 开始执行] --> B[创建独立作用域]
B --> C[for 循环执行]
C --> D[var i = 0...4]
D --> E[循环结束]
E --> F[IIFE 执行完毕]
F --> G[作用域销毁]
G --> H[外部无法访问 i]
3.2 配合闭包保存状态
这是 IIFE 最经典的应用场景,解决循环中异步回调的问题。
问题场景:var 的陷阱
// ❌ 问题代码
for (var i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
// 输出:5 5 5 5 5
问题分析:
var声明的i属于全局作用域,不属于for循环体- 所有
setTimeout回调共享同一个变量i - 1 秒后执行回调时,循环已结束,
i的值为 5 - 所有回调通过作用域链向上查找,都获取到
i = 5
解决方案一:IIFE + 闭包
// ✅ 使用 IIFE 创建独立作用域
for (var i = 0; i < 5; i++) {
(function(i) { // 立即执行函数,参数 i 保存当前值
setTimeout(function() {
console.log(i); // 闭包捕获外层 IIFE 的参数 i
}, 1000);
})(i); // 传入当前的 i 值
}
// 输出:0 1 2 3 4
原理说明:
- 每次循环,IIFE 都会创建一个新的作用域
- 外层
i的值作为参数传入 IIFE,形成闭包 - 每个
setTimeout回调访问的是各自闭包中保存的i值
执行流程:
graph TD
A[循环开始 i=0] --> B[IIFE 执行,参数 i=0]
B --> C[创建闭包,保存 i=0]
C --> D[setTimeout 注册回调]
D --> E[循环继续 i=1,2,3,4]
E --> F[1秒后回调执行]
F --> G[通过闭包访问各自的 i 值]
G --> H[输出: 0,1,2,3,4]
解决方案二:利用 setTimeout 的第三个参数
// ✅ 利用 setTimeout 的额外参数
for (var i = 0; i < 5; i++) {
setTimeout(function(j) {
console.log(j);
}, 1000, i); // 第三个参数直接传递 i 的值
}
// 输出:0 1 2 3 4
原理说明:setTimeout 的第三个及之后的参数会作为回调函数的参数传入,避免了闭包的问题。
解决方案三:使用 let 块级作用域(ES6)
// ✅ 使用 let,每次循环创建新的块级作用域
for (let i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
// 输出:0 1 2 3 4
原理说明:
let在for循环中,每次迭代都会创建新的块级作用域- 相当于每次循环都创建了一个新的变量
i,互不干扰 - 这实际上是 JavaScript 引擎在底层实现的语法糖
三种方案对比
| 方案 | 优点 | 缺点 | 推荐度 |
|---|---|---|---|
| IIFE + 闭包 | ES5 兼容,原理清晰 | 代码稍显繁琐 | ⭐⭐⭐ |
| setTimeout 参数 | 代码简洁 | ES5 兼容性问题 | ⭐⭐⭐⭐ |
| let 块级作用域 | 最简洁,现代语法 | 需要 ES6+ | ⭐⭐⭐⭐⭐ |
四、IIFE 的其他应用场景
4.1 模块化封装(ES6 之前)
// 创建一个简单的计数器模块
const Counter = (function() {
let count = 0; // 私有变量
return {
increment: function() {
count++;
return count;
},
decrement: function() {
count--;
return count;
},
getCount: function() {
return count;
}
};
})();
Counter.increment(); // 1
Counter.increment(); // 2
Counter.getCount(); // 2
// count 无法直接访问,实现了私有化
4.2 避免命名冲突
// 使用 IIFE 创建独立命名空间
(function(window) {
const myLibrary = {
version: '1.0.0',
init: function() {
console.log('Library initialized');
}
};
window.MyLibrary = myLibrary;
})(window);
// 现在可以通过 MyLibrary 访问,不会污染全局作用域
MyLibrary.init();
4.3 初始化代码隔离
// 页面加载时执行初始化逻辑
(function() {
// 初始化 DOM 操作
const button = document.getElementById('myButton');
button.addEventListener('click', handleClick);
// 初始化数据
const data = loadInitialData();
render(data);
// 所有初始化代码都在独立作用域中,不会影响全局
})();
五、IIFE 在 ES6+ 的替代方案
随着 ES6+ 的普及,IIFE 的很多场景已经被替代:
5.1 块级作用域 → let/const
// ES5: 使用 IIFE
(function() {
var x = 1;
// ...
})();
// ES6+: 使用块级作用域
{
let x = 1;
// ...
}
5.2 模块化 → ES Modules
// ES5: 使用 IIFE 模拟模块
const Module = (function() {
// ...
})();
// ES6+: 使用 ES Modules
export const Module = {
// ...
};
六、面试要点总结
核心知识点
- 定义:立即调用的函数表达式
- 语法:两种括号写法等效
- 作用:
- 创建独立作用域(模拟块级作用域)
- 配合闭包保存状态
- 避免变量污染全局
- 现代替代:ES6 的
let/const和模块化
常见面试题
Q1: 为什么需要 IIFE?
答:ES5 只有函数作用域,没有块级作用域。IIFE 通过立即执行函数创建独立作用域,避免变量污染,同时配合闭包可以保存状态。
Q2: 如何理解 IIFE 中的闭包?
答:IIFE 的参数和外层变量形成闭包,内部函数可以访问这些变量。在循环中使用 IIFE,每次循环都会创建新的闭包,保存当前循环变量的值。
Q3: IIFE 现在还需要吗?
答:对于块级作用域,可以用 let/const 替代。对于模块化,可以用 ES Modules。但 IIFE 仍有价值:兼容老项目、理解闭包原理、某些特殊场景。
实战建议
- ✅ 新项目优先使用 ES6+ 语法(
let/const、模块化) - ✅ 理解 IIFE 原理有助于理解闭包和作用域
- ✅ 面试中能够清晰解释 IIFE 的作用和原理
每天3-4节,跟着我50天学完,一起上岸! 靠它,我拿下阿里/百度/滴滴/字节/快手/腾讯/银行等offer 上岸银行,作为面试官使用,跳槽复习使用的小册