一開始看到 jQuery 的開頭時,覺得它不像一般函式那樣直接寫完名稱,而是先包在 () 裡面:
開頭的程式碼大概是這樣子的:
(function (global, factory) {
"use strict";
if (typeof module === "object" && typeof module.exports === "object") {
module.exports = factory(global, true);
} else {
factory(global);
}
})(typeof window !== "undefined" ? window : this, function (window, noGlobal) {
"use strict";
// jQuery 的核心程式碼
});
最前面的左括號 ( 把 function 包起來,讓它成為函式表達式。 讓這個 function 變成函式表達式。因為 function 後面沒有寫名稱,所以它也是匿名函式。函式後面的 () 則是用來呼叫它。
看到這裡可以先分清楚,這段程式碼其實放了兩個函式。第一個是外層函式:
function (global, factory) {
// 判斷環境,決定如何呼叫 factory
}
第二個則是被當成參數傳入的匿名函式,外層函式用 factory 這個參數接住它。
function (window, noGlobal) {
// jQuery 的核心程式碼
}
外層函式在檔案載入時立即執行;內層的 factory 則先被當成一個函式值傳進去,等外層函式判斷完環境後,才由外層呼叫它。這也是 jQuery 把外層控制流程和內層核心程式碼分開的方式。
剛剛的這種寫法稱為 IIFE (Immediately Invoked Function Expression,立即呼叫函式表達式)
先來看一般函式的寫法:
function greet(name) {
console.log("Hello " + name);
}
greet("jQuery"); // 必須另外呼叫函式才會執行
IIFE 的寫法則是:
(function (name) {
console.log("Hello " + name);
})("jQuery");
IIFE 就是把「建立函式」和「呼叫函式」寫在同一個表達式中。函式建立完成後會立即執行,不需要先替它取一個名稱,再從外部呼叫。
這裡的兩組括號也有不同的作用:
(function () {
console.log("執行了");
})();
最前面的左括號 ( 把函式包起來,讓它成為函式表達式;最後面的 () 才是真正呼叫這個函式。也就是說,IIFE 並不是因為有括號就會自動執行,而是因為函式表達式後面緊接著呼叫括號。
我整理出了兩個主要原因:
jquery-4.0.0.js 時,外層 IIFE 就會立即被呼叫,不需要使用者另外呼叫初始化函式。如果自己的 script.js 會使用 $ 或 jQuery,就必須先載入 jQuery,再載入自己的 JS:
<script src="../jquery-4.0.0.js"></script>
<script src="./script.js"></script>
如果不用 IIFE,也可以把初始化寫成普通函式:
function initJQuery() {
// 建立 jQuery
}
initJQuery();
但這樣會留下 initJQuery 這個函式名稱,而且外部還可以再次呼叫它。使用 IIFE 後,初始化函式沒有公開名稱,外部沒有這個函式的參考,也就不能直接再次呼叫同一個初始化入口。這很適合函式庫在載入時完成一次內部準備,只把真正要給使用者使用的 API 公開出去。
jQuery 函式和 $ 別名公開給外部使用,其他內部變數則留在函式作用域中。例如:
(function () {
const secret = "只有函式裡知道";
console.log(secret); // 函式裡可以使用
})();
console.log(secret); // 外部找不到這個變數 ReferenceError
secret 就像 jQuery 內部宣告的 arr、slice,只在函式裡使用,不會直接出現在全域作用域中。
回到 jQuery 的程式碼,外層 IIFE 執行時,會把第一個引數傳給 global,把第二個引數,也就是 factory 函式,傳給外層的 factory 參數。接著外層才會呼叫 factory(global) 或 factory(global, true)。
今天從 jQuery 檔案開頭的 () 開始,我認識了 IIFE,也理解了函式表達式、立即呼叫和函式作用域這幾個觀念。這些語法讓 jQuery 能在載入時執行初始化,同時把內部變數保護在函式裡。