Лучший способ вызвать метод суперкласса в ExtJS

Вся документация и примеры ExtJS, которые я прочитал, предлагают вызывать методы суперкласса следующим образом:

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

Я использую этот шаблон уже довольно давно, и основная проблема заключается в том, что когда вы переименовываете свой класс, вам также приходится изменять все вызовы методов суперкласса. Это довольно неудобно, часто я забываю, и тогда я должен отследить странные ошибки.

Но, прочитав исходный код Ext.extend(), я обнаружил, что вместо этого я могу использовать методы superclass() или super(), которые Ext.extend() добавляет к прототипу:

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

В этом коде переименовать MyPanel во что-то другое очень просто — мне просто нужно изменить одну строку.

Но у меня есть сомнения...

  • Я нигде не видел, чтобы это было задокументировано, и старая мудрость говорит, что я не должен полагаться на недокументированное поведение.

  • Я не нашел ни одного случая использования этих методов superclass() и supr() в исходном коде ExtJS. Зачем создавать их, если вы не собираетесь их использовать?

  • Может быть, эти методы использовались в какой-то старой версии ExtJS, но сейчас они устарели? Но это кажется такой полезной функцией, почему вы осуждаете ее?

Так стоит мне использовать эти методы или нет?


person Rene Saarsoo    schedule 12.11.2009    source источник
comment
Вообще-то, не мог бы ты попробовать this.constructor.superclass.initComponent.call(this); ?   -  person neonski    schedule 13.11.2009
comment
Спасибо, но здесь та же проблема, что описал Jabe — работает только для одного уровня иерархии.   -  person Rene Saarsoo    schedule 13.11.2009


Ответы (6)


Да, действительно, supr() не задокументировано. Я с нетерпением ждал возможности использовать его в ExtJS 3.0.0 (сотрудник Ext ответил на форумах, что они добавили его в эту версию), но он кажется ужасно сломанным.

В настоящее время он не пересекает иерархию наследования, а скорее поднимается на один уровень, затем застревает на этом уровне, бесконечно зацикливается и взрывает стек (IIRC). Итак, если у вас есть два или более supr() подряд, ваше приложение сломается. Я не нашел никакой полезной информации о supr() ни в документах, ни на форумах.

Насчет сопровождения релизов 3.0.x не знаю, так как не получил лицензию на поддержку...

person Jabe    schedule 12.11.2009
comment
Спасибо, это действительно не работает для нескольких уровней иерархии наследования. К вашему сведению: в 3.0.3 в этом отношении ничего не изменилось. - person Rene Saarsoo; 13.11.2009
comment
Еще дело с Sencha Touch 1.1 - person Vitaly; 06.05.2011

Я думаю, что это решается в 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]);
    }
});
person tykovec    schedule 04.05.2011
comment
Вопрос относится к Ext JS 3.x (для пользователей, которые могут прочитать это позже) - person blong; 24.01.2013

Вот шаблон, который я использую, и некоторое время собирался написать об этом в блоге.

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);

Используя этот шаблон, вы получаете множество функций, но вам не обязательно использовать их все.

person Juan Mendes    schedule 28.10.2010
comment
Хорошо, но этот подход вводит довольно много стандартного кода, что может быть хорошо, если вы вызываете много методов родительского класса, но мне обычно просто нужно вызвать родителя initComponent() и ничего больше, и в этом случае дополнительный код ИМХО не стоит. - person Rene Saarsoo; 02.11.2010
comment
да, этот шаблон предназначен для всего ООП-кода с использованием наследования, когда вы вызываете родительские методы. В Ext я часто вызываю onRender, afterRender, beforeDestroy из родителей, так как переопределяю его. - person Juan Mendes; 23.12.2010
comment
Это здорово, спасибо! Мне это особенно нравится, поскольку он может передавать JSLint / JSHint, если все сделано правильно. У меня были некоторые проблемы с линтингом этот формат из старого Sencha учебник. - person blong; 09.03.2012
comment
@ReneSaarsoo, как бы вы (или Хуан Мендес) сохранили нестатические частные методы с этим дизайном? - person blong; 14.03.2012
comment
Есть и другие способы, но я не использую действительно приватные нестатические методы, я просто начинаю их с подчеркивания и ожидаю, что пользователи класса не будут связываться с методами _underscore. - person Juan Mendes; 15.03.2012
comment
@b.long Самым простым решением было бы сделать ваши частные нестатические функции просто такими функциями, как privateStatic, но вы должны вызывать их с помощью privateNonStatic.call(this, arg1, arg2). Не очень элегантно... - person Juan Mendes; 15.03.2012

Вы можете использовать эту малоизвестную функцию 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). Будет продолжать думать об этом немного.

person neonski    schedule 12.11.2009
comment
JavaScript 1.4: Устаревший callee как свойство Function.arguments, сохранил его как свойство локальной переменной arguments функции. Не то же самое! - person neonski; 12.11.2009
comment
Ах, извините, вы правы. Я перепутал его с полностью устаревшим «звонящим». Удаление вызываемого было бы довольно беспорядок. - person Jabe; 13.11.2009

Я бы просто изменил ваш код на:

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

Таким образом, вы сохраняете только одну ссылку на имя вашего класса, теперь $cls. Используйте только $cls в своих методах класса, и все будет в порядке.

person koni    schedule 10.01.2011
comment
Это решение сработало бы, если бы каждый класс был заключен в замыкание. В настоящее время я этого не делаю, поэтому это решение не работает. Может быть, когда-нибудь я это сделаю, так что это может стать возможным. - person Rene Saarsoo; 10.01.2011

Я придумал это решение пару часов назад, хе-хе...

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.

person rseidi    schedule 24.01.2011
comment
Function.caller и Object.__proto__ оба нестандартны. Кроме того, последний устарел. Хотя идея выглядит интересной. - person Rene Saarsoo; 27.01.2011