Какая разница в 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.

  • Что-то() даст вам новые объекты типа "Что-то", когда вы используете новое ключевое слово.
  • something2() предоставит вам новые объекты типа "Объект", которые немедленно возвратят новый пустой объект.
  • новое something2 неэффективно, потому что вы создаете пустую область, из которой вы создаете новый объект

    var that = {};
    

    что эквивалентно

    var that = new Object();