Effective JavaScript Item 13 使用即時調用的函數運算式(IIFE)來建立局部域
本系列作為Effective JavaScript的讀書筆記。
所謂的即時調用的函數運算式,這個翻譯也許不太準確,它對應的英文原文是Immediately Invoked Function Expression (IIFE)。下文也使用IIFE來表達這一概念。
首先看一個程式:
function wrapElements(a) {var result = [], i, n;for (i = 0, n = a.length; i < n; i++) {result[i] = function() { return a[i]; };}return result;}var wrapped = wrapElements([10, 20, 30, 40, 50]);var f = wrapped[0];f(); // ?
這個程式的作用是,將傳入到wrapElements中的數組的每個元素進行一次"打包"操作,將元素替換成一個返回它們自身的函數,
也許你會認為最後輸出的而結果是10,但是最後的結果實際上是undefined。
會做出這種符合直覺的假設的原因是,在function() { return a[i]; };這段代碼中,一般會認為這裡的i就是當前迴圈時使用到的變數i,那麼下面的賦值就應該成立:
result[1] = function() { return a[1]; };
但是,不要忘了一個重要的事實,在以上的賦值語句中,首先會建立一個閉包。在這個閉包中引用到了其外部的變數i,回顧在Item 11我們學習到的關於閉包的一個原則,就是閉包會以引用的形式儲存其外部的變數,而不是以值的形式。
所以,在迴圈結束之後,i的值應該是5。那麼在調用wrapped[0]()時,就相當於調用:return a[5]。很顯然,這個值是不存在的,故返回undefined。
如果將上述代碼換成下面這樣:
function wrapElements(a) {var result = [];for (var i = 0, n = a.length; i < n; i++) {result[i] = function() { return a[i]; };}return result;}var wrapped = wrapElements([10, 20, 30, 40, 50]);var f = wrapped[0];f(); // ?
可以發現,變數i和n直到for迴圈開始的時候才會開始。那麼結果是怎麼樣的呢?
結果還是undefined。
這是因為VariableHoisting(Item 12)的緣故。無論在一個function中的哪裡聲明變數,該變數的定義部分總會出現在function的開始處。
那麼,如何讓程式以我們期待的方式運行呢,IIFE就派上用場了:
function wrapElements(a) {var result = [];for (var i = 0, n = a.length; i < n; i++) {(function() {var j = i;result[i] = function() { return a[j]; };})();}return result;}
在for迴圈中,建立了一個IIFE,將當前的迴圈變數i賦值給了j,然後在後面的function中返回的是a[j]。這相當使用了另外一個局部變數將外部變數當前的值給記錄下來,以便將來使用。那麼IIFE的價值就在於它能夠克服JavaScript語言中沒有塊範圍(Block Scoping)的弊端:通過建立一個匿名的閉包,將某個外部變數的當前值給儲存起來。就好比將一個變數的當前值"凍結"住,供將來使用。
IIFE的另一種形式是將需要"凍結"的變數作為參數傳入到IIFE中:
function wrapElements(a) {var result = [];for (var i = 0, n = a.length; i < n; i++) {(function(j) {result[i] = function() { return a[j]; };})(i);}return result;}
在使用IIFE的時候,有兩個注意事項:
- 如果在for或者while迴圈中使用了IIFE,那麼注意不要在IIFE中包含break以及continue語句。因為break和continue只有在迴圈中才能夠使用,否則拋出SyntaxError: Illegal break statement。
- 如果IIFE中使用了this或者arguments,那麼它們的意義可能會和你想象的有出入,關於這一點,在後續的Items中會進行介紹。
總結:
- 閉包會儲存外部變數的引用,而不是值
- 使用IIFE來建立局部範圍