illumium.org

Главная › Блоги › Блог kayo

Реализация удобной модели наследования на JavaScript

kayo — Сб, 02/02/2013 - 12:42

Чтобы хоть как-то оживить обстановку на борту нашего дирижабля, решил написать таки эту небольшую статью про свою реализацию модели наследования на JavaScript.

Введение

JavaScript — очень простой язык, но простота ни в коем случае не означает примитивность. Многие не находят в нём привычной им реализации ООП, основанной на иерархии классов, и ошибочно полагают, что нечто подобное лежит за рамками возможностей этого языка. На самом деле модель наследования в JS имеет основанную на прототипах природу. Здесь нет никаких классов, в качестве основы для создания объектов используются функции, и потому прототипная модель работает именно с ними. В простейшем случае вам нужно лишь немного подшаманить над стандартным свойством prototype, имеющимся у любой функции, добавив в него функции-«методы», чтобы получить возможность их вызова из производных объектов. Однако, выглядит это не всегда эстетично, потому что JavaScript простой язык, поэтому мы перевернём всё с ног на голову, обеспечив так желанную традиционными ООП программистами простоту.

Снаружи

Вот то, что мы хотим получить:

var ClassA = Base({ /* создаём потомок от базового класса */
  method1: function(){}, /* определяем метод 1 */
  methid2: function(){}  /* определяем метод 2 */
});

var ClassB = ClassA({ /* наследуем от класса A */
  $init: function(){}, /* переопределяем конструктор */
  method1: function(){} /* переопределяем метод 1 */
});

/* если нам нужна возможность вызова методов родителя,
   поступаем следующим образом */
var ClassC = ClassB({
  method2: function(){
    /* делаем что-то */
    ClassB.method2.call(this);
    /* делаем что-то ещё */
  }
});

/* или так, если в момент определения класса предок
   недоступен */
var ClassD = ClassB(function(BaseClass){
  return {
    method2: function(){
      BaseClass.method2.call(this);
      /* делаем что-то ещё */
    }
  };
});

/* а может быть мы хотим осуществить вызов с аргументами,
   переданными методу потомка, и вернуть результат */
var ClassE = ClassB(function(BaseClass){
  return {
    method2: function(){
      /* делаем что-то */
      return BaseClass.method2.apply(this, arguments);
    }
  };
});

/* с наследованием разобрались, а создавать экземпляры
   ещё проще */
var inst_a = new ClassA;
inst_a.method1(); /* вызываем метод 1 */
/* вызов конструктора с аргументами, пожалуйста */
var inst_b = new ClassB(1, 2);
inst_b.method2(1, 2, 3); /* вызываем метод 2 */
/* и так далее */

То есть для создания «классов»-потомков, мы просто вызываем «родителя» как функцию, а для создания объектов-экземпляров, вызываем наши импровизированные «классы» оператором new. Как вы заметили, если при наследовании мы передаём не объект, а функцию, возвращающую объект, то у нас есть доступ к родителя через первый аргумент этой функции. Данная особенность полезна гораздо больше, чем вы себе сейчас можете представить, в Heliko-Framework, так реализуется «вынесенное наследование», когда заранее не известна цепочка наследования, и таковых может быть несколько. Например, этот принцип положен в основу конвейеров обработки запросов, которые можно конструировать по своему желанию для обслуживания нужд конкретного приложения.

Изнутри

А теперь поговорим о том, как это собственно работает. Реализация выжата из suit.js, я старался не менять структуры функций, чтобы не повредить работоспособности кода, однако взял лишь всё самое необходимое:

var voidy = {}; /* пустой объект */
function dummy(){} /* функция, которая ничего не делает */

/* проверка на экземпляр */
function iof(inst, base){
    return (inst && inst.prototype || inst)
      instanceof (base.callee || base);
}

/* утилита, определения/проверки типа */
function is(arg, type){
/* в качестве типа используется первая буква имени типа,
   при этом объект (o) и массив (a) разные типы */
  arg = arg === null ? '_' : (iof(arg, Array) ? 'a' :
    (typeof arg).charAt(0));
  if(type){
    return type.indexOf(arg) > -1;
  }
  return arg;
}

/* расширение копированием */
function mix(dst, src, force){
  for(var i in src){
    if(!(i in voidy) && (!(i in dst) || force)){
      dst[i] = src[i];
    }
  }
  return dst;
}

/* простое наследование через создание объекта-посредника */
function ext(C, B){
  /* на самом деле здесь используется небольшой трюк,
     позволяющий избежать вызов конструктора при 
     наследовании, для чего создаётся посредник с тем же
     прототипом, что и родитель */
  function F(){}
  F.prototype = B.prototype;
  C.prototype = new F();
  C.prototype.constructor = C;
  C.superbase = B.prototype;
}

/* расширение на основе прототипа */
function sub(base, data){
  if(arguments.length < 2){
    data = base;
    base = 0;
  }
  var self = is(this, 'f') ? this : function(){};
  if(base){
    ext(self, base);
  }
  if(is(data, 'f')){
    data = data(base && base.prototype);
  }
  if(data){
    mix(self.prototype, data, true);
  }
  self.prototype.constructor = self;
  return self;
}

/* функция-посредник, реализующая объектную модель */
function pro(self, args){
  if(!iof(self, args)){ /* вызвана как функция */
    var prot = args[0];
    return sub.call(function(){
      return pro(this, arguments);
    }, args.callee, prot);
  } /* вызвана как конструктор */
  self.$init.apply(self, args); /* вызов "конструктора" */
  return self;
}

/* Базовый "класс" */
function Base(){
  return pro(this, arguments);
}

Base.prototype = {
  $init: dummy
};

Вот в общем-то и всё. Если что-то вам покажется лишним или избыточным, всегда есть возможность оптимизировать, дерзайте ^^

  • Разработка для WEB
  • ecmascript
  • inheritance
  • javascript
  • js
  • наследование
  • объектная модель
  • Бортовой журнал Иллюмиума

Отправить комментарий

Содержимое этого поля является приватным и не будет отображаться публично.
  • Доступные HTML теги: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Syntax highlight code surrounded by the {syntaxhighlighter SPEC}...{/syntaxhighlighter} tags, where SPEC is a Syntaxhighlighter options string or "class="OPTIONS" title="the title".

Подробнее о форматировании

CAPTCHA
Этот вопрос задается для того, чтобы выяснить, являетесь ли Вы человеком или представляете из себя автоматическую спам-рассылку.
         ____      _     __  __  _____   ____  
___ / ___| / \ \ \/ / | ____| / ___|
/ __| | | _ / _ \ \ / | _| \___ \
\__ \ | |_| | / ___ \ / \ | |___ ___) |
|___/ \____| /_/ \_\ /_/\_\ |_____| |____/
Введите код, изображенный в стиле ASCII-арт.
RSS-материал

Навигация

  • Подшивки
  • Фотоальбомы

«Иллюмиум» на якоре.

Работает на Drupal, система с открытым исходным кодом.

(L) 2010, Illumium.Org. All rights reversed ^_~