单例模式
在JavaScript中,单例(Singleton)模式是最基本又最有用的模式之一。这种模式提供了一种将代码组织为一个逻辑单元的手段,这个逻辑单元中的代码可以通过单一的变量进行访问。确保单例对象只有一份实例,你就可以确信自己的所有代码使用的都是同样的全局资源。
单例类在JavaScript中用途广泛:
(1)可以用来划分命名空间,以减少网页中全局变量的数量;
(2)可以在一种名为分支的技术中用来封装浏览器之间的差异;
(3)可以借助于单例模式,将代码组织得更为一致,从而使代码更容易阅读和维护。
单例的基本结构
最基本的单例实际上是一个对象字面值,它将一批有一定关联的方法和属性组织在一起。例如如下JavaScript代码:
var Singleton = { attribute1: true; attribute2: 10 method1: function() { }, method2: function() { } };
这些成员可以通过Singleton加圆点运算符来访问:
Singleton.attribute1 = false; var total = Singleton. attribute2 + 5; var result = Singleton.method1();
对象字面值只是用以创建单例的方法之一,后面介绍的那些方法所创建的单体看起来更像其他面向对象语言中的单例类。另外,并非所有对象字面值都是单体,如果它只是用来模仿关联数组或容纳数据的话,那显然不是单例。但如果它是用来组织一批相关方法和属性的话,那就可能是单例,其区别主要在于设计者的意图。
创建拥有私有成员的单例 使用下划线表示法 在单例对象内创建类的私有成员的最简单、最直截了当的方法是使用下划线表示法(在JavaScript业界,如果变量和方法是使用下划线,则表示该变量和方法是私有方法,只允许内部调用,第三方不应该去调用)。参考实例如下:
GiantCorp.DataParser = { // 私有方法 _stripWhitespace: function(str) { return str.replace(/\s+/, ''); }, _stringSplit: function(str, delimiter) { return str.split(delimiter); }, // 公用方法 stringToArray: function(str, delimiter, stripWS) { if (stripWS) { str = this._stripWhitespace(str); } var outputArray = this._stringSplit(str, delimiter); return outputArray; }};
在如上代码中,stringToArray方法中使用this访问单体中的其他方法,这是访问单体中其他成员或方法的最简便的方法。但这样做有一点风险,因为this并不一定指向GiantCorp.DataParser例如,如果把某个方法用作事件监听器,那么其中的this指向的是window对象,因此大多数JavaScript库都会为事件关联进行作用域校正,例如在这里使用GiantCorp.DataParser比使用this更为安全。
使用闭包 在单例对象中创建私有成员的第二种方法是借助闭包。因为单例只会被实例化一次,所以不必担心自己在构造函数中声明了多少成员。每个方法和属性都只会被创建一次,所以可以把它们声明在构造函数内部(因此也就位于同一个闭包中)。
使用闭包创建拥有私有成员的单例类的实例如下:
MyNamespace.Ssingleton = (function() { // 私有成员 var privateAttribute1 = false; var privateAttribute2 = [1, 2, 3]; function privateMethod1() { } function privateMethod2() { } return { // 公有成员 publicAttribute1: true; publicAttribute2: 10, publicMethod1: function() { }, publicMethod2: function() { } }; })();
这种单例模式又称为模块模式,指的是它可以把一批相关方法和属性组织为模块并起到划分命名空间的作用。 使用该种方式改造上面的实例,参考代码如下:
GiantCorp.DataParser = (function() { var whiteSpaceRegex = /\s+/; // 私有方法 function stripWhitespace(str) { return str.replace(whiteSpaceRegex, ''); } function stringSplit(str, delimiter) { return str.split(delimiter); }, return { // 公用方法 stringToArray: function(str, delimiter, stripWS) { if (stripWS) { str = stripWhitespace(str); } var outputArray = stringSplit(str, delimiter); return outputArray; } }; })();
将私有成员放在闭包中可以确保其不会在单例对象之外被使用,因此开发人员可以自由的改变对象的实现细节,而不会殃及别人的代码。还可以使用这种办法对数据进行保护和封装。
在如上的代码中,单例对象都是在脚本加载时被创建出来。对于资源密集型或配置开销甚大的单例,更合理的是使用“懒汉式”单例实现。这种实现方式的特别之处在于,对它们的访问必须借助于一个静态方法,例如调用单例类的getInstance()方法获得对象实例。参考实现代码如下:
MyNamespace.Singleton = (function() { // 定义一个私有的属性,该属性用于储存单例对象 var uniqueInstance; function constructor() { // 将单态操作放在这里 } return { getInstance: function() { if (!uniqueInstance) { uniqueInstance = constructor(); } return uniqueInstance; } } })();
将一个单例转换为懒汉式加载方式后,必须对调用它的代码进行修改,例如之前调用:
MyNamespace.Singleton.publicMethod1();
应该改成如下代码:
MyNamespace.Singleton.getInstance().publicMethod1();
如果觉得命名空间名称太长,可以创建一个别名来简化它。
使用单例模式实现分支
分支是一种用来把浏览器之间的差异封装到运行期间进行设置的动态方法中的技术。例如,假设我们需要一个创建XHR对象的方法,这种XHR对象在大多数浏览器中是XMLHttpRequest对象的实例,但在IE早期版本中是某种ActiveX类的实例,这样一种方法通常会进行某种浏览器嗅探或对象探测。如果不用分支技术,那么每次调用这个方法时,所有这些浏览器嗅探代码都要再次运行。如果该方法调用频繁,将会严重影响效率。
要实现获取不同浏览器的XHR对象的功能,参考实现代码的实现步骤如下:
(1)判断有多少个分支(有3个),这些分支按其返回的XHR对象类型命名,这三个分支都有一个名为createXhrObject()的方法,该方法返回一个可以执行异步请求的新对象;
(2)根据条件将3个分支中某一分支的对象赋给那个变量,具体做法是逐一尝试XHR对象,直到遇到一个当前JavaScript环境所支持的对象为止。
参考代码如下所示:
使用单例模式实现JavaScript中的分支
用了分支技术后,所有的那些特性嗅探代码都只会执行一次,而不是没生成一个对象执行一次。这种技术适用于任何只有在运行时才能确定具体实现的情况。
在JavaScript中使用单例模式的主要优点如下:(1)对代码的组织作用:它将相关方法和属性组织在一个不会被多次实例话的单例中,可以使代码的调试和维护变得更轻松。描述性的命名空间还可以增强代码的自我说明性。将方法包裹在单例中,可以防止它们被其它程序员误改。
(2)单例模式的一些高级变体可以在开发周期的后期用于对脚本进行优化。
主要缺点如下:
(1)因为提供的是一种单点访问,所以它有可能导致模块间的强耦合。单体最好是留给定义命名空间和实现分支型方法这些用途,这些情况下,耦合不是什么问题;
(2)有时某些更高级的模式会更符合任务的需要。与“懒汉式”加载单例相比,虚拟代理能给予你对实例化方式更多的控制权。也可以用一个真正的对象工厂来取代分支型单例。
本文学习地址:http://www.108js.com/article/article5/50021.html?id=333