Уроки JavaScript – ознакомление с классами (часть 1)
Классы в JavaScript являются одной из самых популярных функций, и сегодня мы сделаем их краткий обзор. Мы рассмотрим такие части и концепции, как конструктор, свойства и методы класса, наследование, суперклассы и подклассы, а также способы переопределения свойств и методов конструктора и класса.
Создание объектов по-старому (с помощью конструкторов функций)
Как разработчики создавали объекты до введения классов в JavaScript? Они использовали конструкторы функций. Затем, когда нужно было добавить некоторые свойства или методы к объекту, они использовали один из двух способов.
Во-первых, с помощью ключевого слова this
прямо в конструкторе.
А во-вторых? Можно добавлять свойства или методы к объекту вне конструктора. В этом случае нужно использовать прототип объекта. Когда вам нужно создать новый экземпляр объекта, вы определяете его как новую переменную и используете ключевое слово new
, за которым следует имя объекта и круглые скобки. Например, let myInstance = new MyObj()
.
Теперь давайте перейдем к практике.
Для начала давайте создадим новый объект Person
с четырьмя свойствами: именем, возрастом, ростом, весом. В объекте будем использовать один метод (шаг 1). Далее, создадим два экземпляра этого объекта с некоторыми свойствами (шаг 2).
Если нам позднее нужно добавить свойство ко всем экземплярам Person, мы можем сделать это либо внутри конструктора, или же вне его, используя объект-прототип.
Чтобы добавить дополнительный метод к объекту Person
, используйте прототип объекта. Как и в случае с новым свойством, все экземпляры также автоматически наследуют этот метод (шаг 5).
<script> // Шаг 1: Используем конструктор функции для создания объекта Person function Person(name, age, height, weight) { // Добавляем свойства объекта по умолчанию, используя this // this относится к объекту Person this.name = name; this.age = age; this.height = height; this.weight = weight; // Добавляем метод для получения свойства name this.getName = function() { // Используем ключевое слово this для ссылки на свойство name объекта Person return this.name; } } // Шаг 2: Создаем два экземпляра объекта Person, ivanov и petrov let ivanov = new Person('Иванов', 25, 195, 105); let petrov = new Person('Петров', 30, 180, 72); console.log(ivanov.getName()) // Вывод: Иванов console.log(petrov.getName()) // Вывод: Петров // Шаг 3: Добавляем свойство пола только экземпляру ivanov и выводим его ivanov.gender = 'мужской'; console.log(ivanov.gender); // Вывод: мужской // при попытке записать пол для экземпляра petrov, вывод будет undefined, поскольку свойство «gender» есть только в экземпляре ivanov // Шаг 4: Использование прототипа объекта для добавления свойства пола в объект Person Person.prototype.gender = 'some gender'; // попробуем снова записать пол для экземпляра petrov console.log(petrov.gender); // Вывод: 'some gender' // Причина: добавление «gender» к прототипу Person автоматически добавило его во все экземпляры // Шаг 5: Использование прототипа объекта для добавления метода, чтобы получить свойство age для прототипа объекта Person Person.prototype.getAge = function () { // this относится к объекту Person return this.age; } // получаем возраст ivanov и petrov console.log(ivanov.getAge()) // 25 console.log(petrov.getAge()) // 30 </script>
Создание объектов по-новому (с классами JavaScript)
Хотя классы в JavaScript могут выглядеть как что-то совершенно новое, под капотом все еще сохраняются конструкторы функций. Есть только немного новых отличий.
Давайте теперь перепишем предыдущий пример с помощью класса JavaScript. Как видите, единственное отличие заключается на первом этапе. Здесь мы определили Person
как класс. Свойства, которые мы хотим передать в качестве аргументов при создании нового экземпляра, теперь определяются с помощью конструктора классов. Также обратите внимание на отсутствие this
, когда мы определяем метод getName()
.
Все остальное в основном то же самое и работает так же, как и раньше. Это относится и к тому, как вы создаете новые экземпляры. Вы по-прежнему используете переменные вместе с ключевым словом new
и именем объекта (теперь класса).
<script> // Использование класса JavaScript для создания объекта Person class Person { constructor(name, age, height, weight) { // Добавляем свойства объекта по умолчанию this.name = name; this.age = age; this.height = height; this.weight = weight; } // Добавляем метод для получения свойства name getName() { return this.name; } } </script>
Конструктор в JavaScript
Одна из общих черт классов JavaScript – это метод конструктора. Это особый метод в классе. Это метод, который создает и инициализирует объект, созданный с помощью класса. Это означает, что каждый раз, когда вы создаете новый экземпляр класса, JavaScript автоматически вызывает метод конструктора.
У каждого класса может быть только один конструктор. Типичное использование конструктора – создание свойств класса по умолчанию. Затем вы можете передать эти свойства при создании новых экземпляров класса. Или вы можете объявить их с некоторыми значениями по умолчанию.
Метод конструктора не является обязательным. Вы можете определить классы с помощью конструктора (Пример 1) или без него (Пример 2).
Если вы включаете конструктор в класс, вы должны определить его в самом начале класса, вверху кода класса. В противном случае JavaScript выдаст ошибку.
Пример 1: Класс с использованием конструктора
<script> class MyClass { // Используем конструктор для добавления свойств класса constructor(message = 'Hello world!') { this.message = message; } // Добавляем метод класса printMessage() { return this.message; } } // Создаем экземпляр класса MyClass const instanceOfMyClass = new MyClass(); console.log(instanceOfMyClass.printMessage()); // Вывод: 'Hello world!' </script>
Пример 2: Класс без использования конструктора
<script> class MyClass { // Добавляем метод класса printMessage() { return 'Hello world!'; } } // Создаем экземпляр класса MyClass const instanceOfMyClass = new MyClass(); console.log(instanceOfMyClass.printMessage()); // Вывод: 'Hello world!' </script>
Свойства и методы класса
Атрибуты и поведение в классах JavaScript называются свойствами класса и методами класса. Вы уже видели их примеры выше.
Когда вы хотите добавить свойство в класс, вы делаете это в методе конструктора.
Когда вы хотите добавить метод, вы делаете это внутри класса, но вне конструктора.
Когда вы хотите сослаться на любое свойство или метод внутри класса, вы должны использовать ключевое слово this
.
Давайте создадим класс NewClass
с двумя свойствами, classPropOne
и classPropTwo
, и двумя методами, someClassMethod
и anotherClassMethod
.
<script> // Создаем новый класс под названием MyClass class NewClass { // Добавляем два свойства класса, classPropOne и classPropTwo constructor(classPropOne, classPropTwo) { this.classPropOne = classPropOne; this.classPropTwo = classPropTwo; } // Добавляем метод класса с именем someClassMethod someClassMethod() { return this.classPropOne; } // Добавляем метод класса с именем anotherClassMethod anotherClassMethod() { return this.classPropTwo; } } </script>
Работать с классами JavaScript, их свойствами и методами очень просто. Вы можете увидеть это в примере в самом начале этой статьи, но это стоит упомянуть еще раз. Вы также можете добавить новые свойства и методы в классы JavaScript позже, не внося изменений непосредственно в определение класса.
Вы можете сделать это с помощью объекта-прототипа. Это работает как с свойствами класса, так и с методами. Синтаксис прост. Во-первых, это название класса. Далее следует ключевое слово prototype
, за которым следует имя метода или свойства, с точками между именем класса прототипа и именем метода или свойства.
<script> // Добавляем новый метод с именем newClassMethod в класс NewClass NewClass.prototype.newClassMethod = function() { return this.classPropOne + ' и ' + this.classPropTwo; } // Создаем экземпляр NewClass под названием foo let foo = new NewClass('foo', 'bar'); console.log(foo.newClassMethod()); // Вывод: 'foo и bar' </script>
Наследование класса (расширение)
Теперь давайте поговорим о наследовании или расширении классов. Расширение классов в основном означает, что вы создаете один класс, дочерний класс или подкласс на основе другого класса, родительского класса или суперкласса. Дочерний класс или подкласс наследует свойства и методы от родительского класса или суперкласса.
Основным преимуществом этого является то, что вы можете добавить функциональность без изменения исходного класса. Это особенно важно, когда вы не хотите менять экземпляры этого класса. Если вы добавите функциональность в класс с помощью прототипа, любое изменение, которое вы сделаете в классе, будет автоматически распространяться на все его экземпляры.
Представьте, что у вас есть класс под названием Vehicle
. Этот класс имеет некоторые свойства, такие как название, состояние и скорость. Теперь допустим, что вы хотите использовать этот класс для создания экземпляра не только автомобиля, но и, например, самолета. Все эти транспортные средства могут иметь специфические для них свойства, такие как количество колес, мощность двигателя и т.д.
Первый способ – добавить все эти свойства в класс Vehicle
. Проблема состоит в том, что это загромождает все экземпляры класса Vehicle свойствами или методами, которые они никогда не будут использовать. Другой, и гораздо лучший, вариант – использовать наследование. Это означает, что вы создадите подклассы для автомобиля и самолета, используя Vehicle
в качестве суперкласса.
Это позволит вам добавлять определенные свойства только к классам или подклассам, которые будут их использовать. Более того, поскольку все эти новые классы будут подклассами суперкласса Vehicle, все они смогут использовать некоторые свойства и методы, унаследованные от Vehicle
.
Способ создания подклассов суперкласса или расширения классов прост. Вы объявляете класс как обычно, но добавляете extends
и имя суперкласса между именем класса и фигурными скобками. Например, class MySubclass extends SuperClass{}
. Затем вы можете добавить свойства и методы, как если бы вы использовали обычный класс.
<script> // Создаем суперкласс Vehicle class Vehicle { constructor(name, condition, speed) { this.name = name; this.condition = condition; this.speed = speed; } } // Создаем подкласс Car class Car extends Vehicle { constructor(name, condition, speed, numOfWheels) { //Вызываем функцию super() со всеми параметрами, необходимыми для класса Vehicle super(name, condition, speed); this.numOfWheels = numOfWheels; } // Добавляем метод для вывода всех свойств printInfo() { return `Название: ${this.name}, Состояние: ${this.condition}, Макс. скорость: ${this.speed}, Колес: ${this.numOfWheels}`; } } // Создаем экземпляр класса Car const tesla = new Car('Tesla', 'новый', 280, 4); console.log(tesla.printInfo()); // Вывод: 'Название: Tesla, Состояние: новый, Макс. скорость: 280, Колес: 4' </script>
Наследование и дочерние классы (или подклассы)
Одна вещь, которую вы должны знать о наследовании. Это не ограничено суперклассами. Вы также можете позволить одному подклассу наследовать от другого подкласса, который также может наследовать от еще одного подкласса, который может наследовать от суперкласса. В крайнем случае, вы можете создать цепочку из сотен подклассов, наследующих один от другого, с одним суперклассом вверху.
Переопределение конструктора класса
Как вы могли видеть в примерах выше, все подклассы имели свой собственный метод конструктора. Это означает, что они переопределяли конструктор суперкласса. Когда это происходит, когда подкласс переопределяет конструктор суперкласса, вы должны вызывать метод super()
со всеми начальными параметрами конструктора.
Вызов super()
внутри конструктора вызывает конструктор суперкласса, в данном случае – Vehicle. Это позволяет подклассам использовать свойства, определенные в конструкторе суперкласса. Важно помнить, что вам нужно вызывать метод super()
в самой верхней части конструктора.
Вы должны вызывать его перед тем, как добавлять какие-либо свойства. Если вы забудете об этом, ключевое слово this
и его ссылка на класс не будут существовать, и JavaScript выдаст ошибку. Если у подкласса нет собственного конструктора, тогда вам не нужно беспокоиться ни о конструкторе суперкласса, ни о super()
.
Продолжение во второй части.