該文章是直接翻譯國外一篇文章,關於JS的Module模式的深度解析(這也是JS設計模式中的一種模式)。
都是基於原文處理的,其餘的都是直接進行翻譯可能有些生硬,因此爲了行文方便,就作了一些簡單的本地化處理。
若是想直接根據原文學習,能夠忽略此文。javascript
同時該篇文章也算是,前端模塊化的番外篇。(這篇文章也在準備當中,敬請期待)
複製代碼
模塊模式是一種經常使用的代碼模式。它簡單實用,可是也有一些「優雅」的使用方式,沒有獲得開發者的重視。因此,這篇文章,帶你們來重溫一下基層的用法,而且介紹一些比較優雅的使用方式。html
譯者注:前端
模塊模式,其實就是JS實現模塊化的最基礎的地基。例如AMD,UMD,COMMONJS,還有ES6的module都是基於這個實現方式(構建一個IIFE,{獨立做用域})來實現模塊化編程
還有一點就是ES6中class
是ES5構造函數的語法糖。ES5在自定義一個類,須要構造函數+構造函數.prototype來實現,可是爲何ES6的class
卻能夠將prototype中的方法放在class
的代碼範圍中。(也就是說,class
一次性將構造函數和prototype都構建了。)若是想了解Class如何優雅的進行「糖化」java
咱們來簡單回顧一下什麼是module pattern
。若是你對基礎知識比較熟悉的話,能夠跳過這部分,直接翻閱"高級用法"。編程
匿名做用域是實現模塊化最基本的結構,也是在JS的語言範疇中,最好的實現方式。咱們簡單的構建了一個匿名函數,而且立馬執行該匿名函數。在該函數中的全部代碼都獨立的運行在指定做用域中。而且該做用域中定義的私有變量和狀態值貫穿項目的全部週期。設計模式
(function () {
//在該做用域中的全部變量和函數都掛載在了全局變量上(都是全局變量)
}());
複製代碼
Notice:安全
在匿名函數外包還有一個
()
。這是必需要的。
由於在JS中若是一個語句是以function
開頭,JS引擎會認爲這是一個函數聲明。而經過()
包裹以後,就變成了函數表達式。異步
JS語法中,存在一個頗有意思的特性:隱含的全局變量。ide
當訪問一個變量名,JS編譯器就會循着做用域鏈(
scope chain
)去查找是否在指定的結點中存在與之相同的變量名。若是在整條做用域鏈中都沒有發現該變量名,這個變量就會被自動賦給全局變量。
當編譯器對一個本來不存在的變量進行賦值,該變量也會自動掛載在全局變量。模塊化
針對隱含的全局變量這個特性,在一個匿名做用域中使用/建立一個變量是很是簡單的。而這偏偏讓代碼變的維護性降低。
幸運的是,匿名函數爲咱們提供了一種解決方案。經過將全局變量做爲參數傳入到匿名函數中,直接對傳入的全局變量賦值和查值。這樣就比隱含的全局變量經過做用域鏈查找和賦值變量的方式更快,更簡潔。
(function ($, YAHOO) {
//在該做用域中,就能訪問jQuery, YAHOO的實例了
}(jQuery, YAHOO));
複製代碼
有些應用場景中,不只僅是用到全局變量,並且還想聲明一個全局變量。咱們能夠經過在匿名函數中return
一個對象,來實現聲明全局變量。
var MODULE = (function () {
var my = {},
privateVariable = 1;
function privateMethod() {
// ...
}
my.moduleProperty = 1;
my.moduleMethod = function () {
// ...
};
return my;
}());
複製代碼
Notice:
咱們聲明瞭一個名爲
MODULE
的全局模塊,該模塊擁有兩個公共(public)屬性:
一個方法(MODULE.moduleMethod
)、一個變量(MODULE.moduleProperty
)
而且該模塊經過匿名函數實現了私有的(private)變量和方法。
儘管上面的簡單用法,能知足咱們90%的模塊需求,可是咱們能夠基於普通用法,來構建更加高級的用法
針對上述模塊實現而言,存在一個弊端/限制,就是一個文件定義整個模塊的實現。 針對大型項目而言,代碼的佈局的高內聚,低耦合很重要。因此,有些特定的實現是不須要都堆砌在一個文件中的。
而argment modules
這種代碼佈局方式就應運而生。
var MODULE = (function (my) {
//基於MODULE的基礎上,新增指定方法/屬性
my.anotherMethod = function () {
};
return my;
}(MODULE));
複製代碼
在該匿名函數被執行以後,原先的module
就會新增了一個新的公共方法(MODULE.anotherMethod)。該文件也能夠存在本身的私有方法等。
咱們上述的例子中,要求咱們先構建一個初始模塊,而後進行追加操做。其實這種處理方式不是必要的。由於,<script>
標籤能夠實現異步加載,這樣的話,就不存在模塊初始化的問題,可能追加的模塊先加載。這樣就不會存在初始模塊這個概念。
因此,咱們須要一種定義模塊的方式,而這種方式是不關心各個模塊的加載順序。
Talk is cheap ,show you the code:
var MODULE = (function (my) {
// 隨意新增屬性
return my;
}(MODULE || {}));
複製代碼
Notice:
1.在該中模式下,
var
的聲明是必要的。
2. 導入的模塊是不須要考慮先前是否存在。也就意味着,使用Loose Augmentation
構建的模塊,在調用的時候,能夠利用相似於LABjs
的工具庫,實現平行加載。
雖然利用loose augmentation
構建的模塊很好,可是也對模塊新增了一些約束。其中比較重要的就是,1.你沒法安全的對模塊中的屬性和方法進行重寫。2.在初始化的時候,是沒法使用在另一個文件中定義的模塊的屬性。
而Tight augmentation
隱藏了加載順序。可是容許進行方法和屬性的重載(override) 咱們將原先實現過的MODULE
做爲參數傳入到函數中
var MODULE = (function (my) {
var old_moduleMethod = my.moduleMethod;
//進行方法的從新,可是能夠經過old_moduleMethod訪問原來的方法
my.moduleMethod = function () {
// ...
};
return my;
}(MODULE));
複製代碼
上述代碼中,咱們即對MODULE.moduleMethod
進行重寫,同時保持了對原始方法的引用(若是有必要的話)。
var MODULE_TWO = (function (old) {
var my = {},
key;
for (key in old) {
if (old.hasOwnProperty(key)) {
my[key] = old[key];
}
}
var super_moduleMethod = old.moduleMethod;
my.moduleMethod = function () {
//從新複製以後的方法,經過super_moduleMethod來訪問原始方法
};
return my;
}(MODULE));
複製代碼
該實現方式,多是最靈活的選擇。
將一個模塊分紅不少文件組成最大的限制就是:每一個文件擁有本身的私有變量,同時這些私有變量沒法跨文件訪問。這樣就沒法進行單一模塊的拆分處理。
可是,利用loosely augmented module
能夠很好的解決這個問題:
var MODULE = (function (my) {
var _private = my._private = my._private || {},
_seal = my._seal = my._seal || function () {
delete my._private;
delete my._seal;
delete my._unseal;
},
_unseal = my._unseal = my._unseal || function () {
my._private = _private;
my._seal = _seal;
my._unseal = _unseal;
};
// permanent access to _private, _seal, and _unseal
return my;
}(MODULE || {}));
複製代碼
任何文件均可以在局部變量(_private
)上設置屬性,而且在其餘文件中能夠立馬範圍到。
一旦該模塊加載完成,程序調用MODULE._seal()
,用於阻止外部文件訪問該模塊的內部屬性(internal _private
)。
若是須要對該模塊進行擴展,則在應用程序的生命週期中任何文件下的內部方法中在新模塊加載以前調用_unseal()
。在擴展以後,繼續調用_seal()
用於私有屬性的加密處理。
咱們上述介紹的高級模塊都很簡單。同時也有不少構建一個子模塊的方式。
MODULE.sub = (function () {
var my = {};
// ...
return my;
}());
複製代碼
大多數的高級模式均可以互相組合用於構建一個更加方便的模式。若是想要構建一個比較複雜的程序。能夠嘗試loose augmentation、private state、 和 sub-modules的組合。