Similar presentations:
Технологии индустриального программирования
1.
МИРЭА – Российский технологический университетИнститут перспективных технологий и индустриального программирования
09.03.02 «Информационные системы и технологии» - фуллстек разработка
Дисциплина «Технологии индустриального программирования» (3 семестр)
Лекция 3
Объектно-ориентированное программирование на JavaScript
доцент кафедры индустриального программирования
Юдин Александр Викторович
[email protected]
Москва, 2023 г.
2.
ООП в JavaScriptВ JavaScript есть объекты, но они не похожи
на объекты, которые мы привыкли видеть
в объектно-ориентированных языках
программирования типа C++. Все свойства
объекта в JavaScript открыты, а сами объекты
не принадлежат никакому классу.
Однако в JavaScript можно реализовать
методы, классы и наследование
3.
МетодыJavaScript, в отличие от большинства объектно-ориентированных языков, позволяет работать с объектами, не объявляя
предварительно классов. Мы уже видели, как создаются объекты:
let harry = { name: 'Harry Smith', salary: 90000 }
Согласно классическому определению, у объекта имеется идентичность, состояние и поведение. Только что показанный
объект, безусловно, обладает идентичностью – он отличается от любого другого объекта. Состояние объекта определяется его
свойствами. Добавим поведение в виде «метода», т. е. свойства, значением которого является функция:
harry = {
name: 'Harry Smith',
salary: 90000,
raiseSalary: function(percent) {
this.salary *= 1 + percent / 100
}
}
harry.raiseSalary(10)
При вызове функции this ссылается на объект, указанный слева от точки. Для объявления методов существует краткий
синтаксис. Можно опустить двоеточие и ключевое слово function:
harry = {
name: 'Harry Smith',
salary: 90000,
raiseSalary(percent) {
this.salary *= 1 + percent / 100
}
}
4.
ПрототипыПредположим, что имеется много объектов работников, похожих на показанный в предыдущем разделе. И
мы хотим включить свойство raiseSalary в каждый из них. Для автоматизации этой задачи можно написать
фабричную функцию:
function createEmployee(name, salary) {
return {
name: name,
salary: salary,
raiseSalary: function(percent) {
this.salary *= 1 + percent / 100
}
}
}
Но все равно у каждого объекта работника имеется собственное свойство raiseSalary, пусть даже его
значениями являются одинаковые функции. Было бы лучше, если бы все работники разделяли одну общую
функцию. Для этого и предназначены прототипы
5.
ПрототипыВ прототипе собраны свойства, общие для нескольких объектов. Вот как выглядит прототип, содержащий
разделяемые методы:
const employeePrototype = {
raiseSalary: function(percent) {
this.salary *= 1 + percent / 100
}
}
При создании объекта работника мы задаем его прототип. Прототип – это «внутренний слот» объекта. Этот
технический термин употребляется в спецификации языка ECMAScript для обозначения атрибута объекта,
над которым производятся внутренние манипуляции, но который не раскрывается программистам в виде
свойства. Читать и изменять внутренний слот [[Property]] (так он называется в спецификации) позволяют
методы Object.getPrototypeOf и Object.setPrototypeOf. Следующая функция создает объект работника и
устанавливает его прототип:
function createEmployee(name, salary) {
const result = { name, salary }
Object.setPrototypeOf(result, employeePrototype)
return result
}
6.
ПрототипыВо многих реализациях JavaScript к прототипу объекта можно обратиться с помощью свойства
obj.__proto__. Это не стандартный способ, следует использовать методы Object.getPrototypeOf и
Object.setPrototypeOf.
Поиск в прототипе – простая, но очень важная в JavaScript идея. Прототипы используются для реализации
классов и наследования, а также для модификации поведения объектов уже после создания.
7.
КонструкторыИтак, мы можем написать фабричную функцию, которая создает новые экземпляры объектов с общим прототипом.
Для вызова таких функций существует специальный синтаксис с оператором new. По соглашению, функции,
конструирующие объекты, называют так, как назвали бы класс в языке, где классы существуют. В нашем примере
назовем функцию Employee:
function Employee(name, salary) {
this.name = name
this.salary = salary
}
При вызове
new Employee('Harry Smith', 90000)
оператор new создает новый пустой объект, а затем вызывает функцию-конструктор. Параметр this указывает на вновь
созданный объект. В теле функции Employee устанавливаются свойства объекта с помощью параметра this. Этот объект
становится значением выражения new.
Помимо вызова конструктора, выражение new выполняет еще одно важное действие: заполняет внутренний слот
[[Prototype]]. В этот слот записывается специальный объект, прикрепленный к функции-конструктору. Напомним, что
функция является объектом, поэтому может иметь свойства. У каждой функции в JavaScript имеется свойство prototype,
значением которого является объект. Этот объект предоставляет готовое место для добавления методов, например:
Employee.prototype.raiseSalary = function(percent) {
this.salary *= 1 + percent / 100
}
8.
Конструкторыconst harry = new Employee('Harry Smith', 90000)
Распишем детально все шаги.
1. Оператор new создает новый объект.
2. Во внутренний слот [[Prototype]] этого
объекта записывается объект
Employee.prototype.
3. Оператор new вызывает функциюконструктор с тремя параметрами:
this (указывает на только что созданный
объект), name и salary.
4. В теле функции Employee
устанавливаются свойства объекта,
для чего используется параметр this.
5. Конструктор возвращает управление, значением оператора new
является полностью инициализированный к этому моменту объект.
6. Переменная harry инициализируется ссылкой на объект.
9.
Синтаксис классовВ настоящее время JavaScript обзавелся синтаксисом классов, упаковывающим функцию-конструктор и
методы прототипа в знакомую форму. Вот как пример с предыдущих слайдов записывается с применением
синтаксиса классов:
class Employee {
constructor(name, salary) {
this.name = name
this.salary = salary
}
raiseSalary(percent) {
this.salary *= 1 + percent / 100
}
}
Этот код делает в точности то же самое, что код с предыдущих слайдов. Но собственно класса по-прежнему не
существует. За кулисами объявление class просто объявляет функцию-конструктор Employee. Ключевое слово
constructor служит для объявления тела функции-конструктора Employee. Метод raiseSalary добавляется в
Employee.prototype. Как и в предыдущем разделе, мы конструируем объект, вызывая функцию-конструктор с помощью
оператора new:
const harry = new Employee('Harry Smith', 90000)
10.
Акцессоры чтения и записиАкцессором чтения (getter) называется метод без параметров, объявленный с помощью ключевого слова
get:
class Person {
constructor(last, first) {
this.last = last;
this.first = first
}
get fullName() { return `${this.last}, ${this.first}` }
}
При вызове акцессора чтения скобки не ставятся, как если бы мы обращались к значению свойства:
const harry = new Person('Smith', 'Harry')
const harrysName = harry.fullName // 'Smith, Harry'
У объекта harry нет свойства fullName, но вызывается акцессор чтения. Можно считать, что акцессор чтения
– это динамически вычисляемое свойство.
Можно также задать акцессор записи (setter) – метод с одним параметром
11.
Поля экземпляра и закрытые методыМы можем динамически создать свойство объекта в конструкторе или любом другом методе, присвоив значение
this.имяСвойства. Такие свойства аналогичны полям экземпляра в языках, основанных на классах.
class BankAccount {
constructor() { this.balance = 0 }
deposit(amount) { this.balance += amount }
...
}
Поле считается закрытым (т. е. доступным только из методов класса), если его имя начинается знаком #:
class BankAccount {
#balance = 0
deposit(amount) { this.#balance += amount }
...
}
Метод считается закрытым , если его имя начинается знаком #.
12.
Статические методы и поляВ объявлении class метод можно объявить с ключевым словом static. Такой метод не вызывается от имени
объекта. Это обычная функция, являющаяся свойством класса, например:
class BankAccount {
...
static percentOf(amount, rate) { return amount * rate / 100 }
...
addInterest(rate) {
this.balance += BankAccount.percentOf(this.balance, rate)
}
}
Для вызова статического метода, все равно – изнутри или извне класса, нужно добавить имя класса, как в
примере выше
13.
ПодклассыКлючевой концепцией объектно-ориентированного программирования является наследование. Класс определяет
поведение своих экземпляров. Мы можем создать подкласс данного класса (который называется суперклассом),
экземпляры которого будут вести себя в каком-то отношении по-другому, но остальное поведение унаследуют
от суперкласса. Стандартный учебный пример – иерархия наследования с суперклассом Employee и подклассом
Manager. Ожидается, что работники получают за свою работу зарплату, а менеджеры, помимо основной
зарплаты, получают бонусы, если достигают поставленных целей.
В JavaScript для выражения такой связи между классами Employee и Manager служит ключевое слово extends:
class Employee {
constructor(name, salary) { ... }
raiseSalary(percent) { ... }
...
}
class Manager extends Employee {
getSalary() { return this.salary + this.bonus }
...
}
14.
ПодклассыОрганизуется цепочка прототипов. Прототипом Manager.prototype становится Employee.prototype.
Таким образом, любой метод, не объявленный в подклассе, ищется в его суперклассе. Например, мы
можем вызвать метод raiseSalary объекта менеджера:
const boss = new Manager(...)
boss.raiseSalary(10) // вызывается Employee.prototype.raiseSalary
15.
Оператор instanceofОператор instanceof проверяет, принадлежит ли объект классу или одному из его
подклассов. Технически оператор обходит цепочку прототипов объекта и проверяет, есть
ли в ней прототип данной функции-конструктора.
Например, выражение
boss instanceof Employee
равно true, поскольку Employee.prototype встречается в цепочке прототипов boss.
16.
Конструирование подклассаВ конструкторе подкласса обязательно вызывать конструктор суперкласса. Для этого используется
синтаксис super(...), как в Java. Внутри скобок поместите аргументы, которые нужно передать конструктору
суперкласса.
class Manager extends Employee {
constructor(name, salary, bonus) {
super(name, salary) // вызывать конструктор суперкласса обязательно
this.bonus = bonus // потом допустим такой код
}
...
}
Ссылку this разрешается использовать только после вызова super. Однако если вы не предоставили
конструктор подкласса, то он будет сгенерирован автоматически. И этот автоматический конструктор
передает все аргументы конструктору суперкласса.