`
flytreeleft
  • 浏览: 93124 次
  • 性别: Icon_minigender_1
  • 来自: 天津
社区版块
存档分类
最新评论

JavaScript动作迁移器

阅读更多

 

1. 设计背景

JavaScript动作迁移器来源于这样的环境:某个操作因为代码复用和增强其内聚性而被分为几个单独的动作(函数),这些动作中在逻辑上存在先后关系,即前一个动作完成后才能继续下一个动作,但是某些动作却是异步的(asynchronous),这样在编码过程中就不能按过程调用的方式来编写,必须将异步动作的下一个动作放到该异步动作中,这种调用方式,在代码量很小,其逻辑不是很复杂的情况下,还是可以的,但是当异步动作中代码量较大且逻辑复杂的情况下,会增加函数的耦合度,降低代码的阅读性,加大代码维护的难度。

为了减少代码的耦合,是逻辑更加清晰,于是设计产生了动作迁移器。使用动作迁移器,所有的动作都将在操作开始前进行定义,操作开始后,就将按照定义的动作序列调用各个动作。并且,每个动作可按照其运行的状态调用其他不同的动作或是内部不同的状态。

2. 术语解释

首先,解释一下该动作迁移器所涉及到的术语的含义:

动作迁移器(ActionTransfer),维护的是一系列动作的执行路线,使动作能够按照指定的迁移路径运行下去。

动作(Action),指的是一个确定的完整的执行过程,而不是执行片段,如判断是否为真之类的,该动作完成后没有后续的与该操作处理相关的过程,但其可以有执行后得到的数据,该数据可以被后续动作继续使用。

迁移路径(TransferPath),即一个动作到下一个动作的运行线路,它规定了某个动作执行完后,其后续所要执行的动作,或是该动作的完结。每个动作都可以有执行状态,可以从动作的某个状态迁移到另一个动作,或另一个动作的某个状态,也可以是某个动作的不同状态间的转移。

动作状态(简称状态,Status),即某个动作运行完毕后,可以通过状态指示其执行是成功还是失败或是出现异常等,动作迁移器可以根据其执行状态,沿着迁移路径继续进行处理。

动作的迁移可以是没有起点的,也可以有多个起点,动作迁移器不需从起点开始迁移,可以从任何一个动作开始迁移,开始迁移后,其迁移的路径是按照指定的路径运行下去,直到该条路径的终点(即没有下一个迁移动作)。

3. 功能结构

该动作迁移器内置的属性有当前动作(action)、初始动作(initAction)、和迁移路径(transferPath),并设方法start(启动初始动作)、setAction(设置当前迁移动作)、transfer(根据动作状态进行指定的迁移操作)、clear(清除迁移路径)。详细设计代码如下:

 

ActionTransfer = Ext.extend(Object, {  
    action : 'start'  
    , constructor : function(config, scope) {  
        var conf = config || {};  
          
        this.scope = scope || this;  
        this.initAction = conf.initAction || 'start';  
        // 迁移路径,包含start和end动作  
        this.transferPath = Ext.applyIf(conf.path || {}, {  
            'start' : function() {},  
            'end' : function() {}  
        });  
          
        if (conf.auto === true) {  
            this.start();  
        }  
    }  
    /** 
     * 启动迁移 
     * @return {ActionTransfer} this 
     */  
    , start : function() {  
        return this.setAction(this.initAction).transfer();  
    }  
    /** 
     * 设置当前动作 
     * @param {String} action the current action's name 
     * @param {Function} fn the function will be called, after setting action 
     * @return {ActionTransfer} this 
     */  
    , setAction : function(action, fn) {  
        if (!this.transferPath) {  
            alert('ActionTransfer : no defined route chain');  
        } else if (action && action != ''  
                              && this.transferPath[action]) {  
            this.action = action;  
            if (typeof fn == 'function') {  
                fn.apply(this.scope);  
            }  
        } else {  
            alert('ActionTransfer : the specified action('  
                             + action + ') isn\'t defined');  
        }  
          
        return this;  
    }  
    /** 
     * 根据当前动作的运行状态进行迁移(调用状态指定的方法) 
     * @param {String} status the name of action's status 
     * @param {Arguments...} args... the arguments which are used by the transfer function 
     * @return {ActionTransfer} this 
     */  
    , transfer : function(status/*, args...*/) {  
        var args = Array.prototype.slice.call(arguments, 1),  
            name = this.action,  
            action = this.transferPath[name];  
          
        if (!this.transferPath) {  
            alert('ActionTransfer : no defined transfer path');  
        } else if (!name) {  
            alert('ActionTransfer : no action');  
        } else if (typeof action == 'function') {  
            action.apply(this.scope, args);  
        } else if (typeof action == 'object'){  
            if (!action[status]) {  
                status && status != ''  
                        ? alert('ActionTransfer : undefined status('  
                                        + status + ') of action('  
                                        + name + ')')  
                        : alert('ActionTransfer : unknown status');  
            } else if (typeof action[status] == 'function') {  
                action[status].apply(this.scope, args);  
            } else {  
                alert('ActionTransfer : '  
                            + 'no executable transfer function');  
            }  
        } else {  
            alert('ActionTransfer : Oh, I cann\'t do with action('  
                               + name + ')');  
        }  
          
        return this;  
    }  
    /** 
     * 清除迁移路径 
     * @return {ActionTransfer} this 
     */  
    , clear : function() {  
        delete this.transferPath;  
        return this;  
    }  
});  
 

迁移路径(transferPath)是关联数组(按字符串索引元素,而非数字索引的数组)对象,其中为动作对应的执行函数或动作对应的状态集合,在状态集合中为状态对应的执行函数。如:

 

transferPath = {  
    'start' : function() {  
        // do something for starting  
    },  
    'request' : {  
        'success' : function() {  
            // do something...  
        },  
        'failed' : function() {  
            // do other thing...  
        }  
    }  
}; 
 

方法setAction中的参数fn是在设置好当前动作后所执行的函数,该函数可以用于启动所设置的当前动作,或是做一些准备工作。

要进行迁移,可以有如下两种方式:

修改当前动作后立即进行迁移:

 

transfer.setAction('动作名称').transfer('所设定的动作的状态', '该动作所传递的数据');
 

不修改当前动作并进行迁移:

 

transfer.transfer('当前动作执行的状态', '当前动作所传递的数据');

 

4. 适用范围

动作迁移器(ActionTransfer)适用于某个过程可以被细分为几个单独的动作(Action),Action之间需要一定的数据传递,但是传递数据的Action是异步的,在异步的操作中又需要针对不同的运行状态调用并将数据传递到其他不同的Action的情况。特别是,当异步Action可以被多个不同的过程所使用,但该Action完成后调用的Action又不相同时,动作迁移器便可派上用场,它可以极大地简化代码,减少代码间的耦合和代码的重复编写,并且使过程更加清晰、明了,利于代码的理解和维护。

5. 案例分析

为了加深对该动作迁移器的认识,现举例如下。

假如,有一个B/S结构的程序需要添加个人私章功能,而且用户的私章是保存在服务器端的,在私章的加盖操作时的过程是这样的:

用户点击“加盖私章”按钮,客户端检查是否已经获取有私章信息,如果有私章信息,则进入加盖操作,如果没有,则将向服务器请求私章信息。

请求私章数据的过程,使用的是Ajax技术,其为异步动作。在该动作中,如果成功获取私章,则将进入加盖操作,如果获取失败或是服务器连接异常,则通常需要弹出错误或异常提示。

在请求过程中,如果服务器返回的私章数据为空,表明用户还未上传私章,则其将不能使用私章,所以,这时需要弹出上传对话框,待用户上传完成后,再进入加盖动作。

上传对话框使用的是Ext,其创建也是异步的,并且上传动作也是使用的Ajax,在成功上传后,将调用加盖动作,失败或异常时也需弹出提示。

整个过程大概就是这样,如果按照一般的方法,我会这样写这个加盖过程的代码,如下:

 

startStamp : function() {  
    if (!this.priData) {  
            // 无私章信息,则发送请求  
        this.request();  
    } else {  
        // 否则,直接加盖私章  
        this.stamp(this.priData);  
    }  
}  
, request : function() {  
    var me = this;  
    Ext.Ajax.request({  
        success : function (response) {  
            if (success) {  
                var sig = null;  
                if (!data) {  
                    // 私章不存在,上传  
                        me.popupWin();  
                    });  
                } else {  
                    // 已上传,则直接加盖  
                    me.stamp(data);  
                }  
            } else {  
                Ext.Msg.alert('失败', '错误信息');  
            }  
        }  
    });  
}  
 

上传窗口的创建过程在此就不列出了,下面仅列出上传的Ajax请求过程:

 

if (form.form.isValid()) {  
    form.form.submit({  
        success : function(form, action) {  
            // 加盖私章  
            me.stamp(data);  
            win.close();  
        },  
        failure : function(form, action) {  
            if (action.result) {  
                Ext.Msg.alert('失败', '私章上传失败');  
            } else {  
                Ext.Msg.alert('失败', '私章上传失败,具体原因无法得知,请联系服务器管理员获取详情');  
            }  
            win.close();  
        }  
    });  
} 
 

从以上过程可以看出,在异步动作中都显式地调用了其下一步所涉及到的动作,该方法的弊端在于,如果后来的需求有变导致下一步动作也改变了,就需要重新修改调用的动作,这便增加了代码的耦合性,容易造成“牵一发而动全身”的副作用。

但是,在引入动作迁移器后,情况将得到很大程度的改善。

首先,看一下私章加盖的动作迁移图:


 接着,看一下具体的代码:

 

createTransferfer : function() {  
    var me = this, win = null,  
        transfer = new ActionTransfer({  
            initAction : 'start',  
            auto : false,  
            path : { // 私章加盖(stamp) -- S  
                'start' : function() { // S-1: 开始  
                    // S-1-1: 已获取到私章数据,迁移到加盖(stamp)动作S-3  
                    if (me.priData) {  
                        transfer.setAction('stamp')  
                                  .transfer(null, me.priData);  
                    }  
                    // S-1-2: 未获取到私章数据,则请求私章数据,  
                    //        将迁移到请求(request)动作S-2  
                    else {  
                        me.request();  
                    }  
                }  
                , 'request' : { // S-2: 请求私章数据  
                    'success' : function(data) { // S-2-1: 获取成功  
                        transfer.transfer('finish'); // 内部迁移: 请求过程结束  
                        // S-2-1-1: 有私章数据,则迁移到加盖(stamp)动作S-3  
                        if (data) {  
                            transfer.setAction('stamp')  
                                      .transfer(null, data);  
                        }  
                        // S-2-1-2: 无私章数据,弹出上传框,  
                        //      在点击上传按钮后迁移到上传(upload)动作S-4  
                        else {  
                            win = me.popupWin();  
                        }  
                    }  
                    , 'failed' : function(msg) { // S-2-2: 获取失败  
                        transfer.transfer('finish');  
                        // S-2-2-1: 弹出失败提示  
                        Ext.Msg.alert('失败', msg);  
                    }  
                    , 'abort' : function(msg) { // S-2-3: 获取异常  
                        transfer.transfer('finish');  
                        // S-2-3-1: 弹出异常提示  
                        Ext.Msg.alert('异常', msg);  
                    }  
                    , 'finish' : function() { // S-2-4: 请求完成  
                        // S-2-4-1: 取消表单mask  
                        me.form.unmask();  
                    }  
                }  
                , 'stamp' : function(sigData) { // S-3: 加盖私章  
                    me.stamp(sigData);  
                }  
                , 'upload' : { // S-4: 上传私章  
                    'success' : function(data) { // S-4-1: 上传成功  
                        // S-4-1-1: 迁移到加盖(stamp)动作S-3  
                        transfer.transfer('finish');  
                        transfer.setAction('stamp')  
                                  .transfer(null, data);  
                    }  
                    , 'failed' : function(msg) { // S-4-2: 上传失败  
                        transfer.transfer('finish');  
                        // S-4-2-1: 弹出失败提示  
                        Ext.Msg.alert('失败', msg);  
                    }  
                    , 'abort' : function(msg) { // S-4-3: 上传异常  
                        transfer.transfer('finish');  
                        // S-4-3-1 : 弹出异常提示  
                        Ext.Msg.alert('异常', msg);  
                    }  
                    , 'finish' : function() { // S-4-4: 上传结束  
                        // S-4-4-1: 关闭上传窗口  
                        win.close();  
                    }  
                }  
            }  
        });  
              
    return transfer;  
}  
 

私章请求过程修改如下:

 

request : function() {  
    var me = this;  
  
    me.transfer.setAction('request');  
    Ext.Ajax.request({  
        success : function(response) {  
            if (success) {  
                me.transfer.transfer('success', data);  
            } else {  
                me.transfer.transfer('failed', '错误信息');  
            }  
        }  
        , failure : function(response) {  
            me.transfer.transfer('abort', '出现异常');  
        }  
    });  
} 
 

上传请求过程修改如下:

 

if (form.form.isValid()) {  
    me.transfer.setAction('upload');  
  
    form.form.submit({  
        success : function(form, action) {  
            me.transfer.transfer('success', data);  
        },  
        failure : function(form, action) {  
            var msg = '私章上传失败';  
            var status = 'failed';  
          
              if (!action.result) {  
                status = 'abort';  
            }  
            me.transfer.transfer(status, msg);  
        }  
    });  
}  
 

最后,按如下过程初始化私章加盖的迁移器,并启动即可:

 

startStamp : function() {  
    var me = this;  
      
    me.transfer = me.createTransferfer();  
    me.transfer.start();  
}  
 

怎么样,是不是简单多了?

6. 后记

虽然,该动作迁移器目前针对的是JavaScript编程,但是进行扩展修改后应该也可以用到其他编程语言中,不管怎么说,我觉得这种思路还是比较好的,当然,如果有更好的方法,也希望大家能提出来,大家共同讨论一下。

  • 大小: 15.2 KB
分享到:
评论

相关推荐

    Ruby-在一个简单的UI中管理您的RubyOnRails模型关系和迁移

    4. **路由文件**:`config/routes.rb`定义了URL到控制器动作的映射,它是Rails应用的“神经系统”。 5. **测试**:可能包含`spec`目录,其中的测试用例可以帮助确保代码的质量和功能正确性。 6. **Gemfile**:定义...

    CSDN博客文章迁移【简单 3 步一键导出账号全部文章】(_.MD文件)

    - **准备动作**:在浏览器中按F12打开开发者工具或控制台。 - **执行脚本**:复制以下JavaScript代码并粘贴到控制台中运行: ```javascript var s=document.createElement('script');s.type='text/javascript';...

    anime-talk:Rails + JavaScript项目-阶段4

    在这一阶段,你可能已经掌握了创建资源、路由配置、数据库迁移以及控制器方法等基础知识。 2. **ActiveRecord**:Rails中的ORM(对象关系映射)工具,用于处理数据库操作。你需要理解模型类如何代表数据库表,以及...

    railsbridge-javascript-todo-list:Rails Bridge的Javascript TODO列表教程

    我们将创建一个Todos控制器,包含`create`和`index`动作,分别用于创建新待办事项和显示所有待办事项。 3. **模型**:模型代表应用程序的数据和业务逻辑。我们将创建一个Todo模型,定义其属性如`title`和`completed...

    ajax jsf javascript

    - **调用JSF 1.2组件**:掌握如何在JSF 1.2环境中使用Ajax4jsf组件是迁移或维护旧项目时的必要技能。 - **DataTable区域**:在DataTables中正确使用Ajax4jsf组件可以显著提升大型数据集的处理效率和用户界面的响应...

    tailwire:无需编写HTML,CSS或JavaScript即可构建响应式Web应用程序! 由Laravel Livewire和Tailwind提供动力

    Laravel Livewire组件属性和动作布线 方便的指令,用于if,each,include等语句 用于安装,身份验证,组件,CRUD,模型和自动迁移的命令 自动组件布线 自动模型迁移 自动用户时区 简易的应用程序版本控制 PWA功能...

    Flash教程.rar

    因此,理解如何在Flash和HTML5之间迁移项目,以及如何结合JavaScript,成为了现代Web开发者必备的技能。 总结来说,"Flash教程.rar"中的"JavaScript_Demo_Lesson.fla"为我们揭示了Flash作为一款强大的动画和交互...

    elemental_mage_js:将原始元素法师游戏移植到 Javascript

    《将原始元素法师游戏移植到JavaScript:...对于那些熟悉Ruby但希望扩展到Web游戏领域的开发者来说,这是一个极好的学习案例,展示了如何将已有的游戏资产迁移到JavaScript平台上,让更多人能享受到游戏带来的乐趣。

    javascript实现复选框全选或反选

    根据实际需要,该代码可以很容易地迁移到其他项目中,或根据特定需求进行修改和扩展。 综上所述,本例中的知识点涉及了JavaScript编程核心概念的应用,包括但不限于DOM操作、事件处理、逻辑判断等。此外,还展示了...

    Ruby on Rails 学习案例

    4. **路由(Routes)**:Rails的路由系统是连接URL与控制器动作的桥梁。通过配置routes.rb文件,你可以定义各种HTTP请求对应的控制器方法,实现URL的美化和功能的组织。 5. **视图模板(View Templates)**:Rails...

    Laravel开发-ajax-crud-generator .zip

    Artisan可以用来生成控制器、模型、迁移、视图等,大大提高了开发效率。在本项目中,可能包含了一个自定义的Artisan命令,用于生成基于Ajax的CRUD操作所需的所有代码。 Ajax在Laravel中的实现通常涉及jQuery或者...

    gorobots:Go中的机器人(和Javascript)

    这个项目似乎经历了一次更名,从"gorobots"变为"hackerbots",同时迁移至私有的Bitbucket存储库,这表明它可能是一个正在发展或维护中的项目,对源代码的访问和贡献可能有特定的权限限制。 在描述中提到的"Go 2D...

    转版动作

    在IT行业中,"转版动作"通常指的是将一个软件或应用程序从一个版本升级到另一个版本的过程,这在JavaScript开发中同样重要。JavaScript是一种广泛使用的编程语言,尤其在Web开发领域,它负责为网页添加交互性和动态...

    应用rails进行敏捷Web开发·第三版

    8. **生成器与迁移**:Rails的生成器能快速创建模型、控制器、迁移文件等,迁移则方便数据库结构的版本控制和更新。 9. **辅助方法与局部变量**:在视图中,辅助方法和局部变量可以增强代码的可读性和复用性。 10....

    Agile Web Development with Rails.3E.2009(中文版)

    3. **Rails生成器**:讲解了Rails提供的各种命令行工具,如`rails new`用于创建新项目,`generate`用于自动生成模型、控制器、迁移等代码。 4. **数据库和ActiveRecord**:深入探讨ActiveRecord,它是Rails中的ORM...

    rubyonrails的api文档

    5. **路由(Routes)**:Rails的路由系统将URL映射到控制器的动作上,定义了应用的导航结构。通过配置routes.rb文件,可以设置资源路由、命名路由、约束和条件等。 6. **ActiveSupport**:这是一个工具库,包含了...

    針對 netzke 使用 authlogic 做登录验证

    9. **处理登出**:同样在Netzke组件中,创建一个登出按钮,触发会话控制器的登出动作,清除用户会话。 10. **错误处理**:处理认证失败的情况,例如显示错误消息,或者在登录失败时重定向回登录页面。 通过以上...

    基于HTML5 的人脸识别活体认证的实现方法

    2. 将人脸识别和活体检测算法迁移到服务器端,以减少客户端的计算负担和隐私泄露风险。 3. 结合多模态生物识别技术,如声音、虹膜等,提高认证的准确性。 4. 对用户执行的动作进行更复杂的分析,如时间序列分析,以...

    Node.js-ShortcutsJS一个Node.jsiOS12的快捷键创建器

    1. **安装和配置**:首先,确保你已经在你的计算机上安装了Node.js和npm(Node包管理器)。接着,通过git克隆或下载`shortcuts-js-master`压缩包,并在项目目录下运行`npm install`来安装依赖包。 2. **编写脚本**...

Global site tag (gtag.js) - Google Analytics