+
+А на этом поле функция handler вызывается с применением debounce – debounce(handler, 1000):
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/solution.md b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/solution.md
index 82c2ecb5fe..5952020f11 100644
--- a/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/solution.md
+++ b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/solution.md
@@ -1,27 +1,12 @@
```js demo
-function debounce(f, ms) {
-
- let isCooldown = false;
-
+function debounce(func, ms) {
+ let timeout;
return function() {
- if (isCooldown) return;
-
- f.apply(this, arguments);
-
- isCooldown = true;
-
- setTimeout(() => isCooldown = false, ms);
+ clearTimeout(timeout);
+ timeout = setTimeout(() => func.apply(this, arguments), ms);
};
-
}
-```
-
-Вызов `debounce` возвращает обёртку. Возможны два состояния:
-- `isCooldown = false` -- готова к выполнению.
-- `isCooldown = true` -- ожидание окончания тайм-аута.
-В первом вызове `isCoolDown = false`, поэтому вызов продолжается, и состояние изменяется на `true`.
-
-Пока `isCoolDown` имеет значение `true`, все остальные вызовы игнорируются.
+```
-Затем `setTimeout` устанавливает его в `false` после заданной задержки.
+Вызов `debounce` возвращает обёртку. При вызове он планирует вызов исходной функции через указанное количество `ms` и отменяет предыдущий такой тайм-аут.
\ No newline at end of file
diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/task.md b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/task.md
index 9c87aa129f..bea131145e 100644
--- a/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/task.md
+++ b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/task.md
@@ -4,20 +4,49 @@ importance: 5
# Декоратор debounce
-Результатом декоратора `debounce(f, ms)` должна быть обёртка, которая передаёт вызов `f` не более одного раза в `ms` миллисекунд.
-Другими словами, когда мы вызываем `debounce`, это гарантирует, что все остальные вызовы будут игнорироваться в течение `ms`.
+Результат декоратора `debounce(f, ms)` – это обёртка, которая откладывает вызовы `f`, пока не пройдёт `ms` миллисекунд бездействия (без вызовов, «cooldown period»), а затем вызывает `f` один раз с последними аргументами.
-Например:
+Другими словами, `debounce` – это так называемый секретарь, который принимает «телефонные звонки», и ждёт, пока не пройдет `ms` миллисекунд тишины. И только после этого передает «начальнику» информацию о последнем звонке (вызывает непосредственно `f`).
+
+Например, у нас была функция `f` и мы заменили её на `f = debounce(f, 1000)`.
+
+Затем, если обёрнутая функция вызывается в 0, 200 и 500 мс, а потом вызовов нет, то фактическая `f` будет вызвана только один раз, в 1500 мс. То есть: по истечению 1000 мс от последнего вызова.
+
+
+
+...И она получит аргументы самого последнего вызова, остальные вызовы игнорируются.
+
+Ниже код этого примера (используется декоратор debounce из библиотеки [Lodash](https://lodash.com/docs/4.17.15#debounce)):
```js no-beautify
-let f = debounce(alert, 1000);
+let f = _.debounce(alert, 1000);
+
+f("a");
+setTimeout( () => f("b"), 200);
+setTimeout( () => f("c"), 500);
+
+// Обёрнутая в debounce функция ждёт 1000 мс после последнего вызова, а затем запускает: alert("c")
+```
-f(1); // выполняется немедленно
-f(2); // проигнорирован
+Теперь практический пример. Предположим, пользователь набирает какой-то текст, и мы хотим отправить запрос на сервер, когда ввод этого текста будет завершён.
-setTimeout( () => f(3), 100); // проигнорирован (прошло только 100 мс)
-setTimeout( () => f(4), 1100); // выполняется
-setTimeout( () => f(5), 1500); // проигнорирован (прошло только 400 мс от последнего вызова)
+Нет смысла отправлять запрос для каждого набранного символа. Вместо этого мы хотели бы подождать, а затем обработать весь результат.
+
+В браузере мы можем настроить обработчик событий – функцию, которая вызывается при каждом изменении поля для ввода. Обычно обработчик событий вызывается очень часто, для каждого набранного символа. Но если мы воспользуемся `debounce` на 1000мс, то он будет вызван только один раз, через 1000мс после последнего ввода символа.
+
+```online
+
+В этом живом примере обработчик помещает результат в поле ниже, попробуйте:
+
+[iframe border=1 src="debounce" height=200]
+
+Видите? На втором поле вызывается функция, обёрнутая в `debounce`, поэтому его содержимое обрабатывается через 1000мс с момента последнего ввода.
```
-На практике `debounce` полезен для функций, которые получают/обновляют данные, и мы знаем, что повторный вызов в течение короткого промежутка времени не даст ничего нового. Так что лучше не тратить на него ресурсы.
+Таким образом, `debounce` – это отличный способ обработать последовательность событий: будь то последовательность нажатий клавиш, движений мыши или ещё что-либо.
+
+Он ждёт заданное время после последнего вызова, а затем запускает свою функцию, которая может обработать результат.
+
+Задача — реализовать декоратор `debounce`.
+
+Подсказка: это всего лишь несколько строк, если вдуматься :)
\ No newline at end of file
diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/solution.md b/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/solution.md
index 4d64b9d427..1f1a8c24c1 100644
--- a/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/solution.md
+++ b/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/solution.md
@@ -34,6 +34,6 @@ function throttle(func, ms) {
1. Во время первого вызова обёртка просто вызывает `func` и устанавливает состояние задержки (`isThrottled = true`).
2. В этом состоянии все вызовы запоминаются в `saveArgs / saveThis`. Обратите внимание, что контекст и аргументы одинаково важны и должны быть запомнены. Они нам нужны для того, чтобы воспроизвести вызов позднее.
-3. ... Затем по прошествии `ms` миллисекунд срабатывает `setTimeout`. Состояние задержки сбрасывается (`isThrottled = false`). И если мы проигнорировали вызовы, то "обёртка" выполняется с последними запомненными аргументами и контекстом.
+3. Затем по прошествии `ms` миллисекунд срабатывает `setTimeout`. Состояние задержки сбрасывается (`isThrottled = false`). И если мы проигнорировали вызовы, то "обёртка" выполняется с последними запомненными аргументами и контекстом.
На третьем шаге выполняется не `func`, а `wrapper`, потому что нам нужно не только выполнить `func`, но и ещё раз установить состояние задержки и таймаут для его сброса.
diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/task.md b/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/task.md
index fe5949e7df..f6f7aab282 100644
--- a/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/task.md
+++ b/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/task.md
@@ -4,18 +4,21 @@ importance: 5
# Тормозящий (throttling) декоратор
-Создайте "тормозящий" декоратор `throttle(f, ms)`, который возвращает обёртку, передавая вызов в `f` не более одного раза в `ms` миллисекунд. Те вызовы, которые попадают в период "торможения", игнорируются.
+Создайте «тормозящий» декоратор `throttle(f, ms)`, который возвращает обёртку.
-**Отличие от `debounce` - если проигнорированный вызов является последним во время "задержки", то он выполняется в конце.**
+При многократном вызове он передает вызов `f` не чаще одного раза в `ms` миллисекунд.
-Давайте рассмотрим реальное применение, чтобы лучше понять это требование и выяснить, откуда оно взято.
+По сравнению с декоратором `debounce` поведение совершенно другое:
+- `debounce` запускает функцию один раз после периода «бездействия». Подходит для обработки конечного результата.
+- `throttle` запускает функцию не чаще, чем указанное время `ms`. Подходит для регулярных обновлений, которые не должны быть слишком частыми.
-**Например, мы хотим отслеживать движения мыши.**
+Другими словами, `throttle` похож на секретаря, который принимает телефонные звонки, но при этом беспокоит начальника (вызывает непосредственно `f`) не чаще, чем один раз в `ms` миллисекунд.
+Давайте рассмотрим реальное применение, чтобы лучше понять это требование и выяснить, откуда оно взято.
-В браузере мы можем объявить функцию, которая будет запускаться при каждом движении указателя и получать его местоположение. Во время активного использования мыши эта функция запускается очень часто, это может происходить около 100 раз в секунду (каждые 10 мс).
+**Например, мы хотим отслеживать движения мыши.**
-**Мы бы хотели обновлять информацию на странице при передвижениях.**
+В браузере мы можем реализовать функцию, которая будет запускаться при каждом перемещении указателя и получать его местоположение. Во время активного использования мыши эта функция запускается очень часто, что-то около 100 раз в секунду (каждые 10 мс). **Мы бы хотели обновлять некоторую информацию на странице при передвижении указателя.**
...Но функция обновления `update()` слишком ресурсоёмкая, чтобы делать это при каждом микродвижении. Да и нет смысла делать обновление чаще, чем один раз в 1000 мс.
diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/article.md b/1-js/06-advanced-functions/09-call-apply-decorators/article.md
index 94cac25d51..6fc33f17f8 100644
--- a/1-js/06-advanced-functions/09-call-apply-decorators/article.md
+++ b/1-js/06-advanced-functions/09-call-apply-decorators/article.md
@@ -62,7 +62,7 @@ alert( "Again: " + slow(2) ); // возвращаем из кеша
- При необходимости мы можем объединить несколько декораторов (речь об этом пойдёт позже).
-## Применение "func.call" для передачи контекста.
+## Применение "func.call" для передачи контекста
Упомянутый выше кеширующий декоратор не подходит для работы с методами объектов.
diff --git a/1-js/06-advanced-functions/10-bind/article.md b/1-js/06-advanced-functions/10-bind/article.md
index 9b1c8d4731..1c597cdf12 100644
--- a/1-js/06-advanced-functions/10-bind/article.md
+++ b/1-js/06-advanced-functions/10-bind/article.md
@@ -191,7 +191,7 @@ for (let key in user) {
}
```
-Некоторые JS-библиотеки предоставляют встроенные функции для удобной массовой привязки контекста, например [_.bindAll(obj)](http://lodash.com/docs#bindAll) в lodash.
+Некоторые JS-библиотеки предоставляют встроенные функции для удобной массовой привязки контекста, например [_.bindAll(obj)](https://lodash.com/docs#bindAll) в lodash.
````
## Частичное применение
diff --git a/1-js/07-object-properties/01-property-descriptors/article.md b/1-js/07-object-properties/01-property-descriptors/article.md
index 7f776c76fd..40dbc8f601 100644
--- a/1-js/07-object-properties/01-property-descriptors/article.md
+++ b/1-js/07-object-properties/01-property-descriptors/article.md
@@ -194,7 +194,7 @@ alert(Object.keys(user)); // name
Флаг неконфигурируемого свойства (`configurable:false`) иногда предустановлен для некоторых встроенных объектов и свойств.
-Неконфигурируемое свойство не может быть удалено.
+Неконфигурируемое свойство не может быть удалено, его атрибуты не могут быть изменены.
Например, свойство `Math.PI` - только для чтения, неперечислимое и неконфигурируемое:
@@ -214,36 +214,58 @@ alert( JSON.stringify(descriptor, null, 2 ) );
То есть программист не сможет изменить значение `Math.PI` или перезаписать его.
```js run
-Math.PI = 3; // Ошибка
+Math.PI = 3; // Ошибка, потому что writable: false
// delete Math.PI тоже не сработает
```
+Мы также не можем изменить `writable`:
-Определение свойства как неконфигурируемого - это дорога в один конец. Мы не сможем отменить это действие, потому что `defineProperty` не работает с неконфигурируемыми свойствами.
+```js run
+// Ошибка, из-за configurable: false
+Object.defineProperty(Math, "PI", { writable: true });
+```
+Мы абсолютно ничего не можем сделать с `Math.PI`.
+
+Определение свойства как неконфигурируемого - это дорога в один конец. Мы не можем изменить его обратно с помощью `defineProperty`.
-В коде ниже мы делаем `user.name` "навечно запечатанной" константой:
+**Обратите внимание: `configurable: false` не даст изменить флаги свойства, а также не даст его удалить. При этом можно изменить значение свойства.**
+
+В коде ниже свойство `user.name` является неконфигурируемым, но мы все ещё можем изменить его значение (т.к. `writable: true`).
```js run
-let user = { };
+let user = {
+ name: "John"
+};
+
+Object.defineProperty(user, "name", {
+ configurable: false
+});
+
+user.name = "Pete"; // работает
+delete user.name; // Ошибка
+```
+А здесь мы делаем `user.name` "навечно запечатанной" константой, как и встроенный `Math.PI`:
+```js run
+let user = {
+ name: "John"
+};
Object.defineProperty(user, "name", {
- value: "John",
writable: false,
configurable: false
});
-*!*
// теперь невозможно изменить user.name или его флаги
// всё это не будет работать:
-// user.name = "Pete"
-// delete user.name
-// defineProperty(user, "name", ...)
-Object.defineProperty(user, "name", {writable: true}); // Ошибка
-*/!*
-```
+user.name = "Pete";
+delete user.name;
+Object.defineProperty(user, "name", { value: "Pete" });
+```
+
+```smart header="Единственное возможное изменение атрибута: writable true -> false"
+Есть небольшое исключение, касающееся изменения флагов.
-```smart header="Ошибки отображаются только в строгом режиме"
-В нестрогом режиме мы не увидим никаких ошибок при записи в свойства "только для чтения" и т.п. Эти операции всё равно не будут выполнены успешно. Действия, нарушающие ограничения флагов, в нестрогом режиме просто молча игнорируются.
+Мы можем изменить `writable: true` на `false` для неконфигурируемого свойства, тем самым предотвратив изменение его значения (чтобы добавить ещё один уровень защиты). Однако не наоборот.
```
## Метод Object.defineProperties
@@ -292,7 +314,7 @@ for (let key in user) {
...Но это не копирует флаги. Так что если нам нужен клон "получше", предпочтительнее использовать `Object.defineProperties`.
-Другое отличие в том, что `for..in` игнорирует символьные свойства, а `Object.getOwnPropertyDescriptors` возвращает дескрипторы *всех* свойств, включая свойства-символы.
+Другое отличие в том, что `for..in` игнорирует символьные и неперечислимые свойства, а `Object.getOwnPropertyDescriptors` возвращает дескрипторы *всех* свойств.
## Глобальное запечатывание объекта
diff --git a/1-js/07-object-properties/02-property-accessors/article.md b/1-js/07-object-properties/02-property-accessors/article.md
index 023449fa6f..5be8d15a1b 100644
--- a/1-js/07-object-properties/02-property-accessors/article.md
+++ b/1-js/07-object-properties/02-property-accessors/article.md
@@ -55,6 +55,8 @@ alert(user.fullName); // John Smith
Снаружи свойство-аксессор выглядит как обычное свойство. В этом и заключается смысл свойств-аксессоров. Мы не *вызываем* `user.fullName` как функцию, а *читаем* как обычное свойство: геттер выполнит всю работу за кулисами.
+На данный момент у `fullName` есть только геттер. Если мы попытаемся назначить `user.fullName=`, произойдёт ошибка:
+
```js run
let user = {
get fullName() {
diff --git a/1-js/08-prototypes/01-prototype-inheritance/1-property-after-delete/task.md b/1-js/08-prototypes/01-prototype-inheritance/1-property-after-delete/task.md
index 5f9791ee3e..624b27c3c6 100644
--- a/1-js/08-prototypes/01-prototype-inheritance/1-property-after-delete/task.md
+++ b/1-js/08-prototypes/01-prototype-inheritance/1-property-after-delete/task.md
@@ -12,6 +12,7 @@ importance: 5
let animal = {
jumps: null
};
+
let rabbit = {
__proto__: animal,
jumps: true
diff --git a/1-js/08-prototypes/01-prototype-inheritance/article.md b/1-js/08-prototypes/01-prototype-inheritance/article.md
index eb354126cb..b02f250839 100644
--- a/1-js/08-prototypes/01-prototype-inheritance/article.md
+++ b/1-js/08-prototypes/01-prototype-inheritance/article.md
@@ -18,10 +18,11 @@
Одним из них является использование `__proto__`, например так:
-```js run
+```js
let animal = {
eats: true
};
+
let rabbit = {
jumps: true
};
@@ -31,14 +32,6 @@ rabbit.__proto__ = animal;
*/!*
```
-```smart header="Свойство `__proto__` — исторически обусловленный геттер/сеттер для `[[Prototype]]`"
-Обратите внимание, что `__proto__` — *не то же самое*, что `[[Prototype]]`. Это геттер/сеттер для него.
-
-Он существует по историческим причинам, в современном языке его заменяют функции `Object.getPrototypeOf/Object.setPrototypeOf`, которые также получают/устанавливают прототип. Мы рассмотрим причины этого и сами функции позже.
-
-По спецификации `__proto__` должен поддерживаться только браузерами, но по факту все среды, включая серверную, поддерживают его. Далее мы будем в примерах использовать `__proto__`, так как это самый короткий и интуитивно понятный способ установки и чтения прототипа.
-```
-
Если мы ищем свойство в `rabbit`, а оно отсутствует, JavaScript автоматически берёт его из `animal`.
Например:
@@ -47,6 +40,7 @@ rabbit.__proto__ = animal;
let animal = {
eats: true
};
+
let rabbit = {
jumps: true
};
@@ -130,6 +124,8 @@ alert(longEar.jumps); // true (из rabbit)

+Теперь, если мы прочтём что-нибудь из `longEar`, и оно будет отсутствовать, JavaScript будет искать его в `rabbit`, а затем в `animal`.
+
Есть только два ограничения:
1. Ссылки не могут идти по кругу. JavaScript выдаст ошибку, если мы попытаемся назначить `__proto__` по кругу.
@@ -137,6 +133,18 @@ alert(longEar.jumps); // true (из rabbit)
Это вполне очевидно, но всё же: может быть только один `[[Prototype]]`. Объект не может наследоваться от двух других объектов.
+```smart header="Свойство `__proto__` — исторически обусловленный геттер/сеттер для `[[Prototype]]`"
+Это распространённая ошибка начинающих разработчиков - не знать разницы между этими двумя понятиями.
+
+Обратите внимание, что `__proto__` — *не то же самое*, что внутреннее свойство `[[Prototype]]`. Это геттер/сеттер для `[[Prototype]]`. Позже мы увидим ситуации, когда это имеет значение, а пока давайте просто будем иметь это в виду, поскольку мы строим наше понимание языка JavaScript.
+
+Свойство `__proto__` немного устарело, оно существует по историческим причинам. Современный JavaScript предполагает, что мы должны использовать функции `Object.getPrototypeOf/Object.setPrototypeOf` вместо того, чтобы получать/устанавливать прототип. Мы также рассмотрим эти функции позже.
+
+По спецификации `__proto__` должен поддерживаться только браузерами, но по факту все среды, включая серверную, поддерживают его. Так что мы вполне безопасно его используем.
+
+Далее мы будем в примерах использовать `__proto__`, так как это самый короткий и интуитивно понятный способ установки и чтения прототипа.
+```
+
## Операция записи не использует прототип
Прототип используется только для чтения свойств.
@@ -170,7 +178,7 @@ rabbit.walk(); // Rabbit! Bounce-bounce!

-Свойства-аксессоры - исключение, так как запись в него обрабатывается функцией-сеттером. То есть, это, фактически, вызов функции.
+Свойства-аксессоры - исключение, так как запись в него обрабатывается функцией-сеттером. То есть это фактически вызов функции.
По этой причине `admin.fullName` работает корректно в приведённом ниже коде:
@@ -308,11 +316,11 @@ for(let prop in rabbit) {

-Заметим ещё одну деталь. Откуда взялся метод `rabbit.hasOwnProperty`? Мы его явно не определяли. Если посмотреть на цепочку прототипов, то видно, что он берётся из `Object.prototype.hasOwnProperty`. То есть, он унаследован.
+Заметим ещё одну деталь. Откуда взялся метод `rabbit.hasOwnProperty`? Мы его явно не определяли. Если посмотреть на цепочку прототипов, то видно, что он берётся из `Object.prototype.hasOwnProperty`. То есть он унаследован.
...Но почему `hasOwnProperty` не появляется в цикле `for..in` в отличие от `eats` и `jumps`? Он ведь перечисляет все унаследованные свойства.
-Ответ простой: оно не перечислимо. То есть, у него внутренний флаг `enumerable` стоит `false`, как и у других свойств `Object.prototype`. Поэтому оно и не появляется в цикле.
+Ответ простой: оно не перечислимо. То есть у него внутренний флаг `enumerable` стоит `false`, как и у других свойств `Object.prototype`. Поэтому оно и не появляется в цикле.
```smart header="Почти все остальные методы получения ключей/значений игнорируют унаследованные свойства"
Почти все остальные методы, получающие ключи/значения, такие как `Object.keys`, `Object.values` и другие - игнорируют унаследованные свойства.
diff --git a/1-js/08-prototypes/02-function-prototype/4-new-object-same-constructor/solution.md b/1-js/08-prototypes/02-function-prototype/4-new-object-same-constructor/solution.md
index ca15792760..561330a679 100644
--- a/1-js/08-prototypes/02-function-prototype/4-new-object-same-constructor/solution.md
+++ b/1-js/08-prototypes/02-function-prototype/4-new-object-same-constructor/solution.md
@@ -39,6 +39,11 @@ alert( user2.name ); // undefined
1. Сначала ищется свойство `constructor` в объекте `user`. Не нашлось.
2. Потом задействуется поиск по цепочке прототипов. Прототип объекта `user` -- это `User.prototype`, и там тоже нет искомого свойства.
-3. Значение `User.prototype` -- это пустой объект `{}`, чей прототип -- `Object.prototype`. `Object.prototype.constructor == Object`. Таким образом, свойство `constructor` всё-таки найдено.
+3. Идя дальше по цепочке, значение `User.prototype` -- это пустой объект `{}`, чей прототип -- встроенный `Object.prototype`.
+4. Наконец, для встроенного `Object.prototype` предусмотрен встроенный `Object.prototype.constructor == Object`. Таким образом, свойство `constructor` всё-таки найдено.
-Наконец срабатывает `let user2 = new Object('Pete')`, но конструктор `Object` игнорирует аргументы, он всегда создаёт пустой объект: `let user2 = {}` -- это как раз то, чему равен `user2` в итоге.
+В итоге срабатывает `let user2 = new Object('Pete')`.
+
+Вероятно, это не то, что нам нужно. Мы хотели создать `new User`, а не `new Object`. Это и есть результат отсутствия конструктора.
+
+(На всякий случай, если вам интересно, вызов `new Object(...)` преобразует свой аргумент в объект. Это теоретическая вещь, на практике никто не вызывает `new Object` со значением, тем более, в основном мы вообще не используем `new Object` для создания объектов).
diff --git a/1-js/08-prototypes/02-function-prototype/article.md b/1-js/08-prototypes/02-function-prototype/article.md
index fd3ab2f6ce..cba991f886 100644
--- a/1-js/08-prototypes/02-function-prototype/article.md
+++ b/1-js/08-prototypes/02-function-prototype/article.md
@@ -40,15 +40,15 @@ alert( rabbit.eats ); // true
На изображении: `"prototype"` - горизонтальная стрелка, обозначающая обычное свойство для `"F"`, а `[[Prototype]]` -- вертикальная, обозначающая наследование `rabbit` от `animal`.
-```smart header="`F.prototype` используется только в момент вызова `new F()`"
-`F.prototype` используется только при вызове `new F()` и присваивается в качестве свойства `[[Prototype]]` нового объекта. После этого `F.prototype` и новый объект ничего не связывает. Следует понимать это как "единоразовый подарок" объекту.
+```smart header="`F.prototype` используется только в момент вызова `new F`"
+`F.prototype` используется только при вызове `new F` и присваивается в качестве свойства `[[Prototype]]` нового объекта.
-После создания `F.prototype` может измениться, и новые объекты, созданные с помощью `new F()`, будут иметь другой объект в качестве `[[Prototype]]`, но уже существующие объекты сохранят старый.
+Если после создания свойство `F.prototype` изменится (`F.prototype = <другой объект>`), то новые объекты, созданные с помощью `new F`, будут иметь в качестве `[[Prototype]]` другой объект, а уже существующие объекты сохранят старый.
```
## F.prototype по умолчанию, свойство constructor
-У каждой функции по умолчанию уже есть свойство `"prototype"`.
+У каждой функции (за исключением стрелочных) по умолчанию уже есть свойство `"prototype"`.
По умолчанию `"prototype"` - объект с единственным свойством `constructor`, которое ссылается на функцию-конструктор.
@@ -160,7 +160,7 @@ Rabbit.prototype = {
Всё достаточно просто. Выделим основные моменты:
-- Свойство `F.prototype` (не путать с `[[Prototype]]`) устанавливает`[[Prototype]]` для новых объектов при вызове `new F()`.
+- Свойство `F.prototype` (не путать с `[[Prototype]]`) устанавливает `[[Prototype]]` для новых объектов при вызове `new F()`.
- Значение `F.prototype` должно быть либо объектом, либо `null`. Другие значения не будут работать.
- Свойство `"prototype"` является особым, только когда оно назначено функции-конструктору, которая вызывается оператором `new`.
diff --git a/1-js/08-prototypes/03-native-prototypes/article.md b/1-js/08-prototypes/03-native-prototypes/article.md
index 035ff13fc7..7e365b3062 100644
--- a/1-js/08-prototypes/03-native-prototypes/article.md
+++ b/1-js/08-prototypes/03-native-prototypes/article.md
@@ -33,7 +33,7 @@ alert( obj ); // "[object Object]" ?
let obj = {};
alert(obj.__proto__ === Object.prototype); // true
-// obj.toString === obj.__proto__.toString == Object.prototype.toString
+// obj.toString === obj.__proto__.toString === Object.prototype.toString
```
Обратите внимание, что по цепочке прототипов выше `Object.prototype` больше нет свойства `[[Prototype]]`:
diff --git a/1-js/08-prototypes/04-prototype-methods/3-compare-calls/task.md b/1-js/08-prototypes/04-prototype-methods/3-compare-calls/task.md
index 235b4d11ab..7e9fc4a414 100644
--- a/1-js/08-prototypes/04-prototype-methods/3-compare-calls/task.md
+++ b/1-js/08-prototypes/04-prototype-methods/3-compare-calls/task.md
@@ -17,7 +17,7 @@ Rabbit.prototype.sayHi = function() {
let rabbit = new Rabbit("Rabbit");
```
-Все эти вызовы делают одно и тоже или нет?
+Все эти вызовы делают одно и то же или нет?
```js
rabbit.sayHi();
diff --git a/1-js/08-prototypes/04-prototype-methods/article.md b/1-js/08-prototypes/04-prototype-methods/article.md
index bd0ee42c16..5403e47e5d 100644
--- a/1-js/08-prototypes/04-prototype-methods/article.md
+++ b/1-js/08-prototypes/04-prototype-methods/article.md
@@ -7,7 +7,7 @@
Современные же методы это:
-- [Object.create(proto, [descriptors])](mdn:js/Object/create) -- создаёт пустой объект со свойством `[[Prototype]]`, указанным как `proto`, и необязательными дескрипторами свойств `descriptors`.
+- [Object.create(proto[, descriptors])](mdn:js/Object/create) -- создаёт пустой объект со свойством `[[Prototype]]`, указанным как `proto`, и необязательными дескрипторами свойств `descriptors`.
- [Object.getPrototypeOf(obj)](mdn:js/Object/getPrototypeOf) -- возвращает свойство `[[Prototype]]` объекта `obj`.
- [Object.setPrototypeOf(obj, proto)](mdn:js/Object/setPrototypeOf) -- устанавливает свойство `[[Prototype]]` объекта `obj` как `proto`.
@@ -156,12 +156,12 @@ alert(obj[key]); // "some value"
let obj = Object.create(null);
*/!*
-alert(obj); // Error (no toString)
+alert(obj); // Ошибка (no toString)
```
...Но обычно это нормально для ассоциативных массивов.
-Обратите внимание, что большая часть методов, связанных с объектами, имеют вид `Object.something(...)`. К примеру, `Object.keys(obj)`. Подобные методы не находятся в прототипе, так что они продолжат работать для таких объектов:
+Обратите внимание, что большинство методов, связанных с объектами, имеют вид `Object.something(...)`. К примеру, `Object.keys(obj)`. Подобные методы не находятся в прототипе, так что они продолжат работать для таких объектов:
```js run
diff --git a/1-js/09-classes/01-class/article.md b/1-js/09-classes/01-class/article.md
index 9407856101..a41d43a601 100644
--- a/1-js/09-classes/01-class/article.md
+++ b/1-js/09-classes/01-class/article.md
@@ -109,7 +109,7 @@ alert(typeof User); // function
alert(User === User.prototype.constructor); // true
// Методы находятся в User.prototype, например:
-alert(User.prototype.sayHi); // alert(this.name);
+alert(User.prototype.sayHi); // sayHi() { alert(this.name); }
// в прототипе ровно 2 метода
alert(Object.getOwnPropertyNames(User.prototype)); // constructor, sayHi
@@ -165,7 +165,7 @@ user.sayHi();
```
2. Методы класса являются неперечислимыми.
- Определение класса устанавливает флаг `enumerable` в` false` для всех методов в `"prototype"`.
+ Определение класса устанавливает флаг `enumerable` в `false` для всех методов в `"prototype"`.
И это хорошо, так как если мы проходимся циклом `for..in` по объекту, то обычно мы не хотим при этом получать методы класса.
diff --git a/1-js/09-classes/02-class-inheritance/2-clock-class-extended/solution.view/extended-clock.js b/1-js/09-classes/02-class-inheritance/2-clock-class-extended/solution.view/extended-clock.js
index ca613ca5e5..be2053cfcf 100644
--- a/1-js/09-classes/02-class-inheritance/2-clock-class-extended/solution.view/extended-clock.js
+++ b/1-js/09-classes/02-class-inheritance/2-clock-class-extended/solution.view/extended-clock.js
@@ -1,7 +1,7 @@
class ExtendedClock extends Clock {
constructor(options) {
super(options);
- let { precision=1000 } = options;
+ let { precision = 1000 } = options;
this.precision = precision;
}
diff --git a/1-js/09-classes/02-class-inheritance/article.md b/1-js/09-classes/02-class-inheritance/article.md
index edbe624ccd..e49e329e70 100644
--- a/1-js/09-classes/02-class-inheritance/article.md
+++ b/1-js/09-classes/02-class-inheritance/article.md
@@ -1,8 +1,13 @@
+
# Наследование классов
-Допустим, у нас есть два класса.
+Наследование классов - это способ расширения одного класса другим классом.
+
+Таким образом, мы можем добавить новый функционал к уже существующему.
+
+## Ключевое слово "extends"
-`Animal`:
+Допустим, у нас есть класс `Animal`:
```js
class Animal {
@@ -16,57 +21,26 @@ class Animal {
}
stop() {
this.speed = 0;
- alert(`${this.name} стоит.`);
+ alert(`${this.name} стоит неподвижно.`);
}
}
let animal = new Animal("Мой питомец");
```
-
-
-...И `Rabbit`:
-
-```js
-class Rabbit {
- constructor(name) {
- this.name = name;
- }
- hide() {
- alert(`${this.name} прячется!`);
- }
-}
-
-let rabbit = new Rabbit("Мой кролик");
-```
-
-
+Вот как мы можем представить объект `animal` и класс `Animal` графически:
-Сейчас они полностью независимы.
+
-Но мы хотим, чтобы `Rabbit` расширял `Animal`. Другими словами, кролики должны происходить от животных, т.е. иметь доступ к методам `Animal` и расширять функциональность `Animal` своими методами.
+...И мы хотели бы создать ещё один `class Rabbit`.
-Для того, чтобы наследовать класс от другого, мы должны использовать ключевое слово `"extends"` и указать название родительского класса перед `{..}`.
+Поскольку кролики - это животные, класс `Rabbit` должен быть основан на `Animal`, и иметь доступ к методам животных, так чтобы кролики могли делать то, что могут делать "общие" животные.
-Ниже `Rabbit` наследует от `Animal`:
+Синтаксис для расширения другого класса следующий: `class Child extends Parent`.
-```js run
-class Animal {
- constructor(name) {
- this.speed = 0;
- this.name = name;
- }
- run(speed) {
- this.speed = speed;
- alert(`${this.name} бежит со скоростью ${this.speed}.`);
- }
- stop() {
- this.speed = 0;
- alert(`${this.name} стоит.`);
- }
-}
+Давайте создадим `class Rabbit`, который наследуется от `Animal`:
-// Наследуем от Animal указывая "extends Animal"
+```js
*!*
class Rabbit extends Animal {
*/!*
@@ -80,24 +54,30 @@ let rabbit = new Rabbit("Белый кролик");
rabbit.run(5); // Белый кролик бежит со скоростью 5.
rabbit.hide(); // Белый кролик прячется!
```
-Теперь код `Rabbit` стал короче, так как используется конструктор класса `Animal` по умолчанию и кролик может использовать метод `run` как и все животные.
-Ключевое слово `extends` работает, используя прототипы. Оно устанавливает `Rabbit.prototype.[[Prototype]]` в `Animal.prototype`. Так что если метод не найден в `Rabbit.prototype`, JavaScript берёт его из `Animal.prototype`.
+Объект класса `Rabbit` имеет доступ как к методам `Rabbit`, таким как `rabbit.hide()`, так и к методам `Animal`, таким как `rabbit.run()`.
+
+Внутри ключевое слово `extends` работает по старой доброй механике прототипов. Оно устанавливает `Rabbit.prototype.[[Prototype]]` в `Animal.prototype`. Таким образом, если метода не оказалось в `Rabbit.prototype`, JavaScript берет его из `Animal.prototype`.

-Как мы помним из главы , в JavaScript используется наследование на прототипах для встроенных объектов. Например `Date.prototype.[[Prototype]]` это `Object.prototype`, поэтому у дат есть универсальные методы объекта.
+Например, чтобы найти метод `rabbit.run`, движок проверяет (снизу вверх на картинке):
+1. Объект `rabbit` (не имеет `run`).
+2. Его прототип, то есть `Rabbit.prototype` (имеет `hide`, но не имеет `run`).
+3. Его прототип, то есть (вследствие `extends`) `Animal.prototype`, в котором, наконец, есть метод `run`.
+
+Как мы помним из главы , сам JavaScript использует наследование на прототипах для встроенных объектов. Например, `Date.prototype.[[Prototype]]` является `Object.prototype`, поэтому у дат есть универсальные методы объекта.
````smart header="После `extends` разрешены любые выражения"
-Синтаксис создания класса допускает указывать после `extends` не только класс, но любое выражение.
+Синтаксис создания класса допускает указывать после `extends` не только класс, но и любое выражение.
Пример вызова функции, которая генерирует родительский класс:
```js run
function f(phrase) {
return class {
- sayHi() { alert(phrase) }
- }
+ sayHi() { alert(phrase); }
+ };
}
*!*
@@ -113,23 +93,25 @@ new User().sayHi(); // Привет
## Переопределение методов
-Давайте пойдём дальше и переопределим метод. Сейчас `Rabbit` наследует от `Animal` метод `stop`, который устанавливает `this.speed = 0`.
+Теперь давайте продвинемся дальше и переопределим метод. По умолчанию все методы, не указанные в классе `Rabbit`, берутся непосредственно "как есть" из класса `Animal`.
-Если мы определим свой метод `stop` в классе `Rabbit`, то он будет использоваться взамен родительского:
+Но если мы укажем в `Rabbit` собственный метод, например `stop()`, то он будет использован вместо него:
```js
class Rabbit extends Animal {
stop() {
- // ...будет использован для rabbit.stop()
+ // ...теперь это будет использоваться для rabbit.stop()
+ // вместо stop() из класса Animal
}
}
```
-...Впрочем, обычно мы не хотим полностью заменить родительский метод, а скорее хотим сделать новый на его основе, изменяя или расширяя его функциональность. Мы делаем что-то в нашем методе и вызываем родительский метод до/после или в процессе.
+Впрочем, обычно мы не хотим полностью заменить родительский метод, а скорее хотим сделать новый на его основе, изменяя или расширяя его функциональность. Мы делаем что-то в нашем методе и вызываем родительский метод до/после или в процессе.
У классов есть ключевое слово `"super"` для таких случаев.
+
- `super.method(...)` вызывает родительский метод.
-- `super(...)` вызывает родительский конструктор (работает только внутри нашего конструктора).
+- `super(...)` для вызова родительского конструктора (работает только внутри нашего конструктора).
Пусть наш кролик автоматически прячется при остановке:
@@ -178,6 +160,7 @@ rabbit.stop(); // Белый кролик стоит. Белый кролик п
Как упоминалось в главе , стрелочные функции не имеют `super`.
При обращении к `super` стрелочной функции он берётся из внешней функции:
+
```js
class Rabbit extends Animal {
stop() {
@@ -247,11 +230,13 @@ let rabbit = new Rabbit("Белый кролик", 10); // Error: this is not de
Упс! При создании кролика - ошибка! Что не так?
-Если коротко, то в классах-потомках конструктор обязан вызывать `super(...)`, и (!) делать это перед использованием `this`.
+Если коротко, то:
+
+- **Конструкторы в наследуемых классах должны обязательно вызывать `super(...)`, и (!) делать это перед использованием `this`.**
...Но почему? Что происходит? Это требование кажется довольно странным.
-Конечно, всему есть объяснение. Давайте углубимся в детали, чтобы вы действительно поняли, что происходит.
+Конечно, всему есть своё объяснение. Давайте углубимся в детали, чтобы вы действительно поняли, что происходит.
В JavaScript существует различие между "функцией-конструктором наследующего класса" и всеми остальными. В наследующем классе соответствующая функция-конструктор помечена специальным внутренним свойством `[[ConstructorKind]]:"derived"`.
@@ -295,12 +280,106 @@ alert(rabbit.earLength); // 10
*/!*
```
+### Переопределение полей класса: тонкое замечание
+
+```warn header="Продвинутое замечание"
+В этом подразделе предполагается, что у вас уже есть определённый опыт работы с классами, возможно, в других языках программирования.
+
+Это даёт лучшее представление о языке, а также объясняет поведение, которое может быть источником ошибок (но не очень часто).
+
+Если вы считаете этот материал слишком трудным для понимания, просто продолжайте читать дальше, а затем вернитесь к нему через некоторое время.
+```
+
+Мы можем переопределять не только методы, но и поля класса.
+
+Однако, когда мы получаем доступ к переопределенному полю в родительском конструкторе, это поведение отличается от большинства других языков программирования.
+
+Рассмотрим этот пример:
+
+```js run
+class Animal {
+ name = 'animal';
+
+ constructor() {
+ alert(this.name); // (*)
+ }
+}
+
+class Rabbit extends Animal {
+ name = 'rabbit';
+}
+
+new Animal(); // animal
+*!*
+new Rabbit(); // animal
+*/!*
+```
+
+Здесь, класс `Rabbit` расширяет `Animal` и переопределяет поле `name` своим собственным значением.
+
+В `Rabbit` нет собственного конструктора, поэтому вызывается конструктор `Animal`.
+
+Что интересно, в обоих случаях: `new Animal()` и `new Rabbit()`, `alert` в строке `(*)` показывает `animal`.
+
+**Другими словами, родительский конструктор всегда использует своё собственное значение поля, а не переопределённое.**
+
+Что же в этом странного?
+
+Если это ещё не ясно, сравните с методами.
+
+Вот тот же код, но вместо поля `this.name`, мы вызываем метод `this.showName()`:
+
+```js run
+class Animal {
+ showName() { // вместо this.name = 'animal'
+ alert('animal');
+ }
+
+ constructor() {
+ this.showName(); // вместо alert(this.name);
+ }
+}
+
+class Rabbit extends Animal {
+ showName() {
+ alert('rabbit');
+ }
+}
+
+new Animal(); // animal
+*!*
+new Rabbit(); // rabbit
+*/!*
+```
+
+Обратите внимание: теперь результат другой.
+
+И это то, чего мы, естественно, ожидаем. Когда родительский конструктор вызывается в производном классе, он использует переопределённый метод.
+
+...Но для полей класса это не так. Как уже было сказано, родительский конструктор всегда использует родительское поле.
+
+Почему же наблюдается разница?
+
+Что ж, причина заключается в порядке инициализации полей. Поле класса инициализируется:
+- Перед конструктором для базового класса (который ничего не расширяет),
+- Сразу после `super()` для производного класса.
+
+В нашем случае `Rabbit` - это производный класс. В нем нет конструктора `constructor()`. Как было сказано ранее, это то же самое, как если бы был пустой конструктор, содержащий только `super(...args)`.
+
+Итак, `new Rabbit()` вызывает `super()`, таким образом, выполняя родительский конструктор, и (согласно правилу для производных классов) только после этого инициализируются поля его класса. На момент выполнения родительского конструктора ещё нет полей класса `Rabbit`, поэтому используются поля `Animal`.
+
+Это тонкое различие между полями и методами характерно для JavaScript.
+
+К счастью, такое поведение проявляется только в том случае, когда переопределенное поле используется в родительском конструкторе. Тогда может быть трудно понять, что происходит, поэтому мы объясняем это здесь.
+
+Если это становится проблемой, её можно решить, используя методы или геттеры/сеттеры вместо полей.
+
## Устройство super, [[HomeObject]]
```warn header="Продвинутая информация"
Если вы читаете учебник первый раз - эту секцию можно пропустить.
-Она рассказывает о внутреннем устройстве наследования и вызов `super`.
+Она рассказывает о внутреннем устройстве наследования и вызовe `super`.
```
Давайте заглянем "под капот" `super`. Здесь есть некоторые интересные моменты.
@@ -316,6 +395,7 @@ alert(rabbit.earLength); // 10
Вы можете пропустить эту часть и перейти ниже к подсекции `[[HomeObject]]`, если не хотите знать детали. Вреда не будет. Или читайте далее, если хотите разобраться.
В примере ниже `rabbit.__proto__ = animal`. Попробуем в `rabbit.eat()` вызвать `animal.eat()`, используя `this.__proto__`:
+
```js run
let animal = {
name: "Animal",
@@ -417,7 +497,6 @@ longEar.eat(); // Error: Maximum call stack size exceeded
Давайте посмотрим, как это работает - опять же, используя простые объекты:
-
```js run
let animal = {
name: "Животное",
@@ -463,7 +542,7 @@ longEar.eat(); // Длинноух ест.
```js run
let animal = {
sayHi() {
- console.log("Я животное");
+ alert("Я животное");
}
};
@@ -477,7 +556,7 @@ let rabbit = {
let plant = {
sayHi() {
- console.log("Я растение");
+ alert("Я растение");
}
};
@@ -547,4 +626,4 @@ rabbit.eat(); // Ошибка вызова super (потому что нет [[
- Поэтому копировать метод, использующий `super`, между разными объектами небезопасно.
Также:
-- У функций-стрелок нет своего `this` и `super`, поэтому они "прозрачно" встраиваются во внешний контекст.
+- У стрелочных функций нет своего `this` и `super`, поэтому они "прозрачно" встраиваются во внешний контекст.
diff --git a/1-js/09-classes/03-static-properties-methods/1-class-extend-object/solution.md b/1-js/09-classes/03-static-properties-methods/1-class-extend-object/solution.md
index ad7402930c..3c1a42f600 100644
--- a/1-js/09-classes/03-static-properties-methods/1-class-extend-object/solution.md
+++ b/1-js/09-classes/03-static-properties-methods/1-class-extend-object/solution.md
@@ -28,7 +28,7 @@ alert( rabbit.hasOwnProperty('name') ); // true
1. Между `"prototype"` функций-конструкторов (для методов)
2. Между самими функциями-конструкторами (для статических методов).
-В нашем случае, для `class Rabbit extends Object` это значит:
+В случае с `class Rabbit extends Object` это значит:
```js run
class Rabbit extends Object {}
@@ -61,13 +61,13 @@ alert( Rabbit.__proto__ === Function.prototype ); // как у каждой фу
*!*
// ошибка - нет такой функции у Rabbit
-alert( Rabbit.getOwnPropertyNames({a: 1, b: 2}) ); // Error
+alert( Rabbit.getOwnPropertyNames({a: 1, b: 2}) ); // Ошибка
*/!*
```
Таким образом, в этом случае у `Rabbit` нет доступа к статическим методам `Object`.
-Кстати, у `Function.prototype` есть "общие" методы, такие как `call`, `bind` и т. д. Они в конечном итоге доступны в обоих случаях, потому что для встроенного конструктора `Object` `Object.__proto__ === Function.prototype`.
+Кстати, у `Function.prototype` также есть "общие" методы, такие как `call`, `bind` и т. д. Они в конечном итоге доступны в обоих случаях, потому что для встроенного конструктора `Object` `Object.__proto__ === Function.prototype`.
Пример на картинке:
diff --git a/1-js/09-classes/03-static-properties-methods/1-class-extend-object/task.md b/1-js/09-classes/03-static-properties-methods/1-class-extend-object/task.md
index 407b04dafe..55586e055a 100644
--- a/1-js/09-classes/03-static-properties-methods/1-class-extend-object/task.md
+++ b/1-js/09-classes/03-static-properties-methods/1-class-extend-object/task.md
@@ -1,4 +1,4 @@
-importance: 5
+importance: 3
---
diff --git a/1-js/09-classes/03-static-properties-methods/article.md b/1-js/09-classes/03-static-properties-methods/article.md
index 56b26311f4..68b7ce6395 100644
--- a/1-js/09-classes/03-static-properties-methods/article.md
+++ b/1-js/09-classes/03-static-properties-methods/article.md
@@ -1,9 +1,9 @@
# Статические свойства и методы
-Мы также можем присвоить метод самой функции-классу, а не её `"prototype"`. Такие методы называются *статическими*.
+Мы также можем присвоить метод самому классу. Такие методы называются *статическими*.
-В классе такие методы обозначаются ключевым словом `static`, например:
+В объявление класса они добавляются с помощью ключевого слова `static`, например:
```js run
class User {
@@ -29,9 +29,13 @@ User.staticMethod = function() {
Значением `this` при вызове `User.staticMethod()` является сам конструктор класса `User` (правило "объект до точки").
-Обычно статические методы используются для реализации функций, принадлежащих классу, но не к каким-то конкретным его объектам.
+Обычно статические методы используются для реализации функций, которые будут принадлежать классу в целом, но не какому-либо его конкретному объекту.
-Например, есть объекты статей `Article`, и нужна функция для их сравнения. Естественное решение - сделать для этого метод `Article.compare`:
+Звучит не очень понятно? Сейчас все встанет на свои места.
+
+Например, есть объекты статей `Article`, и нужна функция для их сравнения.
+
+Естественное решение – сделать для этого статический метод `Article.compare`:
```js run
class Article {
@@ -61,9 +65,11 @@ articles.sort(Article.compare);
alert( articles[0].title ); // CSS
```
-Здесь метод `Article.compare` стоит "над" статьями, как способ их сравнения. Это метод не отдельной статьи, а всего класса.
+Здесь метод `Article.compare` стоит "над" статьями, как средство для их сравнения. Это метод не отдельной статьи, а всего класса.
+
+Другим примером может быть так называемый "фабричный" метод.
-Другим примером может быть так называемый "фабричный" метод. Представим, что нам нужно создавать статьи различными способами:
+Скажем, нам нужно несколько способов создания статьи:
1. Создание через заданные параметры (`title`, `date` и т. д.).
2. Создание пустой статьи с сегодняшней датой.
@@ -99,13 +105,22 @@ alert( article.title ); // Сегодняшний дайджест
```js
// предположим, что Article - это специальный класс для управления статьями
-// статический метод для удаления статьи:
+// статический метод для удаления статьи по id:
Article.remove({id: 12345});
```
-## Статические свойства
+````warn header="Статические методы недоступны для отдельных объектов"
+Статические методы могут вызываться для классов, но не для отдельных объектов.
+
+Например. такой код не будет работать:
-[recent browser=Chrome]
+```js
+// ...
+article.createTodays(); /// Error: article.createTodays is not a function
+```
+````
+
+## Статические свойства
Статические свойства также возможны, они выглядят как свойства класса, но с `static` в начале:
@@ -123,7 +138,7 @@ alert( Article.publisher ); // Илья Кантор
Article.publisher = "Илья Кантор";
```
-## Наследование статических свойств и методов
+## Наследование статических свойств и методов [#statics-and-inheritance]
Статические свойства и методы наследуются.
diff --git a/1-js/09-classes/04-private-protected-properties-methods/article.md b/1-js/09-classes/04-private-protected-properties-methods/article.md
index 4a3694b9e4..9add309bc9 100644
--- a/1-js/09-classes/04-private-protected-properties-methods/article.md
+++ b/1-js/09-classes/04-private-protected-properties-methods/article.md
@@ -178,7 +178,7 @@ new CoffeeMachine().setWaterAmount(100);
```smart header="Защищённые поля наследуются"
Если мы унаследуем `class MegaMachine extends CoffeeMachine`, ничто не помешает нам обращаться к `this._waterAmount` или `this._power` из методов нового класса.
-Таким образом защищённые методы, конечно же, наследуются. В отличие от приватных полей, в чём мы убедимся ниже.
+Таким образом, защищённые поля, конечно же, наследуются. В отличие от приватных полей, в чём мы убедимся ниже.
```
## Приватное свойство "#waterLimit"
@@ -208,7 +208,7 @@ class CoffeeMachine {
let coffeeMachine = new CoffeeMachine();
*!*
-// снаружи нет доступа к приватным методам класса
+// снаружи нет доступа к приватным методам класса
coffeeMachine.#checkWater(); // Error
coffeeMachine.#waterLimit = 1000; // Error
*/!*
@@ -260,7 +260,7 @@ class MegaCoffeeMachine extends CoffeeMachine {
````warn
Приватные поля особенные.
-Как мы помним, обычно мы можем получить доступ к полям объекта с помощью this[name]:
+Как мы помним, обычно мы можем получить доступ к полям объекта с помощью `this[name]`:
```js
class User {
diff --git a/1-js/09-classes/06-instanceof/article.md b/1-js/09-classes/06-instanceof/article.md
index c246ed5c22..a9dc6317a4 100644
--- a/1-js/09-classes/06-instanceof/article.md
+++ b/1-js/09-classes/06-instanceof/article.md
@@ -1,6 +1,6 @@
# Проверка класса: "instanceof"
-Оператор `instanceof` позволяет проверить, к какому классу принадлежит объект, с учётом наследования.
+Оператор `instanceof` позволяет проверить, принадлежит ли объект указанному классу, с учётом наследования.
Такая проверка может потребоваться во многих случаях. Здесь мы используем её для создания *полиморфной* функции, которая интерпретирует аргументы по-разному в зависимости от их типа.
@@ -125,7 +125,7 @@ alert( rabbit instanceof Rabbit ); // false
## Бонус: Object.prototype.toString возвращает тип
-Мы уже знаем, что обычные объекты преобразуется к строке как `[object Object]`:
+Мы уже знаем, что обычные объекты преобразуются к строке как `[object Object]`:
```js run
let obj = {};
diff --git a/1-js/09-classes/07-mixins/article.md b/1-js/09-classes/07-mixins/article.md
index f5afa3ee14..e92a1aae8a 100644
--- a/1-js/09-classes/07-mixins/article.md
+++ b/1-js/09-classes/07-mixins/article.md
@@ -69,7 +69,7 @@ let sayMixin = {
};
let sayHiMixin = {
- __proto__: sayMixin, // (или мы можем использовать Object.create для задания прототипа)
+ __proto__: sayMixin, // (или мы можем использовать Object.setPrototypeOf для задания прототипа)
sayHi() {
*!*
@@ -97,19 +97,19 @@ new User("Вася").sayHi(); // Привет, Вася!
Обратим внимание, что при вызове родительского метода `super.say()` из `sayHiMixin` (строки, помеченные `(*)`) этот метод ищется в прототипе самой примеси, а не класса.
-Вот диаграмма (см правую часть):
+Вот диаграмма (см. правую часть):

Это связано с тем, что методы `sayHi` и `sayBye` были изначально созданы в объекте `sayHiMixin`. Несмотря на то, что они скопированы, их внутреннее свойство `[[HomeObject]]` ссылается на `sayHiMixin`, как показано на картинке выше.
-Так как `super` ищет родительские методы в `[[HomeObject]].[[Prototype]]`, это означает `sayHiMixin.[[Prototype]]`, а не `User.[[Prototype]]`.
+Так как `super` ищет родительские методы в `[[HomeObject]].[[Prototype]]`, это означает, что он ищет `sayHiMixin.[[Prototype]]`.
## EventMixin
Многие объекты в браузерной разработке (и не только) обладают важной способностью - они могут генерировать события. События - отличный способ передачи информации всем, кто в ней заинтересован. Давайте создадим примесь, которая позволит легко добавлять функциональность по работе с событиями любым классам/объектам.
-- Примесь добавит метод `.trigger(name, [data])` для генерации события. Аргумент `name` - это имя события, за которым могут следовать другие аргументы с данными для события.
+- Примесь добавит метод `.trigger(name, [...data])` для генерации события. Аргумент `name` - это имя события, за которым могут следовать дополнительные аргументы с данными для события.
- Также будет добавлен метод `.on(name, handler)`, который назначает обработчик для события с заданным именем. Обработчик будет вызван, когда произойдёт событие с указанным именем `name`, и получит данные из `.trigger`.
- ...и метод `.off(name, handler)`, который удаляет обработчик указанного события.
@@ -138,7 +138,7 @@ let eventMixin = {
* menu.off('select', handler)
*/
off(eventName, handler) {
- let handlers = this._eventHandlers && this._eventHandlers[eventName];
+ let handlers = this._eventHandlers?.[eventName];
if (!handlers) return;
for (let i = 0; i < handlers.length; i++) {
if (handlers[i] === handler) {
@@ -152,7 +152,7 @@ let eventMixin = {
* this.trigger('select', data1, data2);
*/
trigger(eventName, ...args) {
- if (!this._eventHandlers || !this._eventHandlers[eventName]) {
+ if (!this._eventHandlers?.[eventName]) {
return; // обработчиков для этого события нет
}
@@ -164,11 +164,13 @@ let eventMixin = {
Итак, у нас есть 3 метода:
-1. `.on(eventName, handler)` -- назначает функцию `handler`, чтобы обработать событие с заданным именем. Обработчики хранятся в свойстве `_eventHandlers`, представляющим собой объект, в котором имя события является ключом, а массив обработчиков - значением.
+1. `.on(eventName, handler)` -- назначает функцию `handler`, чтобы обработать событие с заданным именем.
-2. `.off(eventName, handler)` -- убирает функцию из списка обработчиков.
+ Технически существует свойство `_eventHandlers`, в котором хранится массив обработчиков для каждого имени события, и оно просто добавляет это событие в список.
-3. `.trigger(eventName, ...args)` -- генерирует событие: все назначенные обработчики из `_eventHandlers[eventName]` вызываются, и `...args` передаются им в качестве аргументов.
+3. `.off(eventName, handler)` -- убирает функцию из списка обработчиков.
+
+4. `.trigger(eventName, ...args)` -- генерирует событие: все назначенные обработчики из `_eventHandlers[eventName]` вызываются, и `...args` передаются им в качестве аргументов.
Использование:
@@ -184,7 +186,7 @@ Object.assign(Menu.prototype, eventMixin);
let menu = new Menu();
-// Добавить обработчик, который будет вызван при событии "select":
+// Добавим обработчик, который будет вызван при событии "select":
*!*
menu.on("select", value => alert(`Выбранное значение: ${value}`));
*/!*
diff --git a/1-js/10-error-handling/1-try-catch/article.md b/1-js/10-error-handling/1-try-catch/article.md
index 669356eb3c..5a9e089544 100644
--- a/1-js/10-error-handling/1-try-catch/article.md
+++ b/1-js/10-error-handling/1-try-catch/article.md
@@ -283,22 +283,22 @@ let error = new ReferenceError(message);
Например:
```js run
-let error = new Error(" Ого, ошибка! o_O");
+let error = new Error("Ого, ошибка! o_O");
alert(error.name); // Error
-alert(error.message); // Ого, ошибка! o_O
+alert(error.message); // Ого, ошибка! o_O
```
Давайте посмотрим, какую ошибку генерирует `JSON.parse`:
```js run
try {
- JSON.parse("{ bad json o_O }");
+ JSON.parse("{ некорректный json o_O }");
} catch(e) {
*!*
alert(e.name); // SyntaxError
*/!*
- alert(e.message); // Unexpected token b in JSON at position 2
+ alert(e.message); // Expected property name or '}' in JSON at position 2 (line 1 column 3)
}
```
@@ -628,7 +628,7 @@ window.onerror = function(message, url, line, col, error) {
Роль глобального обработчика `window.onerror` обычно заключается не в восстановлении выполнения скрипта -- это скорее всего невозможно в случае программной ошибки, а в отправке сообщения об ошибке разработчикам.
-Существуют также веб-сервисы, которые предоставляют логирование ошибок для таких случаев, такие как или .
+Существуют также веб-сервисы, которые предоставляют логирование ошибок для таких случаев, такие как .
Они работают так:
diff --git a/1-js/10-error-handling/2-custom-errors/article.md b/1-js/10-error-handling/2-custom-errors/article.md
index 2cbc1b2d29..fbc3b9dc35 100644
--- a/1-js/10-error-handling/2-custom-errors/article.md
+++ b/1-js/10-error-handling/2-custom-errors/article.md
@@ -40,7 +40,7 @@ class Error {
Теперь давайте унаследуем от него `ValidationError` и попробуем новый класс в действии:
-```js run untrusted
+```js run
*!*
class ValidationError extends Error {
*/!*
@@ -119,7 +119,7 @@ try {
// вместо (err instanceof SyntaxError)
} else if (err.name == "SyntaxError") { // (*)
// ...
-```
+```
Версия с `instanceof` гораздо лучше, потому что в будущем мы собираемся расширить `ValidationError`, сделав его подтипы, такие как `PropertyRequiredError`. И проверка `instanceof` продолжит работать для новых наследованных классов. Так что это на будущее.
diff --git a/1-js/11-async/01-callbacks/01-animate-circle-callback/task.md b/1-js/11-async/01-callbacks/01-animate-circle-callback/task.md
deleted file mode 100644
index c50d632368..0000000000
--- a/1-js/11-async/01-callbacks/01-animate-circle-callback/task.md
+++ /dev/null
@@ -1,25 +0,0 @@
-
-# Анимация круга с помощью колбэка
-
-В задаче находится код для анимации появления круга.
-
-Давайте представим, что теперь нам нужен не просто круг, а круг с сообщением внутри. И сообщение должно появляться *после* анимации (когда круг достигнет своих размеров), иначе это будет некрасиво.
-
-В том решении функция `showCircle(cx, cy, radius)` рисовала круг, но способа узнать, что всё нарисовано, не было.
-
-Поэтому добавим в параметры колбэк: `showCircle(cx, cy, radius, callback)`, который выполним, когда анимация будет завершена. Функция `callback` будет добавлять в наш круг `
@@ -399,7 +399,7 @@ alert( formatter.format(1234567890.123) ); // 1 230 000 000
С опциями для валюты:
-```js
+```js run
let formatter = new Intl.NumberFormat("ru", {
style: "currency",
currency: "GBP"
@@ -410,7 +410,7 @@ alert( formatter.format(1234.5) ); // 1 234,5 £
С двумя цифрами после запятой:
-```js
+```js run
let formatter = new Intl.NumberFormat("ru", {
style: "currency",
currency: "GBP",
diff --git a/1-js/99-js-misc/09-weakref-finalizationregistry/article.md b/1-js/99-js-misc/09-weakref-finalizationregistry/article.md
new file mode 100644
index 0000000000..6a7d9ba40f
--- /dev/null
+++ b/1-js/99-js-misc/09-weakref-finalizationregistry/article.md
@@ -0,0 +1,431 @@
+
+# WeakRef и FinalizationRegistry
+
+```warn header="\"Скрытые\" возможности языка"
+В этой статье рассматривается очень узконаправленная тема, с которой большинство разработчиков на практике сталкиваются чрезвычайно редко (а могут и вообще не знать о её существовании).
+
+Мы рекомендуем пропустить эту главу, если вы только начали изучение JavaScript.
+```
+
+Вспоминая основную концепцию *принципа достижимости* из главы , мы можем отметить, что движок JavaScript гарантированно хранит в памяти значения, которые доступны или используются.
+
+Например:
+
+```js
+// в переменной user находится сильная ссылка на объект
+let user = { name: "John" };
+
+// перезапишем значение переменной user
+user = null;
+
+// ссылка теряется и объект будет удалён из памяти
+```
+
+Или же похожий, но немного усложнённый код с двумя сильными ссылками:
+
+```js
+// в переменной user находится сильная ссылка на объект
+let user = { name: "John" };
+
+// скопировали сильную ссылку на объект в переменную admin
+*!*
+let admin = user;
+*/!*
+
+// перезапишем значение переменной user
+user = null;
+
+// объект всё ещё доступен через переменную admin
+```
+Объект `{ name: "John" }` удалился бы из памяти только в случае отсутствия сильных ссылок на него (если бы мы также перезаписали значение переменной `admin`).
+
+В JavaScript существует концепция под названием `WeakRef`, которая ведёт себя немного иначе в этом случае.
+
+````smart header="Термины: \"Сильная ссылка\", \"Слабая ссылка\""
+**Сильная ссылка** - это ссылка на объект или значение, которая предотвращает их удаление сборщиком мусора. При этом, сохраняя объект или значение в памяти, на которые она указывает.
+
+Это означает, что объект или значение остаются в памяти и не удаляются сборщиком мусора до тех пор, пока на них есть активные сильные ссылки.
+
+В JavaScript стандартные ссылки на объекты являются *сильными* ссылками. Например:
+```js
+// переменная user содержит сильную ссылку на этот объект.
+let user = { name: "John" };
+```
+
+**Слабая ссылка** - это ссылка на объект или значение, которая *не* предотвращает их удаление сборщиком мусора. Объект или значение могут быть удалены сборщиком мусора в случае, если на них существуют только слабые ссылки.
+
+````
+
+## WeakRef
+
+````warn header="Предостережение"
+
+Прежде чем мы перейдём к изучению, стоит отметить, что правильное применение структур, о которых пойдёт речь в этой статье, требует очень тщательного обдумывания, и по возможности их использования лучше избегать.
+
+````
+
+`WeakRef` - это объект, содержащий слабую ссылку на другой объект, называемый `target` или `referent`.
+
+Особенность `WeakRef` заключается в том, что он не препятствует сборщику мусора удалять свой объект-референт. Другими словами, он просто не удерживает его "в живых".
+
+Теперь давайте возьмём переменную `user` в качестве "референта" и создадим слабую ссылку от неё к переменной `admin`. Чтобы создать слабую ссылку, необходимо использовать конструктор `WeakRef`, передав целевой объект (объект, на который вы хотите создать слабую ссылку).
+
+В нашем случае — это переменная `user`:
+
+```js
+// в переменной user находится сильная ссылка на объект
+let user = { name: "John" };
+
+// в переменной admin находится слабая ссылка на объект
+*!*
+let admin = new WeakRef(user);
+*/!*
+```
+
+На схеме ниже изображены два типа ссылок: сильная ссылка с использованием переменной `user` и слабая ссылка с использованием переменной `admin`:
+
+
+
+Затем, в какой-то момент, мы перестаём использовать переменную `user` - она перезаписывается, выходит из области видимости и т.д., при этом сохраняя экземпляр `WeakRef` в переменной `admin`:
+
+```js
+// перезапишем значение переменной user
+user = null;
+```
+
+Слабой ссылки на объект недостаточно, чтобы сохранить его "в живых". Когда единственными оставшимися ссылками на объект-референт являются слабые ссылки, сборщик мусора вправе уничтожить этот объект и использовать его память для чего-то другого.
+
+Однако до тех пор, пока объект фактически не уничтожен, слабая ссылка может вернуть его, даже если на данный объект больше нет сильных ссылок. То есть наш объект становится своеобразным "[котом Шрёдингера](https://ru.wikipedia.org/wiki/Кот_Шрёдингера)" - мы не можем знать точно, "жив" он или "мёртв":
+
+
+
+На этом этапе, чтобы получить объект из экземпляра `WeakRef`, мы воспользуемся его методом `deref()`.
+
+Метод `deref()` возвращает объект-референт, на который ссылается `WeakRef`, в случае, если объект всё ещё находится в памяти. Если объект был удалён сборщиком мусора, - метод `deref()` вернёт `undefined`:
+
+```js
+let ref = admin.deref();
+
+if (ref) {
+ // объект всё ещё доступен: можем произвести какие-либо манипуляции с ним
+} else {
+ // объект был удалён сборщиком мусора
+}
+```
+
+## Варианты использования WeakRef
+
+`WeakRef` обычно используется для создания кешей или
+[ассоциативных массивов](https://ru.wikipedia.org/wiki/Ассоциативный_массив), в которых хранятся ресурсоёмкие объекты. Это позволяет избежать предотвращения удаления этих объектов сборщиком мусора только на основе их присутствия в кеше или ассоциативном массиве.
+
+Один из основных примеров - это ситуация, когда у нас есть большое количество объектов бинарных изображений (например, представленных в виде `ArrayBuffer` или `Blob`),
+и мы хотим связать имя или путь с каждым изображением. Существующие структуры данных не совсем подходят для этих целей:
+
+- Использование `Map` для создания ассоциаций между именами и изображениями, или наоборот, сохранит объекты изображений в памяти,
+ поскольку они фигурируют в `Map` в качестве ключей или значений.
+- `WeakMap` также не подойдёт в этом случае: из-за того, что объекты, представленные в качестве ключей `WeakMap` используют слабые ссылки, и не защищены от удаления сборщиком мусора.
+
+Но, в данной ситуации нам нужна структура данных, которая бы использовала слабые ссылки в своих значениях.
+
+Для этого мы можем использовать коллекцию `Map`, значениями которой являются экземпляры `WeakRef`, ссылающиеся на нужные нам большие объекты. Следовательно, мы не будем хранить в памяти эти большие и ненужные объекты дольше, чем требуется.
+
+В противном случае это способ получить объект изображения из кеша, если он всё ещё доступен.
+Если же он был удалён сборщиком мусора, мы сгенерируем или скачаем его заново.
+
+Таким образом, в некоторых ситуациях используется меньше памяти.
+
+## Пример №1: применение WeakRef для кеширования
+
+Ниже находится фрагмент кода, который демонстрирует технику использования `WeakRef`.
+
+Говоря кратко, мы используем `Map` со строковыми ключами и объектами `WeakRef` в качестве их значений. Если объект `WeakRef` не был удалён сборщиком мусора, мы берём его из кеша. В противном случае мы скачиваем его заново и помещаем в кеш для возможности повторного использования в будущем:
+
+```js
+function fetchImg() {
+ // абстрактная функция для загрузки изображений...
+}
+
+function weakRefCache(fetchImg) { // (1)
+ const imgCache = new Map(); // (2)
+
+ return (imgName) => { // (3)
+ const cachedImg = imgCache.get(imgName); // (4)
+
+ if (cachedImg?.deref()) { // (5)
+ return cachedImg?.deref();
+ }
+
+ const newImg = fetchImg(imgName); // (6)
+ imgCache.set(imgName, new WeakRef(newImg)); // (7)
+
+ return newImg;
+ };
+}
+
+const getCachedImg = weakRefCache(fetchImg);
+```
+
+Давайте подробно разберём всё, что тут произошло:
+
+1. `weakRefCache` - функция высшего порядка, которая принимает другую функцию `fetchImg` в качестве аргумента. В данном примере мы можем пренебречь подробным описанием функции `fetchImg`, так как это может быть абсолютно любая логика скачивания изображений.
+2. `imgCache` - кеш изображений, который хранит кешированные результаты функции `fetchImg`, в виде строковых ключей (имя изображения) и объектов `WeakRef` в качестве их значений.
+3. Возвращаем анонимную функцию, которая принимает имя изображения в качестве аргумента. Данный аргумент будет использоваться в качестве ключа для кешированного изображения.
+4. Пытаемся получить кешированный результат из кеша, используя предоставленный ключ (имя изображения).
+5. Если кеш содержит значение по указанному ключу, и объект `WeakRef` не был удалён сборщиком мусора, возвращаем кешированный результат.
+6. Если в кеше нет записи с запрошенным ключом, либо метод `deref()` возвращает `undefined` (что означает, что объект `WeakRef` был удалён сборщиком мусора), функция `fetchImg` скачивает изображение заново.
+7. Помещаем скачанное изображение в кеш в виде `WeakRef` объекта.
+
+
+Теперь у нас есть коллекция `Map`, в которой ключи - это имена изображений в виде строк, а значения - это объекты `WeakRef`, содержащие сами изображения.
+
+Эта техника помогает избежать выделения большого объёма памяти на ресурсоёмкие объекты, которые больше никто не использует. Также она экономит память и время в случае повторного использования кешированных объектов.
+
+Вот визуальное представление того, как выглядит этот код:
+
+
+
+Но, у данной реализации есть свои недостатки: со временем `Map` будет заполняться строками в качестве ключей, которые указывают на `WeakRef`, чей объект-референт уже был удалён сборщиком мусора:
+
+
+
+Один из способов справиться с этой проблемой - это периодически проверять кеш и удалять "мёртвые" записи. Другой способ - использовать финализаторы, с которыми мы ознакомимся далее.
+
+## Пример №2: применение WeakRef для отслеживания объектов DOM
+
+Ещё один вариант использования `WeakRef` – отслеживание объектов DOM.
+
+Давайте представим ситуацию, когда какой-либо сторонний код или библиотека работают с элементами на нашей странице до тех пор, пока они существуют в DOM. Например, это может быть сторонняя утилита для мониторинга и оповещений о состоянии системы (так называемый "логгер" - программа, которая присылает информационные сообщения, называемые "логами").
+
+Интерактивный пример:
+
+[codetabs height=420 src="weakref-dom"]
+
+При нажатии на кнопку "Начать отправку сообщений", в так называемом "окне отображения логов" (элемент с классом `.window__body`) начинают появляться надписи (логи).
+
+Но, как только этот элемент удалится из DOM, логгер должен перестать присылать сообщения. Чтобы воспроизвести удаление данного элемента, достаточно лишь нажать на кнопку "Закрыть" в правом верхнем углу.
+
+Для того, чтобы нам не усложнять работу, и не уведомлять сторонний код каждый раз, когда наш DOM-элемент доступен, а когда - нет, достаточно будет создать на него слабую ссылку с помощью `WeakRef`.
+
+После того как элемент будет удалён из DOM, логгер это увидит и перестанет присылать сообщения.
+
+Теперь давайте рассмотрим исходный код детальнее (вкладка `index.js`):
+
+1. Получаем DOM-элемент кнопки "Начать отправку сообщений".
+2. Получаем DOM-элемент кнопки "Закрыть".
+3. Получаем DOM-элемент окна отображения логов с использованием конструктора `new WeakRef()`. Таким образом переменная `windowElementRef` хранит слабую ссылку на DOM-элемент.
+4. Добавляем обработчик событий на кнопку "Начать отправку сообщений", отвечающий за запуск логгера по нажатию.
+5. Добавляем обработчик событий на кнопку "Закрыть", отвечающий за закрытие окна отображения логов по нажатию.
+6. С помощью `setInterval` запускаем отображение нового сообщения каждую секунду.
+7. Если DOM-элемент окна отображения логов всё ещё доступен и находится в памяти, создаём и отправляем новое сообщение.
+8. Если метод `deref()` возвращает `undefined`, это значит, что DOM-элемент был удалён из памяти. В таком случае логгер прекращает показ сообщений и сбрасывает таймер.
+9. `alert`, который будет вызван после того, как DOM-элемент окна отображения логов удалится из памяти (т.е. после нажатия на кнопку "Закрыть"). **Обратите внимание, что удаление из памяти может произойти не сразу, т.к оно зависит только от внутренних механизмов сборщика мусора.**
+
+ Мы не можем контролировать этот процесс напрямую из кода. Но, несмотря на это, у нас всё ещё есть возможность выполнить принудительную сборку мусора из бразуера.
+
+ В Google Chrome, например, для этого нужно открыть инструменты разработчика (`key:Ctrl` + `key:Shift` + `key:J` на Windows/Linux или `key:Option` + `key:⌘` + `key:J` на macOS), перейти во вкладку "Производительность (Performance)" и нажать на кнопку с иконкой урны - "Собрать мусор (Collect garbage)":
+
+ 
+
+ Данный функционал поддерживается в большинстве современных браузеров. После проделанных действий alert сработает незамедлительно.
+
+## FinalizationRegistry
+
+А теперь пришло время поговорить о финализаторах. Прежде чем мы перейдём дальше, давайте разберёмся с терминологией:
+
+**Колбэк очистки (финализатор)** - это функция, которая выполняется в случае, если объект, зарегистрированный в `FinalizationRegistry`, удаляется из памяти сборщиком мусора.
+
+Его цель - предоставить возможность выполнения дополнительных операций, связанных с объектом, после его окончательного удаления из памяти.
+
+**Реестр** (или `FinalizationRegistry`) - это специальный объект в JavaScript, который управляет регистрацией и отменой регистрации объектов и их колбэков очистки.
+
+Этот механизм позволяет зарегистрировать объект для отслеживания и связать с ним колбэк очистки. По сути, это структура, которая хранит информацию о зарегистрированных объектах и их колбэках очистки, а затем автоматически вызывает эти колбэки при удалении объектов из памяти.
+
+Для создания экземпляра реестра `FinalizationRegistry`, необходимо вызвать его конструктор,
+который принимает единственный аргумент - колбэк очистки (финализатор).
+
+Синтаксис:
+
+```js
+function cleanupCallback(heldValue) {
+ // код колбэка очистки
+}
+
+const registry = new FinalizationRegistry(cleanupCallback);
+```
+
+Здесь:
+
+- `cleanupCallback` - колбэк очистки, который будет автоматически вызван при удалении зарегистрированного объекта из памяти.
+- `heldValue` - значение, которое передаётся в качестве аргумента для колбэка очистки. Если `heldValue` является объектом, реестр сохраняет на него сильную ссылку.
+- `registry` - экземпляр `FinalizationRegistry`.
+
+Методы `FinalizationRegistry`:
+
+- `register(target, heldValue [, unregisterToken])` - используется для регистрации объектов в реестре.
+
+ `target` - регистрируемый для отслеживания объект. Если `target` будет удалён сборщиком мусора, колбэк очистки будет вызван с `heldValue` в качестве аргумента.
+
+ Опциональный `unregisterToken` - токен отмены регистрации. Может быть передан для отмены регистрации до удаления объекта сборщиком мусора. Обычно в качестве `unregisterToken` используется объект `target`, что является стандартной практикой.
+- `unregister(unregisterToken)` - метод `unregister` используется для отмены регистрации объекта в реестре. Он принимает один аргумент - `unregisterToken` (токен отмены регистрации, который был получен при регистрации объекта).
+
+Теперь перейдём к простому примеру. Воспользуемся уже известным нам объектом `user` и создадим экземпляр `FinalizationRegistry`:
+
+```js
+let user = { name: "John" };
+
+const registry = new FinalizationRegistry((heldValue) => {
+ console.log(`${heldValue} был собран сборщиком мусора.`);
+});
+```
+
+Затем зарегистрируем объект, для которого требуется колбэк очистки, вызвав метод `register`:
+
+```js
+registry.register(user, user.name);
+```
+
+Реестр не хранит сильную ссылку на регистрируемый объект, так как это бы противоречило его предназначению. Если бы реестр сохранял сильную ссылку, то объект никогда бы не был очищен сборщиком мусора.
+
+Если же объект удаляется сборщиком мусора, наш колбэк очистки может быть вызван в какой-то момент в будущем, с переданным ему `heldValue`:
+
+```js
+// Когда объект user удалится сборщиком мусора, в консоль будет выведено сообщение:
+"John был собран сборщиком мусора."
+```
+
+Также существуют ситуации, когда даже в реализациях, где используется колбэк очистки, есть вероятность, что он не будет вызван.
+
+Например:
+- Когда программа полностью завершает свою работу (например, при закрытии вкладки в браузере).
+- Когда сам экземпляр `FinalizationRegistry` больше не доступен для JavaScript кода.
+ Если объект, создающий экземпляр `FinalizationRegistry`, выходит из области видимости или удаляется, то колбэки очистки, зарегистрированные в этом реестре, также могут быть не вызваны.
+
+## Кеширование с FinalizationRegistry
+
+Возвращаясь к нашему примеру *слабого* кеша, мы можем заметить следующее:
+- Несмотря на то, что значения, обёрнутые в `WeakRef`, были собраны сборщиком мусора, всё ещё актуальна проблема "утечки памяти" в виде оставшихся ключей, значения которых были собраны сборщиком мусора.
+
+Вот улучшенный пример кеширования, в котором используется `FinalizationRegistry`:
+
+```js
+function fetchImg() {
+ // абстрактная функция для загрузки изображений...
+}
+
+function weakRefCache(fetchImg) {
+ const imgCache = new Map();
+
+ *!*
+ const registry = new FinalizationRegistry((imgName) => { // (1)
+ const cachedImg = imgCache.get(imgName);
+ if (cachedImg && !cachedImg.deref()) imgCache.delete(imgName);
+ });
+ */!*
+
+ return (imgName) => {
+ const cachedImg = imgCache.get(imgName);
+
+ if (cachedImg?.deref()) {
+ return cachedImg?.deref();
+ }
+
+ const newImg = fetchImg(imgName);
+ imgCache.set(imgName, new WeakRef(newImg));
+ *!*
+ registry.register(newImg, imgName); // (2)
+ */!*
+
+ return newImg;
+ };
+}
+
+const getCachedImg = weakRefCache(fetchImg);
+```
+
+1. Для управления очисткой "мёртвых" записей в кеше, когда связанные с ними объекты `WeakRef` собираются сборщиком мусора, создаём реестр очистки `FinalizationRegistry`.
+
+ Важным моментом здесь является то, что в колбэке очистки должно проверяться, была ли запись удалена сборщиком мусора и не была ли добавлена заново, чтобы не удалить "живую" запись.
+2. После загрузки и установки нового значения (изображения) в кеш, регистрируем его в реестре финализатора для отслеживания объекта `WeakRef`.
+
+Данная реализация содержит только актуальные или "живые" пары ключ/значение.
+В этом случае каждый объект `WeakRef` зарегистрирован в `FinalizationRegistry`.
+А после того, как объекты будут очищены сборщиком мусора, колбэк очистки удалит все значения `undefined`.
+
+Вот визуальное представление обновлённого кода:
+
+
+
+Ключевым аспектом в обновлённой реализации является то, что финализаторы позволяют создавать параллельные процессы между "основной" программой и колбэками очистки. В контексте JavaScript, "основная" программа - это наш JavaScript-код, который запускается и выполняется в нашем приложении или на веб-странице.
+
+Следовательно, с момента, когда объект помечается для удаления сборщиком мусора, до фактического выполнения колбэка очистки, может возникнуть определённый промежуток времени. Важно понимать, что в этом временном интервале основная программа может внести любые изменения в объект или даже вернуть его обратно в память.
+
+Поэтому, в колбэке очистки мы должны проверить, не была ли запись добавлена обратно в кеш основной программой, чтобы избежать удаления "живых" записей. Аналогично, при поиске ключа в кеше существует вероятность того, что значение было удалено сборщиком мусора, но колбэк очистки ещё не был выполнен.
+
+Такие ситуации требуют особого внимания, если вы работаете с `FinalizationRegistry`.
+
+## Использование WeakRef и FinalizationRegistry на практике
+
+Переходя от теории к практике, представьте себе реальный сценарий, когда пользователь синхронизирует свои фотографии на мобильном устройстве
+с каким-либо облачным сервисом (таким как [iCloud](https://ru.wikipedia.org/wiki/ICloud) или [Google Photos](https://ru.wikipedia.org/wiki/Google_Фото)), и хочет просматривать их с других устройств. Подобные сервисы помимо основного функционала просмотра фотографий, предлагают массу дополнительных возможностей, например:
+
+- Редактирование фотографий и видео эффекты.
+- Создание "воспоминаний" и альбомов.
+- Монтаж видео из серии фотографий.
+- ...и многое другое.
+
+В качестве примера здесь мы будем использовать достаточно примитивную реализацию подобного сервиса. Основная суть — показать возможный сценарий совместного использования `WeakRef` и `FinalizationRegistry` в реальной жизни.
+
+Вот как это выглядит:
+
+
+
+В левой части находится облачная библиотека фотографий (они отображаются в виде миниатюр). Мы можем выбрать нужные нам изображения и создать коллаж, нажав на кнопку "Create collage" в правой части страницы. Затем, получившийся результат можно будет скачать в виде изображения.
+
+Для увеличения скорости загрузки страницы разумно будет загружать и показывать миниатюры фотографий именно в *сжатом* качестве. Но, для создания коллажа из выбранных фотографий, загружать и использовать их в *полноразмерном* качестве.
+
+Ниже мы видим, что внутренний размер миниатюр составляет 240×240 пикселей. Размер был выбран специально для увеличения скорости загрузки. Кроме того, нам не нужны полноразмерные фотографии в режиме предпросмотра.
+
+
+
+Предположим, что нам нужно создать коллаж из 4 фотографий: мы выбираем их, после чего нажимаем кнопку "Create collage".
+На этом этапе уже известная нам функция weakRefCache проверяет, есть ли нужное изображение в кеше. Если нет, то скачивает его из облака и помещает в кеш для возможности дальнейшего использования. И так происходит для каждого выбранного изображения:
+
+
+
+Обратив внимание на вывод в консоли можно увидеть, какие из фотографий были загружены из облака - на это указывает FETCHED_IMAGE. Так как это первая попытка создания коллажа, это означает, что на данном этапе "слабый кеш" ещё был пуст, а все фотографии были скачаны из облака и помещены в него.
+
+Но, наряду с процессом загрузки изображений, происходит ещё и процесс очистки памяти сборщиком мусора. Это означает, что хранящийся в кеше объект, на который мы ссылаемся используя слабую ссылку, удаляется сборщиком мусора. И наш финализатор выполняется успешно, тем самым удаляя ключ, по которому изображение хранилось в кеше. Об этом нас уведомляет CLEANED_IMAGE:
+
+
+
+Далее мы понимаем, что нам не нравится получившийся коллаж, и решаем изменить одно из изображений и создать новый. Для этого достаточно снять выделение с ненужного изображения, выбрать другое, и ещё раз нажать на кнопку "Create collage":
+
+
+
+Но, на этот раз не все изображения были скачаны из сети, и одно из них было взято из слабого кеша: об этом нам говорит сообщение CACHED_IMAGE. Это означает, что на момент создания коллажа сборщик мусора ещё не удалил наше изображение, и мы смело взяли его из кеша,
+тем самым сократив количество сетевых запросов и ускорив общее время процесса создания коллажа:
+
+
+
+Давайте ещё немного "поиграем", заменив одно из изображений ещё раз и создав новый коллаж:
+
+
+
+На этот раз результат ещё более внушительный. Из 4 выбранных изображений, 3 из них были взяты из слабого кеша, и только одно пришлось скачать из сети.
+Снижение нагрузки на сеть составило около 75%. Впечатляет, не правда ли?
+
+
+
+Конечно, не следует забывать, что такое поведение не является гарантированным, и зависит от конкретной реализации и работы сборщика мусора.
+
+Исходя из этого, сразу же возникает вполне логичный вопрос: почему бы нам не использовать обычный кеш, где мы можем сами управлять его сущностями, а не полагаться на сборщик мусора? Всё верно, в большинстве случаев нет необходимости использовать `WeakRef` и `FinalizationRegistry`.
+
+Здесь мы просто продемонстрировали альтернативную реализацию подобного функционала, используя нетривиальный подход с интересными особенностями языка. Всё же, мы не можем полагаться на этот пример, если нам необходим постоянный и предсказуемый результат.
+
+Вы можете [открыть данный пример в песочнице](sandbox:weakref-finalizationregistry).
+
+## Итого
+
+`WeakRef` - предназначен для создания слабых ссылок на объекты, что позволяет им быть удалёнными из памяти сборщиком мусора, если на них больше нет сильных ссылок. Это полезно для решения проблемы чрезмерного использования памяти и оптимизации использования системных ресурсов в приложениях.
+
+`FinalizationRegistry` - это средство регистрации колбэков, которые выполняются при уничтожении объектов, на которые больше нет сильных ссылок. Это позволяет освобождать связанные с объектом ресурсы или выполнять другие необходимые операции перед удалением объекта из памяти.
diff --git a/1-js/99-js-misc/09-weakref-finalizationregistry/google-chrome-developer-tools.png b/1-js/99-js-misc/09-weakref-finalizationregistry/google-chrome-developer-tools.png
new file mode 100644
index 0000000000..0216373425
Binary files /dev/null and b/1-js/99-js-misc/09-weakref-finalizationregistry/google-chrome-developer-tools.png differ
diff --git a/1-js/99-js-misc/09-weakref-finalizationregistry/weakref-dom.view/index.css b/1-js/99-js-misc/09-weakref-finalizationregistry/weakref-dom.view/index.css
new file mode 100644
index 0000000000..f6df812d07
--- /dev/null
+++ b/1-js/99-js-misc/09-weakref-finalizationregistry/weakref-dom.view/index.css
@@ -0,0 +1,49 @@
+.app {
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+}
+
+.start-messages {
+ width: fit-content;
+}
+
+.window {
+ width: 100%;
+ border: 2px solid #464154;
+ overflow: hidden;
+}
+
+.window__header {
+ position: sticky;
+ padding: 8px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ background-color: #736e7e;
+}
+
+.window__title {
+ margin: 0;
+ font-size: 24px;
+ font-weight: 700;
+ color: white;
+ letter-spacing: 1px;
+}
+
+.window__button {
+ padding: 4px;
+ background: #4f495c;
+ outline: none;
+ border: 2px solid #464154;
+ color: white;
+ font-size: 16px;
+ cursor: pointer;
+}
+
+.window__body {
+ height: 250px;
+ padding: 16px;
+ overflow: scroll;
+ background-color: #736e7e33;
+}
\ No newline at end of file
diff --git a/1-js/99-js-misc/09-weakref-finalizationregistry/weakref-dom.view/index.html b/1-js/99-js-misc/09-weakref-finalizationregistry/weakref-dom.view/index.html
new file mode 100644
index 0000000000..c4adde9866
--- /dev/null
+++ b/1-js/99-js-misc/09-weakref-finalizationregistry/weakref-dom.view/index.html
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+