如何深入理解并应用命令模式于JavaScript实践?

更新于
2026-09-26 12:32:26
2阅读来源:SEO问题
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计1461个文字,预计阅读时间需要6分钟。

如何深入理解并应用命令模式于JavaScript实践?

%E7%9B%AE%E5%BD%95+%E5%AE%9A%E4%B9%89+%E7%BB%93%E6%9E%84+%E5%AE%9E%E4%BE%8B+%E8%87%AA%E5%AE%9A%E4%B9%89%E5%BF%AB%E6%8D%B7%E9%94%AE+%E6%92%A4%E9%94%80%E4%B8%8E%E9%87%8D%E5%81%9A+%E5%BD%95%E5%88%B6%E4%B8%8E%E5%9B%9E%E6%94%BE+%E5%AE%8F%E5%91%BD%E4%BB%A4+%E6%80%BB%E7%BB%93+Encapsulate a request as an object, allowing parameterization, queuing, and logging of requests

目录
  • 定义
  • 结构
  • 实例
    • 自定义快捷键
    • 撤销与重做
    • 录制与回放
    • 宏命令
  • 总结

    定义

    Encapsulate a request as an object, thereby letting you parameterize other objects with different requests, queue or log requests,and support undoable operations.“

    「命令模式」将「请求」封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象,同时支持可撤消的操作。

    这里的「请求」的定义,并不是我们前端常说的「Ajax 请求」,而是一个「动作请求」,也就是发起一个行为。例如,通过遥控器关闭电视,这里的「关闭」就是一个请求。在命令模式中,我们将请求抽象成一个命令,这个命令是可复用的,它只关心它的接受者(电视);而对于动作的发起者(遥控器)来说,它只关心它所支持的命令有哪些,而不关心这些命令具体是做什么的。

    结构

    命令模式的类图如下:

    在该类图中,我们看到五个角色:

    • Client - 创建 Concrete Command 与 Receiver(应用层)。
    • Invoker - 命令的发出者,通常会持有命令对象,可以持有很多的命令对象。
    • Receiver - 命令接收者,真正执行命令的对象。任何类都可能成为一个接收者,只要它能够实现命令要求实现的相应功能。
    • Command - 命令接口。
    • ConcreteCommand - 命令接口的实现。

    Reciver 与 Invoker 没有耦合,当需要拓展功能时,通过新增 Command,因此命令模式符合开闭原则。

    实例

    自定义快捷键

    自定义快捷键是一个编辑器的最基本功能。通过命令模式,我们可以写出一个将键位与键位逻辑解耦的结构。

    interface Command { exec():void } type Keymap = { [key:string]: Command } class Hotkey { keymap: Keymap = {} constructor(keymap: Keymap) { this.keymap = keymap } call(e: KeyboardEvent) { const prefix = e.ctrlKey ? 'ctrl+' : '' const key = prefix + e.key this.dispatch(key) } dispatch(key: string) { this.keymap[key].exec() } } class CopyCommand implements Command { constructor(clipboard: any) {} exec() {} } class CutCommand implements Command { constructor(clipboard: any) {} exec() {} } class PasteCommand implements Command { constructor(clipboard: any) {} exec() {} } const clipboard = { data: '' } const keymap = { 'ctrl+x': new CutCommand(clipboard), 'ctrl+c': new CopyCommand(clipboard), 'ctrl+v': new PasteCommand(clipboard) } const hotkey = new Hotkey(keymap) document.onkeydown = (e) => { hotkey.call(e) }

    在本例中,hotkey是 Invoker,clipboard是 Receiver。当我们需要修改已有的 keymap 时,只需要新增或替换已有的key或Command即可。

    是不是觉得这个写法似曾相识?没错Redux 也是应用了命令模式,Store 相当于 Receiver,Action 相当于 Command,Dispatch 相当于 Invoker。

    撤销与重做

    基于命令模式,我们可以很容易拓展,使它支持撤销与重做。

    interface IPerson { moveTo(x: number, y: number): void } class Person implements Person { x = 0 y = 0 moveTo(x: number, y: number) { this.x = x this.y = y } } interface Command { exec(): void undo(): void } class MoveCommand implements Command { prevX = 0 prevY = 0 person: Person constructor(person: Person) { this.person = person } exec() { this.prevX = this.person.x this.prevY = this.person.y this.person.moveTo(this.prevX++, this.prevY++) } undo() { this.person.moveTo(this.prevX, this.prevY) } } const ezio = new Person() const moveCommand = new MoveCommand(ezio) moveCommand.exec() console.log(ezio.x, ezio.y) moveCommand.undo() console.log(ezio.x, ezio.y)

    录制与回放

    想想我们在游戏中的录制与回放功能,如果将角色的每个动作都作为一个命令的话,那么在录制时就能够得到一连串的命令队列。

    class Control { commands: Command[] = [] exec(command) { this.commands.push(command) command.exec(this.person) } } const ezio = new Person() const control = new Control() control.exec(new MoveCommand(ezio)) control.exec(new MoveCommand(ezio)) console.log(control.commands)

    当我们有了命令队列,我们又能够很容易得进行多次的撤销和重做,实现一个命令的历史记录。只需要移动当前命令队列的指针即可。

    class CommandHistory { commands: Command[] = [] index = 0 get currentCommand() { return this.commands[index] } constructor(commands: Command[]) { this.commands = commands } redo() { this.index++ this.currentCommand.exec() } undo() { this.currentCommand.undo() this.index-- } }

    同时,如果我们将命令序列化成一个对象,它便可以用于保存与传递。这样我们将它发送到远程计算机,就能实现远程控制ezio移动的功能。

    [{ type: 'move', x: 1, y: 1, }, { type: 'move', x: 2, y: 2, }]

    宏命令

    对Command进行一些简单的处理就能够将已有的命令组合起来执行,将其变成一个宏命令。

    class BatchedCommand implements Command { commands = [] constructor(commands) { this.commands = commands } exec() { this.commands.forEach(command => command.exec()) } } const batchedMoveCommand = new BatchedCommand([ new MoveCommand(ezio), new SitCommand(ezio), ]) batchedMoveCommand.exec()

    如何深入理解并应用命令模式于JavaScript实践?

    总结

    通过以上几个例子,我们可以看出命令模式有一下几个特点:

    • 低耦合,彻底消除了接受者与调用者之间的耦合。
    • 易拓展,只需要增加新的命令便可拓展出新功能。
    • 支持序列化,易于实现保存与传递。
    • 容易导致 Command 类庞大。

    以上就是详解Javascript实践中的命令模式的详细内容,更多关于Javascript命令模式的资料请关注自由互联其它相关文章!

    本文共计1461个文字,预计阅读时间需要6分钟。

    如何深入理解并应用命令模式于JavaScript实践?

    %E7%9B%AE%E5%BD%95+%E5%AE%9A%E4%B9%89+%E7%BB%93%E6%9E%84+%E5%AE%9E%E4%BE%8B+%E8%87%AA%E5%AE%9A%E4%B9%89%E5%BF%AB%E6%8D%B7%E9%94%AE+%E6%92%A4%E9%94%80%E4%B8%8E%E9%87%8D%E5%81%9A+%E5%BD%95%E5%88%B6%E4%B8%8E%E5%9B%9E%E6%94%BE+%E5%AE%8F%E5%91%BD%E4%BB%A4+%E6%80%BB%E7%BB%93+Encapsulate a request as an object, allowing parameterization, queuing, and logging of requests

    目录
    • 定义
    • 结构
    • 实例
      • 自定义快捷键
      • 撤销与重做
      • 录制与回放
      • 宏命令
    • 总结

      定义

      Encapsulate a request as an object, thereby letting you parameterize other objects with different requests, queue or log requests,and support undoable operations.“

      「命令模式」将「请求」封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象,同时支持可撤消的操作。

      这里的「请求」的定义,并不是我们前端常说的「Ajax 请求」,而是一个「动作请求」,也就是发起一个行为。例如,通过遥控器关闭电视,这里的「关闭」就是一个请求。在命令模式中,我们将请求抽象成一个命令,这个命令是可复用的,它只关心它的接受者(电视);而对于动作的发起者(遥控器)来说,它只关心它所支持的命令有哪些,而不关心这些命令具体是做什么的。

      结构

      命令模式的类图如下:

      在该类图中,我们看到五个角色:

      • Client - 创建 Concrete Command 与 Receiver(应用层)。
      • Invoker - 命令的发出者,通常会持有命令对象,可以持有很多的命令对象。
      • Receiver - 命令接收者,真正执行命令的对象。任何类都可能成为一个接收者,只要它能够实现命令要求实现的相应功能。
      • Command - 命令接口。
      • ConcreteCommand - 命令接口的实现。

      Reciver 与 Invoker 没有耦合,当需要拓展功能时,通过新增 Command,因此命令模式符合开闭原则。

      实例

      自定义快捷键

      自定义快捷键是一个编辑器的最基本功能。通过命令模式,我们可以写出一个将键位与键位逻辑解耦的结构。

      interface Command { exec():void } type Keymap = { [key:string]: Command } class Hotkey { keymap: Keymap = {} constructor(keymap: Keymap) { this.keymap = keymap } call(e: KeyboardEvent) { const prefix = e.ctrlKey ? 'ctrl+' : '' const key = prefix + e.key this.dispatch(key) } dispatch(key: string) { this.keymap[key].exec() } } class CopyCommand implements Command { constructor(clipboard: any) {} exec() {} } class CutCommand implements Command { constructor(clipboard: any) {} exec() {} } class PasteCommand implements Command { constructor(clipboard: any) {} exec() {} } const clipboard = { data: '' } const keymap = { 'ctrl+x': new CutCommand(clipboard), 'ctrl+c': new CopyCommand(clipboard), 'ctrl+v': new PasteCommand(clipboard) } const hotkey = new Hotkey(keymap) document.onkeydown = (e) => { hotkey.call(e) }

      在本例中,hotkey是 Invoker,clipboard是 Receiver。当我们需要修改已有的 keymap 时,只需要新增或替换已有的key或Command即可。

      是不是觉得这个写法似曾相识?没错Redux 也是应用了命令模式,Store 相当于 Receiver,Action 相当于 Command,Dispatch 相当于 Invoker。

      撤销与重做

      基于命令模式,我们可以很容易拓展,使它支持撤销与重做。

      interface IPerson { moveTo(x: number, y: number): void } class Person implements Person { x = 0 y = 0 moveTo(x: number, y: number) { this.x = x this.y = y } } interface Command { exec(): void undo(): void } class MoveCommand implements Command { prevX = 0 prevY = 0 person: Person constructor(person: Person) { this.person = person } exec() { this.prevX = this.person.x this.prevY = this.person.y this.person.moveTo(this.prevX++, this.prevY++) } undo() { this.person.moveTo(this.prevX, this.prevY) } } const ezio = new Person() const moveCommand = new MoveCommand(ezio) moveCommand.exec() console.log(ezio.x, ezio.y) moveCommand.undo() console.log(ezio.x, ezio.y)

      录制与回放

      想想我们在游戏中的录制与回放功能,如果将角色的每个动作都作为一个命令的话,那么在录制时就能够得到一连串的命令队列。

      class Control { commands: Command[] = [] exec(command) { this.commands.push(command) command.exec(this.person) } } const ezio = new Person() const control = new Control() control.exec(new MoveCommand(ezio)) control.exec(new MoveCommand(ezio)) console.log(control.commands)

      当我们有了命令队列,我们又能够很容易得进行多次的撤销和重做,实现一个命令的历史记录。只需要移动当前命令队列的指针即可。

      class CommandHistory { commands: Command[] = [] index = 0 get currentCommand() { return this.commands[index] } constructor(commands: Command[]) { this.commands = commands } redo() { this.index++ this.currentCommand.exec() } undo() { this.currentCommand.undo() this.index-- } }

      同时,如果我们将命令序列化成一个对象,它便可以用于保存与传递。这样我们将它发送到远程计算机,就能实现远程控制ezio移动的功能。

      [{ type: 'move', x: 1, y: 1, }, { type: 'move', x: 2, y: 2, }]

      宏命令

      对Command进行一些简单的处理就能够将已有的命令组合起来执行,将其变成一个宏命令。

      class BatchedCommand implements Command { commands = [] constructor(commands) { this.commands = commands } exec() { this.commands.forEach(command => command.exec()) } } const batchedMoveCommand = new BatchedCommand([ new MoveCommand(ezio), new SitCommand(ezio), ]) batchedMoveCommand.exec()

      如何深入理解并应用命令模式于JavaScript实践?

      总结

      通过以上几个例子,我们可以看出命令模式有一下几个特点:

      • 低耦合,彻底消除了接受者与调用者之间的耦合。
      • 易拓展,只需要增加新的命令便可拓展出新功能。
      • 支持序列化,易于实现保存与传递。
      • 容易导致 Command 类庞大。

      以上就是详解Javascript实践中的命令模式的详细内容,更多关于Javascript命令模式的资料请关注自由互联其它相关文章!