JavaScript 立即调用函式 IIFE (Immediately Invoked Function Expression) 是什么?优缺点是什么?
2023年2月9日
立即调用函式 IIFE (Immediately Invoked Function Expression) 是什么?
JavaScript 中的立即调用函式 (IIFE,Immediately Invoked Function Expression),指的是一种在定义时立即执行的匿名函式,通常用于创建一个局部作用域,避免全局污染。
语法格式如下:
(function () {
// Code to be executed
})();
在这种表达式中,函式定义与函式调用的括号是一起的,确保函式只会被执行一次,而不是定义后可以多次执行。在这个封闭的作用域中,变量和函式都不会污染全局环境。
立即调用函式的好处
创建局部作用域
通过使用 IIFE 可以创建一个局部作用域,避免全局变量的污染。以下代码可以看到,在 IIFE 中,有一个局部变量 localVariable。 localVariable 只能在 IIFE 内访问,不能在 IIFE 外访问,
// Global scope
var globalVariable = "global variable";
(function () {
// Local scope inside IIFE
var localVariable = "local variable";
console.log(localVariable); // local variable
})();
console.log(localVariable); // ReferenceError: localVariable is not defined
console.log(globalVariable); // global variable
避免命名冲突
IIFE 可以为变量创建了一个单独的命名空间,避免函式名和变量名的冲突。
// Global scope
var globalVariable = "global variable";
(function () {
// Local scope inside IIFE
var globalVariable = "local variable inside IIFE";
console.log(globalVariable); // local variable inside the IIFE
})();
console.log(globalVariable); // global variable
模组化编程
IIFE 可以将代码分为独立的模组,方便了代码的管理和维护。在《什么是前端模组化?》这篇文章中曾经有提过,在前端模组化开始发展、并且还没有前端模组化工具时,一开始就是使用 IIFE 作为模组化的实践方式。
在下方代码例子中,我们通过两个 IIFE 分别创建了两个模组:module1 和 module2。每个模组内部都有自己的变量和函式,而 IIFE 的作用是创建局部作用域,避免了变量的污染。接着,我们在 window 添加对应的模组对象,实现了对模组的公开。使用时,我们可以直接通过 window 对象访问模组中的函式,实现了模组化编程。
// Module1
(function () {
var module1Variable = "I am a variable in module 1";
function module1Function() {
console.log(module1Variable);
}
window.module1 = {
module1Function: module1Function,
};
})();
// Module2
(function () {
var module2Variable = "I am a variable in module 2";
function module2Function() {
console.log(module2Variable);
}
window.module2 = {
module2Function: module2Function,
};
})();
// Usage
module1.module1Function(); // I am a variable in module 1
module2.module2Function(); // I am a variable in module 2
提高代码执行效率
IIFE 可以在定义时立即执行,避免了函式的不必要的存储和调用,提高了代码的执行效率。
IIFE 的缺点
尽管上述提到许多 IIFE(Immediately Invoked Function Expression)的优点,但也存在一些缺点,例如
- 代码不易维护:当代码变得更加复杂时,IIFE 的代码容易变得庞大,不易于维护和阅读。
- 不利于重复使用:IIFE 的代码通常是一次性的,无法复用,因此在需要多次调用时不太方便。
- 增加代码复杂度:使用 IIFE 可能会使代码变得更加复杂,特别是当代码量很大时。
经典面试题 - setTimeout、Scope、IIFE
以下为一题有关 setTimeout、Scope、IIFE 的经典面试题,请试着回答
**以下输出结果为何? **
for (var i = 1; i <= 5; i++) {
setTimeout(function () {
console.log(i);
}, 0);
}
解答
// 立即输出五个 6
6;
6;
6;
6;
6;
原因
这是因为在 JavaScript 中,var 声明的变量是函式作用域 (function scope),而不是块级作用域 (block scope)。因此,在循环结束后,i 的值为 6,每个 setTimeout 回调函式引用的都是同一个 i 变量,因此输出的结果都是 6。
延伸阅读:Javascript 的作用域(Scope) 与作用域链(Scope Chain) 是什么?
解决方法
要如何有办法,每隔一秒输出一个 1、2、3、4、5 呢?
IIFE
这里使用立即调用函式(IIFE)和匿名函式形成一个私有作用域(相当于闭包),私有作用域中的变量和全局作用域中的变量互不冲突;这时每次 for 循环传入的 i 的值都将作为私有变量被保存在内存中,等待 for 循环执行完毕后,跟随任务队列输出。
for (var i = 1; i <= 5; i++) { (function (i) { setTimeout(function () { console.log(i); }, i * 1000); })(i); }
使用 let 声明变量
在 ES6 之后,我们就可以使用 let 来解决这个问题,因为 let 声明的变量是块级作用域 (block scope),所以可以通过在循环内部使用 let 声明 i 变量来解决问题。