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 相关文章推荐
javascript编程起步(第二课)
Jan 10 Javascript
基于jquery的模态div层弹出效果
Aug 21 Javascript
JavaScript中setUTCFullYear()方法的使用简介
Jun 12 Javascript
JQuery为元素添加样式的实现方法
Jul 20 Javascript
jQuery leonaScroll 1.1 自定义滚动条插件(推荐)
Sep 17 Javascript
jQuery操作之效果详解
May 19 jQuery
详解基于Node.js的微信JS-SDK后端接口实现代码
Jul 15 Javascript
利用jsonp与代理服务器方案解决跨域问题
Sep 14 Javascript
详解使用create-react-app添加css modules、sasss和antd
Jul 31 Javascript
egg.js的基本使用和调用数据库的方法示例
May 18 Javascript
js实现前端界面导航栏下拉列表
Aug 27 Javascript
Openlayers+EasyUI Tree动态实现图层控制
Sep 28 Javascript
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 socket的讲解与实例分析
2013/06/13 PHP
PHP中使用foreach()遍历二维数组的简单实例
2016/06/13 PHP
PHP的自定义模板引擎
2017/03/24 PHP
PHP递归统计系统中代码行数
2019/09/19 PHP
使用JavaScript动态设置样式实现代码(2)
2013/01/25 Javascript
探讨js中的双感叹号判断
2013/11/11 Javascript
我的Node.js学习之路(三)--node.js作用、回调、同步和异步代码 以及事件循环
2014/07/06 Javascript
JS简单实现多级Select联动菜单效果代码
2015/09/06 Javascript
js实现分割上传大文件
2016/03/09 Javascript
理解AngularJs篇:30分钟快速掌握AngularJs
2016/12/23 Javascript
浅谈js中的变量名和函数名重名
2017/02/13 Javascript
详解Vue-cli webpack移动端自动化构建rem问题
2018/04/07 Javascript
如何用Node写页面爬虫的工具集
2018/10/26 Javascript
vue-cli3 项目从搭建优化到docker部署的方法
2019/01/28 Javascript
js图数据结构处理 迪杰斯特拉算法代码实例
2019/09/11 Javascript
js实现简单的随机点名器
2020/09/17 Javascript
[00:36]DOTA2上海特级锦标赛 Archon战队宣传片
2016/03/04 DOTA
一个简单的python程序实例(通讯录)
2013/11/29 Python
python操作数据库之sqlite3打开数据库、删除、修改示例
2014/03/13 Python
python中使用pyhook实现键盘监控的例子
2014/07/18 Python
详解Python实现多进程异步事件驱动引擎
2017/08/25 Python
Python基于回溯法子集树模板解决最佳作业调度问题示例
2017/09/08 Python
python装饰器深入学习
2018/04/06 Python
如何使用Flask-Migrate拓展数据库表结构
2019/07/24 Python
Python-Flask:动态创建表的示例详解
2019/11/22 Python
Python实现剪刀石头布小游戏(与电脑对战)
2019/12/31 Python
Django模型中字段属性choice使用说明
2020/03/30 Python
解决windows下python3使用multiprocessing.Pool出现的问题
2020/04/08 Python
Python实现http接口自动化测试的示例代码
2020/10/09 Python
美国购物网站:Clickhere2shop
2021/01/28 全球购物
商务英语应届生自我鉴定
2013/12/08 职场文书
2014基层党员干部学习全国两会心得体会
2014/03/17 职场文书
物流管理系毕业生求职信
2014/06/03 职场文书
职务任命书范本
2014/06/05 职场文书
2016学习雷锋精神活动倡议书
2015/04/27 职场文书
2019预备党员转正申请书模板2篇!
2019/08/07 职场文书