摘要:观察构造函数的代码,该构造函数实际上负责了两件事情第一是创建对象和执行初始化方法,第二是保证只有一个对象。惰性单例在实际开发中非常有用,是单例模式的重点。
单例模式
单例模式的定义是:
保证一个类仅有一个实例,并提供一个访问它的全局访问点。
单例模式是一种常用的模式,有一些对象我们往往只需要一个,比如线程池、全局缓存、浏览器的window对象等。例如,当我们点击登录按钮时,页面会弹出一个登录悬浮窗,而这个登录悬浮窗是唯一的,无论点击多少次登录按钮,这个悬浮窗只会被创建一次,这时,这个悬浮窗就适合用单例模式来创建。
实现单例模式实现一个标准的单例模式,一般是用一个变量来标志当前是否已经为某个类创建过对象,若是,则在下一次获取该类的实例时,直接返回之前创建的对象。
不透明的单例模式var Singleton = function(name){ this.name = name; this.instance = null; } Singleton.prototype.getName = function(){ console.log(this.name); }; Singleton.getInstance = function(name){ if(! this.instance){ this.instance = new Singleton(name); } return this.instance; }; var a = Singleton.getInstance("sin1"); var b = Singleton.getInstance("sin2"); console.log(a === b); // 输出:true
我们通过Singleton.getInstance来获取Singleton类的唯一对象,这种方式想对简单,但有一个问题,就是增加了类的“不透明性”,Singleton类的使用者必须知道这是一个单例类,跟以往通过new xxx来获取对象的方式不同,这里只能使用Singleton.getInstance来获取对象。
透明的单例模式现在我们通过一段代码来实现一个透明的单例类,用户从这个类中创建对象的时候,可以像使用其他任何普通类一样。
var createDiv = (function(){ var instance; var createDiv = function(html){ if(instance){ return instance; } this.html = html; this.init(); return instance = this; }; createDiv.prototype.init = function(){ var div = document.createElement("div"); div.innerHTML = this.html; document.body.appendChild(div); }; return createDiv; })(); var a = new createDiv("sin1"); var b = new createDiv("sin2"); console.log(a === b); // 输出:true
为了把instance封装起来,我们使用了自执行的匿名函数和闭包,并且让这个匿名函数返回真正的Singleton构造方法,这增加了一些程序的复杂度,阅读起来也不是很舒服。
观察Singleton构造函数的代码,该构造函数实际上负责了两件事情:第一是创建对象和执行初始化init方法,第二是保证只有一个对象。这不符合设计原则中的“单一职责原则”,这是一种不好的做法。假设我们某天需要利用这个类,在页面中创建很多个div,即让这个类从单例类编程一个普通的可以产生多个实例的类,我们就得改写createDiv构造函数,把控制创建唯一对象的那一段去掉,这种修改会给我们带来不必要的烦恼。
用代理实现单例模式现在我们通过引入代理类的方法,来解决上面提到的问题。
var createDiv = function(html){ this.html = html; this.init(); }; createDiv.prototype.init = function(){ var div = document.createElement("div"); div.innerHTML = this.html; document.body.appendChild(div); }; // 引入代理类 proxySingletonCreateDiv var proxySingletonCreateDiv = (function(){ var instance; return function(html){ if(!instance){ instance = new createDiv(html); } return instance; } })(); var a = new proxySingletonCreateDiv("sin1"); var b = new proxySingletonCreateDiv("sin2");
我们把负责管理单例的逻辑移到了代理类proxySingletonCreateDiv中。这样一来,createDiv就变成了一个普通的类,它跟proxySingletonCreateDiv组合起来就可以达到单例模式的效果;如果多带带使用,就作为一个普通的类,能产生多个实例对象。
JavaScript中的单例模式前面提到的单例模式的实现,更多的是接近传统面向对象语言中的实现,单例对象从类中创建而来。在以类为中心的语言中,这是很自然的做法,比如在Java中,如果需要某个对象,就必须先定义一个类,对象总是从类中创建而来。
但JavaScript是一门无类语言,生搬单例模式的概念并无意义。在JavaScript中创建对象非常简单,直接声明即可。既然这样,我们就没有必要为它先创建一个类。
单例模式的核心是确保只有一个实例,并提供全局访问。
全局变量不是单例模式,但在JavaScript开发中,我们经常会把全局变量当成单例模式来使用,例如var a = {}; 。
当用这种方式创建对象a时,对象a确实独一无二。如果变量a被声明在全局作用域下,则我们可以在代码中的任何位置使用这个变量,全局变量自然能全局访问。这样就满足了单例模式的两个条件。
但是,全局变量存在一些问题:
容易造成命名空间污染;
在大型项目中,如果不加以限制和管理,程序中可能存在很多这样的变量;
JavaScript中的变量很容易被不小心覆盖。
因此,在使用全局变量时,我们要尽力降低它的污染,通过以下方式:
1.使用命名空间
适当地使用命名空间,并不会杜绝全局变量,但可以减少全局变量的数量。
最简单的方法依然是用对象字面量的方式:
var namespace1 = { a: function(){ alert(1); }, b: function(){ alert(2); } };
把a和b都定义为namespace1的属性,这样可以减少变量和全局作用域打交道的机会。‘
另外,可以动态地创建命名空间,如:
var myApp = {}; myApp.namespace = function(name){ var parts = name.split("."); var current = myApp; for(var i in parts){ if(!current[parts[i]]){ current[parts[i]] = {}; } current = current[parts[i]]; } }; myApp.namespace("event"); myApp.namespace("dom.style");
上述代码等价于:
var myApp = { event:{}, dom:{ style:{} } };
2.使用闭包封装私有变量
这种方法把一些变量封装在闭包的内部,只暴露一些接口跟外界通信:
var user = (function(){ var __name = "sin1"; var __age = 29; return { getUserInfo: function(){ return __name + "-" + __age; } } })();
我们用下划线来约定私有变量__name和__age,它们被封装在闭包产生的作用域中,外部是访问不到这两个变量的,这就避免了对全局的命名污染。
惰性单例惰性单例指的是在需要的时候才创建对象实例。惰性单例在实际开发中非常有用,是单例模式的重点。
我们在开头写的Singleton类就用过这种技术,instance实例对象总是在我们调用Singleton.getInstance的时候才被创建,而不是在页面加载好的时候就创建。
实现惰性单例假设,在一个提供登录功能(点击登录按钮弹出一个登录悬浮窗)的web页面中,可能用户在访问过程中,根本不需要进行登录操作,只需要浏览某些内容。所以,没有必要在页面加载好之后就马上创建登录悬浮窗,只需要当用户点击登录按钮的时候才开始创建登录悬浮窗,实现代码如下:
惰性单例
但这段代码还是存在一些问题的:
这段代码仍然是违反单一职责原则的,创建对象和管理单例的逻辑都放在createLoginLayer对象内部;
如果我们下次需要创建页面中唯一的iframe,或者script标签,必须得如法炮制,把createLoginLayer函数几乎照抄一遍。
通用的惰性单例为了解决上面的问题,我们可以实现一段通用的惰性单例代码:
惰性单例
上面的代码,
把管理单例的逻辑抽象了出来:用一个变量来标志是否创建过对象,如果是,则在下次直接返回这个已经创建好的对象;
把如何管理单例的逻辑封装在getSingle函数内部,创建对象的方法fn被当成参数动态传入getSingle函数;
将创建登录悬浮窗的方法传入getSingle,还能传入createIframe,createScript;
getSingle函数返回一个新的函数,并且用一个变量result来保存fn的计算结果,result变量在闭包中,永远不会被销毁,所以在将来的请求中,如果result已经被赋值,那么它将返回这个值。
单例模式的用途不止在于创建对象,比如我们通常渲染完页面中的一个列表后,就要给这个列表绑定click事件,如果通过ajax动态往列表里追加数据,在使用事件代理的前提下,click事件实际上只需要在第一次渲染列表的时候就被绑定一次。
惰性单例
PS:本节内容为《JavaScript设计模式与开发实践》第四章 笔记。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/86213.html
摘要:本系列为设计模式与开发实践作者曾探学习总结,如想深入了解,请支持作者原版单例模式实现单例模式单例模式的定义是保证一个类仅有一个实例,并提供一个访问它的全局访问点。 本系列为《JavaScript设计模式与开发实践》(作者:曾探)学习总结,如想深入了解,请支持作者原版 单例模式 实现单例模式 单例模式的定义是:保证一个类仅有一个实例,并提供一个访问它的全局访问点。单例模式是一种常用的模式...
摘要:引言本文摘自设计模式与开发实践在传统开发工程师眼里,单例就是保证一个类只有一个实例,实现的方法一般是先判断实例存在与否,如果存在直接返回,如果不存在就创建了再返回,这就确保了一个类只有一个实例对象。 引言 本文摘自《JavaScript设计模式与开发实践》 在传统开发工程师眼里,单例就是保证一个类只有一个实例,实现的方法一般是先判断实例存在与否,如果存在直接返回,如果不存在就创建了再返...
摘要:所以程序在引入文件的时候用了单例模式,一个文件实例化一次,这种做法无疑是好的,但是也容易引起。在我们平时的开发过程中,可以借鉴这两种方式去缓存变量,节点等。 这一章作者讲了一个例子,就是在用单例模式生成一个dom节点,还要做到只有访问的时候才创建,后续访问直接用前面创建的。那么实际开发中我们会用到这个模式吗?现在我们基本都是用vue,react,angular开发,不太会直接去操作do...
摘要:订阅模式的一个典型的应用就是后面会写一篇相关的读书笔记。享元模式享元模式的核心思想是对象复用,减少对象数量,减少内存开销。适配器模式对目标函数进行数据参数转化,使其符合目标函数所需要的格式。 设计模式 单例模式 JS的单例模式有别于传统面向对象语言的单例模式,js作为一门无类的语言。使用全局变量的模式来实现单例模式思想。js里面的单例又分为普通单例和惰性单例,惰性单例指的是只有这个实例...
摘要:停更许久,近期计划更新设计模式系列。单例模式是创建型设计模式的一种。虽然它不是正规的单例模式,但不可否认确实具备类单例模式的特点。适用场景单例模式的特点,意图解决维护一个全局实例对象。 停更许久,近期计划更新:设计模式系列。 showImg(https://segmentfault.com/img/bVbt7uw?w=800&h=600); 单例模式:限制类实例化次数只能一次,一个类只...
阅读 2792·2023-04-26 02:00
阅读 2713·2019-08-30 15:54
阅读 817·2019-08-30 11:15
阅读 1450·2019-08-29 15:31
阅读 863·2019-08-29 14:12
阅读 457·2019-08-29 13:08
阅读 786·2019-08-27 10:51
阅读 2665·2019-08-26 12:17