Прототип OO с Object.create и именованными конструкторами - PullRequest
7 голосов
/ 29 февраля 2012

Я прихожу в Javascript с опытом работы с Python и Smalltalk, и я ценю связь между Self и Lisp в этом языке.С ECMAScript5 я хотел попробовать себя в прототипе OO без нового оператора.

Ограничения:

  • необязательный оператор new для создания классов
  • цепочка прототипов должна быть правильной для экземпляров
  • именованных конструкторов для поддержки отладки WebInspector
  • alloc (). Init () последовательность создания, такая как Objective-C и Python

Вот моя попытка реализации, чтобы соответствовать критериям:

function subclass(Class, Base) {
    "use strict";
    function create(self, args) {
        if (!(self instanceof this))
            self = Object.create(this.prototype);
        var init = self.__init__;
        return init ? init.apply(self, args) : self;
    }
    if (Base instanceof Function) Base = Base.prototype;
    else if (Base===undefined) Base = Object.prototype;

    Class.prototype = Object.create(Base);
    Class.prototype.constructor = Class;
    Class.create = create;

    Class.define = function define(name, fn) { return Class.prototype[name] = fn; };
    Class.define('__name__', Class.name);
    return Class;
}

Ипохоже, он работает в простом макете:

function Family(){return Family.create(this, arguments)}
subclass(Family, Object);
Family.define('__init__', function __init__(n){this.name=n; return this;});

function Tribe(){return Tribe.create(this, arguments)}
subclass(Tribe, Family);
function Genus(){return Genus.create(this, arguments)}
subclass(Genus, Tribe);
function Species(){return Species.create(this, arguments)}
subclass(Species, Genus);

Использование класса в качестве фабричной функции:

var dog = Species('dog');
console.assert(dog instanceof Object);
console.assert(dog instanceof Family);
console.assert(dog instanceof Tribe);
console.assert(dog instanceof Genus);
console.assert(dog instanceof Species);

Или использование нового оператора:

var cat = new Species('cat');
console.assert(cat instanceof Object);
console.assert(cat instanceof Family);
console.assert(cat instanceof Tribe);
console.assert(cat instanceof Genus);
console.assert(cat instanceof Species);

console.assert(Object.getPrototypeOf(dog) === Object.getPrototypeOf(cat))

Я упустил из виду необходимые функции прототипа OO в моей реализации?Существуют ли соглашения Javascript или взаимодействия, для которых я должен внести изменения?Итак, что за «здесь есть», и есть ли какие-то очевидные улучшения, которые нужно сделать?

Я хотел быть DRYer с определениями конструктора, но я обнаружил, что свойство name функции не доступно для записи,и это то, что поддерживает имена объектов Инспектора WebKit.Я смог построить eval, чтобы выполнить то, что я хотел, но ... блин.

Ответы [ 4 ]

9 голосов
/ 03 марта 2012

Редактировать: О, я вижу вопрос сейчас.Ответ: нет, вы правильно поняли.Единственный способ установить имя функции - это использовать объявления функций, что означает время оценки.Таким образом, вы должны иметь его в исходном коде (какой eval является задней дверью).Ранее я ответил на более простой вопрос, но с той же сущностью: Незначительный недостаток с наследованием прототипа Крокфорда .Еще один ресурс на эту тему - http://kangax.github.com/nfe/

. Существует движение для использования свойства displayName, чтобы отделить неизменяемое имя функции от ее внешнего вида отладчика.Это реализовано в Firefox и некоторых других вещах, и является бессмысленным для включения в es6, но пока еще не является частью предварительной спецификации: http://wiki.ecmascript.org/doku.php?id=strawman:name_property_of_functions

Вот статья некоторых людей, работающих над Chrome, отема о функциях именования http://querypoint -debugging.googlecode.com / files / NamingJSFunctions.pdf

А вот проблема хрома, в которой обсуждается, почему она еще не реализована: http://code.google.com/p/chromium/issues/detail?id=17356

На первоначальный ответ:

Что вы намеревались выполнить, вы сделали это хорошо.Несколько примеров подобных типов вещей, которые я сделал:

Первая - это простая «наследуемая» функция, которая позволяет вам выполнять такие вещи, как:

var MyObjCtor = heritable({
  constructor: function MyObj(){ /* ctor stuff */},
  super: SomeCtor,
  prop1: val,
  prop2: val,
  /**etc..*/
});


function heritable(definition){
  var ctor = definition.constructor;
  Object.defineProperty(ctor, 'super', {
    value: definition.super,
    configurable: true,
    writable: true
  });
  ctor.prototype = Object.create(ctor.super.prototype);
  delete definition.super;

  Object.keys(definition).forEach(function(prop){
    var desc = Object.getOwnPropertyDescriptor(definition, prop);
    desc.enumerable = false;
    Object.defineProperty(ctor.prototype, prop, desc);
  });

  function construct(){
    var obj = new (ctor.bind.apply(ctor, [].concat.apply([null], arguments)));
    ctor.super.call(obj);
    return obj;
  }

  construct.prototype = ctor.prototype;

  return construct;
}

Другая - в создании структур дляиспользовать с libffi (node-ffi).По сути, здесь у вас есть как наследование конструктора, так и наследование прототипа.Вы создаете конструкторы, которые наследуются от конструкторов, которые создают экземпляры структур.https://github.com/Benvie/node-ffi-tools/blob/master/lib/Struct.js

Использование eval требуется для создания именованной функции, так что это если вам нужен именованный конструктор.У меня нет никаких сомнений по поводу использования его в случае необходимости.

4 голосов
/ 07 марта 2012

Возможно, это не ответит на ваш вопрос, и, вероятно, не будет делать все, что вы хотите, но это еще один способ использовать OO JavaScript. Мне нравится это в основном из-за синтаксиса; мне легче грокать, но у меня нет твоего прошлого.

// ------- Base Animal class -----------------------
var Animal = function(name) {
    this.name = name;
};

Animal.prototype.GetName = function() {
    return this.name;
};
// ------- End Base Animal class -----------------------

// ------- Inherited Dog class -----------------------
var Dog = function(name, breed) { 
    Animal.call(this, name);
    this.breed = breed;
};

Dog.prototype = new Animal();
Dog.prototype.constructor = Dog;

Dog.prototype.GetBreed = function() {
  return this.breed; 
};

Dog.prototype.Bark = function() {
    alert(this.GetName() + ' the ' + this.GetBreed() + ' says "Arf!"');
};
// ------- End Inherited Dog class -----------------------

// Usage
var harper = new Dog('Harper', 'miniature pinscher');
harper.Bark();
4 голосов
/ 03 марта 2012

Шейн Я могу сказать, что, исходя из вашего прошлого, Javascript ведет себя не так, как вы пытаетесь. Прежде чем вы сразу же возненавидите этот ответ, прочитайте ...

Я люблю ООП и родом из ООП. Мне потребовалось некоторое время, чтобы обернуться вокруг объектов Prototype (думать о методах-прототипах как о статических функциях, которые работают только с инициализированными объектами). Многие вещи, которые сделаны в javascript, кажутся очень «неправильными» из-за языка, который использует хорошую безопасность и инкапсуляцию.

Единственная реальная причина использовать оператор «new» - это если вы создаете несколько экземпляров или пытаетесь предотвратить запуск какого-либо типа логики до тех пор, пока не будет инициировано событие.

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

Удачи.

UPDATE

Так что, если вам нужен фабричный метод для создания разных экземпляров одного и того же объекта, вот несколько советов, которые я бы хотел услышать:

  • Пожалуйста, пожалуйста, пожалуйста, забудьте обо всех правилах, которые вы узнали из ОО инкапсуляции, которые вы знаете. Я говорю это потому, что при совместном использовании ваших протологических объектов, литеральных объектов и объектов экземпляров будет так много способов получить доступ к вещам способом, который не будет естественным для вас.
  • Если вы планируете использовать какой-либо тип абстракции или наследования, я настоятельно рекомендую поиграть с DOM. Создайте несколько объектов и найдите их в DOM и посмотрите, что происходит. Вы можете смоделировать свое наследование, используя существующие прототипы (это убило меня, не имея интерфейсов и абстрактных классов для расширения!).
  • Знайте, что все в javascript - это объект ... это позволяет вам делать очень интересные вещи (передавать функции, возвращать функции, создавать простые методы обратного вызова). Посмотрите на вызовы javascript '.call ()', '.apply ()' и '.bind ()'.

Знание этих двух вещей поможет вам в решении вашего решения. Чтобы поддерживать чистоту в DOM и следовать хорошей практике разработки javascript, необходимо поддерживать чистоту глобального пространства имен (ссылка на окно в DOM). Это также заставит вас чувствовать себя «лучше» во всем, что вы считаете «неправильным» в том, что вы делаете. И будьте последовательны.

Это то, чему я научился методом проб и ошибок. Javascript - это отличный и уникальный язык, если вы можете понять его отличие от классических ОО-языков. Удачи!

1 голос
/ 06 марта 2012

Вы не можете создавать именованные конструкторы без eval. Все, что вы найдете, это конструктор со скрытым свойством с именем, если библиотека реализует пространства имен.

Отвечая на остальную часть вопроса, есть много библиотек классов javascript. Вы можете написать свое собственное, но немного непонятно, чтобы понять и внедрить правильную систему наследования, если вы хотите сделать это правильно.

Я недавно написал свою собственную библиотеку классов, пытающуюся улучшить все библиотеки: Classful JS . Я думаю, что стоит взглянуть, потому что его простой дизайн, использование и некоторые другие улучшения, такие как вызов любого супер-метода из подкласса (я не видел ни одной библиотеки, которая могла бы сделать это, все они могут вызывать только супер-метод, который был перекрытие).

...