JavaScript 如何实现单例模式

本博客 hjy-xh,转载请申明出处

定义

单例模式,也叫单子模式,是一种常用的软件设计模式,属于创建型模式的一种。

保证一个类仅有一个实例,并提供一个访问它的全局访问点

思考

全局变量符合单例模式吗?

不是。但我们经常会把变量当成单例来使用,看个例子:

1
var person = {};

通过字面量创建对象时,对象person确实是独一无二的,如果该变量在全局作用域下声明,就可以在代码中的任何地方使用它。

但是全局变量存在一些问题:

  • 污染命名空间(变量名冲突)
  • 不易维护 (被覆盖)

并且随着项目的体积和功能增大,出现问题的概率也会增大。

实现

首先我们要清楚 JS 是一门没有类的语言,ES6 出现类也是原型的语法糖。也正因为没有类,在 JS 中实现单例模式也只需要一个唯一的对象,这是很自然的做法。

这里以一个登录弹窗为例,实践一下单例模式。

假设现在有一个登录按钮,点击后能够出现登录弹窗:

1
<button id="loginBtn">登录</button>

先来写创建登录弹窗的方法:

1
2
3
4
5
6
7
var doCreateLoginModal = function () {
var modal = document.createElement("div");
modal.style.display = "none";
modal.textContent = "登录弹窗";
document.body.appendChild(modal);
return modal;
};

接下来就是单例模式的重点了:

1
2
3
4
5
6
7
8
var getInstance = function (fn) {
var result;
return function () {
return result || (result = fn.apply(this, arguments));
};
};

var createLoginModal = getInstance(doCreateLoginModal);

这里可以发现返回的结果被封装在闭包(内部的函数被保存到了外部)产生的作用域中,外部是访问不到这两个变量的,这就避免了对全局的命名污染。

先看这段代码中产生的闭包:

最后一行外部的createLoginModal变量保存了getInstance中的匿名函数,该拥有getInstance作用域的访问权限。

再仔细看return result || (result = fn.apply(this, arguments));这条语句:

第一次调用方法时,resultundefined,会执行result = fn.apply(this, arguments),这里利用传入的fn调用生成登录弹窗的方法生成登录弹窗,并被赋值给result,使得之后createLoginModal再被调用时,返回第一次创建的登录弹窗。

最后给按钮绑定点击事件:

1
2
3
4
5
6
var loginBtn = document.getElementById("loginBtn");

loginBtn.onclick = function () {
var loginModal = createLoginModal();
loginModal.style.display = "block";
};

完整代码

惰性单例

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

参考

单例模式-维基百科

书目

  • JavaScript 设计模式与开发实践

  • JavaScript 设计模式