JavaScript编程的单例设计模讲解


Posted in Javascript onNovember 10, 2015

在Javascript中,单例模式是一种最基本又经常用到的设计模式,可能在不经意间就用到了单例模式。
本文将从最基础的理论开始,讲述单例模式的基本概念和实现,最后用一个例子来讲述单例模式的应用。

理论基础

概念

单例模式,顾名思义就是只有一个实例存在。通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案。

基本结构

最简单的单例模式起始就是一个对象字面量,它将有关联的属性和方法组织到一起。

var singleton = {
  prop:"value",
  method:function(){
  }
}

这种形式的单例模式,所有成员都是公开的,都可以通过singleton来访问。这样的缺点是单例中有一些辅助的方法并不希望暴露给使用者,如果使用者用了这些方法,然后在后面维护的时候,一些辅助方法被删除,这样会造成程序错误。
如何避免这样从的错误呢?

包含私有成员的单例模式

要怎么在类中创建私有成员呢,这通过需要闭包来进行实现,关于闭包的知识,本文不再赘述,大家可以自行Google。
基本形式如下:

var singleton = (function () {
      var privateVar = "private";
      return {
        prop: "value",
        method: function () {
          console.log(privateVar);
        }
      }
    })();

首先是一个自执行的匿名函数,在匿名函数中,声明了一个变量privateVar,返回一个对象赋值给单例对象singleton。在匿名函数外部无法访问到privateVar变量,它就是单例对象的私有变量,只能在函数内部或通过暴露出来的方法去访问这个私有变量。这种形式又被成为模块模式。

惰性实例化

不管是直接字面量或者私有成员的单例模式,两者都是在脚本加载时就被创建出来的单例,但是有时候,页面可能永远也用不到这个单例对象,这样会造成资源浪费。对于这种情况,最佳的处理方式就是惰性加载,就是说在需要的时候才去真正实例化这个单例对象,如何实现呢?

var singleton = (function () {
      function init() {
        var privateVar = "private";
        return {
          prop: "value",
          method: function () {
            console.log(privateVar);
          }
        }
      }
      var instance = null;
      return {
        getInstance: function () {
          if (!instance) {
            instance = init();
          }
          return instance;
        }
      }
    })();

首先将创建单例对象的代码封装到init函数中,然后声明一个私有变量instance表示单例对象的实例,公开一个方法getInstance来获取单例对象。
调用的时候就通过singleton.getInstance()来进行调用,单例对象是在调用getInstance的时候才真正被创建。

适用场合

单例模式是JS中最常使用的设计模式,从增强模块性和代码组织性等方面来说,应该尽可能的使用单例模式。它可以把相关代码组织到一起便于维护,对于大型项目,每个模块惰性加载可以提高性能,隐藏实现细节,暴露出常用的api。常见的类库比如underscore,jQuery我们都可以将其理解为单例模式的应用。

结合实战

前面已经讲过,单例模式是最常用的设计模式之一,我们来举个例子进行说明,
下面的代码主要实现一个简单的日期帮助类,通过单例模式实现:

基本的单例模式结构

var dateTimeHelper = {
      now: function () {
        return new Date();
      },
      format: function (date) {
        return date.getFullYear() + "-" + (date.getMonth() + 1) + "-" + date.getDate();
      }
    }; 
console.log(dateTimeHelper.now());

这段代码通过对象字面量实现单例模式,使用的时候直接调用方法即可。

惰性加载实现单例模式

var dateTimeHelper = (function () {
      function init() {
        return {
          now: function () {
            return new Date();
          },
          format: function (date) {
            return date.getFullYear() + "-" + (date.getMonth() + 1) + "-" + date.getDate();
          }
        }
      }
      var instance = null;
      return {
        getInstance: function () {
          if (!instance) {
            instance = init();
          }
          return instance;
        }
      }
    })(); 
console.log(dateTimeHelper.getInstance().now())

这就是惰性加载的单例模式。

下面再来看几个实例:
实现1: 最简单的对象字面量

var singleton = {

    attr : 1,

    method : function(){ return this.attr; }

  }



var t1 = singleton ;

var t2 = singleton ;

    那么很显然的, t1 === t2 。

    十分简单,并且非常使用,不足之处在于没有什么封装性,所有的属性方法都是暴露的。对于一些需要使用私有变量的情况就显得心有余而力不足了。当然在对于 this 的问题上也是有一定弊端的。

    实现2:构造函数内部判断

    其实和最初的JS实现有点类似,不过是将对是否已经存在该类的实例的判断放入构造函数内部。

function Construct(){

  // 确保只有单例

  if( Construct.unique !== undefined ){

    return Construct.unique; 

  }

  // 其他代码

  this.name = "NYF";

  this.age="24";

  Construct.unique = this;

}



var t1 = new Construct() ;

var t2 = new Construct() ;

    那么也有的, t1 === t2 。

    也是非常简单,无非就是提出一个属性来做判断,但是该方式也没有安全性,一旦我在外部修改了Construct的unique属性,那么单例模式也就被破坏了。 

    实现3 : 闭包方式   

    对于大着 灵活 牌子的JS来说,任何问题都能找到 n 种答案,只不过让我自己去掂量孰优孰劣而已,下面就简单的举几个使用闭包实现单例模式的方法,无非也就是将创建了的单例缓存而已。

var single = (function(){

  var unique;

  function Construct(){

    // ... 生成单例的构造函数的代码

  }



  unique = new Constuct();



  return unique;

})();

    只要 每次讲 var t1 = single; var t2 = single;即可。 与对象字面量方式类似。不过相对而言更安全一点,当然也不是绝对安全。

    如果希望会用调用 single() 方式来使用,那么也只需要将内部的 return 改为

return function(){

    return unique;

  }

    以上方式也可以使用 new 的方式来进行(形式主义的赶脚)。当然这边只是给了闭包的一种例子而已,也可以在 Construct 中判断单例是否存在 等等。 各种方式在各个不同情况做好选着即可。

总结

单例模式的好处在于对代码的组织作用,将相关的属性和方法封装在一个不会被多次实例化的对象中,让代码的维护和调试更加轻松。隐藏了实现细节,可以防止被错误修改,还防止了全局命名空间的污染。另外可以通过惰性加载提高性能,减少不必要的内存消耗。

Javascript 相关文章推荐
从零开始学习jQuery (六) jquery中的AJAX使用
Feb 23 Javascript
jquery实现通用的内容渐显Tab选项卡效果
Sep 07 Javascript
jQuery为动态生成的select元素添加事件的方法
Aug 29 Javascript
微信小程序实现跟随菜单效果和循环嵌套加载数据
Nov 21 Javascript
Angular实现点击按钮控制隐藏和显示功能示例
Dec 29 Javascript
vue.js系列中的vue-fontawesome使用
Feb 10 Javascript
vue slot 在子组件中显示父组件传递的模板
Mar 02 Javascript
Javascript删除数组里的某个元素
Feb 28 Javascript
vue中filters 传入两个参数 / 使用两个filters的实现方法
Jul 15 Javascript
layui2.0使用table+laypage实现真分页
Jul 27 Javascript
javascript实现下拉菜单效果
Feb 09 Javascript
Vue接口封装的完整步骤记录
May 14 Vue.js
js数组常见操作及数组与字符串相互转化实例详解
Nov 10 #Javascript
浅谈javascript中replace()方法
Nov 10 #Javascript
使用jQuery获取data-的自定义属性
Nov 10 #Javascript
javascript适合移动端的日期时间拾取器
Nov 10 #Javascript
js图片轮播手动切换效果
Nov 10 #Javascript
JS截取与分割字符串常用技巧总结
Nov 10 #Javascript
jquery validate.js表单验证入门实例(附源码)
Nov 10 #Javascript
You might like
PHP之COOKIE支持详解
2010/09/20 PHP
PHP 数据结构 算法描述 冒泡排序 bubble sort
2011/07/10 PHP
PHP解码unicode编码的中文字符代码分享
2014/08/13 PHP
详谈PHP中public,private,protected,abstract等关键字的用法
2017/12/31 PHP
Laravel 使用查询构造器配合原生sql语句查询的例子
2019/10/12 PHP
用javascript实现给出的盒子的序列是否可连为一矩型
2007/08/30 Javascript
JavaScript处理解析JSON数据过程详解
2015/09/11 Javascript
基于Jquery和html5的7款个性化地图插件
2015/11/17 Javascript
详解JavaScript中数组和字符串的lastIndexOf()方法使用
2016/03/13 Javascript
Angularjs的Controller间通信机制实例分析
2016/11/07 Javascript
jquery自定义插件结合baiduTemplate.js实现异步刷新(附源码)
2016/12/22 Javascript
详解Node中导入模块require和import的区别
2017/08/11 Javascript
在Vuex使用dispatch和commit来调用mutations的区别详解
2018/09/18 Javascript
老生常谈JavaScript获取CSS样式的方法(兼容各浏览器)
2018/09/19 Javascript
js canvas实现写字动画效果
2018/11/30 Javascript
详解小程序设置缓存并且不覆盖原有数据
2019/04/15 Javascript
详解微信小程序获取当前时间及日期的方法
2019/04/28 Javascript
使用JQuery自动完成插件Auto Complete详解
2019/06/18 jQuery
JavaScript实现指定数量的并发限制的示例代码
2020/03/10 Javascript
[43:32]2014 DOTA2华西杯精英邀请赛 5 25 LGD VS NewBee第一场
2014/05/26 DOTA
Python抓取淘宝下拉框关键词的方法
2015/07/08 Python
Android模拟器无法启动,报错:Cannot set up guest memory ‘android_arm’ Invalid argument的解决方法
2016/07/01 Python
opencv python 傅里叶变换的使用
2018/07/21 Python
django框架基于模板 生成 excel(xls) 文件操作示例
2019/06/19 Python
Python TKinter如何自动关闭主窗口
2020/02/26 Python
keras-siamese用自己的数据集实现详解
2020/06/10 Python
Python3爬虫里关于代理的设置总结
2020/07/30 Python
Django集成MongoDB实现过程解析
2020/12/01 Python
CSS3 transforms应用于背景图像的解决方法
2019/04/16 HTML / CSS
大学生个人简历中的自我评价
2013/12/27 职场文书
家长通知书教师评语
2014/04/17 职场文书
热情服务标语
2014/10/07 职场文书
大学生助学金感谢信
2015/01/21 职场文书
Python 循环读取数据内存不足的解决方案
2021/05/25 Python
PyCharm 配置SSH和SFTP连接远程服务器
2022/05/11 Python
Win11 vmware不兼容怎么办?Win11与VMware虚拟机不兼容的解决方法
2023/01/09 数码科技