классы в джава скрипт

Класс: базовый синтаксис

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

На практике нам часто надо создавать много объектов одного вида, например пользователей, товары или что-то ещё.

Но в современном JavaScript есть и более продвинутая конструкция «class», которая предоставляет новые возможности, полезные для объектно-ориентированного программирования.

Синтаксис «class»

Базовый синтаксис выглядит так:

Затем используйте вызов new MyClass() для создания нового объекта со всеми перечисленными методами.

Когда вызывается new User(«Иван») :

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

Синтаксис классов отличается от литералов объектов, не путайте их. Внутри классов запятые не требуются.

Что такое класс?

Давайте развеем всю магию и посмотрим, что такое класс на самом деле. Это поможет в понимании многих сложных аспектов.

В JavaScript класс – это разновидность функции.

Вот что на самом деле делает конструкция class User <. >:

При вызове метода объекта new User он будет взят из прототипа, как описано в главе F.prototype. Таким образом, объекты new User имеют доступ к методам класса.

На картинке показан результат объявления class User :

Можно проверить вышесказанное и при помощи кода:

Не просто синтаксический сахар

Иногда говорят, что class – это просто «синтаксический сахар» в JavaScript (синтаксис для улучшения читаемости кода, но не делающий ничего принципиально нового), потому что мы можем сделать всё то же самое без конструкции class :

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

Однако есть важные отличия:

В отличие от обычных функций, конструктор класса не может быть вызван без new :

Кроме того, строковое представление конструктора класса в большинстве движков JavaScript начинается с «class …»

И это хорошо, так как если мы проходимся циклом for..in по объекту, то обычно мы не хотим при этом получать методы класса.

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

Class Expression

Как и функции, классы можно определять внутри другого выражения, передавать, возвращать, присваивать и т.д.

Пример Class Expression (по аналогии с Function Expression):

Аналогично Named Function Expression, Class Expression может иметь имя.

Если у Class Expression есть имя, то оно видно только внутри класса:

Источник

Чистый javascript.Классы

Перевод книги Райана Макдермота clean-code-javascript

Оглавление:

классы в джава скрипт

Принцип единственной ответственности (SRP)

Как написано в clean code, «Должна быть лишь одна причина для изменения класса» (There should never be more than one reason for a class to change). Заманчиво всё засунуть в один класс, как в дорожный чемодан. Проблема в том, что ваш класс не будет концптуально связан, и вы будете часто измененять его на каждый чих. Очень важно минимизировать изменения в классе. Когда вы вносите изменения в класс с огромным функционалом, тяжело отследить последствия ваших измений.

Принцип открытости/закрытости (OCP)

Как заявил Бертран Мейер, «сущности (классы, модули, функции и т.д.) должны быть открыты для расширения, но закрыты для модификации» (software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification). Что это значит? Это значит что вы должны давать возможность расширить функциональность сущности не изменяя существующий код.

Принцип подстановки Барбары Лисков

Это страшный термин для очень простой концепции.

«Пусть q(x) является свойством верным относительно объектов x некоторого типа T. Тогда q(y) также должно быть верным для объектов y типа S, где S является подтипом типа T.» Wikipedia Определение ещё хуже, чем название.

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

Принцип разделения интерфейса (ISP)

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

ISP утверждает, что «Пользователи не должны зависеть от классов, которые они не используют» (Clients should not be forced to depend upon interfaces that they do not use). Интерфейсы это условные соглашения в JavaScript из-за неявной типизации. Хорошим примером в javascript могут быть классы с большыми конфигами. Не заставляйте пользователей вашего класса вводить кучу конфигов. Они, как правило, не будут использовать их все. У вас не будет «жирного интерфейса», если вы их сделаете опциональными.

Принцип инверсии зависимости (DIP)

Этот принцип гласит две важные вещи:

1. Модули высшего уровня не должны зависеть от модулей низшего уровня. Оба должны зависеть от абстракций.
2. В абстракциях не должно быть деталей. Детали должны быть в дочерних классах.

Сначала трудно понять этот принцип. Но если вы работали с Angular.js, вы видели реализацию этого принципа в виде Dependency Injection (DI). Несмотря на то, что они не являются идентичными понятиями, DIP даёт возможность отграничить модули высокого уровня от деталей модулей низкого уровня и установки их. Он может сделать это через DI. Этот принцип уменьшает связь между модулями. Если ваши модули тесно связаны, их тяжело рефакторить.

Абстракции и есть неявными соглашениями, которые представляют интерфейсы в JavaScript. То есть методы и свойства, что объект/класс предоставляет другому объекту/классу. В приведенном ниже примере каждый экземпляр класса InventoryTracker будет иметь метод requestItems.

Отдавайте предпочтение классам (ES2015 / ES6) над простыми функциями (ES5)

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

Используйте метод цепочки

Этот паттерн очень полезнен в JavaScript. Его используют многие библиотеки, такие как JQuery и Lodash. Это делает ваш код выразительным и не многословным. Используя этот паттерн, вы увидите насколько ваш код станет чище. Просто возвращайте this, в конце ваших методов и вы сможете вызывать их по цепочке.

Отдавайте предочтение композиции над наследованием

Как было сказано в книге Design Patterns от Банды четырех, следует отдавать предпочтение композиции над наследованием, где вы только можете. Есть много причин, чтобы использовать наследование и много причин использовать композицию. Если ваш мозг инстиктивно видит наследование, попробуйте представить решение вашей проблемы с помощью композиции.

Когда же использовать наследование? Это зависит от конкретной проблемы. Вот список случаев, когда наследование имеет больше смысла, чем композиция:

Источник

Классы

Классы в 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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Species

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

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

Mix-ins

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

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

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

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

СпецификацияСтатусКомментарий
ECMAScript 2015 (6th Edition, ECMA-262)
Определение ‘Class definitions’ в этой спецификации.
СтандартИзначальное определение.
ECMAScript (ECMA-262)
Определение ‘Class definitions’ в этой спецификации.
Живой стандарт

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

BCD tables only load in the browser

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

Источник

JavaScript: полное руководство по классам

Доброго времени суток, друзья!

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

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

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

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

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

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

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

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

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

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

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

классы в джава скрипт

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

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

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

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

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

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

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

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

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

3. Поля

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

#name — частное поле. Доступ к нему можно получить только внутри класса User. Это позволяет сделать метод getName().

Однако, при попытке получить доступ к #name за пределами класса User будет выброшена синтаксическая ошибка: SyntaxError: Private field ‘#name’ must be declared in an enclosing class.

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

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

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

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

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

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

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

Для этого следует перед названием поля поставить префикс #: static #myPrivateStaticFiled.

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

Статическое поле User.#MAX_INSTANCES определяет допустимое количество экземпляров, а User.#instances — количество созданных экземпляров.

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

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

4. Методы

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

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

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

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

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

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

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

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

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

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

#getName() — частный метод. Внутри метода nameContains(str) мы вызываем его так: this.#getName().

Будучи частным, метод #getName() не может быть вызван за пределами класса User.

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

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

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

Для установки запрета на присвоение полю name пустой строки, обернем частное поле #nameValue в геттер и сеттер:

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

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

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

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

isNameTaken() — статический метод, использующий частное статическое поле User.#takenNames для определения использованных имен.

Статические методы также могут быть частными: static #myPrivateStaticMethod(). Такие методы могут вызываться только внутри класса.

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 позволяет получить доступ к родительскому классу из дочернего.

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

В современном JavaScript классы используются повсеместно.

Надеюсь, статья была вам полезной. Благодарю за внимание.

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *