前端常见设计模式之单例模式
单例模式的定义是产生一个类的唯一实例,但 js 本身是一种“无类”语言。很多讲 js 设计模式的文章把{}当成一个单例来使用也勉强说得通。因为 js 生成对象的方式有很多种,我们来看下另一种更有意义的单例。 有这样一个常见的需求,点击某个按钮的时候需要在页面弹出一个遮罩层。比如 web.qq.com 点击登录的时候.
这个生成灰色背景遮罩层的代码是很好写的.
var createMask = function() {
return document.body.appendChild(document.createElement(div));
}
$('button').click(function() {
Var mask = createMask();
mask.show();
})问题是, 这个遮罩层是全局唯一的, 那么每次调用 createMask 都会创建一个新的 div, 虽然可以在隐藏遮罩层的把它 remove 掉. 但显然这样做不合理. 再看下第二种方案, 在页面的一开始就创建好这个 div. 然后用一个变量引用它.
var mask = document.body.appendChild(document.createElement('div'))
$('button').click( function(){
mask.show();
})这样确实在页面只会创建一个遮罩层 div, 但是另外一个问题随之而来, 也许我们永远都不需要这个遮罩层, 那又浪费掉一个 div, 对 dom 节点的任何操作都应该非常吝啬. 如果可以借助一个变量. 来判断是否已经创建过 div 呢?
var mask;
var createMask = function() {
if (mask) return mask;
else {
mask = document.body.appendChild(document.createElement(div));
return mask;
}
}看起来不错, 到这里的确完成了一个产生单例对象的函数. 我们再仔细看这段代码有什么不妥. 首先这个函数是存在一定副作用的, 函数体内改变了外界变量 mask 的引用, 在多人协作的项目中, createMask 是个不安全的函数. 另一方面, mask 这个全局变量并不是非需不可. 再来改进一下.
用了个简单的闭包把变量 mask 包起来, 至少对于 createMask 函数来讲, 它是封闭的.
再回来正题, 前面那个单例还是有缺点. 它只能用于创建遮罩层. 假如我又需要写一个函数, 用来创建一个唯一的 xhr 对象呢? 能不能找到一个通用的 singleton 包装器.js 中函数是第一公民, 意味着函数也可以当参数传递. 看看最终的代码.
用一个变量来保存第一次的返回值, 如果它已经被赋值过, 那么在以后的调用中优先返回该变量. 而真正创建遮罩层的代码是通过回调函数的方式传入到 singleton 包装器中的. 这种方式其实叫桥接模式. 关于桥接模式, 放在后面一点点来说. 然而 singleton 函数也不是完美的, 它始终还是需要一个变量 result 来寄存 div 的引用. 遗憾的是 js 的函数式特性还不足以完全的消除声明和语句.
将单例与音视频结合起来
我们才用了一个 iife 来构造闭包, 仅返回了一个 _static 对象, 该对象提供了 getinstance 方法, 封装和创建、获取的步骤.以便获取全局只存在一个 audio 实例. 我们可以注意到音频实例并没有直接暴露给使用者,而是通过一个公有方法 getInstance 让使用者创建、获取音频实例。这么做的目的是禁止使用者主动实例化 Audio,在公共组件的层面上保证全局只存在一个 audio 实例。
类仅允许有一个实例,且该实例在用户侧有一个访问点。
实例必须能通过子类的形式进行扩展,且用户侧能在不修改代码的前提下使用该扩展实例。
单例模式的确会返回具有单例性质的结构,但单例这一性质体现在这些结构上,单例模式本身完全可以返回多个具有单例性质的对象(这是结构的一种)
实现音轨这个功能, 我们定义 tracks 类
纯单例模式
或者采用闭包的方式
以上两种方法都不太透明, 我们需要通过 Singleton.getInstance() 对象, 不知道的需要研究代码的实现 与我们常见的用 new 关键字来获取对象有出入,实际意义不大。
我们通常是采用这种方式。
JavaScript 单例模式
而在开发中我们避免全局变量污染的通常做法如下:
全局命名空间
使用闭包
它们的共同点是都可以定义自己的成员、存储数据。区别是全局命名空间的所有方法和属性都是公共的,而闭包可以实现方法和属性的私有化。
惰性单例模式
而惰性单例模式的原理也是这样的,只有当触发创建实例对象时,实例对象才会被创建。这样的实例对象创建方式在开发中很有必要的。
最后更新于
这有帮助吗?