Sean's Blog

An image showing avatar

Hi, I'm Sean

這裡記錄我學習網站開發的筆記
歡迎交流 (ゝ∀・)b

LinkedInGitHub

Understand JavaScript #14 立即呼叫的函式表達式 (IIFEs) 與安全程式碼

本文主要內容為探討「IIFE」的相關知識,理解為什麼 IIFE 會被應用在各種大型框架或資源庫裡面,並且能幫助撰寫安全的程式碼。

立即呼叫的函式表達式 (IIFEs)

演變過程

  • Immediately Invoked Function Expressions (IIFEs):在創造函式後立刻呼叫函式

以下三個函式最終都會出現 Hello Damao 的結果,其中第三個我們比較陌生,它是回傳 (return) 一段字串,並賦予給等號左邊的變數 greeting

1// Function Statement
2function greet(name) {
3  console.log('Hello ' + name);
4}
5greet('Damao');
6
7// Using a Function Expression
8var greetFunc = function (name) {
9  console.log('Hello ' + name);
10};
11greetFunc('Damao');
12
13// Return a value to variable
14var greeting = function (name) {
15  return 'Hello ' + name;
16};
17console.log(greeting('Damao')); // Hello Damao

我們對剛才的函式做一些修改,我們在 function{}() 後方加上 () 來執行函式。

函式的執行步驟:創造函式 → 接受參數 → 執行函式 → 得到結果 → 傳入等號運算子。

1// Using an Immediately Invoked Function Expression
2var greeting = (function (name) {
3  return 'Hello ' + name; // 回傳一個字串
4})('Damao');
5console.log(greeting); // 是一個字串,不是函式

繞過語法解析器

當語法解析器看到「function 在最前面,或是接在分號的後面」時,Parser 會預期這是一個函式陳述式,因此函式也需要有名稱,不能是匿名函式。

1function greet(name) {
2  return 'Hello ' + name;
3}

那我們該如何讓 Parser 瞭解到我想要執行一個 IIFE,而不是要寫一個函式陳述式呢?

方法就是「確保 function 不是這一行程式碼的第一個字詞」,如此一來 Parser 就會因為第一個字不是 function 而判斷它不是函式陳述式。

最常見的作法就是把函式用一個括號包起來,因為 JavaScript 引擎會判斷括號裡的東西是一個表示式。

1(function (name) {
2  return 'Hello ' + name;
3});

現在我們就有一個函式,它只是單純放在那裡,沒有在運作哩。

常見的 IIFE 寫法

一個函式表達式被括號包住,所以 Parser 會認為這不是函式陳述式,而是一個立即執行的函式,因為最後的 () 執行了函式。

1var firstname = 'Damao';
2
3(function (name) {
4  console.log('Inside IIFE: Hello ' + name);
5})(firstname);
6
7// Inside IIFE: Hello Damao

對了,IIFE 常見的寫法有兩種,但這個不是很重要,因為功能都一樣,只是美感問題而已。

  • 在括號內:(function(){...}());
  • 在括號外:(function(){...})();

個人比較喜歡把調用的 () 放在裡面,因為我希望把所有東西包在小括號內,讓 Parser 知道最外面有個括號,不過 VSCode 的 Prettier 則是會自動把括號校正到外面。

安全程式碼

透過 IIFE 撰寫的安全程式碼

執行函式時,會創造函式自己的執行環境,裡面宣告的變數也都是在函式內被創造,不會接觸到全域環境。

安全程式碼

即使有兩個函式庫,只要有各自建立自己的執行環境,變數之間就不會覆蓋,因為這兩個 greeting 是在不同的執行環境中,所以它們的記憶體位置當然不同。

安全程式碼

如果去看一些資源庫的原始碼,可以看到開頭與最後結尾的地方都是小括號與函式,它們會把所有程式碼包在 IIFE 裡面,避免發生衝突。

故意影響全域物件

以下是一個標準的 IIFE,如果今天想要在使用 IIFE 時,在函式的執行環境下故意修改全域物件的話,我們該怎麼做呢?

1// IIFE
2(function (name) {
3  var greeting = 'Hello';
4  console.log(greeting + ' ' + name);
5})('Damao');

由於物件傳參考的特性,我們可以把全域物件 window 的參考傳給 IIFE,這樣就能影響到全域物件哩。

1(function (global, name) {
2  var greeting = 'Hello';
3  global.greeting = 'Hola'; // 故意影響全域物件
4  console.log(greeting + ' ' + name); // Hello Damao
5})(window, 'Damao');
6
7console.log(greeting); // Hola

回顧

看完這篇文章,我們到底有什麼收穫呢?藉由本文可以理解到…

  • 認識立即函式的觀念,包含演變過程、如何繞過語法解析器的判定,以及目前最常見的寫法
  • 如何透過 IIFE 撰寫的安全程式碼
  • 在函式的執行環境中故意影響全域物件的方法

References