Обновить

Комментарии 39

Какая-то надуманная проблема, хотя у меня базовый конструктор кидает exception если new забываешь, ибо нефик.
Давно применяю паттерн, который я называю «универсальный конструктор»:

function MyType (foo, bar)
{
  var instance = Object.create(MyType.prototype);

  instance._foo = foo;
  instance._bar = bar;

  return instance;
}
// работают оба варианта:
new MyType(1, 2);
MyType(1, 2);


До этого использовал следующий паттерн:

function MyType (foo, bar)
{
  if (this instanceof MyType)
  {
    this._foo = foo;
    this._bar = bar;
  }
  else
  {
    return new MyType(foo, bar);
  }
}

Также работает в обоих вариантах, но чувствительно к количеству аргументов и обвязка громоздкая.
Если во втором варианте в сáмом начале «if(!( this instanceof MyType )) return new MyType(foo, bar)» записывать, то тогда эта однострочная обвязка ужé не покажется громоздкою, к тому же порядок и количество аргументов в ней куда проще будет приводить в соответствие с их определением, после слóва «function» записанным, потому что тогда слово это будет на предыдущей строке — ближе некуда, как говорится.
Вполне согласен с вами. Пожалуй, в своё время я так и писал (как вы указываете). Здесь, потому что это пример, вышло немножко академично.
Дэвид Херман объясняет автонаследование в своей книге Effective JavaScript.
А чтобы кто-нибудь случайно не подумал, что именно Херман и придумал этот трюк, я посвящу три абзаца рассмотрению хронологии событий.

Выход этой книги Хермана состоялся в 2012 году — или, по крайней мере, именно в декабре 2012 года был выложен исходный код её примеров на Гитхабе её автором.

Рассуждение о самовызывающемся конструкторе Джона Резига было выложено мною на Хабрахабре в декабре 2011 года и содержало гиперссылку на блогозапись, которую Резиг оставил у себя во блоге в декабре 2007 года.

С тех давних пор прошло без малого семь лет, и появление конструктора с необязательным «new» можно было заметить в нескольких произведениях Резига, из которых наиболее известна библиотека jQuery, разработка которой была именно им начата.
люто бешено плюсую
НЛО прилетело и опубликовало эту надпись здесь
Именно, на "use strict" нужно было закончить статью.
Вот-вот. И JSHint ругнется на вызов конструктора без new.
По мне так можно было бы ещё добавить одно предложение с призывом писать юнит тесты, если вы невнимательны и можете пропускать в коде new и код не проходит ревью.

Но нет же, сделают библиотеку, которая разовьётся во фреймворк, который форкнут и будут развивать независимо, и спустя какое-то время будет организована конференция по использованию этого фреймворка, а на хабре его фанаты потребуют создать отдельный хаб.
Постоянно попадаюсь на это, т.к. в основном пишу на Python, где создание экземпляра выглядит так:
foo = Foo()

А при работе с тем же Backbone вылетают совсем неадекватные ошибки, например:
var my_model = Model();
// Error: _r or some other minified shit is undefined
4ый способ: jshint(newcap), unit-tests и, как сказали, «use strict» — при таком наборе пропустить `new` просто невозможно, при этом нет хаков в конструкторах. Ну а использование `new` по назначению повышает выразительность кода.
Кажется, первый и второй способы приводят к ухудшению кода (поддержка различных «написаний»).
Третий лучше, но задолбаешься писать везде проверки.

use strict — лучший вариант
Для случая с динамическим количеством аргументов:
Можно убрать танцы с obj = new Fubar; Fubar.apply(obj, args) и написать не очень изящную, но функциональную строчку:

var fubar;
fubar = new (Function.prototype.bind.apply(Fubar, [null].concat(arguments)));


Сравнение
Если я не ошибаюсь, можно даже и [].concat писать
Всмысле без null? Если да, то ошибаетесь, нельзя — тогда первый элемент списка аргументов станет контекстом для результирующей функции. Если аргументов у конструктора нет, то да, можно просто передать пустой массив или вообще ничего. =)
Добавил — Тык =). Это победа =)
А вообще, в случае с динамическим количеством аргументов, лучше поступить так:

var that = this instanceof MyClass ? this : Object.create(MyClass.prototype);

и дальше работать с that. Передача объекта arguments в другую функцию приводит к деоптимизации функции.
Можете пояснить фразу про деоптимизацию? Почему? В моем примере имелся в виду результат Array.prototype.slice.call(arguments, 0); извиняюсь за неточность )
Здесь, например, расписано. С объектом arguments лучше вообще дел не иметь, а если и иметь — только получение элемента по индексу, длины и передача в apply.
Мерси боку!
Простите, но это не изящная строчка, а адовый костыль.

Вообще, проблема топика надуманная.
Если используешь конструктор, напиши new и точка. Если фабрика — не пиши. Проблем нет.
Забыл/лень — ССЗБ.
В чем костыльность и адовость?
Вам знакомо понятие reflection из других языков программирования? Это тоже костыль?

Мне кажется, вы не совсем поняли проблему, но пытаетесь раскритиковать решение.
Извините, когда я читал комментарий, то пропустил «не» перед «очень».

Я прекрасно понимаю «проблему».
Она, как обычно, в головах. За пропущенный new надо давать по рукам, а не копипастить костыли из конструктора в конструктор.
Именно проблему, а не «проблему». Речь идет не только про баги и забывчивость разработчиков, но, например и в частности в моем примере, про абстрактную фабрику, которая не знает конструктор, экземпляр которого она создает. По сему, у конструктора может быть разное количество аргументов.
Я понимаю, почему было написано именно так.
Мой поинт в том, что эта фабрика вообще не нужна =)

Тем более, что в таком виде она сильно неэффективна, можно и нужно ее оптимизировать в производительности, а это уже не одна строчка.
Почему не нужна? IoC контейнер как на js будете писать? ) В более пропаханных языках есть reflection, который позволяет вызывать метод типа newInstance(args) у рефлекшна класса.В js пока что это можно сделать указанными выше способами.
Потому что программист должен уметь пользоваться интерфейсом. Если интерфейс — конструктор, пользуйся им как конструктором.

Никак не буду. Мне хватает обычного конструктора. Вспоминается поговорка про самовар.
Какие-то вы слишком добрые. Если фунция — исключительно конструктор, то нечего вызывать её как просто функцию.

function Fubar (foo, bar){
  if (!(this instanceof Fubar)){
    throw new TypeError('Fubar is a constructor');
  }
  /* … */
}

Кстати, вызывать this._superclass.apply(this, arguments) это не помешает.
Если фунция — исключительно конструктор, то нечего вызывать её как просто функцию.

Я бы не был столь критичен. Предположим, у нас есть фабрика, например,

function Dict(props){
  var dict = Object.create(null);
  return Object.assign(dict, props);
}

которая со временем превращается в полноценный конструктор

function Dict(props){
  Object.assign(this, props);
}
Dict.prototype = Object.create(null);
Dict.prototype[Symbol.iterator] = function(){ /* ... */}

Нам что, перелопачивать сотни кода, добавляя new? :)

Или другая проблема:

// Обычный конструктор
array.map(function(it){
  return new MyConstructor(it);
});
// Самовызавыющийся конструктор
array.map(MyConstructor);

Хотя для решения последней предлагают универсальный фабричный метод:

Object.defineProperty(Function.prototype, 'new', {
  get: function(){
    var Ctor = this;
    return function(...args){
      return new Ctor(...args);
    }
  }
});
// ...
array.map(MyConstructor.new);
Странные проблемы:
— Фабрика должна создавать и проводить другие операции над обьектами через статические методы: `Dict.create` `Dict.resolve` и так далее. Это более гибкое и явное поведение. А если уж там что то себе передумали в архитектуре и захотели убрать фабрику, то да, нужно перелопачивать код. Даже с простым рефакторингом нужно перелопачивать код, а уж с архитектурой тем более нужно.

`array.map(x => new MyConstructor(x))` всё же лучше — кода не больше, поведение явное, нет зависимостей от стороннего АПИ в прототипах.
Может проблемы и странные, но встречаются довольно часто.

Фабрика должна создавать и проводить другие операции над обьектами через статические методы: `Dict.create` `Dict.resolve` и так далее.

Да ладно? :) Всегда мечтал для создания простого ассоциативного массива писать Dict.resolve({/* ... */}).

А если уж там что то себе передумали в архитектуре и захотели убрать фабрику, то да, нужно перелопачивать код. Даже с простым рефакторингом нужно перелопачивать код, а уж с архитектурой тем более нужно.

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

нет зависимостей от стороннего АПИ в прототипах

Собственно, ссылка на предложение добавления этого метода в стандарт ниже.
В этом-то и заключается выразительность кода. Каждое название метода, что-то да обозначает, `resolve`, например, больше значит, что создание объекта зависит от внешних факторов, тогда как `create` действует внутри по строгому алгоритму без внешних параметров.
Далее; у вас `Dict` изначально был функциональным/фабричным методом, а назван существительным. И тут совсем уж не понятно, что он делает — создаёт объект Dict, так почему без new, а если не создает Dict, тогда что же создает; а может вообще ничего не создает, а мутирует аргумент. Вообщем, со стороны возникает много вопросов.

Более того, система была бы более гибкой, сами же видите, что у вас бы возникли проблемы с созданием через конструктор (если бы не хак). Поэтому используя `Dict.create`, например, можно было бы смело сделать создание через конструктор, не боясь поломать обратную совместимость.

В любом случае, это лишь мое мнение, и я вовсе не хочу вам его навязывать; лишь поделился размышлениями.
Почему Dict, а не Dict.createне ко мне. В любом случае, не менее выразительно и, главное, куда более кратко.
создаёт объект Dict, так почему без new
Может потому, как в данном случае он создаёт объект без прототипа, а конструкторы так не умеют? :) Да мало ли почему — фабрика.
сами же видите, что у вас бы возникли проблемы с созданием через конструктор
Возникли бы, если не использовать самовызывающийся конструктор. Без всяких хаков.
Вообщем, со стороны возникает много вопросов.
А на аналогичные Object или Array не возникают. Или что там у нас нонче самое популярное, jQuery? Сама очевидность в плане возвращаемого значения основного метода.
Это они там только предлагают это как «шорткат», но это ни в коем случае, также как и с jQuery, не отменяет того что у фабрик должны быть фабричные методы :) Просто jQuery, стандартный API могут вводить некоторые послабления в угоду краткости, так как всем в принципе известно их поведение, а вот если я пишу код для себя и коллег, то тут следует быть более конкретным. А штуки как «самовызывающий конструктор» скрывает от «читателя» свое поведение, поэтому данный трюк считаю даже немного вредным.
Хм, я так понял, что в статье решает другая проблема — забытый new, т.е., конструктор вызывается как функция. Вы же приводите пример обратного: из функция вызывается как конструктор. Никто же не мешает чуть-чуть подправить исходную функцию Dict:

function Dict(props){
  var dict = Object.create(Dict.prototype);
  return Object.assign(dict, props);
}
Dict.prototype = …


И всё. Функция возврящает объект, поэтому результаты вызова как функции и как конструктора одинаковы.

Кстати, про Function.prototype.new, начиная с ES6 мы же вроде как можем fn[Symbol.create]() (ссылка на черновик спеки). Или нет? Если я неправ, то буду рад, если вы меня поправите, пионеров ES6 ещё не так уж много.
Собственно, это был ответ на ваш комментарий о том, что «нечего вызывать конструктор без new», статья здесь почти не причем.

Никто не мешает чуть-чуть подправить исходную функцию, но, ИМХО, если функции создаёт инстанс из своего прототипа — она должна быть конструктором. Да и в актуальных движках new на подобном значительно быстрее Object.create.

Метод ко ключу Symbol.create создаёт объект из прототипа конструктора и устанавливает, если нужно, скрытые свойства, а не вызывает внутренний метод [[Construct]]. Он предназначен, скорее, для наследования встроенных конструкторов. Его судьба в ES6 туманна. А пример с Function.prototype.new отсюда.
Всё, нет больше @@create. Хотя в tc39 решили его грохнуть еще 24 сентября.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации