-
1. . Классы в JS - часть стандарта ES6 (2015). На деле представляют собой высокоуровневые асбтракции над ф-цией - конструктором
-
2. . Объявление и получение экземпляра класса
Операции с классами для корректности работы кода, проводятся в строгом режиме
Листинг 1. Объявление класса с получением его экземпляра
"use strict";
class User{}
let user1 = new User()
-
3. . Публичные поля и методы класса
Доступ к публичным полям и методам осуществляется, как из экземпляра класса, так и в самом классе и его наследниках
Листинг 2. Объявление публичных полей класса. Публичные поля можно объявлять, как в конструкторе, так и вне его
"use strict";
class Calculator{
counter = 0
constructor(amount){
this.amount = amount
}
getFormattedAmount(){
return '$' + ' ' + this.amount
}
}
let calculator = new Calculator(10)
calculator.counter
calculator.amount
calculator.getFormattedAmount()
-
4. Приватные поля и методы класса
Доступ к приватным полям и методам осуществляется только в самом классе. Но не в его экземпляре и наследниках
Листинг 3. Объявление и получение доступа к приватным полям класса и методам
"use strict";
class Calculator{
#amount = 10
#formattAmmount(){
return '$ ' + this.#amount
}
getFormattedAmount(){
return this.#formattAmmount()
}
}
let calculator = new Calculator(10)
calculator.#amount
calculator.#formattAmmount()
calculator.getFormattedAmount()
Если приватному полю при инициализации об-та класса надо присовить значение, то поле дублируется в конструкторе
Листинг 4. Присвоение значений приватным полям при инициализации об-та класса
"use strict";
class Calculator{
#amount
constructor(amount){
this.#amount = amount
}
#formattAmmount(){
return '$ ' + this.#amount
}
getFormattedAmount(){
return this.#formattAmmount()
}
}
let calculator = new Calculator(100)
calculator.getFormattedAmount()
-
5. . Статические поля и методы
Это поля, которые вызываются прямо из об-та класса без создания его экземпляра
Листинг 5. Объявление и вызов полей переменных
"use strict";
class MathConstants{
static Pi = 3.14
}
MathConstants.Pi
Статические переменные удобно использовать в других классах
Листинг 6. Вызов статической переменной из другого класса
"use strict";
class MathConstants{
static Pi = 3.14
}
class Square{
circle(r){
return MathConstants.Pi * Math.pow(r,2)
}
}
const square = new Square()
square.circle(10)
Создание и вызов статических методов происходит аналогично. Статические поля и методы могут быть, как публичными, так и приватными. Полная информация по ним находится в документации:
Также см учебный материал Статические свойства и методы
Статические св-ва и методы привязаны не к экземпляру класса, а к самому классу, поэтому являются общими для всех его экземпляров
-
6. . Геттеры и сеттеры. Работа с геттерами и сеттерами происходит аналогично такой же работе в об-тах и ф-циях - конструкторе
Листинг 7. Методы get и set в классах
"use strict";
class Pribor{
_temperature = 0
get temperature(){
return this._temperature
}
set temperature(value){
try {
if(value < 0) throw new Error('Температура не может быть отрицательной')
this._temperature = value
} catch (error) {
console.log(error.message)
}
}
}
const pribor = new Pribor()
pribor.temperature = -10
console.log(pribor.temperature)
-
7. . Наследование классов осуществляется с помощью ключевого слова extends
Листинг 8. Простое наследование
"use strict";
class User{
name = 'Den'
getName(){
return this.name
}
}
class Student extends User{}
let student = new Student()
student.getName()
Приватные методы и поля могут быть доступны только внутри класса, где они были объявлены. В данном случае, это класс User. В классе Student и его экземплярах они доступны не будут
Листинг 9. Наследование приватных методов и полей невозможно
"use strict";
class User{
#name = 'Den'
#getName(){
return this.#name
}
}
class Student extends User{}
let student = new Student()
student.#getName()
Объявление конструктора в дочерних классах вызовет перезаписывание родтельского конструктора. Поэтому, его надо также вызывать с помощью ключевого слова super
Листинг 10. Вызов родительского конструктора в дочернем классе
"use strict";
class User{
constructor(name){
this.name = name
}
}
class Student extends User{
constructor(name, faculty){
super(name)
this.faculty = faculty
}
getData(){
return `Имя: ${this.name}, Факультет: ${this.faculty}`
}
}
let student = new Student('Денис', 'экономический')
student.getData()
-
8. . Что такое экземпляр класса и зачем он нужен? Экземпляр класса, это об-т, который имеет в себе всю внутреннюю логику класса, но инкапсулирован от других об-тов.
К примеру, существует, об-т (класс), который формирует кастомный элемент select, который выводится на странице более одного раза. Покажем разницу
Пример 1. На странице находится один селект
Листинг 11.1. Html разметка под плагин
<div class="w-50 p-5 el-1">
<div class="mb-4">
<span>Лог выбора пользователя: </span>
<span class="select-log"></span>
</div>
<div class="select-slot"></div>
</div>
Листинг 11.2 Плагин кастомного элемента select
let CustomSelectObject = {
log: [],
element: null,
init(data){
this.element = document.querySelector(data.selector)
let selectSlot = this.element.querySelector('.select-slot')
selectSlot.innerHTML = null
selectSlot.appendChild(this.select(data.list))
},
select(list){
let select = document.createElement('select')
select.className = "form-select"
select.addEventListener('change',(e) => {
this.addToLog(e.currentTarget.value)
})
select.innerHTML = list.map(item => {
return `<option value="${item}">${item}</option>`
}).join('')
return select
},
addToLog(data){
this.log.push(data)
this.element.querySelector('.select-log').innerHTML = this.log.join(', ')
}
}
CustomSelectObject.init({
selector: '.el-1',
list: ['Германия', 'Украина', 'Швеция'],
})
Пример 2. На страницу необходимо добавить ещё один селект с другими данными
Листинг 12.1. Html разметка под второй селект
<div class="w-50 p-5 el-2">
<div class="mb-4">
<span>Лог выбора пользователя: </span>
<span class="select-log"></span>
</div>
<div class="select-slot"></div>
</div>
Листинг 12.2 Добавляем об-т с данными для второго селекта
CustomSelectObject.init({
selector: '.el-2',
list: ['Берлин', 'Киев', 'Стокгольм'],
})
Ошибка Так, как простые об-ты образуют всего 1 экземпляр, то данные второго об-та перезаписывают данные первого. Поэтому выбор, как в селекте 1, так и в селекте 2, будет добавляться в один общий массив CustomSelectObject.log
Для решения этой проблемы существуют 2 варианта:
- Вариант 1 Усложнить логину плагина, а именно: создать дополнительное поле children (к примеру), куда сохранять об-т данных каждого вновь добавленного селекта, а самому селекту добавлять атрибут data-ref с уникальным номер, по которому будет установлена связь с соответсвующим ему элементом массива
- Вариант 2 Использовать ф-цию конструктор (классы) и под каждый новый селект создавать свой экземпляр класса
Пример 3. Использование классов с созданием своего экземпляра под каждый новый селект
Листинг 13. Плагин кастомного элемента select
"use strict";
class CustomSelectClass{
#log = []
#element = null
init(data){
this.#element = document.querySelector(data.selector)
let selectSlot = this.#element.querySelector('.select-slot')
selectSlot.innerHTML = null
selectSlot.appendChild(this.#select(data.list))
}
#select(list){
let select = document.createElement('select')
select.className = "form-select"
select.addEventListener('change',(e) => {
this.#addToLog(e.currentTarget.value)
})
select.innerHTML = list.map(item => {
return `<option value="${item}">${item}</option>`
}).join('')
return select
}
#addToLog(data){
this.#log.push(data)
this.#element.querySelector('.select-log').innerHTML = this.#log.join(', ')
}
}
let select1 = new CustomSelectClass()
select1.init({
selector: '.el-1',
list: ['Германия', 'Украина', 'Швеция'],
})
let select2 = new CustomSelectClass()
select2.init({
selector: '.el-2',
list: ['Берлин', 'Киев', 'Стокгольм'],
})
Ошибка устранена. Логика обработки каждого селекта осуществляется соответствующим экземпляром класса. Поэтому данные выбора каждого селекта разделены и добавляются исключительно в свой массив
Выводы по главе
1. Классы это высокоуровневая надстройка над ф-цией - конструткором, поэтому они повторяют её логику
2. Поля и методы делятся на публичные и приватные
3. Существуют также статические поля и методы, которые также могут быть, как публичными, так и приватными
4. Синтаксис и смысл геттеров и сеттеров аналогичен таковым в ф-ции - конструкторе
5. Классы могут друг друга наследовать. Один класс может иметь несколько дочерних
Упражнения
Задачи ниже решаются в строгом режиме
1. Создать класс User, который будет иметь предустановленные вне коснтруктора публичные поля name = Макс и age = 25. Создать публичный метод getData, который будет их возвращать в формате 'name age'
2. Создать класс User, который будет иметь публичные поля name и age. Значения их задавать в экземпляре через конструктор. Создать публичный метод getData, который будет их возвращать в формате 'Имя: name, возраст: age'
3. Создать класс User, который будет иметь публичное поле name и приватное поле online (boolean). Значение публичного поля задать любыми данными через конструктор. Значение приватного поля online должно выставляться рандомно (true/false) в приватном методе setStatus. Создать публичный метод getStatus, который будет возвращать информацию в формате 'Имя: name, status: true - онлайн, false - офлайн'
4. Создать класс Summarize, которая на входе будет принимать массив данных о прибыле по месяцам [1000, 2590, ...] (1000 - январь, 2590 - февраль и т.д. продолжить произвольными значениями по всем месяцам).
- Создать приватный метод annualSum, который будет суммировать и возвращать всю годовую прибыль.
- Создать приватный метод formSum, который будет добавлять знак $ впереди переданного в него значения и возвращать в таком формате
- Создать публичный метод showData, который будет выводить в консоль переданные в него данные.
В финальном варианте созданный экземпляр класса Summarize при вызове метода showData, должен выводить в консоль итоговую годовую сумму в формате $ 10000000
5. Создать класс со св-вами firstName и lastName. Создать геттер, который будет выводить полное имя при обращении к св-ву fullName
6. Создать класс со св-вами firstName и lastName. Создать геттер, который будет выводить полное имя при обращении к св-ву fullName и сеттер, который при попытке изменить св-во fullName будет вывод в консоль сообщение 'Изменение свойства fullName напрямую запрещено'
7. Создать класс Figure (фигура). Создать от него два дочерних класса Rectangle (четырёхугольник) и Circle (окружность). Figure должен содержать поле square (площадь) и метод getSquare, который должен его возвращать. Дочерние классы должны иметь метод setSquare, который принимает об-т параметров (для четырёхугольника - 2 стороны: a, b И для окружности - радиус: r) и возвращать вычисленный результат. При получении параметров, приводить их к числовому типу (Number), затем делать проверку (isNaN) - если проверка не пройдена, выводить сообщение в консоль и завершать выполнение программы выбросом ошибки (throw new Error())
Проверка работы программы - вызов метода setSquare с соответствующими параметрами и getSquare для получения результата
Площадь четырёхугольника: S = a * b
Площадь окружности: S = π * r^2
Значение константы π должно быть прописано в статическом св-ве родительского класса