Какая разница в JavaScript между конструкторской функцией и функцией, возвращающей объект, который вызывается как конструктор?
Я знаю, что это не рекомендуемый способ сделать это, но если я объявлю следующие функции, а затем вызову их как конструкторы, какова будет разница (если таковая имеется) между результирующими объектами?
function Something() {
this.foo = "bar";
}
function something2() {
var that = {};
that.foo = "bar";
return that;
}
var x = new Something();
var y = new something2();
var z = something2();
т.е. что будет отличаться между x
, y
и z
здесь?
Не было бы something2
намного лучше писать конструктор, так как использование new
или не повлияет на результат функции?
Кстати, something2
должен быть капитализирован здесь? (Я предполагаю, что с тех пор, как Крокфорд настолько непреклонен в капитализации, поскольку функции будут сжимать глобальное пространство имен...)
Ответы
Ответ 1
Короче:
new something2() instanceof something2 === false
Кроме того, если вы расширяете свой пример, чтобы использовать свойство prototype
Something.prototype.method = function () { };
something2.prototype.method = function () { };
вы обнаружите, что прототип не наследуется в последнем случае:
typeof (new Something()).method === "function"
type (new something2()).method === "undefined"
Реальный ответ заключается в том, что вы используете совершенно разные базовые механизмы. Вызов с помощью new
вызывает механизм [[Construct]], который включает настройку свойства [[Prototype]] в соответствии с .prototype
свойство конструктора.
Но на этапах 8-10 алгоритма [[Construct]] происходит смешное: после создания нового пустого объекта и последующего прикрепления его [[Prototype]] он выполняет [[Call]] к фактическому конструктору, используя этот новый объект "пустой плюс-прототип" как this
. И затем, на шаге 9, если выяснится, что этот конструктор что-то вернул, он выбрасывает этот прототипно-связанный, переданный-as-this
объект, который он потратил все это время!
Примечание. Вы можете получить доступ к объекту [[Прототип]] (который отличается от конструктора .prototype
) с помощью Object.getPrototypeOf
:
Object.getPrototypeOf(new Something()) === Something.prototype // steps 5 & 6
Object.getPrototypeOf(new something2()) === Object.prototype // default
Чтобы ответить на некоторые мета-вопросы:
- Нет, не используйте
something2
, поскольку это функция factory, а не конструктор. Если что-то капитализировано, ожидается, что он будет иметь семантику конструктора, например. new A() instanceof A
.
- Если вас беспокоит опасность слияния глобального пространства имен, вы должны начать использовать строгий режим, поставив
"use strict";
на в верхней части ваших файлов. Одна из многих хороших чисток строгого режима заключается в том, что this
по умолчанию имеет значение undefined
, а не глобальный объект, т.е. вызов конструктора без new
приведет к ошибкам в тот момент, когда конструктор пытается привязать свойства к undefined
.
- Factory функции (так называемые "шаблоны закрытия" ) обычно являются разумной заменой для конструкторов и классов, если вы (а) не используете наследование; (b) не создавать слишком много экземпляров этого объекта. Последнее состоит в том, что в шаблоне закрытия вы присоединяете новый экземпляр каждого метода к каждому вновь созданному объекту, что не очень удобно для использования памяти. Самый большой выигрыш, IMO, схемы закрытия - это возможность использовать "private" переменные (которые являются хорошая вещь, и не позволяйте никому говорить вам иначе: P).
Ответ 2
Во втором случае возвращаемый объект не наследует ничего от конструктора, поэтому мало смысла использовать его как таковое.
> var x = new Something();
> var y = new something2();
> var z = something2();
т.е. что будет отличаться здесь от x, y и z?
x
наследует от Something
, не наследует ни y
, ни z
от something2
.
Разве что-то не было бы намного лучше писать конструктор, поскольку независимо от того, используете ли вы новый или нет, это не повлияет на результат функция?
Нет смысла вызывать something2
как конструктор, потому что возвращаемый им объект не является вновь созданным объектом, назначенным его this
, который наследуется от something2.prototype
, что другие могут ожидать получить при вызове new something2()
.
Кстати, что-то2 должно быть капитализировано здесь? (Я предполагаю, что с тех пор Крокфорд настолько непреклонен в капитализации, что функции clobber глобальное пространство имен...)
Нет, потому что вызов его как конструктора немного бессмыслен, поэтому он характеризует его как обманчивый.
Ответ 3
Вызов функции в качестве конструктора (т.е. с помощью нового keyword
) выполняет следующие шаги:
- создать новый объект
- задайте прототип этого объекта объекту в свойстве
prototype
функции
- выполнить конструкторную функцию в контексте этого объекта (т.е.
this
- новый объект)
- возвращает этот объект (если конструктор не имеет инструкции
return
)
Итак, ваше второе решение просто вернет простой объект с свойством "foo". Но ни y
, ни z
не являются instanceof Something2
и не наследуются от этого прототипа. Есть такие функции, да, но они не должны называться конструкторами (нет прописных имен, нет invokation с new
). Они принадлежат шаблону factory.
Если вам нужен конструктор, который может быть выполнен без нового, используйте этот код:
function Something(params) {
if (! this instanceof Something)
return new Something(params);
// else use "this" as usual
this.foo = "bar";
...
}
Ответ 4
Я бы сказал, что самым важным было бы прототип возвращенных объектов.
function Something() {
this.foo = "bar";
}
Something.prototype = {
// Something prototype code
hello: function(){
//...
}
}
function something2() {
var that = {};
that.foo = "bar";
return that;
}
something2.prototype = {
// something2 prototype code
greetings : function() {
//...
}
}
var x = new Something();
var y = new something2();
var z = something2();
typeof x.hello === function // should be true
typeof y.greetings === undefined // should be true
typeof z.greetings === undefined // should be true
Другими словами, я бы сказал, что вы не создаете объекты с чем-то2, вы создаете чисто новые объекты, которые наследуются от Object.