+
+А на этом поле функция 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 7f9769a60f..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,29 +4,32 @@ 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()` слишком ресурсоёмкая, чтобы делать это при каждом микродвижении. Да и нет смысла делать обновление чаще, чем один раз в 100 мс.
+...Но функция обновления `update()` слишком ресурсоёмкая, чтобы делать это при каждом микродвижении. Да и нет смысла делать обновление чаще, чем один раз в 1000 мс.
-Поэтому мы обернём вызов в декоратор: будем использовать `throttle(update, 100)` как функцию, которая будет запускаться при каждом перемещении указателя вместо оригинальной `update()`. Декоратор будет вызываться часто, но передавать вызов в `update()` максимум раз в 100 мс.
+Поэтому мы обернём вызов в декоратор: будем использовать `throttle(update, 1000)` как функцию, которая будет запускаться при каждом перемещении указателя вместо оригинальной `update()`. Декоратор будет вызываться часто, но передавать вызов в `update()` максимум раз в 1000 мс.
Визуально это будет выглядеть вот так:
1. Для первого движения указателя декорированный вариант сразу передаёт вызов в `update`. Это важно, т.к. пользователь сразу видит нашу реакцию на его перемещение.
-2. Затем, когда указатель продолжает движение, в течение 100 мс ничего не происходит. Декорированный вариант игнорирует вызовы.
-3. По истечению 100 мс происходит ещё один вызов `update` с последними координатами.
-4. Затем, наконец, указатель где-то останавливается. Декорированный вариант ждёт, пока не истечёт 100 мс, и затем вызывает `update` с последними координатами. В итоге окончательные координаты указателя тоже обработаны.
+2. Затем, когда указатель продолжает движение, в течение 1000 мс ничего не происходит. Декорированный вариант игнорирует вызовы.
+3. По истечению 1000 мс происходит ещё один вызов `update` с последними координатами.
+4. Затем, наконец, указатель где-то останавливается. Декорированный вариант ждёт, пока не истечёт 1000 мс, и затем вызывает `update` с последними координатами. В итоге окончательные координаты указателя тоже обработаны.
Пример кода:
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 f481024e36..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
@@ -1,4 +1,4 @@
-# Декораторы и переадресация вызова, сall/apply
+# Декораторы и переадресация вызова, call/apply
JavaScript предоставляет исключительно гибкие возможности по работе с функциями: они могут быть переданы в другие функции, использованы как объекты, и сейчас мы рассмотрим, как *перенаправлять* вызовы между ними и как их декорировать.
@@ -45,7 +45,7 @@ alert( "Again: " + slow(2) ); // возвращаем из кеша
В коде выше `cachingDecorator` -- это *декоратор*, специальная функция, которая принимает другую функцию и изменяет её поведение.
-Идея состоит в том, что мы можем вызвать `cachingDecorator` с любой функцией, в результате чего мы получим кеширующую обёртку. Это здорово, т.к. у нас может быть множество функций, использующих такой функционал, и всё, что нам нужно сделать - это применить к ним `cachingDecorator`.
+Идея состоит в том, что мы можем вызвать `cachingDecorator` с любой функцией, в результате чего мы получим кеширующую обёртку. Это здорово, т.к. у нас может быть множество функций, использующих такую функциональность, и всё, что нам нужно сделать - это применить к ним `cachingDecorator`.
Отделяя кеширующий код от основного кода, мы также сохраняем чистоту и простоту последнего.
@@ -62,7 +62,7 @@ alert( "Again: " + slow(2) ); // возвращаем из кеша
- При необходимости мы можем объединить несколько декораторов (речь об этом пойдёт позже).
-## Применение "func.call" для передачи контекста.
+## Применение "func.call" для передачи контекста
Упомянутый выше кеширующий декоратор не подходит для работы с методами объектов.
@@ -151,8 +151,8 @@ let user = { name: "John" };
let admin = { name: "Admin" };
// используем 'call' для передачи различных объектов в качестве 'this'
-sayHi.call( user ); // this = John
-sayHi.call( admin ); // this = Admin
+sayHi.call( user ); // John
+sayHi.call( admin ); // Admin
```
Здесь мы используем `call` для вызова `say` с заданным контекстом и фразой:
diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/decorator-makecaching-wrapper.svg b/1-js/06-advanced-functions/09-call-apply-decorators/decorator-makecaching-wrapper.svg
index ccf55d88d5..3d8428b273 100644
--- a/1-js/06-advanced-functions/09-call-apply-decorators/decorator-makecaching-wrapper.svg
+++ b/1-js/06-advanced-functions/09-call-apply-decorators/decorator-makecaching-wrapper.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
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/06-advanced-functions/12-arrow-functions/article.md b/1-js/06-advanced-functions/12-arrow-functions/article.md
index 088c58b287..ab10c47396 100644
--- a/1-js/06-advanced-functions/12-arrow-functions/article.md
+++ b/1-js/06-advanced-functions/12-arrow-functions/article.md
@@ -79,7 +79,7 @@ group.showList();
## Стрелочные функции не имеют "arguments"
-У стрелочных функции также нет переменной `arguments`.
+У стрелочных функций также нет переменной `arguments`.
Это отлично подходит для декораторов, когда нам нужно пробросить вызов с текущими `this` и `arguments`.
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 7eb9378f98..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`) иногда предустановлен для некоторых встроенных объектов и свойств.
-Неконфигурируемое свойство не может быть удалено или изменено с помощью `defineProperty`.
+Неконфигурируемое свойство не может быть удалено, его атрибуты не могут быть изменены.
Например, свойство `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 5388614b82..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() {
@@ -92,12 +94,7 @@ alert(user.name); // Alice
alert(user.surname); // Cooper
```
-В итоге мы получили "виртуальное" свойство `fullName`. Его можно прочитать и изменить, но по факту его не существует.
-
-```smart header="Нет поддержки для `delete`"
-При попытке удалить свойство-аксессор оператором `delete` будет ошибка.
-```
-
+В итоге мы получили "виртуальное" свойство `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 d2390c82ef..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,12 +124,26 @@ alert(longEar.jumps); // true (из rabbit)

+Теперь, если мы прочтём что-нибудь из `longEar`, и оно будет отсутствовать, JavaScript будет искать его в `rabbit`, а затем в `animal`.
+
Есть только два ограничения:
1. Ссылки не могут идти по кругу. JavaScript выдаст ошибку, если мы попытаемся назначить `__proto__` по кругу.
2. Значение `__proto__` может быть объектом или `null`. Другие типы игнорируются.
-Это вполне очевидно, но всё же: может быть только один `[[Prototype]]`. Объект не может наследовать от двух других объектов.
+Это вполне очевидно, но всё же: может быть только один `[[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` и другие - игнорируют унаследованные свойства.
@@ -323,7 +331,7 @@ for(let prop in rabbit) {
## Итого
- В JavaScript все объекты имеют скрытое свойство `[[Prototype]]`, которое является либо другим объектом, либо `null`.
-- Мы можем использовать `obj.__ proto__` для доступа к нему (исторически обусловленный геттер/сеттер, есть другие способы, которые скоро будут рассмотрены).
+- Мы можем использовать `obj.__proto__` для доступа к нему (исторически обусловленный геттер/сеттер, есть другие способы, которые скоро будут рассмотрены).
- Объект, на который ссылается `[[Prototype]]`, называется "прототипом".
- Если мы хотим прочитать свойство `obj` или вызвать метод, которого не существует у `obj`, тогда JavaScript попытается найти его в прототипе.
- Операции записи/удаления работают непосредственно с объектом, они не используют прототип (если это обычное свойство, а не сеттер).
diff --git a/1-js/08-prototypes/01-prototype-inheritance/object-prototype-empty.svg b/1-js/08-prototypes/01-prototype-inheritance/object-prototype-empty.svg
index dc53f56636..982f2e4c17 100644
--- a/1-js/08-prototypes/01-prototype-inheritance/object-prototype-empty.svg
+++ b/1-js/08-prototypes/01-prototype-inheritance/object-prototype-empty.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-chain.svg b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-chain.svg
index 3e81f262b5..4bf580ae77 100644
--- a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-chain.svg
+++ b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-chain.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk-2.svg b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk-2.svg
index 3cf5c4c78e..838c78395b 100644
--- a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk-2.svg
+++ b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk-2.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk-3.svg b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk-3.svg
index acd4206319..e994331dab 100644
--- a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk-3.svg
+++ b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk-3.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk.svg b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk.svg
index ebdef95877..b324710286 100644
--- a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk.svg
+++ b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit.svg b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit.svg
index 735e1f2b36..4f3c1bc0ec 100644
--- a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit.svg
+++ b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/1-js/08-prototypes/01-prototype-inheritance/proto-user-admin.svg b/1-js/08-prototypes/01-prototype-inheritance/proto-user-admin.svg
index 433bc613f9..bf0baf013a 100644
--- a/1-js/08-prototypes/01-prototype-inheritance/proto-user-admin.svg
+++ b/1-js/08-prototypes/01-prototype-inheritance/proto-user-admin.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/1-js/08-prototypes/01-prototype-inheritance/rabbit-animal-object.svg b/1-js/08-prototypes/01-prototype-inheritance/rabbit-animal-object.svg
index d32585b431..32a9858f83 100644
--- a/1-js/08-prototypes/01-prototype-inheritance/rabbit-animal-object.svg
+++ b/1-js/08-prototypes/01-prototype-inheritance/rabbit-animal-object.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
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/02-function-prototype/function-prototype-constructor.svg b/1-js/08-prototypes/02-function-prototype/function-prototype-constructor.svg
index 35cdc61f1d..1fced82b39 100644
--- a/1-js/08-prototypes/02-function-prototype/function-prototype-constructor.svg
+++ b/1-js/08-prototypes/02-function-prototype/function-prototype-constructor.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/1-js/08-prototypes/02-function-prototype/proto-constructor-animal-rabbit.svg b/1-js/08-prototypes/02-function-prototype/proto-constructor-animal-rabbit.svg
index 3489ecdd8c..ede4e1227e 100644
--- a/1-js/08-prototypes/02-function-prototype/proto-constructor-animal-rabbit.svg
+++ b/1-js/08-prototypes/02-function-prototype/proto-constructor-animal-rabbit.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/1-js/08-prototypes/02-function-prototype/rabbit-prototype-constructor.svg b/1-js/08-prototypes/02-function-prototype/rabbit-prototype-constructor.svg
index 3e11f275b0..2424ea648d 100644
--- a/1-js/08-prototypes/02-function-prototype/rabbit-prototype-constructor.svg
+++ b/1-js/08-prototypes/02-function-prototype/rabbit-prototype-constructor.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/1-js/08-prototypes/03-native-prototypes/article.md b/1-js/08-prototypes/03-native-prototypes/article.md
index efad27e4e2..7e365b3062 100644
--- a/1-js/08-prototypes/03-native-prototypes/article.md
+++ b/1-js/08-prototypes/03-native-prototypes/article.md
@@ -2,7 +2,7 @@
Свойство `"prototype"` широко используется внутри самого языка JavaScript. Все встроенные функции-конструкторы используют его.
-Сначала мы рассмотрим детали, а затем используем `"prototype"` для добавления встроенным объектам нового функционала.
+Сначала мы рассмотрим детали, а затем используем `"prototype"` для добавления встроенным объектам новой функциональности.
## Object.prototype
@@ -21,7 +21,7 @@ alert( obj ); // "[object Object]" ?

-Когда вызывается `new Object()` (или создаётся объект с помощью литерала `{...}`), свойство `[[Prototype]]` этого объекта устанавливается на `Object.prototype` по правилам, который мы обсуждали в предыдущей главе:
+Когда вызывается `new Object()` (или создаётся объект с помощью литерала `{...}`), свойство `[[Prototype]]` этого объекта устанавливается на `Object.prototype` по правилам, которые мы обсуждали в предыдущей главе:

@@ -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]]`:
@@ -124,7 +124,7 @@ String.prototype.show = function() {
```warn
Прототипы глобальны, поэтому очень легко могут возникнуть конфликты. Если две библиотеки добавляют метод `String.prototype.show`, то одна из них перепишет метод другой.
-Так что, в общем, изменение встроенных прототипов считается плохой идей.
+Так что, в общем, изменение встроенных прототипов считается плохой идеей.
```
**В современном программировании есть только один случай, в котором одобряется изменение встроенных прототипов. Это создание полифилов.**
diff --git a/1-js/08-prototypes/03-native-prototypes/function-prototype-constructor.svg b/1-js/08-prototypes/03-native-prototypes/function-prototype-constructor.svg
index 35cdc61f1d..1fced82b39 100644
--- a/1-js/08-prototypes/03-native-prototypes/function-prototype-constructor.svg
+++ b/1-js/08-prototypes/03-native-prototypes/function-prototype-constructor.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/1-js/08-prototypes/03-native-prototypes/native-prototypes-array-tostring.svg b/1-js/08-prototypes/03-native-prototypes/native-prototypes-array-tostring.svg
index 770c908c5e..ebb4f32051 100644
--- a/1-js/08-prototypes/03-native-prototypes/native-prototypes-array-tostring.svg
+++ b/1-js/08-prototypes/03-native-prototypes/native-prototypes-array-tostring.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/1-js/08-prototypes/03-native-prototypes/native-prototypes-classes.svg b/1-js/08-prototypes/03-native-prototypes/native-prototypes-classes.svg
index 4989df56ba..4d6129e0a0 100644
--- a/1-js/08-prototypes/03-native-prototypes/native-prototypes-classes.svg
+++ b/1-js/08-prototypes/03-native-prototypes/native-prototypes-classes.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/1-js/08-prototypes/03-native-prototypes/object-prototype-1.svg b/1-js/08-prototypes/03-native-prototypes/object-prototype-1.svg
index 38c33cae16..9630e68e27 100644
--- a/1-js/08-prototypes/03-native-prototypes/object-prototype-1.svg
+++ b/1-js/08-prototypes/03-native-prototypes/object-prototype-1.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/1-js/08-prototypes/03-native-prototypes/object-prototype-null.svg b/1-js/08-prototypes/03-native-prototypes/object-prototype-null.svg
index 858f8317ee..9ccb342299 100644
--- a/1-js/08-prototypes/03-native-prototypes/object-prototype-null.svg
+++ b/1-js/08-prototypes/03-native-prototypes/object-prototype-null.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/1-js/08-prototypes/03-native-prototypes/object-prototype.svg b/1-js/08-prototypes/03-native-prototypes/object-prototype.svg
index 8d3d0beeec..024dd30213 100644
--- a/1-js/08-prototypes/03-native-prototypes/object-prototype.svg
+++ b/1-js/08-prototypes/03-native-prototypes/object-prototype.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/1-js/08-prototypes/03-native-prototypes/proto-constructor-animal-rabbit.svg b/1-js/08-prototypes/03-native-prototypes/proto-constructor-animal-rabbit.svg
index 3489ecdd8c..ede4e1227e 100644
--- a/1-js/08-prototypes/03-native-prototypes/proto-constructor-animal-rabbit.svg
+++ b/1-js/08-prototypes/03-native-prototypes/proto-constructor-animal-rabbit.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/1-js/08-prototypes/03-native-prototypes/rabbit-prototype-constructor.svg b/1-js/08-prototypes/03-native-prototypes/rabbit-prototype-constructor.svg
index 3e11f275b0..2424ea648d 100644
--- a/1-js/08-prototypes/03-native-prototypes/rabbit-prototype-constructor.svg
+++ b/1-js/08-prototypes/03-native-prototypes/rabbit-prototype-constructor.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
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/08-prototypes/04-prototype-methods/object-prototype-2.svg b/1-js/08-prototypes/04-prototype-methods/object-prototype-2.svg
index 86d09ae0de..cf4d3023f8 100644
--- a/1-js/08-prototypes/04-prototype-methods/object-prototype-2.svg
+++ b/1-js/08-prototypes/04-prototype-methods/object-prototype-2.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/1-js/08-prototypes/04-prototype-methods/object-prototype-null.svg b/1-js/08-prototypes/04-prototype-methods/object-prototype-null.svg
index 858f8317ee..9ccb342299 100644
--- a/1-js/08-prototypes/04-prototype-methods/object-prototype-null.svg
+++ b/1-js/08-prototypes/04-prototype-methods/object-prototype-null.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/1-js/09-classes/01-class/article.md b/1-js/09-classes/01-class/article.md
index db6ebb6de0..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
@@ -142,7 +142,7 @@ user.sayHi();
Однако есть важные отличия:
-1. Во-первых, функция, созданная с помощью `class`, помечена специальным внутренним свойством `[[FunctionKind]]:"classConstructor"`. Поэтому это не совсем то же самое, что создавать её вручную.
+1. Во-первых, функция, созданная с помощью `class`, помечена специальным внутренним свойством `[[IsClassConstructor]]: true`. Поэтому это не совсем то же самое, что создавать её вручную.
В отличие от обычных функций, конструктор класса не может быть вызван без `new`:
@@ -165,14 +165,14 @@ user.sayHi();
```
2. Методы класса являются неперечислимыми.
- Определение класса устанавливает флаг `enumerable` в` false` для всех методов в `"prototype"`.
+ Определение класса устанавливает флаг `enumerable` в `false` для всех методов в `"prototype"`.
И это хорошо, так как если мы проходимся циклом `for..in` по объекту, то обычно мы не хотим при этом получать методы класса.
3. Классы всегда используют `use strict`.
Весь код внутри класса автоматически находится в строгом режиме.
-Также в дополнение к основному, описанному выше, функционалу, синтаксис `class` даёт ряд других интересных возможностей, с которыми мы познакомимся чуть позже.
+Также в дополнение к основной, описанной выше, функциональности, синтаксис `class` даёт ряд других интересных возможностей, с которыми мы познакомимся чуть позже.
## Class Expression
diff --git a/1-js/09-classes/01-class/class-user.svg b/1-js/09-classes/01-class/class-user.svg
index 5ac0146a6c..418d71d187 100644
--- a/1-js/09-classes/01-class/class-user.svg
+++ b/1-js/09-classes/01-class/class-user.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
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/animal-rabbit-extends.svg b/1-js/09-classes/02-class-inheritance/animal-rabbit-extends.svg
index 3412d9825c..203c59447a 100644
--- a/1-js/09-classes/02-class-inheritance/animal-rabbit-extends.svg
+++ b/1-js/09-classes/02-class-inheritance/animal-rabbit-extends.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/1-js/09-classes/02-class-inheritance/article.md b/1-js/09-classes/02-class-inheritance/article.md
index c6ba3f8386..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 {
@@ -11,62 +16,31 @@ class Animal {
this.name = name;
}
run(speed) {
- this.speed += speed;
+ this.speed = speed;
alert(`${this.name} бежит со скоростью ${this.speed}.`);
}
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,26 +54,30 @@ let rabbit = new Rabbit("Белый кролик");
rabbit.run(5); // Белый кролик бежит со скоростью 5.
rabbit.hide(); // Белый кролик прячется!
```
-Теперь код `Rabbit` стал короче, так как используется конструктор класса `Animal` по умолчанию и кролик может использовать метод `run` как и все животные.
-На самом деле ключевое слово `extends` добавляет ссылку на `[[Prototype]]` из `Rabbit.prototype` в `Animal.prototype`:
+Объект класса `Rabbit` имеет доступ как к методам `Rabbit`, таким как `rabbit.hide()`, так и к методам `Animal`, таким как `rabbit.run()`.
+
+Внутри ключевое слово `extends` работает по старой доброй механике прототипов. Оно устанавливает `Rabbit.prototype.[[Prototype]]` в `Animal.prototype`. Таким образом, если метода не оказалось в `Rabbit.prototype`, JavaScript берет его из `Animal.prototype`.

-Если метод не найден в `Rabbit.prototype`, JavaScript возьмёт его из `Animal.prototype`.
+Например, чтобы найти метод `rabbit.run`, движок проверяет (снизу вверх на картинке):
+1. Объект `rabbit` (не имеет `run`).
+2. Его прототип, то есть `Rabbit.prototype` (имеет `hide`, но не имеет `run`).
+3. Его прототип, то есть (вследствие `extends`) `Animal.prototype`, в котором, наконец, есть метод `run`.
-Как мы помним из главы , в JavaScript используется наследование на прототипах для встроенных объектов. Например `Date.prototype.[[Prototype]]` это `Object.prototype`, поэтому у дат есть универсальные методы объекта.
+Как мы помним из главы , сам JavaScript использует наследование на прототипах для встроенных объектов. Например, `Date.prototype.[[Prototype]]` является `Object.prototype`, поэтому у дат есть универсальные методы объекта.
````smart header="После `extends` разрешены любые выражения"
-Синтаксис создания класса допускает указывать после `extends` не только класс, но любое выражение.
+Синтаксис создания класса допускает указывать после `extends` не только класс, но и любое выражение.
Пример вызова функции, которая генерирует родительский класс:
```js run
function f(phrase) {
return class {
- sayHi() { alert(phrase) }
- }
+ sayHi() { alert(phrase); }
+ };
}
*!*
@@ -115,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(...)` для вызова родительского конструктора (работает только внутри нашего конструктора).
Пусть наш кролик автоматически прячется при остановке:
@@ -144,7 +124,7 @@ class Animal {
}
run(speed) {
- this.speed += speed;
+ this.speed = speed;
alert(`${this.name} бежит со скоростью ${this.speed}.`);
}
@@ -180,6 +160,7 @@ rabbit.stop(); // Белый кролик стоит. Белый кролик п
Как упоминалось в главе , стрелочные функции не имеют `super`.
При обращении к `super` стрелочной функции он берётся из внешней функции:
+
```js
class Rabbit extends Animal {
stop() {
@@ -249,11 +230,13 @@ let rabbit = new Rabbit("Белый кролик", 10); // Error: this is not de
Упс! При создании кролика - ошибка! Что не так?
-Если коротко, то в классах-потомках конструктор обязан вызывать `super(...)`, и (!) делать это перед использованием `this`.
+Если коротко, то:
+
+- **Конструкторы в наследуемых классах должны обязательно вызывать `super(...)`, и (!) делать это перед использованием `this`.**
...Но почему? Что происходит? Это требование кажется довольно странным.
-Конечно, всему есть объяснение. Давайте углубимся в детали, чтобы вы действительно поняли, что происходит.
+Конечно, всему есть своё объяснение. Давайте углубимся в детали, чтобы вы действительно поняли, что происходит.
В JavaScript существует различие между "функцией-конструктором наследующего класса" и всеми остальными. В наследующем классе соответствующая функция-конструктор помечена специальным внутренним свойством `[[ConstructorKind]]:"derived"`.
@@ -297,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`. Здесь есть некоторые интересные моменты.
@@ -318,6 +395,7 @@ alert(rabbit.earLength); // 10
Вы можете пропустить эту часть и перейти ниже к подсекции `[[HomeObject]]`, если не хотите знать детали. Вреда не будет. Или читайте далее, если хотите разобраться.
В примере ниже `rabbit.__proto__ = animal`. Попробуем в `rabbit.eat()` вызвать `animal.eat()`, используя `this.__proto__`:
+
```js run
let animal = {
name: "Animal",
@@ -365,7 +443,7 @@ let rabbit = {
let longEar = {
__proto__: rabbit,
eat() {
- // ...делаем что-то, связанное с длинными ушами и вызываем родительский (rabbit) метод
+ // ...делаем что-то, связанное с длинными ушами, и вызываем родительский (rabbit) метод
this.__proto__.eat.call(this); // (**)
}
};
@@ -419,7 +497,6 @@ longEar.eat(); // Error: Maximum call stack size exceeded
Давайте посмотрим, как это работает - опять же, используя простые объекты:
-
```js run
let animal = {
name: "Животное",
@@ -465,7 +542,7 @@ longEar.eat(); // Длинноух ест.
```js run
let animal = {
sayHi() {
- console.log(`Я животное`);
+ alert("Я животное");
}
};
@@ -479,7 +556,7 @@ let rabbit = {
let plant = {
sayHi() {
- console.log("Я растение");
+ alert("Я растение");
}
};
@@ -505,8 +582,6 @@ tree.sayHi(); // Я животное (?!?)
Вот диаграмма происходящего:
-Here's the diagram of what happens:
-

@@ -520,7 +595,7 @@ Here's the diagram of what happens:
```js run
let animal = {
- eat: function() { // намеренно не пишем так, а не eat() { ...
+ eat: function() { // намеренно пишем так, а не eat() { ...
// ...
}
};
@@ -551,4 +626,4 @@ rabbit.eat(); // Ошибка вызова super (потому что нет [[
- Поэтому копировать метод, использующий `super`, между разными объектами небезопасно.
Также:
-- У функций-стрелок нет своего `this` и `super`, поэтому они "прозрачно" встраиваются во внешний контекст.
+- У стрелочных функций нет своего `this` и `super`, поэтому они "прозрачно" встраиваются во внешний контекст.
diff --git a/1-js/09-classes/02-class-inheritance/class-inheritance-array-object.svg b/1-js/09-classes/02-class-inheritance/class-inheritance-array-object.svg
index 546aa334f0..5ea9bf29ea 100644
--- a/1-js/09-classes/02-class-inheritance/class-inheritance-array-object.svg
+++ b/1-js/09-classes/02-class-inheritance/class-inheritance-array-object.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/1-js/09-classes/02-class-inheritance/class-inheritance-rabbit-animal-2.svg b/1-js/09-classes/02-class-inheritance/class-inheritance-rabbit-animal-2.svg
index 3bdda5a02a..2172088e64 100644
--- a/1-js/09-classes/02-class-inheritance/class-inheritance-rabbit-animal-2.svg
+++ b/1-js/09-classes/02-class-inheritance/class-inheritance-rabbit-animal-2.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/1-js/09-classes/02-class-inheritance/class-inheritance-rabbit-animal.svg b/1-js/09-classes/02-class-inheritance/class-inheritance-rabbit-animal.svg
index 706ca0974b..1b21dd81b5 100644
--- a/1-js/09-classes/02-class-inheritance/class-inheritance-rabbit-animal.svg
+++ b/1-js/09-classes/02-class-inheritance/class-inheritance-rabbit-animal.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/1-js/09-classes/02-class-inheritance/rabbit-animal-independent-animal.svg b/1-js/09-classes/02-class-inheritance/rabbit-animal-independent-animal.svg
index bf86db77d9..62b876d3ac 100644
--- a/1-js/09-classes/02-class-inheritance/rabbit-animal-independent-animal.svg
+++ b/1-js/09-classes/02-class-inheritance/rabbit-animal-independent-animal.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/1-js/09-classes/02-class-inheritance/rabbit-animal-independent-rabbit.svg b/1-js/09-classes/02-class-inheritance/rabbit-animal-independent-rabbit.svg
index 8a5e25037a..ff669d3d84 100644
--- a/1-js/09-classes/02-class-inheritance/rabbit-animal-independent-rabbit.svg
+++ b/1-js/09-classes/02-class-inheritance/rabbit-animal-independent-rabbit.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/1-js/09-classes/02-class-inheritance/super-homeobject-wrong.svg b/1-js/09-classes/02-class-inheritance/super-homeobject-wrong.svg
index c9c8fea9e3..f6450ddc49 100644
--- a/1-js/09-classes/02-class-inheritance/super-homeobject-wrong.svg
+++ b/1-js/09-classes/02-class-inheritance/super-homeobject-wrong.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/1-js/09-classes/02-class-inheritance/this-super-loop.svg b/1-js/09-classes/02-class-inheritance/this-super-loop.svg
index 342574da90..4f5f45034a 100644
--- a/1-js/09-classes/02-class-inheritance/this-super-loop.svg
+++ b/1-js/09-classes/02-class-inheritance/this-super-loop.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/1-js/09-classes/02-class-inheritance/3-class-extend-object/rabbit-extends-object.svg b/1-js/09-classes/03-static-properties-methods/1-class-extend-object/rabbit-extends-object.svg
similarity index 95%
rename from 1-js/09-classes/02-class-inheritance/3-class-extend-object/rabbit-extends-object.svg
rename to 1-js/09-classes/03-static-properties-methods/1-class-extend-object/rabbit-extends-object.svg
index 0a1f4382c2..34d783b4de 100644
--- a/1-js/09-classes/02-class-inheritance/3-class-extend-object/rabbit-extends-object.svg
+++ b/1-js/09-classes/03-static-properties-methods/1-class-extend-object/rabbit-extends-object.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/1-js/09-classes/02-class-inheritance/3-class-extend-object/solution.md b/1-js/09-classes/03-static-properties-methods/1-class-extend-object/solution.md
similarity index 80%
rename from 1-js/09-classes/02-class-inheritance/3-class-extend-object/solution.md
rename to 1-js/09-classes/03-static-properties-methods/1-class-extend-object/solution.md
index e60afe16b7..3c1a42f600 100644
--- a/1-js/09-classes/02-class-inheritance/3-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 {}
@@ -44,11 +44,11 @@ class Rabbit extends Object {}
*!*
// обычно мы вызываем Object.getOwnPropertyNames
-alert ( Rabbit.getOwnPropertyNames({a: 1, b: 2})); // a,b
+alert( Rabbit.getOwnPropertyNames({a: 1, b: 2}) ); // a,b
*/!*
```
-Но если явно не наследуем от объекта, то для `Rabbit.__ proto__` не установлено значение `Object`.
+Но если явно не наследуем от объекта, то для `Rabbit.__proto__` не установлено значение `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/02-class-inheritance/3-class-extend-object/task.md b/1-js/09-classes/03-static-properties-methods/1-class-extend-object/task.md
similarity index 93%
rename from 1-js/09-classes/02-class-inheritance/3-class-extend-object/task.md
rename to 1-js/09-classes/03-static-properties-methods/1-class-extend-object/task.md
index 620f9f4792..55586e055a 100644
--- a/1-js/09-classes/02-class-inheritance/3-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
---
@@ -38,5 +38,5 @@ class Rabbit extends Object {
let rabbit = new Rabbit("Кроль");
-alert( rabbit.hasOwnProperty('name') ); // true
+alert( rabbit.hasOwnProperty('name') ); // Ошибка
```
diff --git a/1-js/09-classes/03-static-properties-methods/animal-rabbit-static.svg b/1-js/09-classes/03-static-properties-methods/animal-rabbit-static.svg
index fab401df66..ee1fc54748 100644
--- a/1-js/09-classes/03-static-properties-methods/animal-rabbit-static.svg
+++ b/1-js/09-classes/03-static-properties-methods/animal-rabbit-static.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
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 01c58b6788..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 {
@@ -20,7 +20,7 @@ User.staticMethod(); // true
Это фактически то же самое, что присвоить метод напрямую как свойство функции:
```js
-class User() { }
+class User { }
User.staticMethod = function() {
alert(this === 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]
Статические свойства и методы наследуются.
@@ -177,7 +192,7 @@ rabbits[0].run(); // Чёрный кролик бежит со скорость
Так что `Rabbit extends Animal` создаёт две ссылки на прототип:
-1. Функция `Rabbit` прототипно наследует от `Animal` function.
+1. Функция `Rabbit` прототипно наследует от функции `Animal`.
2. `Rabbit.prototype` прототипно наследует от `Animal.prototype`.
В результате наследование работает как для обычных, так и для статических методов.
@@ -192,7 +207,7 @@ class Rabbit extends Animal {}
alert(Rabbit.__proto__ === Animal); // true
// для обычных методов
-alert(Rabbit.prototype.__proto__ === Animal.prototype);
+alert(Rabbit.prototype.__proto__ === Animal.prototype); // true
```
## Итого
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 55f03bfca0..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
@@ -170,7 +170,7 @@ class CoffeeMachine {
new CoffeeMachine().setWaterAmount(100);
```
-Это выглядит немного длиннее, но функции более гибкие. Они могут принимать несколько аргументов (даже если они нам сейчас не нужны). Итак, на будущее, если нам надо что-то отрефакторить, функции более безопасный выбор.
+Это выглядит немного длиннее, но функции более гибкие. Они могут принимать несколько аргументов (даже если они нам сейчас не нужны). Итак, на будущее, если нам надо что-то отрефакторить, функции -- более безопасный выбор.
С другой стороны, синтаксис get/set короче, решать вам.
````
@@ -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
*/!*
@@ -241,7 +241,7 @@ machine.waterAmount = 100;
alert(machine.#waterAmount); // Error
```
-В отличие от защищённых, функционал приватных полей обеспечивается самим языком. Это хорошо.
+В отличие от защищённых, функциональность приватных полей обеспечивается самим языком. Это хорошо.
Но если мы унаследуем от `CoffeeMachine`, то мы не получим прямого доступа к `#waterAmount`. Мы будем вынуждены полагаться на геттер/сеттер `waterAmount`:
@@ -260,7 +260,7 @@ class MegaCoffeeMachine extends CoffeeMachine {
````warn
Приватные поля особенные.
-Как мы помним, обычно мы можем получить доступ к полям объекта с помощью this[name]:
+Как мы помним, обычно мы можем получить доступ к полям объекта с помощью `this[name]`:
```js
class User {
diff --git a/1-js/09-classes/05-extend-natives/article.md b/1-js/09-classes/05-extend-natives/article.md
index 41ba26ee95..a8840522f4 100644
--- a/1-js/09-classes/05-extend-natives/article.md
+++ b/1-js/09-classes/05-extend-natives/article.md
@@ -74,7 +74,7 @@ alert(filteredArr.isEmpty()); // Error: filteredArr.isEmpty is not a function
Как мы уже знаем, встроенные классы расширяют друг друга.
-Обычно, когда один класс наследует другому, то наследуются и статические методы. Это было подробно разъяснено в главе [](info:static-properties-methods#statics-and-inheritance).
+Обычно, когда один класс наследует другой, то наследуются и статические методы. Это было подробно разъяснено в главе [](info:static-properties-methods#statics-and-inheritance).
Но встроенные классы - исключение. Они не наследуют статические методы друг друга.
diff --git a/1-js/09-classes/05-extend-natives/object-date-inheritance.svg b/1-js/09-classes/05-extend-natives/object-date-inheritance.svg
index a0165bccbc..be47d7fd96 100644
--- a/1-js/09-classes/05-extend-natives/object-date-inheritance.svg
+++ b/1-js/09-classes/05-extend-natives/object-date-inheritance.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/1-js/09-classes/06-instanceof/article.md b/1-js/09-classes/06-instanceof/article.md
index ca4654da6c..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` позволяет проверить, принадлежит ли объект указанному классу, с учётом наследования.
Такая проверка может потребоваться во многих случаях. Здесь мы используем её для создания *полиморфной* функции, которая интерпретирует аргументы по-разному в зависимости от их типа.
@@ -92,7 +92,7 @@ alert( arr instanceof Object ); // true
alert(rabbit instanceof Animal); // true
*/!*
- // rabbit.__proto__ === Rabbit.prototype
+ // rabbit.__proto__ === Animal.prototype (нет совпадения)
*!*
// rabbit.__proto__.__proto__ === Animal.prototype (совпадение!)
*/!*
@@ -102,7 +102,7 @@ alert( arr instanceof Object ); // true

-Кстати, есть метод [objA.isPrototypeOf(objB)](mdn:js/object/isPrototypeOf), которые возвращает `true`, если объект `objA` есть где-то в прототипной цепочке объекта `objB`. Так что `obj instanceof Class` можно перефразировать как `Class.prototype.isPrototypeOf(obj)`.
+Кстати, есть метод [objA.isPrototypeOf(objB)](mdn:js/object/isPrototypeOf), который возвращает `true`, если объект `objA` есть где-то в прототипной цепочке объекта `objB`. Так что `obj instanceof Class` можно перефразировать как `Class.prototype.isPrototypeOf(obj)`.
Забавно, но сам конструктор `Class` не участвует в процессе проверки! Важна только цепочка прототипов `Class.prototype`.
@@ -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/06-instanceof/instanceof.svg b/1-js/09-classes/06-instanceof/instanceof.svg
index 920be68b72..d63b03a8a2 100644
--- a/1-js/09-classes/06-instanceof/instanceof.svg
+++ b/1-js/09-classes/06-instanceof/instanceof.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/1-js/09-classes/07-mixins/article.md b/1-js/09-classes/07-mixins/article.md
index cffb75add0..e92a1aae8a 100644
--- a/1-js/09-classes/07-mixins/article.md
+++ b/1-js/09-classes/07-mixins/article.md
@@ -4,13 +4,13 @@
Иногда это может ограничивать нас. Например, у нас есть класс `StreetSweeper` и класс `Bicycle`, а мы хотим создать их смесь: `StreetSweepingBicycle`.
-Или у нас есть класс `User`, который реализует пользователей, и класс `EventEmitter`, реализующий события. Мы хотели бы добавить функционал класса `EventEmitter` к `User`, чтобы пользователи могли легко генерировать события.
+Или у нас есть класс `User`, который реализует пользователей, и класс `EventEmitter`, реализующий события. Мы хотели бы добавить функциональность класса `EventEmitter` к `User`, чтобы пользователи могли легко генерировать события.
Для таких случаев существуют "примеси".
По определению из Википедии, [примесь](https://ru.wikipedia.org/wiki/%D0%9F%D1%80%D0%B8%D0%BC%D0%B5%D1%81%D1%8C_(%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5)) - это класс, методы которого предназначены для использования в других классах, причём без наследования от примеси.
-Другими словами, *примесь* определяет методы, которые реализуют определённое поведение. Мы не используем примесь саму по себе, а используем её, чтобы добавить функционал другим классам.
+Другими словами, *примесь* определяет методы, которые реализуют определённое поведение. Мы не используем примесь саму по себе, а используем её, чтобы добавить функциональность другим классам.
## Пример примеси
@@ -57,7 +57,7 @@ class User extends Person {
Object.assign(User.prototype, sayHiMixin);
```
-Примеси могут наследовать друг другу.
+Примеси могут наследовать друг друга.
В примере ниже `sayHiMixin` наследует от `sayMixin`:
@@ -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}`));
*/!*
@@ -203,6 +205,6 @@ menu.choose("123"); // Выбранное значение: 123
Некоторые другие языки допускают множественное наследование. JavaScript не поддерживает множественное наследование, но с помощью примесей мы можем реализовать нечто похожее, скопировав методы в прототип.
-Мы можем использовать примеси для расширения функционала классов, например, для обработки событий, как мы сделали это выше.
+Мы можем использовать примеси для расширения функциональности классов, например, для обработки событий, как мы сделали это выше.
С примесями могут возникнуть конфликты, если они перезаписывают существующие методы класса. Стоит помнить об этом и быть внимательнее при выборе имён для методов примеси, чтобы их избежать.
diff --git a/1-js/09-classes/07-mixins/mixin-inheritance.svg b/1-js/09-classes/07-mixins/mixin-inheritance.svg
index 2e7ba8b394..1fdc223936 100644
--- a/1-js/09-classes/07-mixins/mixin-inheritance.svg
+++ b/1-js/09-classes/07-mixins/mixin-inheritance.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/1-js/10-error-handling/1-try-catch/1-finally-or-code-after/task.md b/1-js/10-error-handling/1-try-catch/1-finally-or-code-after/task.md
index f1ef88dea9..427d829f42 100644
--- a/1-js/10-error-handling/1-try-catch/1-finally-or-code-after/task.md
+++ b/1-js/10-error-handling/1-try-catch/1-finally-or-code-after/task.md
@@ -35,6 +35,6 @@ importance: 5
*/!*
```
-Нам определённо нужна очиска после работы, неважно возникли ошибки или нет.
+Нам определённо нужна очистка после работы, неважно возникли ошибки или нет.
Есть ли здесь преимущество в использовании `finally` или оба фрагмента кода одинаковы? Если такое преимущество есть, то дайте пример, когда оно проявляется.
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 8ad1363959..5a9e089544 100644
--- a/1-js/10-error-handling/1-try-catch/article.md
+++ b/1-js/10-error-handling/1-try-catch/article.md
@@ -81,7 +81,7 @@ try {
try {
{{{{{{{{{{{{
} catch(e) {
- alert("Движок не может понять этот код, он не корректен");
+ alert("Движок не может понять этот код, он некорректен");
}
```
@@ -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 o in JSON at position 2
+ alert(e.message); // Expected property name or '}' in JSON at position 2 (line 1 column 3)
}
```
@@ -626,9 +626,9 @@ window.onerror = function(message, url, line, col, error) {
```
-Роль глобального обработчика `window.onerror` обычно заключается не в восстановлении выполнения скрипта -- это скорее всего невозможно в случае программной ошибки, а в отправке сообщение об ошибке разработчикам.
+Роль глобального обработчика `window.onerror` обычно заключается не в восстановлении выполнения скрипта -- это скорее всего невозможно в случае программной ошибки, а в отправке сообщения об ошибке разработчикам.
-Существуют также веб-сервисы, которые предоставляют логирование ошибок для таких случаев, такие как или .
+Существуют также веб-сервисы, которые предоставляют логирование ошибок для таких случаев, такие как .
Они работают так:
diff --git a/1-js/10-error-handling/1-try-catch/try-catch-flow.svg b/1-js/10-error-handling/1-try-catch/try-catch-flow.svg
index 8384c6ca27..8e6ca4d79c 100644
--- a/1-js/10-error-handling/1-try-catch/try-catch-flow.svg
+++ b/1-js/10-error-handling/1-try-catch/try-catch-flow.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
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` будет добавлять в наш круг `
Пример без опций:
```js run
-var formatter = new Intl.NumberFormat("ru");
+let formatter = new Intl.NumberFormat("ru");
alert( formatter.format(1234567890.123) ); // 1 234 567 890,123
```
С ограничением значимых цифр (важны только первые 3):
```js run
-var formatter = new Intl.NumberFormat("ru", {
+let formatter = new Intl.NumberFormat("ru", {
maximumSignificantDigits: 3
});
alert( formatter.format(1234567890.123) ); // 1 230 000 000
@@ -400,8 +399,8 @@ alert( formatter.format(1234567890.123) ); // 1 230 000 000
С опциями для валюты:
-```js
-var formatter = new Intl.NumberFormat("ru", {
+```js run
+let formatter = new Intl.NumberFormat("ru", {
style: "currency",
currency: "GBP"
});
@@ -411,8 +410,8 @@ alert( formatter.format(1234.5) ); // 1 234,5 £
С двумя цифрами после запятой:
-```js
-var formatter = new Intl.NumberFormat("ru", {
+```js run
+let formatter = new Intl.NumberFormat("ru", {
style: "currency",
currency: "GBP",
minimumFractionDigits: 2
@@ -429,7 +428,7 @@ alert( formatter.format(1234.5) ); // 1 234,50 £
: Сравнивает строку с другой, с учётом локали, например:
```js run
- var str = "ёжик";
+ let str = "ёжик";
alert( str.localeCompare("яблоко", "ru") ); // -1
```
@@ -438,7 +437,7 @@ alert( formatter.format(1234.5) ); // 1 234,50 £
: Форматирует дату в соответствии с локалью, например:
```js run no-beautify
- var date = new Date(2014, 11, 31, 12, 00);
+ let date = new Date(2014, 11, 31, 12, 0);
alert( date.toLocaleString("ru", { year: 'numeric', month: 'long' }) ); // Декабрь 2014
```
@@ -456,4 +455,4 @@ alert( formatter.format(1234.5) ); // 1 234,50 £
## Старые IE
-В IE10- рекомендуется использовать полифил, например библиотеку .
+В IE10 рекомендуется использовать полифил, например библиотеку .
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 @@
+
+
+
+
+
+
+
+
+
+
+
';
+};
+
+const downloadCollage = () => {
+ const date = new Date();
+ const fileName = `Collage-${date.getDay()}-${date.getMonth()}-${date.getFullYear()}.png`;
+ const img = canvasEl.toDataURL("image/png");
+ const link = document.createElement("a");
+ link.download = fileName;
+ link.href = img;
+ link.click();
+ link.remove();
+};
+
+const changeLayout = ({ target }) => {
+ state.currentLayout = JSON.parse(target.value);
+};
+
+// Listeners.
+selectEl.addEventListener("change", changeLayout);
+createCollageBtn.addEventListener("click", createCollage);
+startOverBtn.addEventListener("click", startOver);
+downloadBtn.addEventListener("click", downloadCollage);
diff --git a/1-js/99-js-misc/09-weakref-finalizationregistry/weakref-finalizationregistry.view/utils.js b/1-js/99-js-misc/09-weakref-finalizationregistry/weakref-finalizationregistry.view/utils.js
new file mode 100644
index 0000000000..f0140c116a
--- /dev/null
+++ b/1-js/99-js-misc/09-weakref-finalizationregistry/weakref-finalizationregistry.view/utils.js
@@ -0,0 +1,321 @@
+const loggerContainerEl = document.querySelector(".loggerContainer");
+
+export const images = [
+ {
+ img: "https://images.unsplash.com/photo-1471357674240-e1a485acb3e1",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1589118949245-7d38baf380d6",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1527631746610-bca00a040d60",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1500835556837-99ac94a94552",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1503220317375-aaad61436b1b",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1501785888041-af3ef285b470",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1528543606781-2f6e6857f318",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1523906834658-6e24ef2386f9",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1539635278303-d4002c07eae3",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1533105079780-92b9be482077",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1516483638261-f4dbaf036963",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1502791451862-7bd8c1df43a7",
+ },
+ {
+ img: "https://plus.unsplash.com/premium_photo-1663047367140-91adf819d007",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1506197603052-3cc9c3a201bd",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1517760444937-f6397edcbbcd",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1518684079-3c830dcef090",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1505832018823-50331d70d237",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1524850011238-e3d235c7d4c9",
+ },
+ {
+ img: "https://plus.unsplash.com/premium_photo-1661277758451-b5053309eea1",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1541410965313-d53b3c16ef17",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1528702748617-c64d49f918af",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1502003148287-a82ef80a6abc",
+ },
+ {
+ img: "https://plus.unsplash.com/premium_photo-1661281272544-5204ea3a481a",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1503457574462-bd27054394c1",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1499363536502-87642509e31b",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1551918120-9739cb430c6d",
+ },
+ {
+ img: "https://plus.unsplash.com/premium_photo-1661382219642-43e54f7e81d7",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1497262693247-aa258f96c4f5",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1525254134158-4fd5fdd45793",
+ },
+ {
+ img: "https://plus.unsplash.com/premium_photo-1661274025419-4c54107d5c48",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1553697388-94e804e2f0f6",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1574260031597-bcd9eb192b4f",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1536323760109-ca8c07450053",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1527824404775-dce343118ebc",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1612278675615-7b093b07772d",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1522010675502-c7b3888985f6",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1501555088652-021faa106b9b",
+ },
+ {
+ img: "https://plus.unsplash.com/premium_photo-1669223469435-27e091439169",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1506012787146-f92b2d7d6d96",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1511739001486-6bfe10ce785f",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1553342385-111fd6bc6ab3",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1516546453174-5e1098a4b4af",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1527142879-95b61a0b8226",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1520466809213-7b9a56adcd45",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1516939884455-1445c8652f83",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1545389336-cf090694435e",
+ },
+ {
+ img: "https://plus.unsplash.com/premium_photo-1669223469455-b7b734c838f4",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1454391304352-2bf4678b1a7a",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1433838552652-f9a46b332c40",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1506125840744-167167210587",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1522199873717-bc67b1a5e32b",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1495904786722-d2b5a19a8535",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1614094082869-cd4e4b2905c7",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1474755032398-4b0ed3b2ae5c",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1501554728187-ce583db33af7",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1515859005217-8a1f08870f59",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1531141445733-14c2eb7d4c1f",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1500259783852-0ca9ce8a64dc",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1510662145379-13537db782dc",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1573790387438-4da905039392",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1512757776214-26d36777b513",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1518855706573-84de4022b69b",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1500049242364-5f500807cdd7",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1528759335187-3b683174c86a",
+ },
+];
+export const THUMBNAIL_PARAMS = "w=240&h=240&fit=crop&auto=format";
+
+// Console styles.
+export const CONSOLE_BASE_STYLES = [
+ "font-size: 12px",
+ "padding: 4px",
+ "border: 2px solid #5a5a5a",
+ "color: white",
+].join(";");
+export const CONSOLE_PRIMARY = [
+ CONSOLE_BASE_STYLES,
+ "background-color: #13315a",
+].join(";");
+export const CONSOLE_SUCCESS = [
+ CONSOLE_BASE_STYLES,
+ "background-color: #385a4e",
+].join(";");
+export const CONSOLE_ERROR = [
+ CONSOLE_BASE_STYLES,
+ "background-color: #5a1a24",
+].join(";");
+
+// Layouts.
+export const LAYOUT_4_COLUMNS = {
+ name: "Layout 4 columns",
+ columns: 4,
+ itemWidth: 240,
+ itemHeight: 240,
+};
+export const LAYOUT_8_COLUMNS = {
+ name: "Layout 8 columns",
+ columns: 8,
+ itemWidth: 240,
+ itemHeight: 240,
+};
+export const LAYOUTS = [LAYOUT_4_COLUMNS, LAYOUT_8_COLUMNS];
+
+export const createImageFile = async (src) =>
+ new Promise((resolve, reject) => {
+ const img = new Image();
+ img.src = src;
+ img.onload = () => resolve(img);
+ img.onerror = () => reject(new Error("Failed to construct image."));
+ });
+
+export const loadImage = async (url) => {
+ try {
+ const response = await fetch(url);
+ if (!response.ok) {
+ throw new Error(String(response.status));
+ }
+
+ return await response.blob();
+ } catch (e) {
+ console.log(`%cFETCHED_FAILED: ${e}`, CONSOLE_ERROR);
+ }
+};
+
+export const weakRefCache = (fetchImg) => {
+ const imgCache = new Map();
+ const registry = new FinalizationRegistry(({ imgName, size, type }) => {
+ const cachedImg = imgCache.get(imgName);
+ if (cachedImg && !cachedImg.deref()) {
+ imgCache.delete(imgName);
+ console.log(
+ `%cCLEANED_IMAGE: Url: ${imgName}, Size: ${size}, Type: ${type}`,
+ CONSOLE_ERROR,
+ );
+
+ const logEl = document.createElement("div");
+ logEl.classList.add("logger-item", "logger--error");
+ logEl.innerHTML = `CLEANED_IMAGE: Url: ${imgName}, Size: ${size}, Type: ${type}`;
+ loggerContainerEl.appendChild(logEl);
+ loggerContainerEl.scrollTop = loggerContainerEl.scrollHeight;
+ }
+ });
+
+ return async (imgName) => {
+ const cachedImg = imgCache.get(imgName);
+
+ if (cachedImg?.deref() !== undefined) {
+ console.log(
+ `%cCACHED_IMAGE: Url: ${imgName}, Size: ${cachedImg.size}, Type: ${cachedImg.type}`,
+ CONSOLE_SUCCESS,
+ );
+
+ const logEl = document.createElement("div");
+ logEl.classList.add("logger-item", "logger--success");
+ logEl.innerHTML = `CACHED_IMAGE: Url: ${imgName}, Size: ${cachedImg.size}, Type: ${cachedImg.type}`;
+ loggerContainerEl.appendChild(logEl);
+ loggerContainerEl.scrollTop = loggerContainerEl.scrollHeight;
+
+ return cachedImg?.deref();
+ }
+
+ const newImg = await fetchImg(imgName);
+ console.log(
+ `%cFETCHED_IMAGE: Url: ${imgName}, Size: ${newImg.size}, Type: ${newImg.type}`,
+ CONSOLE_PRIMARY,
+ );
+
+ const logEl = document.createElement("div");
+ logEl.classList.add("logger-item", "logger--primary");
+ logEl.innerHTML = `FETCHED_IMAGE: Url: ${imgName}, Size: ${newImg.size}, Type: ${newImg.type}`;
+ loggerContainerEl.appendChild(logEl);
+ loggerContainerEl.scrollTop = loggerContainerEl.scrollHeight;
+
+ imgCache.set(imgName, new WeakRef(newImg));
+ registry.register(newImg, {
+ imgName,
+ size: newImg.size,
+ type: newImg.type,
+ });
+
+ return newImg;
+ };
+};
+
+export const stateObj = {
+ loading: false,
+ drawing: true,
+ collageRendered: false,
+ currentLayout: LAYOUTS[0],
+ selectedImages: new Set(),
+};
diff --git a/1-js/99-js-misc/09-weakref-finalizationregistry/weakref_-finalizationregistry-demo-01.png b/1-js/99-js-misc/09-weakref-finalizationregistry/weakref_-finalizationregistry-demo-01.png
new file mode 100644
index 0000000000..fc33a023af
Binary files /dev/null and b/1-js/99-js-misc/09-weakref-finalizationregistry/weakref_-finalizationregistry-demo-01.png differ
diff --git a/1-js/index.md b/1-js/index.md
index 222ce34ec8..db327c8555 100644
--- a/1-js/index.md
+++ b/1-js/index.md
@@ -1,5 +1,5 @@
-# Язык программирования JavaScript
+# Язык JavaScript
Здесь вы можете изучить JavaScript, начиная с нуля и заканчивая продвинутыми концепциями вроде ООП.
-Мы будем использовать браузер в качестве окружения, но основное внимание будет уделяться именно самому языку JavaScript.
+Мы сосредоточимся на самом языке, изредка добавляя заметки о средах его исполнения.
diff --git a/2-ui/1-document/01-browser-environment/article.md b/2-ui/1-document/01-browser-environment/article.md
index 443dc5887a..a461966b8b 100644
--- a/2-ui/1-document/01-browser-environment/article.md
+++ b/2-ui/1-document/01-browser-environment/article.md
@@ -2,7 +2,7 @@
Язык JavaScript изначально был создан для веб-браузеров. Но с тех пор он значительно эволюционировал и превратился в кроссплатформенный язык программирования для решения широкого круга задач.
-Сегодня JavaScript может использоваться в браузере, на веб-сервере или в какой-то другой среде, даже в кофеварке. Каждая среда предоставляет свой функционал, который спецификация JavaScript называет *окружением*.
+Сегодня JavaScript может использоваться в браузере, на веб-сервере или в какой-то другой среде, даже в кофеварке. Каждая среда предоставляет свою функциональность, которую спецификация JavaScript называет *окружением*.
Окружение предоставляет свои объекты и дополнительные функции, в дополнение базовым языковым. Браузеры, например, дают средства для управления веб-страницами. Node.js делает доступными какие-то серверные возможности и так далее.
@@ -17,7 +17,7 @@
Например, здесь мы используем `window` как глобальный объект:
-```js run
+```js run global
function sayHi() {
alert("Hello");
}
@@ -54,7 +54,7 @@ setTimeout(() => document.body.style.background = "", 1000);
- **DOM Living Standard** на
```smart header="DOM - не только для браузеров"
-Спецификация DOM описывает структуру документа и предоставляет объекты для манипуляций со страницей. Существует и другие, отличные от браузеров, инструменты, использующие DOM.
+Спецификация DOM описывает структуру документа и предоставляет объекты для манипуляций со страницей. Существуют и другие, отличные от браузеров, инструменты, использующие DOM.
Например, серверные скрипты, которые загружают и обрабатывают HTML-страницы, также могут использовать DOM. При этом они могут поддерживать спецификацию не полностью.
```
@@ -96,7 +96,7 @@ BOM является частью общей [спецификации HTML](htt
Говоря о стандартах, у нас есть:
Спецификация DOM
-: описывает структуру документа, манипуляции с контентом и события, подробнее на .
+: Описывает структуру документа, манипуляции с контентом и события, подробнее на .
Спецификация CSSOM
: Описывает файлы стилей, правила написания стилей и манипуляций с ними, а также то, как это всё связано со страницей, подробнее на .
@@ -108,7 +108,7 @@ BOM является частью общей [спецификации HTML](htt
Пожалуйста, заметьте для себя эти ссылки, так как по ним содержится очень много информации, которую невозможно изучить полностью и держать в уме.
-Когда вам нужно будет прочитать о каком-то свойстве или методе, справочник на сайте Mozilla тоже очень хороший ресурс, хотя ничто не сравнится с чтением спецификации: она сложная и объёмная, но сделает ваши знания максимально полными.
+Когда вам нужно будет прочитать о каком-то свойстве или методе, справочник на сайте Mozilla тоже очень хороший ресурс, хотя ничто не сравнится с чтением спецификации: она сложная и объёмная, но сделает ваши знания максимально полными.
Для поиска чего-либо обычно удобно использовать интернет-поиск со словами "WHATWG [термин]" или "MDN [термин]", например , .
diff --git a/2-ui/1-document/01-browser-environment/windowObjects.svg b/2-ui/1-document/01-browser-environment/windowObjects.svg
index 3b525b2cb5..b7e18bb347 100644
--- a/2-ui/1-document/01-browser-environment/windowObjects.svg
+++ b/2-ui/1-document/01-browser-environment/windowObjects.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/2-ui/1-document/02-dom-nodes/article.md b/2-ui/1-document/02-dom-nodes/article.md
index 3f73007e5f..0856d16e75 100644
--- a/2-ui/1-document/02-dom-nodes/article.md
+++ b/2-ui/1-document/02-dom-nodes/article.md
@@ -45,7 +45,7 @@ DOM -- это представление HTML-документа в виде д
@@ -182,7 +182,7 @@ drawHtmlTree(node5, 'div.domtree', 600, 200);
@@ -206,7 +206,7 @@ drawHtmlTree(node6, 'div.domtree', 690, 500);
## Поэкспериментируйте сами
-Чтобы посмотреть структуру DOM в реальном времени, попробуйте [Live DOM Viewer](http://software.hixie.ch/utilities/js/live-dom-viewer/). Просто введите что-нибудь в поле, и ниже вы увидите, как меняется DOM.
+Чтобы посмотреть структуру DOM в реальном времени, попробуйте [Live DOM Viewer](https://software.hixie.ch/utilities/js/live-dom-viewer/). Просто введите что-нибудь в поле, и ниже вы увидите, как меняется DOM.
Другой способ исследовать DOM - это использовать инструменты разработчика браузера. Это то, что мы каждый день делаем при разработке.
diff --git a/2-ui/1-document/03-dom-navigation/1-dom-children/solution.md b/2-ui/1-document/03-dom-navigation/1-dom-children/solution.md
index 732eeff02d..fd383ae49a 100644
--- a/2-ui/1-document/03-dom-navigation/1-dom-children/solution.md
+++ b/2-ui/1-document/03-dom-navigation/1-dom-children/solution.md
@@ -1,6 +1,5 @@
Есть несколько способов для получения элементов, например:
-
DOM-узел элемента `
`:
```js
diff --git a/2-ui/1-document/03-dom-navigation/1-dom-children/task.md b/2-ui/1-document/03-dom-navigation/1-dom-children/task.md
index f4c604ee6e..5d95cf769a 100644
--- a/2-ui/1-document/03-dom-navigation/1-dom-children/task.md
+++ b/2-ui/1-document/03-dom-navigation/1-dom-children/task.md
@@ -18,7 +18,7 @@ importance: 5
```
-Как получить:
-- Напишите код, который получит элемент `
`?
-- Напишите код, который получит `
`?
-- Напишите код, который получит второй `
` (с именем Пит)?
+Напишите код, как получить...
+- элемент `
`?
+- `
`?
+- второй `
` (с именем Пит)?
diff --git a/2-ui/1-document/03-dom-navigation/article.md b/2-ui/1-document/03-dom-navigation/article.md
index ae7e533532..d8ef379328 100644
--- a/2-ui/1-document/03-dom-navigation/article.md
+++ b/2-ui/1-document/03-dom-navigation/article.md
@@ -86,7 +86,7 @@ DOM позволяет нам делать что угодно с элемент