模塊
【示例】:
function CoolModule() {
var something = "cool",
another = [1, 2, 3];
function doSomething() {
console.log(something);
}
function doAnother() {
console.log(another.join(" ! "));
}
return {
doSomething: doSomething,
doAnother: doAnother
};
}
var foo = CoolModule();
foo.doSomething(); // cool
foo.doAnother(); // 1 ! 2 ! 3
【解釋】:
- 這個(gè)模式在 JavaScript 中被稱為模塊。最常見的實(shí)現(xiàn)模塊模式的方法通常被稱為模塊暴露,這里展示的是其變體。
- 首先,CoolModule() 只是一個(gè)函數(shù),必須要通過調(diào)用它來創(chuàng)建一個(gè)模塊實(shí)例。如果不執(zhí)行外部函數(shù),內(nèi)部作用域和閉包都無法被創(chuàng)建。
- 其次,CoolModule() 返回一個(gè)用對(duì)象字面量語法 { key: value, ... } 來表示的對(duì)象。這個(gè)返回的對(duì)象中含有對(duì)內(nèi)部函數(shù)而不是內(nèi)部數(shù)據(jù)變量的引用。我們保持內(nèi)部數(shù)據(jù)變量是隱藏且私有的狀態(tài)??梢詫⑦@個(gè)對(duì)象類型的返回值看作本質(zhì)上模塊的公共 API。
- 這個(gè)對(duì)象類型的返回值最終被賦值給外部的變量 foo,然后就可以通過它來訪問 API 中的屬性方法。
【注意】:從模塊中返回一個(gè)實(shí)際的對(duì)象并不是必須的,也可以直接返回一個(gè)內(nèi)部函數(shù),例如 jQuery(jQuery 和 $ 標(biāo)識(shí)符就是 jQuery 模塊的公共 API,但它們本身都是函數(shù))。
【模塊模式需要具備的兩個(gè)必要條件】:
- 必須有外部的封閉函數(shù),該函數(shù)必須至少被調(diào)用一次。
- 封閉函數(shù)必須返回至少一個(gè)內(nèi)部函數(shù),這樣內(nèi)部函數(shù)才能在私有作用域中形成閉包,并且可以訪問或者修改私有的狀態(tài)。
【注意】:一個(gè)具有函數(shù)屬性的對(duì)象本身并不是真正的模塊。換言之,一個(gè)從函數(shù)調(diào)用所返回的,只有數(shù)據(jù)屬性而沒有閉包函數(shù)的對(duì)象并不是真正的模塊。
【示例】:改進(jìn)-單例模式
var foo = (function CoolModule() {
var something = "cool",
another = [1, 2, 3];
function doSomething() {
console.log(something);
}
function doAnother() {
console.log(another.join(" ! "));
}
return {
doSomething: doSomething,
doAnother: doAnother
};
})();
foo.doSomething(); // cool
foo.doAnother(); // 1 ! 2 ! 3
【解釋】:將模塊函數(shù)轉(zhuǎn)換成了 IIFE,立即調(diào)用這個(gè)函數(shù)并將返回值直接賦值給單例的模塊實(shí)例標(biāo)識(shí)符 foo。
【提示】:模塊也是普通的函數(shù),因此也可以接受參數(shù)。
【用法】:命名將要作為公共 API 返回的對(duì)象。
var foo = (function CoolModule(id) {
function change() {
// 修改公共 API
publicAPI.identify = identify2;
}
function identify1() {
console.log(id);
}
function identify2() {
console.log(id.toUpperCase());
}
var publicAPI = {
change: change,
identify: identify1
};
return publicAPI;
})("foo module");
foo.identify(); // foo module
foo.change();
foo.identify(); //FOO MODULE
【解釋】:通過在模塊實(shí)例的內(nèi)部保留對(duì)公共 API 對(duì)象的內(nèi)部引用,可以從內(nèi)部對(duì)模塊實(shí)例進(jìn)行修改,包括添加或刪除方法和屬性,以及修改它們的值。
現(xiàn)代的模塊機(jī)制
大多數(shù)模塊依賴加載器/管理器本質(zhì)上都是將這種模塊定義封裝進(jìn)一個(gè)友好的 API。
【示例】:
var MyModules = (function Manager() {
var modules = {};
function define(name, deps, impl) {
for(var i = 0; i < deps.length; i++) {
deps[i] = modules[deps[i]];
}
modules[name] = impl.apply(impl, deps);
}
function get(name) {
return modules[name];
}
return {
define: define,
get: get
};
})();
MyModules.define("bar", [], function() {
function hello(who) {
return "Let me introduce: " + who;
}
return {
hello: hello
};
});
MyModules.defind("foo", ["bar"], function(bar) {
var hungry = "hippo";
function awesome() {
console.log(bar.hello(hungry).toUpperCase());
}
return {
awesome: awesome
};
});
var bar = MyModules.get("bar");
var foo = MyModules.get("foo");
console.log(bar.hello("hippo"));
foo.awesome();
未來的模塊機(jī)制
ES6 中為模塊增加了一級(jí)語法支持。在通過模塊系統(tǒng)進(jìn)行加載時(shí),ES6 會(huì)將文件當(dāng)作獨(dú)立的模塊來處理。每個(gè)模塊都可以導(dǎo)入其他模塊或特定的 API 成員,同樣也可以導(dǎo)出自己的 API 成員。
詳細(xì)內(nèi)容請(qǐng)參考 ES6 模塊機(jī)制。
小結(jié)
- 模塊有兩個(gè)主要特征:(1)為創(chuàng)建內(nèi)部作用域而調(diào)用了一個(gè)包裝函數(shù);(2)包裝函數(shù)的返回值必須至少包括一個(gè)對(duì)內(nèi)部函數(shù)的引用,這樣就會(huì)創(chuàng)建涵蓋整個(gè)包裝函數(shù)內(nèi)部作用域的閉包。