JavaScript单例模式:实现原理与惰性单例详解

作者:青柠时间:2026-10-30 12:57:43浏览:208

本文详解JavaScript单例模式的实现原理,通过闭包确保类仅有一个实例并提供全局访问点。介绍两种实现方式:静态方法直接调用与代理类封装。重点讲解惰性单例,仅在需要时创建实例,并结合缓存函数计算结果(如斐波那契数列)展示性能优化,适用于全局缓存和登录浮窗等场景。

单例模式定义与原理

单例模式的核心定义是确保一个类或对象在整个系统中仅存在一个实例,并提供一个全局访问点。其实现原理依赖于闭包机制,通过闭包维持对局部变量的引用,该变量用于存储首次创建的唯一实例。这种模式主要应用于需要全局唯一实例的场景,例如全局缓存管理或登录浮窗组件。

静态方法实现单例

对于非单例模式的普通对象(如Demo类),可以通过添加静态方法来实现单例模式。具体做法是在Demo类上定义一个静态方法,该方法负责管理实例的创建与返回。


 Demo.getSingle = (function(){
  var demo = null;
  return function(name){
    if(!demo){
      demo = new Demo(name);
    }
    return demo;
  }
})(); 

使用方式上,非单例模式通常通过构造函数直接实例化,例如var a = new Demo('Peter')。而在单例模式下,则通过特定的静态方法获取实例,确保每次调用都返回同一个对象引用。


 var b1 = Demo.getSingle('Peter');
var b2 = Demo.getSingle('Sufei');
b1 === b2;//true,都引用的是new Demo('Peter') 

代理类实现单例

另一种实现单例模式的方式是使用代理类。通过创建一个代理构造函数ProxyDemo,内部封装了实例创建逻辑,从而控制Demo类的实例化过程。


 var ProxyDemo = (function(){
  var demo = null;
  return function(name){
    if(!demo){
      demo = new Demo(name);
    }
    return demo;
  }
})(); 

在代理类模式下,非单例模式依然使用var a = new Demo('Peter')直接创建实例。而单例模式则通过实例化代理类来实现,例如var b = new ProxyDemo('Peter'),代理类内部会确保只生成一个Demo实例。

惰性单例与性能优化

惰性单例模式是一种优化策略,它只在真正需要实例时才进行创建,从而节省资源。通用惰性单例的创建方法通常涉及一个高阶函数,该函数接收一个创建实例的函数作为参数,并返回一个单例访问器。


 var getSingle = function(foo){
  var single = null;
  return function(){
    return single || (single = foo.apply(this,arguments));
  }
}; 

惰性单例的典型用法是定义一个创建层(如createLoginLayer)的函数,然后将其传递给getSingle函数。getSingle返回的新函数在首次调用时执行创建逻辑,后续调用直接返回已创建的实例,避免重复创建。


 var createLoginLayer = function(){
  var frag = document.createDocumentFragment();
  var div = document.createElement('div');
  div.style.display = 'none';
  //以下给div添加其它登录元素
  ...
  document.body.appendChild(frag.appendChild(div));
  return div;
}
var createSingleLoginLayer = getSingle(createLoginLayer);
//当用户第一次点击按钮(id = 'lgBtn')时,来创建并显示登录窗口,之后重复点击按钮不会重复创建;
document.getElementById('lgBtn').onclick = function(){
  var lg = createSingleLoginLayer();
  lg.style.display = 'block';
} 

为了展示性能差异,以下代码展示了不缓存计算结果的情况。函数foo递归计算数列,当n较大时(如40),由于重复计算,执行时间显著增加,耗时数秒。


 function foo(n){
  results = n < 2 ? n : foo(n - 1) + foo(n - 2);
  return results;
}
console.log(foo(40));//得计算好几秒 

引入缓存机制后,性能得到极大提升。通过对象cache存储已计算的结果,在递归前检查缓存,若存在则直接返回,否则计算并存储。这样即使n=100,也能瞬间得出结果。


 var cache = {};
function foo(n){
  if(!cache[n]){
    cache[n] = n < 2 ? n : foo(n - 1) + foo(n - 2);
  }
  return cache[n];
}
console.log(foo(100));

更优雅的写法是将缓存逻辑封装在闭包中。利用立即执行函数返回一个内部函数,该函数持有cache变量。这样既实现了缓存功能,又保持了接口的简洁,调用foo(100)即可快速返回结果。


 var foo = (function(){
  var cache = {};
  return function(n){
    if(!cache[n]){
      cache[n] = n < 2 ? n : foo(n - 1) + foo(n - 2);
    }
    return cache[n];
  };
})();
console.log(foo(100)); 

本文内容参考了《JavaScript模式》与《JavaScript设计模式与开发实践》两部著作,旨在帮助开发者深入理解并应用单例模式。

希望本文对您的学习有所帮助,欢迎继续支持本站获取更多技术内容。

作者最新文章