设计模式:单例
设计模式:单例

设计模式:单例

Published
Jul 23, 2023
Author
本文主要介绍单例模式,以及随着 JavaScript 的发展,我们如何在 JavaScript 中实现单例模式。

设计模式(Design Patterns)

设计模式是在软件设计中解决问题的特定方法,由于这些问题出现频繁,慢慢就形成了解决这类问题的一种特定模式。设计模式是软件开发的基本部分,它不是特定的软件,而是解决重复出现问题的一种方法。
随着软件架构的改变,生态系统的发展,新的问题也催生出新的设计模式。就 Web 生态而言,不同的框架也会催生出新的模式,或者优化、修改旧的模式。理解这些模式有助于我们明析软件开发中的概念,可以帮助和优化改进应用程序。

单例模式(Singleton Pattern)

单例模式可以限制类只能实例化一次,这在有全局共享资源时非常有用,例如:应用程序的配置,React 程序的全局状态。单例应该是不会被使用者修改的,并且多次实例化也不会有危险。

JavaScript 中的单例

随着语言的发展,尤其 ES6(ES2015)为 JavaScript 提供了大量的新特性(这也是为什么 ES6 最常被提及),其中包括新的 class 语法、新的声明变量的关键字(constlet)等等,这也使得实现有些不同。

ES5

旧的实现方法依赖于闭包(closures)和立即执行函数(IIFE),下面是一个应用程序的全局状态:
var AppStore = (function() { var _todos = []; function add(item) { _todos.push(item); } function get(id) { return _todos.find((d) => { return d.id === id; }); } return { add: add, get: get } }());
当程序执行时,匿名函数会立即调用并返回一个带有两个方法的对象,但是没有真实存储数据集合 _todos 的访问权限(闭包的作用)。
但是,返回的对象的方法仍然可以被修改,甚至可以完全重新定义 AppStore,而且这些修改可能出现在任何地方,这让在大项目中追踪问题非常困难。我们希望阻止这种修改,ES6 的新特性将派上用场。

ES6

现在,全新的 const 关键字和模块系统将帮助我们解决之前的问题,并且代码更简洁。
const _todos = []; const AppStore = { add: item => _todos.push(item), get: id => _todos.find(d => d.id === id) } Object.freeze(AppStore); export default AppStore;
得益于 const 的作用_todos 的作用域限制在模块内,AppStore 无法再被重新赋值。此外,Object.freeze 方法冻结 AppStore,使其方法无法被修改。
但这和上面提到的 class 又有什么关系?让我们用类改写:
class AppStore { constructor(){ this._todos = []; } add(item){ this._todos.push(item); } get(id){ return this._todos.find(d => d.id === id); } } const instance = new AppStore(); Object.freeze(instance); export default instance;
如果你是一个有经验的 JavaScript 使用者,你会发现两种实现方式的单例都不能保证其不变性和不可重写性。
  • 第一种方式导出的对象虽然是无法被重新赋值,属性也被冻结,但是仍然可以使用 Object.assign 对其进行复制。
  • 第二种方式虽然没有暴露出类供使用者调用,但是导出的实例的构造器仍然是可用的,可以用来创建新的实例。
此时,class 的优势出现了,我们可以对其构造器进行改进:
class AppStore { constructor(){ // 还没有实例,对其实例化 if(!AppStore.instance) { this._todos = []; AppStore.instance = this; } // 实例化过,直接返回 return AppStore.instance; } add(item){ this._todos.push(item); } get(id){ return this._todos.find(d => d.id === id); } } const instance = new AppStore(); Object.freeze(instance); export default instance;
现在,额外保存实例并在构造器中进行判断,实例化过返回之前保存的实例,以此来确保应用中有且仅有一个实例。

参考