目前基于MVVM的前端框架已经深入人心了,框架在很多方面都简化了我们的开发,在某种场景下甚至还提高了我们代码的运行效率,如果强行去套用那些不常用的设计模式的话,反而会把代码写的很怪异。
另外,有些设计模式不适合JS,因为JS的语法比较灵活,大家一定听说过“函数是JS的一等公民”这样的提法,像C#,Java,这些语言的方法是不能像JS一样既可以执行,又可以像普通变量那样传递的,这就导致了在写法上的差异。
前端设计模式之单例模式
单例模式,保证一个类仅有一个实例,并提供一个访问他的全局节点。
把构造方法私有化,这样外界就无法实例它了,暴露出一个访问它的唯一实例方法。
单例模式属于前端设计模式中创建型的一种,我们常用前端状态管理工具Vuex都是采用单例模式,来实现了一个全局的 Store 用于存储应用的所有状态。当然它们具体的实现细节肯定不完全一样,但无疑都是应用了单例模式的思想。
单例模式的实现思路
一般情况下,当我们创建了一个类后,可以通过new关键字调用构造函数进而生成任意多的实例对象。像这样:
class SingleDog {
person () {
console.log('单例对象')
}
}
const person1 = new SingleDog()
const person2 = new SingleDog()
console.log(person1 === person2) // false
但现在我们要思考这样一个问题:如何才能保证一个类仅有一个实例?
要做到这一点,就需要构造函数具备判断自己是否已经创建过一个实例的能力。我们现在把这段判断逻辑写成一个静态方法:
class SingleDog {
person () {
console.log('单例对象')
}
static getInstance () {
// 判断是否已经new过1个实例
if (!SingleDog.instance) {
// 若这个唯一的实例不存在,那么先创建它
SingleDog.instance = new SingleDog()
}
// 如果这个唯一的实例已经存在,则直接返回
return SingleDog.instance
}
}
const person1 = SingleDog.getInstance()
const person2 = SingleDog.getInstance()
console.log(person1 === person2) // true
除了上面这种实现方式之外,getInstance的逻辑还可以用闭包来实现:
function Person() {
this.age = 18
}
const SingleDog = (function() {
// 定义闭包变量
let instance = null
return function() {
if(!instance) {
// 创建实例
instance = new Person()
}
return instance
}
})()
const person1 = new SingleDog()
const person2 = new SingleDog()
console.log(person1)
console.log(person2)
console.log(person1 === person2) // true 因为他们是同一个实例
person1.age++
console.log(person2.age) // 19
可以看出,在已经创建的实例instance的判断和拦截下,我们不管调用多少次,SingleDog都只会给我们返回一个实例,person1和person2现在都指向这个唯一的实例。
了解了上面的内容,接下来实践一下。
单例模式实践:iview中Message组件的扩展
一个比较熟悉的场景,iview中的Message全局提示组件,大家开发中肯定都有用到过,如果频繁的执行,就会出现下面这种情况:
可能是为了我们使用的时候可以更加的灵活,iview这里的源码并没有在实现的时候就保证单例。如果我们希望在页面中保持本次全局提示完成后再弹出下一次提示,就可以使用单例模式对Message组件进行封装。
// messagePlugin.js
import { Message } from 'view-design'
class SingletonMessage {
static instance = null
constructor () {
// 不允许当前类实例化
throw new Error('this class can not called by new')
}
static showMessage (options) {
// 如果当前实例存在 无需做任何操作
if (this.instance) {
return
}
let config
if (typeof options === 'string') {
config = {
content: options,
onClose: () => {
// 做一些清理工作
this.instance = null
}
}
} else {
const { onClose, ...others } = options
config = {
...others,
onClose: (...args) => {
// 处理额外的清理工作
this.instance = null
// 处理默认的参数
typeof onClose === 'function' && onClose.apply(this, args)
}
}
}
this.instance = Message.success(config)
}
static closeMessage () {
if (!this.instance) {
return
}
this.instance.close()
}
}
export default SingletonMessage
openMessage () {
this.$singletonMessage.showMessage('测试一下吧')
}
这样一个简单的单例模式的封装就做好了,确保无论点击多少次按钮,都只有一个提示框的实例存在,符合单例模式的设计理念。
总结
以上介绍了Javascript最经典的设计模式之一单例模式,简单来说,单例就是单实例,单例模式的所有实例化都是同一个实例,确保所有的对象访问的是一个,因为内存只存在一个对象,节约了资源,在频繁的创建和销毁时可以提高系统性能。但是,单例类违背了“单一职责”原则。他的职责过重,这其实是一种设计弊端。并且滥用实例可能会导致单例类过于臃肿,被回收时可能会导致某些状态丢失。