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 相关文章推荐
URL地址中的#符号使用说明
Feb 12 Javascript
使用基于jquery的gamequery插件做JS乒乓球游戏
Jul 31 Javascript
用javascript作一个通用向导说明
Aug 30 Javascript
20款非常优秀的 jQuery 工具提示插件 推荐
Jul 15 Javascript
JavaScript 判断浏览器是否支持SVG的代码
Mar 21 Javascript
js计算时间差代码【包括计算,天,时,分,秒】
Apr 26 Javascript
js HTML5多图片上传及预览实例解析(不含前端的文件分割)
Aug 26 Javascript
js监听input输入框值的实时变化实例
Jan 26 Javascript
AngularJS学习笔记之表单验证功能实例详解
Jul 06 Javascript
Vue 2.0入门基础知识之内部指令详解
Oct 15 Javascript
vue环形进度条组件实例应用
Oct 10 Javascript
JS实现数组删除指定元素功能示例
Jun 05 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
SWFUpload与CI不能正确上传识别文件MIME类型解决方法分享
2011/04/18 PHP
php中存储用户ID和密码到mysql数据库的方法
2013/02/06 PHP
解析php安全性问题中的:Null 字符问题
2013/06/21 PHP
ThinkPHP实现支付宝接口功能实例
2014/12/02 PHP
Yii实现微信公众号场景二维码的方法实例
2020/08/30 PHP
jQuery textarea的长度进行验证
2009/05/06 Javascript
NodeJS 模块开发及发布详解分享
2012/03/07 NodeJs
js 实现菜单左右滚动显示示例介绍
2013/11/21 Javascript
JS转换HTML转义符的方法
2016/08/24 Javascript
扩展jquery easyui tree的搜索树节点方法(推荐)
2016/10/28 Javascript
node vue项目开发之前后端分离实战记录
2017/12/13 Javascript
vue2 mint-ui loadmore实现下拉刷新,上拉更多功能
2018/03/21 Javascript
JavaScript实现的文本框placeholder提示文字功能示例
2018/07/25 Javascript
vue服务端渲染添加缓存的方法
2018/09/18 Javascript
基于layui实现高级搜索(筛选)功能
2019/07/26 Javascript
使用layui+ajax实现简单的菜单权限管理及排序的方法
2019/09/10 Javascript
vue实现文字加密功能
2019/09/27 Javascript
基于Vue el-autocomplete 实现类似百度搜索框功能
2019/10/25 Javascript
[55:47]DOTA2上海特级锦标赛C组小组赛#2 LGD VS Newbee第三局
2016/02/27 DOTA
[04:40]2016国际邀请赛中国区预选赛全程TOP10镜头集锦
2016/07/01 DOTA
python如何对实例属性进行类型检查
2018/03/20 Python
python之django母板页面的使用
2018/07/03 Python
windows下python安装小白入门教程
2018/09/18 Python
Python命名空间的本质和加载顺序
2018/12/17 Python
python获取本机所有IP地址的方法
2018/12/26 Python
python 将对象设置为可迭代的两种实现方法
2019/01/21 Python
Window版下在Jupyter中编写TensorFlow的环境搭建
2020/04/10 Python
详解pandas获取Dataframe元素值的几种方法
2020/06/14 Python
python实现图片素描效果
2020/09/26 Python
HTML5在微信内置浏览器下右上角菜单的调整字体导致页面显示错乱的问题
2021/01/19 HTML / CSS
Boston Proper官网:美国女装品牌
2017/10/30 全球购物
Java中采用什么结构来捕获、处理异常?各子句的顺序、功能如何
2013/10/07 面试题
实习评语
2013/12/16 职场文书
个人维稳承诺书
2015/05/04 职场文书
用Python将GIF动图分解成多张静态图片
2021/06/11 Python
基于python定位棋子位置及识别棋子颜色
2021/07/26 Python