Tuesday, June 12, 2012

Наследование в JavaScript с возможностью вызова super методов.

    Недавно мне понадобилось реализовать наследование в JavaScript с возможностью вызова супер конструктора ( конструктор родительского класса, кто не в курсе ). Под рукой у меня была старая добрая крокфордовская функция, которая всегда мне помогала:
function extend(Child, Parent){
   var F = function(){}
   F.prototype = Parent.prototype;
   Child.prototype = new F();
   Child.prototype.constructor = Child;
}
Немного почесав репу я решил добавить в нее немного функционала. Идея достаточно проста -- в каждой функции в JS есть переменная arguments, которая помимо аргументов вызова функции хранит в себе свойство callee, а то в свою очередь свойство caller, которое указывает на функцию, которая вызвала текущую функцию.... Короче говоря вот что у меня получилось:
function extend(Child, Parent){
   var F = function(){}
   F.prototype = Parent.prototype;
   Child.prototype = new F();
   Child.prototype.constructor = Child;


   Child.prototype._super = function(){
      if( arguments.callee.caller === Child){
         Parent.apply(this, arguments);
         return;
      }
      if( !this.__proto__){this.__proto__ = this.constructor.prototype;}
      for(var o in this.__proto__){
         if(this.__proto__[o] === arguments.callee.caller){
            Parent.prototype[o].apply(this, arguments);
         }
      }
   }
}
Теперь постараюсь объяснить свой ход мысли.
Добавляем в прототип дочернего обьекта функцию _super:
   Child.prototype._super = function(){
Первым делом эта функция проверяет не в конструкторе ли она была вызвана:
   if( arguments.callee.caller === Child){
И если она была вызвана в конструкторе, то мы соответственно вызываем конструктор родительского класса.
   Parent.apply(this, arguments);
В ИЕ не существует переменной __proto__, по этому мы ее добавляем сами
   if( !this.__proto__){this.__proto__ = this.constructor.prototype;}
Если функция была вызвана не из конструктора, то мы пробегаемся по всем полям в прототипе нашей текущей функции, чтоб выяснить ее имя, после чего вызываем эту самую функцию, но из родительского класса
   for(var o in this.__proto__){
      if(this.__proto__[o] === arguments.callee.caller){
         Parent.prototype[o].apply(this, arguments);
      }
   }

UPD: Если я правильно понял эт заметку caller - MDN - Mozilla Developer Network то свойство caller не должно существовать... но тем не менее у меня все заработало.