Skip to content

观察者模式

观察者模式是前端工程中最常见的代码设计模式之一, 目前几乎所有的前端框架的核心就是观察者模式, 也就是所谓的状态驱动.

在 18 年前后, 前端的主流库还是 JQuery, JQuery 库帮助开发者封装 dom 操作, 比如选择器, 设置样式, 增加删除 dom 节点等等.

而最近几年前端框架爆发式增长, 他们用无需手动操作 dom 的方式碾压了 jquery, 旧的时代从此没落...

对比以下的代码:

ts
// 一段TODOList的jquery代码

const list = []

const genItem = item => {
  return $(`<li>${item.index} - ${Math.n}</li>`)
}

~(function getData() {
  setTimeout(() => {
    list = Array.from({ length: 20 }).map((_, index) => ({
      index,
      n: Math.random()
    }))

    $('.container').append(list.map(genItem))
  }, 300)
})()

$('.btn').click(function () {
  $('.container').append(genItem({ index: list.length, n: Math.random() }))
})

// 同样功能的vue
const list = shallowRef([])

~(function getData() {
  setTimeout(() => {
    list.value = Array.from({ length: 20 }).map(() => ({ n: Math.random() }))
  }, 300)
})()

const handleClick = () => {
  list.value = [...list.value, { n: Math.random() }]
}

显而易见的是, vue 的代码会少很多, 主要是帮我们省去了 dom 操作, 并且直接改值, 不需要封装方法就能触发 dom 的更新, 这就是观察者模式的强大之处.

何时使用

一句话概括就是: 每当一个事物发生改变就做一些动作时都能使用观察者模式.

当有人犯罪时, 警察就逮捕.

当感染病毒时, 免疫系统就开始消灭病毒.

当一个人无聊时, 就想找乐子.

当一个人不吃饭时, 就会饿... 😂

上面的三个例子都可以用程序来描述.

ts
// GTA(侠盗列车手)中的经典场景是, 只要你伤害了路人, 你就会被通缉, 通缉星级越高, 警察出动的人数就越多, 装备就越离谱.
// 下面我们用普通思维和观察者模式来描述一下.

// 列出全部对象

/** 主角 */
class Leader {
  killed = 0

  /** 射击 */
  shot(person: SomeOne, part: 'header' | 'body' | 'limb') {
    if (person.hp <= 0) return

    // 爆头
    if (part === 'header') {
      person.hp -= 100
    }
    // 打中身体
    else if (part === 'body') {
      person.hp -= 50
    }
    // 打中四肢
    else if (part === 'limb') {
      person.hp -= 15
    }

    if (person.hp <= 0) {
      this.killed += 1
    }
  }
}

class SomeOne {
  hp = 100
}

class Police {
  target = null

  constructor(target: any) {
    this.target = target

    this.arrest()
  }

  // 抓捕
  arrest() {}
}

const createPolices = (target, num: number) => {
  return Array.form({ length: num }).map(() => new Police())
}

const you = new Leader()

// 路人A
const someOneA = new SomeOne()
// 路人B
const someOneB = new SomeOne()

// 线性的逻辑
you.shot(someOneA)
if (you.killed > 0) {
  if (you.killed > 0 && you.killed <= 1) {
    createPolice(you, 2)
  } else if (you.killed > 1 && you.killed <= 5) {
    createPolice(you, 10)
  } else if (you.killed > 5) {
    createPolice(you, 30)
  }
}

you.shot(someOneB)
if (you.killed > 0) {
  if (you.killed > 0 && you.killed <= 1) {
    createPolice(you, 2)
  } else if (you.killed > 1 && you.killed <= 5) {
    createPolice(you, 10)
  } else if (you.killed > 5) {
    createPolice(you, 30)
  }
}

// 观察者的逻辑
watch(you, you => {
  if (you.killed <= 0) return

  if (you.killed > 0 && you.killed <= 1) {
    createPolice(you, 2)
  } else if (you.killed > 1 && you.killed <= 5) {
    createPolice(you, 10)
  } else if (you.killed > 5) {
    createPolice(you, 30)
  }
})

you.shot(someOneA)
you.shot(someOneB)
// ...

实现要点

比如我们在家里安装了监控来看看家里的宠物有没有偷吃.

ts

const createState = <T extends  Record<string, ayn>>(state: T, cb: (state: T) => void) => {
  const setState = (_state: Record<string, ayn>) => {
    Object.assign(state, _state)

    cb(state)
  }

  return [
    state, setState
  ]
}

const [state, setState] = createState({
  len: 0
}, (state) => {
  if (state.len < 4.3) {
    console.log('小型车')
  }
  else if (state.len >= 4.3 && state.len < 4.8) {
    console.log('A级车')
  }

  else if (state.len >= 4.8 && state.len < 5) {
    console.log('B级车')
  }
  else if (state.len >= 5 && state.len < 5.1) {
    console.log('C级车')
  }
  else if (state.len >= 5.1) {
    console.log('D级车')
  }
})

setState({ len: 4.6 })
setState({ len: 5.2 })
setState({ len: 4 })

MIT Licensed