воскресенье, 19 мая 2013 г.

Небольшой JS-велосипед с пояснениями

Микробиблиотечка для формирования классов в JavaScript. Целиком обозреть файл можно на GitHub, а ниже я его рассмотрю по порядку фрагментов.

§ 1

var WeRB = function WeRB (config) {
  WeRB.merge(this, config || {});
};

Формируем объект – пространство имен, а заодно и функцию – конструктор базового класса. Возможно, в дальнейшем я откажусь от совмещения этих двух ролей в одном объекте, но пока так.

§ 2

if (Object.defineProperty) {

  WeRB.merge = function (target, source, deep) {
    var props = Object.keys(source);
    for (var i = 0, l = props.length; i < l; i++) {
      var desc = Object.getOwnPropertyDescriptor(source, props[i]);
      if (deep) {
        var value = desc.value;
        if (value && typeof value === 'object') {
          if (!(value instanceof Array || value instanceof Boolean ||
                value instanceof Date || value instanceof Error ||
                value instanceof Function || value instanceof Number ||
                value instanceof RegExp || value instanceof String)) {
            desc.value = WeRB.merge(target[props[i]] || {}, value);
          }
        }
      }
      Object.defineProperty(target, props[i], desc);
    }
    return target;
  };

} else {

  WeRB.merge = function (target, source, deep) {
    for (var prop in source) {
      if (source.hasOwnProperty(prop) && prop !== 'inherited' &&
          prop != 'constructor' && prop != '__proto__') {
        var value = source[prop];
        var old = target[prop];
        if (deep && old && typeof value === 'object') {
          if (!(value instanceof Array || value instanceof Boolean ||
                value instanceof Date || value instanceof Error ||
                value instanceof Function || value instanceof Number ||
                value instanceof RegExp || value instanceof String)) {
            value = WeRB.merge(old, value);
          }
        }
        target[prop] = value;
      }
    }
    return target;
  };

}

Определяем функцию, примешивающую к одному объекту перечислимые свойства другого... Тут возникает проблема со старыми браузерами — если функция совместима с ними, то в новых вместо корректного копирования геттеров и сеттеров она перенесет только значение. В данном случае я счел наилучшим выходом сделать два варианта функции.

target
объект, в который копируются свойства;
source
объект, из которого копируются свойства;
deep
булев флаг, определяющий, следует ли объединять одноименные свойства источника и приемника, если это объект, исключая встроенные классы.

Возвращает функция объект-приемник.

§ 3

WeRB.mixin = function (mix) {
  if (typeof mix === 'function') {
    return this.mixin(mix.prototype);
  }
  var p = function () {};
  p.prototype = this.prototype;
  this.prototype = new p();
  WeRB.merge(this.prototype, mix);
  this.prototype.constructor = this;
  return this;
};

«Подмешиваем» объект к классу. Если подмешивается функция, то берется ее прототип как класса. Хочу обратить внимание, что свойства не просто так объединяются, а вставляются отдельным пунктом в цепочку прототипов. Каждую такую примесь можно также рассматривать как анонимный класс в цепочке наследования.

§ 4

WeRB.class = function (config) {
  var c = config || {};
  var s = c['superclass'] || this;
  var n = c['namespace'] || this;
  var m = c['mixin'] || [];
  var f = c['constructor'] || function () { s.apply(this, arguments); };
  var p = function () {};
  p.prototype = s.prototype;
  f.prototype = new p();
  if (Object.defineProperty) {
    Object.defineProperty(f.prototype, 'constructor', {
      value : f,
      writeable : false,
      configurable : false,
      enumerable : false
    });
    Object.defineProperty(f.prototype, 'inherited', {
      value : p.prototype,
      writeable : false,
      configurable : false,
      enumerable : false
    });
    Object.defineProperty(f.prototype, '__proto__', {
      writeable : false,
      configurable : false,
      enumerable : false
    });
  } else {
    f.prototype.constructor = f;
    f.prototype.inherited = p.prototype;
  }
  delete c['superclass'];
  delete c['namespace'];
  delete c['mixin'];
  delete c['constructor'];
  WeRB.merge(f.prototype, c);
  f.superclass = s;
  f.class = WeRB.class;
  f.mixin = WeRB.mixin;
  for (var i = 0, l = m.length; i < l; i++) {
    f.mixin(m[i]);
  }
  if (f.name && f.name != '') {
    n[f.name] = f;
  }
  return f;
};

Создает класс по объекту-описанию. В качестве конструктора лучше передавать неанонимную функцию.

Микро-демонстрация

  WeRB.class({
    desc : 'DEMO',
    constructor : function Alpha () {}
  });

  WeRB.Alpha.class({
    get beta() {
      return this._beta;
    },
    set beta(val) {
      console.log({set_beta : val});
      this._beta = val;
    },
    namespace : WeRB,
    constructor : function Beta () {}
  });

  WeRB.class({
    superclass : WeRB.Beta,
    constructor : function Gamma () {}
  });

  var cx = new WeRB.Gamma();

  cx.beta = 100;

  console.log({
    a : new WeRB.Alpha(),
    b : new WeRB.Beta(),
    c : cx
  });

Комментариев нет:

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