likes
comments
collection
share

【青训营】JavaScript中的设计模式

作者站长头像
站长
· 阅读数 21

什么是设计模式

在软件设计过程中,针对特定问题的简介而优雅的解决方案

我们在实际的开发过程中,会有各种各样的不同的需求,需求的解决方案也有很多,我们需要解决方案是可持续使用的,以便在下次遇到类似需求的时候能够拿来就用,避免重复造轮子。

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
评论
请登录