iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 19
0
Modern Web

教練我想學 JavaScript 系列 第 19

Day 19 立即呼叫的函數表達式 IIFE

  • 分享至 

  • xImage
  •  

我們再來分別看一下函數陳述句與函數表達式,
函數陳述句如下:

// 函數陳述句
function greet(name) {
  console.log('Hello ' + name);
}

greet('Jimmy');



來看 Console 中的結果:

函數表達式如下:

// 函數表達式
var greet = function (name) {
  console.log('Hello ' + name);
}

greet('Jimmy');

來看 Console 中的結果:

目前這兩種執行函數的方式都是先新增函數後在不同時間呼叫函數,

可以在新增函數表達式時同時呼叫函數,
並在呼叫時帶入參數
程式碼如下:

// 立即呼叫函數表達式 (IIFE)
var greet = function (name) {
  console.log('Hello ' + name);
}();

來看 Console 中的結果:

我們在新增函數時同時呼叫函數,因為沒有傳入參數,所以 name 是 undefined,

如果我不用 console.log 來輸出結果改成用 return 將回傳值用等號(指派)運算子設值給變數 greet ,
在透過 console.log 來看變數greet 的值
程式碼如下:

var greet = function (name) {
  return 'Hello ' + name;
};

console.log(greet);

來看 Console 中的結果:

現在變數 greet 是一個函數,

我們呼叫變數 greet 可以得到正確的回傳值,
程式碼如下:

var greet = function (name) {
  return 'Hello ' + name;
};

console.log(greet());



來看 Console 中的結果:

現在正確顯示,但因為我們沒傳參數所以 name 會是 undefined

現在我們在函數表達式新增時同時呼叫函數,
程式碼如下:

var greet = function (name) {
  return 'Hello ' + name;
}('Jimmy');

console.log(greet);

來看 Console 中的結果:

我們只有 console.log 變數 greet 就會將函數被呼叫執行完的結果正確顯示出來,這次我在呼叫時有傳入參數,

如果我在 console.log 變數 greet 時呼叫它,你覺得結果是什麼?
程式碼如下:

var greet = function (name) {
  return 'Hello ' + name;
}('Jimmy');

console.log(greet());

來看 Console 中的結果:

會看到出現這個錯誤,因為變數 greet 現在在記憶體中被設值成字串,

我們來看一些有趣的例子:
程式碼如下:

3;

'Hello Jimmy';

{
  name: 'Jimmy'
};

來看 Console 中的結果:

會看到沒有任何錯誤,這些程式碼被正確執行,也會在記憶體空間位址裡面佔用空間,

但如果是匿名函數呢 也可以成功被執行嗎?

function(name) {
  return 'Hello ' + name;
}

來看 Console 中的結果:

會看到錯誤提示,是因為語法解析器預期每一行程式碼的開頭如果是 function 這個單字,它會預期這是函數陳述句,並且要你給 function 一個名稱,

要解決這個問題前我們先來看群組運算子,群組運算子由 () 組成,
常用在計算
程式碼如下:

console.log((4 + 3) * 5);

來看 Console 中的結果:

語法解析器在解析群組運算子內的程式碼時時會把 ( ) 內的程式碼當作表達式回傳一個值

因此我們可以將剛才的匿名函數透過這種方式,讓匿名函數可以正常被執行,這樣 function 這個單字就不是在每一行程式碼的最開頭,因為 function 在每一行程式碼最開頭會被語法解析器以為你要新增一個函數陳述句並且要求要有函數名稱,

實際來看用群組運算子 () 來把匿名函數包住,
程式碼如下:

(function(name) {
  return 'Hello ' + name;
});

來看 Console 中的結果:

現在匿名函數被正確執行,並且也存在記憶體中,

我們可以同時呼叫匿名函數,但要確定匿名函數被包在括號中,
程式碼如下:

var firstname = 'Jimmy';

(function(name) {
  var greeting = 'Inside IIFE: Hello ';
  console.log(greeting + name);
}(firstname));

來看 Console 中的結果:

我們在新增這個匿名函數時將它包在括號裡,新增函數的同時呼叫這個匿名函數,傳入變數 firstname 當作參數,
匿名函數被成功執行並輸出結果,

另外 呼叫函數的括號位置也可以在立即呼叫函數表達式外面,
程式碼如下:

var firstname = 'Jimmy';

(function(name) {
  var greeting = 'Inside IIFE: Hello ';
  console.log(greeting + name);
})(firstname);

這個的執行結果會跟剛才一樣,
來看 Console 中的結果:

這兩種寫法並沒有哪一種比較好的差別,你只要在寫程式碼時選一種方式來編寫程式碼就可以了,

這就是立即呼叫的函數表達式 IIFE 。

IIFE 與安全程式碼

有些時候我們可能會有相同名稱的變數,導致程式碼被修改,
但我們可以透過 IIFE 的特性來確保函數裡的程式碼不會被修改到,
程式碼如下:

var greeting = 'Hola';

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

console.log(greeting);

你覺得輸出的結果分別會是什麼?

來看 Console 中的結果:

會看到在 IIFE 裡面的變數 greeting 與 全域執行環境的變數 greeting 分別是不同的值,
因為當程式碼被語法解析器解析時全域執行環境被創造並被放進執行堆執行,全域執行環境有自己的變數環境,
在全域執行環境裡變數 greeting 指向的記憶體位址裡的值被設值成 'Hola',
接著 IIFE 執行時會創造自己的執行環境並被丟到執行堆最上方(正在執行的程式碼),
在 IIFE 的執行環境的裡有自己的變數環境,而變數 greeting 現在在記憶體位址裡的值被設值成 'Hello',
所以在 console.log 出來時會有各自的值,

也可以刻意的在 IIFE 中修改全域環境的變數,只需要將全域物件 Window 當作參數傳入即可,
記得物件都是 by reference 的指向同一個記憶體位址,
IIFE 的參數名稱取成 global 是因為在伺服器端的全域物件不是 Window 物件,
如果你在伺服器端呼叫時傳入的參數就會是 global , 因為伺服器端的全域物件是 Global 物件,

程式碼如下:

var greeting = "Hola ";

(function(global, name) {
  var greeting = "Hello ";

  global.greeting = "Hello ";

  console.log(greeting + name);
}(window, 'Jimmy'));

console.log(greeting);

來看 Console 中的結果:

瀏覽器的全域物件 Window 底下的變數 greeting 成功的被我們在 IIFE 中修改了,
需要有意識的知道這是故意修改的,因為我們故意要這樣做,

在許多框架或是函式庫中的原始碼都會使用 IIFE 來避免程式碼被修改到,
所以如果你也有這樣的需求時就可以考慮使用 IIFE 來保護你的變數不被修改。


上一篇
Day 18 arguments 與 spread 運算子
下一篇
Day 20 閉包
系列文
教練我想學 JavaScript 30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言