JavaScript单例模式

JavaScript单例模式

  • [1 什么是单例模式](#1 什么是单例模式)
  • [2 实现一个基础的单例模式](#2 实现一个基础的单例模式)
  • [3 透明的单例模式](#3 透明的单例模式)
  • [4 用代理实现单例模式](#4 用代理实现单例模式)
  • [5 JavaScript 中的单例模式](#5 JavaScript 中的单例模式)
  • [6 惰性单例](#6 惰性单例)

1 什么是单例模式

保证一个类只有一个实例,并提供一个访问它的全局访问点,这就是单例模式。

单例模式是一种常用的模式,有一些对象我们往往只需要一个,比如window对象、全局缓存等。

2 实现一个基础的单例模式

根据单例模式的特点,在实现单例模式时,我们要保证某个类只能创建一个实例对象,那么实现的基本思路就是:用一个变量标识当前是都已经为某个类创建过对象,如果是,在下一次获取该类的实例时,直接返回之前创建的对象,否则创建一个新对象,示例代码如下:

javascript 复制代码
// 定义一个构造函数MyExample,定义属性name与instance
function MyExample(name) {
  this.name = name;
  this.instance = null;
}
// 在原型上定义getName方法
MyExample.prototype.getName = function () {
  console.log(this.name);
};
// 为构造函数定义getInstance方法,用来获取类的实例
MyExample.getInstance = function (name) {
  // 标识是否创建了实例,如果没有,赋值为新的实例,如果已经创建,返回原来的实例
  if (!this.instance) {
    this.instance = new MyExample(name);
  }
  return this.instance;
};

// 为MyExample类创建实例,看似创建两个实例,实际上都是同一个实例
var e1 = MyExample.getInstance("example1");
var e2 = MyExample.getInstance("example2");

console.log(e1 === e2); // true

我们通过MyExample.getInstance来获取 MyExample类的唯一对象,这种方式相对简单,但有一个问题,就是增加了这个类的"不透明性",MyExample类的使用者必须知道这是一个单例类,跟以往通过new XXX的方式来获取对象不同。

3 透明的单例模式

在上一步中,由于该类的"不透明性",用户在使用时会有一些困难,因此在本节中实现一个"透明"的单例类,让用户在使用这个类创建对象时,可以和使用其他普通类一样。

接下来实现一个单例类CreateDiv,这个类的作用是在页面创建唯一的div节点,代码如下:

javascript 复制代码
var CreateDiv = (function () {
  var instance; // 标识是否创建过实例

  // 创建CreateDiv类
  var CreateDiv = function (html) {
    // 如果已经创建过实例,返回当前实例
    if (instance) {
      return instance;
    }
    // 如果没有创建实例。创建一个div元素,并返回
    this.init();
    this.html = html;
    return (instance = this);
  };

  // 定义init方法,目的是创建一个div元素,元素内容由我们自己定义
  CreateDiv.prototype.init = function () {
    var div = document.createElement("div");
    div.innerHTML = this.html;
    document.body.appendChild(div);
  };

  return CreateDiv;
})();

var e1 = new CreateDiv("example1");
var e2 = new CreateDiv("example2");

console.log(e1 === e2); // true

虽然现在完成了一个透明的单例类的编写,但它同样有一些缺点,为了把instance封装起来,使用了自执行的匿名函数和闭包,并且让这个匿名函数返回真正的构造方法,复杂度高,可读性也变差了。

4 用代理实现单例模式

在上面的例子中,假设我们某天需要利用这个类,在页面中创建千千万万的div,即要让这个类从单例类变成一个普通的可产生多个实例的类,那我们必须得改写CreateDiv构造函数,这种修改会给我们带来不必要的烦恼,因此我们通过引入代理类的方式来解决这个问题

首先在CreateDiv构造函数中,把负责管理单例的代码提出来,使它成为一个普通的创建div的类:

javascript 复制代码
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);
};

接下来引入代理类proxySingletonCreateDiv

javascript 复制代码
var ProxySingletonCreateDiv = (function () {
  var instance;
  return function (html) {
    if (!instance) {
      instance = new CreateDiv(html);
    }
    return instance;
  };
})();

var e1 = new ProxySingletonCreateDiv("example1");
var e2 = new ProxySingletonCreateDiv("example2");

console.log(e1 === e2); // true

上面的例子通过引入代理类的方式完成了一个单例模式的改写,与之前不同的是,现在把负责管理单例的逻辑移到了代理类proxySingletonCreateDiv中。这样一来,CreateDiv就变成了一个普通的类,它跟 proxySingletonCreateDiv组合起来可以达到单例模式的效果。

5 JavaScript 中的单例模式

前面提到的几种单例模式的实现,更多的是接近传统面向对象语言中的实现,单例对象从"类"中创建而来,在以类为中心的语言中,这是很自然的做法。比如在Java中,如果需要某个对象,就必须先定义一个类,对象总是从类中创建而来的。

JavaScript其实是一门无类语言,创建对象的方法非常简单,既然我们只需要一个"唯一"的对象,为什么要为它先创建一个"类"呢,传统的单例模式实现在JavaScript中并不适用。

单例模式的核心是确保只有一个实例,并提供全局访问。

全局变量不是单例模式,但在JavaScript开发中,我们经常会把全局变量当成单例来使用。例如:

javascript 复制代码
var a = {}; 

当用这种方式创建对象a时,对象a确实是独一无二的。如果a变量被声明在全局作用域下,我们可以在代码中的任何位置使用这个变量,这样就满足了单例模式的两个条件。但是全局变量存在很多问题,它很容易造成命名空间污染。

我们可以采用以下几种方式降低全局变量带来的命名污染:

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

javascript 复制代码
var namespace1 = {
  a: function () {
    alert(1);
  },
  b: function () {
    alert(2);
  },
};

2、使用闭包封装私有变量,这种方法把一些变量封装在闭包的内部,只暴露一些接口跟外界通信:

javascript 复制代码
var user = (function () {
  var __name = "sven",
    __age = 29;
  return {
    getUserInfo: function () {
      return __name + "-" + __age;
    },
  };
})();

我们用下划线来约定私有变量__name__age,它们被封装在闭包产生的作用域中,外部是访问不到这两个变量的,这就避免了对全局的命令污染。

6 惰性单例

惰性单例指的是在需要的时候才创建对象实例。

惰性单例是单例模式的重点,实例对象instance总是在我们调用MyExample.getInstance的时候才被创建,而不是在页面加载好的时候就创建,示例代码如下:

javascript 复制代码
MyExample.getInstance = (function () {
  var instance = null;
  return function (name) {
    if (!instance) {
      instance = new MyExample(name);
    }
    return instance;
  };
})();
相关推荐
10年前端老司机32 分钟前
什么!纯前端也能识别图片中的文案、还支持100多个国家的语言
前端·javascript·vue.js
摸鱼仙人~35 分钟前
React 性能优化实战指南:从理论到实践的完整攻略
前端·react.js·性能优化
程序员阿超的博客1 小时前
React动态渲染:如何用map循环渲染一个列表(List)
前端·react.js·前端框架
magic 2451 小时前
模拟 AJAX 提交 form 表单及请求头设置详解
前端·javascript·ajax
小小小小宇6 小时前
前端 Service Worker
前端
只喜欢赚钱的棉花没有糖7 小时前
http的缓存问题
前端·javascript·http
小小小小宇7 小时前
请求竞态问题统一封装
前端
loriloy7 小时前
前端资源帖
前端
源码超级联盟7 小时前
display的block和inline-block有什么区别
前端
GISer_Jing7 小时前
前端构建工具(Webpack\Vite\esbuild\Rspack)拆包能力深度解析
前端·webpack·node.js