命令模式
命令模式是最简单和优雅的模式之一,命令模式中的命令(command)指的是一个执行某些特定事情的指令。
命令模式最常见的应用场景是:有时候需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是什么。此时希望用一种松耦合的方式来设计程序,使得请求发送者和请求接收者能够消除彼此之间的耦合关系。
菜单程序
1const button1 = document.querySelector('#button1')2const button2 = document.querySelector('#button2')3const button3 = document.querySelector('#button3')45const setCommand = function (button, command) {6 button.onclick = function () {7 command.execute()8 }9}1011const MenuBar = {12 refresh() {13 //14 },15}1617const SubMenu = {18 add() {19 //20 },21 del() {22 //23 },24}2526class RefreshMenuBarCommand {27 constructor(receiver) {28 this.receiver = receiver29 }3031 execute() {32 this.receiver.refresh()33 }34}353637class AddSubMenuCommand {38 constructor(receiver) {39 this.receiver = receiver40 }4142 execute() {43 this.receiver.add()44 }45}4647class DelSubMenuCommand {48 constructor(receiver) {49 this.receiver = receiver50 }5152 execute() {53 this.receiver.del()54 }55}5657const refreshMenuBarCommand = new RefreshMenuBarCommand(MenuBar)58const addSubMenuCommand = new AddSubMenuCommand(SubMenu)59const delSubMenuCommand = new DelSubMenuCommand(SubMenu)6061setCommand(button1, refreshMenuBarCommand)62setCommand(button2, addSubMenuCommand)63setCommand(button3, delSubMenuCommand)
JavaScript 中的命令模式
传统 OOP 语言中,命令模式的由来,其实是回调(callback)函数的一个面向对象的替代品
JavaScript 作为将函数作为一等对象的语言,跟策略模式一样,命令模式也早已融入到了 JavaScript 语言之中。运算块不一定要封装在 command.execute 方法中,也可以封装在普通函数中。函数作为一等对象,本身就可以被四处传递。即使我们依然需要请求“接收者”,那也未必使用面向对象的方式,闭包可以完成同样的功能。
1const bindClick = function(button, func) {2 button.onclick = func3}45const MenuBar = {6 refresh() {7 console.log('刷新菜单界面')8 }9}1011const SubMenu = {12 add() {13 console.log('增加子菜单')14 },15 del() {16 console.log('删除子菜单')17 }18}1920bindClick(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 39}).then(r => {10 console.log(r)11})12console.log(4)1314// 输出:1 4 2 3
熟悉事件循环的都可以看出它的执行顺序:
new Promise(cb)
console.log(1) // 同时 setTimeout 在浏览器的定时器线程挂起
两个 then 方法把 cb 加入 onFulfilledCallbacks
console.log(4)
距步骤 2 一秒后 setTimeout 把 cb 加入执行队列
执行队列为空,执行 cb,resolve(2)
resolve 时 onFulfilledCallbacks 中的 cb 依次执行
关键就是第 7 步中的 onFulfilledCallbacks.forEach(cb => cb(self.value)) 是在 resolve 时依次全部执行了,由此实现的微任务