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 變量來解決問題。