深入理解JavaScript设计模式之单例模式

news2025/6/12 19:25:32

目录

    • 什么是单例模式
    • 为什么需要单例模式
      • 常见应用场景包括
    • 单例模式实现
      • 透明单例模式实现
      • 不透明单例模式
      • 用代理实现单例模式
      • javaScript中的单例模式
        • 使用命名空间
        • 使用闭包封装私有变量
      • 惰性单例
      • 通用的惰性单例
    • 结语

什么是单例模式

单例模式(Singleton Pattern)就像‘独生子女’,确保某个类在整个系统中只有一个实例,并提供一个全局访问点。简单来说,一个类一辈子只有一个孩子。在 JavaScript 编程世界中,例如 Web 应用中的登录窗口,无论你点击多少次登录按钮,只会弹出一次。

为什么需要单例模式

想象一下,你是DR钻戒的老板。我们推出一款特殊的钻戒——“单例钻戒”。这款钻戒一生只能购买一次,每位顾客终身仅能拥有一枚。这正是单例模式(Singleton Pattern)的生动写照。

常见应用场景包括

  1. 浏览器中的任务管理器或标签页管理:保证只有一个全局管理器在运行。
  2. 通知中心 / 消息中心:统一处理应用内的消息推送和响应。
  3. 侧边栏 / 弹窗组件:页面中始终只显示一个实例,避免重复创建。
  4. 全局配置文件管理:读取并维护一份全局配置,供整个应用使用。
  5. VueVuexReactContextRedux)和组件生命周期管理机制。

通过单例模式,我们可以有效地控制资源的使用,减少重复对象的创建,提升系统性能与稳定性。

单例模式实现

透明单例模式实现

      var Singleton = function (name) {
        this.name = name;
      };
      Singleton.prototype.getName = function () {
        alert(this.name);
      };
      Singleton.getInstance = (function () {
        var instance = null;
        return function (name) {
          if (!instance) {
            instance = new Singleton(name);
          }
          return instance;
        };
      })();
	var a = Singleton.getInstance( 'sven1' ); 
	var b = Singleton.getInstance( 'sven2' ); 
	alert ( a === b ); // true

深入理解JavaScript设计模式之单例模式
上面的代码中是一个简单实现单例模式的方式,通过调用Singleton.getInstance()来获取Singketon类的唯一实例,实现很简单,但是有一个缺点,降低了类的“透明性”
通常情况下,使用一个类的时候,习惯用new ClassName()的方式来新建一个对象,但是Singleton类并不是通过new直接创建实例的,而是通过Singleton.getInstance()这个静态方法来获取唯一的实力,这就必须要求使用这个类的人必须知道它是一个单例模式,并且要记住不能用new来创建对象,必须使用Singleton.getInstance()来创建对象,这种写法会让类的使用变得不够直观和不透明,增加了使用上的认知负担,很容易导致吴用。

不透明单例模式

 var CreateDiv = (function () {
        var instance;
        var CreateDiv = function (html) {
          if (instance) {
            return instance;
          }
          this.html = html;
          this.init();
          return (instance = this);
        };
        CreateDiv.prototype.init = function () {
          var div = document.createElement("div");
          div.innerHTML = this.html;
          document.body.appendChild(div);
        };
        return CreateDiv;
      })();
      var a = new CreateDiv("sven1");
      var b = new CreateDiv("sven2");
      console.log("a:", a);
      console.log("b:", b);
      console.log(a === b); 

深入理解JavaScript设计模式之单例模式
在上一步骤中,我们实现了一个不透明的单例模式,即用户必须通过 Singleton.getInstance() 的方式来获取实例,而不是使用常见的 new 方式创建对象。这种方式虽然实现了单例的效果,但不够直观,降低了类的“透明性”

为了改进这一点,我们可以尝试实现一个透明的单例模式。所谓“透明”,是指用户在使用时可以像使用普通类一样,直接通过 new CreateDiv() 的方式来创建实例,而无需关心这是一个单例类。
我们通过一个自执行的闭包函数来实现这个目标。在这个闭包中,我们将实例变量(如 instance)封装为“私有变量”,使其不会被外部轻易访问或修改,并且在整个页面不刷新的情况下,始终只存在一个实例。

例如,我们定义了一个 CreateDiv 类,它在被 new 调用时会检查是否已经存在实例,如果存在,则返回已有的实例,从而保证了全局唯一性。整个过程对使用者是透明的,使用方式和普通类完全一致,这样一来,既保留了单例的核心特性,又提升了使用的直观性和代码的可维护性。

虽然很流畅,很有单例的感觉,但是透明的单例模式还是有缺点的:

  1. 代码复杂度提高了:为了封装单例逻辑,使用了闭包和自执行函数,让代码变得不容易理解。
  2. 构造函数职责不单一:createDiv构造函数不仅要负责创建对象和初始化,还要控制只能有一个单例的逻辑,违反了"单一职责原则"
  3. 不容易扩散或者修改:如果我们还想把这个类从“只能创建一个实例”改为“可以创建多个实例”,就必须去修改构造函数内部的逻辑,非常不灵活,虽然这个写法让使用更自然(像普通类一样用 new 创建实例),但实现起来更复杂,维护成本也更高

用代理实现单例模式

var CreateDiv = function (html) {
    this.html = html;
    this.init();
  };
  CreateDiv.prototype.init = function () {
    var div = document.createElement("div");
    div.innerHTML = this.html;
    document.body.appendChild(div);
  };
  var ProxySingletonCreateDiv = (function () {
    var instance;
    return function (html) {
      if (!instance) {
        instance = new CreateDiv(html);
      }
      return instance;
    };
  })();
  var a = new ProxySingletonCreateDiv("sven1");
  var b = new ProxySingletonCreateDiv("sven2");
  console.log("a", a);
  console.log("b", b);
  console.log(a === b);

深入理解JavaScript设计模式之单例模式上面代码对透明单例模式的代理模式进行了优化,原来的CreateDiv类在构造函数中直接包含了控制单例的逻辑导致职责不清楚,不容易扩展,现在通过引入一个代理类ProxySingletonCreateDiv,将管理单例的逻辑从CreateDiv中抽离出去,让它回归为一个存粹用于创建div元素的普通类,具体来说,CreateDiv只负责创建对象和初始化操作,比如生成div并插入页面,单例的逻辑则有代理函数ProxySingletonCreateDiv来处理,这个代理函数是一个闭包,内部维护了一个唯一的实例,当第一次调用的时候会创建CreativeDiv实力,之后直接返回该实例,这种方法是缓存代理的一种典型应用,让CreateDiv更加的灵活,通用,同时又不丢失单例的特性。

javaScript中的单例模式

上面的集中单例模式实现方式偏向于传统的面向对象的写法,即通过"类"来创建唯一的对象,但是在javaScript中,这种做法也不完全适用,javaScriptes5中是一门无类(class-free)语言,创建对象非常简单,如果只需要一个唯一的对象,没必要先定义一个类再来创建实例,要知道单例模式的核心即可:“确保只有一个实例,并提供全局访问”,最然全局变量具备唯一全局访问的特性,但是它并不是真正的单例模式,而且存在命名冲突,全局作用域污染,在大型的项目中使用全局作用域尤其危险,为了减少全局变量污染带来的问题,可以采取使用命名空间和使用闭包封装私有变量解决。

使用命名空间

适当的使用命名空间,并不会杜绝全局变量,但是可以减少全局变量的数量,最简单的方法就是对象的字面量的方式解决:

var namespace1 = {
     a: function () {
       alert(1);
     },
     b: function () {
       alert(2);
     },
   };

ab都定义为 namespace1 的属性,这样可以减少变量和全局作用域打交道的机会。

使用闭包封装私有变量
  var user = (function () {
        var __name = "sven",
          __age = 29;
        return {
          getUserInfo: function () {
            return __name + "-" + __age;
          },
        };
      })();

将一些变量封装在闭包的内部,只暴露一些接口跟外界通信,用下划线来约定私用变量__name__age,它们被封装在闭包产生的作用域中,外部变量访问不到这两个变量,这就进行了全局的隔绝污染。

惰性单例

前面了解了单例模式的一些实现方法,接下来学习一下单例模式比较重要的一个知识"惰性单例【Lazy Singleton】",惰性单例指的是需要的时候才创建实例,而不是在页面加载的时候就提前创建,这种方式更节省资源,实际开发中,以弹窗为例,如果一开始就创建弹窗元素,用户可能根本不需要弹出窗口,这样回造成资源浪费,如果每次点击按钮都创建一个新的弹窗,虽然实现了按需展示,但却失去了单例的唯一性,正确的做法是首次点击创建弹窗,再次点击直接使用已经创建好的弹窗实例,这样就实现了惰性单例,即实现了只创建一次,又坐到了用的时候才创建,兼顾了功能与性能。

<html>
  <head>
    <style>
      .modal {
        position: fixed;
        width: 200px;
        height: 200px;
        line-height: 200px;
        text-align: center;
        left: 50%;
        top: 50%;
        transform: translate(-50%, -50%);
        background: goldenrod;
      }
    </style>
  </head>
  <body>
    <button id="open">打开</button>
    <button id="close">关闭</button>
    <script>
      const Moda = (function () {
        let instance = null;
        return function () {
          if (!instance) {
            instance = document.createElement("div");
            instance.innerHTML = "模态框";
            instance.className = "modal";
            instance.style.display = "none";
            document.body.appendChild(instance);
          }
          return instance;
        };
      })();
      document.getElementById("open").onclick = function () {
        // 创建一个模态框
        const modal = Moda();
        modal.style.display = "block";
      };
      document.getElementById("close").onclick = function () {
        // 删除模态框
        const modal = Moda();
        modal.style.display = "none";
      };
    </script>
  </body>
</html>

深入理解JavaScript设计模式之单例模式

通用的惰性单例

我们知道了什么是惰性单例和如何实现惰性单例,但是不难发现,普通的惰性单例还是违反了单一职责原则,创建对象和管理单例的逻辑都放在了Moda内部,如果我们下次需要创建页面唯一的iframe或者script或者更多类型的标签,就必须如法炮制创建更多的Moda函数,几乎照抄一遍,针对这种方法需要将不变的部分隔离出来,创建diviframeimg逻辑其实可以完全抽象出来【使用一个变量标志是否创建过,如果创建过则下次直接返回这个已经常见好的对象】。

return function () {
  if (!instance) {
     instance = document.createElement("div");
     ...    
   }
   return instance;
 };

接下来就把如何管理单例的逻辑抽离出来,这些逻辑封装在getSingle函数内部,创建对象的方法fn被当成参数动态传入getSingle函数。

var getSingle = function( fn ){ 
 var result; 
 return function(){ 
 return result || ( result = fn .apply(this, arguments ) ); 
 } 
}; 

接下来将用于创建登录浮窗的方法用参数 fn 的形式传入 getSingle,我们不仅可以传入Mode,还能传入 ModeScriptModeIframeModeXhr 等。之后再让getSingle返回一个新的函数,并且用一个变量 result 来保存fn 的计算结果。result 变量因为身在闭包中,它永远不会被销毁。在将来的请求,如果 result 已经被赋值,那么它将返回这个值。

<html>
  <head>
    <style>
      .modal {
        position: fixed;
        width: 200px;
        height: 200px;
        line-height: 200px;
        text-align: center;
        left: 50%;
        top: 50%;
        transform: translate(-50%, -50%);
        background: goldenrod;
      }
    </style>
  </head>
  <body>
    <button id="open">打开</button>
    <button id="close">关闭</button>
    <script>
      let getSingle = function (fn) {
        let result;
        return function () {
          return result || (result = fn.apply(this, arguments));
        };
      };
      let ModelDiv = function () {
        let div = document.createElement("div");
        div.innerHTML = "模态框";
        div.className = "modal";
        div.style.display = "none";
        document.body.appendChild(div);
        return div;
      };

      let ModelIframe = function () {
        let iframe = document.createElement("iframe");
        iframe.style.display = "none";
        document.body.appendChild(iframe);
        return iframe;
      };
      var createSingleLoginLayer = getSingle(ModelDiv);
      var createSingleLoginLayer2 = getSingle(ModelIframe);
      document.getElementById("open").onclick = function () {
        // 创建一个模态框
        const modal = createSingleLoginLayer();
        const ModelIframe1 = createSingleLoginLayer2();
        modal.style.display = "block";
        ModelIframe1.style.display = "block";
      };
      document.getElementById("close").onclick = function () {
        // 删除模态框
        const modal = createSingleLoginLayer();
        const ModelIframe2 = createSingleLoginLayer2();
        modal.style.display = "none";
        ModelIframe2.style.display = "none";
      };
    </script>
  </body>
</html>

深入理解JavaScript设计模式之单例模式

结语

设计模式不是“炫技”,而是"沉淀",希望我们通过阅读和学习《JavaScript设计模式》和实践中,在显示业务需求开发中写出更具有可维护性,可扩展性的代码。

致敬—— 《JavaScript设计模式》· 曾探

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2407762.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

STM32F4基本定时器使用和原理详解

STM32F4基本定时器使用和原理详解 前言如何确定定时器挂载在哪条时钟线上配置及使用方法参数配置PrescalerCounter ModeCounter Periodauto-reload preloadTrigger Event Selection 中断配置生成的代码及使用方法初始化代码基本定时器触发DCA或者ADC的代码讲解中断代码定时启动…

【机器视觉】单目测距——运动结构恢复

ps&#xff1a;图是随便找的&#xff0c;为了凑个封面 前言 在前面对光流法进行进一步改进&#xff0c;希望将2D光流推广至3D场景流时&#xff0c;发现2D转3D过程中存在尺度歧义问题&#xff0c;需要补全摄像头拍摄图像中缺失的深度信息&#xff0c;否则解空间不收敛&#xf…

蓝牙 BLE 扫描面试题大全(2):进阶面试题与实战演练

前文覆盖了 BLE 扫描的基础概念与经典问题蓝牙 BLE 扫描面试题大全(1)&#xff1a;从基础到实战的深度解析-CSDN博客&#xff0c;但实际面试中&#xff0c;企业更关注候选人对复杂场景的应对能力&#xff08;如多设备并发扫描、低功耗与高发现率的平衡&#xff09;和前沿技术的…

dedecms 织梦自定义表单留言增加ajax验证码功能

增加ajax功能模块&#xff0c;用户不点击提交按钮&#xff0c;只要输入框失去焦点&#xff0c;就会提前提示验证码是否正确。 一&#xff0c;模板上增加验证码 <input name"vdcode"id"vdcode" placeholder"请输入验证码" type"text&quo…

抖音增长新引擎:品融电商,一站式全案代运营领跑者

抖音增长新引擎&#xff1a;品融电商&#xff0c;一站式全案代运营领跑者 在抖音这个日活超7亿的流量汪洋中&#xff0c;品牌如何破浪前行&#xff1f;自建团队成本高、效果难控&#xff1b;碎片化运营又难成合力——这正是许多企业面临的增长困局。品融电商以「抖音全案代运营…

2.Vue编写一个app

1.src中重要的组成 1.1main.ts // 引入createApp用于创建应用 import { createApp } from "vue"; // 引用App根组件 import App from ./App.vue;createApp(App).mount(#app)1.2 App.vue 其中要写三种标签 <template> <!--html--> </template>…

全球首个30米分辨率湿地数据集(2000—2022)

数据简介 今天我们分享的数据是全球30米分辨率湿地数据集&#xff0c;包含8种湿地亚类&#xff0c;该数据以0.5X0.5的瓦片存储&#xff0c;我们整理了所有属于中国的瓦片名称与其对应省份&#xff0c;方便大家研究使用。 该数据集作为全球首个30米分辨率、覆盖2000–2022年时间…

定时器任务——若依源码分析

分析util包下面的工具类schedule utils&#xff1a; ScheduleUtils 是若依中用于与 Quartz 框架交互的工具类&#xff0c;封装了定时任务的 创建、更新、暂停、删除等核心逻辑。 createScheduleJob createScheduleJob 用于将任务注册到 Quartz&#xff0c;先构建任务的 JobD…

STM32标准库-DMA直接存储器存取

文章目录 一、DMA1.1简介1.2存储器映像1.3DMA框图1.4DMA基本结构1.5DMA请求1.6数据宽度与对齐1.7数据转运DMA1.8ADC扫描模式DMA 二、数据转运DMA2.1接线图2.2代码2.3相关API 一、DMA 1.1简介 DMA&#xff08;Direct Memory Access&#xff09;直接存储器存取 DMA可以提供外设…

376. Wiggle Subsequence

376. Wiggle Subsequence 代码 class Solution { public:int wiggleMaxLength(vector<int>& nums) {int n nums.size();int res 1;int prediff 0;int curdiff 0;for(int i 0;i < n-1;i){curdiff nums[i1] - nums[i];if( (prediff > 0 && curdif…

《用户共鸣指数(E)驱动品牌大模型种草:如何抢占大模型搜索结果情感高地》

在注意力分散、内容高度同质化的时代&#xff0c;情感连接已成为品牌破圈的关键通道。我们在服务大量品牌客户的过程中发现&#xff0c;消费者对内容的“有感”程度&#xff0c;正日益成为影响品牌传播效率与转化率的核心变量。在生成式AI驱动的内容生成与推荐环境中&#xff0…

学校招生小程序源码介绍

基于ThinkPHPFastAdminUniApp开发的学校招生小程序源码&#xff0c;专为学校招生场景量身打造&#xff0c;功能实用且操作便捷。 从技术架构来看&#xff0c;ThinkPHP提供稳定可靠的后台服务&#xff0c;FastAdmin加速开发流程&#xff0c;UniApp则保障小程序在多端有良好的兼…

家政维修平台实战20:权限设计

目录 1 获取工人信息2 搭建工人入口3 权限判断总结 目前我们已经搭建好了基础的用户体系&#xff0c;主要是分成几个表&#xff0c;用户表我们是记录用户的基础信息&#xff0c;包括手机、昵称、头像。而工人和员工各有各的表。那么就有一个问题&#xff0c;不同的角色&#xf…

最新SpringBoot+SpringCloud+Nacos微服务框架分享

文章目录 前言一、服务规划二、架构核心1.cloud的pom2.gateway的异常handler3.gateway的filter4、admin的pom5、admin的登录核心 三、code-helper分享总结 前言 最近有个活蛮赶的&#xff0c;根据Excel列的需求预估的工时直接打骨折&#xff0c;不要问我为什么&#xff0c;主要…

基于当前项目通过npm包形式暴露公共组件

1.package.sjon文件配置 其中xh-flowable就是暴露出去的npm包名 2.创建tpyes文件夹&#xff0c;并新增内容 3.创建package文件夹

MMaDA: Multimodal Large Diffusion Language Models

CODE &#xff1a; https://github.com/Gen-Verse/MMaDA Abstract 我们介绍了一种新型的多模态扩散基础模型MMaDA&#xff0c;它被设计用于在文本推理、多模态理解和文本到图像生成等不同领域实现卓越的性能。该方法的特点是三个关键创新:(i) MMaDA采用统一的扩散架构&#xf…

智能在线客服平台:数字化时代企业连接用户的 AI 中枢

随着互联网技术的飞速发展&#xff0c;消费者期望能够随时随地与企业进行交流。在线客服平台作为连接企业与客户的重要桥梁&#xff0c;不仅优化了客户体验&#xff0c;还提升了企业的服务效率和市场竞争力。本文将探讨在线客服平台的重要性、技术进展、实际应用&#xff0c;并…

【CSS position 属性】static、relative、fixed、absolute 、sticky详细介绍,多层嵌套定位示例

文章目录 ★ position 的五种类型及基本用法 ★ 一、position 属性概述 二、position 的五种类型详解(初学者版) 1. static(默认值) 2. relative(相对定位) 3. absolute(绝对定位) 4. fixed(固定定位) 5. sticky(粘性定位) 三、定位元素的层级关系(z-i…

[ICLR 2022]How Much Can CLIP Benefit Vision-and-Language Tasks?

论文网址&#xff1a;pdf 英文是纯手打的&#xff01;论文原文的summarizing and paraphrasing。可能会出现难以避免的拼写错误和语法错误&#xff0c;若有发现欢迎评论指正&#xff01;文章偏向于笔记&#xff0c;谨慎食用 目录 1. 心得 2. 论文逐段精读 2.1. Abstract 2…

转转集团旗下首家二手多品类循环仓店“超级转转”开业

6月9日&#xff0c;国内领先的循环经济企业转转集团旗下首家二手多品类循环仓店“超级转转”正式开业。 转转集团创始人兼CEO黄炜、转转循环时尚发起人朱珠、转转集团COO兼红布林CEO胡伟琨、王府井集团副总裁祝捷等出席了开业剪彩仪式。 据「TMT星球」了解&#xff0c;“超级…