По какой причине конструкторы классов ES6 нельзя вызывать как обычные функции?

Конструкторы классов ES6 нельзя вызывать как обычные функции. Согласно ES6, когда это будет сделано, должно быть поднято TypeError. Раньше я думал, что классы — это просто синтаксический сахар для функции-конструктора + функции в прототипе, но это немного не так.

Мне интересно, что было обоснованием этого? Если я что-то не упустил, это предотвращает вызов функции с пользовательским this, что может быть желательно для некоторых шаблонов.


person frangio    schedule 08.06.2017    source источник
comment
Может быть полезно stackoverflow.com/questions /30689817/   -  person djfdev    schedule 09.06.2017
comment
Этот вопрос, вероятно, основан на мнении, но, скорее всего, вы не забудете назвать его с помощью new. Лучше спросить где-нибудь на дискуссионном форуме TC39.   -  person RobG    schedule 09.06.2017
comment
По той же причине их нельзя вызывать как функции практически на любом другом ООП-языке? Потому что это не то, для чего нужны конструкторы?   -  person Heretic Monkey    schedule 09.06.2017


Ответы (3)


Пересмотр спецификации ES6 показывает, как вызов объекта функции класса без new отключается путем объединения разделов 9.2.9 и 9.2.1:

9.2.9 MakClassConstructor (F)
...
3. Установите для внутреннего слота F [[FunctionKind]] значение "classConstructor".

и при указании метода [[call]] вместо метода [[contruct]] функции:

(9.2.1) 2. Если внутренний слот F [[FunctionKind]] является «classConstructor», сгенерировать исключение TypeError.

Никаких ограничений на вызов функции в разделе «11.2.3 «Вызовы функций»» ES5.1 не накладывается.

Таким образом, вы ничего не упускаете: вы не можете использовать apply в функции конструктора класса.

Основное обоснование, вероятно, состоит в том, чтобы сделать расширение класса довольно строгим упражнением и обнаружить некоторые ранние формы ошибок. Например, вы не можете вызывать Promise, кроме как в качестве конструктора, а отсутствие new перед вызовом Promise является ошибкой программирования. Что касается расширения классов, обратите внимание, что свойство constructor экземпляров класса установлено правильно (последний класс после, возможно, нескольких расширений), а свойство .prototype конструктора класса доступно только для чтения - вы не можете динамически изменять объект-прототип, используемый для построения экземпляры класса, даже если вы можете изменить свойство прототипа функции-конструктора.

Раньше я думал, что классы — это синтаксический сахар, но я отошел от этой концепции.

person traktor    schedule 09.06.2017

Подводя итог, два основных момента:

  1. #P2#
  2. #P3#

Первое, что следует отметить, это то, что с точки зрения поведения класса во время выполнения эти точки функционально не связаны друг с другом. Например, вы можете разрешить Foo() без new, но при этом Foo.call({}) будет вести себя так, как будто он был newed. Возможность вызывать как функцию может разрешить установку this, но это не обязательно, точно так же, как Foo.bind({})() связывает this, а затем вызывает функцию, но связанное this будет игнорироваться.

Что касается обоснования решения, я не могу дать вам первоисточник, но могу сказать, что есть одна веская причина. Синтаксис класса ES6 — это «синтаксический сахар», но не для упрощенного кода, который вы, вероятно, имеете в своей голове. Возьмем, к примеру, этот фрагмент, учитывая вашу цель.

class Parent {}

class Child extends Parent {
  constructor() {
    // What is "this" here?
    super();
  }
}

Child.call({});

Что это должно делать? В ES6 super() фактически устанавливает this. Если вы попытаетесь получить доступ к this до вызова super(), будет выдано исключение. Код вашего примера может работать с Base.call({}), так как у него нет родительского конструктора, поэтому this инициализируется заранее, но как только вы вызываете дочерний класс, this даже не имеет значения впереди . Если вы используете .call, это значение некуда поместить.

Итак, следующий вопрос: почему дочерние классы не получают this раньше super()? Это связано с тем, что он позволяет синтаксису класса ES6 расширять встроенные типы, такие как Array, Error и Map, а также любой другой тип встроенного конструктора. В стандартном ES5 это было невозможно, хотя с нестандартным __proto__ в ES5 это можно было смоделировать грубо. Даже с __proto__ расширение встроенных типов обычно является проблемой производительности. Включив это поведение в классы ES6, механизмы JS могут оптимизировать код, чтобы расширение встроенных типов работало без снижения производительности.

Итак, на ваши вопросы, да, они могут разрешить Foo.call() или Foo(), но в любом случае придется игнорировать this, чтобы разрешить расширение встроенных типов.

person loganfsmyth    schedule 15.06.2017

что за этим стояло?

Это защита. Когда вы вызывали конструктор ES5 function без new, он делал очень нежелательные вещи, молча терпя неудачу. Генерация исключения поможет вам заметить ошибку.

Конечно, они могли бы выбрать синтаксис вызова, чтобы он работал так же, как конструкция, но применение ключевого слова new — это хорошая вещь, которая помогает нам легко распознавать экземпляры.

Это предотвращает вызов функции с пользовательским this, что может быть желательно для некоторых шаблонов.

Да, это то, что принципиально изменилось в ES6. Значение this инициализируется суперклассом, который позволяет использовать встроенные подклассы с внутренними слотами — подробности см. здесь. Это конфликтует с передачей пользовательского аргумента this, и для согласованности это никогда не должно разрешаться.

person Bergi    schedule 15.06.2017
comment
Кстати, несколько желаемых шаблонов все еще можно реализовать с помощью Reflect.construct. Что конкретно вы ищете? - person Bergi; 15.06.2017