《javascript设计模式》笔记之第五章:单体模式
一:单体的基本结构:
最简单的单体,实际就是一个对象字面量:
var Singleton = { attribute1: true, attribute2: 10, method1: function() { }, method2: function(arg) { } };
二:划分命名空间:
单体一个很重要的功能就是划分命名空间,这节其实没什么好说的。。
然后,最好的做法是将命名空间再进一步统一,使自己的所有代码都通过一个全局对象来访问,例子:
var GiantCorp = {}; GiantCorp.Common = { // A singleton with common methods used by all objects and modules. }; GiantCorp.ErrorCodes = { // An object literal used to store data. }; GiantCorp.PageHandler = { // A singleton with page specific methods and attributes. };
然后这样的话,与外部的代码发生冲突的可能性就超级少了
三:用作特定网页专用代码的包装器的单体:
单体的另一个重要的功能就是包装网页专用代码:
Namespace.PageName = { // Page constants. CONSTANT_1: true, CONSTANT_2: 10, // Page methods. method1: function() { }, method2: function() { }, // Initialization method. init: function() { } }
然后就调用这些代码
addLoadEvent(Namespace.PageName.init);
接下来举一个实际使用的例子:我们现在写网页基本上必写的就是用ajax提交表单,那么我们怎样用单体来包装这些代码呢?下面就是一个演示:
先来反例(我自己之前的做法~~~):
var formID = document.getElementById("form"); formID.addEventListener('submit',formSubmit,false); function formSubmit(e) { //阻止表单默认动作,然后用ajax发送数据 //接受数据之后调用callBack函数 } function callBack(data) { //用ajax提交表单之后的回调函数
这样的话污染了超级多的全局变量~~~
然后学习这章之后的做法~~~
var GiantCorp = window.GiantCorp || {}; GiantCorp.RegPage = { formID: 'form', callBack: function(data) { //用ajax提交表单之后的回调函数 }, formSubmit: function(e) { //阻止表单默认动作,然后用ajax发送数据 //接受数据之后调用callBack函数 }, init: function(e) { GiantCorp.RegPage.formEl = document.getElementById(GiantCorp.RegPage.formID); GiantCorp.RegPage.formEl.addEventListener('submit',GiantCorp.RegPage.formSubmit,false); } } GiantCorp.RegPage.init();
注意第一行代码,意思就是如果之前定义过GiantCorp的话就不管,不然就赋予一个空对象给GiantCorp,这样的代码在很多地方都会看到。
四:拥有私用成员的单体:
拥有私用成员的最简单方法就是在属性前加下划线_来区分:
GiantCorp.DataParser = { // Private methods. _stripWhitespace: function(str) { return str.replace(/\s+/, ''); }, _stringSplit: function(str, delimiter) { return str.split(delimiter); }, // Public method. stringToArray: function(str, delimiter, stripWS) { if(stripWS) { str = this._stripWhitespace(str); } var outputArray = this._stringSplit(str, delimiter); return outputArray; } };
上面的代码不用去理解它,看到下划线_就把它当成是私有属性就可以了
此外stringToArray的函数里面用了this来直接调用单体里面的其他方法,不过使用起来注意它有时候指向的对象的问题!
接下来是使用闭包来实现真正拥有私用成员的单体:
MyNamespace.Singleton = (function() { // Private members. var privateAttribute1 = false; var privateAttribute2 = [1, 2, 3]; function privateMethod1() { ... } function privateMethod2(args) { ... } return { // Public members. publicAttribute1: true, publicAttribute2: 10, publicMethod1: function() { ... }, publicMethod2: function(args) { ... } }; })();
注意是公共成员都是放在return里面的,其他都是私有的。
这种模式是JavaScript中最流行,应用最广泛的模式之一
五:惰性实例化:
又来一个比较高级的做法,那就是惰性实例化:当我们需要用到这段代码的时候才创建它。换种方法说就是这样调用其方法:Singleton.getInstance().methodName(),而不是这样调用:Singleton.methodName()。其中getInstance方法会检查该单体是否已经被实例化。如果还没有,那么它将创建并返回其实例,如果单体已经实例过,那么它将返回现有实例。
实现步骤一(单体的所有代码移动一个名为constructor的方法中):
MyNamespace.Singleton = (function() { function constructor() { // All of the normal singleton code goes here. // Private members. var privateAttribute1 = false; var privateAttribute2 = [1, 2, 3]; function privateMethod1() { ... } function privateMethod2(args) { ... } return { // Public members. publicAttribute1: true, publicAttribute2: 10, publicMethod1: function() { ... }, publicMethod2: function(args) { ... } } } })();
上面注意constructor是包含单体里面所有代码的。
实现步骤二(创建一个单体控制的函数,并返回到单体):
MyNamespace.Singleton = (function() { function constructor() { // All of the normal singleton code goes here. ... } return { getInstance: function() { // Control code goes here. } } })();
实现步骤三(创建个私有变量用于判断是否已经实例化,并且在getInstance实现判断逻辑):
MyNamespace.Singleton = (function() { var uniqueInstance; // Private attribute that holds the single instance. function constructor() { // All of the normal singleton code goes here. ... } return { getInstance: function() { if(!uniqueInstance) { // Instantiate only if the instance doesn't exist. uniqueInstance = constructor(); } return uniqueInstance; } } })();
此外,可以用 var MNS = MyNamespace.Singleton的方法简化命名空间,但是要注意this指向问题!
六:分支:
利用分支可以实现判断浏览器支持哪一种代码,然后对该浏览器使用专用的代码:
MyNamespace.Singleton = (function() { var objectA = { method1: function() { ... }, method2: function() { ... } }; var objectB = { method1: function() { ... }, method2: function() { ... } }; return (someCondition) ? objectA : objectB; })();
用上面这样的分支我们就可以用来实现如果是IE8就返回第一种单体,如果不是就返回第二种单体了。
七:示例:用分支技术创建XHR对象
如果要兼容老旧的浏览器使用ajax,那就可以用这种技术了
例子:
/* SimpleXhrFactory singleton, step 1. */ var SimpleXhrFactory = (function() { // The three branches. var standard = { createXhrObject: function() { return new XMLHttpRequest(); } }; var activeXNew = { createXhrObject: function() { return new ActiveXObject('Msxml2.XMLHTTP'); } }; var activeXOld = { createXhrObject: function() { return new ActiveXObject('Microsoft.XMLHTTP'); } }; })(); /* SimpleXhrFactory singleton, step 2. */ var SimpleXhrFactory = (function() { // The three branches. var standard = { createXhrObject: function() { return new XMLHttpRequest(); } }; var activeXNew = { createXhrObject: function() { return new ActiveXObject('Msxml2.XMLHTTP'); } }; var activeXOld = { createXhrObject: function() { return new ActiveXObject('Microsoft.XMLHTTP'); } }; // To assign the branch, try each method; return whatever doesn't fail. var testObject; try { testObject = standard.createXhrObject(); return standard; // Return this if no error was thrown. } catch(e) { try { testObject = activeXNew.createXhrObject(); return activeXNew; // Return this if no error was thrown. } catch(e) { try { testObject = activeXOld.createXhrObject(); return activeXOld; // Return this if no error was thrown. } catch(e) { throw new Error('No XHR object found in this environment.'); } } } })();