JavaScript中的设计模式
设计原则
单一职权原则(SRP)
一个对象或方法只做一件事情。如果一个方法承担了过多的职责,那么在需求的变迁过程中,需要改写这个方法的可能性就越大。
应该把对象或方法划分成较小的粒度
最小知识原则(LKP)
一个软件实体应当 尽可能少地与其他实体发生相互作用
应当尽量减少对象之间的交互。如果两个对象之间不必彼此直接通信,那么这两个对象就不要发生直接的 相互联系,可以转交给第三方进行处理
开放-封闭原则(OCP)
软件实体(类、模块、函数)等应该是可以扩展的,但是不可修改。
当需要改变一个程序的功能或者给这个程序增加新功能的时候,可以使用增加代码的方式,尽量避免改动程序的源代码,防止影响原系统的稳定
什么是设计模式
假设有一个空房间,我们要日复一日地往里面放一些东西。最简单的办法当然是把这些东西直接扔进去,但是时间久了,就会发现很难从这个房子里找到自己想要的东西,要调整某几样东 西的位置也不容易。所以在房间里做一些柜子也许是个更好的选择,虽然柜子会增加我们的成本,但它可以在维护阶段为我们带来好处。使用 这些柜子存放东西的规则,或许就是一种模式
设计模式的原则是 “找出 程序中变化的地方,并将变化封装起来”,它的关键是意图,而不是结构。
单例模式
说明 | 含义 |
---|---|
定义 | 保证一个类仅有一个实例,并提供一个访问它的全局访问点 |
核心 | 确保只有一个实例,并提供全局访问 |
实现 | 假设要设置一个管理员,多次调用也仅设置一次,我们可以使用闭包缓存一个内部变量来实现这个单例 |
通过定义核心,可以联想到JavaScript中的全局对象,利用ES6的let不允许重复声明的特性,刚好符合这两个特点;是的,全局对象是最简单的单例模式
let CreateSingleton = (function () {
let instance = null
return function (name) {
this.name = name
if (instance) {
return instance
}
return (instance = this)
}
})()
CreateSingleton.prototype.getName = function () {
console.log(this.name)
}
let winner = new CreateSingleton('winner') //winner
winner.getName()
let sunner = new CreateSingleton('sunner') //winner
sunner.getName()
第二次实例化
CreateSingleton
对象时,由于已经存在了该实例,因此直接将实例返回。
还可以通过代理的形式,将创建对象的操作和实例判断的操作进行解耦拆分,实现更小粒度的划分,符合”单一职责原则“;
// 只负责判断是否需要创建对象
let ProxyCreateSingleton = (function () {
let instance = null
return function (name) {
if (instance) {
return instance
}
return (instance = new Singlton(name))
}
})()
// 创建对象
let Singlton = function (name) {
this.name = name
}
// 对象方法
Singlton.prototype.getName = function () {
console.log(this.name)
}
工厂模式
什么是工厂:不暴露对象创建的逻辑,而是将逻辑封装在一个函数内,那么这个函数可以成为工厂
- 简单工厂
let factory = function (role) {
function User(obj) {
this.name = obj.name
this.role = obj.role
}
switch (role) {
case 'superman':
return new User({ name: '平台用户', role: ['主页', '登录页'] })
break
case 'man':
return new User({ name: '游客', role: ['登录页'] })
break
default:
throw new Error('参数错误')
}
}
let superman = factory('superman')
let man = factory('man')
- 工厂方法
let factory = function (role) {
if (this instanceof factory) {
var s = new this[role]()
return s
} else {
return new factory(role)
}
}
factory.prototype = {
admin: function () {
this.name = '平台用户'
this.role = ['登录页', '主页']
},
common: function () {
this.name = '游客'
this.role = ['登录页']
},
test: function () {
this.name = '测试'
this.role = ['登录页', '主页', '测试页']
this.test = '我还有一个测试属性哦'
}
}
let admin = new factory('admin')
let common = new factory('common')
let test = new factory('test')
工厂方法:对于类的扩充优于修改 继承优于扩充
安全模式下
var Demo = function () { if (!(this instanceof Demo)) { return new Demo() } } Demo.prototype = { show: function () { console.log(123) } } var d = new Demo() d.show() var d = Demo() d.show()
抽象工厂
抽象工厂的作用是让子类继承父类,但子类必须实现父类没有实现的功能。
class Car {
getPrice() {
return new Error('抽象方法不能调用')
}
getSpeed() {
return new Error('抽象方法不能调用')
}
}
class BMW extends Car {
constructor(price, speed) {
super()
this.price = price
this.speed = speed
}
// 子类必须实现父类没有实现的方法
getPrice() {
return this.price
}
getSpeed() {
return this.speed
}
}
const bmw = new BMW(1, 2)
console.log(bmw.getPrice())
外观模式
说明 | 含义 |
---|---|
定义 | 为子系统中的一组接口提供一个一致的界面,定义一个高层接口,这个接口使子系统更加容易使用 |
核心 | 可以通过请求外观接口来达到访问子系统,也可以选择越过外观来直接访问子系统 |
实现 | 外观模式在 JS 中,可以认为是一组函数的集合 |
// 三个处理函数
function start() {
console.log('start')
}
function doing() {
console.log('doing')
}
function end() {
console.log('end')
}
// 外观函数,将一些处理统一起来,方便调用
function execute() {
start()
doing()
end()
}
// 调用init开始执行
function init() {
// 此处直接调用了高层函数,也可以选择越过它直接调用相关的函数
execute()
}
init() // start doing end
适配器模式
说明 | 含义 |
---|---|
定义 | 是解决两个软件实体间的接口不兼容的问题,对不兼容的部分进行适配 |
核心 | 解决两个已有接口之间不匹配的问题 |
实现 | 比如一个简单的数据格式转换的适配器 |
var A = A || {}
A.g = function (id) {
return document.getElementById(id)
}
A.on = function (id, type, fn) {
var dom = typeof id === 'string' ? this.g(id) : id
if (dom.addEventListener) {
dom.addEventListener(type, fn, false)
} else if (dom.attachEvent) {
dom.attachEvent('on' + type, fn)
} else {
dom['on' + type] = fn
}
}
A.on(window, 'load', function () {
A.on('mybutton', 'click', function () {
alert(123)
})
})
装饰器模式
说明 | 含义 |
---|---|
定义 | 以动态地给某个对象添加一些额外的职责,而不会影响从这个类中派生的其他对象。 是一种 “即用即付” 的方式,能够在不改变对 象自身的基础上,在程序运行期间给对象动态地添加职责 |
核心 | 是为对象动态加入行为,经过多重包装,可以形成一条装饰链 |
实现 | 获取该元素已绑定的事件,调用后在调用扩充的事件 |
var decorator = function (input, fn) {
// 我想要对 input 的点击事件 进行扩充,首先要获取原有的功能
var input = document.getElementById(input)
if (typeof input.onclick === 'function') {
var oldClickEvent = input.onclick
input.onclick = function () {
oldClickEvent()
fn()
}
} else {
input.onclick = fn
}
}
decorator('tel_input', function () {
telDemoText.style.display = 'none'
})