描述:
单例模式是在avascript中使用的最基本最常用的模式之一。
这种模式提供了一种将代码组织为一个逻辑单元的手段,这个逻辑单元中的代码可以通过单一的变量进行访问。
通过确保单例对象中只存在一份实例,你就可以确信自己的所有代码使用的都是同样的全局资源。
定义:
按照传统的意义,单例是一个只能被实例化一次并且可以通过一个众所周知的访问点访问的类。
按照这个定义,第一个例子并不算是个单例,因为它不是一个可以实例化的类。
如果把单例定义的更广义一些的话:单例是一个用来 划分命名空间 并将一批相关 方法和 属性 组织在一起的对象,如果它可以被实例化,那么它只能被实例化一次
作用:
1. 划分命名空间,代码组织更为一致,便于阅读和维护,减少网页中全局变量的数量。
2. 在复杂项目中可以使用惰性加载的单例模式,以提高性能。
示例与分析:
1. 基本结构:
1: /** 2: * 1. 基本结构: 3: * 下面这种对象字面量的方式只是创建单例的方法之一。但如果只是使用字面量语法来模仿关联数组或容纳数据的话,那显然不是单例。 4: * 但如果它是用来阻止一批相关 方法 和 属性 的话,那就可能是单例,区别主要体现在设计者的意图上。 5: * 6: * 7: * 8: * 好处: 直接量语法,简洁,便于阅读,不需要实例化。 9: * 缺点: 可以被随意扩展和修改。 10: */ 11:
12: var SingletonBasic = { 13: attr1 : 10,
14: attr2 : false, 15:
16: method1 : function() { 17:
18: },
19: method2 : function() { 20:
21: }
22: };
|
2. 拥有私有属性、方法的单例模式:
1: /** 2: * 2. 拥有私有属性、方法的单例模式: 3: * 由于真正的私有方法的一个缺点在于她们比较耗费内存,因为每个实例都具有方法的一个新副本。 4: * 但单例对象只会被初始化一次,所以不必顾虑内存方面的问题。 5: * 6: * 1. 使用下划线做区分(伪私有:人为约定) 7: * 描述: 加入下划线以示约定。 8: * 9: * 好处: 这样做有一个好处就是当不再使用_renderUI的时候,可以直接删除而可以不必管有没有人公开调用它。 10: * 缺点: 伪私有有可能导致私有方法被意外调用;在对象内服访问私有成员时,必须通过this调用;不能通过构造函数初始化; 11: */ 12:
13: var SingletonPrivate = { 14: _privateAttr1 : 10,
15: attr2 : false, 16:
17: _privateMethod1 : function() { 18:
19: },
20: sum : function() { 21: return this._privateAttr1 + 20; 22: }
23: };
|
3. 使用闭包:
1: /** 2: * 3. 使用闭包 3: * 描述: 下面这个单例模式通过返回一个对象直接量的方式实现单例,相对于前面一种方式,下面这种方式提供了真正的私有成员。 4: * 场景: yui/history/history.js line: 31 5: * 好处: 创建真正的私有成员;共有成员(暴露的接口)模块化(将一批属性和方法组织为模块并划分命名空间)。 6: * 不再需要使用下划线来区分私有成员。 7: * 缺点: 多次执行或new后的对象实际上不是同一个实例。 8: */ 9:
10: var SingletonPrivate = (function() { 11: var privateVariable = 'something private'; 12: var showPrivate = function() { 13: return privateVariable; 14: }
15: return {//public members. 16: //为什么这里不直接写show:showPrivate 原因是这样做可以在showPrivate前后做更多的事情,类似装饰模式 17: showPrivate : function() { 18: console.log( 'showPrivate:' + showPrivate() ); 19: },
20: publicVariable : 'something public' 21: }
22: })();
23:
24: SingletonPrivate.showPrivate();
25: console.log( SingletonPrivate.publicVariable );
26:
27: //或 下面这种方式仅能返回某个类的实例。 28:
29: var SingletonPrivate2 = (function() { 30: var privateVariable = 'something private'; 31: var showPrivate = function() { 32: return privateVariable; 33: };
34: return new showPrivate(); 35: })();
36:
37: SingletonPrivate2.showPrivate();
38: console.log( SingletonPrivate2.publicVariable );
|
4. 惰性加载的单例模式:
1: /** 2: * 4. 惰性加载的单例模式 3: * 描述: 单例对象都是在脚本加载时被创建出来,对于资源密集型或配置开销甚大的单体,亦或者需要单体在DOMReady或指定的某个时机执行, 4: * 也许更合理的做法是将其实例化推迟到需要使用它的时候。这种方式叫惰性加载。 5: * 6: * 7: * 8: * 特点: 惰性加载单例的特点在于,对她们的访问必须借助一个静态方法,例如:Singleton.getInstance().showPrivate()。 9: * 而不是这样调用Singleton.showPrivate()。getInstance方法会检查单例是否已经实例化,如果没有,就创建单例实例并返回, 10: * 如果已经实例化,则返回现有实例。 11: * 12: * 缺点:比较复杂,代码不直观。 13: * 14: */ 15:
16: /** 17: * 单例类返回的内容为一个直接量对象,可随意修改这个直接量对象。 18: */ 19:
20: var SLazyLiteral = (function() { 21: var instantiated; 22: //将原单例函数的代码放在init函数中,以便控制其调用时机,公用方法getInstance就是用来实现这种控制的。 23: function init() { 24: var privateVariable = 'something private'; 25: var showPrivate = function() { 26: console.log( privateVariable );
27: };
28: return { 29: showPrivate : function() { 30: init();
31: },
32: publicVariable : 'something public' 33: };
34: }
35:
36: return { 37: //共有方法必须知道该类是否已经实例化过,如果实例化过,那么必须能够返回实例化过的实例对象。 38: getInstance : function() { 39: if( !instantiated ) { 40: instantiated = init();
41: }
42: return instantiated; 43: }
44: };
45: })();
46: SLazyLiteral().getInstance().showPrivate();
47:
48: //或 单例类没有返回内容,通过new的方式创建对象,可以保持原型链(不过单例貌似没有实现继承的必要,而且在这个例子中并不能直接访问单例类的原型对象)。 49:
50: var SLazyClass = (function() { 51: function SingleTon( args ) { 52: var args = args || {}; 53:
54: this.pointX = args.pointX || 0; 55: this.pointY = args.pointY || 0; 56:
57: this.showPoint = function() { 58: console.log( 'this.pointX:' + this.pointX ); 59: };
60: }
61:
62: var instantiated; 63:
64: var _static = { 65: getInstance : function( args ) { 66: if( !instantiated ) { 67: instantiated = new SingleTon( args ); 68: }
69: return instantiated; 70: },
71: publicMsg : 'msg public' 72: };
73:
74: return _static; 75: })();
76:
77: var singleInstance = SLazyClass.getInstance({ 78: pointX : 5
79: }).showPoint();
|
5. 单例的其他几种实现技巧:
1: /** 2: * 单例的其他几种实现技巧: 3: * 4: * a. 特点:通过对this闭包式的引用,实现对new的控制,而后在构造函数中判断对this的闭包引用是否存在, 5: * 从而使该单例类无论多少次new结果都会是同一个单例对象。 6: */ 7:
8: function singleton_a( args ) { 9: if( singleton_a.instance && typeof singleton_a.instance === 'object' ) { 10: return singleton_a.instance; 11: }
12:
13: var args = args || {}; 14: this.name = 'singleton_a.instance'; 15:
16: singleton_a.instance = this; 17: }
18:
19: var sta_1 = new singleton_a(); 20: var sta_2 = new singleton_a(); 21: sta_1 === sta_2; //=> true 22:
23: singleton_a.prototype.protoAttr = 'proto attribute'; 24:
25: sta_2.protoAttr //=> proto attribute 26:
27:
28: /** 29: * b. 特点:通过对this闭包式的引用,实现对new的控制,而后重写了构造函数,使该单例类之后每次都将只返回instance。 30: * 省了a中对this闭包引用的判断;可在实例化后添加原型属性。 31: * 32: * 随之而来的麻烦:假如出现这样的代码 33: * var s_b = new singleton_b(); 34: * singleton_b.instance = null; 35: * 此时,由于构造函数已被重写,就再也无法产出单例对象了,所以存在不安全因素。而在例子a中,就算instance属性被置空,依然可以再次实例化出一个单例对象。 36: */ 37:
38: function singleton_b(args) { 39: var args = args || {}, 40: instance = this; 41:
42: this.name = args.name || 'singleton5'; 43: this.job = args.job || 'jobs'; 44: singleton_b = function() { 45: return instance; 46: }
47: }
48:
49: var stb_1 = new singleton_b(); 50: var stb_2 = new singleton_b(); 51:
52: stb_1.newName = 'ins5'; 53: stb_2.newName //=> ins5; 54:
55: singleton_b.prototype.newJob = 'microsoft'; 56: stb_1.newJob //=> undefined; 57:
58: /** 59: * c. 特点:构造出的单例可实现对外层构造函数和内层构造函数的原型继承。 60: */ 61:
62: function singleton_c() { 63:
64: var instance; 65:
66: //第一行 67: //非常巧妙的方法,第一次执行内部new singleton_c(),由于instance为undefined,所以根据new原理返回一个具有内部singleton_c类原型链的空对象, 68: //这样就构造出了一个具有完整原型链的空对象,比b的实现方式优雅了许多 69: singleton_c = function singleton_c() { 70: return instance; 71: };
72: //第二行 73: //实现对外层singleton_c的原型继承,由于用this覆盖了内部singleton_c的原型, 74: //故此时singleton_c的constructor实际上是外层singleton_c函数 75: singleton_c.prototype = this; 76:
77: //第三行 78: //实例化,1可以保证始终是这一个实例,2可以对该实例做一些初始化工作。 79: //注意:实例化不可以这样写:return new singleton_c(),因为instance是undefined, 80: //如果把new放在return中,那么每次会构造一个新的实例。 81: instance = new singleton_c(); 82:
83: //第四行 84: //重写构造函数,由于singleton_c的原型被this覆盖,故在instance初始化时,constructor属性实际上是外层singleton_c函数。 85: //如果删掉了这行代码,并不影响对内层和外层singleton_c的原型继承,只不过构造出的实例的构造函数是外层singleton_c 86: instance.constructor = singleton_c;
87:
88: instance.prop1 = 'value1'; 89:
90: return instance; 91: }
92:
93: var stc_1 = new singleton_c(); 94: var stc_2 = new singleton_c(); 95: console.log( stc_1 === stc_2 ); //=> true 如果没有第一行代码,结果为false 96:
97: // 添加原型属性 98: singleton_c.prototype.nothing = true; 99:
100: var stc_1 = new singleton_c(); 101:
102: singleton_c.prototype.everything = true; 103:
104: var stc_2 = new singleton_c(); 105:
106: console.log( stc_1.nothing ); //=> true 如果没有第二行代码,结果为false 107: console.log( stc_2.nothing ); //=> true 如果没有第二行代码,结果为false 108: console.log( stc_1.everything ); //=> true 如果没有第二行代码,结果为false 109: console.log( stc_2.everything ); //=> true 如果没有第二行代码,结果为false 110: console.log( stc_1.constructor === singleton_c ); //=> true 如果没有第四行代码,则结果为false |
参考: