前端基礎
-JavaScript 面試題 - 超好用的立即執行函數 IIFE
立即執行函數又稱 IIFE(Immediately Invoked Function Expression),對於初學者來說,很容易把立即執行函數跟閉包搞混,也很多文章會將它們兩個混在一起說,因為立即執行函數常常會和閉包結合起來使用。
如果你還不知道閉包是甚麼,可以參考我寫的這篇文章 JavaScript 面試題 - 閉包 Closure 的觀念及實作。裡面用很簡單的一句話來說明 JS 閉包,並且有三個實作範例。
那接下來就讓我們來看看立即執行函數是什麼,以及他是怎麼跟閉包結合的吧!整篇文章的架構為:
- 立即執行函數是什麼
- 立即執行函數可以做什麼
- 立即執行函數的應用
立即執行函數 IIFE 是什麼
簡單說,立即執行函數就是在宣告完函數後直接使用,使用方法為:
- 用一個括號把函數刮起來後
- 馬上接一個小括號
(function () {
console.log('IIFE');
})();
// IIFE
當然 IIFE 也可以使用箭頭函數 👇
(() => {
console.log('IIFE');
})();
// IIFE
以上兩個寫法等於
function fn() {
console.log('IIFE');
}
// 宣告完馬上執行
fn(); // IIEF
而 IIFE 立即執行函數也可以帶入參數使用:
(function (message) {
console.log(message);
})('hi');
// hi
立即執行函數 IIFE 可以做什麼?
若只是宣告完馬上使用使用函數,顯然沒有太大用處。它最厲害的地方其實是用來模擬區塊作用域,因為在 ES6 出現 let
、const
以前,是沒有區塊作用域的,這在某些時候非常不方便,所以我們會使用立即執行函數來模擬區塊作用域,舉例來說:
for (var i = 0; i < 1; i++) {
console.log(i); // 0 1
}
console.log(i); // 1
若是用 var
宣告變數,則在 for
迴圈外部仍然可以讀取到變數 i
。這不是一件好事,因為這樣容易覆蓋到變數的值,所以我們可以用 IIFE 立即執行函數來解決 👇
(function () {
for (var i = 0; i < 1; i++) {
console.log(i); // 0 1
}
})();
console.log(i); // 錯誤
不過現在比較少看到這種寫法了,是因為 ES6 出現了 let
和 const
,這裡個 key word 可以解決這個問題,也是我們平常用的寫法:
for (let i = 0; i < 1; i++) {
console.log(i); // 0 1
}
console.log(i); // 錯誤
立即執行函數 IIFE 的應用 1 - 和閉包結合創造臨時獨立作用域
很多文章會將立即執行函數和閉包一起講是因為,他跟閉包結合可以創建臨時獨立作用域,例如我們想要創造一個計時器,但又不希望計時器裡面的變數 count
影響到外部,就可以如下面這樣寫:
// 設置一個每秒執行一次的定時器
setInterval(
(function () {
// 定義一個 IIFE
let time = 0;
return function () {
// 利用閉包來保留 time 變數的值,並在每次執行時增加 1
console.log(++time);
};
})(),
1000,
);
// 不會和下面互相影響
let time = 100;
console.log(time); // 100
// 開始計時
// 1
// 2 ...
像是上面這樣,每一秒計時一次,而內部的 time
又不會跟外部的變數互相影響。
立即執行函數 IIFE 的應用 2 - 解決變數名衝突
有些時候我們引入的函式庫,可能會有變數名稱衝突的問題,舉例來說 jQuery
是用 $
當作變數名稱,如果其他函式庫也是用 $
,就會有衝突,此時可以傳入參數給立即執行函數來解決這個問題:
// 假設其他函式庫占用 $
const $ = () => console.log('Not jQuery');
(function ($) {
// 通過閉包限制作用域的變數名稱
$(document).ready(function () {
console.log('Hello jQuery');
});
})(jQuery);
// Hello jQuery
$(); // Not jQuery
以及在閉包貼文中提到的函式庫封裝,也是用立即執行函數來避免變數名稱的衝突:
(function () {
var jQuery = (window.$ = function () {
// Intialize
});
})();
立即執行函數 IIFE 的應用 3 - 製作具有私有變數的物件
在 JS 中,沒有私有變數的概念,但我們可以透過立即執行函數創造獨立作用域來模擬私有變數:
const module = (function () {
let privateVar = '私有變數';
function privateFunction() {
console.log('這是私有函數');
}
return {
publicVar: '公開變數',
publicFunction: function () {
console.log('這是公開函數');
console.log('可以訪問私有變數:' + privateVar);
privateFunction();
},
};
})();
module.publicFunction(); // 輸出:這是公開函式、可以訪問私有變數:私有變數、這是私有函數
console.log(module.publicVar); // 輸出:公開變數
console.log(module.privateVar); // 輸出:undefined
module.privateFunction(); // 輸出:錯誤,因為 privateFunction 是私有函式
立即執行函數 IIFE 的應用 4 - 讓 if else 更簡潔
有時候我們會利用 if else
來處理變數的值,比如說:
let animalName = 'rabbit'; // 預設動物是兔子
if (pet.canBark()) { // 如果動物會吠
if (PerformanceTiming.isScary()) animalName = 'wolf'; // 如果動物很可怕
else animalName = 'dog';
} else if (pet.canMeow()) { // 如果動物是喵喵叫
animalName = 'cat';
}
這樣有兩個壞處,一是有巢狀的 if else 不易閱讀,二是當程式碼一多的時候,我們在後面的程式碼可能會不小心改到 animalName。
我們可以搭配 const 和立即執行函數來優化這段程式碼👇
const animalName = (() => {
if (pet.canBark() && pet.isScary()) return 'wolf';
if (pet.canBark()) return 'dog';
if (pet.canMeow()) return 'cat';
return 'rabbit';
})
利用 const
就可以避免之後的程式碼更動到 animalName
的變數值。
小結和後記
今天我們介紹了立即執行函數,簡單說就是宣告完函數後馬上使用,它最主要的應用有四個
- 模擬塊級作用域
- 避免相同變數名的衝突
- 製作有私有變數的物件
- 優化 if else 的寫法
所以它並不只是單純馬上使用函數那麼簡單,雖然因為 ES6 新增 class
類語法,大大減少了 IIFE 和閉包的使用率,不過很多舊的程式碼仍然是使用 IIFE 和閉包,所以它還是很重要的觀念喔!
後記
在我寫了更多程式有更多經驗後,發現在 React 中,可以很常使用立即執行函數的第四種應用來優化程式碼,例如:
const SomeComponent = () => {
const someValue = (() => {
// 執行某些判斷
})
return <div>{someValue}</div>
}
這樣可以讓程式碼變得很簡潔。所以說 IIFE 還是很重要且實用的觀念~