[分享] IIFE 與 for迴圈的一些小知識

Lastor
Code 隨筆放置場
5 min readAug 12, 2019

根據這篇的教學內容, 以及之前跟助教們的一些討論, 終於越來越明晰IIFE到底是幹嘛的。
今天在查找IIFE相關資料時, 看到了一些有趣的內容。

javascript 模仿块级作用域 — wangjun5159的专栏 — CSDN博客

這篇大意是在說明IIFE是如何產生的, 以及為何需要IIFE?

核心的原因是因為, 以前JS只有var的年代, 沒有明確的塊級作用域(block scope)。
以前學期一, Ruby摸的比較深的同學應該會稍微知道Ruby可以創建Class, 幫code進行分類。

實務上, 當我們在一個HTML文件中引入多個script時, 假設這樣↓

<script src="library1.js">
<script src="library2.js">
<script src="main.js">

這種情況, JS的這三個檔案是共用作用域的。
意思是說, 如果我在第一份文件宣了一個變數, const x = 0
我的第二與第三份文件, 將永遠存在 x = 0 這個變數。
這在工作時會碰到非常多的囧境……

但如果是Ruby這樣的語言, 這種狀況, Ruby可以把三個檔案的code, 分別包裝在三個不同的Class類別中。這樣三個檔案就可以各自擁有獨立的作用域, 互不干擾。需要時, 也可以透過Class方法互相調用。

JS的用戶們, 自然的會想說, 既然JS沒有Class系統, 那如果我把檔案都包在一個Local作用域中, 應該也可以解決問題? 而JS最常見, 最好用的Local作用域就是function了。JS用戶們藉由function的特性, 把code全封裝進一個Local作用域, 然後透過宣告即執行的方式, 成功的模擬出塊級作用域(block scope), 或是Ruby那種Class系統的效果。IIFE就這麼誕生了。

另外, 核心需求是為了做出獨立作用域。
所以就算不使用IIFE的寫法, 單純的這樣寫應該也是可以的。

function main() {  
該檔案的code...
}
main() // 宣告後, 手動執行

只是這樣的寫法, 似乎有兩個問題。
一是, 寫起來比IIFE長。
二是, function整塊仍然會保存在記憶體中。 不像IIFE, 執行完記憶體就會被釋放出來。
而且因為用function包住了, 有心人士也很容易把你的code整包存走。

參考:block — JavaScript | MDN

然後來談談最上面那篇文, up主開頭給的經典問題式子

function returnFunArr(){
var arr=[]
for(var i = 0; i < 10; i++){
arr[i] = function() { console.log(i) }
}
return arr
}
for(var f of returnFunArr()){
f()
}

這個式子非常的有趣……
returnFunArr() 這個函式會生產出一個陣列

arr [ f(), f(), f()...]  // 共10個function

後面的for, 就是迭代出arr裡面的function, 逐個執行它。
理論上, 因為這個陣列也是用for迴圈, 代入參數 i 批次生產。
所以console.log理想應該是會打印出數字 0 ~ 9。
但是它卻回傳了10個數字10……?

這牽扯到兩個問題, 一個是該文章提及的「閉包作用域鍊」。
另一個是for迴圈的運作方式。

先來簡單說說, 為什麼會回傳10個一樣的數字, 而不是根據 i++ 回傳一個 0~9 的序列?
「閉包作用域鍊」這個名詞太過艱澀了, 讓我們直接無視吧。
它的邏輯其實很單純, 重點就是小括號的這一行….

for( var f of returnFunArr() ) { ... }

它運作時, of 後面call的是一個函式, 而不是變數。並且加上了(), 執行了這個函式。
所以 returnFunArr() 會全部run完之後, 程式才往下繼續進行這個for。

arr[i] = function() { console.log(i) }

生產arr陣列的這一段是問題點, 前面 arr[i] 裡面的 i , 會正常的依次產生0~9。
但後面的 console.log(i) 被function包住了, 它沒有被執行
所以, 如果去單個檢查return噴出來的arr, 會看到console.log裡面包的還是變數 i

function returnFunArr(){
var arr=[]
for(var i = 0; i < 10; i++){
arr[i] = function() { console.log(i) }
}
return arr
}
const arr = returnFunArr() // 執行函式, 並用一個變數接回傳值
arr[0] // f() { console.log(i) }

如果執行裡面的function, log會噴一個 10 出來

arr[0]()  // 10

這個10是怎麼來的? 它是來自於 for 這個迴圈, i++最後的累加值

for(var i = 0; i < 10; i++)

那就奇怪啦? 為什麼不是 9 而是 10 呢? 式子不是給了條件 i < 10 嗎?
這樣最後出來的 i 應該是 9 才對啊?

這跟就牽扯到 for 的運作模式了。 可以試著寫一串這樣的code來觀察 for 是怎麼工作的。

let i = 0  // 其實給for使用的變數i, 可以直接拿父層已宣過的變數
for(i; i < 10; i++) { } // 花框不放東西, 不動作
console.log(i) // 10

它居然噴了一個 10 出來, 而不是 9。
從這可以看出 for 的工作原理, 應該是它第9次時, i = 9 開始run迴圈。
符合 i < 10, 就把 i 傳進後面的花框進行動作, 然後 i 自己進行了 i++, 變成了 i = 10
第10次迴圈時, i = 10, 撞到了 i < 10 接收到false, 整個for就停在這被break。
所以最後 i 會是 10 而不是 9。

--

--

Lastor
Code 隨筆放置場

Web Frontend / 3D Modeling / Game and Animation. 設計本科生,前遊戲業 3D Artist,專擅日本動畫與遊戲相關領域。現在轉職為前端工程師,以專業遊戲美術的角度涉足 Web 前端開發。