设计模式——单例模式
弹出层是一个很常见的需求。在一个登录功能的设计中,我们都希望无论单击多少次登录按钮,这个弹窗都只会被创建一次。这里就适合用单例模式来创建。在JS中单例模式的应用也非常广泛,比如全局缓存,window对象等等。下面我就来谈谈在JS中单例模式的具体应用。
单例模式的创建
单例模式的核心是确保只有一个实例并提供全局访问。因为JS是一门无类语言(ES6新增类特性),就没必要利用构造函数来实现单例了,那么最简单的创建方式就是使用对象字面量的形式创建全局变量了。例子如下:
let singleton = {
property1: a,
property2: b,
method1() {
console.log('c');
}
};
用这种方式创建单例时确实满足条件,但是全局变量存在很多问题。其一就是造成变量冲突,随时都可能被别人给覆盖。如果确实需要使用全局变量来创建,应该尽可能降低全局变量的污染。下面是两种常用方法。
1.使用命名空间
把单例都定义为命名空间的属性,可以减少变量和全局作用域打交道的机会。例子如下:
let namespace = {
singleton: {
property1: a
// ...
}
}
2.闭包封装私有变量
将单例封装在闭包内部,只暴露接口与全局通信。
let user = (function() {
let singleton = {
property1: a
// ...
};
return {
getSingleton() {
return singleton;
}
}
})();
惰性单例
如果我们想做到只有在使用的时候才初始化单例,那么该如何做呢?很简单,通过一个闭包存储单例即可。当然创建前必须对单例的唯一性做检查。
let createSingleton = (function() {
let singleton;
return function() {
if (!singleton) {
// 单例创建逻辑
singleton = {
property1: a
// ...
}
}
return singleton;
}
})();
进一步封装惰性单例
我们虽然创建了一个可用的惰性单例,但还存在下面一些问题:
- 这段代码违反了单一职责原则,创建单例和管理的逻辑混合在了一起。
- 如果下次我们需要创建其他的单例就必须重写整个函数。
我们需要把不变的部分和可变的部分隔离开。不变的部分是什么呢?实际上无论创建什么单例,管理的逻辑都是不变的。现在我们就可以把管理的逻辑抽出,将创建逻辑封装在一个函数里。代码如下:
let createSingleton = function(fn) {
let singleton;
return function() {
if (!singleton) {
singleton = fn.apply(this, arguments);
}
return singleton;
}
}
在这个例子中我们无论用fn创建什么单例,都可以使用该例子来管理其唯一存在的功能。
小结
单例模式是我开始设计模式正式学习的第一个模式,虽然简单但也是非常实用的一个模式。今后一段时间内也会集中于设计模式文章的更新。
-- EOF --