JavaScript 单例模式核心实现与实战优化

许南星
许南星
发布时间:2026-10-13 | 2人浏览过
原创

单例模式是 JavaScript 中最常用的设计模式之一,旨在确保一个类仅有一个实例并提供全局访问点。本文详细讲解如何通过静态方法、代理类及惰性单例技术实现该模式,并结合函数缓存案例展示其在提升性能方面的实际应用。

单例模式的核心原理与应用场景

单例模式的定义是保证一个对象(或类)仅有一个实例,并提供一个访问它的全局访问点。其核心实现原理是利用 闭包 来保持对一个局部变量的引用,该变量用于保存首次创建的唯一的实例。由于限制了实例化次数,单例模式主要用于以下场景:

  • 全局缓存:管理应用级别的状态或数据,避免重复初始化开销。
  • 单一 UI 组件:如页面中只需要存在一个的登录浮窗、提示框或模态对话框。
  • 唯一资源管理器:如 WebSocket 连接、数据库连接池管理等。

基础实现:静态方法与代理类

针对非单例模式的普通类(如示例中的 Demo),我们可以通过两种经典方式将其改造为单例。

第1步:使用静态方法实现单例

目的:在原有类上直接添加获取唯一实例的方法,保持类结构简洁。 入口:Demo 类定义处。 动作:在 Demo 上挂载一个自执行函数返回的静态方法 getSingle,内部使用私有变量 demo 锁定实例。

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

使用对比:
- 非单例调用:每次 new 都会产生新对象,var a = new Demo('Peter')。
- 单例调用:多次调用 Demo.getSingle() 均返回同一实例,b1 === b2 结果为 true。

第2步:使用代理类实现单例

目的:通过独立的构造函数作为代理,不修改原类代码即可实现单例逻辑。 入口:定义新的构造函数 ProxyDemo。 动作:利用 IIFE(立即执行函数表达式)维护私有状态 demo,在构造函数体内判断是否已存在实例。

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

这种写法符合标准面向对象语法,调用方式为 var b = new ProxyDemo('Peter'),虽然语法上使用了 new,但实际只生成了一个底层实例。

进阶优化:通用惰性单例模式

上述基础方案需要为每个类编写特定的单例逻辑。在实际开发中(如管理多个不同类型的弹窗),我们更需要一个通用

第1步:构建通用惰性单例工厂

目的:抽象出创建单例的通用逻辑,解耦实例创建过程与行为逻辑。 入口:定义高阶函数 getSingle。 动作:传入一个创建对象的函数 foo,返回一个新的包装函数。内部利用闭包保存 single 变量,若为空则执行 foo 并缓存结果。

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

第2步:应用于登录浮窗场景

目的:演示如何在 DOM 操作中应用惰性单例,防止重复渲染和内存浪费。 入口:页面按钮 document.getElementById('lgBtn')。 动作:先定义创建登录层元素的函数 createLoginLayer,再包裹进 getSingle 生成 createSingleLoginLayer。

// 1. 定义具体的创建逻辑
var createLoginLayer = function(){
  var frag = document.createDocumentFragment();
  var div = document.createElement('div');
  div.style.display = 'none';
  // 此处省略其他子元素添加代码...
  document.body.appendChild(frag.appendChild(div));
  return div;
}

// 2. 生成惰性单例实例
var createSingleLoginLayer = getSingle(createLoginLayer);

// 3. 绑定事件调用
document.getElementById('lgBtn').onclick = function(){
  var lg = createSingleLoginLayer();
  lg.style.display = 'block';
};

当用户第一次点击时,DOM 被创建;后续点击将直接复用已有的 div 元素并调整显示样式,无需重新执行复杂的 DOM 构建操作。

实战拓展:结合缓存机制的性能优化

单例思想与缓存技术的结合在处理高复杂度计算时尤为有效。以斐波那契数列递归计算为例,普通的递归实现存在大量重复子问题计算,而引入类似单例的缓存机制可显著提升性能。

第1步:分析未优化的递归瓶颈

目的:展示纯递归在高数值下的性能缺陷。 参数:输入数值越大,冗余计算呈指数级增长。

// 未缓存版本:计算量极大,耗时严重
function foo(n){
  return n < 2 ? n : foo(n - 1) + foo(n - 2);
}
console.log(foo(40)); // 可能需要数秒甚至更久

第2步:实施基于闭包的缓存策略

目的:利用闭包持久化存储已计算结果,实现“查表法”加速。
动作:在外部定义 cache 对象或在 IIFE 内维护缓存字典,每次调用先检查是否存在结果。

// 方案A:全局缓存对象
var cache = {};
function fooWithCache(n){
  if(!cache[n]){
    cache[n] = n < 2 ? n : fooWithCache(n - 1) + fooWithCache(n - 2);
  }
  return cache[n];
}

// 方案B:更好的IIFE隔离写法
var optimizedFoo = (function(){
  var cache = {};
  return function(n){
    if(!cache[n]){
      cache[n] = n < 2 ? n : optimizedFoo(n - 1) + optimizedFoo(n - 2);
    }
    return cache[n];
  };
})();
console.log(optimizedFoo(100)); // 瞬间出结果

此处的 cache 起到了类似于单例中“唯一状态保存者”的作用,确保了不同调用间的数据共享与复用。

总结

JavaScript 单例模式不仅仅是一种限制实例数量的手段,更是一种管理资源和提升性能的编程思维。从基础的静态方法/代理类实现,到通用的惰性单例工厂,再到结合缓存机制的性能优化,开发者可以根据具体业务场景灵活选用。掌握这些技巧,能有效避免 DOM 重复渲染、内存泄漏及计算资源浪费,写出更健壮的客户端代码。

以上就是 JavaScript 单例模式核心实现与实战优化的详细内容,更多关于 JavaScript 设计模式与性能优化的资料请关注本站其它相关文章!

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表