回调在事件中的妙用
Posted 张居斜
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了回调在事件中的妙用相关的知识,希望对你有一定的参考价值。
回调定义
CallBack: A callback is a function that is passed as an argument to another function and is executed after its parent function has completed.
### 回调: 回头调用,函数 A 的事先干完,回头再调用函数 B。
函数 A 的参数为函数 B, 函数 B 被称为回调函数。至于为何要用参数的形式传入,而不是直接在 A 中直接调用 B 函数,主要是为了变量的灵活性考虑。
为何要使用回调?
- 比较常见的情况是两个不同模块之间需要相互调用
事件中的使用。
详细说一下最近使用一个事件的时候遇到的问题,当时琢磨了半天没有想到解决方案,最后同事一句话点醒我,为毛不用回调,问题解决了。
需求如下:
创建一个标注,同时具体 撤销 与 恢复 功能, 具体介绍如下:想实现一个 cad 里面的创建标注的功能, 用户点击创建标注按钮 --> 点击绘图界面,创建一条跟随鼠标移动的直线 --> 再次绘图界面,创建跟随鼠标拖动标注 -->
第三次点击绘图界面,确定标注位置。在操作过程中,按 Esc 键,可取消创建。创建的标注可以 撤销 与 恢复,也就是 Undo & Redo 。- 命令模式,具体创建标注的类如下:
export class DimAddCmd implements ICommand {
undoDimSpritePairs: Stack<DimSpritePair>;
redoDimSpritePairs: Stack<DimSpritePair>;
constructor() {
this.undoDimSpritePairs = new Stack<DimSpritePair>();
this.redoDimSpritePairs = new Stack<DimSpritePair>();
}
/**
* 执行创建命令
* @param {Function} success
* @returns {boolean}
* @memberof DimAddCmd
*/
Execute(): boolean {
let dimEvent = new DimEvent();
let flag = false;
dimEvent.SelectDimButton();
Laya.stage.on(Laya.Event.MOUSE_MOVE, dimEvent, dimEvent.OnShowClickPoint);
Laya.stage.on(Laya.Event.MOUSE_DOWN, dimEvent, dimEvent.OnDrawDim, [this.undoDimSpritePairs, flag]);
Laya.stage.on(Laya.Event.KEY_DOWN, dimEvent, dimEvent.DimKeyInfo);
return flag;
}
Undo(): void {
let currentPair = this.undoDimSpritePairs.Pop();
if (currentPair) {
this.redoDimSpritePairs.Push(currentPair);
currentPair.dimSp.removeSelf();
}
else {
console.log("add dim undo stack is empty!")
}
}
Redo(): void {
let currentPair = this.redoDimSpritePairs.Pop();
if (currentPair) {
this.undoDimSpritePairs.Push(currentPair);
currentPair.dimParentSp.addChild(currentPair.dimSp);
}
else {
console.log("add_dim redo stack is empty!")
}
}
}
- 命令模式中的 control 类:
module Command {
export class Control {
onCommands: ICommand[]
undoCommands: Stack<ICommand>;
redoCommands: Stack<ICommand>;
private _cmdNum: number = 7;
constructor() {
this.onCommands = new Array<ICommand>(this._cmdNum);
this.undoCommands = new Stack<ICommand>();
this.redoCommands = new Stack<ICommand>();
// TODO: 定义枚举,实现按键与命令的绑定
for (let i = 0; i < this._cmdNum; i++) {
this.onCommands[i] = null; // TODO: create NoCommand class
}
}
public SetCommand(cmd: ICommand, cmdType: CommandType): void {
this.onCommands[cmdType] = cmd;
}
public OnButttonWasPressed(cmdType: CommandType): void {
let cmd = this.onCommands[cmdType];
if (cmd) {
let isSuccess = cmd.Execute();
if(isSuccess)
{
this.undoCommands.Push(cmd);
}
}
}
public Undo(): void {
if (this.undoCommands.Count()) {
let cmd = this.undoCommands.Pop();
this.redoCommands.Push(cmd);
cmd.Undo();
}
else {
console.log("undo stack is empty!");
}
}
public Redo(): void {
if (this.redoCommands.Count()) {
let cmd = this.redoCommands.Pop();
this.undoCommands.Push(cmd);
cmd.Redo();
}
else {
console.log("redo stack is empty!");
}
}
}
}
其中涉及到两个函数的调用问题:
Control.ts:
public OnButttonWasPressed(cmdType: CommandType): void {
let cmd = this.onCommands[cmdType];
if (cmd) {
let isSuccess = cmd.Execute();
if(isSuccess)
{
this.undoCommands.Push(cmd);
}
}
}
DimAddCmd.ts:
public Execute(): boolean {
let dimEvent = new DimEvent();
dimEvent.SelectDimButton();
let flag = false;
Laya.stage.on(Laya.Event.MOUSE_MOVE, dimEvent, dimEvent.OnShowClickPoint);
Laya.stage.on(Laya.Event.MOUSE_DOWN, dimEvent, dimEvent.OnDrawDim, [this.undoDimSpritePairs, flag]);
Laya.stage.on(Laya.Event.KEY_DOWN, dimEvent, dimEvent.DimKeyInfo);
return flag;
}
以上代码块中,按键后,需要判断是否成功创建标注,若成功创建标注,则将 dimAddCmd 加入到 undoCommands 栈中。 从逻辑上来说,这样是没有问题的。那么问题在哪?
Execute() 方法中,创建标注的方法绑定在事件中,事件的触发是在另一个线程中执行, 因为 Mouse_Down 事件在我们点击画布之前,无法触发,所以 flag 的值永远都是 false。因此, undo 栈中永远无法添加绘制标注命令。
---
如果将 “命令 push 到栈中” 的操作放在事件函数里面来操作,是不是问题就解决了?
是滴,这样可以解决问题。但是要将 pushbutton 方法静态化,不是特别方便。
这个时候,使用回调的概念,将函数当参数传入,问题轻松加愉快的就解决了。
具体代码如下:
Control.ts:
// 创建一个匿名方法,在这个方法里面完成 “push cmd 到 undo 栈”, 并将这个方法座位 Execute() 方法的参数
public OnButttonWasPressed(cmdType: CommandType): void {
let cmd = this.onCommands[cmdType];
if (cmd) {
// 注意此处改变
cmd.Execute(()=>{ this.undoCommands.Push(cmd);}); // 多个参数的统一
}
}
DimAddCmd.ts:
public Execute(success: Function): boolean {
let dimEvent = new DimEvent();
dimEvent.SelectDimButton();
Laya.stage.on(Laya.Event.MOUSE_MOVE, dimEvent, dimEvent.OnShowClickPoint);
// 注意方法中传入了一个函数
Laya.stage.on(Laya.Event.MOUSE_DOWN, dimEvent, dimEvent.OnDrawDim, [this.undoDimSpritePairs, success]);
Laya.stage.on(Laya.Event.KEY_DOWN, dimEvent, dimEvent.DimKeyInfo);
}
这样,点击绘图面版,触发标注创建后, 在 onDrawDim 方法里面,执行一次 success 函数,即可将 cmd 添加到 undo 栈中去。
通过以上方式,可以中其本模块中调用其它模块变量,有些细节与严格意义上回调不一致,但基本思想一致。
以上是关于回调在事件中的妙用的主要内容,如果未能解决你的问题,请参考以下文章
Java设计模式补充:回调模式事件监听器模式观察者模式(转)
对“xxx”类型的已垃圾回收委托进行了回调。这可能会导致应用程序崩溃损坏和数据丢失。向非托管代码传递委托时,托管应用程序必须让这些委托保持活动状态,直到确信不会再次调用它们。 错误解决一例。(代码片段