Pull to refresh

Comments 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, разработка которой была именно им начата.
UFO just landed and posted this here
Именно, на "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); извиняюсь за неточность )
Простите, но это не изящная строчка, а адовый костыль.

Вообще, проблема топика надуманная.
Если используешь конструктор, напиши 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 отсюда.
Sign up to leave a comment.

Articles