[JS] Function基本介紹-2
TL;DR
本篇介紹立即函式內容如下:
- 為什麼要使用立即函示
- 立即函示的特性
- 立即函示參數傳遞
- 立即函示回傳值
參考資料
- 呼叫函式時,到底有多少個參數 / 變數可供使用? | Casper
- JavaScript 一級函式 (First Class Functions) | Casper
相關連結
- 一級函式(First-class Function) | MDN
- IIFE | MDN
立即函式(IIFE)
如同字面上的意思,立即函式會在載入JS程式碼之後立刻執行。
立即函式具有以下幾個特性:
- 不可再次呼叫
- 可以限制變數作用域避免污染全域
基本範例
首先,我們先從將傳統的函式轉換為立即函式的範例開始。
以下是一段我們定義好的函式,並且在定義之後馬上執行:
function foo() {
console.log('bar');
}
foo(); // bar
轉換為立即函式的方法有數種,以下分別介紹。執行結果完全相同。
• 型式一
將函式使用小括號包起來。並且在小括號結尾前方補上另一組小括號。
(function foo() {
console.log('bar');
}());
• 型式二
將函式使用小括號包起來。在小括號結尾後方補上另一組小括號。
(function foo() {
console.log('bar');
})();
特性介紹
• 無法再次呼叫
前面有介紹立即函式的特點為無法再次被呼叫,這是什麼意思呢?
讓我們用以下範例簡單示範這個概念:
(function IIFE() {
console.log('IIFE',IIFE);
})();
// IIFE ƒ IIFE() {
// console.log('IIFE',IIFE);
// }
執行結果圖示
目前看起來IIFE這個具名函式可以正確被調用。
但是!!當我們在立即函式外再次調用IIFE時則會出現錯誤:
(function IIFE() {
console.log('IIFE',IIFE);
})();
console.log(IIFE)
//執行結果如下:
// IIFE ƒ IIFE() {
// console.log('IIFE',IIFE);
// }
// Uncaught ReferenceError: IIFE is not defined
執行結果圖示
• 限制作用域
另一個使用IIFE的原因是因為,我們可以限制作用域,避免造成變數的衝突或覆蓋。
(function IIFE() {
console.log('IIFE1',IIFE);
})();
(function IIFE() {
console.log('IIFE2',IIFE);
})();
// IIFE1 ƒ IIFE() {
// console.log('IIFE1',IIFE);
// }
// IIFE2 ƒ IIFE() {
// console.log('IIFE2',IIFE);
// }
執行結果圖示
可以發現雖然我們定義了兩個具名函式都叫IIFE,但是因為是立即函式,所以就算Function名稱相同,也可以正確執行而不會產生錯誤。
當然內部的變數也都會被限制在該立即函式中。這麼一來就可以避免污染到全域的變數。
並且因為立即函式的名稱無法在外部再次呼叫,所以大部分狀況我們是不需要給予立即函式名稱的。
(function () {
console.log('立即函式');
})();
上方程式碼是可以正確執行的。
最後,我們來示範一下內部變數是否可以正確限制作用域:
(function(){
var Ming='小明';
console.log(Ming);
})();
// 小明
上方程式碼中,Ming變數可以正確在立即函式內使用。
(function(){
var Ming='小明';
console.log(Ming);
})();
console.log(Ming);
// 執行結果如下
// 小明
// Uncaught ReferenceError: Ming is not defined
可以看到小明在外層是無法正確取用的
雖然我覺得有一點雞肋...因為直接使用function時var變數也會被限制在function scope內...
雖然在ES6之後已經有Const、Let等不同於Var的宣告方式可以使用,來讓我們避免變數污染問題。但是在大型框架上仍然常可以看到匿名函式的蹤影。
參數傳遞
立即函式也可以進行參數的傳遞,範例如下:
(function (myName) {
console.log(`我的名字是${myName}`);
})('小明');
// 我的名字是小明
當然另一種IIFE的寫法也可以傳遞參數:
(function (myName) {
console.log(`我的名字是${myName}`);
}('小明'));
立即函式也是表達式
因為立即函式在立刻執行的時候,如同其他的function在執行的狀況一樣,可以回傳一個結果(即為表達式)。
既然是表達式,我們就可以賦值到變數上。
const whatIsMyName=(function(myName){
return `我的名字是${myName}`
})('小明');
console.log(whatIsMyName); // 我的名字是小明
另外上方程式碼也可以透過ES6的箭頭函式以及語法糖縮寫來改寫成以下程式碼:
const whatIsMyName=(myName=>`我的名字是${myName}`)('小明'); //這邊一樣是立即執行函式
console.log(whatIsMyName); // 我的名字是小明
這段程式碼使用了以下幾個技巧:
- 箭頭函式
(myName => 我的名字是${myName})
這個部分是一個箭頭函式的語法,相當於function(myName) { return 我的名字是${myName}; }
- 模板字面值
程式碼中使用了反引號 `` 來定義字串,並使用
${myName}
的語法將變數插入字串中,這個功能被稱為模板字面值或字串插值。 - 立即執行函式
最後,整個箭頭函式後面加上
('小明')
的部分,代表著在定義完箭頭函式後,馬上以 '小明' 作為參數來呼叫執行該函式。
所以這段程式碼的作用是:定義一個可以接受參數的函式,並立即以 '小明'
作為參數執行該函式,函式的回傳值是一個字串 '我的名字是小明'
。
這種寫法通常被用來取得一個函式的執行結果,而不需要另外再呼叫該函式。
應用-掛載Window
我們可以透過物件傳參考的特性,做到在不同的立即函式的資料傳遞:
const a={};
(function (b) {
b.person='Ming'
})(a)
;(function (c) {
console.log(c.person);
})(a)
// Ming
在連續調用兩個立即執行函式時,ASI並沒有辦法正確運作。
所以需要在立即函式用;
區隔開來。
另外,另一種常見的方法是直接將全域物件給傳入(即window or global,視執行環境不同而有所不同)。
上方程式碼可以改寫如下:
(function (global) {
global.person='Ming'
})(window)
;(function () {
console.log(person);
})()
許多大型框架,都會直接撰寫立即函式,並且將window
給傳入,接著直接將執行過後的物件掛載在全域之下。