状态模式
状态模式允许一个对象在其内部状态发生改变时,能够改变原本的行为.
举例子
假如现在我们需要设计一个售票机器,主要出售 巴士、火车、飞机票等,价格分别为 50、150、1000,并且能够根据剩余票数决定是否能够继续购买.
通过策略模式实现核心代码逻辑
有了上面的 策略模式 的思想,立马就可以设计出如下的代码:
缺点:没有根据剩余票数决定是否可以继续售卖,主要原因就在于抽离的 ticketTypeMap
和 TicketMachine
之间的状态没有关联
const ticketTypeMap = { bus() { // do other thing return 50; }, train() { // do other thing return 150; }, plane() { // do other thing return 1000; }, }; class TicketMachine { constructor() { // 剩余票数 this.remain = { bus: 100, train: 150, plane: 200, }; } selling(type) { return ticketTypeMap[type](); } } 复制代码
关联对象状态 — 函数传参
通过函数传参的方式将对象传递给目标函数,让目标函数通过该对象访问和修改对象内部的状态.
const ticketTypeMap = { bus(remain) { if (remain.bus <= 0) return Error("抱歉,巴士票已售完"); remain.bus--; return 50; }, train(remain) { if (remain.train <= 0) return Error("抱歉,火车票已售完"); remain.train--; return 150; }, plane(remain) { if (remain.plane <= 0) return Error("抱歉,飞机票已售完"); remain.plane--; return 1000; }, }; class TicketMachine { constructor() { // 剩余票数 this.remain = { bus: 100, train: 150, plane: 200, }; } selling(type) { return ticketTypeMap[type](this.remain); } } 复制代码
关联对象状态 — 整合方法
实际上 ticketTypeMap
映射的方法和 TicketMachine
有较强的关联性,不应该单独存在,因此,可以将这个映射对象整合进 TicketMachine
当中
class TicketMachine { constructor() { // 剩余票数 this.remain = { bus: 100, train: 150, plane: 200, }; } ticketTypeMap = { that: this, bus() { const { remain } = this.that; if (remain.bus <= 0) return Error("抱歉,巴士票已售完"); remain.bus--; return 50; }, train() { const { remain } = this.that; if (remain.train <= 0) return Error("抱歉,火车票已售完"); remain.train--; return 150; }, plane() { const { remain } = this.that; if (remain.plane <= 0) return Error("抱歉,飞机票已售完"); remain.plane--; return 1000; }, }; selling(type) { return this.ticketTypeMap[type](); } } 复制代码
观察者模式
观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个目标对象,当这个目标对象的状态发生变化时,会通知所有观察者对象,使它们能够自动更新.
vue 中的观察者模式
vue 中的响应式原理就使用了 观察者模式,我们简单回顾一下其工作流程:
compile
:将模板内容编译得到对应的render
渲染函数render
:渲染函数执行生成VNode
,通过patch
函数初始化视图view
Observe
:负责将data
中返回的对象进行数据劫持(getter/setter
),且其中会使用Dep
来实现watcher
的存储,相当于 被观察者Dep
:在触发getter
时执行dep.depend()
实际上执行的是watcher.addDep()
,该方法会将当前的dep
对象保存到Watcher
,同时将当前的watcher
通过dep.addSub()
添加到Dep
中Watcher
:相当于 观察者,提供统一的update()
方法供Dep
调用
data changed
:响应式数据发生变更,触发数据劫持操作setter
- 进而执行
dep.notify()
方法,通过循环去执行watcher.update()
方法,即执行queueWatcher()
将watcher
添加到queue
队列中 - 最后由
scheduler
调度器 中执行nextTick(flushSchedulerQueue)
进行异步队列刷新操作
以上过程中,显然 Observe
和 Watcher
就是 被观察者 和 观察者 ,因为 Observe
中实现了对 Watcher
的收集和监听到数据状态发生变化时通知 Watcher
更新的处理,可以认为 Dep
只是 Observe
中使用到的一个存储和派发 Watcher
的工具.
发布订阅模式
发布订阅模式有三个核心:发布者、事件中心、订阅者,且发布订阅模式中的 发布者 和 订阅者 不能直接进行通信,必须要经过 事件中心 来统一调度.
与观察者模式的区别
实际上,发布订阅模式和观察者模式在概念上非常相似,做的事情也都一致,主要区别在于:
- 发布订阅模式依赖于 事件中心 统一调度 发布者 和 订阅者,发布者 和 订阅者 不直接进行通信
- 观察者模式中的 被观察者 和 观察者 是直接建立连接的,被观察者 需要保存 观察者 的信息,观察者 需要提供统一的 方法 供观察者进行使用
实现发布订阅模式
vue
中的 全局事件总线(Event Bus
)和 node
中的 Event Emitter
,甚至是浏览器中的事件注册(addEventListener
)和执行,它们都属于发布订阅模式.
下面实现一个简单的发布订阅模式:
class EventEmitter { constructor() { this.handlers = {}; } on(name, handle) { if (!this.handlers[name]) { this.handlers[name] = []; } this.handlers[name].push(handle); } emit(name, ...args) { if (this.handlers[name]) { this.handlers[name].forEach((handle) => { handle(...args); }); } } off(name, handle) { if (this.handlers[name]) { this.handlers[name] = this.handlers[name].filter((h) => { if (handle) return h !== handle; return false; }); } } once(name, handle) { const onceHandle = (...args) => { handle(...args); this.off(name, onceHandle); }; this.on(name, onceHandle); } } 复制代码
迭代器模式
迭代器模式是指提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示,核心目的就是 遍历.
JavaScript 中的遍历方式
- Array :for...of、for...in、forEach、map、filter
- Object :for...in
- Map :for...of、forEach
- Set :for...of、forEach
看起来很难有一种方法能够兼容以上几种数据结构的遍历方式,即不需要考虑数据结构本身就能实现遍历的目的,但我们可以基于 ES6
的 Symbol.iterator
实现自定义迭代器.
Symbol.iterator 实现通用迭代
Symbol.iterator
为每一个对象定义了默认的迭代器,拥有该迭代器后就可以被 for...of
循环使用.
function $each(data, handle) { if (typeof data !== "object") throw TypeError("data should be object!"); if (!data[Symbol.iterator]) { Object.prototype[Symbol.iterator] = function () { let i = 0; let keys = Reflect.ownKeys(this); return { next() { const done = i >= keys.length; return { value: done ? undefined : keys[i++], done, }; }, }; }; } for (const item of data) { handle(item); } } 复制代码
最后
大前端的各种新技术层出不穷,很容易忽视如数据结构、设计模式等基础内容,笔者最近也正在学习设计模式相关的内容,其实看很多设计模式相关的内容,很少有讲得简单易懂的,这里还是向那些需要学习设计模式相关内容的同学推荐修言大佬的 JavaScript 设计模式核⼼原理与应⽤实践 .