Как стать автором
Обновить

Классы в Javascript: вызов методов родительского класса

JavaScript *
JavaScript — очень динамический язык, в нём заложена возможность менять язык под себя и создавать удобные инструменты для дальнейшей работы. «Реализация классического наследования» — как раз один из таких инструментов. В данный момент я не представляю себе, как я программировал бы на JS без «классов».

Для меня «Классы» — это, скорее, подход к проектированию и реализации поставленной задачи. В нашем новом проекте такой подход используется по полной программе (и, я думаю, что он оправдывает себя на все 100%).
Эта статья написана в качестве приложения к другой моей статье «Компоненты в Unobtrusive JavaScript» (хотя, в принципе, может выступать в роли самостоятельного текста).

На данный момент существует море реализаций «Классов в JS».
Я бы выделил два их них: это реализации в Prototype.js и в Base2. В обоих способах нельзя вызвать «другой» метод родительского класса (т.е. нельзя вызвать parent::A из функции this.B) — я считаю это существенным недостатком: бывает так, что нельзя выполнить задачу не изменив сам подход.

В своей реализации «Классического наследования в Javascript» я использовал немного «иной» синтаксис:
// вызов метода myMethod родительского класса
this.myMethod.__parent(param1);


* This source code was highlighted with Source Code Highlighter.
Для реализации такой конструкции, возможно, пришлось чуть-чуть пожертвовать производительностью: не все методы Класса находятся в прототипе функции-инициализатора. Но я знаю, что плюсов от использования подхода гораздо больше.

Я условно разделил методы класса на два вида:
  1. Обычные методы. Они хранятся в прототипе функции-конструкторе
  2. «Виртуальные» методы. Такие методы, например, __constructor и __destructor, требуют возможности вызова методов родительского класса. Хранятся они не в прототипе. А в thisObj попадают в функции-инициализаторе объекта.
Вот пример описания классов:
JooS.Class = JooS.Reflect(null, {
 __common: new Object(),
 
 __destroy: function() {
  arguments.length ? this.__destructor.apply(this, arguments) : this.__destructor();
 },
 __constructor: function() {
  alert("JooS.Class::__constructor");
 },
 __destructor: function() {
  alert("JooS.Class::__destructor");
 }
});
 
 
var Class1 = JooS.Reflect(JooS.Class, {
 myFunction: function() {
  this.__constructor.__parent();
 },

 __constructor: function() {
  alert("Class1::__constructor");
  this.myFunction();
 },
 __destructor: function() {
  alert("Class1::__destructor");
  this.__destructor.__parent();
 }
});

var Obj1 = new Class1(); // Создание объекта
Obj1.__destroy(); // Подготовка к удалению объекта
delete Obj1; // Удаление объекта

* This source code was highlighted with Source Code Highlighter.

Должно выскочить 4 алерта по порядку:
  1. «Class1::__constructor»
  2. «JooS.Class::__constructor»
  3. «Class1::__destructor»
  4. «JooS.Class::__destructor»
А вот код, позволяющий реализовать «иное» наследование:
var JooS = {
 Reflect: (function(F) {
  return function(source, methods) {
   if (!methods)
    methods = { };
   if (source) {
    if (!source.prototype.__constructor)
     source.prototype.__constructor = source;
    F.prototype = source.prototype;
   }
   else
    F.prototype = { constructor: source = F };

   var c;
   if (methods.__constructor) {
    F.prototype = new F;
    c = this.Virtual(F, { __constructor: methods.__constructor }).prototype.__constructor;
    c.prototype = new F;
    delete methods.__constructor;
   }
   else {
    c = function() {
     if (this.__constructor)
      this.__constructor.apply(this, arguments);
    };
    c.prototype = new F;
   }
   c.constructor = source;

   return c.prototype.constructor = this.Virtual(c, methods);
  }
 })(new Function),

 Virtual: (function(hidden) {
  var __index = 1;

  var __extend = function(constructor, name, __self) {
   var __parent = constructor.prototype[name], method;
   if (typeof __self == "function") {
    var __selfName = __self.__name = name + "::" + __index++;
    constructor.prototype[__selfName] = __self;

    if (__parent && typeof __parent == "function") {
     var __thisObj, __originalParent = __self.__parent = __parent.__self || __parent;

     method = function(a1, a2, a3) {
      __thisObj = this;
      __parent = __originalParent;

      switch (arguments.length) { // I'm cheater. Almost...
       case 0: return this[__selfName]();
       case 1: return this[__selfName](a1);
       case 2: return this[__selfName](a1, a2);
       case 3: return this[__selfName](a1, a2, a3);
       default: return __self.apply(this, arguments);
      }
     };

     method.__parent = function(a1, a2, a3) {
      var o = __parent, r;

      __parent = o.__parent;
      switch (arguments.length) {
       case 0: r = __thisObj[o.__name](); break;
       case 1: r = __thisObj[o.__name](a1); break;
       case 2: r = __thisObj[o.__name](a1, a2); break;
       case 3: r = __thisObj[o.__name](a1, a2, a3); break;
       default: r = o.apply(__thisObj, arguments);
      }
      __parent = o;

      return r;
     };

     method.__self = __self;
    }
   }

   constructor.prototype[name] = method || __self;
  };
  
  for (var IE = "IE" in { toString: IE })
   hidden = false;

  return function(constructor, methods) {
   for (var name in methods)
    __extend(constructor, name, methods[name]);

   if (hidden) // thanks to Andrea Giammarchi
    for (var i=0; i<hidden.length, name = hidden[i]; i++)
     if(methods.hasOwnProperty(name))
      __extend(constructor, name, methods[name]);

   return constructor;
  };
 })([ "hasOwnProperty", "isPrototypeOf", "propertyIsEnumerable", "toLocaleString", "toString", "valueOf" ]),

 Mixin: function(destination, source) {
  for (var i in source.prototype)
   if (!destination.prototype[i] && i != "constructor")
    destination.prototype[i] = source.prototype[i];
 },
 Clone: (function(F) {
  return function(Obj) {
   F.prototype = Obj || { };
   return new F();
  };
 })(new Function),

 Extend: function(destination, source) {
  for (var i in source)
   destination[i] = source[i];
  return destination;
 }
};

* This source code was highlighted with Source Code Highlighter.
Теги:
Хабы:
Всего голосов 23: ↑20 и ↓3 +17
Просмотры 16K
Комментарии 94
Комментарии Комментарии 94

Публикации

Истории

Работа