【问题标题】:How to reuse and override inherited javascript function如何重用和覆盖继承的javascript函数
【发布时间】:2011-09-04 22:34:51
【问题描述】:

我有一个鼠标事件类。我正在使用 dojo b/c 我喜欢它的 OO 方法

dojo.declare("MouseObject", null, {
  constructor: function(){},
  onclick : function(){...},
  _onClick : function(){...}
});

_onClick() 侦听窗口生成的鼠标按下/释放事件并确定是否发生了点击。如果有,则调用onClick()onClick() 执行所有可能点击的通用功能,因此每次用户点击时都需要调用它。

有时用户可能想要扩展onClick() 的功能,是否可以在不复制粘贴的情况下合并原始功能?我能想到的两种我都不喜欢的方法是

dojo.declare("MouseObjectChild", [MouseObject], {
  constructor: function(){},
  onclick : function(){this.inherited(arguments);...}
});

它的缺点是我必须不断创建我并不真正需要的新类,并且两个添加一个中间函数

dojo.declare("MouseObject", null, {
      constructor: function(){},
      onclick : function(){this._onClick()...}, //child onClick
      _onClick : function(){...}, //parent onClick
      __onClick : function(){...} //listener
    });

但这看起来不像是好的代码


wrt 赏金,我希望获得有关如何最好地解决程序与用户交互的专家建议。如果程序提供了一个关键功能,例如在画布上画一个圆圈,那么用户如何最好地与之交互。如果用户想在圆圈后面画一个三角形怎么办?圆圈前面有一个正方形?然后程序必须提供如下的 pre 和 post 方法:

beforeDraw();
draw();
afterDraw();

这算不算好的设计?我是否应该将函数指针放在一个数组中并按顺序调用它们?

【问题讨论】:

    标签: javascript function dojo reusability redefine


    【解决方案1】:

    正如“我应该将函数放在它们自己的数组中并按顺序调用它们”和“构建自己的事件系统”的想法所建议的那样,您可能想看看捆绑在扩展标准库。它基本上应该是“dojo.connect”解决方案的更高级版本。

    dojo.require('dojox.lang.aspect');
    var aop = dojox.lang.aspect;
    
    function log(msg){ return function(){
        console.log(msg);
    };}
    
    var obj = {
       draw : log('draw circle');
    };
    
    obj.foo();
    //prints
    //  draw circle
    
    aop.advise(obj, 'draw', [
        {before: log('draw triangle')},
        {after: log('draw a square')},
        {after: log('done!')}
    ]);
    
    obj.foo();
    //prints
    //  draw a triangle
    //  draw a circle
    //  draw a square
    //  done!
    

    【讨论】:

      【解决方案2】:

      如果有人只想在调用 onclick() 时运行一些代码,他可以直接连接到它。请注意,您还可以使用 dojo.connect() 连接到函数调用,而不仅仅是事件。

      dojo.connect(obj, 'onclick', function(){
        // Some extra code that runs when obj.onclick() is called
      });
      

      如果他想创建一个新类并扩展功能调用this.inherited(arguments); 是最好的方法。

      【讨论】:

      • 这是在 html 画布中,我不相信 connect 可以在它上面工作。另外,我不想为每个绘制的新形状创建一个新的鼠标类。
      • 你没有提到 HTML Canvas。到目前为止,我还没有使用过画布。您是否使用 dojox.gfx 进行抽象并在那里查看如何连接到事件?在您的示例中,您展示了一个普通的 Javascript 类,如果是这种情况,您始终可以使用 dojo.connect 连接到它,无论您的类代表什么。
      • 这不是我想要的,尽管它可能会完成我想要做的事情。
      【解决方案3】:

      为什么不创建自己的简单事件模型?

      // store our callbacks
      var hooks = {};
      
      // add a callback
      var hook = function(name,fn){
        if(typeof hooks[name] === 'undefined'){
          hooks[name] = [];
        }
        hooks[name].push(fn);
        return true;
      };
      
      // remove a callback
      var unhook = function(name,fn){
        if(typeof hooks[name] === 'undefined'){ return false }
        var i, u = hooks[name].length;
        for(i=0;i<u;i++){
          if(hooks[name][i] === fn){
            hooks[name].splice(i,1);
            return true;
          }
        }
      };
      
      // trigger a callback
      var callHook = function(name, data){
         if(typeof hooks[name] === 'undefined'){ return false }
         var i, u = hooks[name].length;
         for(i=0;i<u;i++){
           hooks[name][i](data);
         }
      };
      

      然后(示例传递上下文):

      hook('beforeDraw',function(ctx){ 
        console.log('I am called prior to drawing');
      });
      
      hook('afterDraw',function(ctx){
        console.log('I am called after drawing');
      });
      
      var drawCircle = function(){
        callHook('beforeDraw', ctx);
        // drawing code here
        callHook('afterDraw', ctx);
      };
      

      或者,如果您想将作用域传递给回调,您可以更改callHook 方法以接受作用域参数,然后使用callapply 并传入this 或其他一些对象:

      var callHook = function(name, scope, data){
         if(typeof hooks[name] === 'undefined'){ return false }
         var i, u = hooks[name].length;
         for(i=0;i<u;i++){
           hooks[name][i].call(scope, data);
         }
      };
      
      // later
      var drawCircle = function(){
         callHook('beforeDraw', this, ctx);
         // or
         callHook('beforeDraw', someObject, ctx);
      };
      

      根据您定义事件模型函数的方式和位置,您可以将功能范围限定为对象的特定实例、对象的每个实例、全局等等,具体取决于您希望如何分配、更改和触发这些回调.

      【讨论】:

      • 我考虑过这个选项,但是,我认为为用户硬编码这三个函数更容易,尤其是因为永远只需要这三个函数。
      • 我想这取决于您是否希望用户能够为每个函数堆叠一组函数。如果确实是特定的用例,那么您可能是对的,将三个函数调用硬编码为向用户公开的参数参数,然后他们可以手动将代码堆叠在这些函数中。
      【解决方案4】:

      您有多种选择。
      1. Promise 模式越来越受欢迎(尽管有竞争规范):http://wiki.commonjs.org/wiki/Promises/B。 jQuery 有一个接近 B 的实现,并且有据可查。它有最多的选择,它很健壮,但一开始并不容易让你头晕目眩。

      1. AOP 风格的编程允许您摆弄之前和之后的建议。但是,要使其工作,您需要停止将所有内容放入匿名函数中,并决定是在实例化后应用建议还是通过将功能烘焙到类中来计划它们的使用。这需要对代码结构进行更多规划,但提供了大量实用程序。

      例如:

      var MYAPP = {
        onClick: function () {
          //do something...
        }
      };
      
      dojo.declare("MouseObject", null, {
        constructor: function(){},
        onclick : function(){...},
        _onClick : function(){
          return MYAPP.onClick.apply(this, arguments);
        }
      });
      

      由于我的班级每次都调用 MYAPP(是的,这需要收取范围费用),我可以操纵挂起 MYAPP 的功能,任何使用它的人都会受益。

      MYAPP.after("onClick", function () {...}); //lots of ways to write .after, just google some AOP js or use a library
      

      另一边:

      var MYAPP = {
        onClick: function () {
          //do something...
        }
      };
      
      dojo.declare("MouseObject", null, {
        constructor: function(){},
        onclick : function(){...},
        _onClick : MYAPP.onClick
      });
      

      现在我无法更改 MYAPP 并查看 MouseObject 或其实例中的更改,因为该函数已通过对 MouseObject 的引用传递。我只能使用 AOP 更改实例(或通过更改类上的原型来更改所有未来的实例)。

      类似:

      var m = new MouseObject();
      m.after("onClick", function () {...}); //you need to add .after to your class or use an apply pattern from library code
      

      这仅取决于您认为它将如何使用它:某些实例、所有未来实例(直到关闭)或一切。

      1. 建议使用“事件队列”。这更像是一个活动对象模式,您使用对象数组和队列管理器来管理任务。这是最容易实现的,但有几个缺点,队列可能会“阻塞”并且不再处理。我为这种类型的管理编写了一个库,称为 proto-q (http://code.google.com/p/proto-q/),基于我所做的 iPhone 应用程序。它对我很有效,但并不适合所有人的应用。

      【讨论】:

      • 顺便说一句,Dojo 也有 Promise,实际上在 jQuery 之前很长一段时间就有了。此外,Dojo 已经内置了“after” - 它称为 dojo.connect 并且是事件系统的基础。
      • 我从不使用 Dojo,但这是很好的信息。您可能应该将这些方法用于此应用程序。希望我只是阐明了使用不同技术的一些原因。
      【解决方案5】:

      __fn 变得越来越混乱。我的朋友,那是通往地狱的道路。

      这是使用Magic Pushbutton 方法的症状。 您应该考虑使用周围的许多 OO js 库迁移到基于实际继承的类模型。然后你就可以调用 $base.fn() 或 $super.fn()

      【讨论】:

      • 如果您没有注意到 OP 问题中的 this.inherited 部分,Dojo 的模型已经是基于继承的
      • OO 适用于类组,但不适用于单个对象。理论上,我可以为每个类创建一个新对象,但这会极大地浪费时间和精力。
      • 我在MVVM中使用knockout.js这种方式,真正的继承,基本上可以继承视图。例如,在列表视图视图模型中,我的基类实现搜索、过滤和分组等功能,而在派生类中,我只指定组是什么,使用什么过滤谓词等。这是一种范式转变,但是一旦你克服了第一个障碍,它就会变得更容易。
      • 虽然,除非对象定义了整个部分的行为,否则它是无用的。但正如您所说,如果您针对的是单个对象,这并不好。
      【解决方案6】:

      如果我理解您的问题,那么您想针对特定情况修改功能。如果是这种情况,并且您不想要任何开销,那么我会在实例化时依赖覆盖方法。

      覆盖模型

      你的班级

      dojo.declare("some.path.DrawingThing", [dijit._Widget], {
          draw: function () {
              this.inherited(arguments); // Call super class methods
              // override this method to do specialized things that're specific 
              // to an instance.
          }
      });
      

      客户端实例化

      var drawer = new some.path.DrawingThing({
          draw: function () {
              beforeDraw();
              // Access the pre-existing functionality in a somewhat dirty way.
              this.constructor.prototype.draw.apply(this, arguments);
              afterDraw();
          }
      });
      

      如果您遵循此模型,代码会更少,但不会那么干净。另一种选择是编写一个真正的 API,见下文。

      事件模型

      如果你想要一个真正的 API,那么我会提供事件:

      // Dojo 1.6 style declaration.
      dojo.declare("some.path.DrawingThing", [dijit._Widget], {
          onBeforeDraw: function(){
              // Hook into this method to do something before draw
          },
          onDraw: function(){
              // Hook into this method to do something after draw
          },
      
          draw: function () {
              // This method is actually called when the draw even fires
              this.onBeforeDraw();
              this.inherited(arguments); // Call super class methods
              this.onDraw();
          }
      });
      

      【讨论】:

      • 我不明白这与我想要的有什么关系
      • 如果我猜对了,那么也许这个更具体的例子会有所帮助。我还想我会添加一个更清洁的解决方案,因为您想在很多地方使用它。
      • 是的,你有正确的想法。但是,“客户端实例化:”示例存在用户必须记住包含原始 draw() 函数的风险。想象一下,如果draw() 是 50 行代码。这是很多剪切和粘贴
      • “事件模型”版本本质上就是我正在使用的。我会坚持后一种方法。谢谢
      猜你喜欢
      • 1970-01-01
      • 2011-01-10
      • 1970-01-01
      • 2019-01-04
      • 1970-01-01
      • 2011-06-14
      • 1970-01-01
      • 2012-04-13
      • 1970-01-01
      相关资源
      最近更新 更多