回调在事件中的妙用

回调定义

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

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

原文地址:https://www.cnblogs.com/yaolin1228/p/10656442.html

时间: 2024-10-29 11:09:40

回调在事件中的妙用的相关文章

Node.js学习笔记【1】入门(服务器JS、函数式编程、阻塞与非阻塞、回调、事件、内部和外部模块)

笔记来自<Node入门>@2011 Manuel Kiessling JavaScript与Node.js Node.js事实上既是一个运行时环境,同时又是一个库. 使用Node.js时,我们不仅仅在实现一个应用,同时还实现了整个HTTP服务器. 一个基础的HTTP服务器 server.js:一个可以工作的HTTP服务器 var http = require("http"); http.createServer(function(request, response) { r

Android 事件中 OnTouch 事件

Android 事件中 OnTouch  事件: 实现的方式: 1 监听 2 回调 1 监听: package com.example.conflicttest; import android.os.Bundle; import android.app.Activity; import android.util.Log; import android.view.Menu; import android.view.MotionEvent; import android.view.View; imp

浅谈“回调”在程序设计中的好处

1:回调还是返回(return) 在写代码的时候,我们经常碰到这样的场景:调用一个函数或者方法时需要返回多个值给上级调用者,如示例: void methodA(){    Wrap w = methodB();     w.one; //use    w.two; } Wrap methodB(){     do something;     return Wrap; } class Wrap{    Type one;    Type two; } 上面是我刚开始写代码时候常用的方式,在多个类

Node js 安装+回调函数+事件

/* 从网站 https://nodejs.org/zh-cn/ 下载 这里用的 9.4.0 版本 下载完安装 安装目录是 D:\ApacheServer\node 一路默认安装 安装后打开cmd命令行输入 path 在显示的结果中查找是否有 D:\ApacheServer\node有的话表示环境变量中已经包含了 D:\ApacheServer\node\ 可以在cmd中直接使用 node 这个命令 如在当前命令行中输入 node --version 显示 v9.4.0 当前nodejs的版本

回调在python中

回调 函数作为参数 函数执行=> 到一个参数函数时=> 调用另一个函数=> 回到主函数 #!/usr/bin/env python # -*- coding:utf-8 -*- __author__ = 'teng' def test(callback):     print 'test func begin'     callback() def test1(callback):     print 'test1 func begin'     for func in callback

WPF 在事件中绑定命令(可以在模版中绑定命令)

其实这也不属于MVVMLight系列中的东东了,没兴趣的朋友可以跳过这篇文章,本文主要介绍如何在WPF中实现将命令绑定到事件中. 上一篇中我们介绍了MVVMLight中的命令的用法,那么仅仅知道命令是如何构建使用的还不够,很多情况下我们都需要在某个事件触发的时候才去触发命令,所以将命令绑定到事件上是非常有效的做法,下面我们来接着实现将命令绑定到事件中. WPF实现命令绑定到事件 使用 System.Windows.Interactivity.dll 中的 Interaction 可以帮助我们实现

javascript事件之:jQuery事件中实例对象和拓展对象之间的通信

我们总结过jQery事件中的实例原型对象对外接口和拓展对象,现在我们看看他们是如何进行通信联系的. 先来看便捷方法: 1 //调用的还是实例对象下的on()和trigger() 2 jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " + 3 "mousedown mouseup mousemove mouseover mouseout mouseenter

touch事件中的touches、targetTouches和changedTouches详解

touches: 当前屏幕上所有触摸点的列表; targetTouches: 当前对象上所有触摸点的列表; changedTouches: 涉及当前(引发)事件的触摸点的列表 通过一个例子来区分一下触摸事件中的这三个属性: 1. 用一个手指接触屏幕,触发事件,此时这三个属性有相同的值. 2. 用第二个手指接触屏幕,此时,touches有两个元素,每个手指触摸点为一个值.当两个手指触摸相同元素时, targetTouches和touches的值相同,否则targetTouches 只有一个值.ch

.net控件事件中的Sender

private void button2_Click(object sender, RoutedEventArgs e) { } 最近看WPF内容,回顾下.net大家天天都在用,却不是十分关注的一个对象----sender 问:sender到底是什么呢? 答:所有的服务器控件中事件都会有(可能说的太绝对,应该说大多会吧),sender指的是触发事件的控件. private void button2_Click(object sender, RoutedEventArgs e) { Button