Функция конструктор js es6

Обновлено: 28.04.2024

Материал на этой странице устарел, поэтому скрыт из оглавления сайта.

В современном JavaScript появился новый, «более красивый» синтаксис для классов.

Новая конструкция class – удобный «синтаксический сахар» для задания конструктора вместе с прототипом.

Class

Синтаксис для классов выглядит так:

Функция constructor запускается при создании new User , остальные методы записываются в User.prototype .

Это объявление примерно аналогично такому:

В обоих случаях new User будет создавать объекты. Метод sayHi также в обоих случаях находится в прототипе.

Но при объявлении через class есть и ряд отличий:

  • User нельзя вызывать без new , будет ошибка.
  • Объявление класса с точки зрения области видимости ведёт себя как let . В частности, оно видно только в текущем блоке и только в коде, который находится ниже объявления (Function Declaration видно и до объявления).

Методы, объявленные внутри class , также имеют ряд особенностей:

  • Метод sayHi является именно методом, то есть имеет доступ к super .
  • Все методы класса работают в строгом режиме use strict , даже если он не указан.
  • Все методы класса не перечислимы. То есть в цикле for..in по объекту их не будет.

Class Expression

Также, как и Function Expression, классы можно задавать «инлайн», в любом выражении и внутри вызова функции.

Это называется Class Expression:

В примере выше у класса нет имени, что один-в-один соответствует синтаксису функций. Но имя можно дать. Тогда оно, как и в Named Function Expression, будет доступно только внутри класса:

В примере выше имя User будет доступно только внутри класса и может быть использовано, например, для создания новых объектов данного типа.

Наиболее очевидная область применения этой возможности – создание вспомогательного класса прямо при вызове функции.

Например, функция createModel в примере ниже создаёт объект по классу и данным, добавляет ему _id и пишет в «реестр» allModels :

Геттеры, сеттеры и вычисляемые свойства

В классах, как и в обычных объектах, можно объявлять геттеры и сеттеры через get/set , а также использовать […] для свойств с вычисляемыми именами:

При чтении fullName будет вызван метод get fullName() , при присвоении – метод set fullName с новым значением.

В синтаксисе классов, как мы видели выше, можно создавать методы. Они будут записаны в прототип, как например User.prototype.sayHi .

Однако, нет возможности задать в прототипе обычное значение (не функцию), такое как User.prototype.key = "value" .

Конечно, никто не мешает после объявления класса в прототип дописать подобные свойства, однако предполагается, что в прототипе должны быть только методы.

Если свойство-значение, всё же, необходимо, то можно создать геттер, который будет нужное значение возвращать.

Статические свойства

Класс, как и функция, является объектом. Статические свойства класса User – это свойства непосредственно User , то есть доступные из него «через точку».

Для их объявления используется ключевое слово static .

Нет, ничего не изменилось, прототипы никуда не ушли, а классы лишь приятная обёртка над прототипным наследованием. В классическом понимании классов в JavaScript никогда не существовало и не будет существовать никогда. Многие разработчики, особенно те, которые решили познать мир JavaScript после изучения другого языка программирования, не понимают (или не хотят понять) разницы между классическим и прототипным наследованием, в результате чего они буквально отказываются полностью использовать самый мощный инструмент, которым только может вооружиться JavaScript-разработчик. Поэтому данная статья, в первую очередь, призвана объяснить, почему классы, появившиеся в новом стандарте языка, не то, чем кажутся на первый взгляд.

JavaScript не такой, как все

Для начала, три концепции, которые должен включать в себя любой объектно-ориентированный язык программирования: инкапсуляция, наследование и полиморфизм. Знакомые слова, не так ли? Теперь простыми словами о каждой концепции:

Инкапсуляция

Инкапсуляция есть ни что иное, как реализация приватности. В JavaScript подобная концепция реализуется благодаря функциям и их областям видимости.

Что не так с инкапсуляцией в JavaScript? Всё просто. В примере приведённом выше отсутствует возможность явно обозначить приватность, как в других языках программирования с помощью ключевых слов private , protected и public (пример с PHP). Естественно, подобная проблема достаточно просто решается с помощью тех же самых функций — создаётся ещё одна обёртка над кодом в виде самовызывающейся анонимной функции, например, при использовании паттерна “модуль”:

С релизом стандарта ES2015 подобная проблема приватности была частично решена добавлением блочных областей видимости для переменных, в результате чего чистые функции, работающие только для данного конкретного конструктора можно вынести в отдельный модуль и запрашивать по необходимости, не опасаясь того, что кто-то их перезапишет. Но чистые функции лишь вершина айсберга и про некоторые способы организации приватности при работе с классами в JavaScript можно прочитать в этой статье.

Наследование

С помощью наследования вы, буквально, говорите: “У меня есть один конструктор/класс и другой конструктор/класс, который точно такой же, как и первый, кроме вот этого и вот этого”. Чаще всего наследование в JavaScript реализуется с помощью функции Object.create() , позволяющий создать новый объект с заданным прототипом.

И здесь с JavaScript “всё не так”. Тот же самый пример, написанный на PHP:

Да в чём вообще кроется разница? Мы же выполнили одни и те же действия, просто с разным синтаксисом! Чтобы понять, почему JavaScript другой представьте себе семейство птичек: дедушка попугай, отец попугай и сын попугай — все попугаи! Совершенно очевидно, что если у деда попугая вырастет ещё одна лапка, то это ни коем образом не повлияет на отца и сына. То есть попугай, родившийся с двумя лапами, так и останется до конца своей жизни с двумя лапами, в независимости от того, что случилось с любым из его предков. Подобным образом можно представить себе классическое наследование.

С прототипным наследование ситуация абсолютно противоположная. Дед-попугай отрастил себе третью лапку, и она автоматически появилась у отца и сына (прототипное наследование против природы). Сложившееся положение вещей крайне не понравилось отцу попугаю, и он решил, что и попугаем-то больше быть не хочет и стал орлом (с тремя лапами). Как вы уже, наверное, догадались сын попугай уже больше не попугай, а настоящий орёл (но опять же с тремя лапами). Три лапы слишком много для сына и он решает отказаться от одной (теперь у нас есть обычный орёл с двумя лапами).

Но и это ещё не всё! Единственный оставшийся попугай (дедушка) решил, что трёх лап мало и приобрёл себе ещё одну, а также решил стать ласточкой. В результаты из обычного семейства попугаев мы получили: деда ласточку с четырьмя лапами, отца орла с тремя лапами, сына орла с двумя лапами. Вот она вся суть прототипного наследования. Похоже на безумие? Да? Тогда перейдём к коду:

Вывод из всего выше перечисленного: в JavaScript прототипное наследование “динамическое”, можно изменять всё налету, классическое же наследование подобным похвастаться не может: всё, что вы объявили в одном классе останется там навсегда. Грубо говоря, классическое представление наследования предполагает наличие определённой статической схемы, по которой будет строиться каждый объект данного класса. В прототипном наследовании мы имеем дело не со схемой, а с живым, постоянно развивающимся организмом, который со временем изменяется и принимает ту форму, которая нам нужна (и это прекрасно).

Полиморфизм

Полиморфизм проще всего постичь на примере встроенных конструкторов ( String , Array , Object …). Вот если вас спросят: “Чем число 42 отличается от массива [4, 2] и что у них общего?”, чтобы вы ответили? Наверняка, вы были начали рассказывать про примитивы и объекты, чем они отличаются, что можно делать с теми и другими, на вопрос про отличия. Но чем они похожи друг на друга? Абсолютно разные же типы данных! Но, очевидно, что они разделяют определённую часть методов, например, метод toString , унаследованный от Object . Это уже полиморфизм? Ещё нет, но мы уже близко. Метод toString можно весьма успешно переназначить, во-первых, в прототипе функции конструктора, и, во-вторых, сразу же для данного конкретного объекта.

Что происходит? При использовании метода toString на каждом из объектов происходит проверка того, какой метод нужно выбрать. Проверка ведётся следующим образом: изначально проверяется существует ли нужное свойство на самом объекте, если существует, то используется именно оно, если же нет, то проверка продолжается — на наличие свойства проверяется прототип, потом прототип прототипа, потом прототип прототипа прототипа… и так, пока не дойдём до самого конца — null (null является прототипом для Object ). Таким образом, полиморфизм отвечает за то, чей метод вызвать. Подробнее о том, как это всё работает в других языках программирования можно узнать в этом вопросе на Тостере, а более подробный пример с JavaScript можно посмотреть в статье про прототипы.

Большой вывод, который я хочу, чтобы вы сделали из всего вышеперечисленного: “Javascript не такой, как остальные языки программирования с классическим пониманием ООП”. Не стоит жить в стране предрассудков и пытаться перенести своё понимание ООП из другого языка — вы только потеряете время.

Теперь, когда мы во всём разобрались и сделали нужный вывод (“JavaScript другой”) можно перейти к рассмотрению “классов” в JavaScript.

Классы

Итак, выше вы уже достаточно насмотрелись примеров конструкторов, свойств и методов, которыми мы пользовались до выхода в свет нового стандарта. Не буду вас больше томить ожиданием и сразу перейдём к коду:

Пример выше можно записать в стиле ES5 следующим образом:

Что нужно знать про классы:

  • Создавая класс, вы пользуетесь блоком кода (всё, что находится между < и >), внутри которого объявляете, всё, что хотите видеть в прототипе.
  • Запись class Person означает, что будет создана функция конструктор Person (всё точно так же, как и в ES5)
  • Свойство constructor используется для обозначение того, что будет происходить непосредственно в самом конструкторе.
  • Все методы для класса используют краткую запись, которую мы обсуждали ранее в статье про расширение литерала объектов.
  • При перечислении методов не надо использовать запятые (на самом деле, они запрещены)

Важно понимать, что мы до сих пор работаем с обычными функциями. То есть если вы захотите проверить тип класса, то (с удивлением?) обнаружите function :

Как и при работе с функциями конструкторами мы можем записать “класс” (на самом деле, только конструктор) в переменную. Иногда подобная запись может быть чрезвычайно полезной, например, когда нужно записать конструктор, как свойство объекта.

Но, как и во всех остальных вопросах в JavaScript, здесь есть свои подводные камни. Во-первых функция Person обязана быть вызвана с оператором new и, во-вторых, в то время, как функция function Person(name) < this.name = name; >поднималась наверх (стандартный hoisting) и её можно было использовать в любой части кода, конструктор, созданный с помощью class не испытывает на себе поднятия.

extends

ES6 классы также обладают синтаксическим сахаром для реализации прототипного наследования. Для подобных целей используется extends :

super

В примере выше мы использовали super для вызова конструктора-родителя. С помощью подобного вызова мы записали свойство name для текущего объекта. Другуми словами, всё, что делает super при вызове внутри конструктора (свойства constructor ) — вызывает конструктор родителя и записывает в текущий объект (то есть в this ) всё, что от него требуется. В ES5 для подобных действий приходилось напрямую обращаться к конструктору:

Но это ещё не всё, чем может порадовать super ! Если вы захотите обратиться к любому методу, записанному в прототип родителя внутри метода потомка, то super и здесь вас сможет выручить.

Без super нам бы пришлось напрямую обращаться к прототипу конструктора родителя, чтобы получить перезаписанный нами метод:

Таким образом, super “оценивает ситуацию” и в зависимости от того, где вы его решили использовать будет работать по-разному, но при любом использовании он готов достаточно сильно сократить ваш код и избавить от не самого понятного способа вызова конструктора родителя.

Подводный камень super : при реализации наследования с помощью extends и работе с дочерним конструктором необходимо вызвать super() перед добавлением любого нового свойства.

Классы без конструкторов

Как вы, наверное, заметили в примере выше не было использовано свойство constructor для класса Speaker , поскольку в нём просто нет необходимости в данном случае. Но свойство name всё равно было записано. Магия? Магия! Когда вы не указываете явно, что нужно сделать в конструкторе, то всё решается без вашего участия. Можно представить себе данный процесс следующим образом:

static

При работе с конструкторами в ES5 многие привыкли использовать функции, как объекты (они же и есть объекты) и вешать на них служебные функции:

Если вы с подобной реализацией никогда не сталкивались, то обратите внимание на то, что подобные значения не имеют ничего общего с прототипами и доступны только при запросе непосредственно с функции (объекта):

При работе с классами запись подобных свойств доступна сразу внутри класса с помощью оператора static . Причем все записанные подобным образом свойства при наследовании благополучно переносятся на конструктор потомка:

Классы в JavaScript были введены в ECMAScript 2015 и представляют собой синтаксический сахар над существующим в JavaScript механизмом прототипного наследования. Синтаксис классов не вводит новую объектно-ориентированную модель, а предоставляет более простой и понятный способ создания объектов и организации наследования.

Определение классов

На самом деле классы — это "специальные функции", поэтому точно также, как вы определяете функции (function expressions и function declarations), вы можете определять и классы с помощью: class declarations и class expressions.

Объявление класса

Первый способ определения класса — class declaration (объявление класса). Для этого необходимо воспользоваться ключевым словом class и указать имя класса (в примере — «Rectangle»).

Подъём (hoisting)

Разница между объявлением функции (function declaration) и объявлением класса (class declaration) в том, что объявление функции совершает подъём (hoisting), в то время как объявление класса — нет. Поэтому вначале необходимо объявить ваш класс и только затем работать с ним, а код же вроде следующего сгенерирует исключение типа ReferenceError :

Выражение класса

Второй способ определения класса — class expression (выражение класса). Можно создавать именованные и безымянные выражения. В первом случае имя выражения класса находится в локальной области видимости класса и может быть получено через свойства самого класса, а не его экземпляра.

Обратите внимание: выражения класса подвержены тем же проблемам с подъёмом (hoisting), что и объявления класса.

Тело класса и задание методов

Тело класса — это часть кода, заключённая в фигурные скобки <> . Здесь вы можете объявлять члены класса, такие как методы и конструктор.

Строгий режим

Тела объявлений классов и выражений классов выполняются в строгом режиме (strict mode).

Constructor

Метод constructor — специальный метод, необходимый для создания и инициализации объектов, созданных, с помощью класса. В классе может быть только один метод с именем constructor . Исключение типа SyntaxError будет выброшено, если класс содержит более одного вхождения метода constructor .

Ключевое слово super можно использовать в методе constructor для вызова конструктора родительского класса.

Методы прототипа

Статические методы и свойства

Ключевое слово static , определяет статический метод или свойства для класса. Статические методы и свойства вызываются без инстанцирования (en-US) их класса, и не могут быть вызваны у экземпляров (instance) класса. Статические методы, часто используются для создания служебных функций для приложения, в то время как статические свойства полезны для кеширования в рамках класса, фиксированной конфигурации или любых других целей, не связанных с реплецированием данных между экземплярами.

Привязка this в прототипных и статических методах

Когда статический или прототипный метод вызывается без привязки к this объекта (или когда this является типом boolean, string, number, undefined, null), тогда this будет иметь значение undefined внутри вызываемой функции. Автоупаковка не будет произведена. Поведение будет таким же как если бы мы писали код в нестрогом режиме.

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

Свойства экземпляра

Свойства экземпляра должны быть определены в методе класса:

Статические (class-side) свойства и свойства прототипа должны быть определены за рамками тела класса:

Определение полей

Публичные и приватные поля - это экспериментальная особенность (stage 3), предложенная комитетом TC39 по стандартам языка Javascript. Поддержка браузерами ограничена, но это нововведение может быть использовано на моменте сборки, используя к примеру Babel.

Публичные поля

Используя Javascript синтаксис определения полей, приведённый выше пример может быть изменён следующим образом:

Как видно из примера, поля могут быть объявлены как со начальным значением, так и без него.

Более подробно об этом написано в публичные поля класса.

Приватные поля

Предыдущий пример может быть изменён следующим образом, используя приватные поля:

Приватные поля могут быть изменены или прочитаны только в рамках класса и не могут быть вызваны извне. Определяя вещи, которые не видны за пределами класса, вы гарантируете, что пользователи ваших классов не могут зависеть от внутренних компонентов, которые могут изменить версию на версию.

Приватные поля могут быть объявлены только заранее в объявлении поля.

Приватные поля не могут быть созданы позже путём присваивания им значения, в отличии от обычных свойств.

Более подробно об этом написано в Приватные поля класса.

Наследование классов с помощью extends

Ключевое слово extends используется в объявлениях классов и выражениях классов для создания класса, дочернего относительно другого класса.

Если в подклассе присутствует конструктор, он должен сначала вызвать super , прежде чем использовать this .

Аналогичным образом можно расширять традиционные, основанные на функциях "классы":

Обратите внимание, что классы не могут расширять обычные (non-constructible) объекты. Если вам необходимо создать наследование от обычного объекта, в качестве замены можно использовать Object.setPrototypeOf() :

Species

Допустим, вам хотелось бы возвращать объекты типа Array в вашем производном от массива классе MyArray . Паттерн species позволяет вам переопределять конструкторы по умолчанию.

Например, при использовании таких методов, как map() , который возвращает конструктор по умолчанию, вам хотелось бы, чтобы они возвращали родительский объект Array вместо объекта MyArray . Символ Symbol.species позволяет это реализовать:

Обращение к родительскому классу с помощью super

Ключевое слово super используется для вызова функций на родителе объекта.

Mix-ins

Абстрактные подклассы, или mix-ins, — это шаблоны для классов. У класса в ECMAScript может быть только один родительский класс, поэтому множественное наследование (к примеру, от tooling classes) невозможно. Функциональность должен предоставлять родительский класс.

Для реализации mix-ins в ECMAScript можно использовать функцию, которая в качестве аргумента принимает родительский класс, а возвращает подкласс, его расширяющий:

Класс, использующий такие mix-ins, можно описать следующим образом:

Спецификации

Совместимость с браузерами

BCD tables only load in the browser

Повторное определение класа

Класс не может быть переопределён. Попытка этого приведёт к SyntaxError .

В JavaScript используется модель прототипного наследования: каждый объект наследует поля (свойства) и методы объекта-прототипа.

Классов, используемых в Java или Swift в качестве шаблонов или схем для создания объектов, в JavaScript не существует. В прототипном наследовании есть только объекты.

Прототипное наследование может имитировать классическую модель наследования от классов. Для этого в ES6 было представлено ключевое слово class: синтаксический сахар для прототипного наследования.

В данной статье мы научимся работать с классами: определять классы, их частные (приватные) и открытые (публичные) поля и методы, а также создавать экземпляры.

1. Определение: ключевое слово class

Для определения класса используется ключевое слово class:


Такой синтаксис называется объявлением класса.

Класс может не иметь названия. С помощью выражения класса можно присвоить класс переменной:


Классы можно экспортировать в виде модулей. Вот пример экспорта по умолчанию:


А вот пример именованного экспорта:


Классы используются для создания экземпляров. Экземпляр — это объект, содержащий данные и логику класса.


Экземпляры создаются с помощью оператора new: instance = new Class().

Вот как создать экземпляр класса User:

2. Инициализация: constructor()

constructor(param1, param2, . ) — это специальный метод внутри класса, служащий для инициализации экземпляра. Это то место, где устанавливаются начальные значения полей экземпляра и осуществляется его настройка.

В следующем примере конструктор устанавливает начальное значение поля name:


Конструктор принимает один параметр — name, который используется для установки начального значения поля this.name.

this в конструкторе указывает на создаваемый экземпляр.

Аргумент, используемый для создания экземпляра класса, становится параметром его конструктора:


Параметр name внутри конструктора имеет значение 'Печорин'.

Если не определить собственный конструктор, будет создан стандартный конструктор, представляющий собой пустую функцию, не влияющую на экземпляр.

3. Поля

Поля класса — это переменные, содержащие определенную информацию. Поля могут быть разделены на две группы:

  1. Поля экземпляров класса
  2. Поля самого класса (статические)
  1. Открытые (публичные): поля доступны как внутри класса, так и в экзмеплярах
  2. Частные (приватные): поля доступны только внутри класса

3.1. Открытые поля экземпляров класса

Выражение this.name = name создает поле экземпляра name и присваивает ему начальное значение.

Доступ к этому полю можно получить с помощью аксессора свойства:


В данном случае name — открытое поле, поскольку оно доступно за пределами класса User.

При неявном создании полей внутри конструктора, сложно получить список всех полей. Для этого поля нужно извлекать из конструктора.

Лучшим способом является явное определение полей класса. Неважно, что делает конструктор, экземпляр всегда имеет одинаковый набор полей.

Предложение по созданию полей класса позволяет определять поля внутри класса. Кроме того, здесь же можно присваивать полям начальные значения:


Изменим код класса User, определив в нем открытое поле name:


Такие открытые поля являются очень наглядными, быстрый взгляд на класс позволяет понять структуру его данных.

Более того, поле класса может быть инициализировано в момент определения:


На доступ к открытым полям и их изменение нет ограничений. Читать и присваивать значения таким полям можно в конструкторе, методах и за пределами класса.

3.2. Частные поля экземпляров класса

Инкапсуляция позволяет скрывать внутренние детали реализации класса. Тот, кто использует инкапсулированный класс, опирается на публичный интерфейс, не вдаваясь в подробности реализации класса.

Такие классы проще обновлять при изменении деталей реализации.

Хорошим способом скрыть детали является использование частных полей. Такие поля могут быть прочитаны и изменены только внутри класса, которому они принадлежат. За пределами класса частные поля недоступны.

Сделаем поле name частным:

3.3. Открытые статические поля

В классе можно определить поля, принадлежащие самому классу: статические поля. Такие поля используются для создания констант, хранящих нужную классу информацию.

Для создания статических полей используется ключевое слово static перед названием поля: static myStaticField.

Добавим новое поле type для определения типа пользователя: администратора или обычного. Статические поля TYPE_ADMIN и TYPE_REGULAR — константы для каждого типа пользователей:


Для доступа к статическим полям следует использовать название класса и название свойства: User.TYPE_ADMIN и User.TYPE_REGULAR.

3.4. Частные статические поля

Иногда статические поля также являются частью внутренней реализации класса. Для инкапсуляции таких полей можно сделать их частными.

Предположим, что мы хотим ограничить количество экземпляров класса User. Для сокрытия информации о количестве экземпляров можно создать частные статические поля:

Эти частные статические поля доступны только внутри класса User. Ничто из внешнего мира не может повлиять на ограничения: в этом заключается одно из преимуществ инкапсуляции.

Прим. пер.: если ограничить количество экземпляров одним, получится интересная реализация шаблона проектирования «Одиночка» (Singleton).

4. Методы

Поля содержат данные. Возможность изменять данные обеспечивается специальными функциями, являющимися частью класса: методами.

JavaScript поддерживает как методы экземпляров класса, так и статические методы.

4.1. Методы экземпляров класса

Методы экземпляра класса могут изменять его данные. Методы экземпляра могут вызывать другие методы экземпляра, а также статические методы.

Например, определим метод getName(), возвращающий имя пользователя:


В методе класса, также как и в конструкторе, this указывает на создаваемый экземпляр. Используйте this для получения данных экземпляра: this.field, или для вызова методов: this.method().

Добавим новый метод nameContains(str), принимающий один аргумент и вызывающий другой метод:


nameContains(str) — метод класса User, принимающий один аргумент. Он вызывает другой метод экземпляра getName() для получения имени пользователя.

Сделаем метод getName() частным:

4.2. Геттеры и сеттеры

Геттеры и сеттеры — это аксессоры или вычисляемые свойства. Это методы, имитирующие поля, но позволяющие читать и записывать данные.

Геттеры используются для получения данных, сеттеры — для их изменения.

4.3. Статические методы

Статические методы — это функции, принадлежащие самому классу. Они определяют логику класса, а не его экземпляров.

Для создания статического метода используется ключевое слово static перед названием метода: static myStaticMethod().

При работе со статическими методами, следует помнить о двух простых правилах:

  1. Статический метод имеет доступ к статическим полям
  2. Он не имеет доступа к полям экземпляров

5. Наследование: extends

Классы в JavaScript поддерживают наследование с помощью ключевого слова extends.

В выражении class Child extends Parent < >класс Child наследует от класса Parent конструктор, поля и методы.

Создадим дочерний класс ContentWriter, расширяющий родительский класс User:


ContentWriter наследует от User конструктор, метод getName() и поле name. В самом ContentWriter определяется новое поле posts.

Обратите внимание, что частные поля и методы родительского класса не наследуются дочерними классами.

5.1. Родительский конструктор: super() в constructor()

Для того, чтобы вызвать конструктор родительского класса в дочернем классе, следует использовать специальную функцию super(), доступную в конструкторе дочернего класса.

Пусть конструктор ContentWriter вызывает родительский конструктор и инициализирует поле posts:


super(name) в дочернем классе ContentWriter вызывает конструктор родительского класса User.

Обратите внимание, что в дочернем конструкторе перед использованием ключевого слова this вызывается super(). Вызов super() «привязывает» родительский конструктор к экземпляру.

5.2. Родительский экземпляр: super в методах

Для того, чтобы получить доступ к родительскому методу внутри дочернего класса, следует использовать специальное сокращение super:


getName() дочернего класса ContentWriter вызывает метод getName() родительского класса User.

Это называется переопределением метода.

Обратите внимание, что super можно использовать и для статических методов родительского класса.

6. Проверка типа объекта: instanceof

Выражение object instanceof Class определяет, является ли объект экземпляром указанного класса.


Оператор instanceof полиморфичен: он исследует всю цепочку классов.


Что если нам нужно определить конкретный класс экземпляра? Для этого можно использовать свойство constructor:

7. Классы и прототипы

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

Однако, классы являются лишь надстройкой над прототипным наследованием. Любой класс — это функция, создающая экземпляр при вызове конструктора.

Следущие два примера идентичны.


Поэтому для понимания классов требуется хорошее знание прототипного наследования.

8. Доступность возможностей классов

Возможности классов, представленные в данной статье, распределены между спецификацией ES6 и предложениями, находящимися на третьей стадии рассмотрения:

Прим. пер.: по данным Can I use поддержка частных полей классов на сегодняшний день составляет 68%.

9. Заключение

Классы в JavaScript используются для инициализации экземпляров с помощью конструктора, определения их полей и методов. С помощью ключевого слова static можно определять поля и методы самого класса.

Наследование реализуется с помощью ключевого слова extends. Ключевое слово super позволяет получить доступ к родительскому классу из дочернего.

От переводчика:
Предлагаю вашему вниманию перевод краткого (действительно краткого) руководства по ES6. В нём можно ознакомиться с основными понятиями стандарта.
Оригинальный текст в некоторых случаях был дополнен или заменён на более подходящий источник. Например, часть определения ключевого слова const является переводом документации с MDN.
Чтобы лучше разобраться в некоторых концепциях (для выполнения качественного перевода) использовалось описание стандарта на сайте MDN, руководство "You Don't Know JS: ES6 & Beyond" и учебник Ильи Кантора.

Содержание

1. let, const и блочная область видимости

Ключевое слово let позволяет объявлять переменные с ограниченной областью видимости — только для блока <. >, в котором происходит объявление. Это называется блочной областью видимости. Вместо ключевого слова var , которое обеспечивает область видимости внутри функции, стандарт ES6 рекомендует использовать let .

Другой формой объявления переменной с блочной областью видимости является ключевое слово const . Оно предназначено для объявления переменных (констант), значения которых доступны только для чтения. Это означает не то, что значение константы неизменно, а то, что идентификатор переменной не может быть переприсвоен.
Вот простой пример:

О чём стоит помнить:

  • Когда дело касается поднятия переменных (hoisting) let и const , их поведение отличается от традиционного поведения var и function . И let и const не существуют до своего объявления (от переводчика: для подробностей автор оригинального руководства отсылает к статье Temporal Dead Zone)
  • Областью видимости let и const является ближайший блок.
  • При использовании const рекомендуется использовать ПРОПИСНЫЕ_БУКВЫ.
  • В const одновременно с объявлением переменной должно быть присвоено значение.

2. Стрелочные функции

Стрелочные функции представляют собой сокращённую запись функций в ES6. Стрелочная функция состоит из списка параметров ( . ) , за которым следует знак => и тело функции.

Заметим, что в примере выше, тело функции представляет собой краткую запись, в которой не требуется явного указания на то, что мы хотим вернуть результат.

А вот пример с использованием блока из фигурных скобок:

Это ещё не всё.

Стрелочные функции не просто делают код короче. Они тесно связаны с ключевым словом this и привязкой контекста.

Поведение стрелочных функций с ключевым словом this отличается от поведения обычных функций с this . Каждая функция в JavaScript определяет свой собственный контекст this , но внутри стрелочных функций значение this то же самое, что и снаружи (стрелочные функции не имеют своего this ). Посмотрим на следующий код:

В ECMAScript 3/5 это поведение стало возможным изменить, присвоив значение this другой переменной.

Как сказано выше, внутри стрелочных функций значение this то же самое, что и снаружи, поэтому следующий код работает так, как от него и ожидается:

3. Параметры по умолчанию

ES6 позволяет установить параметры по умолчанию при объявлении функции. Вот простой пример:

4. Spread / Rest оператор

. оператор называют как spread или rest, в зависимости от того, как и где он используется.

При использовании в любом итерируемом объекте (iterable), данный оператор "разбивает" ("spread") его на индивидуальные элементы:

Другим распространённым использованием оператора . является объединение набора значений в один массив. В данном случае оператор работает как "rest" (от переводчика: не нашёл подходящего перевода на русский язык, из примера ниже всё станет ясно)

5. Расширение возможностей литералов объекта

ES6 позволяет объявить литералы объекта с помощью короткого синтаксиса для инициализации свойств из переменных и определения функциональных методов. Также, стандарт обеспечивает возможность вычисления свойств непосредственно в литерале объекта.

6. Восьмеричный и двоичный литералы

В ES6 появилась новая поддержка для восьмеричных и двоичных литералов.
Добавление к началу числа 0o или 0O преобразует его в восьмеричную систему счисления (аналогично, 0b или 0B преобразует в двоичную систему счисления). Посмотрим на следующий код:

7. Деструктуризация массивов и объектов

Деструктуризация помогает избежать использования вспомогательных переменных при взаимодействии с объектами и массивами.

8. Ключевое слово super для объектов

ES6 позволяет использовать метод super в (безклассовых) объектах с прототипами. Вот простой пример:

9. Строковые шаблоны и разделители

ES6 предоставяляет более простой способ вставки значения переменной или результата выражения (т.н. "интерполяцию"), которые рассчитываются автоматически.

  • $ < . >используется для вычисления значения переменной/выражения.
  • `` Обратные кавычки используются как разделитель для таких случаев.

10. for. of против for. in

  • for. of используется для перебора в цикле итерируемых объектов, например, массивов.
  • for. in используется для перебора в цикле всех доступных для перебора (enumerable) свойств объекта.

11. Map и WeakMap

ES6 представляет новые структуры данных — Map и WeakMap . На самом деле, мы используем "Map" в JavaScript всё время. Каждый объект можно представить как частный случай Map .

Классический объект состоит из ключей (всегда в строковом виде) и значений, тогда как в Map для ключа и значения можно использовать любое значение (и объекты, и примитивы). Посмотрим на этот код:

WeakMap

WeakMap это Map , в котором ключи обладают неустойчивыми связями, что позволяет не мешать сборщику мусора удалять элементы WeakMap . Это означает, что можно не беспокоиться об утечках памяти.

Стоить отметить, что в WeakMap , в отличие от Map , каждый ключ должен быть объектом.

Для WeakMap есть только четыре метода: delete(ключ) , has(ключ) , get(ключ) и set(ключ, значение) .

12. Set и WeakSet

Объекты Set это коллекции уникальных значений. Дублированные значения игнорируются, т.к. коллекция должна содержать только уникальные значения. Значения могут быть примитивами или ссылками на объекты.

Вы можете перебирать Set в цикле с помощью forEach или for. of . Перебор происходит в том же порядке, что и вставка.

У Set также есть методы delete() и clear() .

WeakSet

Аналогично WeakMap , объект WeakSet позволяет хранить объекты с неустойчивыми связями в коллекции. Объект в WeakSet уникален.

13. Классы в ES6

В ES6 представили новый синтаксис для классов. Здесь стоит отметить, что класс ES6 не представляет собой новую объектно-ориентированную модель наследования. Это просто синтаксический сахар для существующего в JavaScript прототипного наследования.

Класс в ES6 представляет собой просто новый синтаксис для работы с прототипами и функциями-конструкторами, которые мы привыкли использовать в ES5.

Функции, записанные с помощью ключевого слова static , используются для объявления статических свойств класса.

extends и super в классах

Посмотрим на следующий код:

В ES6 ключевое слово extends позволяет классу-потомку наследовать от родительского класса. Важно отметить, что конструктор класса-потомка должен вызывать super().

Также, в классе-потомке можно вызвать метод родительского класса с помощью super.имяМетодаРодителя() .

О чём стоит помнить:

  • Объявления классов не поднимаются наверх (not hoisted). Сначала нужно объявить класс и только после этого использовать его, иначе будет ошибка ReferenceError.
  • Нет необходимости использовать ключевое слово function во время задания функций внутри определения класса.

14. Тип данных Symbol

Symbol это уникальный и неизменяемый тип данных, представленный в ES6. Целью Symbol является создание уникального идентификатора, к которому нельзя получить доступ.

Вот как можно создать Symbol :

Заметим, что использовать new вместе с Symbol(…) нельзя.

Если Symbol используется как свойство/ключ объекта, он сохраняется таким специальным образом, что свойство не будет показано при нормальном перечислении свойств объекта.

Чтобы извлечь символьные свойства объекта, нужно использовать Object.getOwnPropertySymbols(o)

15. Итераторы

Итератор обращается к элементам коллекции по одному, в то же время сохраняя память о своей текущей позиции в этой коллекции. У итератора есть метод next() , который возвращает следующий элемент в последовательности. Этот метод возвращает объект с двумя свойствами: done (окончен ли перебор) и value (значение).

В ES6 есть метод Symbol.iterator , который определяет итератор для объекта по-умолчанию. При каждой необходимости перебора в цикле для объекта (например, в начале цикла for..of), его метод итератора вызывается без аргументов, и возвращённый итератор используется для того, чтобы получить значения для перебора.

Посмотрим на массив, который является перебираемым (iterable), и на итератор, который есть у массива для обработки его значений:

Заметим, что можно написать собственный итератор через определение obj[Symbol.iterator]() с описанием объекта.

Подробнее про итераторы:
На сайте MDN

16. Генераторы

Функции-генераторы представляют собой новую особенность ES6, которая позволяет функции создавать много значений в течение некоторого периода времени, возвращая объект (называемый генератором), который может быть итерирован для выброса значений из функции по одному за раз.

Функция-генератор возвращает итерируемый объект при своём вызове.
Функция-генератор записывается с помощью знака * после ключевого слова function , а в теле функции должно присутствовать ключевое слово yield .

Каждый раз при вызове yield возвращённое значение становится следующим значением в последовательности.

Также заметим, что генераторы вычисляют свои возвращённые значения по запросу, что позволяет им эффективно представлять последовательности, затратные с точки зрения вычислений, или даже бесконечные последовательности.

17. Промисы

В ES6 появилась встроенная поддержка промисов. Промис это объект, который ждёт выполнения асинхронной операции, после которого (т.е. после выполнения) промис принимает одно из двух состояний: fulfilled (resolved, успешное выполнение) или rejected (выполнено с ошибкой).

Стандартным способом создания промиса является конструктор new Promise() , который принимает обработчик с двумя функциями как параметрами. Первый обработчик (обычно именуемый resolve ) представляет собой функцию для вызова вместе с будущим значением, когда оно будет готово; второй обработчик (обычно именуемый reject ) является функцией, которая вызывается для отказа от выполнения промиса, если он не может определить будущее значение.

Каждый промис обладает методом then , в котором есть два коллбэка. Первый коллбэк вызывается, если промис успешно выполнен (resolved), тогда как второй коллбэк вызывается, если промис выполнен с ошибкой (rejected).

При возвращении значения от then коллбэки передадут значение следующему коллбэку then .

При возвращении промиса, успешно обработанное значение промиса пройдёт к следующему коллбэку, для того, чтобы эффективно соединить их вместе.
Эта простая техника помогает избежать ада с коллбэками ("callback hell").

Читайте также: