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 相关文章推荐
弹出广告特效代码(一个IP只弹出一次)
May 11 Javascript
多浏览器兼容性比较好的复制到剪贴板的js代码
Oct 09 Javascript
jquery星级插件、支持页面中多次使用
Mar 25 Javascript
jquery $.each() 使用小探
Aug 23 Javascript
node.js中的http.createClient方法使用说明
Dec 15 Javascript
js实现网页抽奖实例
Aug 05 Javascript
JS中对象与字符串的互相转换详解
May 20 Javascript
怎么引入(调用)一个JS文件
May 26 Javascript
js中let和var定义变量的区别
Feb 08 Javascript
django使用channels2.x实现实时通讯
Nov 28 Javascript
微信小程序列表时间戳转换实现过程解析
Oct 12 Javascript
解决vue-router路由拦截造成死循环问题
Aug 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
php中的字符编码转换函数用法示例
2014/10/20 PHP
详解PHP对数组的定义以及数组的创建方法
2015/11/27 PHP
PHPExcel实现表格导出功能示例【带有多个工作sheet】
2018/06/13 PHP
在textarea文本域中显示HTML代码的方法
2007/03/06 Javascript
js 实现打印网页中定义的部分内容的代码
2010/04/01 Javascript
关于Jqzoom的使用心得 jquery放大镜效果插件
2010/04/12 Javascript
js动态创建上传表单通过iframe模拟Ajax实现无刷新
2014/02/20 Javascript
jquery中子元素和后代元素的区别示例介绍
2014/04/02 Javascript
jQuery实现简单网页遮罩层/弹出层效果兼容IE6、IE7
2014/06/16 Javascript
js和jquery中循环的退出和继续学习记录
2014/09/06 Javascript
使用javascript实现简单的选项卡切换
2015/01/09 Javascript
js判断手机号运营商的方法
2015/10/23 Javascript
ionic js 模型 $ionicModal 可以遮住用户主界面的内容框
2016/06/06 Javascript
react开发教程之React 组件之间的通信方式
2017/08/12 Javascript
layui lay-verify form表单自定义验证规则详解
2019/09/18 Javascript
vue页面加载时的进度条功能(实例代码)
2020/01/13 Javascript
详解Vue数据驱动原理
2020/11/17 Javascript
Python实现从url中提取域名的几种方法
2014/09/26 Python
python安装PIL模块时Unable to find vcvarsall.bat错误的解决方法
2016/09/19 Python
Python实现二维数组输出为图片
2018/04/03 Python
python3 pandas 读取MySQL数据和插入的实例
2018/04/20 Python
python cumsum函数的具体使用
2019/07/29 Python
Python内置类型性能分析过程实例
2020/01/29 Python
Python读取JSON数据操作实例解析
2020/05/18 Python
Python 使用生成器代替线程的方法
2020/08/04 Python
手对手的教你用canvas画一个简单的海报的方法示例
2018/12/10 HTML / CSS
SHEIN香港:价格实惠的女性时尚服装
2018/08/14 全球购物
Redbubble法国:由独立艺术家设计的独特产品
2019/01/08 全球购物
JD Sports丹麦:英国领先的运动时尚零售商
2020/11/24 全球购物
医学护理毕业生自荐信
2013/11/07 职场文书
银行出纳岗位职责
2013/11/25 职场文书
关于清明节的演讲稿
2014/09/13 职场文书
2015年班组长工作总结
2015/04/10 职场文书
莫言获奖感言(全文)
2015/07/31 职场文书
前端学习——JavaScript原生实现购物车案例
2021/03/31 Javascript
详解Go语言Slice作为函数参数的使用
2021/07/02 Golang