JavaScript设计模式
@ 姜波 | 星期二,八月 4 日,2020 年 | 6 分钟阅读 | 更新于 星期二,八月 4 日,2020 年

工厂模式

  • 将new操作单独封装
  • 遇到new时,就要考虑是否该使用工厂模式

UML类图

Image text

实现

class Product {
  constructor(name) {
    this.name = name
  }
  init() {
    console.log('init')
  }
  fun1() {}
  fun2() {}
}
class Creator {
  creat(name) {
    return new Product(name)
  }
}
// 测试
let creator = new Create()
let p = creator.create('p1')

场景

  • jQuery-$(‘div’)
  • React.createElement
  • Vue异步组件

单例模式

  • 系统中被唯一使用
  • 一个类只有一个实例

UML类图

Image text

实现

class SingleObject {
  login() {
    console.log('login')
  }
}
SingleObject.getInstance = (function() {
  let instance
  return function() {
    if(!instance) {
      instance = new SingleObject()
    }
    return instance
  }
})()
// 测试:只能使用静态函数getInstance,不能new SingleObject()
let obj1 = SingleObject.getInstance()
let obj2 = SingleObject.getInstance()
obj1 === obj2 // ture

场景

  • jQuery只有一个$
  • 登录框
  • 购物车
  • vuex和redux中store

适配器模式

  • 旧接口格式和使用者不兼容
  • 中间加一个适配转换接口

UML类图

Image text

实现

class Adaptee {
  specificRequest() {
    return '外国插头'
  }
}
class Target {
  constructor() {
    this.adaptee = new Adaptee()
  }
  request() {
    let info = this.adaptee.specificRequest()
    return '转换后中国插头'
  }
}
// 测试
let target = new Target()
target.request()

场景

  • 封装旧接口
  • vue computed

装饰器模式

  • 为对象添加新功能
  • 不改变其原有的结构和功能

UML类图

Image text

实现

class Circle {
  draw() {
    console.log('画圆形')
  }
}
class Decorator {
  constructor(circle) {
    this.circle = circle
  }
  draw() {
    this.circle.draw()
    this.setRedBorder(circle)
  }
  setRedBorder(circle) {
    console.log('设置红色边框')
  }
}
// 测试
let circle = new Circle()
circle.draw()
let dec = new Decorator(circle)
dec.draw()

场景

  • ES7装饰器
// 装饰类
function testDec(isDec) {
  return function(target) {
    target.isDec = isDec
  }
}
@testDec(false)
class Demo {
  
}
console.log(Demo.isDec)
// 装饰类
function mixins(...list) {
  return function(target) {
    Object.assign(target.prototype,...list)
  }
}
const Foo = {
  foo() {
    console.log('foo')
  }
}
@mixin(Foo)
class MyClass {
  
}
let obj = new MyClass()
obj.foo()
// 装饰方法
function readonly(target, name, descriptor) {
  // descriptor属性描述对象(Object.defineProperty中会用到),原来的值如下
  // {
  //   value: specifiedFunction,
  //   enumerable: false,
  //   configurable: true,
  //   writable: true
  // }
  descriptor.writable = false
  return descriptor
}
class Person {
  constructor() {
    this.first = 'A'
    this.last = 'B'
  }
  @readonly
  name(){
    return this.first + this.last
  }
}
let p = new Person()
console.log(p.name())
p.name = function() {
  
}
// 装饰方法
function log(target, name, descriptor) {
  let oldValue = descriptor.value
	descriptor.value = function() {
    console.log(name,arguments)
    return oldValue.apply(this,arguments)
  }
  return descriptor
}
class Math {
  @log
  add(a, b) {
    return a + b
  }
}
let math = new Math()
const result = math.add(2, 4)
  • core-decorators

代理模式

  • 使用者无权访问目标对象
  • 中间加代理,通过代理做授权和控制

UML类图

Image text

实现

class ReadImg {
  constructor(fileName) {
    this.fileName = fileName
    this.loadFromDisk() // 初始化
  }
  display() {
    console.log(this.fileName)
  }
  loadFromDisk() {
    console.log(this.fileName)
  }
}
class ProxyImg {
  constructor(fileName) {
    this.realImg = new ReadImg(fileName)
  }
  display() {
    this.realImg.display()
  }
}
// 测试
let proxyImg = new ProxyImg('a.png')
proxyImg.display()

场景

  • 网页事件代理
  • jQuery-$.proxy
  • ES6 Proxy

外观模式

  • 为子系统中的一组接口提供了一个高层接口
  • 使用者使用这个高层接口

UML类图

Image text

实现

function bindEvent(elem, type, selector, fn) {
  if(fn == null) {
    fn = selector
    selector = null
  }
  // ...
}
// 调用
bindEvent(elem, 'click', '#div1', fn)
bindEvent(elem, 'click', fn)

场景

  • 业务子系统

观察者模式

  • 发布&订阅
  • 一对多

UML类图

Image text

实现

// 主题,保存状态,状态变化之后触发所有观察者对象
class Subject {
  constructor() {
    this.state = 0
    this.observers = []
  }
  getState() {
    return this.state
  }
  setState(state) {
    this.state = state
    this.notifyAllObservers()
  }
  notifyAllObservers() {
    this.observers.forEach(observer => {
      observer.update()
    })
  }
  attach(observer) {
    this.observers.push(observer)
  }
}
// 观察者
class Observer {
  constructor(name, subject) {
    this.name = name
    this.subject = subject
    this.subject.attach(this)
  }
  update() {
    console.log(this.name, this.subject.getState())
  }
}
// 测试
let s = new Subject()
let o1 = new Observer('o1', s)
let o2 = new Observer('o2', s)
s.setState(1)

场景

  • 网页事件绑定

  • Promise

  • jQuery callbacks

  • nodejs自定义事件

  • nodejs中:处理http请求;多进程通讯

  • Vue和React组件生命周期触发

  • Vue watch

迭代器模式

  • 顺序访问一个集合
  • 使用者无需知道集合的内部结构(封装)

UML类图

Image text

实现

class Iterator {
  constructor(container) {
    this.list = container.list
    this.index = 0
  }
  next() {
    if(this.hasNext()){
      return this.list[this.index++]
    }
    return null
  }
  hasNext() {
    if(this.index >= this.list.length){
      return false
    }
    return true
  }
}
class Container {
  constructor(list) {
    this.list = list
  }
  // 生成遍历器
  getIterator() {
    return new Iterator(this)
  }
}
// 测试
let arr = [1, 2, 3, 4, 5, 6]
let container = new Contaienr(arr)
let iterator = container.getIterator()
while(iterator.hasNext()) {
  console.log(iterator.next())
}

场景

  • jQuery each
  • ES6 Iterator
    • ES6语法中,有序集合的数据类型已经有很多
    • Array、Map、Set、String、TypedArray、arguments、NodeList
    • 需要有一个统一的遍历接口来遍历所有数据类型
    • 以上数据类型,都有[Symbol.iterator]属性
    • 属性值是函数,执行函数返回一个迭代器
    • 这个迭代器就有next方法可顺序迭代子元素
    • 可运行Array.prototype[Symbol.iterator]来测试
function each(data) {
  // 生成遍历器
  let iterator = data[Symbol.iterator]()
  let item = {done: false}
  while(!item.done) {
    item = iterator.next()
    if(!item.done){
      console.log(item.value)
    }
  }
}

function(each) {
  // 带有遍历器特性的对象,data[Symbol.iterator]有值
  for(let item of data) {
    console.log(item)
  }
}

状态模式

  • 一个状态有状态变化
  • 每次状态变化都会触发一个逻辑
  • 不能总是用if…else来控制

UML类图

Image text

实现

// 状态(红灯、绿灯、黄灯)
class State {
  constructor(color) {
    this.color = color
  }
  handle(context) {
    console.log(this.color)
    context.setState(this)
  }
}
class Context {
  constructor() {
    this.state = null
  }
  getState() {
    return this.state
  }
  setState(state) {
    this.state = state
  }
}
// 测试
let context = new Context()
let green = new State('green')
let yellow = new State('yellow')
let red = new State('red')
green.handle(context)
context.getState()

场景

  • 有限状态机

    开源lib:javascript-state-machine

  • Promise

其他设计模式

  • 策略模式、模版方法模式、职责链模式
  • 命令模式、备忘录模式、中介者模式
  • 访问者模式、解释器模式

原型模式

  • clone自己,生成一个新对象
// 一个原型对象
const prototype = {
  getName: function() {
    return this.first + '' + this.last
  },
  say: function() {
    console.log('hello')
  }
}
// 基于原型创建x
let x = Object.create(prototype)
x.first = 'A'
x.last = 'B'
console.log(x.getName())
x.say()
// 基于原型创建x
let y = Object.create(prototype)
y.first = 'C'
y.last = 'C'
console.log(y.getName())
y.say()

桥接模式

  • 用于把抽象化和实现化解耦
  • 使得二者可以独立变化
class Color {
  constructor(name) {
    this.name = name
  }
}
class Shape {
  constructor(name, color) {
    this.name = name
    this.color = color
  }
  draw() {
    console.log(this.color.name, this.name)
  }
}
let red = new Color('red')
let yellow = new Color('yellow')
let circle = new Shape('circle', red)
circle.draw()
let triangle = new Shape('triangle', yellow)
triangle.draw()

组合模式

  • 生成树形结构,表示“整体-部分”关系
  • 让整体和部分都具有一致的操作方式

享元模式

  • 共享内存(主要考虑内存,而非效率)
  • 相同的数据,共享使用

策略模式

  • 不同策略分开处理
  • 避免出现大量if…else或者switch…case
class User {
  constructor(type) {
    this.type = type
  }
  buy() {
    if(this.type === 'ordinary'){
      console.log('普通用户')
    }else if(this.type === 'member'){
      console.log('会用用户')
    }else if(this.type === 'vip'){
      console.log('vip用户')
    }
  }
}
let u1 = new User('ordinary')
u1.buy()
let u2 = new User('member')
u2.buy()
let u3 = new User('vip')
u3.buy()

class OrdinaryUser {
  buy() {
    console.log('普通用户')
  }
}
class MemberUser {
  buy() {
    console.log('会用用户')
  }
}
class VipUser {
  buy() {
    console.log('vip用户')
  }
}
let u1 = new OrdinaryUser()
let u2 = new MemberUser()
let u3 = new VipUser()

模板方法模式

  • 内部特殊顺序特殊逻辑方法封装、合并
  • 输出统一对外方法
class Action {
  handle() {
    this.handle1()
    this.handle2()
    this.handle3()
  }
  handle1() {}
  handle2() {}
  handle3() {}
}

职责链模式

  • 一步操作可能分多个职责角色来完成
  • 把角色分开,然后用链串起来
  • 将发起者和各个处理者进行隔离
// 请假审批
class Action {
  constructor(name) {
    this.name = name
    this.nextAction = null
  }
  setNextAction(action) {
    this.nextAction = action
  }
  handle() {
    console.log(this.name)
    if(this.nextAction != null){
      this.nextAciton.handle()
    }
  }
}
let a1 = new Action('组长')
let a2 = new Action('经理')
let a3 = new Action('总监')
a1.setNextAction('a2')
a2.setNextAction('a3')
a1.handle()

命令模式

  • 执行命令时,发布者和执行者分开
  • 中间加入命令对象,作为中转站
// 接受者
class Receiver {
  exec() {
    console.log('执行')
  }
}
// 命令者
class Command {
  constructor(receiver) {
    this.receiver = receiver
  }
  cmd() {
    console.log('执行命令')
    this.receiver.exec()
  }
}
// 触发者
class Invoker {
  constructor(command) {
    this.command = command
  }
  invoke() {
    console.log('开始')
    this.command.cmd()
  }
}
// 士兵
let soldier = new Receiver()
// 小号手
let trumpeter = new Command(soldier)
// 将军
let general = new Invoker(trumpeter)
general.invoke()

备忘录模式

  • 随时记录一个对象的状态变化
  • 随时可以恢复之前的某个状态(如撤销功能)
// 备忘类
class Memento {
  constructor(content) {
    this.content = content
  }
  getContent() {
    return this.content
  }
}
// 备忘列表
class CareTaker {
  constructor() {
    this.list = []
  }
  add(memento) {
    this.list.push(memento)
  }
  get(index) {
    return this.list[index]
  }
}
// 编辑器
class Editor {
  constructor() {
    this.content = null
  }
  setContent(content) {
    this.content = content
  }
  getContent() {
    return this.content
  }
  saveContentToMemento() {
    return new Memento(this.content)
  }
  getContentFromMenento() {
    this.content = memento.getContent()
  }
}
let editor = new Editor()
let careTaker = new CareTaker()
editor.setContent('111')
editor.setContent('222')
careTaker.add(editor.saveContentToMemento()) // 将当前内容备份
editor.setContent('333')
careTaker.add(editor.saveContentToMemento()) // 将当前内容备份
editor.setContent('444')

console.log(editor.getContent())
editor.getContentFromMenento(careTaker.get(1)) // 撤销
console.log(editor.getContent())
editor.getContentFromMenento(careTaker.get(0)) // 撤销
console.log(editor.getContent())

中介者模式

class A {
  constructor() {
    this.number = 0
  }
  setNumber(num, m) {
    this.number = num
    if(m){
      m.setB()
    }
  }
}
class B {
  constructor() {
    this.number = 0
  }
  setNumber(num, m) {
    this.number = num
    if(m){
      m.setA()
    }
  }
}
// 中介者
class Mediator {
  constructor(a, b) {
    this.a = a
    this.b = b
  }
  setB() {
    let number = this.a.number
    this.b.setNumber(number * 100)
  }
  setA() {
    let number = this.b.number
    this.a.setNumber(number / 100)
  }
}
let a = new A()
let b = new B()
let m = new Mediator(a, b)
a.setNumber(100, m)
console.log(a.number, b.number)
b.setNumber(100, m)
console.log(a.number, b.number)

访问者模式

  • 将数据操作和数据结构进行分离

解释器模式

  • 描述语言语法如何定义,如何解释和编译

公众号

Image text

QQ

Image text

微信

Image text

微信打赏

Image text

社交链接