JavaScript常用设计模式与实践

一、引言

<<设计模式: 可复用面向对象软件的基础>> 一书中的定义是:

在面向对象软件设计过程中针对特定问题的简洁而优雅的解决方案

这些模式并不是算法或者具体的实现。它们更像是想法、观点和抽象,辅助你去解决一些特定问题。

二、设计模式

1、单例模式

保证一个类仅有一个实例,并提供一个访问它的全局访问点

js 复制代码
// 创建单例
class DatabaseConnection {
  constructor() {
    if (DatabaseConnection.instance) {
      return DatabaseConnection.instance
    }
    // 初始化数据库连接
    this.connection = 'connection'
    DatabaseConnection.instance = this
  }

  getConnection() {
    return this.connection
  }
}
// 使用单例模式获取数据库连接
const dbConnection1 = new DatabaseConnection()
const dbConnection2 = new DatabaseConnection()

console.log(dbConnection1 === dbConnection2) // true
console.log(dbConnection1.getConnection()) // connection
console.log(dbConnection2.getConnection()) // connection

其实就是实例只会被创建一次,后续都是复用这一个实例

2、策略模式

定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换

js 复制代码
// 计算奖金
const strategies = {
  S: (salary) => {
    return salary * 4 // 4倍月工资作为奖金
  },
  A: (salary) => {
    return salary * 2 // 2倍
  },
  B: (salary) => {
    return salary * 1 // 1倍
  },
}

const calculateBonus = (level, salary) => {
  return strategies[level](salary)
}

calculateBonus('S', 10000) // 40000
calculateBonus('A', 800) // 16000

策略模式至少要由两部分组成:

  1. 封装了各类算法的策略类
  2. 接受参数的环境类,然后把请求委托给策略类进行处理

3、代理模式

访问对象时为一个对象提供一个代用品或者占位符,以便控制对它的访问

js 复制代码
// proxy 示例

// 原始对象
const obj = {
  foo: 1,
}

// 代理后的对象
const newP = new Proxy(obj, {
  // 拦截读取
  get(target, key) {
    console.log(`访问了  ${key}!`)
    return target[key]
  },

  // 拦截设置属性操作
  set(target, key, value) {
    console.log(`设置了 ${key}!`)
    target[key] = value
  },
})

newP.foo // 访问了 foo!
newP.foo = 2 // 设置了 foo!

这里利用 ES6 的 Proxy 进行举例,可以发现我们不是直接操作原始的对象,而是对代理后的对象进行操作

Vue3中部分响应式原理就是利用了这个特性,如果想要更详细的了解可以访问我写的另一篇文章,这里不再赘述 vue3响应式方案

4、迭代器模式

提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示

js 复制代码
// js 内置了迭代器
const arr = [1, 2, 3]
const iterator = arr[Symbol.iterator]()
iterator.next() // {value: 1, done: false}
iterator.next() // {value: 2, done: false}
iterator.next() // {value: 3, done: false}
iterator.next() // {value: undefined, done: true}

其实Js还有 forEach、map等内置迭代器,这里就不再一一举例

5、发布订阅模式

又称观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖它的对象都将得到通知

js 复制代码
// EventBus
const EventBus = (function () {
  const events = {}

  return {
    subscribe: function (eventName, callback) {
      if (!events[eventName]) {
        events[eventName] = []
      }

      events[eventName].push(callback)
    },

    publish: function (eventName, data) {
      if (!events[eventName]) {
        return
      }

      events[eventName].forEach((callback) => callback(data))
    },
  }
})()

// 订阅事件
EventBus.subscribe('eventA', (data) => {
  console.log(`Event A received: ${data}`)
})

// 发布事件
EventBus.publish('eventA', 'Hello, EventBus!') // Event A received: Hello, EventBus!

订阅者一旦订阅后,发布者发布事件都会通知到 每个订阅者,但是如果过度使用会导致难以跟踪维护,特别是多个发布者和订阅者嵌套的时候

6、命令模式

指一个执行某些特定事情的命令

js 复制代码
// 宏命令 比如回家后通过语音助手执行到家命令
const createCommand = () => {
  return {
    commandsList: [],
    execute: () => {
      this.commandsList.forEach((command) => command.execute())
    },
    add: (command) => {
      this.commandsList.push(command)
    },
  }
}

const command1 = {
  execute: () => {
    console.log('开空调')
  },
}

const command2 = {
  execute: () => {
    console.log('开电视')
  },
}

const macroCommand = createCommand()
macroCommand.add(command1)
macroCommand.add(command2)
macroCommand.execute() // 开空调 开电视

命令模式的特点就是不知道请求的接收者是谁,也不知道被请求的操作是什么,所以需要借助命令对象来执行传入的命令

7、模板方法模式

模板方法是一种只需要使用继承就可以实现的非常简单的模式

js 复制代码
// 每日晚餐模板
class Dinner {
  setup() {}

  start() {
    this.setup()
    this.cook() // 做饭
    this.eat() // 吃饭
    this.washDishes() // 洗碗
  }

  cook() {}
  eat() {}
  washDishes() {}
}

class ToDayDinner extends Dinner {
  setup() {
    console.log('今天吃青椒肉丝')
  }

  cook() {
    console.log('做青椒肉丝')
  }

  eat() {
    console.log('吃青椒肉丝')
  }

  washDishes() {
    console.log('洗碗')
  }
}

class TomorrowDinner extends Dinner {
  setup() {
    console.log('明天吃土豆肉丝')
  }

  cook() {
    console.log('做土豆肉丝')
  }

  eat() {
    console.log('吃土豆肉丝')
  }

  washDishes() {
    console.log('洗盘子')
  }
}

const todayDinner = new ToDayDinner()
todayDinner.start()

const tomorrowDinner = new TomorrowDinner()
tomorrowDinner.start()

这是一种典型通过封装来提高扩展性的设计模式

8、享元模式

运用共享技术来有效支持大量细粒度的对象

js 复制代码
// 对象池工厂 也就是享元模式
const objectPoolFactory = function (createObjFn) {
  const objectPool = []

  return {
    create: function () {
      var obj = objectPool.length === 0 ? createObjFn.apply(this, arguments) : objectPool.shift()
      return obj
    },
    recover: function (obj) {
      objectPool.push(obj)
    },
  }
}

// 创建 DOM 工厂
const iframeFactory = objectPoolFactory(function () {
  const iframe = document.createElement('iframe')
  document.body.appendChild(iframe)

  iframe.onload = function () {
    iframe.onload = null
    iframeFactory.recover(iframe) // iframe加载完了回收节点
  }
  return iframe
})

const iframe1 = iframeFactory.create()
iframe1.src = 'https://www.baidu.com'

const iframe2 = iframeFactory.create()
iframe2.src = 'http://www.google.com/'

上面例子中需要反复创建 DOM,就比较适合使用享元模式来缓存对应的 DOM 对象,然后再添加到 DOM 对象上:

9、职责链模式

使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的偶合关系,将这些对象练成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止

js 复制代码
// 超市折扣  大于300 3折  大于200 5折  大于100 8折
class Discount {
  constructor() {
    this.discount = 0
    this.nextHandler = null
  }

  setNextHandler(handler) {
    this.nextHandler = handler
  }

  handleRequest(originPrice) {
    return originPrice * this.discount
  }
}

class DiscountHandler1 extends Discount {
  constructor() {
    super()
    this.discount = 0.3
  }

  handleRequest(originPrice) {
    if (originPrice >= 300) {
      return super.handleRequest(originPrice)
    }

    return this.nextHandler.handleRequest(originPrice)
  }
}

class DiscountHandler2 extends Discount {
  constructor() {
    super()
    this.discount = 0.5
  }

  handleRequest(originPrice) {
    if (originPrice >= 200) {
      return super.handleRequest(originPrice)
    }

    return this.nextHandler.handleRequest(originPrice)
  }
}

class DiscountHandler3 extends Discount {
  constructor() {
    super()
    this.discount = 0.8
  }

  handleRequest(originPrice) {
    if (originPrice >= 100) {
      return super.handleRequest(originPrice)
    }

    return this.nextHandler.handleRequest(originPrice)
  }
}

const discountHandler1 = new DiscountHandler1()
const discountHandler2 = new DiscountHandler2()
const discountHandler3 = new DiscountHandler3()

discountHandler1.setNextHandler(discountHandler2)
discountHandler2.setNextHandler(discountHandler3)

console.log(discountHandler1.handleRequest(250)) // 125
console.log(discountHandler1.handleRequest(150)) // 120

最大的优点就是可以解耦多个处理逻辑之间的复杂关系

10、装饰者模式

可以动态的给某个对象添加一些额外的职责,而不会影响从这个类派生的其他对象

js 复制代码
// 原始对象
class Plane {
  fire() {
    console.log('发射普通子弹')
  }
}

// 装饰者给原始对象添加额外的职责
class AtomDecorator {
  constructor(plane) {
    this.plane = plane
  }

  fire() {
    this.plane.fire()
    console.log('发射原子弹')
  }
}

const plane = new Plane()
plane.fire() // 发射普通子弹

const atomDecorator = new AtomDecorator(plane)
atomDecorator.fire() // 发射普通子弹 发射原子弹

11、状态模式

区分事物内部的状态,事物内部状态的改变往往会带来事物的行为改变

js 复制代码
// 事物内部的状态
class StateContent {
  constructor() {
    this.state = new StateA(this)
  }

  setState(state) {
    this.state = state
  }

  request() {
    this.state.handle()
  }
}

class State {
  constructor(context) {
    this.context = context
  }

  handle() {}
}

class StateA extends State {
  handle() {
    console.log('Change State to B')
    this.context.setState(new StateB(this.context))
  }
}

class StateB extends State {
  handle() {
    console.log('Change State to A')
    this.context.setState(new StateA(this.context))
  }
}

const context = new StateContent()
context.request() // Change State to B
context.request() // Change State to A
context.request() // Change State to B

状态模式定义了状态和行为之间的关系, 缺点是会在系统中定义许多状态类

12、适配器模式

解决两个软件实体间的接口不兼容

js 复制代码
class OldPrinter {
  print(text) {
    console.log(`Old Printer: ${text}`)
  }
}
class NewPrinter {
  printFormatted(text) {
    console.log(`New Printer: ${text}`)
  }
}
// 适配器类
class PrinterAdapter {
  constructor(newPrinter) {
    this.newPrinter = newPrinter
  }
  print(text) {
    this.newPrinter.printFormatted(text)
  }
}
// 使用适配器模式
const oldPrinter = new OldPrinter()
const newPrinter = new NewPrinter()
const printerAdapter = new PrinterAdapter(newPrinter)
printerAdapter.print('Hello, Adapter Pattern!') // New Printer: Hello, Adapter Pattern!

主要就是解决两个已有接口之间不匹配的问题

三、结语

文本是最近阅读 《JavaScript设计模式与开发实践》 一书后,总结的部分

文中如有错误,欢迎大家在评论区指正,如果本文对你有帮助, 记得点赞👍关注❤️

如需转载请标明作者和出处


相关推荐
昕冉4 分钟前
利用 Axrue9 中继器实现表格数据的查询
设计模式·设计
Coca7 分钟前
Vue 3 缩放盒子组件 ScaleBox:实现内容动态缩放与坐标拾取偏移矫正
前端
枫叶kx8 分钟前
发布一个angular的npm包(包含多个模块)
前端·npm·angular.js
工呈士9 分钟前
Webpack 剖析与策略
前端·面试·webpack
lyc23333311 分钟前
鸿蒙Next智能家居:轻量化模型的场景化落地
前端
天生我材必有用_吴用11 分钟前
Three.js开发必备:几何体BufferGeometry顶点详解
前端
憨憨是条狗11 分钟前
Vue + Vant H5 应用中实现 SSE 实时通知及系统通知功能
前端
dremtri12 分钟前
双 Token 认证机制详解与完整 Demo
前端·后端
CnLiang12 分钟前
fnm无缝切换项目的pnpm和node脚本化实践
前端·javascript
ze_juejin13 分钟前
JavaScript类型之Symbol
javascript