【青训营】JavaScript中的设计模式
什么是设计模式
在软件设计过程中,针对特定问题的简介而优雅的解决方案
我们在实际的开发过程中,会有各种各样的不同的需求,需求的解决方案也有很多,我们需要解决方案是可持续使用的,以便在下次遇到类似需求的时候能够拿来就用,避免重复造轮子。
SOLID五大设计原则
S 单一职责原则
就是一个程序只专注于执行一件事情,在本事件内不会完成其他事件的功能,逻辑相对比较简单,不会非常复杂
O 开放封闭原则
对扩展开放,对修改封闭。我们的代码是需要可拓展的,因为需求会更新,但是不能因为新需求去改变其他原始需求的代码,所以需要对修改封闭。
L 里氏置换原则
首先有一个父类,根据父类继承出子类,子类能覆盖父类实现的功能,基于父类去拓展更多的功能,并能出现在父类出现的地方。可拓展性强,功能性强。
I 接口独立原则
保持接口的单一独立,让接口各自独立,不会相互影响。
D 依赖导致原则
使用方法只关注 接口而不关注具体类的实现
为什么需要设计模式
易读性
使用设计模式能够提升代码的可读性,提升后续开发效率
可拓展性
使用设计模式对代码解耦,能很好的增强代码的易修改性和扩展性
复用性
使用设计模式可以重用已有的解决方案,无须再重复相同的工作
可靠性
使用设计模式能够增加系统的健壮性,使代码编写真正工程化
常见设计模式
单例模式
定义:唯一 & 全局访问。保证一个类仅有一个实例,并提供一个访问它的全局访问点
应用场景:能被缓存的内容,可以复用,例如登录弹窗
//业务逻辑
const createLoginLayer = () =>{
const div = document.createElement("div")
div.innerHTML = "登录浮窗"
div.style.display = "none"
document.body.appendChild(div)
return div
}
document.getElementById("loginBtn").onclick = ()=>{
const loginLayer = createLoginLayer()
loginLayer.style.display = "block"
}
使用单例模式
//单例
const getSingle = (fn)=>{
let result;
return(...rest)=>{
return result || (result = fn.apply(this,rest))
}
}
//业务逻辑
const createLoginLayer = () =>{
const div = document.createElement("div")
div.innerHTML = "登录浮窗"
div.style.display = "none"
document.body.appendChild(div)
return div
}
const createSingleLohinLayer = getSingle(createLoginLayer)
document.getElementById("loginBtn").onclick = ()=>{
const loginLayer = createSingleLohinLayer()
loginLayer.style.display = "block"
}
策略模式
定义: 系列的算法,把它们一个个封装起来,并且使它们可以相互替换,把看似毫无联系的代码提取封装、复用、使之更容易被理解和拓展
应用场景: 要完成一件事情,有不同的策略,例如绩效计算、表单验证规则
//策略
const calculateBonus = (level,salary)=>{
switch (level){
case 's' :{
return salary * 4
}
case 'a' :{
return salary * 3
}
case 'b' :{
return salary * 2
}
default:{
return 0
}
}
}
calculateBonus("s",20000) //80000
calculateBonus("a",10000) //30000
使用策略模式
//策略
const strategies = {
s:(salary)=>{
return salary * 4
},
a:(salary)=>{
return salary * 3
},
b:(salary)=>{
return salary * 2
},
}
const calculateBonus = (level,salary)=>{
return strategies[level](salary)
}
calculateBonus("s",20000) //80000
calculateBonus("a",10000) //30000
代理模式
定义: 为一个对象提供一个代用品或占位符,一遍控制对它的访问。替身对象可对请求预先进行处理,再决定是否交给本体对象
应用场景: 当我们不方便直接访问某个对象时,或不满足需求时,可考虑使用一个替身对象来控制该对象的访问
//原生函数
const rawImage = (()=>{
const imgNode = document.createElement('img')
document.body.appendChild(imgNode)
return {
setSrc:(src)=>{
imgNode.src = "./loading.gif"
const img = new Image()
img.src = src
img.onload = ()=>{
imgNode.src = this.src
}
}
}
})()
rawImage.setSrc("http://xxx.gif")
//原生函数
const rawImage = (()=>{
const imgNode = document.createElement('img')
document.body.appendChild(imgNode)
return {
setSrc:(src)=>{
imgNode.src = src
}
}
})()
//代理函数
const proxyImage = (()=>{
const img = new Image()
img.onload = ()=>{
rawImage.setSrc(this.src)
}
return {
setSrc:(src)=>{
rawImage.setSrc('./loading.gif')
img.src = src
}
}
})()
proxyImage.setSrc("http://xxx.gif")
发布订阅模式
定义: 对象间的一种一对多的依赖关系,当一个对象的状态发生改变时。所有依赖于它的对象都将得到通知
应用场景: DOM事件,消息通知
class PubSub {
constructor(){
this.subscriber = {}
}
//订阅
subscribe(type,fn){
let listeners = this.subscribers[type] || []
listeners.push(fn)
}
//取消订阅
unsubcribe(type,fn){
let listeners = this.subscribers[type]
if(!listeners || !listeners.length) return
this.subscribers[type] = listeners.filter((v)=> v !== fn)
}
//触发订阅事件
public(type,...args){
let listeners = this.subscribers[type]
if(!listeners || !listeners.length) return
listeners.forEach((fn)=>fn(...args))
}
}
let ob = new PubSub()
ob.subscribe("add",(val)=>console.log(val))
ob.publish("add",1)
命令模式
定义:执行某些特定事情的指令
应用场景: 富文本编辑器工具栏
//设置命令
const setCommand = (button,command)=>{
button.onclick = ()=>{
command.execute()
}
}
//业务逻辑
const MenuBar = {
refresh:()=>{
console.log("refresh")
}
}
const RefreshMenuBarCommand = (receiver) =>{
return {
execute:()=>{
receiver.refresh()
}
}
}
const refreshMenuBarCommand = RefreshMenuBarCommand(MenuBar)
//绑定按钮和命令
setCommand(refreshButton,refreshMenuBarCommand)
组合模式
定义:用小的子对象来构建更大的对象,将对象组合成树形结构,以表示“部分-整体”的层次结构
应用场景: 从is-a 到 has-a
class MacroCommand{
constructor(){
this.commands = []
}
//添加子对象逻辑
add(command){
console.log(this)
this.commands.push(command)
}
//执行父对象逻辑
execute(){
for(let i = 0;i < this.commands.length; i++){
this.commands[i].execute()
}
}
}
const macroCommand = MacroCommand()
const openCommand = {
execute:()=>{
console.log('open')
}
}
const closeCommand = {
execute:()=>{
console.log('close')
}
}
marcoCommand.add(openCommand)
marcoCommand.add(closeCommand)
marcoCommand.execute()
装饰器模式
定义: 能够在不改变对象自身的基础上,在程序运行期间给对象动态的添加职责
应用场景: 数据上报、统计函数执行时间
const ajax = (type,url,param)=>{
console.log(param)
}
const getToken = ()=>{
return 'Token'
}
ajax()
getToken()
//添加指责
Function.prototype.before = function (beforeFn){
return (...rest)=>{
beforeFn.apply(this,rest)
return this(...rest)
}
}
let ajax = (type,url,param)=>{
console.log(param)
}
const getToken = ()=>{
return 'Token'
}
ajax = ajax.before((type,url,param)=>{
param.token = getToken()
})
适配器模式
定义: 解决两个软件实体间的接口不兼容问题,不需要改变已有的接口,就能够使它们协同作用
应用场景: 接口不兼容的情况
const aMap = {
show:()=>{
console.log('开始渲染地图A')
}
}
const bMap = {
display:()=>{
console.log('开始渲染地图B')
}
}
const renderMap = (type)=>{
if(type == 'a'){
aMap.show()
}else if(type == 'b'){
bMap.display()
}
}
const aMap = {
show:()=>{
console.log('开始渲染地图A')
}
}
const bMap = {
display:()=>{
console.log('开始渲染地图B')
}
}
//适配层
const bMapAdapter = {
show:()=>{
return bMap.display()
}
}
const renderMap = (map)=>{
if(map.show instanceof Function){
map.show()
}
}
renderMap(aMap)
renderMap(bAdapter)
总结
- 理解使用设计模式的思想,思考每个设计模式的含义和技巧
- 合理使用设计模式,在不同的场景需求灵活变换。分清轻重缓急,如果项目急需上线,首要目标是实现功能,不能盲目的使用
- 总结使用设计模式的经验,因为实际需求都是非常复杂的,总结经验能够帮助我们节省大量的思考时间,选择一个场景的最优的设计模式,提升工作效率
- 同一个需求在不同的场景可能适用不同的设计模式,所以在使用设计模式的时候需要融会贯通,不能死板的使用
转载自:https://juejin.cn/post/7009536721083695117