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 相关文章推荐
怎么清空javascript数组
May 11 Javascript
JavaScript判断变量是否为undefined的两种写法区别
Dec 04 Javascript
用jQuery toggleClass 实现鼠标移上变色
May 14 Javascript
一个仿糯米弹框效果demo
Jul 22 Javascript
Javascript实现获取及设置光标位置的方法
Jul 21 Javascript
基于Jquery实现焦点图淡出淡入效果
Nov 30 Javascript
Vuejs仿网易云音乐实现听歌及搜索功能
Mar 30 Javascript
浅谈Angular 中何时取消订阅
Nov 22 Javascript
浅谈Postman解决token传参的问题
Mar 31 Javascript
小程序绑定用户方案优化小结
May 15 Javascript
vue+mock.js实现前后端分离
Jul 24 Javascript
jQuery HTML设置内容和属性操作实例分析
May 20 jQuery
使用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 Directory 函数的详解
2013/03/07 PHP
php生成静态html页面的方法(2种方法)
2015/09/14 PHP
解决thinkPHP 5 nginx 部署时,只跳转首页的问题
2019/10/16 PHP
YII2框架中查询生成器Query()的使用方法示例
2020/03/18 PHP
PHP设计模式(四)原型模式Prototype实例详解【创建型】
2020/05/02 PHP
JavaScript面向对象程序设计三 原型模式(上)
2011/12/21 Javascript
html超链接打开窗口大小的方法
2013/03/05 Javascript
Extjs中ComboBoxTree实现的下拉框树效果(自写)
2013/05/28 Javascript
js计算两个时间之间天数差的实例代码
2013/11/19 Javascript
JavaScript判断字符长度、数字、Email、电话等常用判断函数分享
2015/04/01 Javascript
javascript实现漂亮的拖动层,窗口拖拽特效
2015/04/24 Javascript
基于javascript制作微信聊天面板
2020/08/09 Javascript
js+html5实现canvas绘制网页时钟的方法
2016/05/21 Javascript
AngularJS 依赖注入详解和简单实例
2016/07/28 Javascript
js数组常用操作方法小结(增加,删除,合并,分割等)
2016/08/02 Javascript
让编辑器支持word复制黏贴、截屏的js代码
2016/10/17 Javascript
JavaScript实现事件的中断传播和行为阻止方法示例
2017/01/20 Javascript
原生JS实现图片网格式渐显、渐隐效果
2017/06/05 Javascript
Vue项目webpack打包部署到服务器的实例详解
2017/07/17 Javascript
node中koa中间件机制详解
2017/08/22 Javascript
js 将canvas生成图片保存,或直接保存一张图片的实现方法
2018/01/02 Javascript
基于element-ui的rules中正则表达式
2018/09/04 Javascript
vue-cli打包后本地运行dist文件中的index.html操作
2020/08/12 Javascript
解决await在forEach中不起作用的问题
2021/02/25 Javascript
Python 开发Activex组件方法
2009/11/08 Python
用实例说明python的*args和**kwargs用法
2013/11/01 Python
浅谈pyqt5在QMainWindow中布局的问题
2019/06/21 Python
Python模块/包/库安装的六种方法及区别
2020/02/24 Python
python实现批量修改文件名
2020/03/23 Python
Python Json数据文件操作原理解析
2020/05/09 Python
keras中的loss、optimizer、metrics用法
2020/06/15 Python
荷兰睡眠专家:Beter Bed
2020/11/23 全球购物
经典c++面试题三
2015/07/08 面试题
授权委托书样本及填写说明
2014/09/19 职场文书
汽车转让协议书范本
2014/12/07 职场文书
财产分割协议书
2016/03/22 职场文书