在讲述惰性实例化之前先来和大家分享一下什么是“单体模式”?
“单体模式”(singleton)是JavaScript中最基本但又最有用的模式之一,它可能比其他任何模式都更常用。这种模式提供了一种将代码组织为一个逻辑单元的手段,这个逻辑单元中的代码可以通过单一的变量进行访问。通过确保单体对象只存在一份实例,你就可以确信自己的所有代码使用的都是同样的全局资源。
单体类在JavaScript中有许多用途。它们可以用来划分命名空间,以减少网页中全局变量的数目。它们还可以在一种名为分支(branching)的技术中用来封闭浏览器之间的差异。更重要的是,借助于单体模式,你可以把代码组织得更为一致,从而使其更容易阅读和维护。这种模式在JavaScript中,也许比其他任何语言中都更重要。在网页上使用全局变量有很大的风险,而用单体对象创建的命名空间则是清除这些全局变量的最佳手段之一。
单体模式的基本结构:
/* Basic Singleton */ var Singleton = { attribute1 : true, attribute2 : 10, method1 : function(){}, method2 : function(){} }
这种单体模式可以被修改。可以为其添加新成员,这一点与别的对象字面量没什么不同。也可以用delete运算符删除其现有成员。
现在言归正传,开始说一下标题中所提到的“惰性实例化”。单体模式的各种实现方式有一个共同点:单体对象都是在脚本加载时被创建出来。对于资源密集型的或配置开销甚大的单体,也许更合理的做法是将其实例化推迟到需要使用它的时候。这种技术被称为“惰性加载”(lazy loading),它最常用于那些必须加载大师数据的单体。
这种惰性加载单体的特别之处在于,对它们的访问必须借助于一个静态方法。应该这样调用其方法:Singleton.getInstance().methodName(),而不是这样调用:Singleton.methodName()。getInstance方法会检查该单体是否已经被实例化。如果还没有,那么它将创建并返回其实例,如果单体已经实例化过,那么返回现有实例。
如何把一个普通单体转化为惰性加载单体?以下我们来看一个示例:
普通单体:
/* Singleton with Private Members */ MyNamespace.Singleton = (function(){ // Private members var privateAttribute1 = false; var privateAttribute2 = [1, 2, 3]; function privateMethod1(){} function privateMethod2(){} return{ // Public members publicAttribute1 : true, publicAttribute2 : 10, publicMethod1 : function(){}, publicMethod2 : function(){} } })();
这段代码还没有进行任何修改。转化工作的第一步是把单体的所有代码移到一个名为constructor的方法中:
/* 转化的第一步 */ MyNamespace.Singleton = (function(){ function constructor(){ // 将普通单体中所有的代码放入此方法中 // Private members var privateAttribute1 = false; var privateAttribute2 = [1, 2, 3]; function privateMethod1(){}; function privateMethod2(){}; return{ // Public members publicAttribute1 : true, publicAttribute2 : 10, publicMethod1 : function(){}, publicMethod2 : function(){} } }; })();
这个方法是不能从闭包外部进行访问的,这是好事,因为我们想控制其调用的时机。公用方法getInstance就是用来实现这种控制的。为了使其成为公用方法,只需将其放到一个对象字面量中并返回该对象即可:
/* 转化惰性加载第二步,使用公用方法getInstance */ MyNamespace.Singleton = (function(){ function constructor(){ // 原普通单体中的所有代码 } retutn{ getInstance : function(){ // 控制代码放在这里 } } })();
要实现单体类实例化时机的代码,需要做两件事。第一,它必须知道该类是否已经被实例化过。第二,如果该类已经实例化过,那么实例的情况,以便能返回这个实例。
MyNamespace.Singleton = (function(){ var uniqueInstance; // 私有属性,用以判断单体是否已经被实例化 function constructor(){ // 原普通单体中的所有代码 } retutn{ getInstance : function(){ if(!uniqueInstance){ uniqueInstance = constructor(); } return uniqueInstance; } } })();
反一个单体转化为惰性加载单体后,必须再对调用代码进行修改。
原普通单体调用方法:MyNamespace.Singleton.publicMethod1();
惰性加载调用方法:MyNamespace.Singleton.getInstance().publicMethod1();
惰性加载单体的缺点之一就是在于其复杂性。