Pull to refresh

Javascript: «классы» и наследование

Reading time3 min
Views7.6K
Меня всегда немного бесило отсутствие в Javascript понятия класса как такового, а с наследованием вообще довольно туговато. Почитав на эту тему довольно много материалов и попробовав несколько «обходных» путей отсюда, в том числе и приведенную в комментариях немного урезанную версию библиотеки ajaxoop.js, не нашел ничего подходящего и привычного.

Однако была задача, потребовавшая реализации «привычного» наследования, дополнительным приятным побочным эффектом которой стало наличие возможности множественного наследования.

Итак, предлагаю такое достаточно простое на мой взгляд решение этой проблемы:
/**
 * Базовый класс для наследования.
 * Также позволяет реализовать множественное наследование.
 * Использование:
 * function MyClass(arg1, arg2) {
 *     ExtClass.call(this, {
 *     MyParentClass1: [arg1], // родительский класс 1 с параметрами
 *     MyParentClass2: [arg2], // родительский класс 2
 *     MyParentClass3: [arg1, arg2], // и так далее
 *     ...
 *   });
 *
 *   // далее можно реализовывать собственные методы или переписывать
 *   // родительские
 * }
 *
 * После такого вызова ваш класс MyClass унаследует все методы и свойства
 * родительских классов MyParentClass1 и т.д.
 * Если оба класса имеют методы с одинаковым именем, то MyClass
 * унаследует в качестве своего метода последний из перечисленных
 * в вызове родительских классов.
 * Однако к любому методу любого из родительских классов всегда можно
 * обратиться посредством следующей конструкции:
 * this.$super['MyParentClassName'].methodName.call(this, ...args...);
 * так как методы всех родителей будут занесены в this.$super
 */
function ExtClass(supers) {
  // переменная для хранения методов родительских классов
  // здесь будут числится не только прямые предки (родительские классы, 
  // перечисленные при вызове), но и их предки тоже
  this.$super = this.$super ? this.$super : {};

  // переменная для хранения массива имен родительских классов
  // для реализации метода this.instanceOf()
  this.$_parents = this.$_parents ? this.$_parents : [];

  /**
   * Проверка, является ли объект объектом заданного класса
   * @param string className Имя класса для проверки
   * @returns boolean TRUE если является, иначе FALSE
   */
  this.instanceOf = function(className) {
    return (__inArray(className, this.$_parents) || (this instanceof eval(className)));
  };

  /**
   * Приватная функция для добавления родительского класса.
   * Методы род. класса могут быть вызваны так: 
   * this.$super['parenClassName'].method.call(this, ...args...);
   * @param object that Обертка для this объекта
   * @param string className Имя класса
   */
  function __addSuper(that, className) {
    var obj = new (eval(className));
    that.$super[className] = {};
    if (!__inArray(className, that.$_parents)) that.$_parents.push(className);
    for (i in obj) {
      if (typeof obj[i] == 'function') {
        that.$super[className][i] = obj[i];
      }
    }
  };

  /**
   * Приватная функция для проверки на наличие элемента в масиве
   * @param mixed value Искомое значение
   * @param array arraySeek Массив поиска
   * @returns boolean TRUE если найдено, иначе FALSE
   */
  function __inArray(value, arraySeek) {
    if (arraySeek && arraySeek.length) {
      for (i in arraySeek) {
        if (arraySeek[i] == value) return true;
      }
    }
    return false;
  };

  // основной цикл добавления родительских классов
  for (i in supers) {
    var className = i;
    var args = supers[i];
    (eval(className)).apply(this, args);
    __addSuper(this, className);
  }
};

Для примера создадим 2 класса родителя:
function Parent1() {
  ExtClass.call(this); // можно не вызывать
  this.message = function() {
    return 'Parent1::message';
  }
}

function Parent2() {
  ExtClass.call(this); // можно не вызывать
  this.message = function() {
    return 'Parent2::message';
  }
}

и два класса дочерних классов, каждый из которых наследует от своего родителя:
function Child1() {
  ExtClass.call(this, {
    Parent1: null
  });

  this.message = function() {
    return 'Child1::message';
  }
}

function Child2() {
  ExtClass.call(this, {
    Parent2: null
  });

  this.message = function() {
    return 'Child2::message';
  }
}

ну и суммарный класс, наследующий от дочерних классов:
function Child12() {
  ExtClass.call(this, {
    Child1: null,
    Child2: null
  });

  this.message = function() {
    // вызов по очереди одноименного метода всех родительских классов
    var message = this.$super['Parent1'].message.call(this) + "\n";
    message += this.$super['Parent2'].message.call(this) + "\n";
    message += this.$super['Child1'].message.call(this) + "\n";
    message += this.$super['Child2'].message.call(this) + "\n";
    // ну и от себя )
    message += 'Child12::message'
    return message;
  }
}

создадим объект последнего класса и проверим, что выдаст метод message() данного объекта:
var childTest = new Child12();
alert(childTest.message());

А вот и результат:
Parent1::message
Parent2::message
Child1::message
Child2::message
Child12::message

Как видно, все родительские методы работают должным образом. Надеюсь, это решение будет полезно многим, удачи!
Tags:
Hubs:
Total votes 21: ↑9 and ↓12-3
Comments39

Articles