Skip to content

AHABHGK

命令模式

命令模式是最简单和优雅的模式之一,命令模式中的命令(command)指的是一个执行某些特定事情的指令。

命令模式最常见的应用场景是:有时候需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是什么。此时希望用一种松耦合的方式来设计程序,使得请求发送者和请求接收者能够消除彼此之间的耦合关系。

菜单程序

1const button1 = document.querySelector('#button1')
2const button2 = document.querySelector('#button2')
3const button3 = document.querySelector('#button3')
4
5const setCommand = function (button, command) {
6 button.onclick = function () {
7 command.execute()
8 }
9}
10
11const MenuBar = {
12 refresh() {
13 //
14 },
15}
16
17const SubMenu = {
18 add() {
19 //
20 },
21 del() {
22 //
23 },
24}
25
26class RefreshMenuBarCommand {
27 constructor(receiver) {
28 this.receiver = receiver
29 }
30
31 execute() {
32 this.receiver.refresh()
33 }
34}
35
36
37class AddSubMenuCommand {
38 constructor(receiver) {
39 this.receiver = receiver
40 }
41
42 execute() {
43 this.receiver.add()
44 }
45}
46
47class DelSubMenuCommand {
48 constructor(receiver) {
49 this.receiver = receiver
50 }
51
52 execute() {
53 this.receiver.del()
54 }
55}
56
57const refreshMenuBarCommand = new RefreshMenuBarCommand(MenuBar)
58const addSubMenuCommand = new AddSubMenuCommand(SubMenu)
59const delSubMenuCommand = new DelSubMenuCommand(SubMenu)
60
61setCommand(button1, refreshMenuBarCommand)
62setCommand(button2, addSubMenuCommand)
63setCommand(button3, delSubMenuCommand)

JavaScript 中的命令模式

传统 OOP 语言中,命令模式的由来,其实是回调(callback)函数的一个面向对象的替代品

JavaScript 作为将函数作为一等对象的语言,跟策略模式一样,命令模式也早已融入到了 JavaScript 语言之中。运算块不一定要封装在 command.execute 方法中,也可以封装在普通函数中。函数作为一等对象,本身就可以被四处传递。即使我们依然需要请求“接收者”,那也未必使用面向对象的方式,闭包可以完成同样的功能。

1const bindClick = function(button, func) {
2 button.onclick = func
3}
4
5const MenuBar = {
6 refresh() {
7 console.log('刷新菜单界面')
8 }
9}
10
11const SubMenu = {
12 add() {
13 console.log('增加子菜单')
14 },
15 del() {
16 console.log('删除子菜单')
17 }
18}
19
20bindClick(button1, MenuBar.refresh.bind(this))
21bindClick(button2, SubMenu.add.bind(this))
22bindClick(button3, SubMenu.del.bind(this))

撤销与重做

撤销:增加一个储存状态信息的栈,比如小球移动,就要有一个储存位置的栈,撤销时就是调用移动方法把小球移动到原来的位置

重做:如果状态信息比较复杂,比如 Canvas 画图,可以用一个 command 栈储存命令,每次撤销就是去掉最后的命令,把栈中的命令重新执行一遍,当然性能消耗较大

命令队列

当一个命令执行需要一定时间,需要命令队列

  • 事件循环中的事件队列

  • 浏览器的 UI 队列

当一个事件结束时,该如何通知队列,进行下一个任务,通常是使用异步,还可以使用发布订阅模式

宏命令

宏命令是一组命令的集合,通过执行宏命令的方式,可以一次执行一批命令

有点像组合模式

事件循环的命令模式

事件队列 - 命令队列

宏任务 - 宏命令

微任务 - 命令

不由得又让人想到了 Promise 🤣

Promise 中的 executor 中的任务是同步任务就同步执行,是异步就异步执行,源码就是 executor(resolve, reject) 没什么可说的,这里说一下他的微任务(then)的实现:

1const p = new Promise(resolve => {
2 console.log(1)
3 setTimeout(() => {
4 resolve(2)
5 }, 1000)
6}).then(r => {
7 console.log(r)
8 return 3
9}).then(r => {
10 console.log(r)
11})
12console.log(4)
13
14// 输出:1 4 2 3

熟悉事件循环的都可以看出它的执行顺序:

  1. new Promise(cb)

  2. console.log(1) // 同时 setTimeout 在浏览器的定时器线程挂起

  3. 两个 then 方法把 cb 加入 onFulfilledCallbacks

  4. console.log(4)

  5. 距步骤 2 一秒后 setTimeout 把 cb 加入执行队列

  6. 执行队列为空,执行 cb,resolve(2)

  7. resolve 时 onFulfilledCallbacks 中的 cb 依次执行

关键就是第 7 步中的 onFulfilledCallbacks.forEach(cb => cb(self.value)) 是在 resolve 时依次全部执行了,由此实现的微任务