В июле 2012 года на совещании ТК39
[1] были приняты классы для ECMAScript.next, будущей версии стандарта языка JavaScript. В этом блоге объясняется, как работают эти классы. Он основан на аннотированных слайдах Аллена Вирфса-Брока
.
обзор
Класс ECMAScript.next является синтаксическим сахаром для конструктора — функции, которая вызывается через new. То есть объявления классов и выражения классов — это просто более удобный синтаксис для написания функций. Ниже приведен пример класса Person с подклассом Employee:
// Supertype class Person { constructor(name) { this.name = name; } describe() { return "Person called "+this.name; } } // Subtype class Employee extends Person { constructor(name, title) { super.constructor(name); this.title = title; } describe() { return super.describe() + " (" + this.title + ")"; } }
Вот как вы используете эти классы:
> let jane = new Employee("Jane", "CTO"); > jane instanceof Person true > jane instanceof Employee true > jane.describe() Person called Jane (CTO)
Классы примерно эквивалентны следующему коду (супер не имеет простого эквивалента
[2] ):
// Supertype function Person(name) { this.name = name; } Person.prototype.describe = function () { return "Person called "+this.name; }; // Subtype function Employee(name, title) { Person.call(this, name); this.title = title; } Employee.prototype = Object.create(Person.prototype); Employee.prototype.constructor = Employee; Employee.prototype.describe = function () { return Person.prototype.describe.call(this) + " (" + this.title + ")"; };
подробности
грамматика
ClassDeclaration: "class" BindingIdentifier ClassTail ClassExpression: "class" BindingIdentifier? ClassTail ClassTail: ClassHeritage? "{" ClassBody? "}" ClassHeritage: "extends" AssignmentExpression ClassBody: ClassElement+ ClassElement: MethodDefinition ";" MethodDefinition: PropName "(" FormalParamList ")" "{" FuncBody "}" "*" PropName "(" FormalParamList ")" "{" FuncBody "}" "get" PropName "(" ")" "{" FuncBody "}" "set" PropName "(" PropSetParamList ")" "{" FuncBody "}"
Замечания:
- Подобно функциям, существуют как объявления классов, так и выражения классов. Также аналогично, идентификатор выражения класса виден только внутри выражения.
- Значение, которое будет расширено, может быть получено произвольным выражением. Это означает, что вы сможете написать такой код:
class Foo extends combine(MyMixin, MySuperClass) {}
- Тело класса может содержать только методы, без свойств данных. Прототипы, имеющие свойства данных, обычно считаются анти-паттернами, так что это просто на практике.
- Точки с запятой разрешены между методами.
- Метод является генератором [3], если перед его именем стоит звездочка (*). Такой метод переводится в свойство, значение которого является функцией генератора.
Различные проверки и особенности
- Проверка ошибок: имя класса не может быть eval или аргументом; повторяющиеся имена элементов класса не допускаются; конструктор имени может использоваться только для обычного метода, но не для метода получения, установки или генератора.
- Класс инициализации не поднимается:
new Bar(); // runtime error class Bar {}
Обычно это не проблема, потому что вы все равно можете обращаться к классу везде. Вам просто нужно подождать, пока определение класса не будет оценено, прежде чем вы сможете его использовать:
function useBar() { new Bar(); } useBar(); // error class Bar {} useBar(); // OK
- Методы экземпляра нельзя использовать в качестве конструкторов:
class C { m() {} } new C.prototype.m(); // TypeError
Это показывает, что мы движемся к специализации: если ранее конструктор ролей, метод и не-методная функция были приняты функциями, то для ECMAScript.next будет выделена синтаксическая конструкция для каждой роли:
- Конструкторы создаются классами.
- Не-методные функции создаются функциями стрелок [4] .
- Методы создаются с помощью определений методов (внутри классов и литералов объектов).
Каждая из этих синтаксических конструкций создает функции, но те, которые немного отличаются от тех, которые создаются функцией () {}: функции # 1 имеют свойства с различными атрибутами (см. Ниже, прототип заморожен и т. Д.). # 2 функции имеют это ограничение. Функции # 3 имеют дополнительные данные, позволяющие использовать super [2] .
- Если конструктор метода отсутствует, по умолчанию используется значение:
constructor(...args) { super(...args); }
простирающийся
Правила продления:
- Не расширяйте: class Foo {}
- Прототип Foo — это Function.prototype (как и для всех функций).
- Прототипом Foo.prototype является Object.prototype.
Это то же самое, что и для функций. Обратите внимание, что приведенное выше не эквивалентно классу Foo extends Object, по единственной причине, по которой вы обычно хотите избегать наследования методов Foo, таких как Object.create ().
- Расширить ноль: класс Foo расширяет нуль {}
- Прототипом Foo является Function.prototype.
- Прототип Foo.prototype является нулевым.
Запретить методы Object.prototype быть доступными для экземпляров Foo.
- Расширяем конструктор: класс Foo расширяет SomeClass
- Прототип Foo — SomeClass.
- Прототипом Foo.prototype является SomeClass.prototype.
Поэтому методы класса тоже наследуются. Например: если есть метод SomeClass.bar (), то этот метод также доступен через Foo.bar (). Именно так CoffeeScript реализует наследование [5] .
- Расширяем не-конструктор: класс Foo расширяет someObject
- Прототип Foo — это Function.prototype
- Прототипом Foo.prototype является someObject
Проверка ошибок: значение extends должно быть либо объектом, либо нулем. Если это конструктор, то прототип этого конструктора должен быть либо объектом, либо нулем.
Изменчивость и конфигурируемость
Объявления класса создают (изменяемые) привязки let. Для данного класса Foo:
- Foo.prototype не является ни записываемым, ни конфигурируемым, ни перечисляемым.
- Foo.constructor доступен для записи и настройки, но не для перечисления.
- Методы Foo.prototype. * Доступны для записи и настройки, но не для перечисления. Создание их для записи позволяет динамическое исправление.
Это точно так же, как текущие встроенные конструкторы.
Вывод
Раньше я выступал против классов и предпочитал образцы объектов
[6] . Но учитывая ограничение не нарушать унаследованный код, я теперь рад, что они были приняты для ECMAScript.next. Три основных преимущества занятий:
- Классы помогут новичкам освоиться с JavaScript.
- Классы облегчат подтипирование для опытных программистов JavaScript. Нет больше вспомогательных API, таких как [7] .
- Классы помогут сделать код более переносимым между фреймворками. В настоящее время многие фреймворки реализуют собственный API наследования, что затрудняет повторное использование кода.
В остальном ничего особенного не меняется, у нас все те же старые функции под капотом.
Рекомендации
- ECMAScript: ES.next против ES 6 против ES Harmony
- Пристальный взгляд на супер-ссылки в JavaScript и ECMAScript.next
- Асинхронное программирование и стиль прохождения продолжения в JavaScript
- ECMAScript.next: функции стрелок и определения методов
- Перевод классов CoffeeScript в JavaScript
- Прототипы как классы — введение в наследование JavaScript
- Облегченные API наследования JavaScript