iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 20
7
Modern Web

JavaScript基礎二三事系列 第 20

Day20 立即呼叫的函式表示式(IIFE)

今天來看看IIFE

IIFE全名為Immediately Invoked Functions Expressions
指的是可以立即執行的Functions Expressions函式表示式,中文多譯為立即(執行)函式。

來看看以下程式碼

var hello = function(name){
	console.log('Hello ' + name);
};

這是一個Functions Expressions函式表示式,要呼叫它通常會寫成hello()

hello();

目前沒有傳值進去,所以函式印出Hello undefined
若把hello()這句刪掉,把程式碼改成這樣:

var hello = function(name){
	console.log('Hello ' + name);
}();

電腦在函式表示式後面讀到(),就知道要立刻呼叫這個函式,這種立刻執行的函式寫法就稱為IIFE。

若要傳值進去可以加參數在最後面的()

var hello = function(name){
	console.log('Hello ' + name);
}('Simon');

再看看另一個例子,將函式裏頭的console.log改成return,透過另一個console.log()呼叫變數指向的函式。

一般的函式表示式

var hello2 = function(name){
	return 'Hello ' + name
};
console.log(hello2);

函式結尾的}後面沒有()表示立即執行,所以console.log印出hello2時,是印出hello2指向的函式

IIFE

var hello2 = function(name){
	return 'Hello ' + name
}();
console.log(hello2)

函式結尾的}後面有()表示立即執行,當console.log印出hello2時,是印出hello2指向函式立即執行的結果值。

若要傳值進去,一樣加參數在函式最後面的()

var hello2 = function(name){
	return 'Hello ' + name
}('Simon');
console.log(hello2);

再來,透過變數指向立即執行函式,要呼叫它只要透過變數名hello2就好,不用像一般的Functions Expressions寫成hello2(),那現在如果寫成這樣會發生什麼事呢?

var hello2 = function(name){
	return 'Hello ' + name
}('Simon');
console.log(hello2());

hello2乍看指向立即執行函式,其實可視為指向立即執行函式的返回值Hello Simon,現在console.log()的hello2後面多加一個(),電腦就會往return回傳的'Hello ' + name去找函式,現在'Hello ' + name是字串Hello Simon,並不是函式,自然就報出錯誤囉!


再看看以下程式碼,在第16天筆記,我們知道Expression表示式會產生(回傳)一個值,而這個值不一定會被開發者用變數指著。
現在我們不用變數指著IIFE,這種寫法可以嗎?

function(food){
	console.log('大俠愛吃' + food)
}('漢堡包');

因為沒有變數指向它(不是Functions Expressions),它也沒有名子(不是Function Statement),所以語法解析器認為這段程式寫錯了,自然報錯。

解決方法是,最外面再用一個()把它包起來

(function(food){
	console.log('大俠愛吃' + food)
}('漢堡包'));

執行結果

這樣就不會報錯了,這也是IIFE,也是最常在JS框架、套件看到的寫法。
至於放在後面,立即呼叫的(),要放在裡面或外面都可以。

(function(food){
	console.log('大俠愛吃' + food)
})('漢堡包');

這樣可以,兩個結果都一樣,不過作者認為開發者擇一使用就好,因為要保持撰寫風格的一致。


另外,ES5版本的JS有全域執行環境(作用域)、函式執行環境(作用域)兩種,直到ES6版才出現塊級作用域,在ES6出來前,為了避免設定太多的全域變數,開發者往往會將變數設定在函式中,使其成為區域變數,尤其是設定在IIFE中,確保不會汙染到全域環境的變數。

例如

var seafood = '波士頓龍蝦';
(function(name){
	var seafood = '北海道帝王蟹';
	console.log(name + '好~想吃' + seafood);
}('Simon'));

結果是

因為執行環境的不同,'北海道帝王蟹'只存在IIFE裡,不會影響到外部環境的變數。
也就是說,我們可以將程式碼包在被()包住的IIFE裡,當IIFE和全域環境有宣告相同變數名稱時,IIFE不會蓋掉全域的變數。

不少JS框架、套件的開頭與結尾被()包住,程式碼被立即函式包著,其目的是怕污染到使用者(開發者)的全域環境。
反過來說,若全域和IIFE內都有重複的變數名,那這些框架、套件該如何取用全域的變數呢?

var food = '水牛城雞翅';
console.log(food);
(function(global){
    var food = '雞塊';
	global.food = '麥脆雞';
    console.log(food);
})(window);
console.log(food);


為避免無意義的外部查找,將window當參數傳入,使其成為這個IIFE的區域物件,確保在IIFE內的程式能故意取用到全域的特定變數。
 
 
 
 
小結

使用立即函式的好處,除了可以立即執行程式碼,省略多餘的呼叫,還可以用來避免汙染全域執行環境的東西,減少開發時因相同命名相衝的bug。
今天的筆記內容可以參照Udemy課程:JavaScript 全攻略:克服JS的奇怪部分4-44、4-45


上一篇
Day19 陣列、arguments、spread與分號
下一篇
Day21 閉包
系列文
JavaScript基礎二三事30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中
0
Ho.Chun
iT邦新手 5 級 ‧ 2019-11-15 09:56:39

相當的清楚 !! 推 /images/emoticon/emoticon41.gif

1
matuyou0301
iT邦新手 5 級 ‧ 2021-04-26 10:45:57

謝謝,解釋的非常清楚,一直在思考匿名函數外部在做一個()的用途是什麼,原來就只是把它當作IIFE。此外IIFE的函數放在函數最後端也造成一時的理解障礙,但看完版大的文章後就非常清楚了解這個函數的用途。
不過我個人認為JS強大的地方在於它是函數式程式語言,不知道ES6出現塊級作用域對於這個語言本身的理念是否有影響。

0
Johnny Fang
iT邦新手 5 級 ‧ 2023-02-25 12:07:37

文字跟範例的 code 都寫得淺顯易懂,謝謝!

0
wei_lin
iT邦新手 5 級 ‧ 2023-05-12 10:04:15

謝謝你,這是我看過所有介紹IIFE的文章中最清楚易懂得一篇!

我要留言

立即登入留言