本文深入解析JavaScript中的命令模式,通过按钮事件绑定、游戏角色移动及自动刷副本脚本等实际案例,展示如何利用命令模式实现请求发送者与接收者的解耦。文章详细讲解了简单撤销机制、基于栈的重播与撤销功能,以及宏命令的执行逻辑,帮助开发者掌握这一核心设计模式。
一、认识命令模式
所谓命令,是指执行特定任务的指令。以喝水为例,该过程包含将水倒入杯中并送入口中两个步骤。无论执行者是谁,也不管杯中装的是水还是其他饮料,核心关注点在于“倒水”和“饮用”这一流程本身。命令模式的核心在于关注过程而非具体的执行者或内容。
命令模式最常见的应用场景是:当需要向某些对象发送请求,但事先不知道请求的具体接收者是谁,也不清楚被请求的操作细节时。此时,采用松耦合的设计方式,能够有效消除请求发送者与接收者之间的依赖关系。
二、代码实现与实际应用场景
在团队协作开发中,解耦至关重要。假设两人分别负责按钮的美化与逻辑实现,负责逻辑的开发者可能不知道按钮最终绑定在页面的哪个位置,也不知道点击后的具体行为。此时,利用命令模式可以解开按钮与具体行为对象之间的耦合。
<script>
let btns = document.querySelectorAll('button');
// 写一个刷新命令
let refreshCommand = {
execute () {
console.log("刷新页面");
}
}
// 写一个登录按钮命令
let loginCommand = {
execute () {
console.log("执行登录操作");
}
}
let setCommand = function( button, command) {
button.onclick = function() {
command.execute();
}
}
// 假如第三个按钮用来负责刷新页面,第五个用来负责登录操作
setCommand( btns[2], refreshCommand);
setCommand( btns[4], loginCommand); 分析:我们将点击按钮后可能触发的事件分别封装,每个事件对象拥有独立的 execute 函数。通过 setCommand 函数将按钮功能进行绑定,最后根据需求为各个按钮安装对应的命令即可。
三、命令模式的撤销操作
下达命令后,若需回退到执行前的状态,就需要引入撤销操作。其实现方式通常是为命令对象增加 undo 或 unexecute 方法,用于执行与 execute 相反的操作。
// 假如写一个人物向左移的命令
let moveLeftCommand = {
execute() {
console.log("人物向左移动");
}
undo (){
console.log("人物向又移动");
}
}上述为简单的逻辑实现。如果某些撤销行为无法通过执行反向操作来实现,应如何处理?
为解决上述问题,我们可以采用一种基于栈的撤销方式。通过栈记录每一步命令,撤销时只需从栈中取出命令并依次执行,同时删除最后一条命令。若不删除最后一条命令而直接依次执行,则实现了重播功能。
这是一个通过 WASD 控制小球移动的代码实现。每次按下有效命令时,将该命令推入 commandStack 以记录小球状态变化。点击重播时,小球将沿之前的轨迹重新运动。
let role = document.getElementById('role');
let move = {
dom: role,
leftt:0,
top: 0,
up() {
console.log("向上");
this.top -= 10;
this.dom.style.top = this.top + 'px';
},
down(){
console.log("向下");
this.top += 10;
this.dom.style.top = this.top + 'px';
},
left() {
console.log("向左");
this.leftt -= 10;
this.dom.style.left = this.leftt + 'px';
},
right() {
console.log("向右");
this.leftt += 10;
this.dom.style.left = this.leftt + 'px';
}
}
commands = {
"119": "up", // W
"115": "down", // S
"97": "left", // A
"100": "right" // D
};
commandStack = []; // 保存命令的堆栈
let setCommand = function (receiver, state) {
return function() {
receiver[state]();
}
}
document.addEventListener('keypress', (e) => {
let code = e.charCode;
if(!commands[code]) {
return ;
}
let command = setCommand(move, commands[code]);
if( command ) {
command();
commandStack.push( command );
}
})
// 设置重播按钮
document.getElementById('replay').onclick = function() {
let command;
while(command = commandStack.shift()) {
command();
}
}四、宏命令
宏命令是一组命令的集合,执行宏命令即可一次性执行一批子命令。
通俗来说,如果有一件事需要重复执行,且每一步骤固定不变,我们可以将所有步骤打包。只需启动宏命令,即可自动完成所有步骤,无需人工干预后续过程。
需求:实现一个游戏自动刷副本的脚本。
let MacroCommand = function() {
return {
commandList: [],
add( command ) {
this.commandList.push(command);
},
execute () {
for(let i = 0,command; command = this.commandList[i++];){
command.execute();
}
}
}
}
let startGameCommand = {
execute() {
console.log('开始游戏')
}
}
let killGhostCommand = {
execute() {
console.log('打怪')
}
}
let giftCommand = {
execute() {
console.log("领取奖励")
}
}
let outGameCommand = {
execute() {
console.log("退出游戏")
}
}
let Game = MacroCommand();
Game.add(startGameCommand);
Game.add(killGhostCommand);
Game.add(giftCommand);
Game.add(outGameCommand);
Game.execute(); 我们只需将命令添加到宏命令队列中,它会自动执行内部的所有命令。开发者只需负责启动,无需关心执行细节,即可放心处理其他事务,等待脚本自动完成。
五、总结
命令模式包含傻瓜命令和智能命令。傻瓜命令包含命令接收者,而智能命令直接提供请求。智能命令与策略模式相似,但使用场景不同:策略模式中对象目标相同,仅实现算法不同;而命令模式的目标更具发散性。此外,命令模式还支持撤销和排队功能。
本文到此结束,希望能为读者带来帮助,也欢迎持续关注本站的更多内容!