JavaScript单例模式详解:从基础到惰性加载的多种实现

作者:小括号

时间:2026-10-29

浏览:594

本文深入解析JavaScript中的单例设计模式,涵盖基本概念、对象字面量实现、基于闭包的私有成员保护以及惰性实例化技巧。通过日期帮助类等多个实战案例,展示如何优化代码组织、隐藏实现细节并提升性能,帮助开发者掌握这一常用设计模式的最佳实践。

文章目录

JavaScript单例模式概述

在JavaScript中,单例模式是一种基础且高频使用的设计模式,开发者在日常编码中可能已不知不觉地应用过它。本文将从理论基础出发,详细阐述单例模式的概念与实现方式,并通过具体案例演示其实际应用场景。

理论基础

概念

单例模式概念与基本结构

单例模式的核心在于确保系统中某个类仅存在一个实例,并便于外部访问。这种模式不仅便于控制实例数量,还能有效节约系统资源。若需限制某类对象只能存在一个实例,单例模式是最佳选择。

基本结构

最简单的单例实现形式为对象字面量,它将相关的属性和方法整合在一个对象中。


var singleton = {
  prop:"value",
  method:function(){
  }
}

这种形式的单例所有成员均为公开状态,可通过singleton直接访问。其缺点在于,若单例内部包含不希望暴露的辅助方法,一旦这些方法在后续维护中被删除,调用者可能会引发程序错误。如何避免此类问题呢?

包含私有成员的单例模式

包含私有成员的单例实现

在类中创建私有成员需借助闭包实现,关于闭包的具体知识此处不再展开,建议读者自行查阅相关资料。基本形式如下:


var singleton = (function () {
      var privateVar = "private";
      return {
        prop: "value",
        method: function () {
          console.log(privateVar);
        }
      }
    })();

该结构首先是一个自执行匿名函数,函数内部声明变量privateVar,并返回一个对象赋值给单例对象singleton。在匿名函数外部无法直接访问privateVar,它成为单例对象的私有变量,仅能在函数内部或通过暴露的方法访问。这种形式也被称为模块模式。

惰性实例化

惰性实例化技巧

无论是直接字面量还是包含私有成员的单例,均在脚本加载时立即创建实例。然而,若页面始终未使用该单例对象,将造成资源浪费。针对此情况,最佳方案是惰性加载,即仅在需要时才真正实例化单例对象。具体实现如下:


var singleton = (function () {
      function init() {
        var privateVar = "private";
        return {
          prop: "value",
          method: function () {
            console.log(privateVar);
          }
        }
      }
      var instance = null;
      return {
        getInstance: function () {
          if (!instance) {
            instance = init();
          }
          return instance;
        }
      }
    })();

首先将创建单例对象的代码封装至init函数中,并声明一个私有变量instance用于存储单例实例,同时公开一个getInstance方法以获取单例对象。调用时通过singleton.getInstance()进行,单例对象仅在调用该方法时才被真正创建。

适用场合

单例模式的适用场合

单例模式是JavaScript中最常用的设计模式之一。从增强模块性和代码组织性的角度来看,应尽可能使用单例模式。它可将相关代码聚合,便于维护;在大型项目中,每个模块的惰性加载可提升性能、隐藏实现细节并暴露常用API。常见的类库如underscore和jQuery均可视为单例模式的应用。

结合实战

单例模式实战案例

前文已述,单例模式是最常用的设计模式之一。以下通过一个简单日期帮助类的例子进行说明,该例子通过单例模式实现:

基本的单例模式结构


var dateTimeHelper = {
      now: function () {
        return new Date();
      },
      format: function (date) {
        return date.getFullYear() + "-" + (date.getMonth() + 1) + "-" + date.getDate();
      }
    }; 
console.log(dateTimeHelper.now());

这段代码通过对象字面量实现单例模式,使用时直接调用方法即可。

惰性加载实现单例模式


 var dateTimeHelper = (function () {
      function init() {
        return {
          now: function () {
            return new Date();
          },
          format: function (date) {
            return date.getFullYear() + "-" + (date.getMonth() + 1) + "-" + date.getDate();
          }
        }
      }
      var instance = null;
      return {
        getInstance: function () {
          if (!instance) {
            instance = init();
          }
          return instance;
        }
      }
    })(); 
console.log(dateTimeHelper.getInstance().now())

这就是惰性加载的单例模式。

多种单例实现方式对比

下面再来看几个实例:实现1:最简单的对象字面量


var singleton = {

    attr : 1,

    method : function(){ return this.attr; }

  }



var t1 = singleton ;

var t2 = singleton ;

显然,t1 === t2。

实现方式十分简单且实用,不足之处在于缺乏封装性,所有属性和方法均暴露在外。对于需要使用私有变量的场景,这种方式显得力不从心。此外,在处理this指向问题时也存在一定弊端。

实现2:构造函数内部判断

这种方式与最初的JS实现类似,区别在于将对类实例是否已存在的判断放入构造函数内部。


function Construct(){

  // 确保只有单例

  if( Construct.unique !== undefined ){

    return Construct.unique; 

  }

  // 其他代码

  this.name = "NYF";

  this.age="24";

  Construct.unique = this;

}



var t1 = new Construct() ;

var t2 = new Construct() ;

此时,t1 === t2。

实现同样简单,仅通过提取一个属性进行判断。但该方式缺乏安全性,若外部修改了Construct的unique属性,单例模式将被破坏。

实现3 : 闭包方式

对于灵活多变的JS而言,任何问题都能找到多种答案,关键在于权衡优劣。以下简述几种使用闭包实现单例模式的方法,核心思路是将已创建的单例进行缓存。


var single = (function(){

  var unique;

  function Construct(){

    // ... 生成单例的构造函数的代码

  }



  unique = new Constuct();



  return unique;

})();

只需每次执行var t1 = single; var t2 = single;即可。与对象字面量方式类似,但相对更安全,尽管并非绝对安全。

若希望以调用single()的方式使用,只需将内部的return改为


  return function(){

    return unique;

  } 

以上方式也可使用new关键字进行(形式上的尝试)。此处仅给出闭包的一种示例,也可在Construct中判断单例是否存在等。根据不同情况选择合适的实现方式即可。

总结

总结

单例模式的优势在于代码的组织能力,它将相关属性和方法封装在一个不会多次实例化的对象中,使代码维护和调试更加轻松。通过隐藏实现细节,可防止错误修改及全局命名空间污染。此外,惰性加载可提升性能,减少不必要的内存消耗。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章