本文最初發(fā)表在個人博客上。
Mastering the Module Pattern
翻譯文章Mastering the Module Pattern,Module Pattern是一種非常常見的JS代碼模式,尤其是各種類庫中被廣泛應用。
再次推薦原文地址:Mastering the Module Pattern
掌握模塊模式
我是JavaScript模塊模式(Module Pattern)的狂熱粉絲,我想與大家分享一些模塊模式的用例以及特點,并解釋它們的重要性。模板模式是一種“設計模式”,從各種方面看它都非常有用。模板模式(以及它的變種Revealing Module Pattern)最吸引我的一點是它可以輕而易舉的劃分程序的作用域并且不會使程序設計變得過于復雜。
它還能使程序非常簡單、易讀易用,并且非常優(yōu)雅的使用對象,不會使你的程序充滿了重復的this
和prototype
聲明。下面我將分享一些模塊模式的優(yōu)點、如何利用該模式,以及它的變種和特性。
創(chuàng)建模塊
為了理解模塊的概念,首先應該理解下面的function
代碼:
function () {
// code
})();
這段代碼聲明了一個函數,然后立即調用自身。這也被稱為Immediately-Invoked-Function-Expressions,在這里function
創(chuàng)建了一個新的作用域(scope
)以及私有空間(privacy
)。Javascript并沒有私有訪問權限,但當創(chuàng)建了一個包含函數邏輯的作用域時,我們模擬了私有作用域的概念。也就是只暴露我們需要的部分,而其他代碼對全局隱藏。
創(chuàng)建新的作用域后,我們需要給代碼賦予命名空間(namespace),以便后續(xù)可以訪問我們返回的方法。下面就為我們的匿名模塊增加命名空間:
var Module = (function () {
// code
})();
在全局聲明Module
變量,也就是說接下來可以在任何地方調用Module,甚至可以將Module傳遞給其他模塊。
私有方法
接下來你會看到很多Javascript的private
方法。嚴格說來Javascript并沒有private
方法,但我們可以創(chuàng)建等同的結果。
What are private methods you might be asking? Private methods are anything you don't want users/devs/hackers to be able to see/call outside the scope they're in. We might be making server calls and posting sensitive data, we don't want to expose those functions publicly, they could post anything back then and take advantage of our code.
所以我們可以創(chuàng)建閉包(Javascript中最好的方式)來更好的保護我們的代碼。做這些并不都是為了保護代碼,還有命名沖突問題。我敢打賭在你剛開始寫Jquery/Javascript時,你肯定在一個文件里扔進了大量的function, function, function
。然而很少有人意識到這些方法的作用域都是全局的,也許在將來某個點你會遭到影響。如果是這樣,你將會了解為什么,以及如何去改變它。
接下來我們使用新創(chuàng)建的Module
作用域來使我們的方法在作用域外不可訪問。對于模塊模式的初學者來說,這個例子將會幫助你了解如何來定義一個私有方法:
var Module = (function () {
var privateMethod = function () {
// do something
};
})();
上面的例子聲明了方法privateMethod
,它是在新的作用域內本地聲明的。如果試圖在模塊外部調用它將會拋出Error。我們并不想讓其他人調用這個方法,尤其是那些可以操作數據并且與服務器交互的地方。
理解"return"
通常典型的模塊都會使用return
并且返回一個Object
,通過這個模塊的命名空間來訪問的方法都綁定在這個Object上。
下面是一個帶有function
成員的返回Object
的例子:
var Module = (function () {
return {
publicMethod: function () {
// code
}
};
})();
我們可以像對待一般的Object一樣來調用Module:
Module.publicMethod();
相比較而言,一個標準的對象看起來像這樣:
var myObjLiteral = {
defaults: { name: 'Todd' },
someMethod: function () {
console.log(this.defaults);
}
};
// console.log: Object { name: 'Todd' }
myObjLiteral.someMethod();
但這樣的問題是這種模式容易被濫用。那些本該是私有的方法也會被調用者使用。這就是模塊(Module)出現的原因,模塊允許我們局部定義私有方法并且只返回"有用的部分"。
下面來看看對象字面量(Object Literal)語法,以及一個非常好的模板模式的例子,并且理解return
關鍵字的作用。通常一個模塊會返回一個對象,但是如何定義和構造這個對象完全取決于你。根據項目以及代碼的作用,以下幾種都可能被用到。
匿名對象字面量(Anonymous Object Literal)
就像上面的例子那樣,這是最簡單的一種方式,對象沒有本地命名,我們只是返回一個對象:
var Module = (function () {
var privateMethod = function () {};
return {
publicMethodOne: function () {
// I can call `privateMethod()` you know...
},
publicMethodTwo: function () {
},
publicMethodThree: function () {
}
};
})();
局部對象字面量(Locally scoped Object Literal)
局部作用域(Local scope)指的是在一個作用域內聲明的變量或者方法。在Conditionizr項目中,我們使用了一個超過100行的局部命名空間,這能很方便的看出public和private方法而不必檢查return
語句。在這種情況下,因為public方法都有本地變量定義,所以很容易辨別。
var Module = (function () {
// locally scoped Object
var myObject = {};
// declared with `var`, must be "private"
var privateMethod = function () {};
myObject.someMethod = function () {
// take it away Mr. Public Method
};
return myObject;
})();
模塊最后一行返回myObject
。我們的全局Module
并不關心本地的局部Object
是否有名字,我們只拿到返回的對象而不是命名。這使得代碼可以更好的管理。
Stacked locally scoped Object Literal
這與前面的例子非常相似,但這里使用了一個更“傳統(tǒng)”地對象字面量表達式:
var Module = (function () {
var privateMethod = function () {};
var myObject = {
someMethod: function () {
},
anotherMethod: function () {
}
};
return myObject;
})();
我個人更喜歡第二種方式。因為在這里我們必須在使用它們之前定義其他方法(你應該這么做,因為使用function myFunction () {}
這樣的語法可能會提升一個方法,如果使用不當可能會引入很多問題)。使用var myFunction = function () {};
這樣的語法可以不必擔心這種情況,因為我們在使用它們之前定義所有方法,這也讓調試更簡單,因為JavaScript解釋器可以按照聲明的順序加載代碼。這種方式也有不好的一面,因為"嵌套"方法使代碼看起來很冗余,同時也沒有顯而易見的本地變量Object namespace
來綁定公用方法。
Revealing Module Pattern
我們已經研究過模塊,它確實包含一個簡潔的的變量可以讓我們通過在模塊內向它綁定方法來公開這些方法。同樣的,我們可以用更好的代碼組織方式來清楚的看到模塊中的哪些方法是公共的:
var Module = (function () {
var privateMethod = function () {
// private
};
var someMethod = function () {
// public
};
var anotherMethod = function () {
// public
};
return {
someMethod: someMethod,
anotherMethod: anotherMethod
};
})();
我非常喜歡上面的語法,它非常的有說明性。對于一些較大的JavaScript模塊,這種方式非常有用,使用標準的模板模式可能因為語法及組織代碼的方式造成代碼失控。
訪問“私有”方法
你也許會想,“我該怎么訪問私有方法?”。這就是JavaScript變得更棒的地方,我們可以通過調用公用方法來實際調用私有方法。觀察如下代碼:
var Module = (function () {
var privateMethod = function (message) {
console.log(message);
};
var publicMethod = function (text) {
privateMethod(text);
};
return {
publicMethod: publicMethod
};
})();
// Example of passing data into a private method
// the private method will then `console.log()` 'Hello!'
Module.publicMethod('Hello!');
并不只局限于方法,還可以訪問對象、數組,任何東西:
var Module = (function () {
var privateArray = [];
var publicMethod = function (somethingOfInterest) {
privateArray.push(somethingOfInterest);
};
return {
publicMethod: publicMethod
};
})();
增強模塊(Augmenting Modules)
到目前為止我們已經創(chuàng)建了一個很好的模塊,并且返回一個對象。但是如果我們想擴展我們的模塊,并且引入另一個較小的模塊來擴展我們的原始模塊呢?
考慮下面代碼:
var Module = (function () {
var privateMethod = function () {
// private
};
var someMethod = function () {
// public
};
var anotherMethod = function () {
// public
};
return {
someMethod: someMethod,
anotherMethod: anotherMethod
};
})();
如果這是我們應用的一部分,但是設計時我們已經決定將某些部分排除在應用核心之外,所以我們可以把它作為獨立的模塊引入,實現擴展。
目前為止我們的Module
對象看起來是這樣:
Object {someMethod: function, anotherMethod: function}
但是如果我們想擴展我們的模塊,增加其他的公共方法,也許代碼編程這樣:
Object {someMethod: function, anotherMethod: function, extension: function}
我們如何管理它呢?我們給它個恰當的命名ModuleTwo
,并且傳入我們的Module
,它使我們能夠擴展Object:
var ModuleTwo = (function (Module) {
// access to `Module`
})(Module);
接下來我們可以在這個模塊里創(chuàng)建another
方法, 同時還能充分利用已有的私有、功能特性。我的偽代碼也許看起來是這樣:
var ModuleTwo = (function (Module) {
Module.extension = function () {
// another method!
};
return Module;
})(Module || {});
Module
被傳入ModuleTwo
,增加一個擴展方法并再次返回Module
。我們的對象就被跑出了,這正是JavaScript的靈活性:D。
現在可以通過Chome開發(fā)者工具看到最初的Module現在已經有了第三個屬性:
// Object {someMethod: function, anotherMethod: function, extension: function}
console.log(Module);
這里有條額外的提示,你也許注意到了我傳入Module || {}
到第二個ModuleTwo
,這是為了防止Module
是undefined
,現在就不會引起異常了。這就是初始化了一個新的對象,并且綁定我們的extension
方法,再return它。
私有命名習慣(Private Naming Conventions)
我個人喜歡Revealing Module Pattern,這就會在代碼里出現很多相同的聲明。那我如何區(qū)分私有的變量或方法呢?使用_符號!你也許在網上已經看到很多這樣的符號,現在你知道為什么我們這樣用了:
var Module = (function () {
var _privateMethod = function () {
// private stuff
};
var publicMethod = function () {
_privateMethod();
};
return {
publicMethod: publicMethod
};
})();