跳至主要内容

[JS] Function基本介紹-2

TL;DR

本篇介紹立即函式內容如下:

  1. 為什麼要使用立即函示
  2. 立即函示的特性
  3. 立即函示參數傳遞
  4. 立即函示回傳值

參考資料

相關連結


立即函式(IIFE)

如同字面上的意思,立即函式會在載入JS程式碼之後立刻執行。

立即函式具有以下幾個特性:

  1. 不可再次呼叫
  2. 可以限制變數作用域避免污染全域

基本範例

首先,我們先從將傳統的函式轉換為立即函式的範例開始。

以下是一段我們定義好的函式,並且在定義之後馬上執行:

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名稱相同,也可以正確執行而不會產生錯誤。

當然內部的變數也都會被限制在該立即函式中。這麼一來就可以避免污染到全域的變數。

tip

並且因為立即函式的名稱無法在外部再次呼叫,所以大部分狀況我們是不需要給予立即函式名稱的。

(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內...

note

雖然在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); // 我的名字是小明
note

另外上方程式碼也可以透過ES6的箭頭函式以及語法糖縮寫來改寫成以下程式碼:

const whatIsMyName=(myName=>`我的名字是${myName}`)('小明'); //這邊一樣是立即執行函式
console.log(whatIsMyName); // 我的名字是小明

這段程式碼使用了以下幾個技巧:

  1. 箭頭函式 (myName => 我的名字是${myName}) 這個部分是一個箭頭函式的語法,相當於 function(myName) { return 我的名字是${myName}; }
  2. 模板字面值 程式碼中使用了反引號 `` 來定義字串,並使用 ${myName} 的語法將變數插入字串中,這個功能被稱為模板字面值或字串插值。
  3. 立即執行函式 最後,整個箭頭函式後面加上 ('小明') 的部分,代表著在定義完箭頭函式後,馬上以 '小明' 作為參數來呼叫執行該函式。

所以這段程式碼的作用是:定義一個可以接受參數的函式,並立即以 '小明' 作為參數執行該函式,函式的回傳值是一個字串 '我的名字是小明'。 這種寫法通常被用來取得一個函式的執行結果,而不需要另外再呼叫該函式。

應用-掛載Window

我們可以透過物件傳參考的特性,做到在不同的立即函式的資料傳遞:

const a={};

(function (b) {
b.person='Ming'
})(a)

;(function (c) {
console.log(c.person);
})(a)

// Ming
note

在連續調用兩個立即執行函式時,ASI並沒有辦法正確運作。

所以需要在立即函式用;區隔開來。

另外,另一種常見的方法是直接將全域物件給傳入(即window or global,視執行環境不同而有所不同)。

上方程式碼可以改寫如下:

(function (global) {
global.person='Ming'
})(window)

;(function () {
console.log(person);
})()
note

許多大型框架,都會直接撰寫立即函式,並且將window給傳入,接著直接將執行過後的物件掛載在全域之下。