update classes

This commit is contained in:
2025-03-05 15:12:57 +03:00
parent 31caa0e6de
commit 8b08f95ce0
12 changed files with 421 additions and 0 deletions

View File

@@ -0,0 +1,44 @@
---
sidebar_position: 1
---
# Прототипное наследование
- В JavaScript все объекты имеют скрытое свойство `[[Prototype]]`, которое является либо другим объектом, либо `null`.
- Мы можем использовать `obj.__proto__` для доступа к нему (исторически обусловленный геттер/сеттер, есть другие способы, которые скоро будут рассмотрены).
- Объект, на который ссылается `[[Prototype]]`, называется «прототипом».
- Если мы хотим прочитать свойство `obj` или вызвать метод, которого не существует у `obj`, тогда JavaScript попытается найти его в прототипе.
- Операции записи/удаления работают непосредственно с объектом, они не используют прототип (если это обычное свойство, а не сеттер).
- Если мы вызываем `obj.method()`, а метод при этом взят из прототипа, то `this` всё равно ссылается на `obj`. Таким образом, методы всегда работают с текущим объектом, даже если они наследуются.
- Цикл `for..in` перебирает как свои, так и унаследованные свойства. Остальные методы получения ключей/значений работают только с собственными свойствами объекта.
## Prototype
В JavaScript объекты имеют специальное скрытое свойство `[[Prototype]]` (так оно названо в спецификации), которое либо равно `null`, либо ссылается на другой объект.\
Когда мы хотим прочитать свойство из object, а оно отсутствует, JavaScript автоматически берёт его из прототипа. В программировании такой механизм называется «прототипным наследованием».
Свойство `[[Prototype]]` является внутренним и скрытым, но есть много способов задать его.
Одним из них является использование `__proto__`
💥 Свойство `__proto__` — исторически обусловленный геттер/сеттер для `[[Prototype]]`\
Обратите внимание, что __proto__ — не то же самое, что внутреннее свойство [[Prototype]].
Это геттер/сеттер для `[[Prototype]]`. Позже мы увидим ситуации, когда это имеет значение, а пока давайте просто будем иметь это в виду, поскольку мы строим наше понимание языка JavaScript.
Свойство `__proto__` немного устарело, оно существует по историческим причинам. Современный JavaScript предполагает, что мы должны использовать функции `Object.getPrototypeOf/Object.setPrototypeOf` вместо того, чтобы получать/устанавливать прототип. Мы также рассмотрим эти функции позже.
По спецификации `__proto__` должен поддерживаться только браузерами, но по факту все среды, включая серверную, поддерживают его. Так что мы вполне безопасно его используем.
## Операция записи не использует прототип
Прототип используется только для чтения свойств. \
Операции записи/удаления работают напрямую с объектом.
## Значение «this»
***Неважно, где находится метод: в объекте или его прототипе. При вызове метода `this` — всегда объект перед точкой.***
Таким образом, вызов сеттера `admin.fullName=` в качестве `this` использует `admin`, а не `user`.
## Цикл for…in
Цикл `for..in` проходит не только по собственным, но и по унаследованным свойствам объекта.
Если унаследованные свойства нам не нужны, то мы можем отфильтровать их при помощи встроенного метода `obj.hasOwnProperty(key)`: он возвращает `true`, если у `obj` есть собственное, не унаследованное, свойство с именем `key`.

View File

@@ -0,0 +1,28 @@
---
sidebar_position: 2
---
# F.prototype
Как мы помним, новые объекты могут быть созданы с помощью функции-конструктора new F().
Если в F.prototype содержится объект, оператор new устанавливает его в качестве [[Prototype]] для нового объекта.
💥 На заметку:
JavaScript использовал прототипное наследование с момента своего появления. Это одна из основных особенностей языка.
Но раньше, в старые времена, прямого доступа к прототипу объекта не было. Надёжно работало только свойство "prototype" функции-конструктора, описанное в этой главе. Поэтому оно используется во многих скриптах.
- Свойство `F.prototype` (не путать с `[[Prototype]]`) устанавливает `[[Prototype]]` для новых объектов при вызове `new F()`.
- Значение `F.prototype` должно быть либо объектом, либо `null`. Другие значения не будут работать.
- Свойство `"prototype"` является особым, только когда оно назначено функции-конструктору, которая вызывается оператором `new`.
-
В обычных объектах prototype не является чем-то особенным:
```js
let user = {
name: "John",
prototype: "Bla-bla" // никакой магии нет - обычное свойство
};
```
По умолчанию все функции имеют `F.prototype = { constructor: F }`, поэтому мы можем получить конструктор объекта через свойство `"constructor"`.

View File

@@ -0,0 +1,66 @@
---
sidebar_position: 3
---
# Встроенные прототипы
- Все встроенные объекты следуют одному шаблону:
- Методы хранятся в прототипах (Array.prototype, Object.prototype, Date.prototype и т.д.).
- Сами объекты хранят только данные (элементы массивов, свойства объектов, даты).
- Примитивы также хранят свои методы в прототипах объектов-обёрток: Number.prototype, String.prototype, Boolean.prototype. Только у значений undefined и null нет объектов-обёрток.
- Встроенные прототипы могут быть изменены или дополнены новыми методами. Но не рекомендуется менять их. Единственная допустимая причина это добавление нового метода из стандарта, который ещё не поддерживается движком JavaScript.
## Object.prototype
Краткая нотация `obj = {}` это то же самое, что и `obj = new Object()`, где `Object` встроенная функция-конструктор для объектов с собственным свойством `prototype`, которое ссылается на огромный объект с методом `toString` и другими.
```js
let obj = {};
alert( obj ); // "[object Object]" ?
```
Таким образом, когда вызывается obj.toString(), метод берётся из Object.prototype.
## Другие встроенные прототипы
Другие встроенные объекты, такие как Array, Date, Function и другие, также хранят свои методы в прототипах.
## Примитивы
Самое сложное происходит со строками, числами и булевыми значениями.
Если мы попытаемся получить доступ к их свойствам, то тогда будет создан временный объект-обёртка с использованием встроенных конструкторов String, Number и Boolean, который предоставит методы и после этого исчезнет.
Эти объекты создаются невидимо для нас, и большая часть движков оптимизирует этот процесс, но спецификация описывает это именно таким образом. Методы этих объектов также находятся в прототипах, доступных как String.prototype, Number.prototype и Boolean.prototype.
💥 **Значения null и undefined не имеют объектов-обёрток**
## Изменение встроенных прототипов
Встроенные прототипы можно изменять. Например, если добавить метод к String.prototype, метод становится доступен для всех строк:
```js
String.prototype.show = function() {
alert(this);
};
"BOOM!".show(); // BOOM!
```
💥 Важно:
Прототипы глобальны, поэтому очень легко могут возникнуть конфликты. Если две библиотеки добавляют метод String.prototype.show, то одна из них перепишет метод другой.
Изменение встроенных прототипов считается плохой идеей.
*** В современном программировании есть только один случай, в котором одобряется изменение встроенных прототипов. Это создание полифилов.***
## Заимствование у прототипов
Некоторые методы встроенных прототипов часто одалживают.
Например, если мы создаём объект, похожий на массив (псевдомассив), мы можем скопировать некоторые методы из Array в этот объект.
```js
let obj = {
0: "Hello",
1: "world!",
length: 2,
};
obj.join = Array.prototype.join;
alert( obj.join(',') ); // Hello,world!
```
Это работает, потому что для внутреннего алгоритма встроенного метода `join` важны только корректность индексов и свойство `length`, он не проверяет, является ли объект на самом деле массивом. И многие встроенные методы работают так же.
Альтернативная возможность мы можем унаследовать от массива, установив `obj.__proto__` как `Array.prototype`, таким образом все методы `Array` станут автоматически доступны в `obj`.
Но это будет невозможно, если `obj` уже наследует от другого объекта. Помните, мы можем наследовать только от одного объекта одновременно.
Заимствование методов гибкий способ, позволяющий смешивать функциональность разных объектов по необходимости.

View File

@@ -0,0 +1,31 @@
---
sidebar_position: 4
---
# Методы прототипов, объекты без свойства __proto__
Свойство `__proto__` считается устаревшим, и по стандарту оно должно поддерживаться только браузерами.
Современные же методы это:
- `Object.create(proto[, descriptors])` создаёт пустой объект со свойством [[Prototype]], указанным как proto, и необязательными дескрипторами свойств `descriptors`.
- `Object.getPrototypeOf(obj)` возвращает свойство `[[Prototype]]` объекта `obj`.
- `Object.setPrototypeOf(obj, proto)` устанавливает свойство `[[Prototype]]` объекта `obj` как `proto`.
Эти методы нужно использовать вместо `__proto__`.
Встроенный геттер/сеттер `__proto__` не безопасен, если мы хотим использовать созданные пользователями ключи в объекте. Как минимум потому, что пользователь может ввести "__proto__" как ключ, от чего может возникнуть ошибка. Если повезёт последствия будут лёгкими, но, вообще говоря, они непредсказуемы.
Так что мы можем использовать либо `Object.create(null)` для создания «простейшего» объекта, либо использовать коллекцию `Map`.
Кроме этого, `Object.create` даёт нам лёгкий способ создать поверхностную копию объекта со всеми дескрипторами:
`__proto__` это геттер/сеттер для свойства `[[Prototype]]`, и находится он в `Object.prototype`, как и другие методы.
Ещё методы:
- `Object.keys(obj)` / `Object.values(obj)` / `Object.entries(obj)` возвращают массив всех перечисляемых собственных строковых ключей/значений/пар ключ-значение.
- `Object.getOwnPropertySymbols(obj)` возвращает массив всех собственных символьных ключей.
- `Object.getOwnPropertyNames(obj)` возвращает массив всех собственных строковых ключей.
- `Reflect.ownKeys(obj)` возвращает массив всех собственных ключей.
- `obj.hasOwnProperty(key)`: возвращает `true`, если у `obj` есть собственное (не унаследованное) свойство с именем `key`.
Все методы, которые возвращают свойства объектов (такие как `Object.keys` и другие), возвращают «собственные» свойства. Если мы хотим получить и унаследованные, можно воспользоваться циклом `for..in`.

View File

@@ -0,0 +1,51 @@
---
sidebar_position: 1
---
# Класс: базовый синтаксис
MyClass технически является функцией (той, которую мы определяем как constructor), в то время как методы, геттеры и сеттеры записываются в MyClass.prototype.
## Синтаксис «class»
```js
class MyClass {
// методы класса
constructor() { ... }
method1() { ... }
method2() { ... }
method3() { ... }
...
}
```
Затем используйте вызов `new MyClass()` для создания нового объекта со всеми перечисленными методами.
При этом автоматически вызывается метод `constructor()`, в нём мы можем инициализировать объект.
💥 **Методы в классе не разделяются запятой**
## Что такое класс?
В JavaScript класс это разновидность функции.
Вот что на самом деле делает конструкция `class User {...}`:
1. Создаёт функцию с именем `User`, которая становится результатом объявления класса. Код функции берётся из метода `constructor` (она будет пустой, если такого метода нет).
2. Сохраняет все методы, такие как `sayHi`, в `User.prototype`.
При вызове метода объекта `new User` он будет взят из прототипа, как описано в главе `F.prototype`. Таким образом, объекты new User имеют доступ к методам класса.
## Геттеры/сеттеры, другие сокращения
Как и в литеральных объектах, в классах можно объявлять вычисляемые свойства, геттеры/сеттеры и т.д.
При объявлении класса геттеры/сеттеры создаются на `User.prototype`
## Свойства классов
В приведённом выше примере у класса `User` были только методы.
```js
class User {
name = "Аноним";
sayHi() {
alert(`Привет, ${this.name}!`);
}
}
new User().sayHi();
```
Свойство name не устанавливается в `User.prototype`. Вместо этого оно создаётся оператором `new` перед запуском конструктора, это именно свойство объекта.

View File

@@ -0,0 +1,48 @@
---
sidebar_position: 2
---
# Наследование классов
Наследование классов это способ расширения одного класса другим классом.
Таким образом, мы можем добавить новый функционал к уже существующему.
1. Чтобы унаследовать от класса: class Child extends Parent:
-При этом Child.prototype.__proto__ будет равен Parent.prototype, так что методы будут унаследованы.
2. При переопределении конструктора:
- Обязателен вызов конструктора родителя super() в конструкторе Child до обращения к this.
3. При переопределении другого метода:
- Мы можем вызвать super.method() в методе Child для обращения к методу родителя Parent.
4. Внутренние детали:
- Методы запоминают свой объект во внутреннем свойстве [[HomeObject]]. Благодаря этому работает super, он в его прототипе ищет родительские методы.
- Поэтому копировать метод, использующий super, между разными объектами небезопасно.
- У стрелочных функций нет своего this и super, поэтому они «прозрачно» встраиваются во внешний контекст.
## Ключевое слово «extends»
Синтаксис создания класса допускает указывать после extends не только класс, но и любое выражение.
Пример вызова функции, которая генерирует родительский класс:
```js
function f(phrase) {
return class {
sayHi() { alert(phrase); }
};
}
class User extends f("Привет") {}
new User().sayHi(); // Привет
```
Здесь class User наследует от результата вызова f("Привет").
## Переопределение методов
```js
class Rabbit extends Animal {
stop() {
// ...теперь это будет использоваться для rabbit.stop()
// вместо stop() из класса Animal
}
}
```
У классов есть ключевое слово "super", чтобы сделать новый на его основе, изменяя или расширяя его функциональность
super.method(...) вызывает родительский метод.
super(...) для вызова родительского конструктора (работает только внутри нашего конструктора).

View File

@@ -0,0 +1,46 @@
---
sidebar_position: 3
---
# Статические свойства и методы
Статические методы используются для функциональности, принадлежат классу «в целом», а не относятся к конкретному объекту класса.
Например, метод для сравнения двух статей Article.compare(article1, article2) или фабричный метод Article.createTodays().
В объявлении класса они помечаются ключевым словом static.
Статические свойства используются в тех случаях, когда мы хотели бы сохранить данные на уровне класса, а не какого-то одного объекта.
```js
class MyClass {
static property = ...;
static method() {
...
}
}
```
Технически, статическое объявление это то же самое, что и присвоение классу:
```
MyClass.property = ...
MyClass.method = ...
```
Статические свойства и методы наследуются.
Для class B extends A прототип класса B указывает на A: B.[[Prototype]] = A. Таким образом, если поле не найдено в B, поиск продолжается в A.
## Статические свойства
Статические свойства также возможны, они выглядят как свойства класса, но с static в начале:
```js
class Article {
static publisher = "Илья Кантор";
}
alert( Article.publisher ); // Илья Кантор
```
Это то же самое, что и прямое присваивание Article:
```
Article.publisher = "Илья Кантор";
```
## Наследование статических свойств и методов
Статические свойства и методы наследуются.

View File

@@ -0,0 +1,46 @@
---
sidebar_position: 4
---
# Приватные и защищённые методы и свойства
В терминах ООП отделение внутреннего интерфейса от внешнего называется инкапсуляция.
Это даёт следующие выгоды:
**Защита для пользователей, чтобы они не выстрелили себе в ногу**
Если мы чётко отделим внутренний интерфейс, то разработчик класса сможет свободно менять его внутренние свойства и методы, даже не информируя пользователей…
#### Поддерживаемость
Если вы разработчик такого класса, то приятно знать, что приватные методы можно безопасно переименовывать, их параметры можно изменять и даже удалять, потому что от них не зависит никакой внешний код.
В новой версии вы можете полностью всё переписать, но пользователю будет легко обновиться, если внешний интерфейс остался такой же.
## Внутренний и внешний интерфейсы
В объектно-ориентированном программировании свойства и методы разделены на 2 группы:
- Внутренний интерфейс методы и свойства, доступные из других методов класса, но не снаружи класса.
- Внешний интерфейс методы и свойства, доступные снаружи класса.
Итак, всё, что нам нужно для использования объекта, это знать его внешний интерфейс. Мы можем совершенно не знать, как это работает внутри, и это здорово.
В JavaScript есть два типа полей (свойств и методов) объекта:
- Публичные: доступны отовсюду. Они составляют внешний интерфейс. До этого момента мы использовали только публичные свойства и методы.
- Приватные: доступны только внутри класса. Они для внутреннего интерфейса.
Защищённые поля не реализованы в JavaScript на уровне языка, но на практике они очень удобны, поэтому их эмулируют.
## Защищённое свойство «waterAmount»
Защищённые свойства обычно начинаются с префикса `_`.
Это не синтаксис языка: есть хорошо известное соглашение между программистами, что такие свойства и методы не должны быть доступны извне. Большинство программистов следуют этому соглашению.
## Приватное свойство «#waterLimit»
Есть новшество в языке JavaScript, которое почти добавлено в стандарт: оно добавляет поддержку приватных свойств и методов.
Приватные свойства и методы должны начинаться с #. Они доступны только внутри класса.
Например, в классе ниже есть приватное свойство #waterLimit и приватный метод #checkWater для проверки количества воды
На уровне языка # является специальным символом, который означает, что поле приватное. Мы не можем получить к нему доступ извне или из наследуемых классов.
Приватные поля не конфликтуют с публичными. У нас может быть два поля одновременно приватное #waterAmount и публичное waterAmount.

View File

@@ -0,0 +1,46 @@
---
sidebar_position: 5
---
# Проверка класса: "instanceof"
| | работает для | возвращает |
|-------------|----------------------------------------------------------------|------------|
| typeof | примитивов | строка |
| {}.toString | примитивов, встроенных объектов, объектов с Symbol.toStringTag | строка |
| instanceof | объектов | true/false |
Как мы можем видеть, технически `{}.toString` «более продвинут», чем `typeof`.
А оператор `instanceof` отличный выбор, когда мы работаем с иерархией классов и хотим делать проверки с учётом наследования.
## Оператор instanceof
```js
obj instanceof Class
```
Оператор вернёт `true`, если `obj` принадлежит классу Class или наследующему от него.
## Object.prototype.toString возвращает тип
У `toString` имеются скрытые возможности, которые делают метод гораздо более мощным. Мы можем использовать его как расширенную версию `typeof` и как альтернативу `instanceof`.
Согласно спецификации встроенный метод `toString` может быть позаимствован у объекта и вызван в контексте любого другого значения. И результат зависит от типа этого значения.
- Для числа это будет [object Number]
- Для булева типа это будет [object Boolean]
- Для null: [object Null]
- Для undefined: [object Undefined]
- Для массивов: [object Array]
- …и т.д. (поведение настраивается).
## Symbol.toStringTag
Поведение метода объектов `toString` можно настраивать, используя специальное свойство объекта `Symbol.toStringTag`.
```js
// toStringTag для браузерного объекта и класса
alert( window[Symbol.toStringTag]); // window
alert( XMLHttpRequest.prototype[Symbol.toStringTag] ); // XMLHttpRequest
alert( {}.toString.call(window) ); // [object Window]
alert( {}.toString.call(new XMLHttpRequest()) ); // [object XMLHttpRequest]
```
В итоге мы получили «typeof на стероидах», который не только работает с примитивными типами данных, но также и со встроенными объектами, и даже может быть настроен.
Можно использовать `{}.toString.call` вместо `instanceof` для встроенных объектов, когда мы хотим получить тип в виде строки, а не просто сделать проверку.

View File

@@ -0,0 +1,15 @@
---
sidebar_position: 6
---
# Примеси
По определению из Википедии, примесь это класс, методы которого предназначены для использования в других классах, причём без наследования от примеси.
Примесь общий термин в объектно-ориентированном программировании: класс, который содержит в себе методы для других классов.
С примесями могут возникнуть конфликты, если они перезаписывают существующие методы класса. Стоит помнить об этом и быть внимательнее при выборе имён для методов примеси, чтобы их избежать.
## Пример примеси
Простейший способ реализовать примесь в JavaScript это создать объект с полезными методами, которые затем могут быть легко добавлены в прототип любого класса.\
Это не наследование, а просто копирование методов.
Примеси могут наследовать друг друга.