在ExtJS中调用父类方法的更好方式

29

我阅读过的所有ExtJS文档和示例都建议像这样调用超类方法:

MyApp.MyPanel = Ext.extend(Ext.Panel, {
  initComponent: function() {
    // do something MyPanel specific here...
    MyApp.MyPanel.superclass.initComponent.call(this);
  }
});

我使用这个模式已经有一段时间了,主要问题是当你重命名你的类时,你也必须改变所有调用父类方法的地方。这非常不方便,经常会忘记,然后我就不得不追踪奇怪的错误。

但是在阅读 Ext.extend() 的源代码时,我发现可以使用 Ext.extend() 添加到原型中的 superclass()super() 方法来解决这个问题:

MyApp.MyPanel = Ext.extend(Ext.Panel, {
  initComponent: function() {
    // do something MyPanel specific here...
    this.superclass().initComponent.call(this);
  }
});

在这段代码中,将MyPanel重命名为其他名称很简单 - 我只需要改变一行代码。

但我有疑虑...

  • 我没有看到这里有任何文档说明,老的智慧说,我不应该依赖于未经文件化的行为。

  • 我在ExtJS源代码中没有找到这些superclass()supr()方法的使用。如果你不打算使用它们,为什么要创建它们?

  • 也许这些方法在某个旧版本的ExtJS中被使用过,但现在已弃用了?但它似乎是一个非常有用的功能,为什么会弃用呢?

那么,我应该使用这些方法吗?


其实,你可以尝试使用 this.constructor.superclass.initComponent.call(this); - neonski
谢谢,但这个问题和Jabe描述的一样 - 它只适用于一个层次结构。 - Rene Saarsoo
6个回答

29

我认为在ExtJS 4中可以使用callParent来解决这个问题。

Ext.define('My.own.A', {
    constructor: function(test) {
        alert(test);
    }
});

Ext.define('My.own.B', {
    extend: 'My.own.A',

    constructor: function(test) {
        alert(test);

        this.callParent([test + 1]);
    }
});

1
该问题涉及到Ext JS 3.x(对于后来者可能会阅读此内容)。 - blong

14

确实,supr()没有被记录在文档中。我一直期待在ExtJS 3.0.0中使用它(一个Ext员工在论坛中回答说他们在那个版本中添加了它),但它看起来非常糟糕。

它目前不会遍历继承层次结构,而是上升一个级别,然后停留在此级别,无限循环并爆栈(如果我没记错的话)。因此,如果您连续使用两个或多个supr(),您的应用程序将崩溃。我在文档和论坛中都没有找到有用的关于supr()的信息。

我不知道维护版本3.0.x的情况,因为我没有得到支持许可证...


谢谢,它确实不能处理多级继承层次结构。FYI:在3.0.3中这方面没有任何改变。 - Rene Saarsoo
1
仍然适用于Sencha Touch 1.1。 - Vitaly

5

这是我使用的一种模式,我一直想写博客介绍它。

这与IT技术有关。
Ext.ns('MyApp.MyPanel');

MyApp.MyPanel = (function(){
  var $this = Ext.extend(Ext.Panel, {
    constructor: function() {
        // Using a 'public static' value from $this
        // (a reference to the constructor)
        // and calling a 'private static' method
        this.thing = $this.STATIC_PROP + privateStatic();
        // Call super using $super that is defined after 
        // the call to Ext.extend
        $super.constructor.apply(this, arguments);
    },
    initComponent: function() {
        $super.initComponent.call(this);
        this.addEvents([Events.SOMETHING]); // missing docs here
    }
  });
  var $super = $this.superclass;

  // This method can only be accessed from the class 
  // and has no access to 'this'
  function privateStatic() {
    return "Whatever";
  }


  /** 
    * This is a private non-static member
    * It must be called like getThing.call(this);
    */
  function getThing() {
     return this.thing;
  }

  // You can create public static properties like this
  // refer to Events directly from the inside
  // but from the outside somebody could also use it as
  //  MyApp.MyPanel.Events.SOMETHING
  var Events = $this.Events = {
      SOMETHING: 'something'
  }

  return $this;
})();

MyApp.MyPanel.STATIC_STRING = 10;

//Later somewhere
var panel = new MyApp.Mypanel();
panel.on(MyApp.Mypanel.Events.SOMETHING, callback);

使用这种模式可以获得很多功能,但您不必全部使用。


不错,但这种方法引入了相当多的样板代码,如果您调用许多父类方法,这可能是可以接受的,但我通常只需要调用initComponent()的父类,而且仅此而已,在这种情况下,额外的代码在我看来不值得。 - Rene Saarsoo
是的,这个模式适用于所有使用继承的面向对象代码,其中您会调用父类的方法。在 Ext 中,我经常从父类中调用 onRender、afterRender 和 beforeDestroy,因为我要重写它们。 - Ruan Mendes
太好了,谢谢!如果正确执行,我特别喜欢它可以通过 JSLint / JSHint。我在_linting_ 这个老的Sencha教程格式时遇到了一些问题。 - blong
@ReneSaarsoo,您(或Juan Mendes)如何在这个设计中保留非静态私有方法? - blong
有其他方法,但我不使用真正的私有非静态方法,我只是用下划线开头并期望类的用户不要搞乱 _下划线方法。 - Ruan Mendes
1
@b.long 最简单的解决方案是将您的私有非静态成员变量设计为函数,例如 privateStatic,但您必须使用 privateNonStatic.call(this, arg1, arg2) 来调用它们。不是很优雅... - Ruan Mendes

3
您可以使用这个鲜为人知的Javascript功能(arguments.callee):
MyApp.MyPanel = Ext.extend(Ext.Panel, {
    constructor: function() {
        // Do your thing
        this.thing = 1;

        // Call super
        arguments.callee.superclass.constructor.apply(this, arguments);
    }
});

查看MDC文档

编辑:实际上,这不适用于initComponent,因为它不是构造函数。我个人总是覆盖构造函数(尽管Ext JS示例可能会建议其他方式)。将继续思考这个问题。


3
JavaScript 1.4版本:废弃了Function.arguments中callee属性,将其保留为函数本地arguments变量的属性。但这两者不是同一物! - neonski
啊,对不起,你是正确的。我把它和完全废弃的“caller”混淆了。移除“callee”会相当麻烦。 - Jabe

2
我会简单地更改您的代码为:

我只需要将您的代码更改为:

var $cls = MyApp.MyPanel = Ext.extend(Ext.Panel, {
  initComponent: function() {
    // do something MyPanel specific here...
    $cls.superclass.initComponent.call(this);
  }
});

这样,您只需保留类名的单个引用,现在是$cls。只在类方法中使用$cls,您就可以了。


1
如果我有每个类都包装在闭包中,那么这个解决方案将可行。但目前我没有,所以这个解决方案不起作用。也许有一天我会这样做,这样就可以变得可行。 - Rene Saarsoo

0

我几个小时前想出了这个解决方案,呵呵...

function extend (parentObj, childObj) {
    parentObj = parentObj || function () {};

    var newObj = function () {
        if (typeof this.initialize == 'function') {
            this.initialize.apply(this, arguments);
        }
    }

    newObj.prototype.__proto__ = parentObj.prototype;

    for (var property in childObj) {
        newObj.prototype[property] = childObj[property];
    }

    newObj.prototype.superclass = function (method) { 
        var callerMethod = arguments.callee.caller,
            currentProto = this.constructor.prototype.__proto__;

        while (callerMethod == currentProto[method]) {
            currentProto = currentProto.__proto__;
        } 

        return currentProto[method]; 
    };

    return newObj;
}

然后你可以这样做:

var A = function () { 
    this.name = "A Function!";
};

A.prototype.initialize = function () {
    alert(this.name);
}

var B = extend(A, {
    initialize: function () {
        this.name = "B Function!";
        this.superclass('initialize').apply(this);
    }
});

var C = extend(B, {
    initialize: function () {
        this.superclass('initialize').apply(this);
    }
});

仅在(Chromium 8.0.552.237(70801)Ubuntu 10.10)和(Firefox 3.6.13)上进行了测试。

希望这能帮助某些人,我差点转向GWT。


1
Function.callerObject.__proto__都是非标准的。此外,后者已被弃用。不过,这个想法看起来很有趣。 - Rene Saarsoo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接