再繼續往下走之前我們先來認識一下什麼是IIFE?
第一次看到這個詞的時候,是在看youtube看**深入淺出 Javascript30
** 的解說影片上得知的,但當時並不在意,想說課堂上沒教應該不是什麼重要的,誰知道現在又在**JavaScript: Understanding the Weird Part
**上看到在教這個,看來是要來好好了解一下
IIFE,全稱Immediately Invoked Functions Expressions(立即執行函式)
在之前有提到建立 function 的方法通常有 function statement 和 function expression 這兩種方式,
而IIFE
指的就是透過 function expression 的方式來建立函式,並且立即執行它。
如下列程式碼:
// function statement
function greet(name) {
console.log('Hello ' + name);
}
greet('John');
// using a function expression
var greetFunc = function(name) {
console.log('Hello ' + name);
};
greetFunc('John');
// using an Immediately Invoked Function Expression (IIFE)
var greeting = (function(name) {
return 'Hello ' + name;
})('John');
console.log(greeting);
輸出結果為:
Hello John
Hello John
Hello John
前面兩段code在之前的章節我們都有學習到,應該難不倒大家吧,但是第三段code看起來很像是第二段code但又多了點東西,讓我有點困惑。
其實這就是IIFE
,用function expression的方式建立function後直接在最後面加上()。
我們用下面兩張圖片來說明會更加了解:
這張圖片就是我們之前學的function expression,我們可以看到,當我們console.log(greeting)出來的會是一個function。
既然知道前面這一段是一個function後,在正常的情況下,當我們今天要使用這個function就要使用「調用 (invoke)」,也就是再加上一個()
。也就是第一段跟第二段的code。
但如果是要使用IIFE
的話,必須先把前面的function用()
包起來,然後再加上「調用 (invoke)」,他就會直接執行這個function裡的內容並回傳字串。
另外這邊還有一個重點,在使用 IIFE
的寫法後,要記住他回傳的內容會是 字串 ,因此我們沒有辦法再次執行它。
如果我們強制在後面加上(),例如:
// using an Immediately Invoked Function Expression (IIFE)
var greeting = (function(name) {
return 'Hello ' + name;
})('John');
console.log(greeting());
我們會得到:
TypeError: greeting is not a function
所以我們要特別注意這邊,避免出現錯誤(現在才知道我之前為什麼有時候會出現這個東西了)
在之前我們有提到過expression 的概念:輸入後能夠直接回傳值的一串程式,我們一般會把它存成一個變數,但是它不一定要被存成一個變數。
當如果想在function也做到,這時候我的第一個想法會是:
// ❌ 錯誤寫法
function(name) {
var greeting = 'Inside IIFE: Hello';
console.log(greeting + ' ' + name);
}
這時候我會得到一個錯誤:
SyntaxError: Function statements require a function name
之所以會錯誤是因為我用 function 做為開頭,所以JavaScript 引擎在解析程式碼的時候,它會認為你現在要輸入 function statement,但我卻沒有給這個function一個的名稱,於是它無法正確理解這段程式碼便拋出錯誤。
所以我們只要跟JavaScript 引擎說,這一整個並不是function statement就可以了。
(function(name) {
var greeting = 'Inside IIFE: Hello';
console.log(greeting + ' ' + name);
});
這是我們最常使用的做法:用括號 () 把 function(){ …} 包起來。
因為課堂上有說到,我們只會在括弧內放入 expression,例如 (3+2),而不會放 statement 在括弧內,所以JavaScript 就會以 expression 的方式來讀取這段函式。因此這個 function 會被建立,但是不會被存在任何變數當中,也不會被執行。
這時候再套用到剛剛學到的IIFE,在最後面加上()
var firstname = 'John';
(function(name) {
var greeting = 'Inside IIFE: Hello';
console.log(greeting + ' ' + name);
}(firstname));
還記得之前有提到的一個名詞:namespace。
namespace的使用是為了避免變項名稱重覆所造成的問題,而IIFE也很類似,他也是可以避免我們所建立的變項名稱因為覆蓋而造成影響。
我們就拿下面的範例來說明在JavaScript引擎實際發生了什麼:
(function(name) {
var greeting = 'Hello';
console.log(greeting + ' ' + name);
}('John'));
當我執行剛剛那段code時,會先建立Global Execution Context(裡面是空的,因為我們沒有設立變數)
接下來JavaScript引擎會執行這段IIFE,它會將這個匿名函式儲存在Execution Context中。
由於我們在function的最後有加上( ),所以這段function會立即被執行。
JavaScript引擎會去逐行執行我們這個function中的程式碼內容,當它發現到我們的程式碼中建立了一個變數var greeting = 'Hello';
,因此這個變數就被建立在這個execution context 中。
因此,透過IIFE,我們可以發現,在IIFE中所建立的變數,都不會影響到Global Execution Context所建立的變數,也就是說,透過IIFE,它避免了我們的變數間可能會互相干擾覆蓋的情況。
那如果我要讓function execution這層的變數能夠同時影響到Global Execution Context的變數時,我該怎麼做?
其實也是之前有提到的一個觀念:by reference
由於我們知道物件是by reference
的特性,因此我們只要把全域變數帶入就可以了,因此我們必須要多一個參數,叫做global
,在帶入參數的地方放入window
(全域變數),之後再直接針對window裡面的物件去做改變。如下:
var greeting = "Hola";
(function(global, name) {
var greeting = 'Hello';
global.greeting = 'Hello';
console.log(greeting + ' ' + name);
}(window, 'John'));
console.log(greeting);
我又發現鐵人賽的一個困難點了,排每天主題!!!
開賽前都不覺得這會是問題,但實際開始寫後發現這是一個很難的問題XD
就例如今天的主題,我非常猶豫要不要先講Event Loop,畢竟上一篇是講Event 剛好名字有相關(喂~~)。但講Event Loop又會帶到callback function,講到這個又會提到closures…一環扣著一環啊,最後我放棄選擇了,我決定用我之前上JavaScript: Understanding the Weird Part所教的順序來寫,這樣我的內容才會比較有條理一點吧><”