Можно ли объединить функцию конструктора javascript и object.create?

Обновить

Если это невозможно, пожалуйста, не стесняйтесь предоставить ответ, объясняющий, почему. Я был бы рад отметить, что он принят.


Я хотел бы немного упростить следующий код (два шага для объявления объекта, я бы хотел иметь один):

var Masher = function(opts) {
    this._name  = opts.name;
};

Masher.prototype = Object.create(Object.prototype, {
    _name:  { writable: true },
    name:  { get: function() { return this._name;  }}
});

// Note: (new Masher({name: 'bar'})).name == 'bar'

Я хотел бы создать весь прототип функции за один раз с функцией конструктора, появляющейся где-то в Object.create. Возможно, что-то вроде этого:

var Basher = Object.create(Function.prototype, {
    _name:  { writable: true },
    name:  { get: function() { return this._name;  }},
    constructor: { value: function(opts) { this._name = opts.name; }}
});

Однако, когда я вызываю new Basher(), я получаю: «Ошибка типа: объект не является функцией».

Хотя я понимаю, что могу сделать это с помощью синтаксического сахара (вспомогательной библиотеки), моя цель здесь состоит в том, чтобы сделать все как можно проще и получить некоторое представление о JS-объекте, прототипе, внутренностях конструктора. Я попытался прочитать как можно больше об этом: вопросы, связанные с SO , Крокфорд, Бен Надель, Йост Дипенмаат.

Возможно я не нашел правильную формулировку, или я борюсь с философией дизайна Object.create, или язык этого не позволяет. Возможно, это действительно просто стилистическая вещь, а значит, и самомнение.

Конечно, я могу жить с двухэтапным процессом (Masher). Есть что-то правильное в том, чтобы упаковать все это в один кадр (Башер).

Есть ли способ сделать это? Спасибо.


person Andrew Philips    schedule 06.10.2014    source источник
comment
Нет, ты не можешь. Вам нужно создать два отдельных объекта: функцию-конструктор и объект-прототип.   -  person Bergi    schedule 06.10.2014


Ответы (3)


Если вы хотите использовать подход на основе классов, когда вы можете вызвать функцию-конструктор с помощью new, у вас всегда будет две части:

  • Сама функция-конструктор (для инициализации экземпляров)
  • Объект-прототип (для общих свойств)

Если вы не хотите полностью отказываться от прототипа, у вас нет синтаксиса JavaScript для одновременного создания функции и настройки прототипа (кроме, конечно, нового синтаксиса ES6 class), сохраняя при этом ссылку .prototype из функцию к объекту-прототипу. Конечно, подойдет тривиальная вспомогательная функция (не обязательно полная библиотека):

function Class(p) {
    return (p.constructor.prototype = p).constructor;
}
var Casher = Class({
    constructor: function(opt) { this._name = opt.name },
    get name() { return this._name }
});
var foo = new Casher({name:'bar'});

Эти шаблоны на самом деле не имеют ничего общего с Object.create (за исключением того, что вы хотите, чтобы ваш прототип наследовал от другого).

Так что да, возможно, вы пытаетесь бороться с философией Object.create, которая заключается в использовании только объектов и получении из них других объектов (прочитайте статья Википедии об этом и обязательно ознакомьтесь с некоторыми примерами из языков, отличных от JS). У вас не было бы конструктора и оператора new, скорее вы бы вызвали метод create для своего объекта:

var Proto = { // some helper methods (usually native in more prototype-focused languages)
    clone: function() {
        return Object.create(this);
    },
    create: function(opt) {
        var derived = this.clone();
        derived.init(opt);
        return derived;
    },
    init: function(opt) {
        Object.getOwnPropertyNames(opt).forEach(function(p) {
            Object.defineProperty(this, p, Object.getOwnPropertyDescriptor(opt, p));
        }, this);
    }
};

var Pasher = Proto.create({ // "subclass" Proto
    init: function(opt) {
        if ("name" in opt) this._name = opt.name;
    },
    _name: "",
    get name() { return this._name; }
});
var foo = Pasher.create({name:'bar'});
person Bergi    schedule 06.10.2014
comment
Спасибо. Указатель синтаксиса класса ES6 помогает. Мне нравится предложение класса, хотя в нем отсутствуют дескрипторы свойств, поэтому не уверен, что буду его использовать. Я прочитал статью в Википедии. Ничто в нем не мотивировало, почему создание объектов на языке, основанном на прототипах, не должно иметь конструкторов, не говоря уже о правильно сформированном синтаксисе для конструкторов. После просмотра нового определения класса ES6 мне кажется, что это вопрос эволюции языка — заимствование хороших идей из других языков. Мой опыт работы с Self, Lisp, C++, Java и C# заставляет меня хотеть упорядоченного, явного определения класса. Думаю, я жду. - person Andrew Philips; 07.10.2014

Object.create() возвращает объект, а не функцию, с определенной определенной прототипной цепочкой наследования.

var Basher = Object.create(Function.prototype, {
    _name:  { writable: true },
    name:  { get: function() { return this._name;  }},
    constructor: { value: function(opts) { this._name = opts.name; }}
});
> undefined
Basher
> Object {_name: undefined, name: (...)}_name: undefinedconstructor: function (opts) { this._name = opts.name; }name: (...)get name: function () { return this._name;  }__proto__: function Empty() {}
> typeof Basher
"object"

Однако вы можете комбинировать функции Object.create() и конструктора, чтобы повторно использовать литералы объектов в качестве API, что сделает код немного чище:

var basherAPI = {
    name: function () {
        return this._name;
    }
};

function Basher(name) {
    var inst = Object.create(basherAPI);
    // assign instance *state* here; the API is
    // reusable but each object needs its own state
    inst._name = name;
    return inst;
}

var basher = new Basher('Tom');
basher.name() // Tom

РЕДАКТИРОВАТЬ: В этом случае использование ключевого слова new является чисто условным; это не имеет никакого отношения к тому, что происходит внутри функции конструктора. Можно было бы также написать: var basher = Basher('Tom');.

person Nicholas Cloud    schedule 06.10.2014
comment
То, что Basher начинается с заглавной B, может сбить с толку других программистов, думая, что они могут наследовать от него, но на самом деле не могут. Поскольку это фабричная функция, возможно, лучше не начинать ее с большой буквы. - person HMR; 06.10.2014
comment
Да, отличный момент. Однако наследование может быть достигнуто с помощью Object.create(): Object.create(basher). Возможно, переименование Bahser в basherFactory было бы еще лучшим подходом. - person Nicholas Cloud; 06.10.2014
comment
@NicholasCloud, я ценю усилия. Тем не менее, я знаю, что могу улучшить это с помощью других функций и сделать так, чтобы это выглядело как одна строка. Кроме того, мне нравится оператор «новый», поскольку он сигнализирует о создании объекта тому, кто читает код. Добавление Factory к имени функции действительно дало бы аналогичный сигнал. Однако здесь не будет шаблона Factory. - person Andrew Philips; 06.10.2014

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

Самое близкое, что я могу сделать, это следующее:

var Lasher;

(Lasher = function (opts) {
    this._name = opts.name;
}).prototype = Object.create(Object.prototype, {
    _name:  { writable: true },
    name:  { get: function() { return this._name;  }},
});

Эта формулировка раскрывает часть того, что происходит, что-то, на что @Bergi намекнул, хотя и кратко. Во-первых, обратите внимание, что это не отвечает на мой вопрос, поскольку для этого все еще требуется два шага (объявление переменной, а затем присвоение значения). Это доходит до сути моей стилистической проблемы, которая заключается в совместном размещении всего кода объявления объекта. Он делает это, захватывая определение анонимной функции (конструктор) в Lasher, затем ссылаясь на свойство prototype, чтобы назначить ему объект-прототип (остальная часть объявления «класса»).

Несмотря на то, что они сгруппированы как одна вычислительная единица, визуально их сложнее разобрать, и, откровенно говоря, они довольно уродливы. Кроме того, анонимная функция может даже захватить Lasher в своей области (я не уверен).

Кстати, я попробовал этот ошибочный подход. Это одно утверждение, хотя все еще немного уродливое:

(function Crasher(opts) {
    this._name = opts.name;
}).prototype = Object.create(Object.prototype, {
    _name:  { writable: true },
    name:  { get: function() { return this._name;  }},
});

Жаль, что это не работает. Crasher относится к первому () и не находится во внешней области видимости, поэтому в предыдущем коде Crasher приходилось объявлять как переменную, чтобы захватить анонимную функцию.

Дело в том, что Lasher на самом деле не проще, чем Masher (выше) с точки зрения количества строк или удобства чтения. Итак, оригинальная формулировка считается лучшей (пока).

Кроме того, хотя я понимаю, почему Object.create нельзя использовать, поскольку он создает объект, я думаю, я не понимаю, как внутренний интерпретатор JS создает функции и объекты. Я имею в виду, что функция — это объект, но я думаю, что нет способа создать функцию, которая может быть возвращена непосредственно из Object.create. Это главная проблема.

Кроме того, что бы ни происходило внутри назначения прототипа и назначения конструктора внутри него, добавление свойства конструктора в Object.create не работает. Мы можем видеть это с помощью этого фрагмента кода:

var Masher = function(){ console.log("default");};

Masher.prototype = Object.create(Object.prototype, {
    _name:  { writable: true },
    name:  { get: function() { return this._name;  }},
    constructor: { value: function(opts) {
        console.log("ctor");
        this._name  = opts.name;
    return this;
    }}
});
// "new Masher()" outputs:
// default
// undefined

Итак, если я правильно понимаю часть из этого, создание функций и создание прототипа вдобавок к этому имеют некоторые глубокие отношения в интерпретаторе/компиляторе JS, и язык не предоставляет средств для выполнения того, что я пытаюсь сделать.

person Andrew Philips    schedule 06.10.2014