JavaScript中实现单体模式分享


Posted in Javascript onJanuary 29, 2015

单体模式作为一种软件开发模式在众多面向对象语言中得到了广泛的使用,在javascript中,单体模式也是使用非常广泛的,但是由于javascript语言拥有其独特的面向对象方式,导致其和一些传统面向对象语言虽然在单体模式的思想上是一致的,但是实现起来还是有差异的。

首先来看看传统面向对象语言对于单体模式的定义:单体模式是只能被实例化一次并且可以通过一个众所周知的访问点来访问的类。这个定义有两点突出了传统面向对象语言的特征,即类和实例化,所以对于传统面向对象语言来讲,单体模式是建立在其类和实例化的自然特性之上的,即使用关键字class定义一个类,该类可通过new关键字来实例化,但是需要保证每次被new实例化之后得到的都是同一个实例或者说只能通过new来调用其构造函数一次。

再来看看javascript中对于单体模式的定义:单体是一个用来划分命名空间并将一批相关方法和属性组织在一起的对象,如果它能够被实例化,那么只能被实例化一次。对比上面的定义,你会发现这里的单体定义将其实质定义为对象,而不是传统面向对象语言中的类,这也表明了javascript这门语言是基于对象的。同时后面又指出,如果能够被实例化,这说明了在javascript中单体定义应该有好几种方式,存在一种或几种能够被实例化即使用new关键字来创建单体对象的方式,但是这种方式不是javascript自身的自然特征,因为使用new关键字创造出来的对象,实际上都是通过function来模拟定义其构造函数的(虽然ES6开始支持class关键字了,但是目前还没有得到浏览器广泛支持),那么如何使用javascript的自然特征来实现单体模式呢?

var Singleton={
  attribute1:true,
  attribute2:10,
  method1:function(){

  },
  method2:function(arg){

  }
}

这里定义了一个对象Singleton,内部包含若干属性和方法,将其包含在页面中,js载入的时候就创建了这个对象,在调用时使用Singleton.method1来调用,它的实例化是随着页面载入js解析执行过程中完成的,我们并没有使用new关键字来实例化这个对象,这也是javascript中实现单体模式和传统面向对象语言一个很大的不同。这种方式更为简单易于理解。但是这种方式存在若干缺点,一个很明显的缺点是它并没有提供命名空间,其他程序员如果在页面中也定义了一个Singleton变量,那么很容易改写和混淆这个单体对象,于是针对这个问题,将其改写如下:

var mySpace={};
mySpace.Singleton={
  attribute1:true,
  attribute2:10,
  method1:function(){

  },
  method2:function(arg){

  }
}

这里首先定义了一个mySpace的命名空间,然后将单体对象Singleton挂载在这个对象的下面,这大大减少了和其他程序员冲突以及误操作的可能,即使其他人在全局作用域中定义一个Singleton变量,也不会污染到这个单体对象,这就实现了前面定义中所说的划分命名空间并且将一些相关属性和方法组织在一起的功能。

这个方法依然存在缺点,这个单体对象的所有属性和方法都是共有的,外部可随时访问和修改,于是采用闭包来模拟私有属性和方法,如下:

mySpace.Singleton=(function(){
  var privateAttribute1=false;
  var privateAttribute1=[1,2,3];
  function privateMethod1(){

  }
  function privateMethod2(){

  }

  return {
  publicAttribute1:true,
  publicAttribute2:10,
  publicMethod1:function(){
    privateAttribute1=true;
    privateMethod1();
  },
  publicMethod2:function(arg){
    privateAttribute1=[4,5,6];
    privateMethod2();
  }

  }

})();

在这里我们直接给该单体对象赋值了一个匿名自执行的函数,在该函数中使用var和function关键字分别来定义其私有属性和方法,这些在函数外部(单体对象外部)是无法直接访问的,因为函数一执行完毕,其内部作用域的空间就会被回收,这也就是能够利用闭包来模拟私有属性和方法的原因所在。在该函数(闭包)中,同时最终返回一个对象,这个对象中包含一些公有方法和属性,在外部可以直接调用,同时这些公有方法由于定义在函数内部,所以可以调用其私有属性和方法,但是外界只能通过返回的公有方法和属性来完成某些操作,不能够直接调用Singleton.privateMethod1这些属性。这就使得该单体对象既隔离了外界去直接访问其私有属性和方法,又提供给外界一些共有属性和方法去完成某些操作。

这种匿名函数自执行所构造的单体模式在很多js库中被广泛使用,但是依然存在一个问题,如果我们在载入页面的时候并不需要用到该对象,而且该对象的创建比较耗费开销(如需要进行大量计算或需要多次访问dom树及其属性等)时,合理的做法是需要它的时候再去创建它,而不是随着js的解析执行直接去创建,这种概念被称之为惰性加载(lazy loading),于是修改以上代码如下:

mySpace.Singleton=(function(){
    var uniqueInstance;
    function constructor(){
      var privateAttribute1=false;
      var privateAttribute1=[1,2,3];
      function privateMethod1(){
      }
      function privateMethod2(){
      }
      return {
        publicAttribute1:true,
        publicAttribute2:10,
        publicMethod1:function(){
          privateAttribute1=true;
          privateMethod1();
        },
        publicMethod2:function(arg){
          privateAttribute1=[4,5,6];
          privateMethod2();
        }

      }
    }

    return {
      getInstance:function(){
       if(!uniqueInstance){
         uniqueInstance=constructor();
       }
        return uniqueInstance;
      }
    }

  })();

这里首先在匿名函数中定义了一个私有变量uniqueInstance,作为一个判断单体对象是否被创建出来的句柄,然后将刚才所有对单体对象定义的属性和方法都放在一个名为constructor的函数中,只有该函数调用了,才会创造出该单体对象,否则不会直接创建它。然后,返回一个对象,其包含一个getInstance方法,该方法是供外部调用的,调用该方法的时候首先判断该单体对象是否存在,如果存在就直接返回它,否则调用constructor函数构造这个单体对象再返回它。最后如果我们调用该单体对象的某个方法,需要使用mySpace.Singleton.getInstance().publicMethod1(),这里,只有我们这样调用的时候才会创建这个单体对象,否则该单体对象是不会被自动创建的,这实际上就实现了按需加载或者惰性加载。

Javascript 相关文章推荐
清空上传控件input file的值
Jul 03 Javascript
javascript下string.format函数补充
Aug 24 Javascript
js将json格式内容转换成对象的方法
Nov 01 Javascript
javascript控制Div层透明属性由浅变深由深变浅逐渐显示
Nov 12 Javascript
jQuery旋转木马式幻灯片轮播特效
Dec 04 Javascript
通过修改360抢票的刷新频率和突破8车次限制实现方法
Jan 04 Javascript
js记录点击某个按钮的次数-刷新次数为初始状态的实例
Feb 15 Javascript
bootstrap折叠调用collapse()后data-parent不生效的快速解决办法
Feb 23 Javascript
利用JS hash制作单页Web应用的方法详解
Oct 10 Javascript
vue2.0使用v-for循环制作多级嵌套菜单栏
Jun 25 Javascript
vue debug 二种方法
Sep 16 Javascript
微信小程序换肤功能实现代码(思路详解)
Aug 25 Javascript
angular简介和其特点介绍
Jan 29 #Javascript
javascript实现获取浏览器版本、操作系统类型
Jan 29 #Javascript
浅谈javascript中自定义模版
Jan 29 #Javascript
jQuery和AngularJS的区别浅析
Jan 29 #Javascript
node.js中的forEach()是同步还是异步呢
Jan 29 #Javascript
Node.js事件循环(Event Loop)和线程池详解
Jan 28 #Javascript
使用Sticker.js实现贴纸效果
Jan 28 #Javascript
You might like
UCenter 批量添加用户的php代码
2012/07/17 PHP
PHP连接MySQL的2种方法小结以及防止乱码
2014/03/11 PHP
PHP 使用 Imagick 裁切/生成缩略图/添加水印自动检测和处理 GIF
2016/02/19 PHP
jQuery学习笔记之Helloworld
2010/12/22 Javascript
JS验证身份证有效性示例
2013/10/11 Javascript
判断文件是否正在被使用的JS代码
2013/12/21 Javascript
javascript实现数字+字母验证码的简单实例
2014/02/10 Javascript
jQuery中has()方法用法实例
2015/01/06 Javascript
jQuery实现统计输入文字个数的方法
2015/03/11 Javascript
使用AngularJS来实现HTML页面嵌套的方法
2015/06/17 Javascript
基于JavaScript的操作系统你听说过吗?
2016/01/28 Javascript
下一代Bootstrap的5个特点 超酷炫!
2016/06/17 Javascript
js实现简单的网页换肤效果
2017/01/18 Javascript
深入nodejs中流(stream)的理解
2017/03/27 NodeJs
js实现首屏延迟加载实现方法 js实现多屏单张图片延迟加载效果
2017/07/17 Javascript
基于vue1和vue2获取dom元素的方法
2018/03/17 Javascript
JavaScript实现计算圆周率到小数点后100位的方法示例
2018/05/08 Javascript
Vue自定义指令封装节流函数的方法示例
2018/07/09 Javascript
Vue-CLI 3.X 部署项目至生产服务器的方法
2019/03/22 Javascript
浅谈vue-router路由切换 组件重用挖下的坑
2019/11/01 Javascript
jQuery 动画与停止动画效果实例详解
2020/05/19 jQuery
JS中队列和双端队列实现及应用详解
2020/09/29 Javascript
[56:17]NB vs Infamous 2019国际邀请赛淘汰赛 败者组 BO3 第三场 8.22
2019/09/05 DOTA
pycharm中使用anaconda部署python环境的方法步骤
2018/12/19 Python
python flask web服务实现更换默认端口和IP的方法
2019/07/26 Python
pip install 使用国内镜像的方法示例
2020/04/03 Python
Python3-异步进程回调函数(callback())介绍
2020/05/02 Python
Django-Scrapy生成后端json接口的方法示例
2020/10/06 Python
某个公司的Java笔面试题
2016/03/11 面试题
Windows和Linux动态库应用异同
2016/04/17 面试题
高中毕业生自我鉴定例文
2013/12/29 职场文书
我的中国梦演讲稿小学篇
2014/08/19 职场文书
大学生入党积极分子党校学习思想汇报
2014/10/25 职场文书
2015年师德师风承诺书
2015/01/22 职场文书
golang内置函数len的小技巧
2021/07/25 Golang
通过T-SQL语句创建游标与实现数据库加解密功能
2022/03/16 SQL Server