9種日常JavaScript編程中經(jīng)常使用的對象創(chuàng)建模式

作者 | 湯姆大叔
介紹
模式1:命名空間(namespace)
var app = app || {};app.moduleA = app.moduleA || {};app.moduleA.subModule = app.moduleA.subModule || {};app.moduleA.subModule.MethodA = function () {console.log("print A");};app.moduleA.subModule.MethodB = function () {console.log("print B");};
如果層級很多的話,那就要一直這樣繼續(xù)下去,很是混亂。namespace模式就是為了解決這個問題而存在的,我們看代碼:
// 不安全,可能會覆蓋已有的MYAPP對象var MYAPP = {};// 還好if (typeof MYAPP === "undefined") {var MYAPP = {};}// 更簡潔的方式var MYAPP = MYAPP || {};//定義通用方法MYAPP.namespace = function (ns_string) {var parts = ns_string.split('.'),parent = MYAPP,i;// 默認如果第一個節(jié)點是MYAPP的話,就忽略掉,比如MYAPP.ModuleAif (parts[0] === "MYAPP") {parts = parts.slice(1);}for (i = 0; i < parts.length; i += 1) {// 如果屬性不存在,就創(chuàng)建if (typeof parent[parts[i]] === "undefined") {parent[parts[i]] = {};}parent = parent[parts[i]];}return parent;};
調(diào)用代碼,非常簡單:
// 通過namespace以后,可以將返回值賦給一個局部變量var module2 = MYAPP.namespace('MYAPP.modules.module2');console.log(module2 === MYAPP.modules.module2); // true// 跳過MYAPPMYAPP.namespace('modules.module51');// 非常長的名字MYAPP.namespace('once.upon.a.time.there.was.this.long.nested.property');
模式2:定義依賴
有時候你的一個模塊或者函數(shù)可能要引用第三方的一些模塊或者工具,這時候最好將這些依賴模塊在剛開始的時候就定義好,以便以后可以很方便地替換掉。
var myFunction = function () {// 依賴模塊var event = YAHOO.util.Event,dom = YAHOO.util.dom;// 其它函數(shù)后面的代碼里使用局部變量event和dom};
模式3:私有屬性和私有方法
JavaScript本書不提供特定的語法來支持私有屬性和私有方法,但是我們可以通過閉包來實現(xiàn),代碼如下:
function Gadget() {// 私有對象var name = 'iPod';// 公有函數(shù)this.getName = function () {return name;};}var toy = new Gadget();// name未定義,是私有的console.log(toy.name); // undefined// 公有方法訪問nameconsole.log(toy.getName()); // "iPod"var myobj; // 通過自執(zhí)行函數(shù)給myobj賦值(function () {// 自由對象var name = "my, oh my";// 實現(xiàn)了公有部分,所以沒有varmyobj = {// 授權(quán)方法getName: function () {return name;}};} ());
模式4:Revelation模式
也是關(guān)于隱藏私有方法的模式,和《深入理解JavaScript之全面解析Module模式》里的Module模式有點類似,但是不是return的方式,而是在外部先聲明一個變量,然后在內(nèi)部給變量賦值公有方法。代碼如下:
var myarray;(function () {var astr = "[object Array]",toString = Object.prototype.toString;function isArray(a) {return toString.call(a) === astr;}function indexOf(haystack, needle) {var i = 0,max = haystack.length;for (; i < max; i += 1) {if (haystack[i] === needle) {return i;}}return -1;}//通過賦值的方式,將上面所有的細節(jié)都隱藏了myarray = {isArray: isArray,indexOf: indexOf,inArray: indexOf};} ());//測試代碼console.log(myarray.isArray([1, 2])); // trueconsole.log(myarray.isArray({ 0: 1 })); // falseconsole.log(myarray.indexOf(["a", "b", "z"], "z")); // 2console.log(myarray.inArray(["a", "b", "z"], "z")); // 2myarray.indexOf = null;console.log(myarray.inArray(["a", "b", "z"], "z")); // 2
模式5:鏈模式
鏈模式可以你連續(xù)可以調(diào)用一個對象的方法,比如obj.add(1).remove(2).delete(4).add(2)這樣的形式,其實現(xiàn)思路非常簡單,就是將this原樣返回。代碼如下:
var obj = {value: 1,increment: function () {this.value += 1;return this;},add: function (v) {this.value += v;return this;},shout: function () {console.log(this.value);}};// 鏈方法調(diào)用obj.increment().add(3).shout(); // 5// 也可以單獨一個一個調(diào)用obj.increment();obj.add(3);obj.shout();
模式6:函數(shù)語法糖
函數(shù)語法糖是為一個對象快速添加方法(函數(shù))的擴展,這個主要是利用prototype的特性,代碼比較簡單,我們先來看一下實現(xiàn)代碼:
if (typeof Function.prototype.method !== "function") {Function.prototype.method = function (name, implementation) {this.prototype[name] = implementation;return this;};}
擴展對象的時候,可以這么用:
var Person = function (name) {this.name = name;}.method('getName',function () {return this.name;}).method('setName', function (name) {this.name = name;return this;});
這樣就給Person函數(shù)添加了getName和setName這2個方法,接下來我們來驗證一下結(jié)果:
var a = new Person('Adam');console.log(a.getName()); // 'Adam'console.log(a.setName('Eve').getName()); // 'Eve'
模式7:對象常量
對象常量是在一個對象提供set,get,ifDefined各種方法的體現(xiàn),而且對于set的方法只會保留最先設(shè)置的對象,后期再設(shè)置都是無效的,已達到別人無法重載的目的。實現(xiàn)代碼如下:
var constant = (function () {var constants = {},ownProp = Object.prototype.hasOwnProperty,// 只允許設(shè)置這三種類型的值allowed = {string: 1,number: 1,boolean: 1},prefix = (Math.random() + "_").slice(2);return {// 設(shè)置名稱為name的屬性set: function (name, value) {if (this.isDefined(name)) {return false;}if (!ownProp.call(allowed, typeof value)) {return false;}constants[prefix + name] = value;return true;},// 判斷是否存在名稱為name的屬性isDefined: function (name) {return ownProp.call(constants, prefix + name);},// 獲取名稱為name的屬性get: function (name) {if (this.isDefined(name)) {return constants[prefix + name];}return null;}};} ());
驗證代碼如下:
// 檢查是否存在console.log(constant.isDefined("maxwidth")); // false// 定義console.log(constant.set("maxwidth", 480)); // true// 重新檢測console.log(constant.isDefined("maxwidth")); // true// 嘗試重新定義console.log(constant.set("maxwidth", 320)); // false// 判斷原先的定義是否還存在console.log(constant.get("maxwidth")); // 480
模式8:沙盒模式
沙盒(Sandbox)模式即時為一個或多個模塊提供單獨的上下文環(huán)境,而不會影響其他模塊的上下文環(huán)境,比如有個Sandbox里有3個方法event,dom,ajax,在調(diào)用其中2個組成一個環(huán)境的話,和調(diào)用三個組成的環(huán)境完全沒有干擾。Sandbox實現(xiàn)代碼如下:
function Sandbox() {// 將參數(shù)轉(zhuǎn)為數(shù)組var args = Array.prototype.slice.call(arguments),// 最后一個參數(shù)為callbackcallback = args.pop(),// 除最后一個參數(shù)外,其它均為要選擇的模塊modules = (args[0] && typeof args[0] === "string") ? args : args[0],i;// 強制使用new操作符if (!(this instanceof Sandbox)) {return new Sandbox(modules, callback);}// 添加屬性this.a = 1;this.b = 2;// 向this對象上需想添加模塊// 如果沒有模塊或傳入的參數(shù)為 "*" ,則以為著傳入所有模塊if (!modules || modules == '*') {modules = [];for (i in Sandbox.modules) {if (Sandbox.modules.hasOwnProperty(i)) {modules.push(i);}}}// 初始化需要的模塊for (i = 0; i < modules.length; i += 1) {Sandbox.modules[modules[i]](this);}// 調(diào)用 callbackcallback(this);}// 默認添加原型對象Sandbox.prototype = {name: "My Application",version: "1.0",getName: function () {return this.name;}};
然后我們再定義默認的初始模塊:
Sandbox.modules = {};Sandbox.modules.dom = function (box) {box.getElement = function () {};box.getStyle = function () {};box.foo = "bar";};Sandbox.modules.event = function (box) {// access to the Sandbox prototype if needed:// box.constructor.prototype.m = "mmm";box.attachEvent = function () {};box.detachEvent = function () {};};Sandbox.modules.ajax = function (box) {box.makeRequest = function () {};box.getResponse = function () {};};
調(diào)用方式如下:
// 調(diào)用方式Sandbox(['ajax', 'event'], function (box) {console.log(typeof (box.foo));// 沒有選擇dom,所以box.foo不存在});Sandbox('ajax', 'dom', function (box) {console.log(typeof (box.attachEvent));// 沒有選擇event,所以event里定義的attachEvent也不存在});Sandbox('*', function (box) {console.log(box); // 上面定義的所有方法都可訪問});
通過三個不同的調(diào)用方式,我們可以看到,三種方式的上下文環(huán)境都是不同的,第一種里沒有foo; 而第二種則沒有attachEvent,因為只加載了ajax和dom,而沒有加載event; 第三種則加載了全部。
模式9:靜態(tài)成員
靜態(tài)成員(Static Members)只是一個函數(shù)或?qū)ο筇峁┑撵o態(tài)屬性,可分為私有的和公有的,就像C#或Java里的public static和private static一樣。
我們先來看一下公有成員,公有成員非常簡單,我們平時聲明的方法,函數(shù)都是公有的,比如:
// 構(gòu)造函數(shù)var Gadget = function () {};// 公有靜態(tài)方法Gadget.isShiny = function () {return "you bet";};// 原型上添加的正常方法Gadget.prototype.setPrice = function (price) {this.price = price;};// 調(diào)用靜態(tài)方法console.log(Gadget.isShiny()); // "you bet"// 創(chuàng)建實例,然后調(diào)用方法var iphone = new Gadget();iphone.setPrice(500);console.log(typeof Gadget.setPrice); // "undefined"console.log(typeof iphone.isShiny); // "undefined"Gadget.prototype.isShiny = Gadget.isShiny;console.log(iphone.isShiny()); // "you bet"
而私有靜態(tài)成員,我們可以利用其閉包特性去實現(xiàn),以下是兩種實現(xiàn)方式。
第一種實現(xiàn)方式:
var Gadget = (function () {// 靜態(tài)變量/屬性var counter = 0;// 閉包返回構(gòu)造函數(shù)的新實現(xiàn)return function () {console.log(counter += 1);};} ()); // 立即執(zhí)行var g1 = new Gadget(); // logs 1var g2 = new Gadget(); // logs 2var g3 = new Gadget(); // logs 3
可以看出,雖然每次都是new的對象,但數(shù)字依然是遞增的,達到了靜態(tài)成員的目的。
第二種方式:
var Gadget = (function () {// 靜態(tài)變量/屬性var counter = 0,NewGadget;//新構(gòu)造函數(shù)實現(xiàn)NewGadget = function () {counter += 1;};// 授權(quán)可以訪問的方法NewGadget.prototype.getLastId = function () {return counter;};// 覆蓋構(gòu)造函數(shù)return NewGadget;} ()); // 立即執(zhí)行var iphone = new Gadget();iphone.getLastId(); // 1var ipod = new Gadget();ipod.getLastId(); // 2var ipad = new Gadget();ipad.getLastId(); // 3
數(shù)字也是遞增了,這是利用其內(nèi)部授權(quán)方法的閉包特性實現(xiàn)的。
總結(jié)
以上就是今天介紹的9種對象創(chuàng)建模式,是我們在日常JavaScript編程中經(jīng)常使用的對象創(chuàng)建模式,不同的場景起到了不同的作用,希望大家根據(jù)各自的需求選擇適用的模式。
參考:http://shichuan.github.com/javascript-patterns/#object-creation-patterns

