单例模式是指保证一个类仅有一个实例,并提供一个访问它的全局访问点。 单例模式是一种常用的模式,有一些对象往往只需要一个,比如线程池、全局缓存、浏览器中的window对象等。在javaScript开发中,单例模式的用途同样非常广泛。试想一下,单击登录按钮时,页面中会出现一个登录浮窗,而这个登录浮窗是唯一的,无论单击多少次登录按钮,这个浮窗都只会被创建一次,那么这个登录浮窗就适合用单例模式来创建

标准单例

  要实现一个标准的单例模式并不复杂,无非是用一个变量来标志当前是否已经为某个类创建过对象,如果是,则在下一次获取该类的实例时,直接返回之前创建的对象。代码如下:

var Singleton = function( name ){ this.name = name; this.instance = null;
};
Singleton.prototype.getName = function(){ alert ( this.name );
};
Singleton.getInstance = function( name ){ if ( !this.instance ){this.instance = new Singleton( name );}return this.instance;
};
var a = Singleton.getInstance( 'sven1' );
var b = Singleton.getInstance( 'sven2' );
alert ( a === b );    // true

  或者:

var Singleton = function( name ){ this.name = name;
};
Singleton.prototype.getName = function(){ alert ( this.name );
};
Singleton.getInstance = (function(){ var instance = null;return function( name ){if ( !instance ){instance = new Singleton( name );}})();}return instance;

  通过Singleton.getInstance来获取Singleton类的唯一对象,这种方式相对简单,但有一个问题,就是增加了这个类的“不透明性”,Singleton类的使用者必须知道这是一个单例类,跟以往通过new XXX的方式来获取对象不同,这里偏要使用Singleton.getInstance来获取对象

  虽然已经完成了一个单例模式的编写,但这段单例模式代码的实际意义并不大

透明单例

  现在的目标是实现一个“透明”的单例类,用户从这个类中创建对象时,可以像使用其他任何普通类一样。在下面的例子中,将使用CreateDiv单例类,它的作用是负责在页面中创建唯一的div节点,代码如下

  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('sven1');var b = new CreateDiv('sven2');alert(a === b);    // true

  虽然现在完成了一个透明的单例类的编写,但它同样有一些缺点。为了把instance封装起来,使用了自执行的匿名函数和闭包,并且让这个匿名函数返回真正的Singleton构造方法,这增加了一些程序的复杂度,阅读起来也不是很舒服

  上面的代码中,CreateDiv构造函数实际上负责了两件事情。第一是创建对象和执行初始化init方法,第二是保证只有一个对象。这是一种不好的做法,至少这个构造函数看起来很奇怪。假设某天需要利用这个类,在页面中创建千千万万的div,即要让这个类从单例类变成一个普通的可产生多个实例的类,那必须得改写CreateDiv构造函数,把控制创建唯一对象的那一段去掉,这种修改会带来不必要的烦恼

代理实现单例

  现在通过引入代理类的方式,来解决上面提到的问题。依然使用上面的代码,首先在CreateDiv构造函数中,把负责管理单例的代码移除出去,使它成为一个普通的创建div的类

  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);};//引入代理类proxySingletonCreateDivvar ProxySingletonCreateDiv = (function () {var instance;return function (html) {if (!instance) {instance = new CreateDiv(html);}return instance;}})();var a = new ProxySingletonCreateDiv('sven1');var b = new ProxySingletonCreateDiv('sven2');alert(a === b);

  通过引入代理类的方式,同样完成了一个单例模式的编写,跟之前不同的是,现在把负责管理单例的逻辑移到了代理类proxySingletonCreateDiv中。这样一来,CreateDiv就变成了一个普通的类,它跟proxySingletonCreateDiv组合起来可以达到单例模式的效果

惰性单例

  惰性单例指的是在需要的时候才创建对象实例。惰性单例是单例模式的重点,这种技术在实际开发中非常有用

  下面继续以登录框的例子来说明

<button id="loginBtn">登录</button>
<script>var loginLayer = (function () {var div = document.createElement('div');div.innerHTML = '我是登录浮窗';div.style.display = 'none';document.body.appendChild(div);return div;})();document.getElementById('loginBtn').onclick = function () {loginLayer.style.display = 'block';};
</script>  

  这种方式有一个问题,如果根本不需要进行登录操作,登录浮窗一开始就被创建好,很有可能将白白浪费一些 DOM 节点

  现在改写一下代码,使用户点击登录按钮的时候才开始创建该浮窗

<button id="loginBtn">登录</button>
<script>var createLoginLayer = function () {var div = document.createElement('div');div.innerHTML = '我是登录浮窗';div.style.display = 'none';document.body.appendChild(div);return div;};document.getElementById('loginBtn').onclick = function () {var loginLayer = createLoginLayer();loginLayer.style.display = 'block';};
</script>  

  虽然现在达到了惰性的目的,但失去了单例的效果。每次点击登录按钮时,都会创建一个新的登录浮窗div

  可以用一个变量来判断是否已经创建过登录浮窗,代码如下

    var createLoginLayer = (function(){var div;return function(){if ( !div ){div = document.createElement( 'div' );div.innerHTML = '我是登录浮窗';div.style.display = 'none';document.body.appendChild( div );}return div;}})();document.getElementById( 'loginBtn' ).onclick = function(){var loginLayer = createLoginLayer();loginLayer.style.display = 'block';};

  上面的代码仍然存在如下问题:

  1、违反单一职责原则的,创建对象和管理单例的逻辑都放在 createLoginLayer对象内部

  2、如果下次需要创建页面中唯一的iframe,或者script标签,用来跨域请求数据,就必须得如法炮制,把createLoginLayer函数几乎照抄一遍

    var createIframe= (function(){var iframe;return function(){if ( !iframe){iframe= document.createElement( 'iframe' );iframe.style.display = 'none';document.body.appendChild( iframe);}return iframe;}})();

 

通用惰性单例

  现在需要把不变的部分隔离出来,先不考虑创建一个div和创建一个iframe有多少差异,管理单例的逻辑其实是完全可以抽象出来的,这个逻辑始终是一样的:用一个变量来标志是否创建过对象,如果是,则在下次直接返回这个已经创建好的对象

var obj;
if ( !obj ){ obj = xxx;
}

  然后,把如何管理单例的逻辑从原来的代码中抽离出来,这些逻辑被封装在getSingle函数内部,创建对象的方法fn被当成参数动态传入getSingle函数

var getSingle = function( fn ){ var result;return function(){return result || ( result = fn .apply(this, arguments ) );}
}

  接下来将用于创建登录浮窗的方法用参数fn的形式传入getSingle,不仅可以传入createLoginLayer,还能传入createScript、createIframe、createXhr等。之后再让getSingle返回一个新的函数,并且用一个变量result来保存fn的计算结果。result变量因为身在闭包中,它永远不会被销毁。在将来的请求中,如果result已经被赋值,那么它将返回这个值

    var createLoginLayer = function(){var div = document.createElement( 'div' );div.innerHTML = '我是登录浮窗';div.style.display = 'none';document.body.appendChild( div );return div;};var createSingleLoginLayer = getSingle( createLoginLayer );document.getElementById( 'loginBtn' ).onclick = function(){var loginLayer = createSingleLoginLayer();loginLayer.style.display = 'block';};

  下面再试试创建唯一的iframe用于动态加载第三方页面

    var createSingleIframe = getSingle(function () {var iframe = document.createElement('iframe');document.body.appendChild(iframe);return iframe;});document.getElementById('loginBtn').onclick = function () {var loginLayer = createSingleIframe();loginLayer.src = 'https://www.hao123.com';};

  上面的例子中,创建实例对象的职责和管理单例的职责分别放置在两个方法里,这两个方法可以独立变化而互不影响,当它们连接在一起的时候,就完成了创建唯一实例对象的功能

  这种单例模式的用途远不止创建对象,比如通常渲染完页面中的一个列表之后,接下来要给这个列表绑定click事件,如果是通过ajax动态往列表里追加数据,在使用事件代理的前提下,click事件实际上只需要在第一次渲染列表的时候被绑定一次,但不想判断当前是否是第一次渲染列表,如果借助于jQuery,通常选择给节点绑定one事件

    var bindEvent = function(){$( 'div' ).one( 'click', function(){alert ( 'click' );});};var render = function(){console.log( '开始渲染列表' );bindEvent();};render();render();render();

  如果利用getSingle函数,也能达到一样的效果

    var getSingle = function (fn) {var result;return function () {return result || (result = fn.apply(this, arguments));}};var bindEvent = getSingle(function(){document.getElementById( 'div1' ).onclick = function(){alert ( 'click' );}return true;});var render = function(){console.log( '开始渲染列表' );bindEvent();};render();render();render();

  可以看到,render函数和bindEvent函数都分别执行了3次,但div实际上只被绑定了一个事件

转载于:https://www.cnblogs.com/xuzhudong/p/8849535.html

[转]JS设计模式-单例模式(二)相关推荐

  1. 2023-01-26 JS设计模式-单例模式:单例模式的原理和实现,懒汉模式和饿汉模式,单例模式实现登录框

    文章目录 1.什么是单例模式? 介绍 特点 结构 2.如何实现一个单例模式? 思路 实现代码 3.单例模式的优缺点 4.懒汉模式和饿汉模式 懒汉模式:一开始不会实例化,什么时候用才new出来实例化 饿 ...

  2. Android设计模式——单例模式(Singleton)

    二十三种设计模式分为三大类: 创建型模式,共五种:工厂方法模式.抽象工厂模式.单例模式.建造者模式.原型模式. 结构型模式,共七种:适配器模式.装饰器模式.代理模式.外观模式.桥接模式.组合模式.享元 ...

  3. js observer 添加_简单了解4种JS设计模式

    阅读本文约需要5分钟 大家好,我是你们的导师,我每天都会在这里给大家分享一些干货内容(当然了,周末也要允许老师休息一下哈).上次老师跟大家分享了15款有用前端开发的ST插件的知识,今天跟大家分享下4种 ...

  4. PureMVC(AS3)剖析:设计模式(二)

    PureMVC(AS3)剖析:设计模式(二) 模式 上一篇中介绍了PureMVC中使用的3种设计模式:单例模式.观察者模式.外观模式.本篇将继续介绍剩下的3种设计模式: l  使用中介者(Mediat ...

  5. 设计模式(二 三)工厂模式:1-简单工厂模式

    模拟场景: 需要构造一个运算器(Operation),分别负责加减乘除的运算功能. 思想: 这里需要构造四个 Operation,可以使用 Factory 去统一创建这四个对象. 所需要构造的对象是运 ...

  6. java singleton inner class_Java面向对象设计模式-单例模式

    Java面向对象设计模式-单例模式 2020-05-28 589 0 单例模式保证一个类仅有一个实例,并提供一个访问它的全局访问点,有多重实现方式. 一.饿汉式单例模式,构造方法私有化,在加载类Sin ...

  7. 炒冷饭系列:设计模式 单例模式

    2019独角兽企业重金招聘Python工程师标准>>> 炒冷饭系列:设计模式 单例模式 摘要: 原创出处: http://www.cnblogs.com/Alandre/ 泥沙砖瓦浆 ...

  8. 小视频源码,设计模式单例模式

    小视频源码,设计模式单例模式实现的相关代码 .一,单线程时候推荐 /*** Created by Shinelon on 2018/10/11.* 单利模式 懒汉式 -->单线程推荐使用*/pu ...

  9. 设计模式(二)—— 创建型模式

    设计模式(二)-- 创建型模式 文章首发于 掘金 作者:MiyueFE 著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处. 创建型模式,即处理对象创建过程的设计模式,根据实际情况来使 ...

最新文章

  1. Uber提出损失变化分配方法LCA,揭秘神经网络“黑盒”
  2. Django 笔记-20190521
  3. STM32串口中断的4种接收数据的实现方式
  4. MySQL—相关子查询
  5. python发送邮箱_你知道怎么用Python发送邮件吗?
  6. Duplicate interface definition for class
  7. 媒体服务器协议,媒体服务器介绍(mediactrl架构)
  8. JVM----类的加载机制
  9. Mybatis_别名的使用
  10. java 分卷 zip
  11. iWebOffice
  12. 淘宝闲鱼等电商平台数据采集软件
  13. kettle-java代码执行hive相关ktr时报错: database type with plugin id [HIVE2] couldn‘t be found!
  14. 论文编辑与投稿——论文页眉、页码编辑,以及换章时偶数页设置成空白页的操作
  15. mac安装win10后触摸板没有右键功能键的添加技巧
  16. 地理坐标系和投影坐标系
  17. Ubuntu16.04 安装 CUDA、CUDNN、OpenCV 并用 Anaconda 配置 Tensorflow 和 Caffe 详细过程
  18. yolov5 目标检测算法
  19. 命令python所在的驱动器和文件夹_Python文件夹与文件的操作-阿里云开发者社区...
  20. sql里left join的效率问题

热门文章

  1. jquery - 动态绑定事件
  2. Ubuntu-16.04 部署 OpenStack Ocata下
  3. Android定位开发之百度定位、高德定位、腾讯定位,三足鼎立一起为我所用!
  4. linux运维实战练习-2015年9月13日-9月15日课程作业(练习)安排
  5. Spring MVC使用拦截器实现权限控制
  6. jsfiddle网站介绍
  7. java httpclient 为邮箱添加来信转发规则
  8. JSP复习(二):EL表达式
  9. mysql学习总结一:mysql的安装,介绍,基本命令操作
  10. mysql基础(九) 索引和视图