JavaScript实现设计模式中的单例模式的一些技巧总结


Posted in Javascript onMay 17, 2016

一、使用全局变量保存单例

这是最简单的实现方法

function Person(){ 
  this.createTime=new Date(); 
} 

var instance=new Person(); 
function getInstance(){ 
  return instance; 
}

加载该js时就创建一个Person对象,保存到instance全局变量中,每次使用都取这个对象。如果一次都没使用,那么创建的这个对象则浪费了,我们可以优化一下,

var instance 
function getInstance(){ 
  if(!instance){ 
    instance=new Person(); 
  } 
  return instance; 
}

这样,第一次使用时才创建对象。
这个方法的缺点是,instance是全局的变量,在多人合作或者开发周期比较长的情况下,很难保证instance不会被其它代码修改或覆盖,很可能到调用的时候,发现instance根本就不是Person对象。
我们考虑下使用闭包来封装起instance,使它不再是全局变量就可以解决这个问题了

二、闭包创建对象

var getInstance(){ 
var instance; 
return function(){ 
    if(!instance){ 
      instance=new Person(); 
    } 
    return instance; 
  } 
}();

这样,instance就被封装起来了,不用担心被修改了。
现在通过getInstance()函数可以获得单例了。新的问题,如果我通过new Person()来创建对象,获得的还是多个对象,javascript又不可以像java一样把构造器私有化。那怎么样可以让多次new出来的对象都是一个实例呢?

三、构造函数的静态属性缓存实例

先看代码

function Person(){ 
  //如果已经缓存了实例,则直接返回缓存的实例 
  if(typeof Person.instance==='object'){ 
    return Person.instance; 
  } 
  this.createTime=new Date(); 
  //缓存实例 
  Person.instance=this; 
  return this; 
}

从代码可以看到,第一次new时,if的条件返回false,会往下走,初始化对象,然后保存对象到Person.instance这个静态属性中。
第二次new 时,if的条件返回true,直接返回Person.instance,不会再往下运行初始化的代码。所以不管new几次,返回的都是第一次创建的对象。

这个方法的缺点和方法一的缺点一样,Person.instance也是公开属性,有可能会被修改。

我们参考方法二,使用闭包来封装一个,也许就能解决该问题了

四、重写构造函数

这个方法要使用闭包,但不能像方法二那么简单,我们需要重写构造函数。

function Person(){ 
  //缓存实例 
  var instance=this; 
  this.createTime=new Date(); 
  //重写构造函数 
  Person=function(){ 
    return instance; 
  } 
}

第一次new 时,调用原始构造函数先缓存该实例,然后再初始化,同时,重写该构造函数。以后再new 时,永远调用不到原始的构造函数了,只能调用到重写后的构造函数,而这个函数总是返回缓存的instance.
上面的方法似乎没什么问题,但通过下面的测试,可以发现问题

//向原型添加属性 
Person.prototype.prop1=true; 
var p1=new Person(); 
//在创建初始化对象后,再次向该原型添加属性 
Person.prototype.prop2=true; 
var p2=new Person(); 

//开始测试 
console.log(p1.prop1);//结果为true 
console.log(p2.prop1);//结果为true 

console.log(p1.prop2);//结果为undefined 
console.log(p2.prop2);//结果为undefined 

console.log(p1.constructor===Person);//结果为false 
console.log(p2.constructor===Person);//结果为false

我们预期中的结果,应该是全都是true。
分析一下上述测试代码

Person.prototype.prop1=true;是在原始构造函数的原型下增加了prop1这个属性,并赋值

而在执行 var p1=new Person();之后,Person这个构造函数已经被重写了

所以Person.prototype.prop2=true;是在新的原型下增加prop2这个属性

var p2=new Person(); p2和p1实际上是同一个对象,即原始构造函数创建的对象

所以p1 p2都有prop1这个属性,而没有prop2这个属性

同样的,p1 p2的constructor指向的也是原始的构造函数,而Person此时已不是原来那个函数了

为了能按预期的结果那样运行,可以通过一些修改来实现

function Person(){ 
  //缓存实例 
  var instance=this; 
  //重写构造函数 
  Person=function(){ 
    return instance; 
  } 
  //保留原型属性 
  Person.prototype=this; 
  //实例 
  instance=new Person(); 
  //重置构造函数引用 
  instance.constructor=Person; 

  //其他初始化 
  instance.createTime=new Date(); 
 
  return instance; 
}

再运行前面的测试代码,结果都是true了。

五、惰性加载:
在大型或复杂的项目中,起到了优化的作用:那些开销较大却很少用到的组件可以被包装到惰性加载单例中,示例程序:

/* Singleton with Private Members, step 3. */

MyNamespace.Singleton = (function() {
 // Private members.
 var privateAttribute1 = false;
 var privateAttribute2 = [1, 2, 3];

 function privateMethod1() {
  ...
 }
 function privateMethod2(args) {
  ...
 }

 return { // Public members.
  publicAttribute1: true,
  publicAttribute2: 10,

  publicMethod1: function() {
   ...
  },
  publicMethod2: function(args) {
   ...
  }
 };
})();

/* General skeleton for a lazy loading singleton, step 1. */

MyNamespace.Singleton = (function() {

 function constructor() { // All of the normal singleton code goes here.
  // Private members.
  var privateAttribute1 = false;
  var privateAttribute2 = [1, 2, 3];

  function privateMethod1() {
   ...
  }
  function privateMethod2(args) {
   ...
  }

  return { // Public members.
   publicAttribute1: true,
   publicAttribute2: 10,

   publicMethod1: function() {
    ...
   },
   publicMethod2: function(args) {
    ...
   }
  }
 }

})();

/* General skeleton for a lazy loading singleton, step 2. */

MyNamespace.Singleton = (function() {

 function constructor() { // All of the normal singleton code goes here.
  ...
 }

 return {
  getInstance: function() {
   // Control code goes here.
  }
 }
})();

/* General skeleton for a lazy loading singleton, step 3. */

MyNamespace.Singleton = (function() {

 var uniqueInstance; // Private attribute that holds the single instance.

 function constructor() { // All of the normal singleton code goes here.
  ...
 }

 return {
  getInstance: function() {
   if(!uniqueInstance) { // Instantiate only if the instance doesn't exist.
    uniqueInstance = constructor();
   }
   return uniqueInstance;
  }
 }
})();

六、使用分支单例:
针对特定环境的代码可以被包装到分支型单例中,示例程序:

/* SimpleXhrFactory singleton, step 1. */

var SimpleXhrFactory = (function() {

 // The three branches.
 var standard = {
  createXhrObject: function() {
   return new XMLHttpRequest();
  }
 };
 var activeXNew = {
  createXhrObject: function() {
   return new ActiveXObject('Msxml2.XMLHTTP');
  }
 };
 var activeXOld = {
  createXhrObject: function() {
   return new ActiveXObject('Microsoft.XMLHTTP');
  }
 };

})();

/* SimpleXhrFactory singleton, step 2. */

var SimpleXhrFactory = (function() {

 // The three branches.
 var standard = {
  createXhrObject: function() {
   return new XMLHttpRequest();
  }
 };
 var activeXNew = {
  createXhrObject: function() {
   return new ActiveXObject('Msxml2.XMLHTTP');
  }
 };
 var activeXOld = {
  createXhrObject: function() {
   return new ActiveXObject('Microsoft.XMLHTTP');
  }
 };

 // To assign the branch, try each method; return whatever doesn't fail.
 var testObject;
 try {
  testObject = standard.createXhrObject();
  return standard; // Return this if no error was thrown.
 }
 catch(e) {
  try {
   testObject = activeXNew.createXhrObject();
   return activeXNew; // Return this if no error was thrown.
  }
  catch(e) {
   try {
    testObject = activeXOld.createXhrObject();
    return activeXOld; // Return this if no error was thrown.
   }
   catch(e) {
    throw new Error('No XHR object found in this environment.');
   }
  }
 }

})();
Javascript 相关文章推荐
[原创]网络复制内容时常用的正则+editplus
Nov 30 Javascript
Js 获取HTML DOM节点元素的方法小结
Apr 24 Javascript
几个常用的JavaScript字符串处理函数 - split()、join()、substring()和indexOf()
Jun 02 Javascript
关于js new Date() 出现NaN 的分析
Oct 23 Javascript
form.submit()不能提交表单的原因分析
Oct 23 Javascript
小心!AngularJS结合RequireJS做文件合并压缩的那些坑
Jan 09 Javascript
浅析AngularJS中的指令
Mar 20 Javascript
通过bootstrap全面学习less
Nov 09 Javascript
JavaScript中数组常见操作技巧
Sep 01 Javascript
vue 设置路由的登录权限的方法
Jul 03 Javascript
小程序实现抽奖动画
Apr 16 Javascript
JS图片预加载三种实现方法解析
May 08 Javascript
使用Promise解决多层异步调用的简单学习心得
May 17 #Javascript
js字符串截取函数slice、substring和substr的比较
May 17 #Javascript
javascript Promise简单学习使用方法小结
May 17 #Javascript
关于安卓手机微信浏览器中使用XMLHttpRequest 2上传图片显示字节数为0的解决办法
May 17 #Javascript
Web前端新人笔记之jquery入门心得(新手必看)
May 17 #Javascript
Angularjs中的事件广播 —全面解析$broadcast,$emit,$on
May 17 #Javascript
iScroll.js 使用方法参考
May 16 #Javascript
You might like
PHP 字符截取 解决中文的截取问题,不用mb系列
2009/09/29 PHP
php网站地图生成类示例
2014/01/13 PHP
仿dedecms下拉分页样式修改的thinkphp分页类实例
2014/10/30 PHP
在Mac OS的PHP环境下安装配置MemCache的全过程解析
2016/02/15 PHP
php封装的pdo数据库操作工具类与用法示例
2019/05/08 PHP
选择TreeView控件的树状数据节点的JS方法(jquery)
2010/02/06 Javascript
javascript动态判断html元素并执行不同的操作
2014/06/16 Javascript
jQuery自带的一些常用方法总结
2014/09/03 Javascript
node.js中的fs.readSync方法使用说明
2014/12/17 Javascript
jQuery使用hide方法隐藏元素自身用法实例
2015/03/30 Javascript
Node.js学习入门
2017/01/03 Javascript
jQuery异步提交表单实例
2017/05/30 jQuery
解决webpack打包速度慢的解决办法汇总
2017/07/06 Javascript
[01:06:18]DOTA2-DPC中国联赛 正赛 Phoenix vs Dynasty BO3 第二场 1月26日
2021/03/11 DOTA
python字典排序实例详解
2015/05/20 Python
python 获取网页编码方式实现代码
2017/03/11 Python
python3学习笔记之多进程分布式小例子
2018/02/13 Python
用python3 返回鼠标位置的实现方法(带界面)
2019/07/05 Python
使用matplotlib绘制图例标签中带有公式的图
2019/12/13 Python
Python bisect模块原理及常见实例
2020/06/17 Python
Python configparser模块封装及构造配置文件
2020/08/07 Python
用Python自动清理电脑内重复文件,只要10行代码(自动脚本)
2021/01/09 Python
Canvas制作的下雨动画的示例
2018/03/06 HTML / CSS
英国的屈臣氏:Boots博姿
2017/12/23 全球购物
荷兰超市:DEEN
2018/03/14 全球购物
美国折扣地毯销售网站:Rugs.com
2020/03/27 全球购物
如何转换一个字符串到enum值
2014/04/12 面试题
Unix如何在一行中运行多个命令
2015/05/29 面试题
运动会广播稿100字
2014/01/11 职场文书
一年级评语大全
2014/04/23 职场文书
语文教育专业求职信
2014/06/28 职场文书
银行反四风对照检查材料
2014/09/29 职场文书
加强作风建设心得体会
2014/10/22 职场文书
党的群众路线教育实践活动整改落实情况自查报告
2014/10/28 职场文书
一篇文章带你深入了解Mysql触发器
2021/08/02 MySQL
详解redis在微服务领域的贡献
2021/10/16 Redis