IIFE 為 FP 的招牌菜,由於其獨特的 Lexical Scope,使其在 ECMAScript 有不少獨特應用。
Version
ECMAScript 2015
IIFE
let f = x => x + 1
f(2)
傳統我們會先定義 function,然後呼叫執行之。
對於這種只使用一次的 function,反正最後都是要執行,可使用 IIFE 進行化簡。
(x => x + 1)(2) // ?
x => x + 1
為 anonymous function,但因為 function 沒有名稱,該如何執行呢 ?
可使用 ()
將 arrow function 刮起來,之後再加上 ()
傳入 argument 立即執行。
IIFE
全名為 Immediately Invoked Function Expression,提供了執行 anonymous function 方法
Global Variable
let count = 0
let inc = () => count++
inc() // ?
inc() // ?
inc() // ?
傳統若要實作 counter,會在 global scope 建立 count
variable,在 inc()
以 side effect 更新 global variable。
這樣做有兩個缺點:
count
為定義在window
object 的 global propertyinc()
為定義在window
object 的 global method
如此雙雙污染了 global scope 與 window
object。
let inc = (x => {
let count = x
return () => count++
})(0)
inc() // ?
inc() // ?
inc() // ?
比較好的是在 IIFE 以回傳 function,如此 count
variable 被鎖在 IIFE 內藉由 closure 鎖住 count
,不會污染 global scope。
New Scope
for(var i = 0; i < 5; i++)
setTimeout(() => console.log(i), 1000)
這也是 ECMAScript 很有名題目,若我們希望在 5 秒之內每秒依序印出 0
、1
、2
、3
、4
。
直覺會使用 for
loop 搭配 setTimeout()
。
但結果為 同時
印出 5
,並不如預期。
因為 setTimeout()
為 asynchronous function,並非立即執行,而是先註冊到 callback queue,等 synchronous function 都執行完,才再 1000ms
後同時執行,因此時間該改成 1000 * i
才對。
另外 var
所宣告 variable 為 function scope,當 _ => console.log(i)
要執行時,i
已經累計到 5
,因此我們要對 setTimeout()
建立自己的 scope 能鎖住 i
。
for(var i = 0; i < 5; i++) {
(i => {
setTimeout(() => console.log(i), 1000 * i)
})(i)
}
ES5 的標準解法就是使用 IIFE 為 setTimeout()
建立新的 scope 鎖住 i
,因此建立 parameter 為 i
的 IIFE 將 for
loop 的 i
傳入。
for(let i = 0; i < 5; i++)
setTimeout(() => console.log(i), 1000 * i)
ES6 則有更簡單解法,由於 let
是以 {}
的 block scope,因此 for
的 body {}
就自帶 scope,可不必使用 IIFE。
Alias
let f = (x, y) => {
let z = x + y
return z * 2 + 1
}
f(2, 3) // ?
實務上常在 function 內根據 parameter 建立新的 variable。
let f = (x, y) => (z => z * 2 + 1)(x + y)
f(2, 3) // ?
可建立 arrow function 以 parameter 取代 variable,定義則改用 IIFE 以 argument 傳入。
Lexical Scope
((w, d) => {
})(window, document)
有時候會看將 window
與 document
這類 global object 以 IIFE 的 argument 傳入,其目的有二:
- 在 arrow function 內只要以
w
與d
即可,程式碼較精簡 - 由於 arrow function 就可找到
w
代表window
,d
代表document
,因此查找 lexical scope 時速度較快
Conclusion
- 隨著 ECMAScript 的演進,IIFE 的重要性不若以往,ES module 可取代 IIFE 的 避免污染 global scope;
let
可取代以 IIFE 建立 scope;SPA 與 data binding 出現也很少直接存取window
與document
,唯藉由 IIFE 的 parameter 實現 alias 或在 IIFE 回傳 function 以 closure 鎖住 variable 在實務上還常常用到
Reference
John Resig, Secrets of the JavaScript Ninja
許國政, 008 天重新認識 JavaScript
Dr. Axel Rauschmayer, Speaking JavaScript