回调在事件中的妙用

回调定义

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 栈中去。

通过以上方式,可以中其本模块中调用其它模块变量,有些细节与严格意义上回调不一致,但基本思想一致。

posted on 2019-04-04 18:24  张居斜  阅读(361)  评论(0编辑  收藏  举报

导航