立即執行函數 Immediately-invoked function expression (IIFE)

 

想要了解IIFE?

首先要先知道什麼是function declaration (statement)?什麼是function expression

 

一、首先了解

這是一個function expression,對使用foo變數加上括號成foo()則該function可被呼叫。

var foo = function () { /* code */ };

 

foo()應會等於function () { /* code */ }()但是

function() { /* code */ } ();

會產生SyntaxError,這是為什麼?

因為parser一看到關鍵字「function」時,預設會將「function() { /* code */ }」視為function declaration,

但做為一個function declaration則必須要有個function name。

 

而我們替他加上name如下

function foo() { /* code */ } ();

其也會產生SyntaxError,這時請在括號裡 - (group operator) 放置expression,如下

function foo() { /* code */ } (1);

 

雖然不會產生Syntax Error了,但foo function也不會被呼叫、執行,

因為其作用相等於下面二行程式

function foo() { /* code */ }
(1);

 

二、IIFE - Immediately-invoked function expression

要讓foo function被呼叫請利用括號把「function foo() { /* code */ } ()」整個給括起來,

其行為就是在告訴parser把「function foo() { /* code */ } ()」這一段視為function expression,如下

(function () { /* code */ }());

其相等於

(function () { /* code */ })();

 

此時有指定function name或不指定function name都是可以的

(function foo() { /* code */ }());
(function foo() { /* code */ })();

 

額外列出下形式也不會產生syntax error,但其結果較無義意。

var i = function () { return 10; }();

true && function () { /* code */ }();

0, function () { /* code */ }();

!function () { /* code */ }();

~function () { /* code */ }();

-function () { /* code */ }();

+function () { /* code */ }();

new function () { /* code */ }

new function () { /* code */ }() // Only need parens if passing arguments

 

三、閉包(closures)

一個function裡面又包了一個內部函式,而內部函式又可存取外部變數,這種關係稱之為閉包。

利用閉包的特性,可以把變數封裝起來,不被外界任意存取,其功能就像是物件導向的「封裝」特性。

而我在網路上有看到段針對 closures 的解釋,我覺得不錯截錄如下

「When a function is created, it has access to a reference to all the variables declared around it, also known as it’s lexical environment. The combination of the function and its enviroment is called a closure.」

1
2
3
4
5
6
7
8
9
10
11
function createCounter() {
var count = 0;
return function() { //=> 直接 return 一個匿名函式
count++;
return count;
}
}

var counter = createCounter();
console.log(counter()); // => 1
console.log(counter()); // => 2
1
2
3
4
5
6
7
8
9
function createAdder(a) {
function f(b) {
const sum = a + b;
return sum;
}
return f;
}
const f = createAdder(3);
console.log(f(4)); // 7
1
2
3
4
5
6
7
8
9
10
11
12
13
var arr = [];
for (var i = 0; i < 5; i +=1) {
arr[i] = function() {
return log(i); // => return function log
}
}

function log(n) {
console.log(n);
}

arr[0](); // 0
arr[1](); // 1

 

四、全域變數污染(global scope pollution)

var elems = document.getElementsByTagName('a');
for (var i = 0; i < elems.length; i++) {
    elems[i].addEventListener('click', function (e) {
        e.preventDefault();
        alert('I am link #' + i);
    }, 'false');
}

注意,function內取用了外部變數「i」,每click一次function就會去取變數「i」現在的值為多少,

上面程式跑完時變數「i」將跑到最終值,也就是每click一次function就會去取已經跑到最終值的變數「i」了。

你會發現變數「i」已遭到全域變數污染(global scope pollution)了。

 

以IIFE的特性還可以將值給保留住一份,避免全域變數污染(global scope pollution),修改如下

var elems = document.getElementsByTagName('a');
for (var i = 0; i < elems.length; i++) {
    (function (lockedInIndex) {
        elems[i].addEventListener('click', function (e) {
            e.preventDefault();
            alert('I am link #' + lockedInIndex);
        }, 'false');
    })(i);
}

上面IIFE範例,實參(實際參數)為 i 變數,i 變數代入 lockedInIndex 變數,

lockedInIndex 變數等同於從 i 變數COPY一份相同值進來。

 

也可以改成如下,但比較不好理解而已

var elems = document.getElementsByTagName('a')
for (var i = 0; i < elems.length; i++) {
    elems[i].addEventListener('click', (function (lockedInIndex) {
        return function (e) {
            e.preventDefault();
            alert('I am link #' + lockedInIndex);
        };
    })(i), 'false');
}

 

五、jQuery Events-Document Loading

介紹一個跟IIFE不相關但容易與jQuery搞混的例子

$(document).ready(function(){});

其精簡寫法相等於

$(function(){});

相關請參考Events-Document LoadingCore

 

参考資料:

Immediately-Invoked Function Expression (IIFE)

IIFE

閉包

Closures explained with JavaScript

[译] JavaScript:立即执行函数表达式(IIFE)

JavaScript世界的一等公民 - 函数(一)