在javascript中,咱們知道可使用對象字面量或者構造函數建立對象,可是如何優雅地建立一個對象你卻不必定了解。javascript
前人在踩過無數坑又填過無數坑以後,給咱們總結了不一樣場景下的幾種對象建立模式:java
有這樣一種場景,假如你正在寫一個插件,這個插件內部會用到不少的全局變量,這時候你要怎麼保證你的變量不會與其餘插件的變量產生命名衝突呢?咱們知道,在javascript中並無內置命名空間,後面的變量會覆蓋掉前面的變量,你要如何避免這種問題的發生?ajax
可能你已經想到,只要足夠獨特的命名就能夠了吧,好比lynneShowVar,這樣確實是能夠的,可是若是變量不少,難道要爲每一個都起一個很獨特的命名?是否是很累?並且維護也是很麻煩的。設計模式
命名模式提出,爲應用程序建立一個全局對象,好比MYAPP,而後將全部的變量和函數掛到這個全局對象(MYAPP)的屬性上。數組
//建立全局對象 var MYAPP = {}; //構造函數 MYAPP.parent = function () {}; MYAPP.child = function () {}; //一個變量 MYAPP.some_var = 1; //一個對象容器 MYAPP.modules = {} //嵌套對象 MYAPP.modules.module1 = {}; MYAPP.modules.module2 = {};
命名約定:一般以所有大寫的方式來命名這個全局對象。閉包
優勢:app
缺點dom
那麼,有什麼辦法能夠避免這些缺陷麼?聲明代碼依賴關係應該是一個不錯的主意了。模塊化
聲明依賴關係函數
var myFunction = function () { //依賴 var event = MYAPP.util.event, dom = MYAPP.util.dom; //... }
優勢:
通用命名空間
隨着程序的複雜度的增長,如何保證全局對象上新添屬性不會覆蓋原有的屬性呢。這就須要每次在添加新屬性以前先檢查它是否已經存在了。
var MYAPP = MYAPP || {}; MYAPP.name = function (ns_string) { var parts = ns_string.split('.'), parent = MYAPP, i; //剝離最前面的冗餘全局變量 if (parts[0] === 'MYAPP') { parts = parts.slice(1); } for(i = 0; i < parts.length; i++) { //若是不存在就建立一個屬性 if (typeof parent[parts[i]] === 'undefined') { parent[parts[i]] = {}; } parent = parent[parts[i]]; } return parent; }
命名模式雖然好用,但仍是有個問題,那就是任何部分的代碼均可以修改全局實例,以及裏面的屬性,怎麼避免?咱們知道es5並無類的概念,若是要實現私有成員,可使用閉包來 模擬這種特性。
其實模塊模式是多種模式的組合
MYAPP.ntilities.array = (function () { //依賴 var uobj = MYAPP.ntilities.object, ulang = MYAPP.ntilities.lang; //私有屬性 var array_string = '[object array]', ops = Object.prototype.toString; //私有方法 //... //其它 //共有API return { inArray: function (needle, haystack) { ... }, isArray: function (str) { ... } //更多... } })()
將全局變量導入到模塊中
能夠將參數傳遞到包裝了模塊的即時函數中,有助於加速即時函數中全局符號的解析。
MYAPP.ntilities.array = (function (app, global) { }(MYAPP, this);
假設須要在同一個頁面運行同一個庫的兩個版本,很遺憾,前面兩種建立模式都是不支持的。這時候就須要模塊化。
在命名模式中,有一個全局對象 ,在沙箱模式中,有一個全局構造函數,咱們這裏命名爲Sandbox()。這個構造函數能夠接收一個或多個參數,這些參數指定對象實例須要的模塊名,以及一個回調函數。如:
//模塊名可使用數組的形式傳參 Sandbox(['ajax', 'dom'], function () { //這裏是回調函數 }); //模塊名也可使用單個參數的形式傳參,參數之間使用,號隔開 Sandbox('ajax', 'dom', function () { //這裏是回調函數 }); //不指定模塊名稱或指定'*',表示須要依賴全部模塊 Sandbox(function () { //這裏是回調函數 }); Sandbox('*', function () { //這裏是回調函數 });
在實現實際的構造函數以前,先爲Sandbox添加一個名爲modules的靜態屬性,這須要理解的是Sandbox同時也是一個對象。
Sandbox.modules = {}
把須要的模塊添加到Sandbox.modules對象中。假設咱們一共須要dom、ajax、event模塊
Sandbox.modules.dom = function(box) { box.getElement = function () {}; }; Sandbox.modules.ajax = function(box) { box.getResponse = function () {}; }; Sandbox.modules.event = function(box) { box.attachEvent = function () {}; };
接下來實現構造函數
function Sandbox = function () { //將參數轉換成一個數組 var args = Array.prototype.slice.call(arguments), //最後一個參數是回調函數 callback = args.pop(), //args[0]若是是String類型,說明模塊做爲單獨參數傳遞,不然模塊做爲數組形式傳遞 modules = (args[0] && typeof args[0] === 'String') ? args : arg[0], i; //確保該函數做爲構造函數調用 if(!(this instanceof Sandbox)) { return new Sandbox (modules, callback); } //須要向this添加的屬性,也可能沒有,這裏看實際項目需求 this.a = 1; this.b = 2; //如今向該核心'this'對象添加模塊 //不指定模塊名稱或指定'*',都表示制定全部模塊 if (!modules || modules === '*') { modules = []; for (i in Sandbox.modules) { modules.push[i]; } } //初始化所需的模塊 for(i = 0; i < modules.lenght; i++) { Sandbox.modules[modules[i]](this) } //依賴模塊已所有初始化,能夠執行回調函數 callback(); } //獲取你還須要添加一些原型屬性,看需求 Sanndbox.prototype = { name: 'MY Application', version: '1.0', getName: function () { return this.name; } };
優勢
鏈式模式可使您可以一個接一個地調用對象的方法,而無需將前一個操做返回的值付給變量,且無需分割成多行。如:
myobj.method1('hello').method2().method3('str');
這個很好理解,咱們直接來看一看代碼
var obj = { al: 1, add: function (v) { this.val += v; return this; }, set: function (v) { this.val = v; return this; }, shout: function (v) { console.log(this.val); } }; //鏈方法調用 obj.add(1).set(2).shout();
能夠看到,obj的每一個方法都返回this對象,這就是實現鏈式的原理。
優勢
缺點
每種設計模式都各有優缺點,具體使用哪一種模式還得看項目需求,你們一塊兒學習吧。