Day 25:不是多了塊魚 —— 立即函數的應用整理
上一篇介紹到立即函數 (Self-Invoking Functions) 的用法,過程有沒有產生一個疑惑?
例如大費周章包裝了一個立即函數:
1
(function () {
2
console.log('Hello');
3
})();
Copied!
不如直接執行還比較乾脆:
1
console.log('Hello');
Copied!
一樣都是立刻執行的效果,何必脫褲子放屁,多此一舉,豈非多餘?
(Source: Youtube)

立即函數是脫褲子放屁?

程式是工具,工具是為了幫助解決問題而存在才有意義,不會單純因為語法酷炫或用起來很潮而增加價值。
一開始學到立即函數時,我的第一個感想確實是覺得很多餘。
但我相信每個語法的存在一定有其用意,因此試著去觀摩和整理使用立即函數的場合。
以下是我目前歸納出的用途,歡迎補充:

用途 1:封裝一次性的 Local Scope

可以封裝一段想立刻執行的程式碼,建立一次性的 Local Scope,讓一次性變數的生命週期留在函數內,避免汙染到 Global Scope。

Before:

在未經封裝前,一次性用途的變數 temp 暴露在全域作用域下,潛藏後續對其他程式造成干擾的風險:
1
var temp = 10 + 5;
2
console.log("Answer is " + temp);
3
4
// 執行其他的任務
5
otherMission();
6
function otherMission(){
7
console.log(temp); // temp 是全域變數,仍然存在
8
}
Copied!
執行結果:
1
Answer is 15
2
15
Copied!

After:

透過立即函數作封裝,避免不必要的變數影響殘留:
1
(function (){
2
var temp = 10 + 5;
3
console.log("Answer is " + temp);
4
})();
5
6
// 執行其他的任務
7
otherMission();
8
function otherMission(){
9
console.log(temp); // temp 不存在於此作用域
10
}
Copied!
執行結果:
1
Answer is 15
2
Uncaught ReferenceError: temp is not defined
Copied!
自然,也可以用普通的函數包裝,再透過函數呼叫來執行,差別是還要為函數作命名:
1
function execOneTime(){
2
var temp = 10 + 5;
3
console.log("Answer is " + temp);
4
}
5
execOneTime();
6
7
// 執行其他的任務
8
otherMission();
9
function otherMission(){
10
console.log(temp); // temp 不存在於此作用域
11
}
Copied!
好的命名也是需要花腦細胞去想,既然只是一次性而且立即性的用途,那就用立即函數吧!

用途 2:為物件實字 (Object Literals) 的屬性初始值產生動態值

Before:

原本如果想為物件屬性產生動態數值,必須先產生基本的初始物件,再額外對屬性來動態賦值:
1
var student = {};
2
student.score = Math.random();
3
console.log(student.score); // 0.7779381225655557
Copied!

After:

透過立即函數的用法,可以在物件實字初始化階段就動態產生結果:
1
var student = {
2
score: (function (){ return Math.random(); })()
3
};
4
console.log(student.score); // 0.7779381225655557
Copied!

用途 3:將字串內容轉成函數物件

資料傳遞格式 JSON 不支援儲存函數型態的資料,理論上無法用來傳遞函數物件。
但透過立即函數,配合 eval(),就有機會實現用 JSON 傳遞函數型態資料,例如以下範例:
1
var text = '{ "name":"John", "age":"function () {return 30;}"}';
2
var person = JSON.parse(text);
3
4
console.log(person); // {name: "John", age: "function () {return 30;}"}
5
console.log(typeof person.age); // "string"
6
7
var getAge = eval("(" + person.age + ")");
8
console.log(getAge); // ƒ () {return 30;}
9
console.log(getAge()); // 30
Copied!
如果不是特殊需求,實際專案不建議這樣的作法,會造成程式維護困難。

用途 4 : 閉包 (Closures) 的應用

透過立即函數的封裝實現閉包應用,例如:
1
var add = (function () {
2
var counter = 0;
3
return function () {return counter += 1;}
4
})();
5
6
console.log(add);
7
console.log(add());
8
console.log(add());
9
console.log(add());
10
console.log(counter);
Copied!
執行結果:
1
ƒ () {return counter += 1;}
2
1
3
2
4
3
5
Uncaught ReferenceError: counter is not defined
Copied!
變數 counter 被宣告於 add() 的函數作用域裡 (Function Scope Level),理論上離開函數就會被消滅。但透過閉包,可以讓 counter 繼續存活,但又不至於被 Global Context 直接操作,只能透過 add() 操控。
關於閉包,我們會另外詳細討論,此處就不贅述。

用途 5 : 瀏覽器書籤小工具

可以把 JavaScript 程式碼包裝成立即函數,存成瀏覽器的書籤,點下去就會立即執行。
例如常見的「解除右鍵」工具就是這種用法:
1
javascript:(function() { function R(a){ona = "on"+a; if(window.addEventListener) window.addEventListener(a, function (e) { for(var n=e.originalTarget; n; n=n.parentNode) n[ona]=null; }, true); window[ona]=null; document[ona]=null; if(document.body) document.body[ona]=null; } R("contextmenu"); R("click"); R("mousedown"); R("mouseup"); R("selectstart");})()
Copied!

總結

立即函數可以應用的用途:
  • 封裝一次性的 Local Scope
  • 物件實字 (Object Literals) 的屬性初始值產生動態值
  • 將字串內容轉成函數物件
  • 閉包 (Closures) 應用
  • 瀏覽器書籤小工具
相信還有其他應用情境,歡迎補充!

References