最近,我寫了一篇關於syntax of JavaScript’s IIFE pattern的文章,來解釋為什麼我們用現在的方式來寫立即執行函式表示式。少數的讀者批評文章過時了,都在爭論在ECMAScript 2015中介紹的塊級作用域變數使IIFE變得過時了。
恰恰相反,立即執行函式表示式一點也沒有過時!因為這個理由,我決定寫這篇後續文章來介紹一些立即執行函式表示式的常見用法。注意以下的列表是不完整的,所以如果你喜歡的用法沒有在文章出現,希望你不要有什麼不好的感覺。
函式作用域 VS 塊級作用域
通過var
關鍵字宣告的本地變數僅作用於當前閉包域,如果不存在這樣的一個閉包函式,那麼將會建立一個汙染全域性作用域的全域性變數。為了防止這種情況出現,我們可以使用IIFE來建立一個包含有這個本地變數的函式。
1 2 3 4 5 6 |
(function() { var foo = "bar"; console.log(foo); })(); foo; // ReferenceError: foo is not defined |
目前的爭論是,我們可以使用在ECMAScript 2015介紹的塊級作用域變數來代替IIFE,以達到相同的效果。相比於函式級作用域,let
和const
關鍵字宣告的本地變數僅作用於當前所處的”塊”級域。
1 2 3 4 5 6 |
{ let foo = "bar"; console.log(foo); } foo; // ReferenceError: foo is not defined |
然而,塊級作用域變數不是立即函式執行表示式的替代品。確實,如果支援ECMAScript 2015,let
和const
能夠用來限制本地變數只在包含它的塊級作用域內使用。
如果,你在不支援ECMAScript 2015的環境(例如一些舊的瀏覽器)中執行你的JavaScript程式碼。你就不能使用let
和const
關鍵字來建立塊級作用域變數。你將不得不求助於以前經典的函式級作用域方法。
閉包和私有資料
IIFE的另一個用法是為區域性變數提供一個封裝的作用域,在IIFE返回的函式中能夠訪問該變數。這種方式即_a closure is created_允許函式訪問這個本地變數,即使這個函式在IIFE的詞法範圍之外執行時。
假設我們要建立一個uniqueId
函式,每次呼叫該函式時就會返回一個唯一的id(比如 “id_1”,“id_2”等)。在下面的IIFE中,記錄了一個私有的計數變數(count
),每次呼叫計數函式uniqueId
的時候,就會將count
加一。我們在IIFE中返回的另一個函式,這個函式在呼叫時會返回一個新的識別符號字串。
1 2 3 4 5 6 7 8 9 10 11 |
const uniqueId = (function() { let count = 0; return function() { ++count; return id_${count}; }; })(); console.log(uniqueId()); // "id_1" console.log(uniqueId()); // "id_2" console.log(uniqueId()); // "id_3" |
注意,在IIEF之外無法訪問這個計數變數count
。除了從IIEF中返回的函式,別人無法讀寫該變數。這樣就能建立真正的私有狀態,它只能以受控的方式進行修改。revealing module pattern非常依賴於這種機制。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
const counter = (function() { let counterValue = 0; return { increment() { ++counterValue; }, get value() { return counterValue; } }; })(); counter.increment(); console.log(counter.value); // 1 counter.increment(); counter.increment(); console.log(counter.value); // 3 |
當使用IIFE來返回一個”封閉”一些本地變數來管理私有資料的函式時,let
和const
都不能替代它。
變數重新命名
有時,你可能碰到一種情況,你正在使用的兩個不同的庫暴露的全域性變數名是相同的。例如,考慮一下你正在使用jQuery同時另一個庫也指定了一個為$
的全域性變數。
為了解決命名衝突問題,可以將一段程式碼封裝在一個IIEF中,將一個全域性變數(比如,jQuery)作為引數傳入IIFE。在函式內部,就可以以一個任意的引數名(比如,$)來訪問該引數值:
1 2 3 4 5 6 7 |
window.$ = function somethingElse() { // ... }; (function($) { // ... })(jQuery); |
不管在外部作用域有什麼值指定給$
,在IIFE中,這些值都會被”遮蔽”,$
引數一直指向jQuery方法。
捕獲全域性物件
JavaScript程式碼在不同環境執行時,你所使用的全域性物件是不同的。當程式碼在瀏覽器執行時,全域性物件是windows
。但是在Node.js中,全域性物件是global
。由於在寫通用的JavaScript程式碼時,你肯定不想硬編碼這兩個名字其中的任何一個,這時你就可以使用一種”包裝”的方式就像下面這樣:
1 2 3 |
(function(global) { // ... })(this); |
不管是瀏覽器還是Node.js的環境,global
引數將會指定到對的全域性物件上。如果想了解更多關於使用這種技巧來捕獲全域性物件的細節內容,請移步this post by Todd Motto。
壓縮方面的優化
混疊變數名的方法也可以用來優化程式碼,這種方式使程式碼能夠被更有效的壓縮。舉例如下:
1 2 3 |
(function(window, document, undefined) { // ... })(window, document); |
一個JavaScript壓縮工具例如UglifyJS可以縮短函式的引數名為單個字母的識別符號
1 2 3 |
(function(w, d, u) { // ... })(window, document); |
更短識別符號名會使檔案的體積變得更小。然而,如果HTTP的返回內容通過Gzip或者Deflate進行壓縮,檔案的大小已經被很有效的壓縮了。因此,如果結合壓縮演算法,壓縮技術的邊際收益會變得更小。所以自己權衡和比較返回內容的大小,較短的名字可能仍然是有作用的。