立即執行函數 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 | function createCounter() { |
1 | function createAdder(a) { |
1 | var arr = []; |
四、全域變數污染(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 Loading、Core
参考資料:
Immediately-Invoked Function Expression (IIFE)
Closures explained with JavaScript