diff --git a/.gitattributes b/.gitattributes index 6313b56c57..d3877a5382 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,2 @@ * text=auto eol=lf +*.svg binary diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000000..ca5355a93c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: Наш Discord-сервер + url: https://discord.gg/X8yWNWpTQs + about: Вопросы по JavaScript, HTML, CSS, Node.js, React, TypeScript, Vue.js, Angular, а также «code review» и обсуждение программирования в целом diff --git a/.github/ISSUE_TEMPLATE/tutorial_suggestion.yml b/.github/ISSUE_TEMPLATE/tutorial_suggestion.yml new file mode 100644 index 0000000000..fc2e5cfa77 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/tutorial_suggestion.yml @@ -0,0 +1,52 @@ +name: 📙 Предложить идею по улучшению материала учебника +description: Предложения о добавлении новых разделов/статей в учебник; о переработке существующих статей; о добавлении новых материалов в статьи +title: "[Предложение: учебник]: " +labels: ["suggestion: tutorial"] +body: + - type: markdown + attributes: + value: | + ## Не открывайте Issue в этой категории, если хотите предложить: + * Исправление/изменение кода в какой-либо из статей + * Исправление неточности/устаревшей информации в какой-либо из статей + * Исправление орфографических/пунктуационных/стилистических ошибок в какой-либо из статей + * Добавление задач в какую-либо из статей + + ...А так же любые другие **небольшие** правки, которые, на ваш взгляд, не требуют обсуждения и могут быть сразу отправлены на рассмотрение. + * «[Как внести правки в учебник?](../blob/master/CONTRIBUTING.md#как-внести-правки-в-учебник)» + ## Открывайте Issue в этой категории, если хотите предложить: + * Переработку какой-либо статьи (или переработку определенной части статьи) + * Добавление новых разделов в учебник + * Добавление новых статей в учебник + * Добавление нового материала в какую-либо из статей + * ...а так же любые другие **крупные** правки + + Перечисленные выше темы требуют предварительного обсуждения с редакторами учебника. + - type: textarea + id: suggestion-description + attributes: + label: Описание + description: Что именно вы хотите предложить? + placeholder: | + Например: «в JavaScript недавно был добавлен [...] — на мой взгляд, было бы неплохо сделать новую статью на эту тему». + validations: + required: true + - type: textarea + id: suggestion-motivation + attributes: + label: Мотивация + description: Почему предлагаемые правки действительно должны быть внесены в учебник? + placeholder: | + Например: «я считаю, что любое представление о [...] без понимания концепции [...] будет недостаточным и неполным, у читателя может сложиться ложное впечатление». + validations: + required: true + - type: dropdown + id: suggestion-question + attributes: + label: Готовы ли вы принять участие в реализации данного предложения? + description: Например, если вы предлагаете добавить новую статью — готовы ли вы заняться её написанием? + options: + - Да, я готов(а) принять участие. + - Нет, я не готов(а) принимать участие. Оставляю эту возможность редакторам учебника. + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/website_suggestion.yml b/.github/ISSUE_TEMPLATE/website_suggestion.yml new file mode 100644 index 0000000000..fb2f960925 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/website_suggestion.yml @@ -0,0 +1,23 @@ +name: 🌐 Предложить идею по улучшению веб-сайта +description: Предложения о добавлении нового функционала на сайт учебника; ошибки/опечатки во вкладках «Курсы», «Тесты знаний», «Скринкасты», «о проекте» и т.д; идеи по улучшению курсов +title: "[Предложение: веб-сайт]: " +labels: ["suggestion: website"] +body: + - type: markdown + attributes: + value: | + Всё, что касается именно **веб-сайта**, на котором опубликован учебник — [https://learn.javascript.ru](https://learn.javascript.ru). + + В этой категории вы можете: + * Предложить добавление нового функционала на веб-сайт учебника + * Сообщить об ошибках/опечатках во вкладках «Курсы», «Тесты знаний», «Скринкасты», «о проекте» и т.д. + * Предложить идеи по улучшению курсов + - type: textarea + id: suggestion-description + attributes: + label: Описание + description: Что именно вы хотите предложить (или о чём хотите сообщить)? + placeholder: | + Например: «мне кажется, было бы неплохо добавить возможность [...]». + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/wquestion.yml b/.github/ISSUE_TEMPLATE/wquestion.yml new file mode 100644 index 0000000000..c92886b37b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/wquestion.yml @@ -0,0 +1,20 @@ +name: 📨 Задать вопрос +description: Вопросы по материалу учебника; по веб-сайту; по редактированию учебника +title: "[Вопрос]: " +labels: ["question"] +body: + - type: markdown + attributes: + value: | + Любые вопросы по материалу учебника; по веб-сайту; по редактированию учебника и т.д. + + ### **Мы не консультируем по вопросам о JavaScript здесь.** Для этих целей есть [Discord-сервер](https://discord.gg/X8yWNWpTQs). + - type: textarea + id: question-description + attributes: + label: Описание + description: О чём вы хотите спросить? + placeholder: | + Например: «почему раздел [...] идёт перед разделом [...], а не после него?». + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/ybug_report.yml b/.github/ISSUE_TEMPLATE/ybug_report.yml new file mode 100644 index 0000000000..d9846d42f6 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/ybug_report.yml @@ -0,0 +1,36 @@ +name: 🛑 Сообщить о баге +description: На сайте учебника что-то неправильно отображается; не работает; работает не так, как нужно +title: "[Баг]: " +labels: ["bug"] +body: + - type: markdown + attributes: + value: | + **Пожалуйста, заполните форму ниже максимально точной информацией.**
Правильное описание бага поможет нам намного быстрее решить проблему. + - type: textarea + id: bug-description + attributes: + label: Описание + description: В чём именно заключается баг? + placeholder: | + Например: «в статье [...] неправильно отображается [...]». + validations: + required: true + - type: textarea + id: bug-reproduce + attributes: + label: Как воспроизвести? + description: Что нужно сделать, чтобы воспроизвести этот баг? + placeholder: | + Например: «чтобы воспроизвести баг, нужно: (1) открыть страницу [...] (2) перейти в раздел [...] (3) нажать на [...]». + validations: + required: true + - type: textarea + id: bug-browsers + attributes: + label: Браузер + description: В каком браузере у вас воспроизводится этот баг? Какая версия этого браузера? (можно указать несколько, если тестировали не в одном браузере) + placeholder: | + Например: «у меня воспроизводится этот баг в Chrome 116.0.5845.98» + validations: + required: true diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000000..5ad133a198 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,20 @@ + + +## Описание + + + +## Ссылки + + + + + + + + + + +## Связанные Issue + + diff --git a/1-js/01-getting-started/1-intro/article.md b/1-js/01-getting-started/1-intro/article.md index 302299ef15..64ef99485d 100644 --- a/1-js/01-getting-started/1-intro/article.md +++ b/1-js/01-getting-started/1-intro/article.md @@ -8,80 +8,80 @@ Программы на этом языке называются *скриптами*. Они могут встраиваться в HTML и выполняться автоматически при загрузке веб-страницы. -Скрипты распространяются и выполняются как простой текст. Им не нужна специальная подготовка или компиляция для запуска. +Скрипты распространяются и выполняются, как простой текст. Им не нужна специальная подготовка или компиляция для запуска. Это отличает JavaScript от другого языка - [Java](https://ru.wikipedia.org/wiki/Java). ```smart header="Почему JavaScript?" -Когда JavaScript создавался, у него было другое имя - "LiveScript". Однако, Java была очень популярная в то время и было решено, что позиционирование языка как "младшего брата" Java будет полезно. +Когда JavaScript создавался, у него было другое имя - "LiveScript". Однако, язык Java был очень популярен в то время, и было решено, что позиционирование JavaScript как "младшего брата" Java будет полезно. -Со временем JavaScript стал полностью независимым языком со своей собственной спецификацией, называющейся [ECMAScript](http://ru.wikipedia.org/wiki/ECMAScript), и сейчас не имеет никакого отношения к Java. +Со временем JavaScript стал полностью независимым языком со своей собственной спецификацией, называющейся [ECMAScript](https://ru.wikipedia.org/wiki/ECMAScript), и сейчас не имеет никакого отношения к Java. ``` -Сегодня JavaScript может выполнятся не только в браузере, но и на сервере или на любом другом устройстве, которое имеет специальную программу, называющуюся ["движком" JavaScript](https://ru.wikipedia.org/wiki/%D0%94%D0%B2%D0%B8%D0%B6%D0%BE%D0%BA_JavaScript). +Сегодня JavaScript может выполняться не только в браузере, но и на сервере или на любом другом устройстве, которое имеет специальную программу, называющуюся ["движком" JavaScript](https://ru.wikipedia.org/wiki/%D0%94%D0%B2%D0%B8%D0%B6%D0%BE%D0%BA_JavaScript). -У браузере есть собственный движок, который иногда называют "виртуальная машина JavaScript". +У браузера есть собственный движок, который иногда называют "виртуальная машина JavaScript". Разные движки имеют разные "кодовые имена". Например: -- [V8](https://ru.wikipedia.org/wiki/V8_(%D0%B4%D0%B2%D0%B8%D0%B6%D0%BE%D0%BA_JavaScript)) -- в Chrome и Opera. +- [V8](https://ru.wikipedia.org/wiki/V8_(%D0%B4%D0%B2%D0%B8%D0%B6%D0%BE%D0%BA_JavaScript)) -- в Chrome, Opera и Edge. - [SpiderMonkey](https://ru.wikipedia.org/wiki/SpiderMonkey) -- в Firefox. -- ...Ещё есть "Trident" и "Chakra" для разных версий IE, "ChakraCore" для Microsoft Edge, "Nitro" и "SquirrelFish" для Safari и т.д. +- ...Ещё есть "Chakra" для IE, "JavaScriptCore", "Nitro" и "SquirrelFish" для Safari и т.д. -Эти названия полезно знать, так как они часто используются в статьях для разработчиков. Мы тоже будем их использовать. Например, если "функциональность X поддерживается V8", тогда "Х", скорее всего, работает в Chrome и Opera. +Эти названия полезно знать, так как они часто используются в статьях для разработчиков. Мы тоже будем их использовать. Например, если "функциональность X поддерживается V8", тогда "Х", скорее всего, работает в Chrome, Opera и Edge. ```smart header="Как работают движки?" Движки сложны. Но основы понять легко. -1. Движок (встроенный, если это браузер) читает ("парсит") сценарий. -2. Затем он преобразует ("компилирует") сценарий в машинный язык. -3. После этого машинный код запускается и весьма быстро работает. +1. Движок (встроенный, если это браузер) читает ("парсит") текст скрипта. +2. Затем он преобразует ("компилирует") скрипт в машинный язык. +3. После этого машинный код запускается и работает достаточно быстро. Движок применяет оптимизации на каждом этапе. Он даже просматривает скомпилированный скрипт во время его работы, анализируя проходящие через него данные, и применяет оптимизации к машинному коду, полагаясь на полученные знания. В результате скрипты работают очень быстро. ``` ## Что может JavaScript в браузере? -Современный JavaScript - это "безопасный" язык программирования. Он не предоставляет низкоуровневый доступ к памяти или процессору, потому что он изначально был создан для браузеров, не требующих этого. +Современный JavaScript - это "безопасный" язык программирования. Он не предоставляет низкоуровневый доступ к памяти или процессору, потому что изначально был создан для браузеров, не требующих этого. -Возможности JavaScript сильно зависят от окружения в котором он работает. Например, [Node.JS](https://ru.wikipedia.org/wiki/Node.js) поддерживает функции чтения/записи произвольных файлов, выполнения сетевых запросов, и т.д. +Возможности JavaScript сильно зависят от окружения, в котором он работает. Например, [Node.js](https://ru.wikipedia.org/wiki/Node.js) поддерживает функции чтения/записи произвольных файлов, выполнения сетевых запросов и т.д. В браузере для JavaScript доступно всё, что связано с манипулированием веб-страницами, взаимодействием с пользователем и веб-сервером. Например, в браузере JavaScript может: -- Добавлять новый HTML на страницу, изменять существующее содержимое, модифицировать стили. +- Добавлять новый HTML-код на страницу, изменять существующее содержимое, модифицировать стили. - Реагировать на действия пользователя, щелчки мыши, перемещения указателя, нажатия клавиш. - Отправлять сетевые запросы на удалённые сервера, скачивать и загружать файлы (технологии [AJAX](https://ru.wikipedia.org/wiki/AJAX) и [COMET](https://ru.wikipedia.org/wiki/Comet_(%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))). - Получать и устанавливать куки, задавать вопросы посетителю, показывать сообщения. - Запоминать данные на стороне клиента ("local storage"). -## Что НЕ может JavaScript в браузере? +## Чего НЕ может JavaScript в браузере? -Возможности JavaScript в браузере ограничены ради безопасности пользователя. Цель заключается в предотвращении доступа не добросовестной веб-страницы к частной информации или нанесению ущерба данным пользователя. +Возможности JavaScript в браузере ограничены ради безопасности пользователя. Цель заключается в предотвращении доступа недобросовестной веб-страницы к личной информации или нанесения ущерба данным пользователя. Примеры таких ограничений включают в себя: -- JavaScript на веб-странице не может читать/записывать произвольные файлы на жестком диске, копировать их или запускать программы. Он не имеет прямого доступа к системным функциям ОС. +- JavaScript на веб-странице не может читать/записывать произвольные файлы на жёстком диске, копировать их или запускать программы. Он не имеет прямого доступа к системным функциям ОС. - Современные браузеры позволяют ему работать с файлами, но с ограниченным доступом и предоставляют его только если пользователь выполняет определённые действия, такие как "перетаскивание" файла в окно браузера или его выбор с помощью тега ``. + Современные браузеры позволяют ему работать с файлами, но с ограниченным доступом, и предоставляют его, только если пользователь выполняет определённые действия, такие как "перетаскивание" файла в окно браузера или его выбор с помощью тега ``. Существуют способы взаимодействия с камерой/микрофоном и другими устройствами, но они требуют явного разрешения пользователя. Таким образом, страница с поддержкой JavaScript не может незаметно включить веб-камеру, наблюдать за происходящим и отправлять информацию в [ФСБ](https://ru.wikipedia.org/wiki/%D0%A4%D0%B5%D0%B4%D0%B5%D1%80%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F_%D1%81%D0%BB%D1%83%D0%B6%D0%B1%D0%B0_%D0%B1%D0%B5%D0%B7%D0%BE%D0%BF%D0%B0%D1%81%D0%BD%D0%BE%D1%81%D1%82%D0%B8_%D0%A0%D0%BE%D1%81%D1%81%D0%B8%D0%B9%D1%81%D0%BA%D0%BE%D0%B9_%D0%A4%D0%B5%D0%B4%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D0%B8). -- Различные окна/вкладки не знают друг о друге. Иногда одно окно, используя JavaScript, открывает другое окно. Но даже в этом случае, JavaScript с одной страницы не имеет доступа к другой, если они пришли с разных сайтов (с другого домена, протокола или порта). +- Различные окна/вкладки не знают друг о друге. Иногда одно окно, используя JavaScript, открывает другое окно. Но даже в этом случае JavaScript с одной страницы не имеет доступа к другой, если они пришли с разных сайтов (с другого домена, протокола или порта). - Это называется "Политика одинакового источника" (Same Origin Policy). Чтобы обойти это ограничение, обе страницы должны быть на это согласны и содержать JavaScript-код, который специальным образом обменивается данными. + Это называется "Политика одинакового источника" (Same Origin Policy). Чтобы обойти это ограничение, обе страницы должны согласиться с этим и содержать JavaScript-код, который специальным образом обменивается данными. - Это ограничение необходимо, опять же, для безопасности пользователя. Страница `http://anysite.com`, которую открыл пользователь, не должна иметь доступ к другой вкладке браузера с URL `http://gmail.com` и воровать информацию оттуда. -- JavaScript может легко взаимодействовать с сервером, с которого пришла текущая страница. Но его способность получать данные с других сайтов/доменов ограничена. Хотя это возможно, требуется явное соглашение (выраженное в заголовках HTTP) с удалённой стороной. Опять же, это ограничение безопасности. + Это ограничение необходимо, опять же, для безопасности пользователя. Страница `https://anysite.com`, которую открыл пользователь, не должна иметь доступ к другой вкладке браузера с URL `https://gmail.com` и воровать информацию оттуда. +- JavaScript может легко взаимодействовать с сервером, с которого пришла текущая страница. Но его способность получать данные с других сайтов/доменов ограничена. Хотя это возможно в принципе, для чего требуется явное согласие (выраженное в заголовках HTTP) с удалённой стороной. Опять же, это ограничение безопасности. -![](limitations.png) +![](limitations.svg) -Подобные ограничения не действуют, если JavaScript используется вне браузера, например — на сервере. Современные браузеры предоставляют плагины/расширения с помощью которых можно запрашивать дополнительные разрешения. +Подобные ограничения не действуют, если JavaScript используется вне браузера, например — на сервере. Современные браузеры предоставляют плагины/расширения, с помощью которых можно запрашивать дополнительные разрешения. ## Что делает JavaScript особенным? -Как минимум *три* сильные стороны JavaScript: +Как минимум, *три* сильные стороны JavaScript: ```compare + Полная интеграция с HTML/CSS. @@ -90,31 +90,32 @@ ``` JavaScript - это единственная браузерная технология, сочетающая в себе все эти три вещи. -Вот что делает JavaScript особенным. Вот почему это самый распространенный инструмент для создания интерфейсов в браузере. - -Планируя изучить новую технологию, полезно оценить её перспективы. Итак, давайте перейдём к современным тенденциям, включая новые языки и браузерные технологии. +Вот что делает JavaScript особенным. Вот почему это самый распространённый инструмент для создания интерфейсов в браузере. +Хотя, конечно, JavaScript позволяет делать приложения не только в браузерах, но и на сервере, на мобильных устройствах и т.п. ## Языки "над" JavaScript -Синтаксис JavaScript подходит не под все нужны. Разные люди хотят иметь разные возможности. +Синтаксис JavaScript подходит не под все нужды. Разные люди хотят иметь разные возможности. Это естественно, потому что проекты разные и требования к ним тоже разные. -Так, в последнее время, появилось много новых языков, которые *транспилируются* (конвертируются) в JavaScript прежде, чем запустятся в браузере. +Так, в последнее время появилось много новых языков, которые *транспилируются* (конвертируются) в JavaScript, прежде чем запустятся в браузере. Современные инструменты делают транспиляцию очень быстрой и прозрачной, фактически позволяя разработчикам писать код на другом языке, автоматически преобразуя его в JavaScript "под капотом". Примеры таких языков: -- [CoffeeScript](http://coffeescript.org/) добавляет "синтаксический сахар" для JavaScript. Он вводит более короткий синтаксис, которые позволяет писать чистый и лаконичный код. Обычно такое нравится Ruby-программистам. -- [TypeScript](http://www.typescriptlang.org/) концентрируется на добавлении "строгой типизации" для упрощения разработки и поддержки больших и сложных систем. Разработан Microsoft. -- [Dart](https://www.dartlang.org/) стоит особняком, потому что имеет собственный движок работающий вне браузера (например, в мобильных приложениях). Первоначально был предложен Google как замена JavaScript, но на данный момент, необходима его транспиляция для запуска, так же как для языков выше. +- [CoffeeScript](https://coffeescript.org/) добавляет "синтаксический сахар" для JavaScript. Он вводит более короткий синтаксис, который позволяет писать чистый и лаконичный код. Обычно такое нравится Ruby-программистам. +- [TypeScript](https://www.typescriptlang.org/) концентрируется на добавлении "строгой типизации" для упрощения разработки и поддержки больших и сложных систем. Разработан Microsoft. +- [Flow](https://flow.org/) тоже добавляет типизацию, но иначе. Разработан Facebook. +- [Dart](https://www.dartlang.org/) стоит особняком, потому что имеет собственный движок, работающий вне браузера (например, в мобильных приложениях). Первоначально был предложен Google, как замена JavaScript, но на данный момент необходима его транспиляция для запуска так же, как для вышеперечисленных языков. +- [Brython](https://brython.info/) транспилирует Python в JavaScript, что позволяет писать приложения на чистом Python без JavaScript. -Есть и другие. Но даже если мы используем один из этих языков, мы, безусловно, должны знать JavaScript, чтобы действительно понимать, что мы делаем. +Есть и другие. Но даже если мы используем один из этих языков, мы должны знать JavaScript, чтобы действительно понимать, что мы делаем. ## Итого - JavaScript изначально создавался только для браузера, но сейчас используется на многих других платформах. -- Сегодня JavaScript занимает уникальную позицию в качестве самого распространенного языка для браузера обладающего полной интеграцией с HTML/CSS. +- Сегодня JavaScript занимает уникальную позицию в качестве самого распространённого языка для браузера, обладающего полной интеграцией с HTML/CSS. - Многие языки могут быть "транспилированы" в JavaScript для предоставления дополнительных функций. Рекомендуется хотя бы кратко рассмотреть их после освоения JavaScript. diff --git a/1-js/01-getting-started/1-intro/limitations.png b/1-js/01-getting-started/1-intro/limitations.png deleted file mode 100644 index 3ef6b1e087..0000000000 Binary files a/1-js/01-getting-started/1-intro/limitations.png and /dev/null differ diff --git a/1-js/01-getting-started/1-intro/limitations.svg b/1-js/01-getting-started/1-intro/limitations.svg new file mode 100644 index 0000000000..76ea43fd7a --- /dev/null +++ b/1-js/01-getting-started/1-intro/limitations.svg @@ -0,0 +1 @@ +https://javascript.info<script> ... </script>https://gmail.comhttps://javascript.info \ No newline at end of file diff --git a/1-js/01-getting-started/1-intro/limitations@2x.png b/1-js/01-getting-started/1-intro/limitations@2x.png deleted file mode 100644 index 7aa1c41ae4..0000000000 Binary files a/1-js/01-getting-started/1-intro/limitations@2x.png and /dev/null differ diff --git a/1-js/01-getting-started/2-code-editors/article.md b/1-js/01-getting-started/2-code-editors/article.md deleted file mode 100644 index d36561bc6f..0000000000 --- a/1-js/01-getting-started/2-code-editors/article.md +++ /dev/null @@ -1,57 +0,0 @@ -# Code editors - -A code editor is the place where programmers spend most of their time. - -There are two main types of code editors: IDEs and lightweight editors. Many people use one tool of each type. - -## IDE - -The term [IDE](https://en.wikipedia.org/wiki/Integrated_development_environment) (Integrated Development Environment) refers to a powerful editor with many features that usually operates on a "whole project." As the name suggests, it's not just an editor, but a full-scale "development environment." - -An IDE loads the project (which can be many files), allows navigation between files, provides autocompletion based on the whole project (not just the open file), and integrates with a version management system (like [git](https://git-scm.com/)), a testing environment, and other "project-level" stuff. - -If you haven't selected an IDE yet, consider the following options: - -- [WebStorm](http://www.jetbrains.com/webstorm/) for frontend development. The same company offers other editors for other languages (paid). -- [Netbeans](http://netbeans.org/) (free). - -All of these IDEs are cross-platform. - -For Windows, there's also "Visual Studio", not to be confused with "Visual Studio Code." "Visual Studio" is a paid and mighty Windows-only editor, well-suited for the .NET platform. A free version of it is called [Visual Studio Community](https://www.visualstudio.com/vs/community/). - -Many IDEs are paid but have a trial period. Their cost is usually negligible compared to a qualified developer's salary, so just choose the best one for you. - -## Lightweight editors - -"Lightweight editors" are not as powerful as IDEs, but they're fast, elegant and simple. - -They are mainly used to open and edit a file instantly. - -The main difference between a "lightweight editor" and an "IDE" is that an IDE works on a project-level, so it loads much more data on start, analyzes the project structure if needed and so on. A lightweight editor is much faster if we need only one file. - -In practice, lightweight editors may have a lot of plugins including directory-level syntax analyzers and autocompleters, so there's no strict border between a lightweight editor and an IDE. - -The following options deserve your attention: - -- [Visual Studio Code](https://code.visualstudio.com/) (cross-platform, free) also has many IDE-like features. -- [Atom](https://atom.io/) (cross-platform, free). -- [Sublime Text](http://www.sublimetext.com) (cross-platform, shareware). -- [Notepad++](https://notepad-plus-plus.org/) (Windows, free). -- [Vim](http://www.vim.org/) and [Emacs](https://www.gnu.org/software/emacs/) are also cool if you know how to use them. - -## My favorites - -The personal preference of the author is to have both an IDE for projects and a lightweight editor for quick and easy file editing. - -I'm using: - -- As an IDE for JS -- [WebStorm](http://www.jetbrains.com/webstorm/) (I switch to one of the other JetBrains offerings when using other languages) -- As a lightweight editor -- [Sublime Text](http://www.sublimetext.com) or [Atom](https://atom.io/). - -## Let's not argue - -The editors in the lists above are those that either I or my friends whom I consider good developers have been using for a long time and are happy with. - -There are other great editors in our big world. Please choose the one you like the most. - -The choice of an editor, like any other tool, is individual and depends on your projects, habits, and personal preferences. diff --git a/1-js/01-getting-started/2-manuals-specifications/article.md b/1-js/01-getting-started/2-manuals-specifications/article.md new file mode 100644 index 0000000000..df109cd933 --- /dev/null +++ b/1-js/01-getting-started/2-manuals-specifications/article.md @@ -0,0 +1,37 @@ + +# Справочники и спецификации + +Эта книга является *учебником* и нацелена на то, чтобы помочь вам постепенно освоить язык. Но когда вы хорошо изучите основы, вам понадобятся дополнительные источники информации. + +## Спецификация + +[Спецификация ECMA-262](https://www.ecma-international.org/publications-and-standards/standards/ecma-262/) содержит самую глубокую, детальную и формализованную информацию о JavaScript. Она определяет сам язык. + +Вначале спецификация может показаться тяжеловатой для понимания из-за слишком формального стиля изложения. Если вы ищете источник самой достоверной информации, то это правильное место, но она не для ежедневного использования. + +Новая версия спецификации появляется каждый год. А пока она не вышла официально, все желающие могут ознакомиться с текущим черновиком на . + +Чтобы почитать о самых последних возможностях, включая те, которые "почти в стандарте" (так называемые "stage 3 proposals"), посетите . + +Если вы разрабатываете под браузеры, то существуют и другие спецификации, о которых рассказывается во [второй части](info:browser-environment) этого учебника. + +## Справочники + +- **MDN (Mozilla) JavaScript Reference** -- это справочник с примерами и другой информацией. Хороший источник для получения подробных сведений о функциях языка, методах встроенных объектов и так далее. + + Располагается по адресу . + + Хотя зачастую вместо их сайта удобнее использовать какой-нибудь интернет-поисковик, вводя там запрос "MDN [что вы хотите найти]", например для поиска информации о функции `parseInt`. + +## Таблицы совместимости + +JavaScript -- это развивающийся язык, в который постоянно добавляется что-то новое. + +Посмотреть, какие возможности поддерживаются в разных браузерах и других движках, можно в следующих источниках: + +- - таблицы с информацией о поддержке по каждой возможности языка. Например, чтобы узнать, какие движки поддерживают современные криптографические функции, посетите: . +- - таблица с возможностями языка и движками, которые их поддерживают и не поддерживают. + +Все эти ресурсы полезны в ежедневной работе программиста, так как они содержат ценную информацию о возможностях использования языка, их поддержке и так далее. + +Пожалуйста, запомните эти ссылки (или ссылку на эту страницу) на случай, когда вам понадобится подробная информация о какой-нибудь конкретной возможности JavaScript. diff --git a/1-js/01-getting-started/3-code-editors/article.md b/1-js/01-getting-started/3-code-editors/article.md new file mode 100644 index 0000000000..c2078d5efa --- /dev/null +++ b/1-js/01-getting-started/3-code-editors/article.md @@ -0,0 +1,44 @@ +# Редакторы кода + +Большую часть своего рабочего времени программисты проводят в редакторах кода. + +Есть два основных типа редакторов: IDE и "лёгкие" редакторы. Многие используют по одному инструменту каждого типа. + +## IDE + +Термином [IDE](https://ru.wikipedia.org/wiki/Integrated_development_environment) (Integrated Development Environment, "интегрированная среда разработки") называют мощные редакторы с множеством функций, которые работают в рамках целого проекта. Как видно из названия, это не просто редактор, а нечто большее. + +IDE загружает проект (который может состоять из множества файлов), позволяет переключаться между файлами, предлагает автодополнение по коду всего проекта (а не только открытого файла), также она интегрирована с системой контроля версий (например, такой как [git](https://git-scm.com/)), средой для тестирования и другими инструментами на уровне всего проекта. + +Если вы ещё не выбрали себе IDE, присмотритесь к этим: + +- [Visual Studio Code](https://code.visualstudio.com/) (кросс-платформенная, бесплатная). +- [WebStorm](https://www.jetbrains.com/webstorm/) (кросс-платформенная, бесплатная для некоммерческого использования). + +Для Windows есть ещё Visual Studio (не путать с Visual Studio Code). Visual Studio - это платная мощная среда разработки, которая работает только на Windows. Она хорошо подходит для .NET платформы. У неё есть бесплатная версия, которая называется [Visual Studio Community](https://www.visualstudio.com/vs/community/). + +Многие IDE платные, но у них есть пробный период. Их цена обычно незначительна по сравнению с зарплатой квалифицированного разработчика, так что пробуйте и выбирайте ту, что вам подходит лучше других. + +## "Лёгкие" редакторы + +"Лёгкие" редакторы менее мощные, чем IDE, но они отличаются скоростью, удобным интерфейсом и простотой. + +В основном их используют для того, чтобы быстро открыть и отредактировать нужный файл. + +Главное отличие между "лёгким" редактором и IDE состоит в том, что IDE работает на уровне целого проекта, поэтому она загружает больше данных при запуске, анализирует структуру проекта, если это необходимо, и так далее. Если вы работаете только с одним файлом, то гораздо быстрее открыть его в "лёгком" редакторе. + +На практике "лёгкие" редакторы могут иметь множество плагинов, включая автодополнение и анализаторы синтаксиса на уровне директории, поэтому границы между IDE и "лёгкими" редакторами размыты. + +Следующие варианты заслуживают вашего внимания: + +- [Sublime Text](https://www.sublimetext.com) (кроссплатформенный, условно-бесплатный). +- [Notepad++](https://notepad-plus-plus.org/) (Windows, бесплатный). +- [Vim](https://www.vim.org/) и [Emacs](https://www.gnu.org/software/emacs/) тоже хороши, если знать, как ими пользоваться. + +## Не будем ссориться + +Редакторы, перечисленные выше, известны автору давно и заслужили много хороших отзывов от коллег. + +Конечно же, есть много других отличных редакторов. Выбирайте тот, который вам больше нравится. + +Выбор редактора, как и любого другого инструмента, индивидуален и зависит от ваших проектов, привычек и личных предпочтений. diff --git a/1-js/01-getting-started/3-devtools/article.md b/1-js/01-getting-started/3-devtools/article.md deleted file mode 100644 index 5ce9387e8a..0000000000 --- a/1-js/01-getting-started/3-devtools/article.md +++ /dev/null @@ -1,64 +0,0 @@ -# Консоль разработчика - -Код уязвим для ошибок. И вы, скорее всего, будете делать ошибки в коде... Впрочем, давайте будем откровенны: вы *точно* будете совершать ошибки в коде. В конце концов, вы человек, а не [робот](https://ru.wikipedia.org/wiki/%D0%91%D0%B5%D0%BD%D0%B4%D0%B5%D1%80_(%D0%A4%D1%83%D1%82%D1%83%D1%80%D0%B0%D0%BC%D0%B0)). - -Но по умолчанию в браузере ошибки не видны. То есть, если что-то пойдет не так, мы не увидим, что именно сломалось и не сможем это починить. - -Для решения задач такого рода в браузер встроены так называемые "Инструменты разработки" (Developer tools, или сокращенно — devtools). - -Chrome и Firefox снискали любовь подавляющего большинства программистов во многом благодаря своим отменным инструментам разработчика. Остальные браузеры хоть и оснащены подобными инструментами, но все же зачастую находятся в роли догоняющих и по качеству и по количеству свойств и особенностей. В общем, почти у всех программистов есть свой "любимый" браузер. Другие используются только для отлова и исправления специфичных "браузерозависимых" багов. - -Для начала знакомства с этими мощными инструментами давайте выясним, как их открывать, смотреть ошибки и запускать команды JavaScript. - -## Google Chrome - -Откройте страницу [bug.html](bug.html). - -В ее JavaScript код закралась ошибка. Она не видна обычному посетителю, поэтому давайте найдем ее при помощи devtools. - -Нажмите `key:F12` или, если вы используете Mac, `key:Cmd+Opt+J`. - -По умолчанию в инструментах разработчика откроется вкладка Console (консоль). - -Она выглядит приблизительно следующим образом: - -![chrome](chrome.png) - -Точный внешний вид инструментов разработки зависит от используемой версии Chrome. Время от времени некоторые детали изменяются, но в целом внешний вид остается примерно похожим на предыдущие версии. - -- В консоли мы можем увидеть сообщение об ошибке, отрисованное красным цветом. В нашем случае скрипт содержит неизвестную команду "lalala". -- Справа присутствует кликабельная ссылка на исходный код `bug.html:12` с номером строки кода, в которой эта ошибка и произошла. - -Под сообщением об ошибке находится синий символ `>`. Он обозначает командную строку, в ней мы можем редактировать и запускать JavaScript-команды. Для их запуска нажмите `key:Enter` (для переноса строки жмите `key:Shift+Enter`). - -Теперь мы явно видим ошибки, для начала этого вполне достаточно. Мы еще вернемся к инструментам разработчика позже и более подробно рассмотрим отладку кода в главе . - - -## Firefox, Edge и другие - -Инструменты разработчика в большинстве браузеров привязаны к кнопке `key:F12`. - -Их внешний вид и принципы работы мало чем отличаются. Разобравшись с инструментами в одном браузере вы без труда сможете работать с ними и в другом. - -## Safari - -Safari (браузер для Mac, не поддерживается в системах Windows/Linux) все же имеет небольшое отличие. Для начала работы нам нужно включить "Меню разработки" ("Developer menu"). - -Откройте настройки (Preferences) и перейдите к панели "Продвинутые" (Advanced). В самом низу вы найдете чекбокс: - -![safari](safari.png) - -Теперь консоль можно активировать нажатием клавиш `key:Cmd+Opt+C`. Также обратите внимание на новый элемент меню "Разработка" ("Develop"). В нем содержится большое количество команд и настроек. - -## Многострочный ввод - -Обычно, при нажатии `key:Enter` введенная строка кода сразу выполняется. - -Чтобы перенести строку нажмите `key:Shift+Enter`. - -## Итого - -- Инструменты разработчика позволяют нам смотреть ошибки, выполнять команды, проверять значение переменных и еще много всего полезного. -- В большинстве браузеров, работающих под Windows, инструменты разработчика можно открыть, нажав `key:F12`. В Chrome для Mac используйте комбинацию `key:Cmd+Opt+J`, Safari: `key:Cmd+Opt+C` (необходимо предварительное включение "меню разработчика"). - -Теперь наше окружение полностью настроено. В следующем разделе мы перейдем непосредственно к JavaScript. \ No newline at end of file diff --git a/1-js/01-getting-started/3-devtools/bug.html b/1-js/01-getting-started/3-devtools/bug.html deleted file mode 100644 index edb02375ca..0000000000 --- a/1-js/01-getting-started/3-devtools/bug.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - There is an error in the script on this page. - - - - - \ No newline at end of file diff --git a/1-js/01-getting-started/3-devtools/chrome.png b/1-js/01-getting-started/3-devtools/chrome.png deleted file mode 100644 index 4cb3ea2f46..0000000000 Binary files a/1-js/01-getting-started/3-devtools/chrome.png and /dev/null differ diff --git a/1-js/01-getting-started/3-devtools/chrome@2x.png b/1-js/01-getting-started/3-devtools/chrome@2x.png deleted file mode 100644 index b87404a8f4..0000000000 Binary files a/1-js/01-getting-started/3-devtools/chrome@2x.png and /dev/null differ diff --git a/1-js/01-getting-started/3-devtools/safari.png b/1-js/01-getting-started/3-devtools/safari.png deleted file mode 100644 index 37598a2610..0000000000 Binary files a/1-js/01-getting-started/3-devtools/safari.png and /dev/null differ diff --git a/1-js/01-getting-started/3-devtools/safari@2x.png b/1-js/01-getting-started/3-devtools/safari@2x.png deleted file mode 100644 index c59cebef21..0000000000 Binary files a/1-js/01-getting-started/3-devtools/safari@2x.png and /dev/null differ diff --git a/1-js/01-getting-started/4-devtools/article.md b/1-js/01-getting-started/4-devtools/article.md new file mode 100644 index 0000000000..1d52219ffc --- /dev/null +++ b/1-js/01-getting-started/4-devtools/article.md @@ -0,0 +1,64 @@ +# Консоль разработчика + +Код уязвим для ошибок. И вы, скорее всего, будете делать ошибки в коде... Впрочем, давайте будем откровенны: вы *точно* будете совершать ошибки в коде. В конце концов, вы человек, а не [робот](https://ru.wikipedia.org/wiki/%D0%91%D0%B5%D0%BD%D0%B4%D0%B5%D1%80_(%D0%A4%D1%83%D1%82%D1%83%D1%80%D0%B0%D0%BC%D0%B0)). + +Но по умолчанию в браузере ошибки не видны. То есть, если что-то пойдёт не так, мы не увидим, что именно сломалось, и не сможем это починить. + +Для решения задач такого рода в браузер встроены так называемые "Инструменты разработки" (Developer tools или сокращённо — devtools). + +Chrome и Firefox снискали любовь подавляющего большинства программистов во многом благодаря своим отменным инструментам разработчика. Остальные браузеры, хотя и оснащены подобными инструментами, но всё же зачастую находятся в роли догоняющих и по качеству, и по количеству свойств и особенностей. В общем, почти у всех программистов есть свой "любимый" браузер. Другие используются только для отлова и исправления специфичных "браузерозависимых" ошибок. + +Для начала знакомства с этими мощными инструментами давайте выясним, как их открывать, смотреть ошибки и запускать команды JavaScript. + +## Google Chrome + +Откройте страницу [bug.html](bug.html). + +В её JavaScript-коде закралась ошибка. Она не видна обычному посетителю, поэтому давайте найдём её при помощи инструментов разработки. + +Нажмите `key:F12` или, если вы используете Mac, `key:Cmd+Opt+J`. + +По умолчанию в инструментах разработчика откроется вкладка Console (консоль). + +Она выглядит приблизительно следующим образом: + +![](chrome.webp) + +Точный внешний вид инструментов разработки зависит от используемой версии Chrome. Время от времени некоторые детали изменяются, но в целом внешний вид остаётся примерно похожим на предыдущие версии. + +- В консоли мы можем увидеть сообщение об ошибке, отрисованное красным цветом. В нашем случае скрипт содержит неизвестную команду "lalala". +- Справа присутствует ссылка на исходный код `bug.html:12` с номером строки кода, в которой эта ошибка и произошла. + +Под сообщением об ошибке находится синий символ `>`. Он обозначает командную строку, в ней мы можем редактировать и запускать JavaScript-команды. Для их запуска нажмите `key:Enter`. + +```smart header="Многострочный ввод" +Обычно при нажатии `key:Enter` введённая строка кода сразу выполняется. + +Чтобы перенести строку, нажмите `key:Shift+Enter`. Так можно вводить более длинный JS-код. +``` + +Теперь мы явно видим ошибки, для начала этого вполне достаточно. Мы ещё вернёмся к инструментам разработчика позже и более подробно рассмотрим отладку кода в главе . + + +## Firefox, Edge и другие + +Инструменты разработчика в большинстве браузеров открываются при нажатии на `key:F12`. + +Их внешний вид и принципы работы мало чем отличаются. Разобравшись с инструментами в одном браузере, вы без труда сможете работать с ними и в другом. + +## Safari + +Safari (браузер для Mac, не поддерживается в системах Windows/Linux) всё же имеет небольшое отличие. Для начала работы нам нужно включить "Меню разработки" ("Developer menu"). + +Откройте Настройки (Preferences) и перейдите к панели "Продвинутые" (Advanced). В самом низу вы найдёте чекбокс: + +![](safari.png) + +Теперь консоль можно активировать нажатием клавиш `key:Cmd+Opt+C`. Также обратите внимание на новый элемент меню "Разработка" ("Develop"). В нем содержится большое количество команд и настроек. + +## Итого + +- Инструменты разработчика позволяют нам смотреть ошибки, выполнять команды, проверять значение переменных и ещё много всего полезного. +- В большинстве браузеров, работающих под Windows, инструменты разработчика можно открыть, нажав `key:F12`. В Chrome для Mac используйте комбинацию `key:Cmd+Opt+J`, Safari: `key:Cmd+Opt+C` (необходимо предварительное включение "Меню разработчика"). + +Теперь наше окружение полностью настроено. В следующем разделе мы перейдём непосредственно к JavaScript. diff --git a/1-js/01-getting-started/4-devtools/bug.html b/1-js/01-getting-started/4-devtools/bug.html new file mode 100644 index 0000000000..6e25cf9cd8 --- /dev/null +++ b/1-js/01-getting-started/4-devtools/bug.html @@ -0,0 +1,17 @@ + + + + + + + + + + На этой странице есть ошибка в скрипте. + + + + + \ No newline at end of file diff --git a/1-js/01-getting-started/4-devtools/chrome.webp b/1-js/01-getting-started/4-devtools/chrome.webp new file mode 100644 index 0000000000..bdf067079e Binary files /dev/null and b/1-js/01-getting-started/4-devtools/chrome.webp differ diff --git a/1-js/01-getting-started/4-devtools/safari.png b/1-js/01-getting-started/4-devtools/safari.png new file mode 100644 index 0000000000..4538827eb0 Binary files /dev/null and b/1-js/01-getting-started/4-devtools/safari.png differ diff --git a/1-js/02-first-steps/01-hello-world/1-hello-alert/solution.view/index.html b/1-js/02-first-steps/01-hello-world/1-hello-alert/solution.view/index.html index 45e6744b3a..16a2d2ce1b 100644 --- a/1-js/02-first-steps/01-hello-world/1-hello-alert/solution.view/index.html +++ b/1-js/02-first-steps/01-hello-world/1-hello-alert/solution.view/index.html @@ -1,12 +1,10 @@ - - - \ No newline at end of file + diff --git a/1-js/02-first-steps/01-hello-world/1-hello-alert/task.md b/1-js/02-first-steps/01-hello-world/1-hello-alert/task.md index afed6a91d3..4078520c0d 100644 --- a/1-js/02-first-steps/01-hello-world/1-hello-alert/task.md +++ b/1-js/02-first-steps/01-hello-world/1-hello-alert/task.md @@ -2,11 +2,10 @@ importance: 5 --- -# Show an alert +# Вызвать alert -Create a page that shows a message "I'm JavaScript!". +Создайте страницу, которая отобразит сообщение "Я JavaScript!". -Do it in a sandbox, or on your hard drive, doesn't matter, just ensure that it works. +Выполните это задание в песочнице, либо на вашем жёстком диске, где - неважно, главное - проверьте, что она работает. [demo src="solution"] - diff --git a/1-js/02-first-steps/01-hello-world/2-hello-alert-ext/alert.js b/1-js/02-first-steps/01-hello-world/2-hello-alert-ext/alert.js index 4de725971d..3d5c74a758 100644 --- a/1-js/02-first-steps/01-hello-world/2-hello-alert-ext/alert.js +++ b/1-js/02-first-steps/01-hello-world/2-hello-alert-ext/alert.js @@ -1 +1 @@ -alert("I'm JavaScript!"); \ No newline at end of file +alert("Я JavaScript!"); diff --git a/1-js/02-first-steps/01-hello-world/2-hello-alert-ext/solution.md b/1-js/02-first-steps/01-hello-world/2-hello-alert-ext/solution.md index f42c41e6db..77d0bdac4a 100644 --- a/1-js/02-first-steps/01-hello-world/2-hello-alert-ext/solution.md +++ b/1-js/02-first-steps/01-hello-world/2-hello-alert-ext/solution.md @@ -1,8 +1,8 @@ -The HTML code: +HTML-код: [html src="index.html"] -For the file `alert.js` in the same folder: +Для файла `alert.js` в той же папке: [js src="alert.js"] diff --git a/1-js/02-first-steps/01-hello-world/2-hello-alert-ext/task.md b/1-js/02-first-steps/01-hello-world/2-hello-alert-ext/task.md index 26168d6a76..fd843c8593 100644 --- a/1-js/02-first-steps/01-hello-world/2-hello-alert-ext/task.md +++ b/1-js/02-first-steps/01-hello-world/2-hello-alert-ext/task.md @@ -2,8 +2,8 @@ importance: 5 --- -# Show an alert with an external script +# Покажите сообщение с помощью внешнего скрипта -Take the solution of the previous task . Modify it by extracting the script content into an external file `alert.js`, residing in the same folder. +Возьмите решение предыдущей задачи , и измените его. Извлеките содержимое скрипта во внешний файл `alert.js`, лежащий в той же папке. -Open the page, ensure that the alert works. +Откройте страницу, убедитесь, что оповещение работает. diff --git a/1-js/02-first-steps/01-hello-world/article.md b/1-js/02-first-steps/01-hello-world/article.md index d96ffd76f4..b42e30a558 100644 --- a/1-js/02-first-steps/01-hello-world/article.md +++ b/1-js/02-first-steps/01-hello-world/article.md @@ -1,17 +1,16 @@ -# Hello, world! +# Привет, мир! -The tutorial that you're reading is about core JavaScript, which is platform-independent. Later on, you'll learn about Node.js and other platforms that use it. +В этой части учебника мы изучаем собственно JavaScript, сам язык. -But we need a working environment to run our scripts and, since this book is online, the browser is a good choice. We'll keep the amount of browser-specific commands (like `alert`) to a minimum so that you don't spend time on them if you plan to concentrate on another environment (like Node.js). We'll focus on JavaScript in the browser in the [next part](/ui) of the tutorial. +Но нам нужна рабочая среда для запуска наших скриптов, и, поскольку это онлайн-книга, то браузер будет хорошим выбором. В этой главе мы сократим количество специфичных для браузера команд (например, `alert`) до минимума, чтобы вы не тратили на них время, если планируете сосредоточиться на другой среде (например, Node.js). А на использовании JavaScript в браузере мы сосредоточимся в [следующей части](/ui) учебника. -So first, let's see how we attach a script to a webpage. For server-side environments (like Node.js), you can execute the script with a command like `"node my.js"`. +Итак, сначала давайте посмотрим, как выполнить скрипт на странице. Для серверных сред (например, Node.js), вы можете выполнить скрипт с помощью команды типа `"node my.js"`. Для браузера всё немного иначе. +## Тег "script" -## The "script" tag +Программы на JavaScript могут быть вставлены в любое место HTML-документа с помощью тега ` */!* -

...After the script.

+

...После скрипта.

@@ -35,24 +34,23 @@ For instance: ``` ```online -You can run the example by clicking the "Play" button in the right-top corner of the box above. +Вы можете запустить пример, нажав на кнопку "Play" в правом верхнем углу блока с кодом выше. ``` -The ` ``` - This trick isn't used in modern JavaScript. These comments hid JavaScript code from old browsers that didn't know how to process the ` ``` -Here, `/path/to/script.js` is an absolute path to the script file (from the site root). +Здесь `/path/to/script.js` - это абсолютный путь от корневой папки до необходимого файла. Корневой папкой может быть корень диска или корень сайта, в зависимости от условий работы сайта. Также можно указать относительный путь от текущей страницы. Например, `src="script.js"` или `src="./script.js"` будет означать, что файл `"script.js"` находится в текущей папке. -You can also provide a relative path from the current page. For instance, `src="script.js"` would mean a file `"script.js"` in the current folder. - -We can give a full URL as well. For instance: +Можно указать и полный URL-адрес. Например: ```html ``` -To attach several scripts, use multiple tags: +Для подключения нескольких скриптов используйте несколько тегов: ```html @@ -92,29 +87,29 @@ To attach several scripts, use multiple tags: ``` ```smart -As a rule, only the simplest scripts are put into HTML. More complex ones reside in separate files. +Как правило, только простейшие скрипты помещаются в HTML. Более сложные выделяются в отдельные файлы. -The benefit of a separate file is that the browser will download it and store it in its [cache](https://en.wikipedia.org/wiki/Web_cache). +Польза отдельных файлов в том, что браузер загрузит скрипт отдельно и сможет хранить его в [кеше](https://en.wikipedia.org/wiki/Web_cache). -Other pages that reference the same script will take it from the cache instead of downloading it, so the file is actually downloaded only once. +Другие страницы, которые подключают тот же скрипт, смогут брать его из кеша вместо повторной загрузки из сети. И таким образом файл будет загружаться с сервера только один раз. -That reduces traffic and makes pages faster. +Это сокращает расход трафика и ускоряет загрузку страниц. ``` -````warn header="If `src` is set, the script content is ignored." -A single ` ``` -We must choose either an external ` @@ -124,11 +119,10 @@ The example above can be split into two scripts to work: ``` ```` -## Summary - -- We can use a ``. +## Итого +- Для добавления кода JavaScript на страницу используется тег ``. -There is much more to learn about browser scripts and their interaction with the webpage. But let's keep in mind that this part of the tutorial is devoted to the JavaScript language, so we shouldn't distract ourselves with browser-specific implementations of it. We'll be using the browser as a way to run JavaScript, which is very convenient for online reading, but only one of many. +Нам ещё многое предстоит изучить про браузерные скрипты и их взаимодействие со страницей. Но, как уже было сказано, эта часть учебника посвящена именно языку JavaScript, поэтому здесь мы постараемся не отвлекаться на детали реализации в браузере. Мы воспользуемся браузером для запуска JavaScript, это удобно для онлайн-демонстраций, но это только одна из платформ, на которых работает этот язык. diff --git a/1-js/02-first-steps/01-hello-world/hello-world-render.png b/1-js/02-first-steps/01-hello-world/hello-world-render.png deleted file mode 100644 index ba94e017d6..0000000000 Binary files a/1-js/02-first-steps/01-hello-world/hello-world-render.png and /dev/null differ diff --git a/1-js/02-first-steps/01-hello-world/hello-world-render@2x.png b/1-js/02-first-steps/01-hello-world/hello-world-render@2x.png deleted file mode 100644 index 9b2d4479f6..0000000000 Binary files a/1-js/02-first-steps/01-hello-world/hello-world-render@2x.png and /dev/null differ diff --git a/1-js/02-first-steps/02-structure/article.md b/1-js/02-first-steps/02-structure/article.md index 1b859053ee..88a6e2bcea 100644 --- a/1-js/02-first-steps/02-structure/article.md +++ b/1-js/02-first-steps/02-structure/article.md @@ -1,12 +1,12 @@ # Структура кода -Сначала рассмотрим основные "строительные блоки" кода. +Начнём изучение языка с рассмотрения основных "строительных блоков" кода. ## Инструкции Инструкции -- это синтаксические конструкции и команды, которые выполняют действия. -Мы уже видели инструкцию, `alert('Hello, world!')`, которая отображает сообщение "Привет, мир!". +Мы уже видели инструкцию `alert('Привет, мир!')`, которая отображает сообщение "Привет, мир!". В нашем коде может быть столько инструкций, сколько мы захотим. Инструкции могут отделяться точкой с запятой. @@ -16,7 +16,7 @@ alert('Привет'); alert('Мир'); ``` -Обычно, каждую инструкцию пишут на новой строке, чтобы код было легче читать: +Обычно каждую инструкцию пишут на новой строке, чтобы код было легче читать: ```js run no-beautify alert('Привет'); @@ -25,7 +25,7 @@ alert('Мир'); ## Точка с запятой [#semicolon] -В большинстве случаев точку с запятой можно опустить, если есть переход на новую строку. +В большинстве случаев точку с запятой можно не ставить, если есть переход на новую строку. Так тоже будет работать: @@ -38,7 +38,7 @@ alert('Мир') **В большинстве случаев новая строка подразумевает точку с запятой. Но "в большинстве случаев" не значит "всегда"!** -В некоторых ситуациях новая строка не означает точку с запятой. Например: +В некоторых ситуациях новая строка всё же не означает точку с запятой. Например: ```js run no-beautify alert(3 + @@ -46,7 +46,7 @@ alert(3 + + 2); ``` -Код выведет `6` потому что JavaScript не вставляет здесь точку с запятой. Интуитивно очевидно, что если строка заканчивается знаком `"+"`, значит это "незавершенное выражение", поэтому точка с запятой не требуется. И в этом случае всё работает как задумано. +Код выведет `6`, потому что JavaScript не вставляет здесь точку с запятой. Интуитивно очевидно, что, если строка заканчивается знаком `"+"`, значит, это "незавершённое выражение", поэтому точка с запятой не требуется. И в этом случае всё работает, как задумано. **Но есть ситуации, где JavaScript "забывает" вставить точку с запятой там, где она нужна.** @@ -56,47 +56,43 @@ alert(3 + Если вы хотите увидеть конкретный пример такой ошибки, обратите внимание на этот код: ```js run -[1, 2].forEach(alert) +alert('Hello'); + +[1, 2].forEach(alert); ``` -Пока нет необходимости знать значение скобок `[]` и `forEach`. Мы изучим их позже. Пока что, просто запомните результат выполнения этого кода: выводится `1`, а затем `2`. +Пока нет необходимости знать значение скобок `[]` и `forEach`. Мы изучим их позже. Пока что просто запомните результат выполнения этого кода: выводится `Hello`, затем `1`, затем `2`. -А теперь добавим `alert` перед кодом и *не* поставим в конце точку с запятой: +А теперь давайте уберем точку с запятой после `alert`: ```js run no-beautify -alert("Сейчас будет ошибка") - -[1, 2].forEach(alert) -``` - -Теперь, если запустить код, выведется только первый `alert`, а затем мы получим ошибку! +alert('Hello') -Всё исправится, если мы поставим точку с запятой после `alert`: -```js run -alert("Теперь всё в порядке"); - -[1, 2].forEach(alert) +[1, 2].forEach(alert); ``` -Теперь мы получим сообщение "Теперь всё в порядке", следом за которым будут `1` и `2`. +Этот код отличается от кода, приведенного выше, только в одном: пропала точка с запятой в конце первой строки. +Если мы запустим этот код, выведется только первый `alert`, а затем мы получим ошибку (вам может потребоваться открыть консоль, чтобы увидеть её)! -В первом примере без точки с запятой возникает ошибка, потому что JavaScript не вставляет точку с запятой перед квадратными скобками `[...]`. +Это потому что JavaScript не вставляет точку с запятой перед квадратными скобками `[...]`. И поэтому код в последнем примере выполняется, как одна инструкция. -Итак, поскольку точка с запятой не вставляется автоматически, код в первом примере выполняется как одна инструкция. Вот как движок видит его: +Вот как движок видит его: ```js run no-beautify -alert("Сейчас будет ошибка")[1, 2].forEach(alert) +alert('Hello')[1, 2].forEach(alert); ``` -Но это должны быть две отдельные инструкции, а не одна. Такое слияние, в данном случае, неправильное, от того и ошибка. Это может произойти и в некоторых других ситуациях. +Выглядит странно, правда? Такое слияние в данном случае неправильное. Мы должны поставить точку с запятой после `alert`, чтобы код работал правильно. + +Это может произойти и в некоторых других ситуациях. ```` -Мы рекомендуем ставить точку с запятой между инструкциями, даже если они отделены переносами строк. Это правило широко используется в сообществе разработчиков. Стоит отметить ещё раз -- в большинстве случаев *можно* опускать точку с запятой. Но безопаснее -- особенно для новичка -- использовать их. +Мы рекомендуем ставить точку с запятой между инструкциями, даже если они отделены переносами строк. Это правило широко используется в сообществе разработчиков. Стоит отметить ещё раз -- в большинстве случаев *можно* не ставить точку с запятой. Но безопаснее, особенно для новичка, ставить её. ## Комментарии -Со временем программы становятся всё сложнее и сложнее. Возникает необходимость добавлять *комментарии*, которые бы описывали что делает код и почему. +Со временем программы становятся всё сложнее и сложнее. Возникает необходимость добавлять *комментарии*, которые бы описывали, что делает код и почему. Комментарии могут находиться в любом месте скрипта. Они не влияют на его выполнение, поскольку движок просто игнорирует их. @@ -104,7 +100,8 @@ alert("Сейчас будет ошибка")[1, 2].forEach(alert) Часть строки после `//` считается комментарием. Такой комментарий может как занимать строку целиком, так и находиться после инструкции. -Like here: +Как здесь: + ```js run // Этот комментарий занимает всю строку alert('Привет'); @@ -124,7 +121,7 @@ alert('Привет'); alert('Мир'); ``` -Содержимое комментария игнорируется, поэтому, если мы поместим внутри код /* ... */, он не будет исполняться. +Содержимое комментария игнорируется, поэтому, если мы поместим код внутри /* ... */, он не будет исполняться. Это бывает удобно для временного отключения участка кода: @@ -136,7 +133,7 @@ alert('Мир'); ``` ```smart header="Используйте горячие клавиши!" -В большинстве редакторов строку кода можно закомментировать, нажав комбинацию клавиш `key:Ctrl+/` для однострочного комментария и что-то вроде `key:Ctrl+Shift+/` -- для многострочных комментариев (выделите кусок кода и нажмите комбинацию клавиш). В системе Mac попробуйте `key:Cmd` вместо `key:Ctrl`. +В большинстве редакторов строку кода можно закомментировать, нажав комбинацию клавиш `key:Ctrl+/` для однострочного комментария и что-то вроде `key:Ctrl+Shift+/` -- для многострочных комментариев (выделите кусок кода и нажмите комбинацию клавиш). В системе Mac попробуйте `key:Cmd` вместо `key:Ctrl` и `key:Option` вместо `key:Shift`. ``` ````warn header="Вложенные комментарии не поддерживаются!" @@ -154,6 +151,6 @@ alert( 'Мир' ); Не стесняйтесь использовать комментарии в своём коде. -Комментарии увеличивают размер кода, но это не проблема. Есть множество инструментов, которые минифицируют код перед публикацией на рабочий сервер. Они убирают комментарии, так что они не содержатся в рабочих скриптах. Таким образом, комментарии никаким образом не вредят рабочему коду. +Комментарии увеличивают размер кода, но это не проблема. Есть множество инструментов, которые минифицируют код перед публикацией на рабочий сервер. Они убирают комментарии, так что они не содержатся в рабочих скриптах. Таким образом, комментарии никоим образом не вредят рабочему коду. Позже в учебнике будет глава , которая объяснит, как лучше писать комментарии. diff --git a/1-js/02-first-steps/03-strict-mode/article.md b/1-js/02-first-steps/03-strict-mode/article.md index 1462903bbc..15290f9dd4 100644 --- a/1-js/02-first-steps/03-strict-mode/article.md +++ b/1-js/02-first-steps/03-strict-mode/article.md @@ -2,13 +2,13 @@ На протяжении долгого времени JavaScript развивался без проблем с обратной совместимостью. Новые функции добавлялись в язык, в то время как старая функциональность не менялась. -Преимуществом данного подхода было то, что существующий код продолжал работать. А недостатком, что любая ошибка или несовершенное решение, принятое создателями JavaScript, застревали в языке навсегда. +Преимуществом данного подхода было то, что существующий код продолжал работать. А недостатком -- что любая ошибка или несовершенное решение, принятое создателями JavaScript, застревали в языке навсегда. -Так было до 2009 года, когда появился ECMAScript 5 (ES5). Он добавил новые возможности в язык и изменил некоторые из существующих. Чтобы старый код работал, большинство модификаций по умолчанию отключены. Вы должны явно включить их с помощью специальной директивы: `"use strict"`. +Так было до 2009 года, когда появился ECMAScript 5 (ES5). Он добавил новые возможности в язык и изменил некоторые из существующих. Чтобы устаревший код работал, как и раньше, по умолчанию подобные изменения не применяются. Поэтому нам нужно явно их активировать с помощью специальной директивы: `"use strict"`. ## "use strict" -Директива выглядит как строка: `"use strict"` или `'use strict'`. Когда она находится в верхней части скрипта, весь сценарий работает в "современном" режиме. +Директива выглядит как строка: `"use strict"` или `'use strict'`. Когда она находится в начале скрипта, весь сценарий работает в "современном" режиме. Например: @@ -19,19 +19,17 @@ ... ``` -Позже мы изучим функции (способ группировки команд). - -Забегая вперед, давайте просто отметим, что `"use strict"` можно поставить в начале большинства видов функций, вместо всего скрипта. Это позволяет включить строгий режим только в этой функции. Но обычно, люди используют его для всего файла. +Совсем скоро мы начнём изучать функции (способ группировки команд), поэтому заранее отметим, что в начале большинства видов функций можно поставить `"use strict"`. Это позволяет включить строгий режим только в конкретной функции. Но обычно люди используют его для всего файла. ````warn header="Убедитесь, что \"use strict\" находится в начале" -Проверьте, что `"use strict"` добавлен вверху ваших скриптов, иначе строгий режим может быть не включен. +Проверьте, что `"use strict"` находится в первой исполняемой строке скрипта, иначе строгий режим может не включиться. -Здесь строгий режим не включен: +Здесь строгий режим не включён: ```js no-strict alert("some code"); -// "use strict" ниже игнорируется - он должен быть вверху +// "use strict" ниже игнорируется - он должен быть в первой строке "use strict"; @@ -42,20 +40,30 @@ alert("some code"); ```` ```warn header="Нет никакого способа отменить `use strict`" -Нет такой директивы как `"no use strict"` которая возвращает движок к старому поведению. +Нет директивы типа `"no use strict"`, которая возвращала бы движок к старому поведению. Как только мы входим в строгий режим, отменить это невозможно. ``` ## Консоль браузера -В будущем, когда вы будете использовать консоль браузера для тестирования функций, обратите внимание, что `use strict` по умолчанию в ней выключен. +В дальнейшем, когда вы будете использовать [консоль браузера](info:devtools) для тестирования функций, обратите внимание, что `use strict` по умолчанию в ней выключен. Иногда, когда `use strict` имеет значение, вы можете получить неправильные результаты. -Даже если мы нажмем `key:Shift+Enter` для ввода нескольких строк и напишем вверху `use strict` -- это не сработает. Это связано с тем, как консоль выполняет код внутри. +Итак, как можно включить `use strict` в консоли? -Надежный способ сделать так, чтобы `use strict` заработал, это ввести код в консоль следующим образом: +Можно использовать `key:Shift+Enter` для ввода нескольких строк и написать в верхней строке `use strict`: + +```js +'use strict'; +// ...ваш код... + +``` + +В большинстве браузеров, включая Chrome и Firefox, это работает. + +Если этого не происходит, например, в старом браузере, есть некрасивый, но надежный способ обеспечить `use strict`. Поместите его в следующую обёртку: ```js (function() { @@ -65,15 +73,18 @@ alert("some code"); })() ``` -## Всегда используйте "use strict" +## Всегда ли нужно использовать "use strict"? + +Вопрос кажется риторическим, но это не так. + +Кто-то посоветует начинать каждый скрипт с `"use strict"`... Но есть способ покруче. + +Современный JavaScript поддерживает "классы" и "модули" — продвинутые структуры языка (и мы, конечно, до них доберёмся), которые автоматически включают строгий режим. Поэтому в них нет нужды добавлять директиву `"use strict"`. -Нам еще предстоит рассмотреть различия между строгим режимом и режимом "по умолчанию". +**Подытожим: пока очень желательно добавлять `"use strict";` в начале ваших скриптов. Позже, когда весь ваш код будет состоять из классов и модулей, директиву можно будет опускать.** -В следующих главах, изучая особенности языка, мы будем отмечать различия между строгим и стандартным режимами. К счастью, их не так много, и они действительно делают нашу жизнь лучше. +Пока мы узнали о `use strict` только в общих чертах. -На данный момент достаточно иметь общее понимание об этом режиме: +В следующих главах, по мере расширения знаний о возможностях языка, мы яснее увидим отличия между строгим и стандартным режимом. К счастью, их не так много, и все они делают жизнь разработчика лучше. -1. Директива `"use strict"` переключает движок в "современный" режим, изменяя поведение некоторых встроенных функций. Позже в учебнике мы увидим подробности. -2. Строгий режим включается путем размещения `"use strict"` в верхней части скрипта или функции. Некоторые функции языка, такие как "классы" и "модули", автоматически включают строгий режим. -3. Строгий режим поддерживается всеми современными браузерами. -4. Мы рекомендуем всегда начинать скрипты с `"use strict"`. Все примеры в этом руководстве предполагают строгий режим, если (очень редко) не указано иное. +Все примеры в этом учебнике подразумевают исполнение в строгом режиме, за исключением случаев (очень редких), когда оговорено иное. diff --git a/1-js/02-first-steps/04-variables/2-declare-variables/solution.md b/1-js/02-first-steps/04-variables/2-declare-variables/solution.md index b371c36e49..d9da22ea3b 100644 --- a/1-js/02-first-steps/04-variables/2-declare-variables/solution.md +++ b/1-js/02-first-steps/04-variables/2-declare-variables/solution.md @@ -1,21 +1,20 @@ -1. Переменная для названия нашей планеты. +## Переменная для названия нашей планеты -Пример: +Например: ```js let ourPlanetName = "Земля"; ``` -Обратите внимание, мы могли бы использовать короткое имя `planet`, но тогда будет непонятно о какой планете мы говорим. Лучше описать содержимое переменной подробнее. По крайней мере до тех пор пока имя переменной неСтанетСлишкомДлинным. - -2. Имя текущего посетителя: +Обратите внимание, мы могли бы использовать короткое имя `planet`, но тогда будет непонятно, о какой планете мы говорим. Лучше описать содержимое переменной подробнее, по крайней мере, до тех пор, пока имя переменной неСтанетСлишкомДлинным. +## Имя текущего посетителя: ```js let currentUserName = "Джон"; ``` Опять же, мы могли бы укоротить название до `userName`, если мы точно знаем, что это текущий пользователь. -Современные редакторы и автодополнение ввода в них позволяют легко писать длинные названия переменных. Не экономьте буквы. Имена, состоящие из трех слов, вполне нормальны. +Современные редакторы и автодополнение ввода в них позволяют легко писать длинные названия переменных. Не экономьте буквы. Имена, состоящие из трёх слов, вполне нормальны. Если в вашем редакторе нет автодополнения, воспользуйтесь [другими](/code-editors). diff --git a/1-js/02-first-steps/04-variables/3-uppercast-constant/solution.md b/1-js/02-first-steps/04-variables/3-uppercast-constant/solution.md index bec53b5cec..abb63b6dc5 100644 --- a/1-js/02-first-steps/04-variables/3-uppercast-constant/solution.md +++ b/1-js/02-first-steps/04-variables/3-uppercast-constant/solution.md @@ -1,5 +1,5 @@ -Обычно мы используем буквы в верхнем регистре для констант, которые "жестко закодированы". Или, другими словами, когда значение известно до выполнения скрипта и записывается непосредственно в код. +Обычно мы используем буквы в верхнем регистре для констант, которые "жёстко закодированы". Или, другими словами, когда значение известно до выполнения скрипта и записывается непосредственно в код. В нашем примере, `birthday` именно такая переменная. Поэтому мы можем использовать заглавные буквы. -В отличие от предыдущей, переменная `age` вычисляется во время выполнения скрипта. Сегодня у нас один возраст, а через год уже совсем другой. Она является константой, потому что не изменяется при выполнении кода. Но она является "меньшей константой", чем `birthday`, она вычисляется, поэтому мы должны сохранить её в нижнем регистре. +В отличие от предыдущей, переменная `age` вычисляется во время выполнения скрипта. Сегодня у нас один возраст, а через год уже совсем другой. Она является константой, потому что не изменяется при выполнении кода. Но она является "менее константной", чем `birthday`: она вычисляется, поэтому мы должны сохранить её в нижнем регистре. diff --git a/1-js/02-first-steps/04-variables/3-uppercast-constant/task.md b/1-js/02-first-steps/04-variables/3-uppercast-constant/task.md index 68ff3a878a..04bf580663 100644 --- a/1-js/02-first-steps/04-variables/3-uppercast-constant/task.md +++ b/1-js/02-first-steps/04-variables/3-uppercast-constant/task.md @@ -2,7 +2,7 @@ importance: 4 --- -# Какие буквы (заглавные или строчные) использовать для имен констант? +# Какие буквы (заглавные или строчные) использовать для имён констант? Рассмотрим следующий код: diff --git a/1-js/02-first-steps/04-variables/article.md b/1-js/02-first-steps/04-variables/article.md index 2f175159c5..b1bdebc933 100644 --- a/1-js/02-first-steps/04-variables/article.md +++ b/1-js/02-first-steps/04-variables/article.md @@ -1,7 +1,7 @@ # Переменные JavaScript-приложению обычно нужно работать с информацией. Например: -1. Интернет магазин -- информация может включать продаваемые товары и корзину покупок. +1. Интернет-магазин -- информация может включать продаваемые товары и корзину покупок. 2. Чат -- информация может включать пользователей, сообщения и многое другое. Переменные используются для хранения этой информации. @@ -10,21 +10,21 @@ JavaScript-приложению обычно нужно работать с ин [Переменная](https://ru.wikipedia.org/wiki/%D0%9F%D0%B5%D1%80%D0%B5%D0%BC%D0%B5%D0%BD%D0%BD%D0%B0%D1%8F_(%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)) -- это "именованное хранилище" для данных. Мы можем использовать переменные для хранения товаров, посетителей и других данных. -Для создания переменной в JavaScript, используйте ключевое слово `let`. +Для создания переменной в JavaScript используйте ключевое слово `let`. -Приведенная ниже инструкция создает (другими словами: *объявляет* или *определяет*) переменную с именем "message": +Приведённая ниже инструкция создаёт (другими словами, *объявляет*) переменную с именем "message": ```js let message; ``` -Теперь можно поместить в неё некоторые данные, используя оператор присваивания `=`: +Теперь можно поместить в неё данные (другими словами, *определить переменную*), используя оператор присваивания `=`: ```js let message; *!* -message = 'Hello'; // хранит строку +message = 'Hello'; // сохранить строку 'Hello' в переменной с именем message */!* ``` @@ -63,7 +63,8 @@ let age = 25; let message = 'Hello'; ``` -Некоторые люди также определяют несколько переменных в многострочном стиле: +Некоторые люди также определяют несколько переменных в таком вот многострочном стиле: + ```js no-beautify let user = 'John', age = 25, @@ -78,8 +79,7 @@ let user = 'John' , message = 'Hello'; ``` -В принципе, все эти варианты одинаковые. Так что это вопрос личного вкуса и эстетики. - +В принципе, все эти варианты работают одинаково. Так что это вопрос личного вкуса и эстетики. ````smart header="`var` вместо `let`" В старых скриптах вы также можете найти другое ключевое слово: `var` вместо `let`: @@ -88,22 +88,23 @@ let user = 'John' *!*var*/!* message = 'Hello'; ``` -Ключевое слово `var` *почти* то же самое, что и `let`. Оно объявляет переменную, но немного по-другому, "устаревшим" способом. +Ключевое слово `var` - *почти* то же самое, что и `let`. Оно объявляет переменную, но немного по-другому, "устаревшим" способом. -Есть тонкие различия между `let` и `var`, но они пока не имеют для нас значения. Мы подробно рассмотрим их в этой главе . +Есть тонкие различия между `let` и `var`, но они пока не имеют для нас значения. Мы подробно рассмотрим их в главе . ```` ## Аналогия из жизни -Мы легко поймем концепцию "переменной", если представим ее в виде "коробки" для данных с уникальным названием на ней. +Мы легко поймём концепцию "переменной", если представим её в виде "коробки" для данных с уникальным названием на ней. Например, переменную `message` можно представить как коробку с названием `"message"` и значением `"Hello!"` внутри: -![](variable.png) +![](variable.svg) Мы можем положить любое значение в коробку. Мы также можем изменить его столько раз, сколько захотим: + ```js run let message; @@ -116,7 +117,7 @@ alert(message); При изменении значения старые данные удаляются из переменной: -![](variable-change.png) +![](variable-change.svg) Мы также можем объявить две переменные и скопировать данные из одной в другую. @@ -135,31 +136,45 @@ alert(hello); // Hello world! alert(message); // Hello world! ``` +````warn header="Повторное объявление вызывает ошибку" +Переменная может быть объявлена только один раз. + +Повторное объявление той же переменной является ошибкой: + +```js run +let message = "Это"; + +// повторение ключевого слова 'let' приводит к ошибке +let message = "Другое"; // SyntaxError: 'message' has already been declared +``` +Поэтому следует объявлять переменную только один раз и затем использовать её уже без `let`. +```` + ```smart header="Функциональные языки программирования" -Примечательно, что существуют [функциональные](https://ru.wikipedia.org/wiki/%D0%A4%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D0%BE%D0%BD%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B5_%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) языки программирования, такие как [Scala](http://www.scala-lang.org/) или [Erlang](http://www.erlang.org/), которые запрещают изменять значение переменной. +Примечательно, что существуют [функциональные](https://ru.wikipedia.org/wiki/%D0%A4%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D0%BE%D0%BD%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B5_%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) языки программирования, такие как [Scala](https://www.scala-lang.org/) или [Erlang](https://www.erlang.org/), которые запрещают изменять значение переменной. -В таких языках, однажды сохраненное "в коробку" значение остается там навсегда. Если нам нужно сохранить что-то другое, язык заставляет нас создать новую коробку (объявить новую переменную). Мы не можем использовать старую переменную. +В таких языках однажды сохранённое "в коробку" значение остаётся там навсегда. Если нам нужно сохранить что-то другое, язык заставляет нас создать новую коробку (объявить новую переменную). Мы не можем использовать старую переменную. -Хотя на первый взгляд это может показаться немного странным, эти языки вполне подходят для серьезной разработки. Более того, есть такая область как параллельные вычисления, где это ограничение дает определенные преимущества. Изучение такого языка (даже если вы не планируете использовать его в ближайшее время) рекомендуется для расширения кругозора. +Хотя на первый взгляд это может показаться немного странным, эти языки вполне подходят для серьёзной разработки. Более того, есть такая область, как параллельные вычисления, где это ограничение даёт определённые преимущества. Изучение такого языка (даже если вы не планируете использовать его в ближайшее время) рекомендуется для расширения кругозора. ``` ## Имена переменных [#variable-naming] -В JavaScript есть два ограничения, касающиеся имён переменных : +В JavaScript есть два ограничения, касающиеся имён переменных: 1. Имя переменной должно содержать только буквы, цифры или символы `$` и `_`. 2. Первый символ не должен быть цифрой. -Примеры допустимых имен: +Примеры допустимых имён: ```js let userName; let test123; ``` -Если имя содержит несколько слов, обычно используется [верблюжья нотация](https://ru.wikipedia.org/wiki/CamelCase). То есть слова следуют одно за другим, где каждое слово начинается с заглавной буквы: `myVeryLongName`. +Если имя содержит несколько слов, обычно используется [верблюжья нотация](https://ru.wikipedia.org/wiki/CamelCase), то есть, слова следуют одно за другим, где каждое следующее слово начинается с заглавной буквы: `myVeryLongName`. -Самое интересное -- знак доллара `'$'` и подчеркивание `'_'` также можно использовать в названиях. Это обычные символы, как и буквы, без какого-либо особого значения. +Самое интересное -- знак доллара `'$'` и подчёркивание `'_'` также можно использовать в названиях. Это обычные символы, как и буквы, без какого-либо особого значения. Эти имена являются допустимыми: @@ -170,19 +185,19 @@ let _ = 2; // а теперь переменную с именем "_" alert($ + _); // 3 ``` -Примеры неправильных имен переменных: +Примеры неправильных имён переменных: ```js no-beautify let 1a; // не может начинаться с цифры -let my-name; // дефис '-' не разрешен в имени +let my-name; // дефис '-' не разрешён в имени ``` ```smart header="Регистр имеет значение" -Переменные с именами `apple` and `AppLE` -- это две разные переменные. +Переменные с именами `apple` и `APPLE` -- это две разные переменные. ``` -````smart header="Не-латинские буквы разрешены, но не рекомендуются" +````smart header="Нелатинские буквы разрешены, но не рекомендуются" Можно использовать любой язык, включая кириллицу или даже иероглифы, например: ```js @@ -190,15 +205,15 @@ let имя = '...'; let 我 = '...'; ``` -Технически здесь нет ошибки, такие имена разрешены, но есть международная традиция использовать английский язык в именах переменных. Даже если мы пишем небольшой скрипт, у него может быть долгая жизнь впереди. Людям из других стран, возможно, придется прочесть его не один раз. +Технически здесь нет ошибки, такие имена разрешены, но есть международная традиция использовать английский язык в именах переменных. Даже если мы пишем небольшой скрипт, у него может быть долгая жизнь впереди. Людям из других стран, возможно, придётся прочесть его не один раз. ```` ````warn header="Зарезервированные имена" -Существует [список зарезервированных слов](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Lexical_grammar#%D0%9A%D0%BB%D1%8E%D1%87%D0%B5%D0%B2%D1%8B%D0%B5_%D1%81%D0%BB%D0%BE%D0%B2%D0%B0), которые нельзя использовать в качестве имен переменных, потому что они используются самим языком. +Существует [список зарезервированных слов](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Lexical_grammar#%D0%BA%D0%BB%D1%8E%D1%87%D0%B5%D0%B2%D1%8B%D0%B5_%D1%81%D0%BB%D0%BE%D0%B2%D0%B0), которые нельзя использовать в качестве имён переменных, потому что они используются самим языком. Например: `let`, `class`, `return` и `function` зарезервированы. -Приведенный ниже код дает синтаксическую ошибку: +Приведённый ниже код даёт синтаксическую ошибку: ```js run no-beautify let let = 5; // нельзя назвать переменную "let", ошибка! @@ -208,53 +223,52 @@ let return = 5; // также нельзя назвать переменную " ````warn header="Создание переменной без использования `use strict`" -Обычно нам нужно определить переменную перед ее использованием. Но в старые времена было технически возможно создать переменную простым присвоением значения без использования `let`. Это все еще работает, если мы не включаем `строгий режим` в наших файлах для поддержания совместимости со старыми сценариями. +Обычно нам нужно определить переменную перед её использованием. Но в старые времена было технически возможно создать переменную простым присвоением значения без использования `let`. Это все ещё работает, если мы не включаем `use strict` в наших файлах, чтобы обеспечить совместимость со старыми скриптами. ```js run no-strict // заметка: "use strict" в этом примере не используется -num = 5; // если переменная "num" не существовала, она создается +num = 5; // если переменная "num" раньше не существовала, она создаётся alert(num); // 5 ``` -Это плохая практика и приведет к ошибке в строгом режиме: +Это плохая практика, которая приводит к ошибке в строгом режиме: ```js "use strict"; *!* -num = 5; // error: num is not defined +num = 5; // ошибка: num is not defined */!* ``` ```` ## Константы -Чтобы объявить константу (неизменяемую) переменную, используйте `const` вместо `let`: +Чтобы объявить константную, то есть, неизменяемую переменную, используйте `const` вместо `let`: ```js const myBirthday = '18.04.1982'; ``` -Переменные объявленные с помощью `const`, называются "константами". Их нельзя изменить. Попытка сделать это приведет к ошибке: +Переменные, объявленные с помощью `const`, называются "константами". Их нельзя изменить. Попытка сделать это приведёт к ошибке: ```js run const myBirthday = '18.04.1982'; -myBirthday = '01.01.2001'; // ошибка, константу нельзя повторно определить! +myBirthday = '01.01.2001'; // ошибка, константу нельзя перезаписать! ``` -Если программист уверен, что переменная никогда не будет меняться, он может гарантировать это и наглядно донести до каждого, объявив ее через `const`. - +Если программист уверен, что переменная никогда не будет меняться, он может гарантировать это и наглядно донести до каждого, объявив её через `const`. ### Константы в верхнем регистре -Широко распространена практика использования констант в качестве псевдонимов для трудно запоминаемых значений, которые известны до начала исполнения сценария. +Широко распространена практика использования констант в качестве псевдонимов для трудно запоминаемых значений, которые известны до начала исполнения скрипта. -Названия таких констант пишутся с использованием заглавных букв и подчеркивания. +Названия таких констант пишутся с использованием заглавных букв и подчёркивания. -Вот так: +Например, сделаем константы для различных цветов в "шестнадцатеричном формате": ```js run const COLOR_RED = "#F00"; @@ -271,61 +285,61 @@ alert(color); // #FF7F00 - `COLOR_ORANGE` гораздо легче запомнить, чем `"#FF7F00"`. - Гораздо легче допустить ошибку при вводе `"#FF7F00"`, чем при вводе `COLOR_ORANGE`. -- При чтении кода, `COLOR_ORANGE` намного понятнее, чем `#FF7F00`. +- При чтении кода `COLOR_ORANGE` намного понятнее, чем `#FF7F00`. -Когда мы должны использовать для констант заглавные буквы, а когда называть их нормально? Давайте разберемся с этим. +Когда мы должны использовать для констант заглавные буквы, а когда называть их нормально? Давайте разберёмся и с этим. Название "константа" просто означает, что значение переменной никогда не меняется. Но есть константы, которые известны до выполнения (например, шестнадцатеричное значение для красного цвета), а есть константы, которые *вычисляются* во время выполнения сценария, но не изменяются после их первоначального назначения. Например: + ```js -const pageLoadTime = /* время потраченное на загрузку веб-страницы */; +const pageLoadTime = /* время, потраченное на загрузку веб-страницы */; ``` -Значение `pageLoadTime` не известно до загрузки страницы, поэтому ее имя записано обычными, а не прописными буквами. Но это все еще константа, потому что она не изменяется после назначения. +Значение `pageLoadTime` неизвестно до загрузки страницы, поэтому её имя записано обычными, а не прописными буквами. Но это всё ещё константа, потому что она не изменяется после назначения. -Другими словами, константы с именами, записанными заглавными буквами, используются только как псевдонимы для "жестко закодированных" значений. +Другими словами, константы с именами, записанными заглавными буквами, используются только как псевдонимы для "жёстко закодированных" значений. ## Придумывайте правильные имена -В разговоре о переменных необходимо упомянуть, что есть еще одна чрезвычайно важная вещь. +В разговоре о переменных необходимо упомянуть, что есть ещё одна чрезвычайно важная вещь. Название переменной должно иметь ясный и понятный смысл, говорить о том, какие данные в ней хранятся. -Именование переменных -- это один из самых важных и сложных навыков в программировании. Быстрый взгляд на имена переменных может показать, какой код был написан новичком, а какой опытным разработчиком. +Именование переменных -- это один из самых важных и сложных навыков в программировании. Быстрый взгляд на имена переменных может показать, какой код был написан новичком, а какой -- опытным разработчиком. -В реальном проекте большая часть времени тратится на изменение и расширение существующей кодовой базы, а не на написание чего-то совершенно нового с нуля. Когда мы возвращаемся к какому-то коду, после выполнения чего-то другого в течение некоторого времени, гораздо легче найти информацию, которая хорошо размечена. Или, другими словами, когда переменные имеют хорошие имена. +В реальном проекте большая часть времени тратится на изменение и расширение существующей кодовой базы, а не на написание чего-то совершенно нового с нуля. Когда мы возвращаемся к коду после какого-то промежутка времени, гораздо легче найти информацию, которая хорошо размечена. Или, другими словами, когда переменные имеют хорошие имена. -Пожалуйста, потратьте время на обдумывание правильного имени переменной перед ее объявлением. Делайте так, и будете вознаграждены. +Пожалуйста, потратьте время на обдумывание правильного имени переменной перед её объявлением. Делайте так, и будете вознаграждены. Несколько хороших правил: - Используйте легко читаемые имена, такие как `userName` или `shoppingCart`. -- Избегайте использования аббревиатур или коротких имен, таких как `a`, `b`, `c`, за исключением тех случаев, когда вы точно знаете, что так нужно. -- Делайте имена максимально описательными и лаконичными. Примеры плохих имен `data` и `value`. Такие имена ничего не говорят. Их можно использовать только в том случае, если из контекста кода очевидно, на какие данные или значения ссылается переменная. -- Договоритесь с вашей командой о используемых терминах. Если посетитель сайта называется "user" тогда мы должны назвать связанные с ним переменные `currentUser` или `newUser` вместо того, чтобы называть их `currentVisitor` или `newManInTown`. - -Звучит просто? Действительно это так, но на практике создание описательных и кратких имен переменных -- редкость. Действуйте. +- Избегайте использования аббревиатур или коротких имён, таких как `a`, `b`, `c`, за исключением тех случаев, когда вы точно знаете, что так нужно. +- Делайте имена максимально описательными и лаконичными. Примеры плохих имён: `data` и `value`. Такие имена ничего не говорят. Их можно использовать только в том случае, если из контекста кода очевидно, какие данные хранит переменная. +- Договоритесь с вашей командой об используемых терминах. Если посетитель сайта называется "user", тогда мы должны называть связанные с ним переменные `currentUser` или `newUser`, а не, к примеру, `currentVisitor` или `newManInTown`. +Звучит просто? Действительно, это так, но на практике для создания описательных и кратких имён переменных зачастую требуется подумать. Действуйте. -```smart header="Переиспользовать или создавать?" -И последняя заметка. Есть ленивые программисты, которые вместо объявления новых переменных, повторно используют существующие. +```smart header="Повторно использовать или создавать новую переменную?" +И последняя заметка. Есть ленивые программисты, которые вместо объявления новых переменных повторно используют существующие. -В результате их переменные похожи на коробки, в которые люди бросают разные предметы, не меняя на них стикеры. Что сейчас находится внутри коробки? Кто знает? Нам необходимо подойти поближе и проверить. +В результате их переменные похожи на коробки, в которые люди бросают разные предметы, не меняя на них этикетки. Что сейчас находится внутри коробки? Кто знает? Нам необходимо подойти поближе и проверить. -Такие программисты экономят немного на объявлении переменных, но теряют в десять раз больше при отладке. +Такие программисты немного экономят на объявлении переменных, но теряют в десять раз больше при отладке. Дополнительная переменная - это добро, а не зло. -Современные JavaScript-минификаторы и браузеры оптимизируют код достаточно хорошо, поэтому он не создает проблем с производительностью. Использование разных переменных для разных значений может даже помочь движку оптимизировать ваш код. +Современные JavaScript-минификаторы и браузеры оптимизируют код достаточно хорошо, поэтому он не создаёт проблем с производительностью. Использование разных переменных для разных значений может даже помочь движку оптимизировать ваш код. ``` ## Итого Мы можем объявить переменные для хранения данных с помощью ключевых слов `var`, `let` или `const`. -- `let` -- это современный способ объявления. При использовании `let` в Chrome (V8) должен быть включен строгий режим. -- `var` -- это устаревший способ объявления. Обычно мы не используем его вообще, но мы рассмотрим тонкие отличия от `let` в главе , на случай, если это всё-таки вам понадобится. -- `const` -- похож на `let`, но значение переменной не может изменяться. +- `let` -- это современный способ объявления. +- `var` -- это устаревший способ объявления. Обычно мы вообще не используем его, но мы рассмотрим тонкие отличия от `let` в главе на случай, если это всё-таки вам понадобится. +- `const` -- похоже на `let`, но значение переменной не может изменяться. Переменные должны быть названы таким образом, чтобы мы могли легко понять, что у них внутри. diff --git a/1-js/02-first-steps/04-variables/variable-change.png b/1-js/02-first-steps/04-variables/variable-change.png deleted file mode 100644 index 9135e930a0..0000000000 Binary files a/1-js/02-first-steps/04-variables/variable-change.png and /dev/null differ diff --git a/1-js/02-first-steps/04-variables/variable-change.svg b/1-js/02-first-steps/04-variables/variable-change.svg new file mode 100644 index 0000000000..1b26792380 --- /dev/null +++ b/1-js/02-first-steps/04-variables/variable-change.svg @@ -0,0 +1 @@ +"World!""Hello!"message \ No newline at end of file diff --git a/1-js/02-first-steps/04-variables/variable-change@2x.png b/1-js/02-first-steps/04-variables/variable-change@2x.png deleted file mode 100644 index c9569e6386..0000000000 Binary files a/1-js/02-first-steps/04-variables/variable-change@2x.png and /dev/null differ diff --git a/1-js/02-first-steps/04-variables/variable.png b/1-js/02-first-steps/04-variables/variable.png deleted file mode 100644 index 6d24825561..0000000000 Binary files a/1-js/02-first-steps/04-variables/variable.png and /dev/null differ diff --git a/1-js/02-first-steps/04-variables/variable.svg b/1-js/02-first-steps/04-variables/variable.svg new file mode 100644 index 0000000000..1c3d8b0cbe --- /dev/null +++ b/1-js/02-first-steps/04-variables/variable.svg @@ -0,0 +1 @@ +"Hello!"message \ No newline at end of file diff --git a/1-js/02-first-steps/04-variables/variable@2x.png b/1-js/02-first-steps/04-variables/variable@2x.png deleted file mode 100644 index 845f344084..0000000000 Binary files a/1-js/02-first-steps/04-variables/variable@2x.png and /dev/null differ diff --git a/1-js/02-first-steps/05-types/1-string-quotes/solution.md b/1-js/02-first-steps/05-types/1-string-quotes/solution.md index 68a13c15b2..dd9f7195c8 100644 --- a/1-js/02-first-steps/05-types/1-string-quotes/solution.md +++ b/1-js/02-first-steps/05-types/1-string-quotes/solution.md @@ -1,15 +1,15 @@ -Backticks embed the expression inside `${...}` into the string. +Обратные кавычки позволяют вставить выражение внутри `${...}` в строку. ```js run let name = "Ilya"; -// the expression is a number 1 +// выражение - число 1 alert( `hello ${1}` ); // hello 1 -// the expression is a string "name" +// выражение - строка "name" alert( `hello ${"name"}` ); // hello name -// the expression is a variable, embed it +// выражение - переменная, вставим её в строку alert( `hello ${name}` ); // hello Ilya ``` diff --git a/1-js/02-first-steps/05-types/1-string-quotes/task.md b/1-js/02-first-steps/05-types/1-string-quotes/task.md index 14ea6b4d66..2b64e6a54d 100644 --- a/1-js/02-first-steps/05-types/1-string-quotes/task.md +++ b/1-js/02-first-steps/05-types/1-string-quotes/task.md @@ -2,9 +2,9 @@ importance: 5 --- -# String quotes +# Шаблонные строки -What is the output of the script? +Что выведет этот скрипт? ```js let name = "Ilya"; @@ -14,4 +14,4 @@ alert( `hello ${1}` ); // ? alert( `hello ${"name"}` ); // ? alert( `hello ${name}` ); // ? -``` \ No newline at end of file +``` diff --git a/1-js/02-first-steps/05-types/article.md b/1-js/02-first-steps/05-types/article.md index 0c9954ecf9..5410236e3c 100644 --- a/1-js/02-first-steps/05-types/article.md +++ b/1-js/02-first-steps/05-types/article.md @@ -1,203 +1,254 @@ -# Data types +# Типы данных -A variable in JavaScript can contain any data. A variable can at one moment be a string and at another be a number: +Значение в JavaScript всегда относится к данным определённого типа. Например, это может быть строка или число. + +Есть восемь основных типов данных в JavaScript. В этой главе мы рассмотрим их в общем, а в следующих главах поговорим подробнее о каждом. + +Переменная в JavaScript может содержать любые данные. В один момент там может быть строка, а в другой - число: ```js -// no error +// Не будет ошибкой let message = "hello"; message = 123456; ``` -Programming languages that allow such things are called "dynamically typed", meaning that there are data types, but variables are not bound to any of them. +Языки программирования, в которых такое возможно, называются "динамически типизированными". Это значит, что типы данных есть, но переменные не привязаны ни к одному из них. -There are seven basic data types in JavaScript. Here, we'll cover them in general and in the next chapters we'll talk about each of them in detail. - -## A number +## Число ```js let n = 123; n = 12.345; ``` -The *number* type represents both integer and floating point numbers. +*Числовой* тип данных (`number`) представляет как целочисленные значения, так и числа с плавающей точкой. -There are many operations for numbers, e.g. multiplication `*`, division `/`, addition `+`, subtraction `-`, and so on. +Существует множество операций для чисел, например, умножение `*`, деление `/`, сложение `+`, вычитание `-` и так далее. -Besides regular numbers, there are so-called "special numeric values" which also belong to this data type: `Infinity`, `-Infinity` and `NaN`. +Кроме обычных чисел, существуют так называемые "специальные числовые значения", которые относятся к этому типу данных: `Infinity`, `-Infinity` и `NaN`. -- `Infinity` represents the mathematical [Infinity](https://en.wikipedia.org/wiki/Infinity) ∞. It is a special value that's greater than any number. +- `Infinity` представляет собой математическую [бесконечность](https://ru.wikipedia.org/wiki/Бесконечность#В_математике) ∞. Это особое значение, которое больше любого числа. - We can get it as a result of division by zero: + Мы можем получить его в результате деления на ноль: ```js run alert( 1 / 0 ); // Infinity ``` - Or just reference it directly: + Или задать его явно: ```js run alert( Infinity ); // Infinity ``` -- `NaN` represents a computational error. It is a result of an incorrect or an undefined mathematical operation, for instance: +- `NaN` означает вычислительную ошибку. Это результат неправильной или неопределённой математической операции, например: ```js run - alert( "not a number" / 2 ); // NaN, such division is erroneous + alert( "не число" / 2 ); // NaN, такое деление является ошибкой ``` - `NaN` is sticky. Any further operation on `NaN` returns `NaN`: + Значение `NaN` "прилипчиво". Любая математическая операция с `NaN` возвращает `NaN`: ```js run - alert( "not a number" / 2 + 5 ); // NaN + alert( NaN + 1 ); // NaN + alert( 3 * NaN ); // NaN + alert( "не число" / 2 - 1 ); // NaN ``` - So, if there's a `NaN` somewhere in a mathematical expression, it propagates to the whole result. + Если где-то в математическом выражении есть `NaN`, то оно распространяется на весь результат (есть только одно исключение: `NaN ** 0` равно `1`). + +```smart header="Математические операции -- безопасны" +Математические операции в JavaScript "безопасны". Мы можем делать что угодно: делить на ноль, обращаться с нечисловыми строками как с числами и т.д. + +Скрипт никогда не остановится с фатальной ошибкой (не "умрёт"). В худшем случае мы получим `NaN` как результат выполнения. +``` + +Специальные числовые значения относятся к типу "число". Конечно, это не числа в привычном значении этого слова. -```smart header="Mathematical operations are safe" -Doing maths is "safe" in JavaScript. We can do anything: divide by zero, treat non-numeric strings as numbers, etc. +Подробнее о работе с числами мы поговорим в главе . -The script will never stop with a fatal error ("die"). At worst, we'll get `NaN` as the result. +## BigInt + +В JavaScript тип `number` не может безопасно работать с числами, большими, чем (253-1) (т. е. `9007199254740991`) или меньшими, чем -(253-1) для отрицательных чисел. + +Если говорить совсем точно, то, технически, тип `number` *может* хранить большие целые числа (до 1.7976931348623157 * 10308), но за пределами безопасного диапазона целых чисел ±(253-1) будет ошибка точности, так как не все цифры помещаются в фиксированную 64-битную память. Поэтому можно хранить "приблизительное" значение. + +Например, эти два числа (прямо за пределами безопасного диапазона) совпадают: + +```js +console.log(9007199254740991 + 1); // 9007199254740992 +console.log(9007199254740991 + 2); // 9007199254740992 ``` -Special numeric values formally belong to the "number" type. Of course they are not numbers in the common sense of this word. +То есть все нечетные целые числа, большие чем (253-1), вообще не могут храниться в типе `number`. -We'll see more about working with numbers in the chapter . +В большинстве случаев безопасного диапазона чисел от -(253-1) до (253-1) вполне достаточно, но иногда нам требуется весь диапазон действительно гигантских целых чисел без каких-либо ограничений или пропущенных значений внутри него. Например, в криптографии или при использовании метки времени («timestamp») с микросекундами. -## A string +Тип `BigInt` был добавлен в JavaScript, чтобы дать возможность работать с целыми числами произвольной длины. -A string in JavaScript must be surrounded by quotes. +Чтобы создать значение типа `BigInt`, необходимо добавить `n` в конец числового литерала: ```js -let str = "Hello"; -let str2 = 'Single quotes are ok too'; -let phrase = `can embed ${str}`; +// символ "n" в конце означает, что это BigInt +const bigInt = 1234567890123456789012345678901234567890n; ``` -In JavaScript, there are 3 types of quotes. +Так как необходимость в использовании `BigInt`--чисел появляется достаточно редко, мы рассмотрим их в отдельной главе . Ознакомьтесь с ней, когда вам понадобятся настолько большие числа. -1. Double quotes: `"Hello"`. -2. Single quotes: `'Hello'`. -3. Backticks: `Hello`. +```smart header="Поддержка" +В данный момент `BigInt` поддерживается только в браузерах Firefox, Chrome, Edge и Safari, но не поддерживается в IE. +``` -Double and single quotes are "simple" quotes. There's no difference between them in JavaScript. +## Строка -Backticks are "extended functionality" quotes. They allow us to embed variables and expressions into a string by wrapping them in `${…}`, for example: +Строка (`string`) в JavaScript должна быть заключена в кавычки. + +```js +let str = "Привет"; +let str2 = 'Одинарные кавычки тоже подойдут'; +let phrase = `Обратные кавычки позволяют встраивать переменные ${str}`; +``` + +В JavaScript существует три типа кавычек. + +1. Двойные кавычки: `"Привет"`. +2. Одинарные кавычки: `'Привет'`. +3. Обратные кавычки: `Привет`. + +Двойные или одинарные кавычки являются "простыми", между ними нет разницы в JavaScript. + +Обратные же кавычки имеют расширенную функциональность. Они позволяют нам встраивать выражения в строку, заключая их в `${…}`. Например: ```js run -let name = "John"; +let name = "Иван"; -// embed a variable -alert( `Hello, *!*${name}*/!*!` ); // Hello, John! +// Вставим переменную +alert( `Привет, *!*${name}*/!*!` ); // Привет, Иван! -// embed an expression -alert( `the result is *!*${1 + 2}*/!*` ); // the result is 3 +// Вставим выражение +alert( `результат: *!*${1 + 2}*/!*` ); // результат: 3 ``` -The expression inside `${…}` is evaluated and the result becomes a part of the string. We can put anything in there: a variable like `name` or an arithmetical expression like `1 + 2` or something more complex. +Выражение внутри `${…}` вычисляется, и его результат становится частью строки. Мы можем положить туда всё, что угодно: переменную `name`, или выражение `1 + 2`, или что-то более сложное. -Please note that this can only be done in backticks. Other quotes don't have this embedding functionality! +Обратите внимание, что это можно делать только в обратных кавычках. Другие кавычки не имеют такой функциональности встраивания! ```js run -alert( "the result is ${1 + 2}" ); // the result is ${1 + 2} (double quotes do nothing) +alert( "результат: ${1 + 2}" ); // результат: ${1 + 2} (двойные кавычки ничего не делают) ``` -We'll cover strings more thoroughly in the chapter . +Мы рассмотрим строки более подробно в главе . -```smart header="There is no *character* type." -In some languages, there is a special "character" type for a single character. For example, in the C language and in Java it is `char`. +```smart header="Нет отдельного типа данных для одного символа." +В некоторых языках, например C и Java, для хранения одного символа, например `"a"` или `"%"`, существует отдельный тип. В языках C и Java это `char`. -In JavaScript, there is no such type. There's only one type: `string`. A string may consist of only one character or many of them. +В JavaScript подобного типа нет, есть только тип `string`. Строка может содержать ноль символов (быть пустой), один символ или множество. ``` -## A boolean (logical type) +## Булевый (логический) тип -The boolean type has only two values: `true` and `false`. +Булевый тип (`boolean`) может принимать только два значения: `true` (истина) и `false` (ложь). -This type is commonly used to store yes/no values: `true` means "yes, correct", and `false` means "no, incorrect". +Такой тип, как правило, используется для хранения значений да/нет: `true` значит "да, правильно", а `false` значит "нет, не правильно". -For instance: +Например: ```js -let nameFieldChecked = true; // yes, name field is checked -let ageFieldChecked = false; // no, age field is not checked +let nameFieldChecked = true; // да, поле отмечено +let ageFieldChecked = false; // нет, поле не отмечено ``` -Boolean values also come as a result of comparisons: +Булевые значения также могут быть результатом сравнений: ```js run let isGreater = 4 > 1; -alert( isGreater ); // true (the comparison result is "yes") +alert( isGreater ); // true (результатом сравнения будет "да") ``` -We'll cover booleans more deeply in the chapter . +Мы рассмотрим булевые значения более подробно в главе . -## The "null" value +## Значение "null" -The special `null` value does not belong to any of the types described above. +Специальное значение `null` не относится ни к одному из типов, описанных выше. -It forms a separate type of its own which contains only the `null` value: +Оно формирует отдельный тип, который содержит только значение `null`: ```js let age = null; ``` -In JavaScript, `null` is not a "reference to a non-existing object" or a "null pointer" like in some other languages. +В JavaScript `null` не является "ссылкой на несуществующий объект" или "нулевым указателем", как в некоторых других языках. -It's just a special value which represents "nothing", "empty" or "value unknown". +Это просто специальное значение, которое представляет собой "ничего", "пусто" или "значение неизвестно". -The code above states that `age` is unknown or empty for some reason. +В приведённом выше коде указано, что значение переменной `age` неизвестно. -## The "undefined" value +## Значение "undefined" -The special value `undefined` also stands apart. It makes a type of its own, just like `null`. +Специальное значение `undefined` также стоит особняком. Оно формирует тип из самого себя так же, как и `null`. -The meaning of `undefined` is "value is not assigned". +Оно означает, что "значение не было присвоено". -If a variable is declared, but not assigned, then its value is `undefined`: +Если переменная объявлена, но ей не присвоено никакого значения, то её значением будет `undefined`: ```js run -let x; +let age; -alert(x); // shows "undefined" +alert(age); // выведет "undefined" ``` -Technically, it is possible to assign `undefined` to any variable: +Технически мы можем присвоить значение `undefined` любой переменной: ```js run -let x = 123; +let age = 123; -x = undefined; +// изменяем значение на undefined +age = undefined; -alert(x); // "undefined" +alert(age); // "undefined" ``` -...But we don't recommend doing that. Normally, we use `null` to assign an "empty" or "unknown" value to a variable, and we use `undefined` for checks like seeing if a variable has been assigned. +...Но так делать не рекомендуется. Обычно `null` используется для присвоения переменной "пустого" или "неизвестного" значения, а `undefined` -- для проверок, была ли переменная назначена. -## Objects and Symbols +## Объекты и символы -The `object` type is special. +Тип `object` (объект) -- особенный. -All other types are called "primitive" because their values can contain only a single thing (be it a string or a number or whatever). In contrast, objects are used to store collections of data and more complex entities. We'll deal with them later in the chapter after we learn more about primitives. +Все остальные типы называются "примитивными", потому что их значениями могут быть только простые значения (будь то строка, или число, или что-то ещё). В объектах же хранят коллекции данных или более сложные структуры. -The `symbol` type is used to create unique identifiers for objects. We have to mention it here for completeness, but it's better to study this type after objects. +Объекты занимают важное место в языке и требуют особого внимания. Мы разберёмся с ними в главе после того, как узнаем больше о примитивах. -## The typeof operator [#type-typeof] +Тип `symbol` (символ) используется для создания уникальных идентификаторов в объектах. Мы упоминаем здесь о нём для полноты картины, изучим этот тип после объектов. -The `typeof` operator returns the type of the argument. It's useful when we want to process values of different types differently or just want to do a quick check. +## Оператор typeof [#type-typeof] -It supports two forms of syntax: +Оператор `typeof` возвращает тип аргумента. Это полезно, когда мы хотим обрабатывать значения различных типов по-разному или просто хотим сделать проверку. -1. As an operator: `typeof x`. -2. As a function: `typeof(x)`. +У него есть две синтаксические формы: -In other words, it works with parentheses or without them. The result is the same. +```js +// Обычный синтаксис +typeof 5 // Выведет "number" +// Синтаксис, напоминающий вызов функции (встречается реже) +typeof(5) // Также выведет "number" +``` + +Если передается выражение, то нужно заключать его в скобки, т.к. typeof имеет более высокий приоритет, чем бинарные операторы: + +```js +typeof 50 + " Квартир"; // Выведет "number Квартир" +typeof (50 + " Квартир"); // Выведет "string" +``` +Другими словами, скобки необходимы для определения типа значения, которое получилось в результате выполнения выражения в них. -The call to `typeof x` returns a string with the type name: +Вызов `typeof x` возвращает строку с именем типа: ```js typeof undefined // "undefined" typeof 0 // "number" +typeof 10n // "bigint" + typeof true // "boolean" typeof "foo" // "string" @@ -217,29 +268,32 @@ typeof alert // "function" (3) */!* ``` -The last three lines may need additional explanation: +Последние три строки нуждаются в пояснении: -1. `Math` is a built-in object that provides mathematical operations. We will learn it in the chapter . Here, it serves just as an example of an object. -2. The result of `typeof null` is `"object"`. That's wrong. It is an officially recognized error in `typeof`, kept for compatibility. Of course, `null` is not an object. It is a special value with a separate type of its own. So, again, this is an error in the language. -3. The result of `typeof alert` is `"function"`, because `alert` is a function of the language. We'll study functions in the next chapters where we'll see that there's no special "function" type in JavaScript. Functions belong to the object type. But `typeof` treats them differently. Formally, it's incorrect, but very convenient in practice. +1. `Math` — это встроенный объект, который предоставляет математические операции и константы. Мы рассмотрим его подробнее в главе . Здесь он служит лишь примером объекта. +2. Результатом вызова `typeof null` является `"object"`. Это официально признанная ошибка в `typeof`, ведущая начало с времён создания JavaScript и сохранённая для совместимости. Конечно, `null` не является объектом. Это специальное значение с отдельным типом. +3. Вызов `typeof alert` возвращает `"function"`, потому что `alert` является функцией. Мы изучим функции в следующих главах, где заодно увидим, что в JavaScript нет специального типа "функция". Функции относятся к объектному типу. Но `typeof` обрабатывает их особым образом, возвращая `"function"`. Так тоже повелось от создания JavaScript. Формально это неверно, но может быть удобным на практике. -## Summary +## Итого -There are 7 basic data types in JavaScript. +В JavaScript есть 8 основных типов данных. -- `number` for numbers of any kind: integer or floating-point. -- `string` for strings. A string may have one or more characters, there's no separate single-character type. -- `boolean` for `true`/`false`. -- `null` for unknown values -- a standalone type that has a single value `null`. -- `undefined` for unassigned values -- a standalone type that has a single value `undefined`. -- `object` for more complex data structures. -- `symbol` for unique identifiers. +- Семь из них называют «примитивными» типами данных: + - `number` для любых чисел: целочисленных или чисел с плавающей точкой; целочисленные значения ограничены диапазоном ±(253-1). + - `bigint` для целых чисел произвольной длины. + - `string` для строк. Строка может содержать ноль или больше символов, нет отдельного символьного типа. + - `boolean` для `true`/`false`. + - `null` для неизвестных значений -- отдельный тип, имеющий одно значение `null`. + - `undefined` для неприсвоенных значений -- отдельный тип, имеющий одно значение `undefined`. + - `symbol` для уникальных идентификаторов. +- И один не является «примитивным» и стоит особняком: + - `object` для более сложных структур данных. -The `typeof` operator allows us to see which type is stored in a variable. +Оператор `typeof` позволяет нам увидеть, какой тип данных сохранён в переменной. -- Two forms: `typeof x` or `typeof(x)`. -- Returns a string with the name of the type, like `"string"`. -- For `null` returns `"object"` -- this is an error in the language, it's not actually an object. +- Имеет две формы: `typeof x` или `typeof(x)`. +- Возвращает строку с именем типа. Например, `"string"`. +- Для `null` возвращается `"object"` -- это ошибка в языке, на самом деле это не объект. -In the next chapters, we'll concentrate on primitive values and once we're familiar with them, we'll move on to objects. +В следующих главах мы сконцентрируемся на примитивных значениях, а когда познакомимся с ними, перейдём к объектам. diff --git a/1-js/02-first-steps/09-alert-prompt-confirm/1-simple-page/solution.md b/1-js/02-first-steps/06-alert-prompt-confirm/1-simple-page/solution.md similarity index 100% rename from 1-js/02-first-steps/09-alert-prompt-confirm/1-simple-page/solution.md rename to 1-js/02-first-steps/06-alert-prompt-confirm/1-simple-page/solution.md diff --git a/1-js/02-first-steps/09-alert-prompt-confirm/1-simple-page/task.md b/1-js/02-first-steps/06-alert-prompt-confirm/1-simple-page/task.md similarity index 100% rename from 1-js/02-first-steps/09-alert-prompt-confirm/1-simple-page/task.md rename to 1-js/02-first-steps/06-alert-prompt-confirm/1-simple-page/task.md diff --git a/1-js/02-first-steps/06-alert-prompt-confirm/article.md b/1-js/02-first-steps/06-alert-prompt-confirm/article.md new file mode 100644 index 0000000000..6a0bce1713 --- /dev/null +++ b/1-js/02-first-steps/06-alert-prompt-confirm/article.md @@ -0,0 +1,105 @@ +# Взаимодействие: alert, prompt, confirm + +Так как мы будем использовать браузер как демо-среду, нам нужно познакомиться с несколькими функциями его интерфейса, а именно: `alert`, `prompt` и `confirm`. + +## alert + +С этой функцией мы уже знакомы. Она показывает сообщение и ждёт, пока пользователь нажмёт кнопку "ОК". + +Например: + +```js run +alert("Hello"); +``` + +Это небольшое окно с сообщением называется *модальным окном*. Понятие *модальное* означает, что пользователь не может взаимодействовать с интерфейсом остальной части страницы, нажимать на другие кнопки и т.д. до тех пор, пока взаимодействует с окном. В данном случае -- пока не будет нажата кнопка "OK". + +## prompt + +Функция `prompt` принимает два аргумента: + +```js no-beautify +result = prompt(title, [default]); +``` + +Этот код отобразит модальное окно с текстом, полем для ввода текста и кнопками OK/Отмена. + +`title` +: Текст для отображения в окне. + +`default` +: Необязательный второй параметр, который устанавливает начальное значение в поле для текста в окне. + +```smart header="Квадратные скобки в синтаксисе `[...]`" +Квадратные скобки вокруг `default` в описанном выше синтаксисе означают, что параметр факультативный, необязательный. +``` + +Пользователь может напечатать что-либо в поле ввода и нажать OK. Введённый текст будет присвоен переменной `result`. Пользователь также может отменить ввод нажатием на кнопку "Отмена" или нажав на клавишу `key:Esc`. В этом случае значением `result` станет `null`. + +Вызов `prompt` возвращает текст, указанный в поле для ввода, или `null`, если ввод отменён пользователем. + +Например: + +```js run +let age = prompt('Сколько тебе лет?', 100); + +alert(`Тебе ${age} лет!`); // Тебе 100 лет! +``` + +````warn header="Для IE: всегда устанавливайте значение по умолчанию" +Второй параметр является необязательным, но если не указать его, то Internet Explorer вставит строку `"undefined"` в поле для ввода. + +Запустите код в Internet Explorer и посмотрите на результат: + +```js run +let test = prompt("Test"); +``` + +Чтобы `prompt` хорошо выглядел в IE, рекомендуется всегда указывать второй параметр: + +```js run +let test = prompt("Test", ''); // <-- для IE +``` +```` + +## confirm + +Синтаксис: + +```js +result = confirm(question); +``` + +Функция `confirm` отображает модальное окно с текстом вопроса `question` и двумя кнопками: OK и Отмена. + +Результат -- `true`, если нажата кнопка OK. В других случаях -- `false`. + +Например: + +```js run +let isBoss = confirm("Ты здесь главный?"); + +alert( isBoss ); // true, если нажата OK +``` + +## Итого + +Мы рассмотрели 3 функции браузера для взаимодействия с пользователем: + +`alert` +: показывает сообщение. + +`prompt` +: показывает сообщение и запрашивает ввод текста от пользователя. Возвращает напечатанный в поле ввода текст или `null`, если была нажата кнопка "Отмена" или `key:Esc` с клавиатуры. + +`confirm` +: показывает сообщение и ждёт, пока пользователь нажмёт OK или Отмена. Возвращает `true`, если нажата OK, и `false`, если нажата кнопка "Отмена" или `key:Esc` с клавиатуры. + +Все эти методы являются модальными: останавливают выполнение скриптов и не позволяют пользователю взаимодействовать с остальной частью страницы до тех пор, пока окно не будет закрыто. + +На все указанные методы распространяются два ограничения: + +1. Расположение окон определяется браузером. Обычно окна находятся в центре. +2. Визуальное отображение окон зависит от браузера, и мы не можем изменить их вид. + +Такова цена простоты. Есть другие способы показать более приятные глазу окна с богатой функциональностью для взаимодействия с пользователем, но если "навороты" не имеют значения, то данные методы работают отлично. diff --git a/1-js/02-first-steps/06-type-conversions/1-primitive-conversions-questions/solution.md b/1-js/02-first-steps/06-type-conversions/1-primitive-conversions-questions/solution.md deleted file mode 100644 index e2ec4190de..0000000000 --- a/1-js/02-first-steps/06-type-conversions/1-primitive-conversions-questions/solution.md +++ /dev/null @@ -1,24 +0,0 @@ - -```js no-beautify -"" + 1 + 0 = "10" // (1) -"" - 1 + 0 = -1 // (2) -true + false = 1 -6 / "3" = 2 -"2" * "3" = 6 -4 + 5 + "px" = "9px" -"$" + 4 + 5 = "$45" -"4" - 2 = 2 -"4px" - 2 = NaN -7 / 0 = Infinity -" -9 " + 5 = " -9 5" // (3) -" -9 " - 5 = -14 // (4) -null + 1 = 1 // (5) -undefined + 1 = NaN // (6) -``` - -1. Сложение со строкой `"" + 1` преобразует `1` к строке: `"" + 1 = "1"`, и в следующем случае `"1" + 0`, работает то же самое правило. -2. Вычитание `-` (как и большинство математических операторов) работает только с числами, пустая строка `""` приводится к `0`. -3. Складываясь со строкой, `5` становится строчкой. -4. Вычитание всегда преобразует к числу, значит строка `" -9 "` становится числом `-9` (пробелы по краям обрезаются). -5. `null` становится `0` после численного преобразования. -6. `undefined` становится `NaN` после численного преобразования. diff --git a/1-js/02-first-steps/06-type-conversions/1-primitive-conversions-questions/task.md b/1-js/02-first-steps/06-type-conversions/1-primitive-conversions-questions/task.md deleted file mode 100644 index 821bc3f385..0000000000 --- a/1-js/02-first-steps/06-type-conversions/1-primitive-conversions-questions/task.md +++ /dev/null @@ -1,26 +0,0 @@ -важность: 5 - ---- - -# Преобразование типов - -Какой результат будет у выражений ниже? - -```js no-beautify -"" + 1 + 0 -"" - 1 + 0 -true + false -6 / "3" -"2" * "3" -4 + 5 + "px" -"$" + 4 + 5 -"4" - 2 -"4px" - 2 -7 / 0 -" -9 " + 5 -" -9 " - 5 -null + 1 -undefined + 1 -``` - -Подумайте как следует. Когда закончите – сверьтесь с решением. \ No newline at end of file diff --git a/1-js/02-first-steps/06-type-conversions/article.md b/1-js/02-first-steps/06-type-conversions/article.md deleted file mode 100644 index c865ec1bdd..0000000000 --- a/1-js/02-first-steps/06-type-conversions/article.md +++ /dev/null @@ -1,160 +0,0 @@ -# Преобразование типов - -Чаще всего, операторы и функции автоматически приводят переданные им значения к нужному типу. - -Например, `alert` автоматически преобразует любое значение к строке. Математические операторы преобразуют значения к числам. - -Есть также случаи, когда нам нужно явно преобразовать значение в ожидаемый тип. - -```smart header="Пока что мы не говорим об объектах" -В этой главе мы не касаемся объектов. Сначала мы разберем преобразование примитивных значений. Мы разберем преобразование объектов позже, в главе . -``` - -## Строковое преобразование - -Строковое преобразование происходит, когда требуется представление чего-либо в виде строки. - -Например, `alert(value)` преобразует значение к строке. - -Также мы можем использовать функцию `String(value)` чтобы преобразовать значение к строке: - -```js run -let value = true; -alert(typeof value); // boolean - -*!* -value = String(value); // теперь value это строка "true" -alert(typeof value); // string -*/!* -``` - -Преобразование происходит наиболее очевидным способом. `false` становится `"false"`, `null` становится `"null"` и т.п. - -## Численное преобразование - -Численное преобразование происходит в математических функциях и выражениях. - -Например, когда операция деления `/` применяется не к числу: - -```js run -alert( "6" / "2" ); // 3, Строки преобразуются в значения. -``` - -Мы можем использовать функцию `Number(value)` чтобы явно преобразовать `value` к числу: - -```js run -let str = "123"; -alert(typeof str); // string - -let num = Number(str); // становится числом 123 - -alert(typeof num); // number -``` - -Явноe преобразование часто применяется, когда мы ожидаем получить число из строковых источников, вроде форм текстового ввода. - -Если строка не может быть явно приведена к числу, то результатом преобразования будет `NaN` Например: - -```js run -let age = Number("Любая строка вместо числа"); - -alert(age); // NaN, преобразование не удалось -``` - -Правила численного преобразования: - -| Значение | Преобразуется в... | -|----------|-------------| -|`undefined`|`NaN`| -|`null`|`0`| -|true / false | `1` / `0` | -| `string` | Пробельные символы по краям обрезаются. Далее, если остаётся пустая строка, то `0`, иначе из непустой строки "считывается" число. При ошибке результат `NaN`.| - -Примеры: - -```js run -alert( Number(" 123 ") ); // 123 -alert( Number("123z") ); // NaN (ошибка чтения числа в "z") -alert( Number(true) ); // 1 -alert( Number(false) ); // 0 -``` - -Учтите, что `null` и `undefined` ведут себя по-разному. Так, `null` становится нулём, тогда как `undefined` приводится к `NaN`. - -````smart header="Сложение '+' объединяет строки" -Почти все математические операторы выполняют численное преобразование. Исключение составляет `+`. Если одно из слагаемых является строкой, тогда и все остальные приводятся к строкам. - -Тогда, они конкатенируются (присоединяются) друг к другу: - -```js run -alert( 1 + '2' ); // '12' (строка справа) -alert( '1' + 2 ); // '12' (строка слева) -``` - -Так происходит, только если хотя бы один из аргументов является строкой. Во всех остальных случаях, значения складываются как числа. -```` - -## Логическое преобразование - -Логическое преобразование самое простое. - -Происходит в логических операторах (позже мы познакомимся с условиями подобными конструкциями) но так же может быть выполнено явно с помощью функции `Boolean(value)`. - -Правило преобразования: - -- Значения, которые интуитивно "пустые", вроде `0`, пустой строки, `null`, `undefined`, и `NaN`, становятся `false`. -- Все остальные значения становятся `true`. - -Например: - -```js run -alert( Boolean(1) ); // true -alert( Boolean(0) ); // false - -alert( Boolean("Привет!") ); // true -alert( Boolean("") ); // false -``` - -````warn header="Учтите, что строчка с нулём `\"0\"` это `true`" -Некоторые языки (к примеру, PHP) воспринимают `"0"` как `false`. Но в JavaScript, если строка не пустая, то она всегда `true`. - -```js run -alert( Boolean("0") ); // true -alert( Boolean(" ") ); // пробел это тоже true (любая непустая строка это true) -``` -```` - - -## Итого - -Существует 3 наиболее широко используемых преобразований: строковое, численное и логическое. - -**`Строковое`** -- Происходит, когда там нужно что-то вывести. Может быть вызвано с помощью `String(value)`. Для примитивных значений работает очевидным образом. - -**`Численное`** -- Происходит в математических операциях. Может быть вызвано с помощью `Number(value)`. - -Преобразование подчиняется правилам: - -| Значение | Становится... | -|-------|-------------| -|`undefined`|`NaN`| -|`null`|`0`| -|true / false | `1 / 0` | -| `string` | Пробельные символы по краям обрезаются. Далее, если остаётся пустая строка, то `0`, иначе из непустой строки "считывается" число. При ошибке результат `NaN`.| - -**`Логическое`** -- Происходит в логических операторах. Может быть вызвано с помощью `Boolean(value)`. - -Подчиняется правилам: - -| Значение | Становится... | -|----------|-------------| -|`0`, `null`, `undefined`, `NaN`, `""` |`false`| -|любое другое значение| `true` | - - -Большую часть из этих правил легко понять и запомнить. Особые случаи, в которых часто допускаются ошибки: - -- `undefined` при численном преобразовании становится `NaN`, не `0`. -- `"0"` и строки с одними пробелами вроде `" "` при логическом преобразовании всегда `true`. - -В этой главе мы не говорили об объектах. Мы вернемся к ним позже, в главе , посвященной только объектам, сразу после того как узнаем больше про основы JavaScript. diff --git a/1-js/02-first-steps/07-operators/1-increment-order/solution.md b/1-js/02-first-steps/07-operators/1-increment-order/solution.md deleted file mode 100644 index 8a44d798eb..0000000000 --- a/1-js/02-first-steps/07-operators/1-increment-order/solution.md +++ /dev/null @@ -1,18 +0,0 @@ - -The answer is: - -- `a = 2` -- `b = 2` -- `c = 2` -- `d = 1` - -```js run no-beautify -let a = 1, b = 1; - -alert( ++a ); // 2, prefix form returns the new value -alert( b++ ); // 1, postfix form returns the old value - -alert( a ); // 2, incremented once -alert( b ); // 2, incremented once -``` - diff --git a/1-js/02-first-steps/07-operators/1-increment-order/task.md b/1-js/02-first-steps/07-operators/1-increment-order/task.md deleted file mode 100644 index 7db0923890..0000000000 --- a/1-js/02-first-steps/07-operators/1-increment-order/task.md +++ /dev/null @@ -1,14 +0,0 @@ -importance: 5 - ---- - -# The postfix and prefix forms - -What are the final values of all variables `a`, `b`, `c` and `d` after the code below? - -```js -let a = 1, b = 1; - -let c = ++a; // ? -let d = b++; // ? -``` diff --git a/1-js/02-first-steps/07-operators/2-assignment-result/solution.md b/1-js/02-first-steps/07-operators/2-assignment-result/solution.md deleted file mode 100644 index e3113b4cd3..0000000000 --- a/1-js/02-first-steps/07-operators/2-assignment-result/solution.md +++ /dev/null @@ -1,5 +0,0 @@ -The answer is: - -- `a = 4` (multiplied by 2) -- `x = 5` (calculated as 1 + 4) - diff --git a/1-js/02-first-steps/07-operators/2-assignment-result/task.md b/1-js/02-first-steps/07-operators/2-assignment-result/task.md deleted file mode 100644 index 5345c9485b..0000000000 --- a/1-js/02-first-steps/07-operators/2-assignment-result/task.md +++ /dev/null @@ -1,13 +0,0 @@ -importance: 3 - ---- - -# Assignment result - -What are the values of `a` and `x` after the code below? - -```js -let a = 2; - -let x = 1 + (a *= 2); -``` diff --git a/1-js/02-first-steps/07-operators/article.md b/1-js/02-first-steps/07-operators/article.md deleted file mode 100644 index 74b27e8713..0000000000 --- a/1-js/02-first-steps/07-operators/article.md +++ /dev/null @@ -1,446 +0,0 @@ -# Operators - -We know many operators from school. They are things like addition `+`, multiplication `*`, subtraction `-`, and so on. - -In this chapter, we'll concentrate on aspects of operators that are not covered by school arithmetic. - -## Terms: "unary", "binary", "operand" - -Before we move on, let's grasp some common terminology. - -- *An operand* -- is what operators are applied to. For instance, in the multiplication of `5 * 2` there are two operands: the left operand is `5` and the right operand is `2`. Sometimes, people call these "arguments" instead of "operands". -- An operator is *unary* if it has a single operand. For example, the unary negation `-` reverses the sign of a number: - - ```js run - let x = 1; - - *!* - x = -x; - */!* - alert( x ); // -1, unary negation was applied - ``` -- An operator is *binary* if it has two operands. The same minus exists in binary form as well: - - ```js run no-beautify - let x = 1, y = 3; - alert( y - x ); // 2, binary minus subtracts values - ``` - - Formally, we're talking about two different operators here: the unary negation (single operand: reverses the sign) and the binary subtraction (two operands: subtracts). - -## String concatenation, binary + - -Now, let's see special features of JavaScript operators that are beyond school arithmetics. - -Usually, the plus operator `+` sums numbers. - -But, if the binary `+` is applied to strings, it merges (concatenates) them: - -```js -let s = "my" + "string"; -alert(s); // mystring -``` - -Note that if one of the operands is a string, the other one is converted to a string too. - -For example: - -```js run -alert( '1' + 2 ); // "12" -alert( 2 + '1' ); // "21" -``` - -See, it doesn't matter whether the first operand is a string or the second one. The rule is simple: if either operand is a string, the other one is converted into a string as well. - -However, note that operations run from left to right. If there are two numbers followed by a string, the numbers will be added before being converted to a string: - - -```js run -alert(2 + 2 + '1' ); // "41" and not "221" -``` - -String concatenation and conversion is a special feature of the binary plus `+`. Other arithmetic operators work only with numbers and always convert their operands to numbers. - -For instance, subtraction and division: - -```js run -alert( 2 - '1' ); // 1 -alert( '6' / '2' ); // 3 -``` - -## Numeric conversion, unary + - -The plus `+` exists in two forms: the binary form that we used above and the unary form. - -The unary plus or, in other words, the plus operator `+` applied to a single value, doesn't do anything to numbers. But if the operand is not a number, the unary plus converts it into a number. - -For example: - -```js run -// No effect on numbers -let x = 1; -alert( +x ); // 1 - -let y = -2; -alert( +y ); // -2 - -*!* -// Converts non-numbers -alert( +true ); // 1 -alert( +"" ); // 0 -*/!* -``` - -It actually does the same thing as `Number(...)`, but is shorter. - -The need to convert strings to numbers arises very often. For example, if we are getting values from HTML form fields, they are usually strings. - -What if we want to sum them? - -The binary plus would add them as strings: - -```js run -let apples = "2"; -let oranges = "3"; - -alert( apples + oranges ); // "23", the binary plus concatenates strings -``` - -If we want to treat them as numbers, we need to convert and then sum them: - -```js run -let apples = "2"; -let oranges = "3"; - -*!* -// both values converted to numbers before the binary plus -alert( +apples + +oranges ); // 5 -*/!* - -// the longer variant -// alert( Number(apples) + Number(oranges) ); // 5 -``` - -From a mathematician's standpoint, the abundance of pluses may seem strange. But from a programmer's standpoint, there's nothing special: unary pluses are applied first, they convert strings to numbers, and then the binary plus sums them up. - -Why are unary pluses applied to values before the binary ones? As we're going to see, that's because of their *higher precedence*. - -## Operator precedence - -If an expression has more than one operator, the execution order is defined by their *precedence*, or, in other words, the implicit priority order of operators. - -From school, we all know that the multiplication in the expression `1 + 2 * 2` should be calculated before the addition. That's exactly the precedence thing. The multiplication is said to have *a higher precedence* than the addition. - -Parentheses override any precedence, so if we're not satisfied with the implicit order, we can use them to change it. For example: `(1 + 2) * 2`. - -There are many operators in JavaScript. Every operator has a corresponding precedence number. The one with the larger number executes first. If the precedence is the same, the execution order is from left to right. - -Here's an extract from the [precedence table](https://developer.mozilla.org/en/JavaScript/Reference/operators/operator_precedence) (you don't need to remember this, but note that unary operators are higher than corresponding binary ones): - -| Precedence | Name | Sign | -|------------|------|------| -| ... | ... | ... | -| 16 | unary plus | `+` | -| 16 | unary negation | `-` | -| 14 | multiplication | `*` | -| 14 | division | `/` | -| 13 | addition | `+` | -| 13 | subtraction | `-` | -| ... | ... | ... | -| 3 | assignment | `=` | -| ... | ... | ... | - -As we can see, the "unary plus" has a priority of `16` which is higher than the `13` of "addition" (binary plus). That's why, in the expression `"+apples + +oranges"`, unary pluses work before the addition. - -## Assignment - -Let's note that an assignment `=` is also an operator. It is listed in the precedence table with the very low priority of `3`. - -That's why, when we assign a variable, like `x = 2 * 2 + 1`, the calculations are done first and then the `=` is evaluated, storing the result in `x`. - -```js -let x = 2 * 2 + 1; - -alert( x ); // 5 -``` - -It is possible to chain assignments: - -```js run -let a, b, c; - -*!* -a = b = c = 2 + 2; -*/!* - -alert( a ); // 4 -alert( b ); // 4 -alert( c ); // 4 -``` - -Chained assignments evaluate from right to left. First, the rightmost expression `2 + 2` is evaluated and then assigned to the variables on the left: `c`, `b` and `a`. At the end, all the variables share a single value. - -````smart header="The assignment operator `\"=\"` returns a value" -An operator always returns a value. That's obvious for most of them like addition `+` or multiplication `*`. But the assignment operator follows this rule too. - -The call `x = value` writes the `value` into `x` *and then returns it*. - -Here's a demo that uses an assignment as part of a more complex expression: - -```js run -let a = 1; -let b = 2; - -*!* -let c = 3 - (a = b + 1); -*/!* - -alert( a ); // 3 -alert( c ); // 0 -``` - -In the example above, the result of `(a = b + 1)` is the value which is assigned to `a` (that is `3`). It is then used to subtract from `3`. - -Funny code, isn't it? We should understand how it works, because sometimes we see it in 3rd-party libraries, but shouldn't write anything like that ourselves. Such tricks definitely don't make code clearer or readable. -```` - -## Remainder % - -The remainder operator `%`, despite its appearance, is not related to percents. - -The result of `a % b` is the remainder of the integer division of `a` by `b`. - -For instance: - -```js run -alert( 5 % 2 ); // 1 is a remainder of 5 divided by 2 -alert( 8 % 3 ); // 2 is a remainder of 8 divided by 3 -alert( 6 % 3 ); // 0 is a remainder of 6 divided by 3 -``` - -## Exponentiation ** - -The exponentiation operator `**` is a recent addition to the language. - -For a natural number `b`, the result of `a ** b` is `a` multiplied by itself `b` times. - -For instance: - -```js run -alert( 2 ** 2 ); // 4 (2 * 2) -alert( 2 ** 3 ); // 8 (2 * 2 * 2) -alert( 2 ** 4 ); // 16 (2 * 2 * 2 * 2) -``` - -The operator works for non-integer numbers as well. - -For instance: - -```js run -alert( 4 ** (1/2) ); // 2 (power of 1/2 is the same as a square root, that's maths) -alert( 8 ** (1/3) ); // 2 (power of 1/3 is the same as a cubic root) -``` - -## Increment/decrement - - - -Increasing or decreasing a number by one is among the most common numerical operations. - -So, there are special operators for it: - -- **Increment** `++` increases a variable by 1: - - ```js run no-beautify - let counter = 2; - counter++; // works the same as counter = counter + 1, but is shorter - alert( counter ); // 3 - ``` -- **Decrement** `--` decreases a variable by 1: - - ```js run no-beautify - let counter = 2; - counter--; // works the same as counter = counter - 1, but is shorter - alert( counter ); // 1 - ``` - -```warn -Increment/decrement can only be applied to variables. Trying to use it on a value like `5++` will give an error. -``` - -The operators `++` and `--` can be placed either before or after a variable. - -- When the operator goes after the variable, it is in "postfix form": `counter++`. -- The "prefix form" is when the operator goes before the variable: `++counter`. - -Both of these statements do the same thing: increase `counter` by `1`. - -Is there any difference? Yes, but we can only see it if we use the returned value of `++/--`. - -Let's clarify. As we know, all operators return a value. Increment/decrement is no exception. The prefix form returns the new value while the postfix form returns the old value (prior to increment/decrement). - -To see the difference, here's an example: - -```js run -let counter = 1; -let a = ++counter; // (*) - -alert(a); // *!*2*/!* -``` - -In the line `(*)`, the *prefix* form `++counter` increments `counter` and returns the new value, `2`. So, the `alert` shows `2`. - -Now, let's use the postfix form: - -```js run -let counter = 1; -let a = counter++; // (*) changed ++counter to counter++ - -alert(a); // *!*1*/!* -``` - -In the line `(*)`, the *postfix* form `counter++` also increments `counter` but returns the *old* value (prior to increment). So, the `alert` shows `1`. - -To summarize: - -- If the result of increment/decrement is not used, there is no difference in which form to use: - - ```js run - let counter = 0; - counter++; - ++counter; - alert( counter ); // 2, the lines above did the same - ``` -- If we'd like to increase a value *and* immediately use the result of the operator, we need the prefix form: - - ```js run - let counter = 0; - alert( ++counter ); // 1 - ``` -- If we'd like to increment a value but use its previous value, we need the postfix form: - - ```js run - let counter = 0; - alert( counter++ ); // 0 - ``` - -````smart header="Increment/decrement among other operators" -The operators `++/--` can be used inside expressions as well. Their precedence is higher than most other arithmetical operations. - -For instance: - -```js run -let counter = 1; -alert( 2 * ++counter ); // 4 -``` - -Compare with: - -```js run -let counter = 1; -alert( 2 * counter++ ); // 2, because counter++ returns the "old" value -``` - -Though technically okay, such notation usually makes code less readable. One line does multiple things -- not good. - -While reading code, a fast "vertical" eye-scan can easily miss something like `counter++` and it won't be obvious that the variable increased. - -We advise a style of "one line -- one action": - -```js run -let counter = 1; -alert( 2 * counter ); -counter++; -``` -```` - -## Bitwise operators - -Bitwise operators treat arguments as 32-bit integer numbers and work on the level of their binary representation. - -These operators are not JavaScript-specific. They are supported in most programming languages. - -The list of operators: - -- AND ( `&` ) -- OR ( `|` ) -- XOR ( `^` ) -- NOT ( `~` ) -- LEFT SHIFT ( `<<` ) -- RIGHT SHIFT ( `>>` ) -- ZERO-FILL RIGHT SHIFT ( `>>>` ) - -These operators are used very rarely. To understand them, we need to delve into low-level number representation and it would not be optimal to do that right now, especially since we won't need them any time soon. If you're curious, you can read the [Bitwise Operators](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators) article on MDN. It would be more practical to do that when a real need arises. - -## Modify-in-place - -We often need to apply an operator to a variable and store the new result in that same variable. - -For example: - -```js -let n = 2; -n = n + 5; -n = n * 2; -``` - -This notation can be shortened using the operators `+=` and `*=`: - -```js run -let n = 2; -n += 5; // now n = 7 (same as n = n + 5) -n *= 2; // now n = 14 (same as n = n * 2) - -alert( n ); // 14 -``` - -Short "modify-and-assign" operators exist for all arithmetical and bitwise operators: `/=`, `-=`, etc. - -Such operators have the same precedence as a normal assignment, so they run after most other calculations: - -```js run -let n = 2; - -n *= 3 + 5; - -alert( n ); // 16 (right part evaluated first, same as n *= 8) -``` - -## Comma - -The comma operator `,` is one of the rarest and most unusual operators. Sometimes, it's used to write shorter code, so we need to know it in order to understand what's going on. - -The comma operator allows us to evaluate several expressions, dividing them with a comma `,`. Each of them is evaluated but only the result of the last one is returned. - -For example: - -```js run -*!* -let a = (1 + 2, 3 + 4); -*/!* - -alert( a ); // 7 (the result of 3 + 4) -``` - -Here, the first expression `1 + 2` is evaluated and its result is thrown away. Then, `3 + 4` is evaluated and returned as the result. - -```smart header="Comma has a very low precedence" -Please note that the comma operator has very low precedence, lower than `=`, so parentheses are important in the example above. - -Without them: `a = 1 + 2, 3 + 4` evaluates `+` first, summing the numbers into `a = 3, 7`, then the assignment operator `=` assigns `a = 3`, and finally the number after the comma, `7`, is not processed so it's ignored. -``` - -Why do we need an operator that throws away everything except the last part? - -Sometimes, people use it in more complex constructs to put several actions in one line. - -For example: - -```js -// three operations in one line -for (*!*a = 1, b = 3, c = a * b*/!*; a < 10; a++) { - ... -} -``` - -Such tricks are used in many JavaScript frameworks. That's why we're mentioning them. But, usually, they don't improve code readability so we should think well before using them. diff --git a/1-js/02-first-steps/07-type-conversions/article.md b/1-js/02-first-steps/07-type-conversions/article.md new file mode 100644 index 0000000000..bd413fa539 --- /dev/null +++ b/1-js/02-first-steps/07-type-conversions/article.md @@ -0,0 +1,150 @@ +# Преобразование типов + +Чаще всего операторы и функции автоматически приводят переданные им значения к нужному типу. + +Например, `alert` автоматически преобразует любое значение к строке. Математические операторы преобразуют значения к числам. + +Есть также случаи, когда нам нужно явно преобразовать значение в ожидаемый тип. + +```smart header="Пока что мы не говорим об объектах" +В этой главе мы не касаемся объектов. Сначала мы разберём преобразование примитивных значений. + +Мы разберём преобразование объектов позже, в главе . +``` + +## Строковое преобразование + +Строковое преобразование происходит, когда требуется представление чего-либо в виде строки. + +Например, `alert(value)` преобразует значение к строке. + +Также мы можем использовать функцию `String(value)`, чтобы преобразовать значение к строке: + +```js run +let value = true; +alert(typeof value); // boolean + +*!* +value = String(value); // теперь value это строка "true" +alert(typeof value); // string +*/!* +``` + +Преобразование происходит очевидным образом. `false` становится `"false"`, `null` становится `"null"` и т.п. + +## Численное преобразование + +Численное преобразование происходит в математических функциях и выражениях. + +Например, когда операция деления `/` применяется не к числу: + +```js run +alert( "6" / "2" ); // 3, строки преобразуются в числа +``` + +Мы можем использовать функцию `Number(value)`, чтобы явно преобразовать `value` к числу: + +```js run +let str = "123"; +alert(typeof str); // string + +let num = Number(str); // становится числом 123 + +alert(typeof num); // number +``` + +Явное преобразование часто применяется, когда мы ожидаем получить число из строкового контекста, например из текстовых полей форм. + +Если строка не может быть явно приведена к числу, то результатом преобразования будет `NaN`. Например: + +```js run +let age = Number("Любая строка вместо числа"); + +alert(age); // NaN, преобразование не удалось +``` + +Правила численного преобразования: + +| Значение | Преобразуется в... | +|----------|-------------| +|`undefined`|`NaN`| +|`null`|`0`| +|true / false | `1` / `0` | +| `string` | Пробельные символы (пробелы, знаки табуляции `\t`, знаки новой строки `\n` и т. п.) по краям обрезаются. Далее, если остаётся пустая строка, то получаем `0`, иначе из непустой строки "считывается" число. При ошибке результат `NaN`.| + +Примеры: + +```js run +alert( Number(" 123 ") ); // 123 +alert( Number("123z") ); // NaN (ошибка чтения числа на месте символа "z") +alert( Number(true) ); // 1 +alert( Number(false) ); // 0 +``` + +Учтите, что `null` и `undefined` ведут себя по-разному. Так, `null` становится нулём, тогда как `undefined` приводится к `NaN`. + +Большинство математических операторов также производит данное преобразование, как мы увидим в следующей главе. + +## Логическое преобразование + +Логическое преобразование самое простое. + +Происходит в логических операциях (позже мы познакомимся с условными проверками и подобными конструкциями), но также может быть выполнено явно с помощью функции `Boolean(value)`. + +Правило преобразования: + +- Значения, которые интуитивно "пустые", вроде `0`, пустой строки, `null`, `undefined` и `NaN`, становятся `false`. +- Все остальные значения становятся `true`. + +Например: + +```js run +alert( Boolean(1) ); // true +alert( Boolean(0) ); // false + +alert( Boolean("Привет!") ); // true +alert( Boolean("") ); // false +``` + +````warn header="Заметим, что строка с нулём `\"0\"` — это `true`" +Некоторые языки (к примеру, PHP) воспринимают строку `"0"` как `false`. Но в JavaScript, если строка не пустая, то она всегда `true`. + +```js run +alert( Boolean("0") ); // true +alert( Boolean(" ") ); // пробел это тоже true (любая непустая строка это true) +``` +```` + +## Итого + +Существует 3 наиболее широко используемых преобразования: строковое, численное и логическое. + +**`Строковое`** -- Происходит, когда нам нужно что-то вывести. Может быть вызвано с помощью `String(value)`. Для примитивных значений работает очевидным образом. + +**`Численное`** -- Происходит в математических операциях. Может быть вызвано с помощью `Number(value)`. + +Преобразование подчиняется правилам: + +| Значение | Становится... | +|-------|-------------| +|`undefined`|`NaN`| +|`null`|`0`| +|true / false | `1 / 0` | +| `string` | Пробельные символы по краям обрезаются. Далее, если остаётся пустая строка, то получаем `0`, иначе из непустой строки "считывается" число. При ошибке результат `NaN`.| + +**`Логическое`** -- Происходит в логических операциях. Может быть вызвано с помощью `Boolean(value)`. + +Подчиняется правилам: + +| Значение | Становится... | +|----------|-------------| +|`0`, `null`, `undefined`, `NaN`, `""` |`false`| +|любое другое значение| `true` | + + +Большую часть из этих правил легко понять и запомнить. Особые случаи, в которых часто допускаются ошибки: + +- `undefined` при численном преобразовании становится `NaN`, не `0`. +- `"0"` и строки из одних пробелов типа `" "` при логическом преобразовании всегда `true`. + +В этой главе мы не говорили об объектах. Мы вернёмся к ним позже, в главе , посвящённой только объектам, сразу после того, как узнаем больше про основы JavaScript. diff --git a/1-js/02-first-steps/08-comparison/1-comparison-questions/solution.md b/1-js/02-first-steps/08-comparison/1-comparison-questions/solution.md deleted file mode 100644 index fd1c0f6224..0000000000 --- a/1-js/02-first-steps/08-comparison/1-comparison-questions/solution.md +++ /dev/null @@ -1,21 +0,0 @@ - - -```js no-beautify -5 > 4 → true -"ананас" > "яблоко" → false -"2" > "12" → true -undefined == null → true -undefined === null → false -null == "\n0\n" → false -null === +"\n0\n" → false -``` - -Разъяснения: - -1. Очевидно, `true`. -2. Используется посимвольное сравнение, поэтому `false`. -3. Снова посимвольное сравнение. Первый символ первой строки `"2"` больше, чем первый символ второй `"1"`. -4. Специальный случай. Значения `null` и `undefinded` равны друг другу при нестрогом сравнении. -5. Строгое сравнение разных типов, поэтому `false`. -6. Смотрите (4). -7. Строгое сравнение разных типов. diff --git a/1-js/02-first-steps/08-comparison/article.md b/1-js/02-first-steps/08-comparison/article.md deleted file mode 100644 index dea557852c..0000000000 --- a/1-js/02-first-steps/08-comparison/article.md +++ /dev/null @@ -1,212 +0,0 @@ -# Операторы сравнения - -Многие операторы сравнения известны нам из математики: - -- Больше/меньше: a > b, a < b. -- Больше/меньше или равно: a >= b, a <= b. -- Равно: `a == b`. Обратите внимание, для сравнения используется двойной знак равенства `=`. Один знак равенства `a = b` означал бы присваивание. -- Не равно. В математике обозначается символом ≠. В JavaScript записывается как знак равенства с предшествующим ему восклицательным знаком: a != b. - -## Результат сравнения имеет логический тип - -Операторы сравнения, как и другие операторы, возвращают значение. Это значение имеет логический тип. - -- `true` -- означает "да", "верно", "истина". -- `false` -- означает "нет", "неверно", "ложь". - -Например: - -```js run -alert( 2 > 1 ); // true (верно) -alert( 2 == 1 ); // false (неверно) -alert( 2 != 1 ); // true (верно) -``` - -Результат сравнения можно присвоить переменной, как и любое значение: - -```js run -let result = 5 > 4; // результат сравнения присваивается переменной result -alert( result ); // true -``` - -## Сравнение строк - -Чтобы определить, что одна строка больше другой, JavaScript использует "алфавитный" или "лексикографический" порядок. - -Другими словами, строки сравниваются посимвольно. - -Например: - -```js run -alert( 'Я' > 'А' ); // true -alert( 'Кот' > 'Код' ); // true -alert( 'Сонный' > 'Сон' ); // true -``` - -Алгоритм сравнения двух строк довольно прост: - -1. Сначала сравниваются первые символы строк. -2. Если первый символ первой строки больше (меньше), чем первый символ второй, то первая строка больше (меньше) второй. -3. Если первые символы равны, то таким же образом сравниваются уже вторые символы строк. -4. Сравнение продолжается, пока не закончится одна из строк. -5. Если обе строки заканчиваются одновременно, то они равны. Иначе, большей считается более длинная строка. - -В примерах выше сравнение `'Я' > 'А'` завершится на первом шаге, тогда как строки `"Кот"` и `"Код"` будут сравниваться посимвольно: - -1. `К` равна `К`. -2. `о` равна `о`. -3. `т` больше чем `д`. На этом сравнение заканчивается. Первая строка больше. - -```smart header="Используется кодировка Unicode, а не настоящий алфавит" -Приведенный выше алгоритм сравнения похож на алгоритм, используемый в словарях и телефонных книгах, но между ними есть и различия. - -Например, в JavaScript имеет значение регистр символов. Заглавная буква `"A"` не равна строчной `"a"`. Какая же из них больше? Строчная `"a"`. Почему? Потому что строчные буквы имеют больший код во внутренней таблице кодирования, которую использует JavaScript (Unicode). -``` - -## Сравнение разных типов - -При сравнении значений разных типов, JavaScript приводит каждое из них к числу. - -Например: - -```js run -alert( '2' > 1 ); // true, строка '2' становится числом 2 -alert( '01' == 1 ); // true, строка '01' становится числом 1 -``` - -Логическое значение `true` становится `1`, а `false` – `0`. - -Например: - -```js run -alert( true == 1 ); // true -alert( false == 0 ); // true -``` - -````smart header="Забавное следствие" -Возможна следующая ситуация: - -- Два значения равны. -- Одно из них `true` как логическое значение, другое – `false`. - -Например: - -```js run -let a = 0; -alert( Boolean(a) ); // false - -let b = "0"; -alert( Boolean(b) ); // true - -alert(a == b); // true! -``` - -С точки зрения JavaScript, результат ожидаем. Равенство преобразует значения, используя числовое преобразование, поэтому `"0"` становится `0`. В то время как явное преобразование с помощью `Boolean` использует другой набор правил. -```` - -## Строгое сравнение - -Использование обычного сравнения `==` может вызывать проблемы. Например, оно не отличает `0` от `false`: - -```js run -alert( 0 == false ); // true -``` - -Та же проблема с пустой строкой: - -```js run -alert( '' == false ); // true -``` - -Это происходит из-за того, что операнды разных типов преобразуются оператором `==` к числу. В итоге, и пустая строка, и `false` становятся нулем. - -Как же тогда отличать `0` от `false`? - -**Оператор строгого равенства `===` проверяет равенство без приведения типов.** - -Другими словами, если `a` и `b` имеют разные типы, то проверка `a === b` немедленно возвращает `false` без попытки их преобразования. - -Давайте проверим: - -```js run -alert( 0 === false ); // false, так как сравниваются разные типы -``` - -Ещё есть оператор строгого неравенства `!==`, аналогичный `!=`. - -Оператор строгого равенства дольше писать, но он делает код более очевидным и оставляет меньше мест для ошибок. - -## Сравнение с null и undefined - -Рассмотрим больше крайних случаев. - -Поведение `null` и `undefined` при сравнении с другими значениями - особое: - - -При строгом равенстве `===` -: Эти значения различны, так как различны их типы. - - ```js run - alert( null === undefined ); // false - ``` - -При нестрогом равенстве `==` -: Эти значения равны друг другу и не равны никаким другим значениям. Это специальное правило языка. - - ```js run - alert( null == undefined ); // true - ``` - -При использовании математических операторов и других операторов сравнения `< > <= >=` -: Значения `null/undefined` преобразуются к числам: `null` становится `0`, а `undefined` – `NaN`. - -Посмотрим, какие забавные вещи случаются, когда мы применяем эти правила. И, что более важно, как избежать ошибок при их использовании. - -### Странный результат сравнения null и 0 - -Сравним `null` с нулем: - -```js run -alert( null > 0 ); // (1) false -alert( null == 0 ); // (2) false -alert( null >= 0 ); // (3) *!*true*/!* -``` - -С точки зрения математики это странно. Результат последнего сравнения говорит о том, что "`null` больше или равно нулю", тогда результат одного из сравнений выше должен быть `true`, но они оба ложны. - -Причина в том, что нестрогое равенство и сравнения `> < >= <=` работают по-разному. Последние преобразуют `null` в число, рассматривая его как `0`. Поэтому выражение (3) `null >= 0` истинно, а `null > 0` ложно. - -С другой стороны, для нестрогого равенства `==` значений `undefined` и `null` действует особое правило: эти значения ни к чему не приводятся, они равны друг другу и не равны ничему другому. Поэтому (2) `null == 0` ложно. - -### Несравнимое значение undefined - -Значение `undefined` несравнимо с другими значениями: - -```js run -alert( undefined > 0 ); // false (1) -alert( undefined < 0 ); // false (2) -alert( undefined == 0 ); // false (3) -``` - -Почему же сравнение `undefined` с нулем всегда ложно? - -На это есть следующие причины: - -- Сравнения `(1)` и `(2)` возвращают `false`, потому что `undefined` преобразуется в `NaN`, а `NaN` – это специальное числовое значение, которое возвращает `false` при любых сравнениях. -- Нестрогое равенство `(3)` возвращает `false`, потому что `undefined` равно только `null` и ни чему больше. - -### Как избежать проблем - -Зачем мы рассмотрели все эти примеры? Должны ли мы постоянно помнить обо всех этих особенностях? Не обязательно. Со временем, все эти запутанные правила станут вам знакомы, но можно избежать проблем, если следовать простому правилу. - -Просто относитесь к любому сравнению с `undefined/null`, кроме строгого равенства `===`, с осторожностью. - -Не используйте сравнения `>= > < <=` с переменными, которые могут принимать значения `null/undefined`, если вы не уверены в том, что делаете. Если переменная может принимать эти значения, то добавьте для них отдельные проверки. - -## Итого - -- Операторы сравнения возвращают значения логического типа. -- Строки сравниваются посимвольно в лексикографическом порядке. -- Значения разных типов при сравнении приводятся к числу. Исключением является сравнение с помощью операторов строгого равенства/неравенства. -- Значения `null` и `undefined` равны `==` друг другу и не равны любому другому. -- Будьте осторожны при использовании операторов сравнений `>` и `<` с переменными, которые могут принимать значения `null/undefined`. Хорошей идеей будет сделать отдельную проверку на `null/undefined`. diff --git a/1-js/02-first-steps/08-operators/1-increment-order/solution.md b/1-js/02-first-steps/08-operators/1-increment-order/solution.md new file mode 100644 index 0000000000..77ebe8974f --- /dev/null +++ b/1-js/02-first-steps/08-operators/1-increment-order/solution.md @@ -0,0 +1,17 @@ +Ответ: + +- `a = 2` +- `b = 2` +- `c = 2` +- `d = 1` + +```js run no-beautify +let a = 1, b = 1; + +alert( ++a ); // 2, префиксная форма возвращает новое значение +alert( b++ ); // 1, постфиксная форма возвращает старое значение + +alert( a ); // 2, значение увеличено один раз +alert( b ); // 2, значение увеличено один раз +``` + diff --git a/1-js/02-first-steps/08-operators/1-increment-order/task.md b/1-js/02-first-steps/08-operators/1-increment-order/task.md new file mode 100644 index 0000000000..c4ea257da1 --- /dev/null +++ b/1-js/02-first-steps/08-operators/1-increment-order/task.md @@ -0,0 +1,14 @@ +importance: 5 + +--- + +# Постфиксная и префиксная формы + +Чему будут равны переменные `a`, `b`, `c` и `d` в примере ниже? + +```js +let a = 1, b = 1; + +let c = ++a; // ? +let d = b++; // ? +``` diff --git a/1-js/02-first-steps/08-operators/2-assignment-result/solution.md b/1-js/02-first-steps/08-operators/2-assignment-result/solution.md new file mode 100644 index 0000000000..6c7306fb43 --- /dev/null +++ b/1-js/02-first-steps/08-operators/2-assignment-result/solution.md @@ -0,0 +1,5 @@ +Ответ: + +- `a = 4` (умножено на 2) +- `x = 5` (вычислено как 1 + 4) + diff --git a/1-js/02-first-steps/08-operators/2-assignment-result/task.md b/1-js/02-first-steps/08-operators/2-assignment-result/task.md new file mode 100644 index 0000000000..242266f946 --- /dev/null +++ b/1-js/02-first-steps/08-operators/2-assignment-result/task.md @@ -0,0 +1,13 @@ +importance: 3 + +--- + +# Результат присваивания + +Чему будут равны переменные `a` и `x` после исполнения кода в примере ниже? + +```js +let a = 2; + +let x = 1 + (a *= 2); +``` diff --git a/1-js/02-first-steps/08-operators/3-primitive-conversions-questions/solution.md b/1-js/02-first-steps/08-operators/3-primitive-conversions-questions/solution.md new file mode 100644 index 0000000000..24d683549d --- /dev/null +++ b/1-js/02-first-steps/08-operators/3-primitive-conversions-questions/solution.md @@ -0,0 +1,25 @@ + +```js no-beautify +"" + 1 + 0 = "10" // (1) +"" - 1 + 0 = -1 // (2) +true + false = 1 +6 / "3" = 2 +"2" * "3" = 6 +4 + 5 + "px" = "9px" +"$" + 4 + 5 = "$45" +"4" - 2 = 2 +"4px" - 2 = NaN +" -9 " + 5 = " -9 5" // (3) +" -9 " - 5 = -14 // (4) +null + 1 = 1 // (5) +undefined + 1 = NaN // (6) +" \t \n" - 2 = -2 // (7) +``` + +1. Сложение со строкой `"" + 1` преобразует `1` к строке: `"" + 1 = "1"`, и в следующем случае `"1" + 0` работает то же самое правило. +2. Вычитание `-` (как и большинство математических операторов) работает только с числами, пустая строка `""` приводится к `0`. +3. Сложение со строкой превращает число `5` в строку и добавляет к строке. +4. Вычитание всегда преобразует к числу, значит строка `" -9 "` становится числом `-9` (пробелы по краям обрезаются). +5. `null` становится `0` после численного преобразования. +6. `undefined` становится `NaN` после численного преобразования. +7. Пробельные символы, такие как `\t` и `\n`, по краям строки игнорируются при преобразовании в число, так что строка `" \t \n"`, аналогично пустой строке, становится `0` после численного преобразования. diff --git a/1-js/02-first-steps/08-operators/3-primitive-conversions-questions/task.md b/1-js/02-first-steps/08-operators/3-primitive-conversions-questions/task.md new file mode 100644 index 0000000000..ccabd3689b --- /dev/null +++ b/1-js/02-first-steps/08-operators/3-primitive-conversions-questions/task.md @@ -0,0 +1,26 @@ +importance: 5 + +--- + +# Преобразование типов + +Какой результат будет у выражений ниже? + +```js no-beautify +"" + 1 + 0 +"" - 1 + 0 +true + false +6 / "3" +"2" * "3" +4 + 5 + "px" +"$" + 4 + 5 +"4" - 2 +"4px" - 2 +" -9 " + 5 +" -9 " - 5 +null + 1 +undefined + 1 +" \t \n" - 2 +``` + +Подумайте как следует, запишите ответы и сверьтесь с решением. diff --git a/1-js/02-first-steps/08-operators/4-fix-prompt/solution.md b/1-js/02-first-steps/08-operators/4-fix-prompt/solution.md new file mode 100644 index 0000000000..ed5f105448 --- /dev/null +++ b/1-js/02-first-steps/08-operators/4-fix-prompt/solution.md @@ -0,0 +1,32 @@ +Причина в том, что окно запроса возвращает пользовательский ввод как строку. + +Поэтому переменные получают значения `"1"` и `"2"` соответственно. + +```js run +let a = "1"; // prompt("Первое число?", 1); +let b = "2"; // prompt("Второе число?", 2); + +alert(a + b); // 12 +``` + +Нам нужно привести строки к числам перед применением оператора `+`. Например, с помощью `Number()` или вставки `+` перед ними. + +Вставить `+` можно прямо перед `prompt`: + +```js run +let a = +prompt("Первое число?", 1); +let b = +prompt("Второе число?", 2); + +alert(a + b); // 3 +``` + +Или внутри `alert`: + +```js run +let a = prompt("Первое число?", 1); +let b = prompt("Второе число?", 2); + +alert(+a + +b); // 3 +``` + +В последнем варианте унарный и бинарный `+` используются вместе. Выглядит забавно, правда? diff --git a/1-js/02-first-steps/08-operators/4-fix-prompt/task.md b/1-js/02-first-steps/08-operators/4-fix-prompt/task.md new file mode 100644 index 0000000000..e390f8c62e --- /dev/null +++ b/1-js/02-first-steps/08-operators/4-fix-prompt/task.md @@ -0,0 +1,18 @@ +importance: 5 + +--- + +# Исправьте сложение + +Ниже приведён код, который запрашивает у пользователя два числа и показывает их сумму. + +Он работает неправильно. Код в примере выводит `12` (для значения полей по умолчанию). + +В чём ошибка? Исправьте её. Результат должен быть `3`. + +```js run +let a = prompt("Первое число?", 1); +let b = prompt("Второе число?", 2); + +alert(a + b); // 12 +``` diff --git a/1-js/02-first-steps/08-operators/article.md b/1-js/02-first-steps/08-operators/article.md new file mode 100644 index 0000000000..95685286e7 --- /dev/null +++ b/1-js/02-first-steps/08-operators/article.md @@ -0,0 +1,483 @@ +# Базовые операторы, математика + +Многие операторы знакомы нам ещё со школы: сложение `+`, умножение `*`, вычитание `-` и так далее. + +В этой главе мы начнём с простых операторов, а потом сконцентрируемся на специфических для JavaScript аспектах, которые не проходят в школьном курсе арифметики. + +## Термины: "унарный", "бинарный", "операнд" + +Прежде, чем мы двинемся дальше, давайте разберёмся с терминологией. + +- *Операнд* -- то, к чему применяется оператор. Например, в умножении `5 * 2` есть два операнда: левый операнд равен `5`, а правый операнд равен `2`. Иногда их называют "аргументами" вместо "операндов". +- *Унарным* называется оператор, который применяется к одному операнду. Например, оператор унарный минус `"-"` меняет знак числа на противоположный: + + ```js run + let x = 1; + + *!* + x = -x; + */!* + alert( x ); // -1, применили унарный минус + ``` +- *Бинарным* называется оператор, который применяется к двум операндам. Тот же минус существует и в бинарной форме: + + ```js run no-beautify + let x = 1, y = 3; + alert( y - x ); // 2, бинарный минус вычитает значения + ``` + + Формально, в последних примерах мы говорим о двух разных операторах, использующих один символ: оператор отрицания (унарный оператор, который обращает знак) и оператор вычитания (бинарный оператор, который вычитает одно число из другого). + +## Математика + +Поддерживаются следующие математические операторы: + +- Сложение `+`, +- Вычитание `-`, +- Умножение `*`, +- Деление `/`, +- Взятие остатка от деления `%`, +- Возведение в степень `**`. + +Первые четыре оператора очевидны, а про `%` и `**` стоит сказать несколько слов. + +### Взятие остатка % + +Оператор взятия остатка `%`, несмотря на обозначение, никакого отношения к процентам не имеет. + +Результат `a % b` – это [остаток](https://ru.wikipedia.org/wiki/Деление_с_остатком) от целочисленного деления `a` на `b`. + +Например: + +```js run +alert( 5 % 2 ); // 1, остаток от деления 5 на 2 +alert( 8 % 3 ); // 2, остаток от деления 8 на 3 +alert( 8 % 4 ); // 0, остаток от деления 8 на 4 +``` + +### Возведение в степень ** + +Оператор возведения в степень `a ** b` возводит `a` в степень `b`. + +В школьной математике мы записываем это как ab. + +Например: + +```js run +alert( 2 ** 2 ); // 2² = 4 +alert( 2 ** 3 ); // 2³ = 8 +alert( 2 ** 4 ); // 2⁴ = 16 +``` + +Математически, оператор работает и для нецелых чисел. Например, квадратный корень является возведением в степень ½: + +```js run +alert( 4 ** (1/2) ); // 2 (степень 1/2 эквивалентна взятию квадратного корня) +``` + +## Сложение строк при помощи бинарного + + +Давайте рассмотрим специальные возможности операторов JavaScript, которые выходят за рамки школьной арифметики. + +Обычно при помощи плюса `'+'` складывают числа. + +Но если бинарный оператор `'+'` применить к строкам, то он их объединяет в одну: + +```js +let s = "моя" + "строка"; +alert(s); // моястрока +``` + +Обратите внимание, если хотя бы один операнд является строкой, то второй будет также преобразован в строку. + +Например: + +```js run +alert( '1' + 2 ); // "12" +alert( 2 + '1' ); // "21" +``` + +Как видите, не важно, первый или второй операнд является строкой. + +Вот пример посложнее: + +```js run +alert(2 + 2 + '1' ); // будет "41", а не "221" +``` + +Здесь операторы работают один за другим. Первый `+` складывает два числа и возвращает `4`, затем следующий `+` объединяет результат со строкой, производя действие `4 + '1' = '41'`. + +Сложение и преобразование строк — это особенность бинарного плюса `+`. Другие арифметические операторы работают только с числами и всегда преобразуют операнды в числа. + +Например, вычитание и деление: + +```js run +alert( 6 - '2' ); // 4, '2' приводится к числу +alert( '6' / '2' ); // 3, оба операнда приводятся к числам +``` + +## Приведение к числу, унарный + + +Плюс `+` существует в двух формах: бинарной, которую мы использовали выше, и унарной. + +Унарный, то есть применённый к одному значению, плюс `+` ничего не делает с числами. Но если операнд не число, унарный плюс преобразует его в число. + +Например: + +```js run +// Не влияет на числа +let x = 1; +alert( +x ); // 1 + +let y = -2; +alert( +y ); // -2 + +*!* +// Преобразует не числа в числа +alert( +true ); // 1 +alert( +"" ); // 0 +*/!* +``` + +На самом деле это то же самое, что и `Number(...)`, только короче. + +Необходимость преобразовывать строки в числа возникает очень часто. Например, обычно значения полей HTML-формы — это строки. А что, если их нужно, к примеру, сложить? + +Бинарный плюс сложит их как строки: + +```js run +let apples = "2"; +let oranges = "3"; + +alert( apples + oranges ); // "23", так как бинарный плюс объединяет строки +``` + +Поэтому используем унарный плюс, чтобы преобразовать к числу: + +```js run +let apples = "2"; +let oranges = "3"; + +*!* +// оба операнда предварительно преобразованы в числа +alert( +apples + +oranges ); // 5 +*/!* + +// более длинный вариант +// alert( Number(apples) + Number(oranges) ); // 5 +``` + +С точки зрения математики, такое изобилие плюсов выглядит странным. Но с точки зрения программиста тут нет ничего особенного: сначала выполнятся унарные плюсы, которые приведут строки к числам, а затем бинарный `'+'` их сложит. + +Почему унарные плюсы выполнились до бинарного сложения? Как мы сейчас увидим, дело в их приоритете. + +## Приоритет операторов + +В том случае, если в выражении есть несколько операторов – порядок их выполнения определяется *приоритетом*, или, другими словами, существует определённый порядок выполнения операторов. + +Из школы мы знаем, что умножение в выражении `1 + 2 * 2` выполнится раньше сложения. Это как раз и есть "приоритет". Говорят, что умножение имеет более высокий приоритет, чем сложение. + +Скобки важнее, чем приоритет, так что, если мы не удовлетворены порядком по умолчанию, мы можем использовать их, чтобы изменить приоритет. Например, написать `(1 + 2) * 2`. + +В JavaScript много операторов. Каждый оператор имеет соответствующий номер приоритета. Тот, у кого это число больше, – выполнится раньше. Если приоритет одинаковый, то порядок выполнения – слева направо. + +Отрывок из [таблицы приоритетов](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Operators/Operator_Precedence) (нет необходимости всё запоминать, обратите внимание, что приоритет унарных операторов выше, чем соответствующих бинарных): + +| Приоритет | Название | Обозначение | +|------------|------|------| +| ... | ... | ... | +| 15 | унарный плюс | `+` | +| 15 | унарный минус | `-` | +| 14 | возведение в степень | `**` | +| 13 | умножение | `*` | +| 13 | деление | `/` | +| 12 | сложение | `+` | +| 12 | вычитание | `-` | +| ... | ... | ... | +| 2 | присваивание | `=` | +| ... | ... | ... | + +Так как "унарный плюс" имеет приоритет `15`, который выше, чем `12` у "сложения" (бинарный плюс), то в выражении `"+apples + +oranges"` сначала выполнятся унарные плюсы, а затем сложение. + +## Присваивание + +Давайте отметим, что в таблице приоритетов также есть оператор присваивания `=`. У него один из самых низких приоритетов: `2`. + +Именно поэтому, когда переменной что-либо присваивают, например, `x = 2 * 2 + 1`, то сначала выполнится арифметика, а уже затем произойдёт присваивание `=` с сохранением результата в `x`. + +```js +let x = 2 * 2 + 1; + +alert( x ); // 5 +``` + +### Присваивание = возвращает значение + +Тот факт, что `=` является оператором, а не "магической" конструкцией языка, имеет интересные последствия. + +Большинство операторов в JavaScript возвращают значение. Для некоторых это очевидно, например сложение `+` или умножение `*`. Но и оператор присваивания не является исключением. + +Вызов `x = value` записывает `value` в `x` *и возвращает его*. + +Благодаря этому присваивание можно использовать как часть более сложного выражения: + +```js run +let a = 1; +let b = 2; + +*!* +let c = 3 - (a = b + 1); +*/!* + +alert( a ); // 3 +alert( c ); // 0 +``` + +В примере выше результатом `(a = b + 1)` будет значение, которое присваивается переменной `a` (то есть `3`). Потом оно используется для дальнейших вычислений. + +Забавное применение присваивания, не так ли? Нам нужно понимать, как это работает, потому что иногда это можно увидеть в JavaScript-библиотеках. + +Однако писать самим в таком стиле не рекомендуется. Такие трюки не сделают ваш код более понятным или читабельным. + +### Присваивание по цепочке + +Рассмотрим ещё одну интересную возможность: цепочку присваиваний. + +```js run +let a, b, c; + +*!* +a = b = c = 2 + 2; +*/!* + +alert( a ); // 4 +alert( b ); // 4 +alert( c ); // 4 +``` + +Такое присваивание работает справа налево. Сначала вычисляется самое правое выражение `2 + 2`, и затем результат присваивается переменным слева: `c`, `b` и `a`. В конце у всех переменных будет одно значение. + +Опять-таки, чтобы код читался легче, лучше разделять подобные конструкции на несколько строчек: + +```js +c = 2 + 2; +b = c; +a = c; +``` +Польза от такого стиля особенно ощущается при быстром просмотре кода. + +## Сокращённая арифметика с присваиванием + +Часто нужно применить оператор к переменной и сохранить результат в ней же. + +Например: + +```js +let n = 2; +n = n + 5; +n = n * 2; +``` + +Эту запись можно укоротить при помощи совмещённых операторов `+=` и `*=`: + +```js run +let n = 2; +n += 5; // теперь n = 7 (работает как n = n + 5) +n *= 2; // теперь n = 14 (работает как n = n * 2) + +alert( n ); // 14 +``` + +Подобные краткие формы записи существуют для **всех** арифметических и побитовых операторов: `/=`, `-=`, `**=` и так далее. + +Вызов с присваиванием имеет в точности такой же приоритет, как обычное присваивание, то есть выполнится после большинства других операций: + +```js run +let n = 2; + +n *= 3 + 5; + +alert( n ); // 16 (сначала выполнится правая часть, выражение идентично n *= 8) +``` + +## Инкремент/декремент + + + +Одной из наиболее частых числовых операций является увеличение или уменьшение на единицу. + +Для этого существуют даже специальные операторы: + +- **Инкремент** `++` увеличивает переменную на 1: + + ```js run no-beautify + let counter = 2; + counter++; // работает как counter = counter + 1, просто запись короче + alert( counter ); // 3 + ``` +- **Декремент** `--` уменьшает переменную на 1: + + ```js run no-beautify + let counter = 2; + counter--; // работает как counter = counter - 1, просто запись короче + alert( counter ); // 1 + ``` + +```warn +Инкремент/декремент можно применить только к переменной. Попытка использовать его на значении, типа 5++, приведёт к ошибке. +``` + +Операторы `++` и `--` могут быть расположены не только после, но и до переменной. + +- Когда оператор идёт после переменной — это "постфиксная форма": `counter++`. +- "Префиксная форма" — это когда оператор идёт перед переменной: `++counter`. + +Обе эти инструкции делают одно и то же: увеличивают `counter` на `1`. + +Есть ли разница между ними? Да, но увидеть её мы сможем, только если будем использовать значение, которое возвращают `++/--`. + +Давайте проясним этот момент. Как мы знаем, все операторы возвращают значение. Операторы инкремента/декремента не исключение. Префиксная форма возвращает новое значение, в то время как постфиксная форма возвращает старое (до увеличения/уменьшения числа). + +Чтобы увидеть разницу, вот небольшой пример: + +```js run +let counter = 1; +let a = ++counter; // (*) + +alert(a); // *!*2*/!* +``` + +В строке `(*)` *префиксная* форма `++counter` увеличивает `counter` и возвращает новое значение `2`. Так что `alert` покажет `2`. + +Теперь посмотрим на постфиксную форму: + +```js run +let counter = 1; +let a = counter++; // (*) меняем ++counter на counter++ + +alert(a); // *!*1*/!* +``` + +В строке `(*)` *постфиксная* форма `counter++` также увеличивает `counter`, но возвращает *старое* значение (которое было до увеличения). Так что `alert` покажет `1`. + +Подведём итоги: + +- Если результат оператора не используется, а нужно только увеличить/уменьшить переменную, тогда без разницы, какую форму использовать: + + ```js run + let counter = 0; + counter++; + ++counter; + alert( counter ); // 2, обе строки сделали одно и то же + ``` +- Если хочется тут же использовать результат, то нужна префиксная форма: + + ```js run + let counter = 0; + alert( ++counter ); // 1 + ``` +- Если нужно увеличить и при этом получить значение переменной *до увеличения* – нужна постфиксная форма: + + ```js run + let counter = 0; + alert( counter++ ); // 0 + ``` + +````smart header="Инкремент/декремент можно использовать в любых выражениях" +Операторы `++/--` могут также использоваться внутри выражений. Их приоритет выше, чем у большинства других арифметических операций. + +Например: + +```js run +let counter = 1; +alert( 2 * ++counter ); // 4 +``` + +Сравните с: + +```js run +let counter = 1; +alert( 2 * counter++ ); // 2, потому что counter++ возвращает "старое" значение +``` + +Хотя технически здесь всё в порядке, такая запись обычно делает код менее читабельным. Одна строка выполняет множество действий -- нехорошо. + +При беглом чтении кода можно с лёгкостью пропустить такой `counter++`, и будет неочевидно, что переменная увеличивается. + +Лучше использовать стиль "одна строка -- одно действие": + +```js run +let counter = 1; +alert( 2 * counter ); +counter++; +``` +```` + +## Побитовые операторы + +Побитовые операторы работают с 32-разрядными целыми числами (при необходимости приводят к ним), на уровне их внутреннего двоичного представления. + +Эти операторы не являются чем-то специфичным для JavaScript, они поддерживаются в большинстве языков программирования. + +Поддерживаются следующие побитовые операторы: + +- AND(и) ( `&` ) +- OR(или) ( `|` ) +- XOR(побитовое исключающее или) ( `^` ) +- NOT(не) ( `~` ) +- LEFT SHIFT(левый сдвиг) ( `<<` ) +- RIGHT SHIFT(правый сдвиг) ( `>>` ) +- ZERO-FILL RIGHT SHIFT(правый сдвиг с заполнением нулями) ( `>>>` ) + +Они используются редко, когда возникает необходимость оперировать с числами на очень низком (побитовом) уровне. В ближайшем времени они нам не понадобятся, так как веб-разработчики редко к ним прибегают, хотя в некоторых сферах (например, в криптографии) они полезны. + +Вы можете прочитать о них в главе , когда возникнет реальная необходимость. + +## Оператор "запятая" + +Оператор "запятая" (`,`) редко применяется и является одним из самых необычных. Иногда он используется для написания более короткого кода, поэтому нам нужно знать его, чтобы понимать, что при этом происходит. + +Оператор "запятая" предоставляет нам возможность вычислять несколько выражений, разделяя их запятой `,`. Каждое выражение выполняется, но возвращается результат только последнего. + +Например: + +```js run +*!* +let a = (1 + 2, 3 + 4); +*/!* + +alert( a ); // 7 (результат вычисления 3 + 4) +``` + +Первое выражение `1 + 2` выполняется, а результат отбрасывается. Затем идёт `3 + 4`, выражение выполняется и возвращается результат. + +````smart header="Запятая имеет очень низкий приоритет" +Пожалуйста, обратите внимание, что оператор `,` имеет очень низкий приоритет, ниже `=`, поэтому скобки важны в приведённом выше примере. + +Попробуйте запустить следующий код (**строгий режим `"use strict"` в примере ниже не используется, иначе мы бы получили ошибку**): + +```js run no-strict +a = 1 + 2, 3 + 4; + +alert(a); // 3 +``` + +Необычный результат, правда? Особенно учитывая то, что оператор `,` должен «выполнять каждое выражение, но возвращать результат только последнего». + +Без скобок в `a = 1 + 2, 3 + 4` сначала выполнится `+`, суммируя числа в `a = 3, 7`, затем оператор присваивания `=` присвоит `a = 3`, а то, что идёт дальше, будет проигнорировано. Всё так же, как в `(a = 1 + 2), 3 + 4`. +```` + +Зачем нам оператор, который отбрасывает всё, кроме последнего выражения? + +Иногда его используют в составе более сложных конструкций, чтобы сделать несколько действий в одной строке. + +Например: + +```js +// три операции в одной строке +for (*!*a = 1, b = 3, c = a * b*/!*; a < 10; a++) { + ... +} +``` + +Такие трюки используются во многих JavaScript-фреймворках. Вот почему мы упоминаем их. Но обычно они не улучшают читабельность кода, поэтому стоит хорошо подумать, прежде чем их использовать. diff --git a/1-js/02-first-steps/09-alert-prompt-confirm/article.md b/1-js/02-first-steps/09-alert-prompt-confirm/article.md deleted file mode 100644 index 68b5d58cae..0000000000 --- a/1-js/02-first-steps/09-alert-prompt-confirm/article.md +++ /dev/null @@ -1,110 +0,0 @@ -# Взаимодействие: alert, prompt, confirm - -В этой части учебника мы разбираем "собственно JavaScript", без привязки к браузеру или другой среде выполнения. - -Но, так как мы будем использовать браузер как демо-среду, нам нужно познакомиться по крайней мере с несколькими функциями его пользовательского интерфейса, а именно: `alert`, `prompt` и `confirm`. - - -## alert - -Синтаксис: - -```js -alert(message); -``` - -Этот код отобразит окно в браузере и приостановит дальнейшее выполнение скриптов, до тех пор пока пользователь не нажмет кнопку "OK". - -Например: - -```js run -alert("Hello"); -``` - -Это небольшое окно с сообщением называется *модальным окном*. Понятие *модальный* означает, что пользователь не может взаимодействовать с интерфейсом остальной части страницы, нажимать на другие кнопки и т.д. до тех пор, пока взаимодействует с окном. В данном случае -- пока не будет нажата кнопка "OK". - -## prompt - -Функция `prompt` принимает два аргумента: - -```js no-beautify -result = prompt(title, [default]); -``` - -Этот код отобразит модальное окно с текстом, полем для ввода текста и кнопками OK/CANCEL. - -`title` -: Текст для отображения в окне. - -`default` -: Необязательный второй параметр, который устанавливает начальное значение в поле для текста в окне. - -Пользователь может напечатать что-либо в поле ввода и нажать OK. Или отменить ввод нажатием на кнопку CANCEL или нажав клавишу `key:Esc`. - -Вызов `prompt` вернет текст, указанный в поле для ввода, или `null` если ввод отменен пользователем. - -Например: - -```js run -let age = prompt('Сколько тебе лет?', 100); - -alert(`Тебе ${age} лет!`); // Тебе 100 лет! -``` - -````warn header="Для IE: всегда устанавливайте значение по умолчанию" -Второй параметр не обязательный, но если не указать его, то Internet Explorer установить значение `"undefined"` в поле для ввода. - -Запустите код в Internet Explorer и посмотрите на результат: - -```js run -let test = prompt("Test"); -``` - -Чтобы `prompt` хорошо выглядел в IE, рекомендуется всегда указывать второй параметр: - -```js run -let test = prompt("Test", ''); // <-- для IE -``` -```` - -## confirm - -Синтаксис: - -```js -result = confirm(question); -``` - -Функция `confirm` отображает модальное окно с текстом вопроса `question` и двумя кнопками: OK и CANCEL. - -Результат `true`, если нажата кнопка OK. В других случаях `false`. - -Например: - -```js run -let isBoss = confirm("Ты тут главный?"); - -alert( isBoss ); // true если нажата OK -``` - -## Итого - -Рассмотрели 3 функции браузера для взаимодействия с пользователем: - -`alert` -: показывает сообщение. - -`prompt` -: показывает сообщение и запрашивает ввод текста от пользователя. Возвращает напечатанный текст в поле ввода или `null`, если были нажаты кнопки CANCEL или `key:Esc` с клавиатуры. - -`confirm` -: показывает сообщение и ждет, пока пользователь нажмет OK или CANCEL. Возвращает `true`, если нажата OK и `false`, если нажаты кнопки CANCEL или `key:Esc` с клавиатуры. - -Все эти методы являются модальными: останавливают выполнение скриптов и не позволяют пользователю взаимодействовать с остальной частью страницы до тех пор, пока окно не будет закрыто. - -На все указанные методы распространяется два ограничения: - -1. Расположение окон определяется браузером. Обычно окна находятся в центре. -2. Визуальное отображение окон зависит от браузера и мы не можем изменит их вид. - -Такова цена простоты. Есть другие способы показать более приятные глазу окна с богатым функционалом для взаимодействия с пользователем, но если "навороты" не имеют значения, то данные методы работают отлично. diff --git a/1-js/02-first-steps/09-comparison/1-comparison-questions/solution.md b/1-js/02-first-steps/09-comparison/1-comparison-questions/solution.md new file mode 100644 index 0000000000..16e343a8aa --- /dev/null +++ b/1-js/02-first-steps/09-comparison/1-comparison-questions/solution.md @@ -0,0 +1,21 @@ + + +```js no-beautify +5 > 4 → true +"ананас" > "яблоко" → false +"2" > "12" → true +undefined == null → true +undefined === null → false +null == "\n0\n" → false +null === +"\n0\n" → false +``` + +Разъяснения: + +1. Очевидно, `true`. +2. Используется посимвольное сравнение, поэтому `false`. `"а"` меньше, чем `"я"`. +3. Снова посимвольное сравнение. Первый символ первой строки `"2"` больше, чем первый символ второй `"1"`. +4. Специальный случай. Значения `null` и `undefined` равны только друг другу при нестрогом сравнении. +5. Строгое сравнение разных типов, поэтому `false`. +6. Аналогично `(4)`, `null` равен только `undefined`. +7. Строгое сравнение разных типов. diff --git a/1-js/02-first-steps/08-comparison/1-comparison-questions/task.md b/1-js/02-first-steps/09-comparison/1-comparison-questions/task.md similarity index 100% rename from 1-js/02-first-steps/08-comparison/1-comparison-questions/task.md rename to 1-js/02-first-steps/09-comparison/1-comparison-questions/task.md diff --git a/1-js/02-first-steps/09-comparison/article.md b/1-js/02-first-steps/09-comparison/article.md new file mode 100644 index 0000000000..ced30d93d8 --- /dev/null +++ b/1-js/02-first-steps/09-comparison/article.md @@ -0,0 +1,214 @@ +# Операторы сравнения + +Многие операторы сравнения известны нам из математики. + +В JavaScript они записываются так: + +- Больше/меньше: a > b, a < b. +- Больше/меньше или равно: a >= b, a <= b. +- Равно: `a == b`. Обратите внимание, для сравнения используется двойной знак равенства `==`. Один знак равенства `a = b` означал бы присваивание. +- Не равно. В математике обозначается символом ≠, но в JavaScript записывается как a != b. + +В этом разделе мы больше узнаем про то, какие бывают сравнения, как язык с ними работает и к каким неожиданностям мы должны быть готовы. + +В конце вы найдёте хороший рецепт того, как избегать "причуд" сравнения в JavaScript. + +## Результат сравнения имеет логический тип + +Все операторы сравнения возвращают значение логического типа: + +- `true` -- означает "да", "верно", "истина". +- `false` -- означает "нет", "неверно", "ложь". + +Например: + +```js run +alert( 2 > 1 ); // true (верно) +alert( 2 == 1 ); // false (неверно) +alert( 2 != 1 ); // true (верно) +``` + +Результат сравнения можно присвоить переменной, как и любое значение: + +```js run +let result = 5 > 4; // результат сравнения присваивается переменной result +alert( result ); // true +``` + +## Сравнение строк + +Чтобы определить, что одна строка больше другой, JavaScript использует "алфавитный" или "лексикографический" порядок. + +Другими словами, строки сравниваются посимвольно. + +Например: + +```js run +alert( 'Я' > 'А' ); // true +alert( 'Коты' > 'Кода' ); // true +alert( 'Сонный' > 'Сон' ); // true +``` + +Алгоритм сравнения двух строк довольно прост: + +1. Сначала сравниваются первые символы строк. +2. Если первый символ первой строки больше (меньше), чем первый символ второй, то первая строка больше (меньше) второй. Сравнение завершено. +3. Если первые символы равны, то таким же образом сравниваются уже вторые символы строк. +4. Сравнение продолжается, пока не закончится одна из строк. +5. Если обе строки заканчиваются одновременно, то они равны. Иначе, большей считается более длинная строка. + +В примерах выше сравнение `'Я' > 'А'` завершится на первом шаге, тогда как строки `'Коты'` и `'Кода'` будут сравниваться посимвольно: + +1. `К` равна `К`. +2. `о` равна `о`. +3. `т` больше, чем `д`. На этом сравнение заканчивается. Первая строка больше. + +```smart header="Используется кодировка Unicode, а не настоящий алфавит" +Приведённый выше алгоритм сравнения похож на алгоритм, используемый в словарях и телефонных книгах, но между ними есть и различия. + +Например, в JavaScript имеет значение регистр символов. Заглавная буква `"A"` не равна строчной `"a"`. Какая же из них больше? Строчная `"a"`. Почему? Потому что строчные буквы имеют больший код во внутренней таблице кодирования, которую использует JavaScript (Unicode). Мы ещё поговорим о внутреннем представлении строк и его влиянии в главе . +``` + +## Сравнение разных типов + +При сравнении значений разных типов JavaScript приводит каждое из них к числу. + +Например: + +```js run +alert( '2' > 1 ); // true, строка '2' становится числом 2 +alert( '01' == 1 ); // true, строка '01' становится числом 1 +``` + +Логическое значение `true` становится `1`, а `false` – `0`. + +Например: + +```js run +alert( true == 1 ); // true +alert( false == 0 ); // true +``` + +````smart header="Забавное следствие" +Возможна следующая ситуация: + +- Два значения равны. +- Одно из них `true` как логическое значение, другое – `false`. + +Например: + +```js run +let a = 0; +alert( Boolean(a) ); // false + +let b = "0"; +alert( Boolean(b) ); // true + +alert(a == b); // true! +``` + +С точки зрения JavaScript, результат ожидаем. Равенство преобразует значения, используя числовое преобразование, поэтому `"0"` становится `0`. В то время как явное преобразование с помощью `Boolean` использует другой набор правил. +```` + +## Строгое сравнение + +Использование обычного сравнения `==` может вызывать проблемы. Например, оно не отличает `0` от `false`: + +```js run +alert( 0 == false ); // true +``` + +Та же проблема с пустой строкой: + +```js run +alert( '' == false ); // true +``` + +Это происходит из-за того, что операнды разных типов преобразуются оператором `==` к числу. В итоге, и пустая строка, и `false` становятся нулём. + +Как же тогда отличать `0` от `false`? + +**Оператор строгого равенства `===` проверяет равенство без приведения типов.** + +Другими словами, если `a` и `b` имеют разные типы, то проверка `a === b` немедленно возвращает `false` без попытки их преобразования. + +Давайте проверим: + +```js run +alert( 0 === false ); // false, так как сравниваются разные типы +``` + +Ещё есть оператор строгого неравенства `!==`, аналогичный `!=`. + +Оператор строгого равенства дольше писать, но он делает код более очевидным и оставляет меньше места для ошибок. + +## Сравнение с null и undefined + +Поведение `null` и `undefined` при сравнении с другими значениями — особое: + +При строгом равенстве `===` +: Эти значения различны, так как различны их типы. + + ```js run + alert( null === undefined ); // false + ``` + +При нестрогом равенстве `==` +: Эти значения равны друг другу и не равны никаким другим значениям. Это специальное правило языка. + + ```js run + alert( null == undefined ); // true + ``` + +При использовании математических операторов и других операторов сравнения `< > <= >=` +: Значения `null/undefined` преобразуются к числам: `null` становится `0`, а `undefined` – `NaN`. + +Посмотрим, какие забавные вещи случаются, когда мы применяем эти правила. И, что более важно, как избежать ошибок при их использовании. + +### Странный результат сравнения null и 0 + +Сравним `null` с нулём: + +```js run +alert( null > 0 ); // (1) false +alert( null == 0 ); // (2) false +alert( null >= 0 ); // (3) *!*true*/!* +``` + +С точки зрения математики это странно. Результат последнего сравнения говорит о том, что "`null` больше или равно нулю", тогда результат одного из сравнений выше должен быть `true`, но они оба ложны. + +Причина в том, что нестрогое равенство и сравнения `> < >= <=` работают по-разному. Сравнения преобразуют `null` в число, рассматривая его как `0`. Поэтому выражение (3) `null >= 0` истинно, а `null > 0` ложно. + +С другой стороны, для нестрогого равенства `==` значений `undefined` и `null` действует особое правило: эти значения ни к чему не приводятся, они равны друг другу и не равны ничему другому. Поэтому (2) `null == 0` ложно. + +### Несравненное значение undefined + +Значение `undefined` несравнимо с другими значениями: + +```js run +alert( undefined > 0 ); // false (1) +alert( undefined < 0 ); // false (2) +alert( undefined == 0 ); // false (3) +``` + +Почему же сравнение `undefined` с нулём всегда ложно? + +На это есть следующие причины: + +- Сравнения `(1)` и `(2)` возвращают `false`, потому что `undefined` преобразуется в `NaN`, а `NaN` – это специальное числовое значение, которое возвращает `false` при любых сравнениях. +- Нестрогое равенство `(3)` возвращает `false`, потому что `undefined` равно только `null`, `undefined` и ничему больше. + +### Как избежать проблем + +Зачем мы рассмотрели все эти примеры? Должны ли мы постоянно помнить обо всех этих особенностях? Необязательно. Со временем все они станут вам знакомы, но можно избежать проблем, если следовать надёжным правилам: + +- Относитесь очень осторожно к любому сравнению с `undefined/null`, кроме случаев строгого равенства `===`. +- Не используйте сравнения `>= > < <=` с переменными, которые могут принимать значения `null/undefined`, разве что вы полностью уверены в том, что делаете. Если переменная может принимать эти значения, то добавьте для них отдельные проверки. + +## Итого + +- Операторы сравнения возвращают значения логического типа. +- Строки сравниваются посимвольно в лексикографическом порядке. +- Значения разных типов при сравнении приводятся к числу. Исключением является сравнение с помощью операторов строгого равенства/неравенства. +- Значения `null` и `undefined` равны `==` друг другу и не равны любому другому значению. +- Будьте осторожны при использовании операторов сравнений вроде `>` и `<` с переменными, которые могут принимать значения `null/undefined`. Хорошей идеей будет сделать отдельную проверку на `null/undefined`. diff --git a/1-js/02-first-steps/10-ifelse/1-if-zero-string/solution.md b/1-js/02-first-steps/10-ifelse/1-if-zero-string/solution.md index 51f1d46809..14601d3cc9 100644 --- a/1-js/02-first-steps/10-ifelse/1-if-zero-string/solution.md +++ b/1-js/02-first-steps/10-ifelse/1-if-zero-string/solution.md @@ -1,12 +1,11 @@ -**Yes, it will.** +**Да, выведется.** -Any string except an empty one (and `"0"` is not empty) becomes `true` in the logical context. +Любая строка, кроме пустой (а строка `"0"` -- не пустая), в логическом контексте становится `true`. -We can run and check: +Можно запустить и проверить: ```js run if ("0") { - alert( 'Hello' ); + alert( 'Привет' ); } ``` - diff --git a/1-js/02-first-steps/10-ifelse/1-if-zero-string/task.md b/1-js/02-first-steps/10-ifelse/1-if-zero-string/task.md index 5f16cda857..68b28e171c 100644 --- a/1-js/02-first-steps/10-ifelse/1-if-zero-string/task.md +++ b/1-js/02-first-steps/10-ifelse/1-if-zero-string/task.md @@ -2,13 +2,12 @@ importance: 5 --- -# if (a string with zero) +# if (строка с нулём) -Will `alert` be shown? +Выведется ли `alert`? ```js if ("0") { - alert( 'Hello' ); + alert( 'Привет' ); } ``` - diff --git a/1-js/02-first-steps/10-ifelse/2-check-standard/ifelse_task2.png b/1-js/02-first-steps/10-ifelse/2-check-standard/ifelse_task2.png deleted file mode 100644 index 04fb1fb0b7..0000000000 Binary files a/1-js/02-first-steps/10-ifelse/2-check-standard/ifelse_task2.png and /dev/null differ diff --git a/1-js/02-first-steps/10-ifelse/2-check-standard/ifelse_task2.svg b/1-js/02-first-steps/10-ifelse/2-check-standard/ifelse_task2.svg new file mode 100644 index 0000000000..2ef4442c7b --- /dev/null +++ b/1-js/02-first-steps/10-ifelse/2-check-standard/ifelse_task2.svg @@ -0,0 +1 @@ +НачалоНе знаете? “ECMAScript”!Верно!Какое "официальное" название JavaScript?ДругоеECMAScript \ No newline at end of file diff --git a/1-js/02-first-steps/10-ifelse/2-check-standard/ifelse_task2/index.html b/1-js/02-first-steps/10-ifelse/2-check-standard/ifelse_task2/index.html index ea99666539..a9c05f9d7b 100644 --- a/1-js/02-first-steps/10-ifelse/2-check-standard/ifelse_task2/index.html +++ b/1-js/02-first-steps/10-ifelse/2-check-standard/ifelse_task2/index.html @@ -5,12 +5,12 @@ diff --git a/1-js/02-first-steps/10-ifelse/2-check-standard/ifelse_task2@2x.png b/1-js/02-first-steps/10-ifelse/2-check-standard/ifelse_task2@2x.png deleted file mode 100644 index 08226b7903..0000000000 Binary files a/1-js/02-first-steps/10-ifelse/2-check-standard/ifelse_task2@2x.png and /dev/null differ diff --git a/1-js/02-first-steps/10-ifelse/2-check-standard/task.md b/1-js/02-first-steps/10-ifelse/2-check-standard/task.md index 46fe05dc9b..45386667a1 100644 --- a/1-js/02-first-steps/10-ifelse/2-check-standard/task.md +++ b/1-js/02-first-steps/10-ifelse/2-check-standard/task.md @@ -2,13 +2,12 @@ importance: 2 --- -# The name of JavaScript +# Название JavaScript -Using the `if..else` construct, write the code which asks: 'What is the "official" name of JavaScript?' +Используя конструкцию `if..else`, напишите код, который будет спрашивать: 'Какое "официальное" название JavaScript?' -If the visitor enters "ECMAScript", then output "Right!", otherwise -- output: "Didn't know? ECMAScript!" +Если пользователь вводит "ECMAScript", то показать: "Верно!", в противном случае -- отобразить: "Не знаете? ECMAScript!" -![](ifelse_task2.png) +![](ifelse_task2.svg) [demo src="ifelse_task2"] - diff --git a/1-js/02-first-steps/10-ifelse/3-sign/if_sign/index.html b/1-js/02-first-steps/10-ifelse/3-sign/if_sign/index.html index f168360dd7..95114cd4c4 100644 --- a/1-js/02-first-steps/10-ifelse/3-sign/if_sign/index.html +++ b/1-js/02-first-steps/10-ifelse/3-sign/if_sign/index.html @@ -6,7 +6,7 @@ - + - + - +
- + diff --git a/1-js/03-code-quality/05-testing-mocha/beforeafter.view/test.js b/1-js/03-code-quality/05-testing-mocha/beforeafter.view/test.js index cad51d3eef..2116ec3b01 100644 --- a/1-js/03-code-quality/05-testing-mocha/beforeafter.view/test.js +++ b/1-js/03-code-quality/05-testing-mocha/beforeafter.view/test.js @@ -1,12 +1,12 @@ -describe("test", function() { +describe("тест", function () { - before(() => alert("Testing started – before all tests")); - after(() => alert("Testing finished – after all tests")); + before(() => alert("Тестирование началось – перед тестами")); + after(() => alert("Тестирование закончилось – после всех тестов")); - beforeEach(() => alert("Before a test – enter a test")); - afterEach(() => alert("After a test – exit a test")); + beforeEach(() => alert("Перед тестом – начинаем выполнять тест")); + afterEach(() => alert("После теста – заканчиваем выполнение теста")); - it('test 1', () => alert(1)); - it('test 2', () => alert(2)); + it('тест 1', () => alert(1)); + it('тест 2', () => alert(2)); }); diff --git a/1-js/03-code-quality/05-testing-mocha/index.html b/1-js/03-code-quality/05-testing-mocha/index.html index 28a2ea62b4..19b460a379 100644 --- a/1-js/03-code-quality/05-testing-mocha/index.html +++ b/1-js/03-code-quality/05-testing-mocha/index.html @@ -1,17 +1,18 @@ - + - + - + @@ -20,17 +21,17 @@ - + - +
- + diff --git a/1-js/03-code-quality/05-testing-mocha/pow-1.view/index.html b/1-js/03-code-quality/05-testing-mocha/pow-1.view/index.html index e48a8d3a25..8044499f42 100644 --- a/1-js/03-code-quality/05-testing-mocha/pow-1.view/index.html +++ b/1-js/03-code-quality/05-testing-mocha/pow-1.view/index.html @@ -1,18 +1,18 @@ - + - + - + @@ -21,17 +21,17 @@ - + - +
- + diff --git a/1-js/03-code-quality/05-testing-mocha/pow-1.view/test.js b/1-js/03-code-quality/05-testing-mocha/pow-1.view/test.js index 89ba412ed5..046f460425 100644 --- a/1-js/03-code-quality/05-testing-mocha/pow-1.view/test.js +++ b/1-js/03-code-quality/05-testing-mocha/pow-1.view/test.js @@ -1,6 +1,6 @@ describe("pow", function() { - it("raises to n-th power", function() { + it("возводит число в степень n", function() { assert.equal(pow(2, 3), 8); }); diff --git a/1-js/03-code-quality/05-testing-mocha/pow-2.view/index.html b/1-js/03-code-quality/05-testing-mocha/pow-2.view/index.html index e8d6be23d5..7387b24c85 100644 --- a/1-js/03-code-quality/05-testing-mocha/pow-2.view/index.html +++ b/1-js/03-code-quality/05-testing-mocha/pow-2.view/index.html @@ -1,18 +1,18 @@ - + - + - + @@ -21,17 +21,17 @@ - + - +
- + diff --git a/1-js/03-code-quality/05-testing-mocha/pow-2.view/test.js b/1-js/03-code-quality/05-testing-mocha/pow-2.view/test.js index 9a2f8fde7e..6df4aabed4 100644 --- a/1-js/03-code-quality/05-testing-mocha/pow-2.view/test.js +++ b/1-js/03-code-quality/05-testing-mocha/pow-2.view/test.js @@ -1,10 +1,10 @@ describe("pow", function() { - it("2 raised to power 3 is 8", function() { + it("2 в степени 3 будет 8", function () { assert.equal(pow(2, 3), 8); }); - it("3 raised to power 3 is 27", function() { + it("3 в степени 3 будет 27", function () { assert.equal(pow(3, 3), 27); }); diff --git a/1-js/03-code-quality/05-testing-mocha/pow-3.view/index.html b/1-js/03-code-quality/05-testing-mocha/pow-3.view/index.html index c71b0d5d50..ba50d0caf4 100644 --- a/1-js/03-code-quality/05-testing-mocha/pow-3.view/index.html +++ b/1-js/03-code-quality/05-testing-mocha/pow-3.view/index.html @@ -1,18 +1,18 @@ - + - + - + @@ -31,13 +31,13 @@ } - + - +
- + diff --git a/1-js/03-code-quality/05-testing-mocha/pow-3.view/test.js b/1-js/03-code-quality/05-testing-mocha/pow-3.view/test.js index 8663952aac..7a1c8d7b7d 100644 --- a/1-js/03-code-quality/05-testing-mocha/pow-3.view/test.js +++ b/1-js/03-code-quality/05-testing-mocha/pow-3.view/test.js @@ -2,7 +2,7 @@ describe("pow", function() { function makeTest(x) { let expected = x * x * x; - it(`${x} in the power 3 is ${expected}`, function() { + it(`${x} в степени 3 будет ${expected}`, function () { assert.equal(pow(x, 3), expected); }); } @@ -11,4 +11,4 @@ describe("pow", function() { makeTest(x); } -}); +}); \ No newline at end of file diff --git a/1-js/03-code-quality/05-testing-mocha/pow-4.view/index.html b/1-js/03-code-quality/05-testing-mocha/pow-4.view/index.html index c71b0d5d50..ba50d0caf4 100644 --- a/1-js/03-code-quality/05-testing-mocha/pow-4.view/index.html +++ b/1-js/03-code-quality/05-testing-mocha/pow-4.view/index.html @@ -1,18 +1,18 @@ - + - + - + @@ -31,13 +31,13 @@ } - + - +
- + diff --git a/1-js/03-code-quality/05-testing-mocha/pow-4.view/test.js b/1-js/03-code-quality/05-testing-mocha/pow-4.view/test.js index e5ce2ce433..258838ad4d 100644 --- a/1-js/03-code-quality/05-testing-mocha/pow-4.view/test.js +++ b/1-js/03-code-quality/05-testing-mocha/pow-4.view/test.js @@ -1,19 +1,15 @@ describe("pow", function() { - describe("raises x to power 3", function() { - + describe("возводит x в степень 3", function() { function makeTest(x) { let expected = x * x * x; - it(`${x} in the power 3 is ${expected}`, function() { + it(`${x} в степени 3 будет ${expected}`, function () { assert.equal(pow(x, 3), expected); }); } - for (let x = 1; x <= 5; x++) { makeTest(x); } - }); - - // ... more tests to follow here, both describe and it can be added + // ... другие тесты. Можно писать и describe, и it блоки. }); diff --git a/1-js/03-code-quality/05-testing-mocha/pow-full.view/index.html b/1-js/03-code-quality/05-testing-mocha/pow-full.view/index.html index 076b1e5a90..94ac43dd7d 100644 --- a/1-js/03-code-quality/05-testing-mocha/pow-full.view/index.html +++ b/1-js/03-code-quality/05-testing-mocha/pow-full.view/index.html @@ -1,18 +1,18 @@ - + - + - + @@ -25,20 +25,22 @@ if (Math.round(n) != n) return NaN; let result = 1; + for (let i = 0; i < n; i++) { result *= x; } + return result; } - + - +
- + diff --git a/1-js/03-code-quality/05-testing-mocha/pow-full.view/test.js b/1-js/03-code-quality/05-testing-mocha/pow-full.view/test.js index 75ff5e99fd..acd5a72b6a 100644 --- a/1-js/03-code-quality/05-testing-mocha/pow-full.view/test.js +++ b/1-js/03-code-quality/05-testing-mocha/pow-full.view/test.js @@ -1,10 +1,10 @@ describe("pow", function() { - describe("raises x to power 3", function() { + describe("возводит x в степень 3", function() { function makeTest(x) { let expected = x * x * x; - it(`${x} in the power 3 is ${expected}`, function() { + it(`${x} в степени 3 будет ${expected}`, function() { assert.equal(pow(x, 3), expected); }); } @@ -15,11 +15,11 @@ describe("pow", function() { }); - it("if n is negative, the result is NaN", function() { + it("если n - отрицательное число, результат будет NaN", function() { assert.isNaN(pow(2, -1)); }); - it("if n is not integer, the result is NaN", function() { + it("если n - дробное число, результат будет NaN", function() { assert.isNaN(pow(2, 1.5)); }); diff --git a/1-js/03-code-quality/05-testing-mocha/pow-min.view/index.html b/1-js/03-code-quality/05-testing-mocha/pow-min.view/index.html index d82a79dca2..3d1986e25a 100644 --- a/1-js/03-code-quality/05-testing-mocha/pow-min.view/index.html +++ b/1-js/03-code-quality/05-testing-mocha/pow-min.view/index.html @@ -1,18 +1,18 @@ - + - + - + @@ -21,17 +21,17 @@ - + - +
- + diff --git a/1-js/03-code-quality/05-testing-mocha/pow-min.view/test.js b/1-js/03-code-quality/05-testing-mocha/pow-min.view/test.js index 89ba412ed5..6ba0a50c67 100644 --- a/1-js/03-code-quality/05-testing-mocha/pow-min.view/test.js +++ b/1-js/03-code-quality/05-testing-mocha/pow-min.view/test.js @@ -1,6 +1,6 @@ describe("pow", function() { - it("raises to n-th power", function() { + it("возводит в степень n", function() { assert.equal(pow(2, 3), 8); }); diff --git a/1-js/03-code-quality/05-testing-mocha/pow-nan.view/index.html b/1-js/03-code-quality/05-testing-mocha/pow-nan.view/index.html index 523ae25ec0..ba50d0caf4 100644 --- a/1-js/03-code-quality/05-testing-mocha/pow-nan.view/index.html +++ b/1-js/03-code-quality/05-testing-mocha/pow-nan.view/index.html @@ -1,18 +1,18 @@ - + - + - + @@ -22,20 +22,22 @@ - + - +
- + diff --git a/1-js/03-code-quality/05-testing-mocha/pow-nan.view/test.js b/1-js/03-code-quality/05-testing-mocha/pow-nan.view/test.js index 75ff5e99fd..fd334d3f75 100644 --- a/1-js/03-code-quality/05-testing-mocha/pow-nan.view/test.js +++ b/1-js/03-code-quality/05-testing-mocha/pow-nan.view/test.js @@ -1,10 +1,10 @@ describe("pow", function() { - describe("raises x to power 3", function() { + describe("возводит x в степень 3", function() { function makeTest(x) { let expected = x * x * x; - it(`${x} in the power 3 is ${expected}`, function() { + it(`${x} в степени 3 будет ${expected}`, function() { assert.equal(pow(x, 3), expected); }); } @@ -15,11 +15,11 @@ describe("pow", function() { }); - it("if n is negative, the result is NaN", function() { + it("для отрицательных n возвращает NaN", function() { assert.isNaN(pow(2, -1)); }); - it("if n is not integer, the result is NaN", function() { + it("для дробных n возвращает NaN", function() { assert.isNaN(pow(2, 1.5)); }); diff --git a/1-js/03-code-quality/06-polyfills/article.md b/1-js/03-code-quality/06-polyfills/article.md index 8a25b5be92..870054631c 100644 --- a/1-js/03-code-quality/06-polyfills/article.md +++ b/1-js/03-code-quality/06-polyfills/article.md @@ -1,37 +1,36 @@ -# Полифилл +# Полифилы -JavaScript - динамично развивающийся язык программирования. Регулярно появляются новые предложения, они анализируются и, если предложения одобряются, их переносят в черновик , а затем публикуют в [спецификации](http://www.ecma-international.org/publications/standards/Ecma-262.htm). +JavaScript - динамично развивающийся язык программирования. Регулярно появляются предложения о добавлении в JS новых возможностей, они анализируются, и, если предложения одобряются, то описания новых возможностей языка переносятся в черновик , а затем публикуются в [спецификации](https://www.ecma-international.org/publications/standards/Ecma-262.htm). -Разработчики JavaScript движков сами принимают решение какие предложения реализовать в первую очередь. Они могут заранее реализовать функции, которые находятся в черновике и отложить разработку функций, которые уже перенесены в спецификацию, потому что они менее интересны разработчикам, а может их сложнее реализовать. +Разработчики JavaScript-движков сами решают, какие предложения реализовывать в первую очередь. Они могут заранее добавить в браузеры поддержку функций, которые всё ещё находятся в черновике, и отложить разработку функций, которые уже перенесены в спецификацию, потому что они менее интересны разработчикам или более сложные в реализации. Таким образом, довольно часто реализуется только часть стандарта. -Можно проверить текущее состояние поддержки различных возможностей JavaScript на странице (нам предстоит многое ещё изучить). +Можно проверить текущее состояние поддержки различных возможностей JavaScript на странице (нам ещё предстоит изучить многое из этого списка). ## Babel -Когда мы используем современные возможности JavaScript, некоторые движки могут не поддерживать их. Как и было сказано выше, не везде реализованы все функции. +Когда мы используем современные возможности JavaScript, некоторые движки могут не поддерживать их. Как было сказано выше, не везде реализованы все функции. И тут приходит на помощь Babel. -[Babel](https://babeljs.io) - это [транспилер](https://ru.wikipedia.org/wiki/%D0%A2%D1%80%D0%B0%D0%BD%D1%81%D0%BF%D0%B0%D0%B9%D0%BB%D0%B5%D1%80). Он переписывает современные JavaScript-функции в предыдущий стандарт. +[Babel](https://babeljs.io) - это [транспилер](https://ru.wikipedia.org/wiki/%D0%A2%D1%80%D0%B0%D0%BD%D1%81%D0%BF%D0%B0%D0%B9%D0%BB%D0%B5%D1%80). Он переписывает современный JavaScript-код в предыдущий стандарт. На самом деле, есть две части Babel: -1. Во-первых, транспилер, который переписывает код. Разработчик запускает Babel на своём компьютере. Он переписывает код в старый стандарт. И после, код отправляется на сайт. Современные сборщики проектов, такие как [webpack](http://webpack.github.io/) или [brunch](http://brunch.io/), предоставляют возможность запускать транспилер автоматически, после каждого изменения кода, что позволяет экономить время. +1. Во-первых, транспилер, который переписывает код. Разработчик запускает Babel на своём компьютере. Он переписывает код в старый стандарт. И после этого код отправляется на сайт. Современные сборщики проектов, такие как [webpack](https://webpack.github.io/) или [brunch](https://brunch.io/), предоставляют возможность запускать транспилер автоматически после каждого изменения кода, что позволяет экономить время. -2. Во-вторых, полифилл. +2. Во-вторых, полифил. - Новые возможности языка могут включать встроенные функции и синтаксические конструкции. Транспилер переписывает код, преобразовывая синтаксические конструкции в старые. Но что касается новых встроенных функций, нам нужно их реализовать. JavaScript является высокодинамичным языком, скрипты могут добавлять/изменять любые функции, чтобы они вели себя в соответствии с современным стандартом. + Новые возможности языка могут включать встроенные функции и синтаксические конструкции. Транспилер переписывает код, преобразовывая новые синтаксические конструкции в старые. Но что касается новых встроенных функций, нам нужно их как-то реализовать. JavaScript является высокодинамичным языком, скрипты могут добавлять/изменять любые функции, чтобы они вели себя в соответствии с современным стандартом. - Термин "полифилл" означает, что скрипт "заполняет" пробелы и добавляет современные функции. + Термин "полифил" означает, что скрипт "заполняет" пробелы и добавляет современные функции. - Два интересных полифилла: - - [babel polyfill](https://babeljs.io/docs/usage/polyfill/) поддерживает много функций, но очень большой. - - [polyfill.io](http://polyfill.io) - сервис, который позволяет загружать/создавать полифиллы в зависимости от необходимых функций. + Интересное хранилище полифилов: + - [core js](https://github.com/zloirock/core-js) поддерживает много функций, можно подключать только нужные. -Таким образом, чтобы современные функции поддерживались в старых движках, нам надо установить транспилер и добавить полифилл. +Таким образом, чтобы современные функции поддерживались в старых движках, нам надо установить транспилер и добавить полифил. ## Примеры в учебнике @@ -50,4 +49,4 @@ alert('Нажмите кнопку "Play" в крайнем правом угл Вы читаете оффлайн-версию, примеры в PDF запустить не получится, в EPUB некоторые работают. ``` -Google Chrome обычно поддерживает современные функции, можно запускать новейшие примеры без каких-либо транспилеров, но другие современные браузеры также хорошо работают. +Google Chrome обычно поддерживает современные функции, можно запускать новейшие примеры без каких-либо транспилеров, но и другие современные браузеры тоже хорошо работают. diff --git a/1-js/03-code-quality/index.md b/1-js/03-code-quality/index.md index 2ef64fa690..cddf50d37b 100644 --- a/1-js/03-code-quality/index.md +++ b/1-js/03-code-quality/index.md @@ -1,3 +1,3 @@ -# Code quality +# Качество кода -This chapter explains coding practices that we'll use further in the development. +В этой главе объясняются подходы к написанию кода, которые мы будем использовать в дальнейшем при разработке. diff --git a/1-js/04-object-basics/01-object/2-hello-object/task.md b/1-js/04-object-basics/01-object/2-hello-object/task.md index 2841a058f1..f36b8e49db 100644 --- a/1-js/04-object-basics/01-object/2-hello-object/task.md +++ b/1-js/04-object-basics/01-object/2-hello-object/task.md @@ -2,13 +2,13 @@ importance: 5 --- -# Hello, object +# Привет, object -Write the code, one line for each action: +Напишите код, выполнив задание из каждого пункта отдельной строкой: -1. Create an empty object `user`. -2. Add the property `name` with the value `John`. -3. Add the property `surname` with the value `Smith`. -4. Change the value of the `name` to `Pete`. -5. Remove the property `name` from the object. +1. Создайте пустой объект `user`. +2. Добавьте свойство `name` со значением `John`. +3. Добавьте свойство `surname` со значением `Smith`. +4. Измените значение свойства `name` на `Pete`. +5. Удалите свойство `name` из объекта. diff --git a/1-js/04-object-basics/01-object/3-is-empty/_js.view/solution.js b/1-js/04-object-basics/01-object/3-is-empty/_js.view/solution.js index db3283e49a..8de38dcdb1 100644 --- a/1-js/04-object-basics/01-object/3-is-empty/_js.view/solution.js +++ b/1-js/04-object-basics/01-object/3-is-empty/_js.view/solution.js @@ -1,6 +1,6 @@ function isEmpty(obj) { for (let key in obj) { - // if the loop has started, there is a property + // если тело цикла начнет выполняться - значит в объекте есть свойства return false; } return true; diff --git a/1-js/04-object-basics/01-object/3-is-empty/_js.view/test.js b/1-js/04-object-basics/01-object/3-is-empty/_js.view/test.js index 4db5efabee..84339d00ff 100644 --- a/1-js/04-object-basics/01-object/3-is-empty/_js.view/test.js +++ b/1-js/04-object-basics/01-object/3-is-empty/_js.view/test.js @@ -1,9 +1,9 @@ describe("isEmpty", function() { - it("returns true for an empty object", function() { + it("возвращает true для пустого объекта", function() { assert.isTrue(isEmpty({})); }); - it("returns false if a property exists", function() { + it("возвращает false, если свойство существует", function() { assert.isFalse(isEmpty({ anything: false })); diff --git a/1-js/04-object-basics/01-object/3-is-empty/solution.md b/1-js/04-object-basics/01-object/3-is-empty/solution.md index b876973b5e..da728047cd 100644 --- a/1-js/04-object-basics/01-object/3-is-empty/solution.md +++ b/1-js/04-object-basics/01-object/3-is-empty/solution.md @@ -1 +1 @@ -Just loop over the object and `return false` immediately if there's at least one property. +Просто в цикле перебираем свойства объекта и возвращаем `false`, как только встречаем свойство. diff --git a/1-js/04-object-basics/01-object/3-is-empty/task.md b/1-js/04-object-basics/01-object/3-is-empty/task.md index c438d36a2d..6bc80f8b80 100644 --- a/1-js/04-object-basics/01-object/3-is-empty/task.md +++ b/1-js/04-object-basics/01-object/3-is-empty/task.md @@ -2,11 +2,11 @@ importance: 5 --- -# Check for emptiness +# Проверка на пустоту -Write the function `isEmpty(obj)` which returns `true` if the object has no properties, `false` otherwise. +Напишите функцию `isEmpty(obj)`, которая возвращает `true`, если у объекта нет свойств, иначе `false`. -Should work like that: +Должно работать так: ```js let schedule = {}; diff --git a/1-js/04-object-basics/01-object/4-const-object/solution.md b/1-js/04-object-basics/01-object/4-const-object/solution.md index f73c2f92bb..c057b7cd1e 100644 --- a/1-js/04-object-basics/01-object/4-const-object/solution.md +++ b/1-js/04-object-basics/01-object/4-const-object/solution.md @@ -1,8 +1,8 @@ -Sure, it works, no problem. +Конечно, это сработает без проблем. -The `const` only protects the variable itself from changing. +Объявление `const` защищает только саму переменную от изменений. -In other words, `user` stores a reference to the object. And it can't be changed. But the content of the object can. +Другими словами, `user` хранит ссылку на объект. И это не может быть изменено. Но содержимое объекта менять можно. ```js run const user = { @@ -10,10 +10,10 @@ const user = { }; *!* -// works +// Работает! user.name = "Pete"; */!* -// error +// Ошибка user = 123; ``` diff --git a/1-js/04-object-basics/01-object/4-const-object/task.md b/1-js/04-object-basics/01-object/4-const-object/task.md index a9aada6310..174fd65ee4 100644 --- a/1-js/04-object-basics/01-object/4-const-object/task.md +++ b/1-js/04-object-basics/01-object/4-const-object/task.md @@ -2,9 +2,9 @@ importance: 5 --- -# Constant objects? +# Объекты-константы? -Is it possible to change an object declared with `const`? What do you think? +Можно ли изменить объект, объявленный с помощью `const`? Как вы думаете? ```js const user = { @@ -12,7 +12,7 @@ const user = { }; *!* -// does it work? +// это будет работать? user.name = "Pete"; */!* ``` diff --git a/1-js/04-object-basics/01-object/5-sum-object/task.md b/1-js/04-object-basics/01-object/5-sum-object/task.md index 7e3e048d0d..dccfca033c 100644 --- a/1-js/04-object-basics/01-object/5-sum-object/task.md +++ b/1-js/04-object-basics/01-object/5-sum-object/task.md @@ -2,9 +2,9 @@ importance: 5 --- -# Sum object properties +# Сумма свойств объекта -We have an object storing salaries of our team: +У нас есть объект, в котором хранятся зарплаты нашей команды: ```js let salaries = { @@ -14,6 +14,6 @@ let salaries = { } ``` -Write the code to sum all salaries and store in the variable `sum`. Should be `390` in the example above. +Напишите код для суммирования всех зарплат и сохраните результат в переменной `sum`. Должно получиться `390`. -If `salaries` is empty, then the result must be `0`. \ No newline at end of file +Если объект `salaries` пуст, то результат должен быть `0`. diff --git a/1-js/04-object-basics/01-object/8-multiply-numeric/_js.view/source.js b/1-js/04-object-basics/01-object/8-multiply-numeric/_js.view/source.js index a02b1e1cb7..720b846049 100644 --- a/1-js/04-object-basics/01-object/8-multiply-numeric/_js.view/source.js +++ b/1-js/04-object-basics/01-object/8-multiply-numeric/_js.view/source.js @@ -7,7 +7,7 @@ let menu = { function multiplyNumeric(obj) { - /* your code */ + /* ваш код */ } diff --git a/1-js/04-object-basics/01-object/8-multiply-numeric/_js.view/test.js b/1-js/04-object-basics/01-object/8-multiply-numeric/_js.view/test.js index 064e5414ff..4d7dec71f5 100644 --- a/1-js/04-object-basics/01-object/8-multiply-numeric/_js.view/test.js +++ b/1-js/04-object-basics/01-object/8-multiply-numeric/_js.view/test.js @@ -1,5 +1,5 @@ describe("multiplyNumeric", function() { - it("multiplies all numeric properties by 2", function() { + it("умножаем все числовые свойства на 2", function() { let menu = { width: 200, height: 300, diff --git a/1-js/04-object-basics/01-object/8-multiply-numeric/task.md b/1-js/04-object-basics/01-object/8-multiply-numeric/task.md index 33eb89220c..c00c0d1ba7 100644 --- a/1-js/04-object-basics/01-object/8-multiply-numeric/task.md +++ b/1-js/04-object-basics/01-object/8-multiply-numeric/task.md @@ -2,14 +2,14 @@ importance: 3 --- -# Multiply numeric properties by 2 +# Умножаем все числовые свойства на 2 -Create a function `multiplyNumeric(obj)` that multiplies all numeric properties of `obj` by `2`. +Создайте функцию `multiplyNumeric(obj)`, которая умножает все числовые свойства объекта `obj` на `2`. -For instance: +Например: ```js -// before the call +// до вызова функции let menu = { width: 200, height: 300, @@ -18,7 +18,7 @@ let menu = { multiplyNumeric(menu); -// after the call +// после вызова функции menu = { width: 400, height: 600, @@ -26,8 +26,7 @@ menu = { }; ``` -Please note that `multiplyNumeric` does not need to return anything. It should modify the object in-place. - -P.S. Use `typeof` to check for a number here. +Обратите внимание, что `multiplyNumeric` не нужно ничего возвращать. Следует напрямую изменять объект. +P.S. Используйте `typeof` для проверки, что значение свойства числовое. diff --git a/1-js/04-object-basics/01-object/article.md b/1-js/04-object-basics/01-object/article.md index 980670439a..3c5474b851 100644 --- a/1-js/04-object-basics/01-object/article.md +++ b/1-js/04-object-basics/01-object/article.md @@ -1,138 +1,163 @@ -# Objects +# Объекты -As we know from the chapter , there are seven data types in JavaScript. Six of them are called "primitive", because their values contain only a single thing (be it a string or a number or whatever). +Как мы знаем из главы , в JavaScript существует 8 типов данных. Семь из них называются "примитивными", так как содержат только одно значение (будь то строка, число или что-то другое). -In contrast, objects are used to store keyed collections of various data and more complex entities. In JavaScript, objects penetrate almost every aspect of the language. So we must understand them first before going in-depth anywhere else. +Объекты же используются для хранения коллекций различных значений и более сложных сущностей. В JavaScript объекты используются очень часто, это одна из основ языка. Поэтому мы должны понять их, прежде чем углубляться куда-либо ещё. -An object can be created with figure brackets `{…}` with an optional list of *properties*. A property is a "key: value" pair, where `key` is a string (also called a "property name"), and `value` can be anything. +Объект может быть создан с помощью фигурных скобок `{…}` с необязательным списком *свойств*. Свойство - это пара "ключ: значение", где `ключ` - это строка (также называемая "именем свойства"), а `значение` может быть чем угодно. -We can imagine an object as a cabinet with signed files. Every piece of data is stored in its file by the key. It's easy to find a file by its name or add/remove a file. +Мы можем представить объект в виде ящика с подписанными папками. Каждый элемент данных хранится в своей папке, на которой написан ключ. По ключу папку легко найти, удалить или добавить в неё что-либо. -![](object.png) +![](object.svg) -An empty object ("empty cabinet") can be created using one of two syntaxes: +Пустой объект ("пустой ящик") можно создать, используя один из двух вариантов синтаксиса: ```js -let user = new Object(); // "object constructor" syntax -let user = {}; // "object literal" syntax +let user = new Object(); // синтаксис "конструктор объекта" +let user = {}; // синтаксис "литерал объекта" ``` -![](object-user-empty.png) +![](object-user-empty.svg) -Usually, the figure brackets `{...}` are used. That declaration is called an *object literal*. +Обычно используют вариант с фигурными скобками `{...}`. Такое объявление называют *литералом объекта* или *литеральной нотацией*. -## Literals and properties +## Литералы и свойства -We can immediately put some properties into `{...}` as "key: value" pairs: +При использовании литерального синтаксиса `{...}` мы сразу можем поместить в объект несколько свойств в виде пар "ключ: значение": ```js -let user = { // an object - name: "John", // by key "name" store value "John" - age: 30 // by key "age" store value 30 +let user = { // объект + name: "John", // под ключом "name" хранится значение "John" + age: 30 // под ключом "age" хранится значение 30 }; ``` -A property has a key (also known as "name" or "identifier") before the colon `":"` and a value to the right of it. +У каждого свойства есть ключ (также называемый "имя" или "идентификатор"). После имени свойства следует двоеточие `":"`, и затем указывается значение свойства. Если в объекте несколько свойств, то они перечисляются через запятую. -In the `user` object, there are two properties: +В объекте `user` сейчас находятся два свойства: -1. The first property has the name `"name"` and the value `"John"`. -2. The second one has the name `"age"` and the value `30`. +1. Первое свойство с именем `"name"` и значением `"John"`. +2. Второе свойство с именем `"age"` и значением `30`. -The resulting `user` object can be imagined as a cabinet with two signed files labeled "name" and "age". +Можно сказать, что наш объект `user` - это ящик с двумя папками, подписанными "name" и "age". -![user object](object-user.png) +![user object](object-user.svg) -We can add, remove and read files from it any time. +Мы можем в любой момент добавить в него новые папки, удалить папки или прочитать содержимое любой папки. -Property values are accessible using the dot notation: +Для обращения к свойствам используется запись "через точку": ```js -// get fields of the object: +// получаем свойства объекта: alert( user.name ); // John alert( user.age ); // 30 ``` -The value can be of any type. Let's add a boolean one: +Значение может быть любого типа. Давайте добавим свойство с логическим значением: ```js user.isAdmin = true; ``` -![user object 2](object-user-isadmin.png) +![user object 2](object-user-isadmin.svg) -To remove a property, we can use `delete` operator: +Для удаления свойства мы можем использовать оператор `delete`: ```js delete user.age; ``` -![user object 3](object-user-delete.png) +![user object 3](object-user-delete.svg) -We can also use multiword property names, but then they must be quoted: +Имя свойства может состоять из нескольких слов, но тогда оно должно быть заключено в кавычки: ```js let user = { name: "John", age: 30, - "likes birds": true // multiword property name must be quoted + "likes birds": true // имя свойства из нескольких слов должно быть в кавычках }; ``` -![](object-user-props.png) +![](object-user-props.svg) +Последнее свойство объекта может заканчиваться запятой: -The last property in the list may end with a comma: ```js let user = { name: "John", age: 30*!*,*/!* } ``` -That is called a "trailing" or "hanging" comma. Makes it easier to add/remove/move around properties, because all lines become alike. +Это называется "висячая запятая". Такой подход упрощает добавление, удаление и перемещение свойств, так как все строки объекта становятся одинаковыми. -## Square brackets +````smart header="Объект, объявленный как константа, может быть изменён" +Объект, объявленный через `const`, *может* быть изменён. -For multiword properties, the dot access doesn't work: +Например: ```js run -// this would give a syntax error +const user = { + name: "John" +}; + +*!* +user.name = "Pete"; // (*) +*/!* + +alert(user.name); // Pete +``` + +Может показаться, что строка `(*)` должна вызвать ошибку, но нет, здесь всё в порядке. Дело в том, что объявление `const` защищает от изменений только саму переменную `user`, а не её содержимое. + +Определение `const` выдаст ошибку только если мы присвоим переменной другое значение: `user=...`. + +Есть ещё один способ сделать константами свойства объекта, который мы рассмотрим в главе . +```` + +## Квадратные скобки + +Для свойств, имена которых состоят из нескольких слов, доступ к значению "через точку" не работает: + +```js run +// это вызовет синтаксическую ошибку user.likes birds = true ``` -That's because the dot requires the key to be a valid variable identifier. That is: no spaces and other limitations. +JavaScript видит, что мы обращаемся к свойству `user.likes`, а затем идёт непонятное слово `birds`. В итоге синтаксическая ошибка. -There's an alternative "square bracket notation" that works with any string: +Точка требует, чтобы ключ был именован по правилам именования переменных. То есть не имел пробелов, не начинался с цифры и не содержал специальные символы, кроме `$` и `_`. +Для таких случаев существует альтернативный способ доступа к свойствам через квадратные скобки. Такой способ сработает с любым именем свойства: ```js run let user = {}; -// set +// присваивание значения свойству user["likes birds"] = true; -// get +// получение значения свойства alert(user["likes birds"]); // true -// delete +// удаление свойства delete user["likes birds"]; ``` -Now everything is fine. Please note that the string inside the brackets is properly quoted (any type of quotes will do). +Сейчас всё в порядке. Обратите внимание, что строка в квадратных скобках заключена в кавычки (подойдёт любой тип кавычек). -Square brackets also provide a way to obtain the property name as the result of any expression -- as opposed to a literal string -- like from a variable as follows: +Квадратные скобки также позволяют обратиться к свойству, имя которого может быть результатом выражения. Например, имя свойства может храниться в переменной: ```js let key = "likes birds"; -// same as user["likes birds"] = true; +// то же самое, что и user["likes birds"] = true; user[key] = true; ``` -Here, the variable `key` may be calculated at run-time or depend on the user input. And then we use it to access the property. That gives us a great deal of flexibility. The dot notation cannot be used in a similar way. +Здесь переменная `key` может быть вычислена во время выполнения кода или зависеть от пользовательского ввода. После этого мы используем её для доступа к свойству. Это даёт нам большую гибкость. -For instance: +Пример: ```js run let user = { @@ -140,47 +165,59 @@ let user = { age: 30 }; -let key = prompt("What do you want to know about the user?", "name"); +let key = prompt("Что вы хотите узнать о пользователе?", "name"); -// access by variable -alert( user[key] ); // John (if enter "name") +// доступ к свойству через переменную +alert( user[key] ); // John (если ввели "name") ``` +Запись "через точку" такого не позволяет: -### Computed properties +```js run +let user = { + name: "John", + age: 30 +}; + +let key = "name"; +alert( user.key ); // undefined +``` + +### Вычисляемые свойства -We can use square brackets in an object literal. That's called *computed properties*. +Мы можем использовать квадратные скобки в литеральной нотации для создания *вычисляемого свойства*. -For instance: +Пример: ```js run -let fruit = prompt("Which fruit to buy?", "apple"); +let fruit = prompt("Какой фрукт купить?", "apple"); let bag = { *!* - [fruit]: 5, // the name of the property is taken from the variable fruit + [fruit]: 5, // имя свойства будет взято из переменной fruit */!* }; -alert( bag.apple ); // 5 if fruit="apple" +alert( bag.apple ); // 5, если fruit="apple" ``` -The meaning of a computed property is simple: `[fruit]` means that the property name should be taken from `fruit`. +Смысл вычисляемого свойства прост: запись `[fruit]` означает, что имя свойства необходимо взять из переменной `fruit`. -So, if a visitor enters `"apple"`, `bag` will become `{apple: 5}`. +И если посетитель введёт слово `"apple"`, то в объекте `bag` теперь будет лежать свойство `{apple: 5}`. + +По сути, пример выше работает так же, как и следующий пример: -Essentially, that works the same as: ```js run -let fruit = prompt("Which fruit to buy?", "apple"); +let fruit = prompt("Какой фрукт купить?", "apple"); let bag = {}; -// take property name from the fruit variable +// имя свойства будет взято из переменной fruit bag[fruit] = 5; ``` -...But looks nicer. +...Но первый пример выглядит лаконичнее. -We can use more complex expressions inside square brackets: +Мы можем использовать и более сложные выражения в квадратных скобках: ```js let fruit = 'apple'; @@ -189,58 +226,22 @@ let bag = { }; ``` -Square brackets are much more powerful than the dot notation. They allow any property names and variables. But they are also more cumbersome to write. - -So most of the time, when property names are known and simple, the dot is used. And if we need something more complex, then we switch to square brackets. +Квадратные скобки дают намного больше возможностей, чем запись через точку. Они позволяют использовать любые имена свойств и переменные, хотя и требуют более громоздких конструкций кода. +Подведём итог: в большинстве случаев, когда имена свойств известны и просты, используется запись через точку. Если же нам нужно что-то более сложное, то мы используем квадратные скобки. +## Свойство из переменной -````smart header="Reserved words are allowed as property names" -A variable cannot have a name equal to one of language-reserved words like "for", "let", "return" etc. +В реальном коде часто нам необходимо использовать существующие переменные как значения для свойств с тем же именем. -But for an object property, there's no such restriction. Any name is fine: - -```js run -let obj = { - for: 1, - let: 2, - return: 3 -}; - -alert( obj.for + obj.let + obj.return ); // 6 -``` - -Basically, any name is allowed, but there's a special one: `"__proto__"` that gets special treatment for historical reasons. For instance, we can't set it to a non-object value: - -```js run -let obj = {}; -obj.__proto__ = 5; -alert(obj.__proto__); // [object Object], didn't work as intended -``` - -As we see from the code, the assignment to a primitive `5` is ignored. - -That can become a source of bugs and even vulnerabilities if we intend to store arbitrary key-value pairs in an object, and allow a visitor to specify the keys. - -In that case the visitor may choose "__proto__" as the key, and the assignment logic will be ruined (as shown above). - -There is a way to make objects treat `__proto__` as a regular property, which we'll cover later, but first we need to know more about objects. -There's also another data structure [Map](info:map-set-weakmap-weakset), that we'll learn in the chapter , which supports arbitrary keys. -```` - - -## Property value shorthand - -In real code we often use existing variables as values for property names. - -For instance: +Например: ```js run function makeUser(name, age) { return { name: name, age: age - // ...other properties + // ...другие свойства }; } @@ -248,103 +249,148 @@ let user = makeUser("John", 30); alert(user.name); // John ``` -In the example above, properties have the same names as variables. The use-case of making a property from a variable is so common, that there's a special *property value shorthand* to make it shorter. +В примере выше название свойств `name` и `age` совпадают с названиями переменных, которые мы подставляем в качестве значений этих свойств. Такой подход настолько распространён, что существуют специальные *короткие свойства* для упрощения этой записи. -Instead of `name:name` we can just write `name`, like this: +Вместо `name:name` мы можем написать просто `name`: ```js function makeUser(name, age) { *!* return { - name, // same as name: name - age // same as age: age + name, // то же самое, что и name: name + age // то же самое, что и age: age // ... }; */!* } ``` -We can use both normal properties and shorthands in the same object: +Мы можем использовать как обычные свойства, так и короткие в одном и том же объекте: ```js let user = { - name, // same as name:name + name, // тоже самое, что и name:name age: 30 }; ``` -## Existence check -A notable objects feature is that it's possible to access any property. There will be no error if the property doesn't exist! Accessing a non-existing property just returns `undefined`. It provides a very common way to test whether the property exists -- to get it and compare vs undefined: +## Ограничения на имена свойств + +Как мы уже знаем, имя переменной не может совпадать с зарезервированными словами, такими как "for", "let", "return" и т.д. + +Но для свойств объекта такого ограничения нет: + +```js run +// эти имена свойств допустимы +let obj = { + for: 1, + let: 2, + return: 3 +}; + +alert( obj.for + obj.let + obj.return ); // 6 +``` + +Иными словами, нет никаких ограничений к именам свойств. Они могут быть в виде строк или символов (специальный тип для идентификаторов, который будет рассмотрен позже). + +Все другие типы данных будут автоматически преобразованы к строке. + +Например, если использовать число `0` в качестве ключа, то оно превратится в строку `"0"`: + +```js run +let obj = { + 0: "Тест" // то же самое что и "0": "Тест" +}; + +// обе функции alert выведут одно и то же свойство (число 0 преобразуется в строку "0") +alert( obj["0"] ); // Тест +alert( obj[0] ); // Тест (то же свойство) +``` + +Есть небольшой подводный камень, связанный со специальным свойством `__proto__`. Мы не можем установить его в необъектное значение: + +```js run +let obj = {}; +obj.__proto__ = 5; // присвоим число +alert(obj.__proto__); // [object Object], значение - это объект, т.е. не то, что мы ожидали +``` + +Как мы видим, присвоение примитивного значения `5` игнорируется. + +Мы более подробно исследуем особенности свойства `__proto__` в следующих главах [](info:prototype-inheritance), а также предложим [способы исправления](info:prototype-methods) такого поведения. + +## Проверка существования свойства, оператор "in" + +В отличие от многих других языков, особенность JavaScript-объектов в том, что можно получить доступ к любому свойству. Даже если свойства не существует - ошибки не будет! + +При обращении к свойству, которого нет, возвращается `undefined`. Это позволяет просто проверить существование свойства: ```js run let user = {}; -alert( user.noSuchProperty === undefined ); // true means "no such property" +alert( user.noSuchProperty === undefined ); // true означает "свойства нет" ``` -There also exists a special operator `"in"` to check for the existence of a property. +Также существует специальный оператор `"in"` для проверки существования свойства в объекте. -The syntax is: +Синтаксис оператора: ```js "key" in object ``` -For instance: +Пример: ```js run let user = { name: "John", age: 30 }; -alert( "age" in user ); // true, user.age exists -alert( "blabla" in user ); // false, user.blabla doesn't exist +alert( "age" in user ); // true, user.age существует +alert( "blabla" in user ); // false, user.blabla не существует ``` -Please note that on the left side of `in` there must be a *property name*. That's usually a quoted string. +Обратите внимание, что слева от оператора `in` должно быть *имя свойства*. Обычно это строка в кавычках. -If we omit quotes, that would mean a variable containing the actual name will be tested. For instance: +Если мы опускаем кавычки, это значит, что мы указываем переменную, в которой находится имя свойства. Например: ```js run let user = { age: 30 }; let key = "age"; -alert( *!*key*/!* in user ); // true, takes the name from key and checks for such property +alert( *!*key*/!* in user ); // true, имя свойства было взято из переменной key ``` -````smart header="Using \"in\" for properties that store `undefined`" -Usually, the strict comparison `"=== undefined"` check works fine. But there's a special case when it fails, but `"in"` works correctly. +Для чего вообще нужен оператор `in`? Разве недостаточно сравнения с `undefined`? + +В большинстве случаев прекрасно сработает сравнение с `undefined`. Но есть особый случай, когда оно не подходит и нужно использовать `"in"`. -It's when an object property exists, but stores `undefined`: +Это когда свойство существует, но содержит значение `undefined`: ```js run let obj = { test: undefined }; -alert( obj.test ); // it's undefined, so - no such property? - -alert( "test" in obj ); // true, the property does exist! +alert( obj.test ); // выведет undefined, значит свойство не существует? +alert( "test" in obj ); // true, свойство существует! ``` +В примере выше свойство `obj.test` технически существует в объекте. Оператор `in` сработал правильно. -In the code above, the property `obj.test` technically exists. So the `in` operator works right. +Подобные ситуации случаются очень редко, так как `undefined` обычно явно не присваивается. Для "неизвестных" или "пустых" свойств мы используем значение `null`. -Situations like this happen very rarely, because `undefined` is usually not assigned. We mostly use `null` for "unknown" or "empty" values. So the `in` operator is an exotic guest in the code. -```` +## Цикл "for..in" [#forin] +Для перебора всех свойств объекта используется цикл `for..in`. Этот цикл отличается от изученного ранее цикла `for(;;)`. -## The "for..in" loop - -To walk over all keys of an object, there exists a special form of the loop: `for..in`. This is a completely different thing from the `for(;;)` construct that we studied before. - -The syntax: +Синтаксис: ```js for (key in object) { - // executes the body for each key among object properties + // тело цикла выполняется для каждого свойства объекта } ``` -For instance, let's output all properties of `user`: +К примеру, давайте выведем все свойства объекта `user`: ```js run let user = { @@ -354,33 +400,32 @@ let user = { }; for (let key in user) { - // keys + // ключи alert( key ); // name, age, isAdmin - // values for the keys + // значения ключей alert( user[key] ); // John, 30, true } ``` -Note that all "for" constructs allow us to declare the looping variable inside the loop, like `let key` here. +Обратите внимание, что все конструкции "for" позволяют нам объявлять переменную внутри цикла, как, например, `let key` здесь. -Also, we could use another variable name here instead of `key`. For instance, `"for (let prop in obj)"` is also widely used. +Кроме того, мы могли бы использовать другое имя переменной. Например, часто используется вариант `"for (let prop in obj)"`. +### Упорядочение свойств объекта -### Ordered like an object +Упорядочены ли свойства объекта? Другими словами, если мы будем в цикле перебирать все свойства объекта, получим ли мы их в том же порядке, в котором мы их добавляли? Можем ли мы на это рассчитывать? -Are objects ordered? In other words, if we loop over an object, do we get all properties in the same order they were added? Can we rely on this? +Короткий ответ: свойства упорядочены особым образом: свойства с целочисленными ключами сортируются по возрастанию, остальные располагаются в порядке создания. Разберёмся подробнее. -The short answer is: "ordered in a special fashion": integer properties are sorted, others appear in creation order. The details follow. - -As an example, let's consider an object with the phone codes: +В качестве примера рассмотрим объект с телефонными кодами: ```js run let codes = { - "49": "Germany", - "41": "Switzerland", - "44": "Great Britain", + "49": "Германия", + "41": "Швейцария", + "44": "Великобритания", // .., - "1": "USA" + "1": "США" }; *!* @@ -390,56 +435,56 @@ for (let code in codes) { */!* ``` -The object may be used to suggest a list of options to the user. If we're making a site mainly for German audience then we probably want `49` to be the first. +Если мы делаем сайт для немецкой аудитории, то, вероятно, мы хотим, чтобы код `49` был первым. -But if we run the code, we see a totally different picture: +Но если мы запустим код, мы увидим совершенно другую картину: -- USA (1) goes first -- then Switzerland (41) and so on. +- США (1) идёт первым +- затем Швейцария (41) и так далее. -The phone codes go in the ascending sorted order, because they are integers. So we see `1, 41, 44, 49`. +Телефонные коды идут в порядке возрастания, потому что они являются целыми числами: `1, 41, 44, 49`. -````smart header="Integer properties? What's that?" -The "integer property" term here means a string that can be converted to-and-from an integer without a change. +````smart header="Целочисленные свойства? Это что?" +Термин "целочисленное свойство" означает строку, которая может быть преобразована в целое число и обратно без изменений. -So, "49" is an integer property name, because when it's transformed to an integer number and back, it's still the same. But "+49" and "1.2" are not: +То есть, `"49"` - это целочисленное имя свойства, потому что если его преобразовать в целое число, а затем обратно в строку, то оно не изменится. А вот свойства `"+49"` или `"1.2"` таковыми не являются: ```js run -// Math.trunc is a built-in function that removes the decimal part -alert( String(Math.trunc(Number("49"))) ); // "49", same, integer property -alert( String(Math.trunc(Number("+49"))) ); // "49", not same "+49" ⇒ not integer property -alert( String(Math.trunc(Number("1.2"))) ); // "1", not same "1.2" ⇒ not integer property +// Math.trunc - встроенная функция, которая удаляет десятичную часть +alert( String(Math.trunc(Number("49"))) ); // "49", то же самое ⇒ свойство целочисленное +alert( String(Math.trunc(Number("+49"))) ); // "49", не то же самое, что "+49" ⇒ свойство не целочисленное +alert( String(Math.trunc(Number("1.2"))) ); // "1", не то же самое, что "1.2" ⇒ свойство не целочисленное ``` ```` -...On the other hand, if the keys are non-integer, then they are listed in the creation order, for instance: +...С другой стороны, если ключи не целочисленные, то они перебираются в порядке создания, например: ```js run let user = { name: "John", surname: "Smith" }; -user.age = 25; // add one more +user.age = 25; // добавим ещё одно свойство *!* -// non-integer properties are listed in the creation order +// не целочисленные свойства перечислены в порядке создания */!* for (let prop in user) { alert( prop ); // name, surname, age } ``` -So, to fix the issue with the phone codes, we can "cheat" by making the codes non-integer. Adding a plus `"+"` sign before each code is enough. +Таким образом, чтобы решить нашу проблему с телефонными кодами, мы можем схитрить, сделав коды не целочисленными свойствами. Добавления знака `"+"` перед каждым кодом будет достаточно. -Like this: +Пример: ```js run let codes = { - "+49": "Germany", - "+41": "Switzerland", - "+44": "Great Britain", + "+49": "Германия", + "+41": "Швейцария", + "+44": "Великобритания", // .., - "+1": "USA" + "+1": "США" }; for (let code in codes) { @@ -447,294 +492,35 @@ for (let code in codes) { } ``` -Now it works as intended. - -## Copying by reference - -One of the fundamental differences of objects vs primitives is that they are stored and copied "by reference". - -Primitive values: strings, numbers, booleans -- are assigned/copied "as a whole value". - -For instance: - -```js -let message = "Hello!"; -let phrase = message; -``` - -As a result we have two independent variables, each one is storing the string `"Hello!"`. - -![](variable-copy-value.png) - -Objects are not like that. - -**A variable stores not the object itself, but its "address in memory", in other words "a reference" to it.** - -Here's the picture for the object: - -```js -let user = { - name: "John" -}; -``` - -![](variable-contains-reference.png) - -Here, the object is stored somewhere in memory. And the variable `user` has a "reference" to it. - -**When an object variable is copied -- the reference is copied, the object is not duplicated.** - -If we imagine an object as a cabinet, then a variable is a key to it. Copying a variable duplicates the key, but not the cabinet itself. - -For instance: - -```js no-beautify -let user = { name: "John" }; - -let admin = user; // copy the reference -``` - -Now we have two variables, each one with the reference to the same object: - -![](variable-copy-reference.png) - -We can use any variable to access the cabinet and modify its contents: - -```js run -let user = { name: 'John' }; - -let admin = user; - -*!* -admin.name = 'Pete'; // changed by the "admin" reference -*/!* - -alert(*!*user.name*/!*); // 'Pete', changes are seen from the "user" reference -``` - -The example above demonstrates that there is only one object. As if we had a cabinet with two keys and used one of them (`admin`) to get into it. Then, if we later use the other key (`user`) we would see changes. - -### Comparison by reference - -The equality `==` and strict equality `===` operators for objects work exactly the same. - -**Two objects are equal only if they are the same object.** - -For instance, two variables reference the same object, they are equal: - -```js run -let a = {}; -let b = a; // copy the reference - -alert( a == b ); // true, both variables reference the same object -alert( a === b ); // true -``` - -And here two independent objects are not equal, even though both are empty: - -```js run -let a = {}; -let b = {}; // two independent objects - -alert( a == b ); // false -``` - -For comparisons like `obj1 > obj2` or for a comparison against a primitive `obj == 5`, objects are converted to primitives. We'll study how object conversions work very soon, but to tell the truth, such comparisons are necessary very rarely and usually are a result of a coding mistake. - -### Const object - -An object declared as `const` *can* be changed. - -For instance: - -```js run -const user = { - name: "John" -}; - -*!* -user.age = 25; // (*) -*/!* - -alert(user.age); // 25 -``` - -It might seem that the line `(*)` would cause an error, but no, there's totally no problem. That's because `const` fixes the value of `user` itself. And here `user` stores the reference to the same object all the time. The line `(*)` goes *inside* the object, it doesn't reassign `user`. - -The `const` would give an error if we try to set `user` to something else, for instance: - -```js run -const user = { - name: "John" -}; - -*!* -// Error (can't reassign user) -*/!* -user = { - name: "Pete" -}; -``` - -...But what if we want to make constant object properties? So that `user.age = 25` would give an error. That's possible too. We'll cover it in the chapter . - -## Cloning and merging, Object.assign - -So, copying an object variable creates one more reference to the same object. - -But what if we need to duplicate an object? Create an independent copy, a clone? - -That's also doable, but a little bit more difficult, because there's no built-in method for that in JavaScript. Actually, that's rarely needed. Copying by reference is good most of the time. - -But if we really want that, then we need to create a new object and replicate the structure of the existing one by iterating over its properties and copying them on the primitive level. - -Like this: - -```js run -let user = { - name: "John", - age: 30 -}; - -*!* -let clone = {}; // the new empty object - -// let's copy all user properties into it -for (let key in user) { - clone[key] = user[key]; -} -*/!* - -// now clone is a fully independent clone -clone.name = "Pete"; // changed the data in it - -alert( user.name ); // still John in the original object -``` - -Also we can use the method [Object.assign](mdn:js/Object/assign) for that. - -The syntax is: - -```js -Object.assign(dest, [src1, src2, src3...]) -``` - -- Arguments `dest`, and `src1, ..., srcN` (can be as many as needed) are objects. -- It copies the properties of all objects `src1, ..., srcN` into `dest`. In other words, properties of all arguments starting from the 2nd are copied into the 1st. Then it returns `dest`. - -For instance, we can use it to merge several objects into one: -```js -let user = { name: "John" }; - -let permissions1 = { canView: true }; -let permissions2 = { canEdit: true }; - -*!* -// copies all properties from permissions1 and permissions2 into user -Object.assign(user, permissions1, permissions2); -*/!* - -// now user = { name: "John", canView: true, canEdit: true } -``` - -If the receiving object (`user`) already has the same named property, it will be overwritten: - -```js -let user = { name: "John" }; - -// overwrite name, add isAdmin -Object.assign(user, { name: "Pete", isAdmin: true }); - -// now user = { name: "Pete", isAdmin: true } -``` - -We also can use `Object.assign` to replace the loop for simple cloning: - -```js -let user = { - name: "John", - age: 30 -}; - -*!* -let clone = Object.assign({}, user); -*/!* -``` - -It copies all properties of `user` into the empty object and returns it. Actually, the same as the loop, but shorter. - -Until now we assumed that all properties of `user` are primitive. But properties can be references to other objects. What to do with them? - -Like this: -```js run -let user = { - name: "John", - sizes: { - height: 182, - width: 50 - } -}; - -alert( user.sizes.height ); // 182 -``` - -Now it's not enough to copy `clone.sizes = user.sizes`, because the `user.sizes` is an object, it will be copied by reference. So `clone` and `user` will share the same sizes: - -Like this: -```js run -let user = { - name: "John", - sizes: { - height: 182, - width: 50 - } -}; - -let clone = Object.assign({}, user); - -alert( user.sizes === clone.sizes ); // true, same object - -// user and clone share sizes -user.sizes.width++; // change a property from one place -alert(clone.sizes.width); // 51, see the result from the other one -``` - -To fix that, we should use the cloning loop that examines each value of `user[key]` and, if it's an object, then replicate its structure as well. That is called a "deep cloning". - -There's a standard algorithm for deep cloning that handles the case above and more complex cases, called the [Structured cloning algorithm](http://w3c.github.io/html/infrastructure.html#safe-passing-of-structured-data). In order not to reinvent the wheel, we can use a working implementation of it from the JavaScript library [lodash](https://lodash.com), the method is called [_.cloneDeep(obj)](https://lodash.com/docs#cloneDeep). - - - -## Summary +Теперь код работает так, как мы задумывали. -Objects are associative arrays with several special features. +## Итого -They store properties (key-value pairs), where: -- Property keys must be strings or symbols (usually strings). -- Values can be of any type. +Объекты - это ассоциативные массивы с рядом дополнительных возможностей. -To access a property, we can use: -- The dot notation: `obj.property`. -- Square brackets notation `obj["property"]`. Square brackets allow to take the key from a variable, like `obj[varWithKey]`. +Они хранят свойства (пары ключ-значение), где: +- Ключи свойств должны быть строками или символами (обычно строками). +- Значения могут быть любого типа. -Additional operators: -- To delete a property: `delete obj.prop`. -- To check if a property with the given key exists: `"key" in obj`. -- To iterate over an object: `for (let key in obj)` loop. +Чтобы получить доступ к свойству, мы можем использовать: +- Запись через точку: `obj.property`. +- Квадратные скобки `obj["property"]`. Квадратные скобки позволяют взять ключ из переменной, например, `obj[varWithKey]`. -Objects are assigned and copied by reference. In other words, a variable stores not the "object value", but a "reference" (address in memory) for the value. So copying such a variable or passing it as a function argument copies that reference, not the object. All operations via copied references (like adding/removing properties) are performed on the same single object. +Дополнительные операторы: -To make a "real copy" (a clone) we can use `Object.assign` or [_.cloneDeep(obj)](https://lodash.com/docs#cloneDeep). +- Удаление свойства: `delete obj.prop`. +- Проверка существования свойства: `"key" in obj`. +- Перебор свойств объекта: цикл for `for (let key in obj)`. -What we've studied in this chapter is called a "plain object", or just `Object`. +То, что мы изучали в этой главе, называется "простым объектом" ("plain object") или просто `Object`. -There are many other kinds of objects in JavaScript: +В JavaScript есть много других типов объектов: -- `Array` to store ordered data collections, -- `Date` to store the information about the date and time, -- `Error` to store the information about an error. -- ...And so on. +- `Array` для хранения упорядоченных коллекций данных, +- `Date` для хранения информации о дате и времени, +- `Error` для хранения информации об ошибке. +- ... и так далее. -They have their special features that we'll study later. Sometimes people say something like "Array type" or "Date type", but formally they are not types of their own, but belong to a single "object" data type. And they extend it in various ways. +У них есть свои особенности, которые мы изучим позже. Иногда люди говорят что-то вроде "тип данных Array" или "тип данных Date", но формально они не являются отдельными типами, а относятся к типу данных `Object`. Они лишь расширяют его различными способами. -Objects in JavaScript are very powerful. Here we've just scratched the surface of a topic that is really huge. We'll be closely working with objects and learning more about them in further parts of the tutorial. +Объекты в JavaScript очень мощные. Здесь мы только немного углубились в действительно огромную тему. Мы будем плотно работать с объектами и узнаем о них больше в следующих частях учебника. diff --git a/1-js/04-object-basics/01-object/object-user-delete.png b/1-js/04-object-basics/01-object/object-user-delete.png deleted file mode 100644 index 8702675c81..0000000000 Binary files a/1-js/04-object-basics/01-object/object-user-delete.png and /dev/null differ diff --git a/1-js/04-object-basics/01-object/object-user-delete.svg b/1-js/04-object-basics/01-object/object-user-delete.svg new file mode 100644 index 0000000000..c5af7e7afc --- /dev/null +++ b/1-js/04-object-basics/01-object/object-user-delete.svg @@ -0,0 +1 @@ +nameisAdminuser \ No newline at end of file diff --git a/1-js/04-object-basics/01-object/object-user-delete@2x.png b/1-js/04-object-basics/01-object/object-user-delete@2x.png deleted file mode 100644 index 698766bb0a..0000000000 Binary files a/1-js/04-object-basics/01-object/object-user-delete@2x.png and /dev/null differ diff --git a/1-js/04-object-basics/01-object/object-user-empty.png b/1-js/04-object-basics/01-object/object-user-empty.png deleted file mode 100644 index 6b1f27a690..0000000000 Binary files a/1-js/04-object-basics/01-object/object-user-empty.png and /dev/null differ diff --git a/1-js/04-object-basics/01-object/object-user-empty.svg b/1-js/04-object-basics/01-object/object-user-empty.svg new file mode 100644 index 0000000000..edde844cf5 --- /dev/null +++ b/1-js/04-object-basics/01-object/object-user-empty.svg @@ -0,0 +1 @@ +пустоuser \ No newline at end of file diff --git a/1-js/04-object-basics/01-object/object-user-empty@2x.png b/1-js/04-object-basics/01-object/object-user-empty@2x.png deleted file mode 100644 index 5f261eca41..0000000000 Binary files a/1-js/04-object-basics/01-object/object-user-empty@2x.png and /dev/null differ diff --git a/1-js/04-object-basics/01-object/object-user-isadmin.png b/1-js/04-object-basics/01-object/object-user-isadmin.png deleted file mode 100644 index 2ce66a49d3..0000000000 Binary files a/1-js/04-object-basics/01-object/object-user-isadmin.png and /dev/null differ diff --git a/1-js/04-object-basics/01-object/object-user-isadmin.svg b/1-js/04-object-basics/01-object/object-user-isadmin.svg new file mode 100644 index 0000000000..e2cc0eaf15 --- /dev/null +++ b/1-js/04-object-basics/01-object/object-user-isadmin.svg @@ -0,0 +1 @@ +nameageisAdminuser \ No newline at end of file diff --git a/1-js/04-object-basics/01-object/object-user-isadmin@2x.png b/1-js/04-object-basics/01-object/object-user-isadmin@2x.png deleted file mode 100644 index 4a15dac64b..0000000000 Binary files a/1-js/04-object-basics/01-object/object-user-isadmin@2x.png and /dev/null differ diff --git a/1-js/04-object-basics/01-object/object-user-props.png b/1-js/04-object-basics/01-object/object-user-props.png deleted file mode 100644 index b0486e900e..0000000000 Binary files a/1-js/04-object-basics/01-object/object-user-props.png and /dev/null differ diff --git a/1-js/04-object-basics/01-object/object-user-props.svg b/1-js/04-object-basics/01-object/object-user-props.svg new file mode 100644 index 0000000000..b3d5c9b71b --- /dev/null +++ b/1-js/04-object-basics/01-object/object-user-props.svg @@ -0,0 +1 @@ +nameagelikes birdsuser \ No newline at end of file diff --git a/1-js/04-object-basics/01-object/object-user-props@2x.png b/1-js/04-object-basics/01-object/object-user-props@2x.png deleted file mode 100644 index 20859fe911..0000000000 Binary files a/1-js/04-object-basics/01-object/object-user-props@2x.png and /dev/null differ diff --git a/1-js/04-object-basics/01-object/object-user.png b/1-js/04-object-basics/01-object/object-user.png deleted file mode 100644 index 6215b82079..0000000000 Binary files a/1-js/04-object-basics/01-object/object-user.png and /dev/null differ diff --git a/1-js/04-object-basics/01-object/object-user.svg b/1-js/04-object-basics/01-object/object-user.svg new file mode 100644 index 0000000000..f499fbc10f --- /dev/null +++ b/1-js/04-object-basics/01-object/object-user.svg @@ -0,0 +1 @@ +nameageuser \ No newline at end of file diff --git a/1-js/04-object-basics/01-object/object-user@2x.png b/1-js/04-object-basics/01-object/object-user@2x.png deleted file mode 100644 index c66fa5159b..0000000000 Binary files a/1-js/04-object-basics/01-object/object-user@2x.png and /dev/null differ diff --git a/1-js/04-object-basics/01-object/object.png b/1-js/04-object-basics/01-object/object.png deleted file mode 100644 index a853c9c393..0000000000 Binary files a/1-js/04-object-basics/01-object/object.png and /dev/null differ diff --git a/1-js/04-object-basics/01-object/object.svg b/1-js/04-object-basics/01-object/object.svg new file mode 100644 index 0000000000..47431a6e1f --- /dev/null +++ b/1-js/04-object-basics/01-object/object.svg @@ -0,0 +1 @@ +key1key2key3 \ No newline at end of file diff --git a/1-js/04-object-basics/01-object/object@2x.png b/1-js/04-object-basics/01-object/object@2x.png deleted file mode 100644 index 12011ff5c7..0000000000 Binary files a/1-js/04-object-basics/01-object/object@2x.png and /dev/null differ diff --git a/1-js/04-object-basics/01-object/variable-contains-reference.png b/1-js/04-object-basics/01-object/variable-contains-reference.png deleted file mode 100644 index cdd53d0b2d..0000000000 Binary files a/1-js/04-object-basics/01-object/variable-contains-reference.png and /dev/null differ diff --git a/1-js/04-object-basics/01-object/variable-contains-reference@2x.png b/1-js/04-object-basics/01-object/variable-contains-reference@2x.png deleted file mode 100644 index 0701261980..0000000000 Binary files a/1-js/04-object-basics/01-object/variable-contains-reference@2x.png and /dev/null differ diff --git a/1-js/04-object-basics/01-object/variable-copy-reference.png b/1-js/04-object-basics/01-object/variable-copy-reference.png deleted file mode 100644 index 2870858420..0000000000 Binary files a/1-js/04-object-basics/01-object/variable-copy-reference.png and /dev/null differ diff --git a/1-js/04-object-basics/01-object/variable-copy-reference@2x.png b/1-js/04-object-basics/01-object/variable-copy-reference@2x.png deleted file mode 100644 index e7b994c534..0000000000 Binary files a/1-js/04-object-basics/01-object/variable-copy-reference@2x.png and /dev/null differ diff --git a/1-js/04-object-basics/01-object/variable-copy-value.png b/1-js/04-object-basics/01-object/variable-copy-value.png deleted file mode 100644 index c360a0e132..0000000000 Binary files a/1-js/04-object-basics/01-object/variable-copy-value.png and /dev/null differ diff --git a/1-js/04-object-basics/01-object/variable-copy-value@2x.png b/1-js/04-object-basics/01-object/variable-copy-value@2x.png deleted file mode 100644 index 323bc46224..0000000000 Binary files a/1-js/04-object-basics/01-object/variable-copy-value@2x.png and /dev/null differ diff --git a/1-js/04-object-basics/02-garbage-collection/article.md b/1-js/04-object-basics/02-garbage-collection/article.md deleted file mode 100644 index 27682ef40b..0000000000 --- a/1-js/04-object-basics/02-garbage-collection/article.md +++ /dev/null @@ -1,212 +0,0 @@ -# Garbage collection - -Memory management in JavaScript is performed automatically and invisibly to us. We create primitives, objects, functions... All that takes memory. - -What happens when something is not needed any more? How does the JavaScript engine discover it and clean it up? - -## Reachability - -The main concept of memory management in JavaScript is *reachability*. - -Simply put, "reachable" values are those that are accessible or usable somehow. They are guaranteed to be stored in memory. - -1. There's a base set of inherently reachable values, that cannot be deleted for obvious reasons. - - For instance: - - - Local variables and parameters of the current function. - - Variables and parameters for other functions on the current chain of nested calls. - - Global variables. - - (there are some other, internal ones as well) - - These values are called *roots*. - -2. Any other value is considered reachable if it's reachable from a root by a reference or by a chain of references. - - For instance, if there's an object in a local variable, and that object has a property referencing another object, that object is considered reachable. And those that it references are also reachable. Detailed examples to follow. - -There's a background process in the JavaScript engine that is called [garbage collector](https://en.wikipedia.org/wiki/Garbage_collection_(computer_science)). It monitors all objects and removes those that have become unreachable. - -## A simple example - -Here's the simplest example: - -```js -// user has a reference to the object -let user = { - name: "John" -}; -``` - -![](memory-user-john.png) - -Here the arrow depicts an object reference. The global variable `"user"` references the object `{name: "John"}` (we'll call it John for brevity). The `"name"` property of John stores a primitive, so it's painted inside the object. - -If the value of `user` is overwritten, the reference is lost: - -```js -user = null; -``` - -![](memory-user-john-lost.png) - -Now John becomes unreachable. There's no way to access it, no references to it. Garbage collector will junk the data and free the memory. - -## Two references - -Now let's imagine we copied the reference from `user` to `admin`: - -```js -// user has a reference to the object -let user = { - name: "John" -}; - -*!* -let admin = user; -*/!* -``` - -![](memory-user-john-admin.png) - -Now if we do the same: -```js -user = null; -``` - -...Then the object is still reachable via `admin` global variable, so it's in memory. If we overwrite `admin` too, then it can be removed. - -## Interlinked objects - -Now a more complex example. The family: - -```js -function marry(man, woman) { - woman.husband = man; - man.wife = woman; - - return { - father: man, - mother: woman - } -} - -let family = marry({ - name: "John" -}, { - name: "Ann" -}); -``` - -Function `marry` "marries" two objects by giving them references to each other and returns a new object that contains them both. - -The resulting memory structure: - -![](family.png) - -As of now, all objects are reachable. - -Now let's remove two references: - -```js -delete family.father; -delete family.mother.husband; -``` - -![](family-delete-refs.png) - -It's not enough to delete only one of these two references, because all objects would still be reachable. - -But if we delete both, then we can see that John has no incoming reference any more: - -![](family-no-father.png) - -Outgoing references do not matter. Only incoming ones can make an object reachable. So, John is now unreachable and will be removed from the memory with all its data that also became unaccessible. - -After garbage collection: - -![](family-no-father-2.png) - -## Unreachable island - -It is possible that the whole island of interlinked objects becomes unreachable and is removed from the memory. - -The source object is the same as above. Then: - -```js -family = null; -``` - -The in-memory picture becomes: - -![](family-no-family.png) - -This example demonstrates how important the concept of reachability is. - -It's obvious that John and Ann are still linked, both have incoming references. But that's not enough. - -The former `"family"` object has been unlinked from the root, there's no reference to it any more, so the whole island becomes unreachable and will be removed. - -## Internal algorithms - -The basic garbage collection algorithm is called "mark-and-sweep". - -The following "garbage collection" steps are regularly performed: - -- The garbage collector takes roots and "marks" (remembers) them. -- Then it visits and "marks" all references from them. -- Then it visits marked objects and marks *their* references. All visited objects are remembered, so as not to visit the same object twice in the future. -- ...And so on until there are unvisited references (reachable from the roots). -- All objects except marked ones are removed. - -For instance, let our object structure look like this: - -![](garbage-collection-1.png) - -We can clearly see an "unreachable island" to the right side. Now let's see how "mark-and-sweep" garbage collector deals with it. - -The first step marks the roots: - -![](garbage-collection-2.png) - -Then their references are marked: - -![](garbage-collection-3.png) - -...And their references, while possible: - -![](garbage-collection-4.png) - -Now the objects that could not be visited in the process are considered unreachable and will be removed: - -![](garbage-collection-5.png) - -That's the concept of how garbage collection works. - -JavaScript engines apply many optimizations to make it run faster and not affect the execution. - -Some of the optimizations: - -- **Generational collection** -- objects are split into two sets: "new ones" and "old ones". Many objects appear, do their job and die fast, they can be cleaned up aggressively. Those that survive for long enough, become "old" and are examined less often. -- **Incremental collection** -- if there are many objects, and we try to walk and mark the whole object set at once, it may take some time and introduce visible delays in the execution. So the engine tries to split the garbage collection into pieces. Then the pieces are executed one by one, separately. That requires some extra bookkeeping between them to track changes, but we have many tiny delays instead of a big one. -- **Idle-time collection** -- the garbage collector tries to run only while the CPU is idle, to reduce the possible effect on the execution. - -There are other optimizations and flavours of garbage collection algorithms. As much as I'd like to describe them here, I have to hold off, because different engines implement different tweaks and techniques. And, what's even more important, things change as engines develop, so going deeper "in advance", without a real need is probably not worth that. Unless, of course, it is a matter of pure interest, then there will be some links for you below. - -## Summary - -The main things to know: - -- Garbage collection is performed automatically. We cannot force or prevent it. -- Objects are retained in memory while they are reachable. -- Being referenced is not the same as being reachable (from a root): a pack of interlinked objects can become unreachable as a whole. - -Modern engines implement advanced algorithms of garbage collection. - -A general book "The Garbage Collection Handbook: The Art of Automatic Memory Management" (R. Jones et al) covers some of them. - -If you are familiar with low-level programming, the more detailed information about V8 garbage collector is in the article [A tour of V8: Garbage Collection](http://jayconrod.com/posts/55/a-tour-of-v8-garbage-collection). - -[V8 blog](http://v8project.blogspot.com/) also publishes articles about changes in memory management from time to time. Naturally, to learn the garbage collection, you'd better prepare by learning about V8 internals in general and read the blog of [Vyacheslav Egorov](http://mrale.ph) who worked as one of V8 engineers. I'm saying: "V8", because it is best covered with articles in the internet. For other engines, many approaches are similar, but garbage collection differs in many aspects. - -In-depth knowledge of engines is good when you need low-level optimizations. It would be wise to plan that as the next step after you're familiar with the language. diff --git a/1-js/04-object-basics/02-garbage-collection/family-delete-refs.png b/1-js/04-object-basics/02-garbage-collection/family-delete-refs.png deleted file mode 100644 index 05160890e2..0000000000 Binary files a/1-js/04-object-basics/02-garbage-collection/family-delete-refs.png and /dev/null differ diff --git a/1-js/04-object-basics/02-garbage-collection/family-delete-refs@2x.png b/1-js/04-object-basics/02-garbage-collection/family-delete-refs@2x.png deleted file mode 100644 index a92fc573b6..0000000000 Binary files a/1-js/04-object-basics/02-garbage-collection/family-delete-refs@2x.png and /dev/null differ diff --git a/1-js/04-object-basics/02-garbage-collection/family-no-family.png b/1-js/04-object-basics/02-garbage-collection/family-no-family.png deleted file mode 100644 index e60f9b88d8..0000000000 Binary files a/1-js/04-object-basics/02-garbage-collection/family-no-family.png and /dev/null differ diff --git a/1-js/04-object-basics/02-garbage-collection/family-no-family@2x.png b/1-js/04-object-basics/02-garbage-collection/family-no-family@2x.png deleted file mode 100644 index 7549037060..0000000000 Binary files a/1-js/04-object-basics/02-garbage-collection/family-no-family@2x.png and /dev/null differ diff --git a/1-js/04-object-basics/02-garbage-collection/family-no-father-2.png b/1-js/04-object-basics/02-garbage-collection/family-no-father-2.png deleted file mode 100644 index 823d0a618c..0000000000 Binary files a/1-js/04-object-basics/02-garbage-collection/family-no-father-2.png and /dev/null differ diff --git a/1-js/04-object-basics/02-garbage-collection/family-no-father-2@2x.png b/1-js/04-object-basics/02-garbage-collection/family-no-father-2@2x.png deleted file mode 100644 index 9e100d522a..0000000000 Binary files a/1-js/04-object-basics/02-garbage-collection/family-no-father-2@2x.png and /dev/null differ diff --git a/1-js/04-object-basics/02-garbage-collection/family-no-father.png b/1-js/04-object-basics/02-garbage-collection/family-no-father.png deleted file mode 100644 index 5ddb6f68ff..0000000000 Binary files a/1-js/04-object-basics/02-garbage-collection/family-no-father.png and /dev/null differ diff --git a/1-js/04-object-basics/02-garbage-collection/family-no-father@2x.png b/1-js/04-object-basics/02-garbage-collection/family-no-father@2x.png deleted file mode 100644 index 868c748a06..0000000000 Binary files a/1-js/04-object-basics/02-garbage-collection/family-no-father@2x.png and /dev/null differ diff --git a/1-js/04-object-basics/02-garbage-collection/family.png b/1-js/04-object-basics/02-garbage-collection/family.png deleted file mode 100644 index f37464e6c5..0000000000 Binary files a/1-js/04-object-basics/02-garbage-collection/family.png and /dev/null differ diff --git a/1-js/04-object-basics/02-garbage-collection/family@2x.png b/1-js/04-object-basics/02-garbage-collection/family@2x.png deleted file mode 100644 index 4f5d47210d..0000000000 Binary files a/1-js/04-object-basics/02-garbage-collection/family@2x.png and /dev/null differ diff --git a/1-js/04-object-basics/02-garbage-collection/garbage-collection-1.png b/1-js/04-object-basics/02-garbage-collection/garbage-collection-1.png deleted file mode 100644 index 5cfe664c35..0000000000 Binary files a/1-js/04-object-basics/02-garbage-collection/garbage-collection-1.png and /dev/null differ diff --git a/1-js/04-object-basics/02-garbage-collection/garbage-collection-1@2x.png b/1-js/04-object-basics/02-garbage-collection/garbage-collection-1@2x.png deleted file mode 100644 index cf93a1605e..0000000000 Binary files a/1-js/04-object-basics/02-garbage-collection/garbage-collection-1@2x.png and /dev/null differ diff --git a/1-js/04-object-basics/02-garbage-collection/garbage-collection-2.png b/1-js/04-object-basics/02-garbage-collection/garbage-collection-2.png deleted file mode 100644 index 2bbe4241d4..0000000000 Binary files a/1-js/04-object-basics/02-garbage-collection/garbage-collection-2.png and /dev/null differ diff --git a/1-js/04-object-basics/02-garbage-collection/garbage-collection-2@2x.png b/1-js/04-object-basics/02-garbage-collection/garbage-collection-2@2x.png deleted file mode 100644 index f4a7abb526..0000000000 Binary files a/1-js/04-object-basics/02-garbage-collection/garbage-collection-2@2x.png and /dev/null differ diff --git a/1-js/04-object-basics/02-garbage-collection/garbage-collection-3.png b/1-js/04-object-basics/02-garbage-collection/garbage-collection-3.png deleted file mode 100644 index 665a22784f..0000000000 Binary files a/1-js/04-object-basics/02-garbage-collection/garbage-collection-3.png and /dev/null differ diff --git a/1-js/04-object-basics/02-garbage-collection/garbage-collection-3@2x.png b/1-js/04-object-basics/02-garbage-collection/garbage-collection-3@2x.png deleted file mode 100644 index 60d4059cca..0000000000 Binary files a/1-js/04-object-basics/02-garbage-collection/garbage-collection-3@2x.png and /dev/null differ diff --git a/1-js/04-object-basics/02-garbage-collection/garbage-collection-4.png b/1-js/04-object-basics/02-garbage-collection/garbage-collection-4.png deleted file mode 100644 index 4ba6e17ecc..0000000000 Binary files a/1-js/04-object-basics/02-garbage-collection/garbage-collection-4.png and /dev/null differ diff --git a/1-js/04-object-basics/02-garbage-collection/garbage-collection-4@2x.png b/1-js/04-object-basics/02-garbage-collection/garbage-collection-4@2x.png deleted file mode 100644 index 8ac09c80d6..0000000000 Binary files a/1-js/04-object-basics/02-garbage-collection/garbage-collection-4@2x.png and /dev/null differ diff --git a/1-js/04-object-basics/02-garbage-collection/garbage-collection-5.png b/1-js/04-object-basics/02-garbage-collection/garbage-collection-5.png deleted file mode 100644 index 35c8816dcb..0000000000 Binary files a/1-js/04-object-basics/02-garbage-collection/garbage-collection-5.png and /dev/null differ diff --git a/1-js/04-object-basics/02-garbage-collection/garbage-collection-5@2x.png b/1-js/04-object-basics/02-garbage-collection/garbage-collection-5@2x.png deleted file mode 100644 index 4db0c1732e..0000000000 Binary files a/1-js/04-object-basics/02-garbage-collection/garbage-collection-5@2x.png and /dev/null differ diff --git a/1-js/04-object-basics/02-garbage-collection/garbage-collection.png b/1-js/04-object-basics/02-garbage-collection/garbage-collection.png deleted file mode 100644 index 013d0a48a4..0000000000 Binary files a/1-js/04-object-basics/02-garbage-collection/garbage-collection.png and /dev/null differ diff --git a/1-js/04-object-basics/02-garbage-collection/garbage-collection@2x.png b/1-js/04-object-basics/02-garbage-collection/garbage-collection@2x.png deleted file mode 100644 index 681f29e4bd..0000000000 Binary files a/1-js/04-object-basics/02-garbage-collection/garbage-collection@2x.png and /dev/null differ diff --git a/1-js/04-object-basics/02-garbage-collection/memory-user-john-admin.png b/1-js/04-object-basics/02-garbage-collection/memory-user-john-admin.png deleted file mode 100644 index 9ddac3e299..0000000000 Binary files a/1-js/04-object-basics/02-garbage-collection/memory-user-john-admin.png and /dev/null differ diff --git a/1-js/04-object-basics/02-garbage-collection/memory-user-john-admin@2x.png b/1-js/04-object-basics/02-garbage-collection/memory-user-john-admin@2x.png deleted file mode 100644 index 9069781f24..0000000000 Binary files a/1-js/04-object-basics/02-garbage-collection/memory-user-john-admin@2x.png and /dev/null differ diff --git a/1-js/04-object-basics/02-garbage-collection/memory-user-john-lost.png b/1-js/04-object-basics/02-garbage-collection/memory-user-john-lost.png deleted file mode 100644 index ae1684b2d2..0000000000 Binary files a/1-js/04-object-basics/02-garbage-collection/memory-user-john-lost.png and /dev/null differ diff --git a/1-js/04-object-basics/02-garbage-collection/memory-user-john-lost@2x.png b/1-js/04-object-basics/02-garbage-collection/memory-user-john-lost@2x.png deleted file mode 100644 index c510380f9e..0000000000 Binary files a/1-js/04-object-basics/02-garbage-collection/memory-user-john-lost@2x.png and /dev/null differ diff --git a/1-js/04-object-basics/02-garbage-collection/memory-user-john.png b/1-js/04-object-basics/02-garbage-collection/memory-user-john.png deleted file mode 100644 index 2ad00b6cb1..0000000000 Binary files a/1-js/04-object-basics/02-garbage-collection/memory-user-john.png and /dev/null differ diff --git a/1-js/04-object-basics/02-garbage-collection/memory-user-john@2x.png b/1-js/04-object-basics/02-garbage-collection/memory-user-john@2x.png deleted file mode 100644 index f365ac0365..0000000000 Binary files a/1-js/04-object-basics/02-garbage-collection/memory-user-john@2x.png and /dev/null differ diff --git a/1-js/04-object-basics/02-object-copy/article.md b/1-js/04-object-basics/02-object-copy/article.md new file mode 100644 index 0000000000..e648d34098 --- /dev/null +++ b/1-js/04-object-basics/02-object-copy/article.md @@ -0,0 +1,265 @@ + +# Копирование объектов и ссылки + +Одно из фундаментальных отличий объектов от примитивов заключается в том, что объекты хранятся и копируются "по ссылке", тогда как примитивные значения: строки, числа, логические значения и т.д. – всегда копируются "как целое значение". + +Это легко понять, если мы немного заглянем под капот того, что происходит, когда мы копируем значение. + +Давайте начнём с примитива, такого как строка. + +Здесь мы помещаем копию `message` во `phrase`: + +```js +let message = "Привет!"; +let phrase = message; +``` + +В результате мы имеем две независимые переменные, каждая из которых хранит строку `"Привет!"`. + +![](variable-copy-value.svg) + +Вполне очевидный результат, не так ли? + +Объекты ведут себя иначе. + +**Переменная, которой присвоен объект, хранит не сам объект, а его "адрес в памяти" – другими словами, "ссылку" на него.** + +Давайте рассмотрим пример такой переменной: + +```js +let user = { + name: "John" +}; +``` + +И вот как это на самом деле хранится в памяти: + +![](variable-contains-reference.svg) + +Объект хранится где-то в памяти (справа от изображения), в то время как переменная `user` (слева) имеет лишь "ссылку" на него. + +Мы можем думать о переменной объекта, такой как `user`, как о листе бумаги с адресом объекта на нем. + +Когда мы выполняем действия с объектом, к примеру, берём свойство `user.name `, движок JavaScript просматривает то, что находится по этому адресу, и выполняет операцию с самим объектом. + +Теперь вот почему это важно. + +**При копировании переменной объекта копируется ссылка, но сам объект не дублируется.** + +Например: + +```js no-beautify +let user = { name: "John" }; + +let admin = user; // копируется ссылка +``` + +Теперь у нас есть две переменные, каждая из которых содержит ссылку на один и тот же объект: + +![](variable-copy-reference.svg) + +Как вы можете видеть, все ещё есть один объект, но теперь с двумя переменными, которые ссылаются на него. + +Мы можем использовать любую переменную для доступа к объекту и изменения его содержимого: + +```js run +let user = { name: 'John' }; + +let admin = user; + +*!* +admin.name = 'Pete'; // изменено по ссылке из переменной "admin" +*/!* + +alert(*!*user.name*/!*); // 'Pete', изменения видны по ссылке из переменной "user" +``` + +Это как если бы у нас был шкафчик с двумя ключами, и мы использовали один из них (`admin`), чтобы войти в него и внести изменения. А затем, если мы позже используем другой ключ (`user`), мы все равно открываем тот же шкафчик и можем получить доступ к изменённому содержимому. + +## Сравнение по ссылке + +Два объекта равны только в том случае, если это один и тот же объект. + +Например, здесь `a` и `b` ссылаются на один и тот же объект, поэтому они равны: + +```js run +let a = {}; +let b = a; // копирование по ссылке + +alert( a == b ); // true, обе переменные ссылаются на один и тот же объект +alert( a === b ); // true +``` + +И здесь два независимых объекта не равны, даже если они выглядят одинаково (оба пусты): + +```js run +let a = {}; +let b = {}; // два независимых объекта + +alert( a == b ); // false +``` + +Для сравнений типа `obj1 > obj2` или для сравнения с примитивом `obj == 5` объекты преобразуются в примитивы. Очень скоро мы изучим, как работают преобразования объектов, но, по правде говоря, такие сравнения требуются очень редко и обычно они появляются в результате ошибок программиста. + +## Клонирование и объединение, Object.assign [#cloning-and-merging-object-assign] + +Итак, копирование объектной переменной создаёт ещё одну ссылку на тот же объект. + +Но что, если нам всё же нужно дублировать объект? Создать независимую копию, клон? + +Это тоже выполнимо, но немного сложнее, потому что в JavaScript для этого нет встроенного метода. Но на самом деле в этом редко возникает необходимость, копирования по ссылке в большинстве случаев вполне хватает. + +Но если мы действительно этого хотим, то нам нужно создать новый объект и воспроизвести структуру существующего, перебрав его свойства и скопировав их на примитивном уровне. + +Например так: + +```js run +let user = { + name: "John", + age: 30 +}; + +*!* +let clone = {}; // новый пустой объект + +// давайте скопируем все свойства user в него +for (let key in user) { + clone[key] = user[key]; +} +*/!* + +// теперь clone это полностью независимый объект с тем же содержимым +clone.name = "Pete"; // изменим в нём данные + +alert( user.name ); // все ещё John в первоначальном объекте +``` + +Также мы можем использовать для этого метод [Object.assign](mdn:js/Object/assign). + +Синтаксис: + +```js +Object.assign(dest, [src1, src2, src3...]) +``` + +- Первый аргумент `dest` — целевой объект. +- Остальные аргументы `src1, ..., srcN` (может быть столько, сколько необходимо) являются исходными объектами +- Метод копирует свойства всех исходных объектов `src1, ..., srcN` в целевой объект `dest`. Другими словами, свойства всех аргументов, начиная со второго, копируются в первый объект. +- Возвращает объект `dest`. + +Например, мы можем использовать его для объединения нескольких объектов в один: +```js +let user = { name: "John" }; + +let permissions1 = { canView: true }; +let permissions2 = { canEdit: true }; + +*!* +// копируем все свойства из permissions1 и permissions2 в user +Object.assign(user, permissions1, permissions2); +*/!* + +// теперь user = { name: "John", canView: true, canEdit: true } +``` + +Если скопированное имя свойства уже существует, оно будет перезаписано: + +```js run +let user = { name: "John" }; + +Object.assign(user, { name: "Pete" }); + +alert(user.name); // теперь user = { name: "Pete" } +``` + +Мы также можем использовать `Object.assign` для замены цикла `for..in ` для простого клонирования: + +```js +let user = { + name: "John", + age: 30 +}; + +*!* +let clone = Object.assign({}, user); +*/!* +``` + +Он копирует все свойства `user` в пустой объект и возвращает его. + +Также существуют и другие методы клонирования объекта. Например, с использованием [оператора расширения](info:rest-parameters-spread-operator) `clone = {...user}`, рассмотренного далее в учебнике. + +## Вложенное клонирование + +До сих пор мы предполагали, что все свойства `user` примитивныe. Но свойства могут быть и ссылками на другие объекты. Что с ними делать? + +Например, есть объект: +```js run +let user = { + name: "John", + sizes: { + height: 182, + width: 50 + } +}; + +alert( user.sizes.height ); // 182 +``` + +Теперь недостаточно просто скопировать `clone.sizes = user.sizes`, потому что `user.sizes` - это объект, он будет скопирован по ссылке. Таким образом, `clone` и `user` будут иметь общий объект `sizes`: + +```js run +let user = { + name: "John", + sizes: { + height: 182, + width: 50 + } +}; + +let clone = Object.assign({}, user); + +alert( user.sizes === clone.sizes ); // true, тот же объект + +// user и clone обладают общим свойством sizes +user.sizes.width++; // изменяем свойства в первом объекте +alert(clone.sizes.width); // 51, видим результат в другом +``` + +Чтобы исправить это, мы должны использовать цикл клонирования, который проверяет каждое значение `user[key]` и, если это объект, тогда также копирует его структуру. Это называется "глубоким клонированием". + +Мы можем реализовать глубокое клонирование, используя рекурсию. Или, чтобы не изобретать велосипед заново, возьмите готовую реализацию, например [_.cloneDeep(obj)](https://lodash.com/docs#cloneDeep) из библиотеки JavaScript [lodash](https://lodash.com). + +Также мы можем использовать глобальный метод [structuredClone()](https://developer.mozilla.org/en-US/docs/Web/API/structuredClone), который позволяет сделать полную копию объекта. К сожалению он поддерживается только современными браузерами. [Здесь](https://caniuse.com/?search=structuredClone) можно ознакомиться с поддержкой этого метода. + +````smart header="Объекты, объявленные как константа, могут быть изменены" +Важным побочным эффектом хранения объектов в качестве ссылок является то, что объект, объявленный как `const`, *может* быть изменён. + +Например: + +```js run +const user = { + name: "John" +}; + +*!* +user.name = "Pete"; // (*) +*/!* + +alert(user.name); // Pete +``` + +Может показаться, что строка `(*)` вызовет ошибку, но, это не так. Значение `user` это константа, оно всегда должно ссылаться на один и тот же объект, но свойства этого объекта могут свободно изменяться. + +Другими словами, `const user` выдаст ошибку только в том случае, если мы попытаемся задать `user=...` в целом. + +Тем не менее, если нам действительно нужно создать постоянные свойства объекта, это тоже возможно, но с использованием совершенно других методов. Мы затронем это в главе . +```` + +## Итого + +Объекты присваиваются и копируются по ссылке. Другими словами, переменная хранит не "значение объекта", а "ссылку" (адрес в памяти) на это значение. Таким образом, копирование такой переменной или передача её в качестве аргумента функции копирует эту ссылку, а не сам объект. + +Все операции с использованием скопированных ссылок (например, добавление/удаление свойств) выполняются с одним и тем же объектом. + +Чтобы создать "реальную копию" (клон), мы можем использовать `Object.assign` для так называемой "поверхностной копии" (вложенные объекты копируются по ссылке) или функцию "глубокого клонирования", такую как [_.cloneDeep(obj)](https://lodash.com/docs#cloneDeep). diff --git a/1-js/04-object-basics/02-object-copy/variable-contains-reference.svg b/1-js/04-object-basics/02-object-copy/variable-contains-reference.svg new file mode 100644 index 0000000000..267f045785 --- /dev/null +++ b/1-js/04-object-basics/02-object-copy/variable-contains-reference.svg @@ -0,0 +1 @@ +username \ No newline at end of file diff --git a/1-js/04-object-basics/02-object-copy/variable-copy-reference.svg b/1-js/04-object-basics/02-object-copy/variable-copy-reference.svg new file mode 100644 index 0000000000..a847fb200e --- /dev/null +++ b/1-js/04-object-basics/02-object-copy/variable-copy-reference.svg @@ -0,0 +1 @@ +useradminname \ No newline at end of file diff --git a/1-js/04-object-basics/02-object-copy/variable-copy-value.svg b/1-js/04-object-basics/02-object-copy/variable-copy-value.svg new file mode 100644 index 0000000000..f12e7fc4c8 --- /dev/null +++ b/1-js/04-object-basics/02-object-copy/variable-copy-value.svg @@ -0,0 +1 @@ +"Привет!"message"Привет!"phrase \ No newline at end of file diff --git a/1-js/04-object-basics/03-garbage-collection/article.md b/1-js/04-object-basics/03-garbage-collection/article.md new file mode 100644 index 0000000000..4c627d2687 --- /dev/null +++ b/1-js/04-object-basics/03-garbage-collection/article.md @@ -0,0 +1,212 @@ +# Сборка мусора + +Управление памятью в JavaScript выполняется автоматически и незаметно. Мы создаём примитивы, объекты, функции... Всё это занимает память. + +Но что происходит, когда что-то больше не нужно? Как движок JavaScript обнаруживает, что пора очищать память? + +## Достижимость + +Основной концепцией управления памятью в JavaScript является принцип *достижимости*. + +Если упростить, то "достижимые" значения - это те, которые доступны или используются. Они гарантированно находятся в памяти. + +1. Существует базовое множество достижимых значений, которые не могут быть удалены. + + Например: + + - Выполняемая в данный момент функция, её локальные переменные и параметры. + - Другие функции в текущей цепочке вложенных вызовов, их локальные переменные и параметры. + - Глобальные переменные. + - (некоторые другие внутренние значения) + + Эти значения мы будем называть *корнями*. + +2. Любое другое значение считается достижимым, если оно доступно из корня по ссылке или по цепочке ссылок. + + Например, если в глобальной переменной есть объект, и он имеет свойство, в котором хранится ссылка на другой объект, то *этот* объект считается достижимым. И те, на которые он ссылается, тоже достижимы. Далее вы познакомитесь с подробными примерами на эту тему. + +В движке JavaScript есть фоновый процесс, который называется [сборщиком мусора](https://ru.wikipedia.org/wiki/Сборка_мусора). Он отслеживает все объекты и удаляет те, которые стали недоступными. + +## Простой пример + +Вот самый простой пример: + +```js +// в user находится ссылка на объект +let user = { + name: "John" +}; +``` + +![](memory-user-john.svg) + +Здесь стрелка обозначает ссылку на объект. Глобальная переменная `user` ссылается на объект `{name: "John"}` (мы будем называть его просто "John" для краткости). В свойстве `"name"` объекта John хранится примитив, поэтому оно нарисовано внутри объекта. + +Если перезаписать значение `user`, то ссылка потеряется: + +```js +user = null; +``` + +![](memory-user-john-lost.svg) + +Теперь объект John становится недостижимым. К нему нет доступа, на него нет ссылок. Сборщик мусора удалит эти данные и освободит память. + +## Две ссылки + +Представим, что мы скопировали ссылку из `user` в `admin`: + +```js +// в user находится ссылка на объект +let user = { + name: "John" +}; + +*!* +let admin = user; +*/!* +``` + +![](memory-user-john-admin.svg) + +Теперь, если мы сделаем то же самое: +```js +user = null; +``` + +...то объект John всё ещё достижим через глобальную переменную `admin`, поэтому он находится в памяти. Если бы мы также перезаписали `admin`, то John был бы удалён. + +## Взаимосвязанные объекты + +Теперь более сложный пример. Семья: + +```js +function marry(man, woman) { + woman.husband = man; + man.wife = woman; + + return { + father: man, + mother: woman + } +} + +let family = marry({ + name: "John" +}, { + name: "Ann" +}); +``` + +Функция `marry` "женит" два объекта, давая им ссылки друг на друга, и возвращает новый объект, содержащий ссылки на два предыдущих. + +В результате получаем такую структуру памяти: + +![](family.svg) + +На данный момент все объекты достижимы. + +Теперь удалим две ссылки: + +```js +delete family.father; +delete family.mother.husband; +``` + +![](family-delete-refs.svg) + +Недостаточно удалить только одну из этих двух ссылок, потому что все объекты останутся достижимыми. + +Но если мы удалим обе, то увидим, что у объекта John больше нет входящих ссылок: + +![](family-no-father.svg) + +Исходящие ссылки не имеют значения. Только входящие ссылки могут сделать объект достижимым. Объект John теперь недостижим и будет удалён из памяти со всеми своими данными, которые также стали недоступны. + +После сборки мусора: + +![](family-no-father-2.svg) + +## Недостижимый "остров" + +Вполне возможна ситуация, при которой целый "остров" взаимосвязанных объектов может стать недостижимым и удалиться из памяти. + +Возьмём объект `family` из примера выше. А затем: + +```js +family = null; +``` + +Структура в памяти теперь станет такой: + +![](family-no-family.svg) + +Этот пример демонстрирует, насколько важна концепция достижимости. + +Объекты John и Ann всё ещё связаны, оба имеют входящие ссылки, но этого недостаточно. + +Бывший объект `family` был отсоединён от корня, на него больше нет ссылки, поэтому весь "остров" становится недостижимым и будет удалён. + +## Внутренние алгоритмы + +Основной алгоритм сборки мусора называется "алгоритм пометок" (от англ. "mark-and-sweep"). + +Согласно этому алгоритму, сборщик мусора регулярно выполняет следующие шаги: + +- Сборщик мусора "помечает" (запоминает) все корневые объекты. +- Затем он идёт по ним и "помечает" все ссылки из них. +- Затем он идёт по отмеченным объектам и отмечает их ссылки. Все посещённые объекты запоминаются, чтобы в будущем не посещать один и тот же объект дважды. +- ...И так далее, пока не будут посещены все достижимые (из корней) ссылки. +- Все непомеченные объекты удаляются. + +Например, пусть наша структура объектов выглядит так: + +![](garbage-collection-1.svg) + +Мы ясно видим "недостижимый остров" справа. Теперь давайте посмотрим, как будет работать "алгоритм пометок" сборщика мусора. + +На первом шаге помечаются корни: + +![](garbage-collection-2.svg) + +Затем помечаются объекты по их ссылкам: + +![](garbage-collection-3.svg) + +...А затем объекты по их ссылкам и так далее, пока это возможно: + +![](garbage-collection-4.svg) + +Теперь объекты, которые не удалось посетить в процессе, считаются недостижимыми и будут удалены: + +![](garbage-collection-5.svg) + +Мы также можем представить себе этот процесс как выливание огромного ведра краски из корней, которая течёт по всем ссылкам и отмечает все достижимые объекты. Затем непомеченные удаляются. + +Это концепция того, как работает сборка мусора. Движки JavaScript применяют множество оптимизаций, чтобы она работала быстрее и не задерживала выполнение кода. + +Вот некоторые из оптимизаций: + +- **Сборка по поколениям (Generational collection)** – объекты делятся на два набора: "новые" и "старые". В типичном коде многие объекты имеют короткую жизнь: они появляются, выполняют свою работу и быстро умирают, так что имеет смысл отслеживать новые объекты и, если это так, быстро очищать от них память. Те, которые выживают достаточно долго, становятся "старыми" и проверяются реже. +- **Инкрементальная сборка (Incremental collection)** – если объектов много, и мы пытаемся обойти и пометить весь набор объектов сразу, это может занять некоторое время и привести к видимым задержкам в выполнении скрипта. Так что движок делит всё множество объектов на части, и далее очищает их одну за другой. Получается несколько небольших сборок мусора вместо одной всеобщей. Это требует дополнительного учёта для отслеживания изменений между частями, но зато получается много крошечных задержек вместо одной большой. +- **Сборка в свободное время (Idle-time collection)** - чтобы уменьшить возможное влияние на производительность, сборщик мусора старается работать только во время простоя процессора. + +Существуют и другие способы оптимизации и разновидности алгоритмов сборки мусора. Но как бы мне ни хотелось описать их здесь, я должен воздержаться, потому что разные движки реализуют разные хитрости и методы. И, что ещё более важно, все меняется по мере развития движков, поэтому изучать тему глубоко "заранее", без реальной необходимости, вероятно, не стоит. Если, конечно, это не вопрос чистого интереса, тогда для вас будет несколько ссылок ниже. + +## Итого + +Главное, что нужно знать: + +- Сборка мусора выполняется автоматически. Мы не можем ускорить или предотвратить её. +- Объекты сохраняются в памяти, пока они достижимы. +- Если на объект есть ссылка - вовсе не факт, что он является достижимым (из корня): набор взаимосвязанных объектов может стать недоступен в целом, как мы видели в примере выше. + +Современные движки реализуют разные продвинутые алгоритмы сборки мусора. + +О многих из них рассказано в прекрасной книге о сборке мусора "The Garbage Collection Handbook: The Art of Automatic Memory Management" (R. Jones и др.). + +Если вы знакомы с низкоуровневым программированием, то более подробная информация о сборщике мусора V8 находится в статье [A tour of V8: Garbage Collection](https://jayconrod.com/posts/55/a-tour-of-v8-garbage-collection). + +Также в [блоге V8](https://v8.dev/) время от времени публикуются статьи об изменениях в управлении памятью. Разумеется, чтобы изучить сборку мусора, вам лучше подготовиться, узнав о том как устроен движок V8 внутри в целом и почитав блог [Вячеслава Егорова](https://mrale.ph), одного из инженеров, разрабатывавших V8. Я говорю про "V8", потому что он лучше всего освещается в статьях в Интернете. Для других движков многие подходы схожи, но сборка мусора отличается во многих аспектах. + +Глубокое понимание работы движков полезно, когда вам нужна низкоуровневая оптимизация. Было бы разумно запланировать их изучение как следующий шаг после того, как вы познакомитесь с языком. diff --git a/1-js/04-object-basics/03-garbage-collection/family-delete-refs.svg b/1-js/04-object-basics/03-garbage-collection/family-delete-refs.svg new file mode 100644 index 0000000000..a582ca64b7 --- /dev/null +++ b/1-js/04-object-basics/03-garbage-collection/family-delete-refs.svg @@ -0,0 +1 @@ +<global variable>ObjectObjectwifefamilyname: "John"name: "Ann"motherObjectfatherhusband \ No newline at end of file diff --git a/1-js/04-object-basics/03-garbage-collection/family-no-family.svg b/1-js/04-object-basics/03-garbage-collection/family-no-family.svg new file mode 100644 index 0000000000..c73dd6a48f --- /dev/null +++ b/1-js/04-object-basics/03-garbage-collection/family-no-family.svg @@ -0,0 +1 @@ +<global>ObjectObjectfatherwifename: "John"name: "Ann"motherObjecthusbandfamily: null \ No newline at end of file diff --git a/1-js/04-object-basics/03-garbage-collection/family-no-father-2.svg b/1-js/04-object-basics/03-garbage-collection/family-no-father-2.svg new file mode 100644 index 0000000000..6bd13c0e80 --- /dev/null +++ b/1-js/04-object-basics/03-garbage-collection/family-no-father-2.svg @@ -0,0 +1 @@ +Objectfamilyname: "Ann"motherObject<global> \ No newline at end of file diff --git a/1-js/04-object-basics/03-garbage-collection/family-no-father.svg b/1-js/04-object-basics/03-garbage-collection/family-no-father.svg new file mode 100644 index 0000000000..fd1f206073 --- /dev/null +++ b/1-js/04-object-basics/03-garbage-collection/family-no-father.svg @@ -0,0 +1 @@ +ObjectObjectwifefamilyname: "John"name: "Ann"motherObject<global> \ No newline at end of file diff --git a/1-js/04-object-basics/03-garbage-collection/family.svg b/1-js/04-object-basics/03-garbage-collection/family.svg new file mode 100644 index 0000000000..fd05348740 --- /dev/null +++ b/1-js/04-object-basics/03-garbage-collection/family.svg @@ -0,0 +1 @@ +ObjectObjectfatherwifefamilyname: "John"name: "Ann"motherObjecthusband<global variable> \ No newline at end of file diff --git a/1-js/04-object-basics/03-garbage-collection/garbage-collection-1.svg b/1-js/04-object-basics/03-garbage-collection/garbage-collection-1.svg new file mode 100644 index 0000000000..5cac52e9a9 --- /dev/null +++ b/1-js/04-object-basics/03-garbage-collection/garbage-collection-1.svg @@ -0,0 +1 @@ +<global> \ No newline at end of file diff --git a/1-js/04-object-basics/03-garbage-collection/garbage-collection-2.svg b/1-js/04-object-basics/03-garbage-collection/garbage-collection-2.svg new file mode 100644 index 0000000000..7dd3a693a8 --- /dev/null +++ b/1-js/04-object-basics/03-garbage-collection/garbage-collection-2.svg @@ -0,0 +1 @@ +<global> \ No newline at end of file diff --git a/1-js/04-object-basics/03-garbage-collection/garbage-collection-3.svg b/1-js/04-object-basics/03-garbage-collection/garbage-collection-3.svg new file mode 100644 index 0000000000..1060577874 --- /dev/null +++ b/1-js/04-object-basics/03-garbage-collection/garbage-collection-3.svg @@ -0,0 +1 @@ +<global> \ No newline at end of file diff --git a/1-js/04-object-basics/03-garbage-collection/garbage-collection-4.svg b/1-js/04-object-basics/03-garbage-collection/garbage-collection-4.svg new file mode 100644 index 0000000000..bd485adeeb --- /dev/null +++ b/1-js/04-object-basics/03-garbage-collection/garbage-collection-4.svg @@ -0,0 +1 @@ +<global> \ No newline at end of file diff --git a/1-js/04-object-basics/03-garbage-collection/garbage-collection-5.svg b/1-js/04-object-basics/03-garbage-collection/garbage-collection-5.svg new file mode 100644 index 0000000000..6de756f7cf --- /dev/null +++ b/1-js/04-object-basics/03-garbage-collection/garbage-collection-5.svg @@ -0,0 +1 @@ +<global>недостижимые \ No newline at end of file diff --git a/1-js/04-object-basics/03-garbage-collection/memory-user-john-admin.svg b/1-js/04-object-basics/03-garbage-collection/memory-user-john-admin.svg new file mode 100644 index 0000000000..1913243548 --- /dev/null +++ b/1-js/04-object-basics/03-garbage-collection/memory-user-john-admin.svg @@ -0,0 +1 @@ +username: "John"Objectadmin<global> \ No newline at end of file diff --git a/1-js/04-object-basics/03-garbage-collection/memory-user-john-lost.svg b/1-js/04-object-basics/03-garbage-collection/memory-user-john-lost.svg new file mode 100644 index 0000000000..07914a9ca0 --- /dev/null +++ b/1-js/04-object-basics/03-garbage-collection/memory-user-john-lost.svg @@ -0,0 +1 @@ +name: "John"Objectuser: null<global> \ No newline at end of file diff --git a/1-js/04-object-basics/03-garbage-collection/memory-user-john.svg b/1-js/04-object-basics/03-garbage-collection/memory-user-john.svg new file mode 100644 index 0000000000..15bd51afb1 --- /dev/null +++ b/1-js/04-object-basics/03-garbage-collection/memory-user-john.svg @@ -0,0 +1 @@ +username: "John"Object<global> \ No newline at end of file diff --git a/1-js/04-object-basics/03-symbol/article.md b/1-js/04-object-basics/03-symbol/article.md deleted file mode 100644 index 8323d66431..0000000000 --- a/1-js/04-object-basics/03-symbol/article.md +++ /dev/null @@ -1,282 +0,0 @@ - -# Symbol type - -By specification, object property keys may be either of string type, or of symbol type. Not numbers, not booleans, only strings or symbols, these two types. - -Till now we've only seen strings. Now let's see the advantages that symbols can give us. - -## Symbols - -"Symbol" value represents a unique identifier. - -A value of this type can be created using `Symbol()`: - -```js -// id is a new symbol -let id = Symbol(); -``` - -We can also give symbol a description (also called a symbol name), mostly useful for debugging purposes: - -```js run -// id is a symbol with the description "id" -let id = Symbol("id"); -``` - -Symbols are guaranteed to be unique. Even if we create many symbols with the same description, they are different values. The description is just a label that doesn't affect anything. - -For instance, here are two symbols with the same description -- they are not equal: - -```js run -let id1 = Symbol("id"); -let id2 = Symbol("id"); - -*!* -alert(id1 == id2); // false -*/!* -``` - -If you are familiar with Ruby or another language that also has some sort of "symbols" -- please don't be misguided. JavaScript symbols are different. - -````warn header="Symbols don't auto-convert to a string" -Most values in JavaScript support implicit conversion to a string. For instance, we can `alert` almost any value, and it will work. Symbols are special. They don't auto-convert. - -For instance, this `alert` will show an error: - -```js run -let id = Symbol("id"); -*!* -alert(id); // TypeError: Cannot convert a Symbol value to a string -*/!* -``` - -That's a "language guard" against messing up, because strings and symbols are fundamentally different and should not occasionally convert one into another. - -If we really want to show a symbol, we need to call `.toString()` on it, like here: -```js run -let id = Symbol("id"); -*!* -alert(id.toString()); // Symbol(id), now it works -*/!* -``` - -Or get `symbol.description` property to get the description only: -```js run -let id = Symbol("id"); -*!* -alert(id.description); // id -*/!* -``` - -```` - -## "Hidden" properties - -Symbols allow us to create "hidden" properties of an object, that no other part of code can occasionally access or overwrite. - -For instance, if we want to store an "identifier" for the object `user`, we can use a symbol as a key for it: - -```js run -let user = { name: "John" }; -let id = Symbol("id"); - -user[id] = "ID Value"; -alert( user[id] ); // we can access the data using the symbol as the key -``` - -What's the benefit of using `Symbol("id")` over a string `"id"`? - -Let's make the example a bit deeper to see that. - -Imagine that another script wants to have its own "id" property inside `user`, for its own purposes. That may be another JavaScript library, so the scripts are completely unaware of each other. - -Then that script can create its own `Symbol("id")`, like this: - -```js -// ... -let id = Symbol("id"); - -user[id] = "Their id value"; -``` - -There will be no conflict, because symbols are always different, even if they have the same name. - -Now note that if we used a string `"id"` instead of a symbol for the same purpose, then there *would* be a conflict: - -```js run -let user = { name: "John" }; - -// our script uses "id" property -user.id = "ID Value"; - -// ...if later another script the uses "id" for its purposes... - -user.id = "Their id value" -// boom! overwritten! it did not mean to harm the colleague, but did it! -``` - -### Symbols in a literal - -If we want to use a symbol in an object literal, we need square brackets. - -Like this: - -```js -let id = Symbol("id"); - -let user = { - name: "John", -*!* - [id]: 123 // not just "id: 123" -*/!* -}; -``` -That's because we need the value from the variable `id` as the key, not the string "id". - -### Symbols are skipped by for..in - -Symbolic properties do not participate in `for..in` loop. - -For instance: - -```js run -let id = Symbol("id"); -let user = { - name: "John", - age: 30, - [id]: 123 -}; - -*!* -for (let key in user) alert(key); // name, age (no symbols) -*/!* - -// the direct access by the symbol works -alert( "Direct: " + user[id] ); -``` - -That's a part of the general "hiding" concept. If another script or a library loops over our object, it won't unexpectedly access a symbolic property. - -In contrast, [Object.assign](mdn:js/Object/assign) copies both string and symbol properties: - -```js run -let id = Symbol("id"); -let user = { - [id]: 123 -}; - -let clone = Object.assign({}, user); - -alert( clone[id] ); // 123 -``` - -There's no paradox here. That's by design. The idea is that when we clone an object or merge objects, we usually want *all* properties to be copied (including symbols like `id`). - -````smart header="Property keys of other types are coerced to strings" -We can only use strings or symbols as keys in objects. Other types are converted to strings. - -For instance, a number `0` becomes a string `"0"` when used as a property key: - -```js run -let obj = { - 0: "test" // same as "0": "test" -}; - -// both alerts access the same property (the number 0 is converted to string "0") -alert( obj["0"] ); // test -alert( obj[0] ); // test (same property) -``` -```` - -## Global symbols - -As we've seen, usually all symbols are different, even if they have the same names. But sometimes we want same-named symbols to be same entities. - -For instance, different parts of our application want to access symbol `"id"` meaning exactly the same property. - -To achieve that, there exists a *global symbol registry*. We can create symbols in it and access them later, and it guarantees that repeated accesses by the same name return exactly the same symbol. - -In order to create or read a symbol in the registry, use `Symbol.for(key)`. - -That call checks the global registry, and if there's a symbol described as `key`, then returns it, otherwise creates a new symbol `Symbol(key)` and stores it in the registry by the given `key`. - -For instance: - -```js run -// read from the global registry -let id = Symbol.for("id"); // if the symbol did not exist, it is created - -// read it again -let idAgain = Symbol.for("id"); - -// the same symbol -alert( id === idAgain ); // true -``` - -Symbols inside the registry are called *global symbols*. If we want an application-wide symbol, accessible everywhere in the code -- that's what they are for. - -```smart header="That sounds like Ruby" -In some programming languages, like Ruby, there's a single symbol per name. - -In JavaScript, as we can see, that's right for global symbols. -``` - -### Symbol.keyFor - -For global symbols, not only `Symbol.for(key)` returns a symbol by name, but there's a reverse call: `Symbol.keyFor(sym)`, that does the reverse: returns a name by a global symbol. - -For instance: - -```js run -let sym = Symbol.for("name"); -let sym2 = Symbol.for("id"); - -// get name from symbol -alert( Symbol.keyFor(sym) ); // name -alert( Symbol.keyFor(sym2) ); // id -``` - -The `Symbol.keyFor` internally uses the global symbol registry to look up the key for the symbol. So it doesn't work for non-global symbols. If the symbol is not global, it won't be able to find it and return `undefined`. - -For instance: - -```js run -alert( Symbol.keyFor(Symbol.for("name")) ); // name, global symbol - -alert( Symbol.keyFor(Symbol("name2")) ); // undefined, the argument isn't a global symbol -``` - -## System symbols - -There exist many "system" symbols that JavaScript uses internally, and we can use them to fine-tune various aspects of our objects. - -They are listed in the specification in the [Well-known symbols](https://tc39.github.io/ecma262/#sec-well-known-symbols) table: - -- `Symbol.hasInstance` -- `Symbol.isConcatSpreadable` -- `Symbol.iterator` -- `Symbol.toPrimitive` -- ...and so on. - -For instance, `Symbol.toPrimitive` allows us to describe object to primitive conversion. We'll see its use very soon. - -Other symbols will also become familiar when we study the corresponding language features. - -## Summary - -`Symbol` is a primitive type for unique identifiers. - -Symbols are created with `Symbol()` call with an optional description. - -Symbols are always different values, even if they have the same name. If we want same-named symbols to be equal, then we should use the global registry: `Symbol.for(key)` returns (creates if needed) a global symbol with `key` as the name. Multiple calls of `Symbol.for` return exactly the same symbol. - -Symbols have two main use cases: - -1. "Hidden" object properties. - If we want to add a property into an object that "belongs" to another script or a library, we can create a symbol and use it as a property key. A symbolic property does not appear in `for..in`, so it won't be occasionally listed. Also it won't be accessed directly, because another script does not have our symbol, so it will not occasionally intervene into its actions. - - So we can "covertly" hide something into objects that we need, but others should not see, using symbolic properties. - -2. There are many system symbols used by JavaScript which are accessible as `Symbol.*`. We can use them to alter some built-in behaviors. For instance, later in the tutorial we'll use `Symbol.iterator` for [iterables](info:iterable), `Symbol.toPrimitive` to setup [object-to-primitive conversion](info:object-toprimitive) and so on. - -Technically, symbols are not 100% hidden. There is a built-in method [Object.getOwnPropertySymbols(obj)](mdn:js/Object/getOwnPropertySymbols) that allows us to get all symbols. Also there is a method named [Reflect.ownKeys(obj)](mdn:js/Reflect/ownKeys) that returns *all* keys of an object including symbolic ones. So they are not really hidden. But most libraries, built-in methods and syntax constructs adhere to a common agreement that they are. And the one who explicitly calls the aforementioned methods probably understands well what he's doing. diff --git a/1-js/04-object-basics/04-object-methods/2-check-syntax/solution.md b/1-js/04-object-basics/04-object-methods/2-check-syntax/solution.md deleted file mode 100644 index e2e87de7c9..0000000000 --- a/1-js/04-object-basics/04-object-methods/2-check-syntax/solution.md +++ /dev/null @@ -1,43 +0,0 @@ -**Error**! - -Try it: - -```js run -let user = { - name: "John", - go: function() { alert(this.name) } -} - -(user.go)() // error! -``` - -The error message in most browsers does not give understanding what went wrong. - -**The error appears because a semicolon is missing after `user = {...}`.** - -JavaScript does not assume a semicolon before a bracket `(user.go)()`, so it reads the code like: - -```js no-beautify -let user = { go:... }(user.go)() -``` - -Then we can also see that such a joint expression is syntactically a call of the object `{ go: ... }` as a function with the argument `(user.go)`. And that also happens on the same line with `let user`, so the `user` object has not yet even been defined, hence the error. - -If we insert the semicolon, all is fine: - -```js run -let user = { - name: "John", - go: function() { alert(this.name) } -}*!*;*/!* - -(user.go)() // John -``` - -Please note that brackets around `(user.go)` do nothing here. Usually they setup the order of operations, but here the dot `.` works first anyway, so there's no effect. Only the semicolon thing matters. - - - - - - diff --git a/1-js/04-object-basics/04-object-methods/2-check-syntax/task.md b/1-js/04-object-basics/04-object-methods/2-check-syntax/task.md deleted file mode 100644 index f40d687352..0000000000 --- a/1-js/04-object-basics/04-object-methods/2-check-syntax/task.md +++ /dev/null @@ -1,19 +0,0 @@ -importance: 2 - ---- - -# Syntax check - -What is the result of this code? - - -```js no-beautify -let user = { - name: "John", - go: function() { alert(this.name) } -} - -(user.go)() -``` - -P.S. There's a pitfall :) diff --git a/1-js/04-object-basics/04-object-methods/3-why-this/solution.md b/1-js/04-object-basics/04-object-methods/3-why-this/solution.md deleted file mode 100644 index 89bc0d722a..0000000000 --- a/1-js/04-object-basics/04-object-methods/3-why-this/solution.md +++ /dev/null @@ -1,22 +0,0 @@ - -Here's the explanations. - -1. That's a regular object method call. - -2. The same, brackets do not change the order of operations here, the dot is first anyway. - -3. Here we have a more complex call `(expression).method()`. The call works as if it were split into two lines: - - ```js no-beautify - f = obj.go; // calculate the expression - f(); // call what we have - ``` - - Here `f()` is executed as a function, without `this`. - -4. The similar thing as `(3)`, to the left of the dot `.` we have an expression. - -To explain the behavior of `(3)` and `(4)` we need to recall that property accessors (dot or square brackets) return a value of the Reference Type. - -Any operation on it except a method call (like assignment `=` or `||`) turns it into an ordinary value, which does not carry the information allowing to set `this`. - diff --git a/1-js/04-object-basics/04-object-methods/3-why-this/task.md b/1-js/04-object-basics/04-object-methods/3-why-this/task.md deleted file mode 100644 index f22de29ccd..0000000000 --- a/1-js/04-object-basics/04-object-methods/3-why-this/task.md +++ /dev/null @@ -1,26 +0,0 @@ -importance: 3 - ---- - -# Explain the value of "this" - -In the code below we intend to call `user.go()` method 4 times in a row. - -But calls `(1)` and `(2)` works differently from `(3)` and `(4)`. Why? - -```js run no-beautify -let obj, method; - -obj = { - go: function() { alert(this); } -}; - -obj.go(); // (1) [object Object] - -(obj.go)(); // (2) [object Object] - -(method = obj.go)(); // (3) undefined - -(obj.go || obj.stop)(); // (4) undefined -``` - diff --git a/1-js/04-object-basics/04-object-methods/4-object-property-this/solution.md b/1-js/04-object-basics/04-object-methods/4-object-property-this/solution.md index f5773ec2cd..8856617f58 100644 --- a/1-js/04-object-basics/04-object-methods/4-object-property-this/solution.md +++ b/1-js/04-object-basics/04-object-methods/4-object-property-this/solution.md @@ -1,28 +1,39 @@ -**Answer: an error.** +**Ответ: ошибка.** -Try it: +Проверьте: ```js run function makeUser() { return { name: "John", ref: this }; -}; +} let user = makeUser(); alert( user.ref.name ); // Error: Cannot read property 'name' of undefined ``` -That's because rules that set `this` do not look at object literals. +Это потому, что правила, которые определяют значение `this`, никак не смотрят на объявление объекта. Важен лишь момент вызова. -Here the value of `this` inside `makeUser()` is `undefined`, because it is called as a function, not as a method. +Здесь значение `this` внутри `makeUser()` равно `undefined`, потому что оно вызывается как функция, а не через "точечный" синтаксис как метод. -And the object literal itself has no effect on `this`. The value of `this` is one for the whole function, code blocks and object literals do not affect it. +Значение `this` одно для всей функции, блоки кода и объектные литералы на него не влияют. -So `ref: this` actually takes current `this` of the function. +Таким образом, `ref: this` фактически принимает текущее `this` функции `makeUser()`. -Here's the opposite case: +Мы можем переписать функцию и вернуть то же самое `this` со значением `undefined`: + +```js run +function makeUser(){ + return this; // на этот раз нет литерала объекта +} + +alert( makeUser().name ); // Error: Cannot read property 'name' of undefined +``` +Как вы можете видеть, результат `alert( makeUser().name )` совпадает с результатом `alert( user.ref.name )` из предыдущего примера. + +Вот противоположный случай: ```js run function makeUser() { @@ -34,13 +45,11 @@ function makeUser() { } */!* }; -}; +} let user = makeUser(); alert( user.ref().name ); // John ``` -Now it works, because `user.ref()` is a method. And the value of `this` is set to the object before dot `.`. - - +Теперь это работает, поскольку `user.ref()` - это метод. И значением `this` становится объект перед точкой `.`. diff --git a/1-js/04-object-basics/04-object-methods/4-object-property-this/task.md b/1-js/04-object-basics/04-object-methods/4-object-property-this/task.md index 4784b082c0..f40a201fd8 100644 --- a/1-js/04-object-basics/04-object-methods/4-object-property-this/task.md +++ b/1-js/04-object-basics/04-object-methods/4-object-property-this/task.md @@ -2,11 +2,11 @@ importance: 5 --- -# Using "this" in object literal +# Использование "this" в литерале объекта -Here the function `makeUser` returns an object. +Здесь функция `makeUser` возвращает объект. -What is the result of accessing its `ref`? Why? +Каким будет результат при обращении к свойству объекта `ref`? Почему? ```js function makeUser() { @@ -14,10 +14,10 @@ function makeUser() { name: "John", ref: this }; -}; +} let user = makeUser(); -alert( user.ref.name ); // What's the result? +alert( user.ref.name ); // Каким будет результат? ``` diff --git a/1-js/04-object-basics/04-object-methods/7-calculator/_js.view/test.js b/1-js/04-object-basics/04-object-methods/7-calculator/_js.view/test.js index 1f71eda4c6..bde14d6cd4 100644 --- a/1-js/04-object-basics/04-object-methods/7-calculator/_js.view/test.js +++ b/1-js/04-object-basics/04-object-methods/7-calculator/_js.view/test.js @@ -1,8 +1,8 @@ -describe("calculator", function() { +describe("калькулятор", function() { - context("when 2 and 3 entered", function() { + context("когда 2 и 3 введены", function() { beforeEach(function() { sinon.stub(window, "prompt"); @@ -15,12 +15,17 @@ describe("calculator", function() { afterEach(function() { prompt.restore(); }); + + it('read получает два значения и сохраняет их как свойства объекта', function () { + assert.equal(calculator.a, 2); + assert.equal(calculator.b, 3); + }); - it("the sum is 5", function() { + it("сумма равна 5", function() { assert.equal(calculator.sum(), 5); }); - it("the multiplication product is 6", function() { + it("произведение равно 6", function() { assert.equal(calculator.mul(), 6); }); }); diff --git a/1-js/04-object-basics/04-object-methods/7-calculator/task.md b/1-js/04-object-basics/04-object-methods/7-calculator/task.md index aa22608ece..f6c93164fb 100644 --- a/1-js/04-object-basics/04-object-methods/7-calculator/task.md +++ b/1-js/04-object-basics/04-object-methods/7-calculator/task.md @@ -2,17 +2,17 @@ importance: 5 --- -# Create a calculator +# Создайте калькулятор -Create an object `calculator` with three methods: +Создайте объект `calculator` (калькулятор) с тремя методами: -- `read()` prompts for two values and saves them as object properties. -- `sum()` returns the sum of saved values. -- `mul()` multiplies saved values and returns the result. +- `read()` (читать) запрашивает два значения и сохраняет их как свойства объекта с именами `a` и `b`. +- `sum()` (суммировать) возвращает сумму сохранённых значений. +- `mul()` (умножить) перемножает сохранённые значения и возвращает результат. ```js let calculator = { - // ... your code ... + // ... ваш код ... }; calculator.read(); diff --git a/1-js/04-object-basics/04-object-methods/8-chain-calls/_js.view/solution.js b/1-js/04-object-basics/04-object-methods/8-chain-calls/_js.view/solution.js index e98fe6410c..a35c009cce 100644 --- a/1-js/04-object-basics/04-object-methods/8-chain-calls/_js.view/solution.js +++ b/1-js/04-object-basics/04-object-methods/8-chain-calls/_js.view/solution.js @@ -11,5 +11,6 @@ let ladder = { }, showStep: function() { alert(this.step); + return this; } }; \ No newline at end of file diff --git a/1-js/04-object-basics/04-object-methods/8-chain-calls/_js.view/test.js b/1-js/04-object-basics/04-object-methods/8-chain-calls/_js.view/test.js index a2b17fcc4f..c44088c7c0 100644 --- a/1-js/04-object-basics/04-object-methods/8-chain-calls/_js.view/test.js +++ b/1-js/04-object-basics/04-object-methods/8-chain-calls/_js.view/test.js @@ -1,5 +1,5 @@ -describe('Ladder', function() { +describe('Лестница', function() { before(function() { window.alert = sinon.stub(window, "alert"); }); @@ -8,30 +8,38 @@ describe('Ladder', function() { ladder.step = 0; }); - it('up() should return this', function() { + it('up() должен возвращать this', function() { assert.equal(ladder.up(), ladder); }); - it('down() should return this', function() { + it('down() должен возвращать this', function() { assert.equal(ladder.down(), ladder); }); - it('showStep() should call alert', function() { + it('showStep() должен вызывать alert', function() { ladder.showStep(); assert(alert.called); }); - it('up() should increase step', function() { + it('up() должен увеличивать ступеньку', function() { assert.equal(ladder.up().up().step, 2); }); - it('down() should decrease step', function() { + it('down() должен уменьшать ступеньку', function() { assert.equal(ladder.down().step, -1); }); it('down().up().up().up() ', function() { assert.equal(ladder.down().up().up().up().step, 2); }); + + it('showStep() должен возвращать this', function() { + assert.equal(ladder.showStep(), ladder); + }); + + it('up().up().down().showStep().down().showStep()', function () { + assert.equal(ladder.up().up().down().showStep().down().showStep().step, 0) + }); after(function() { ladder.step = 0; diff --git a/1-js/04-object-basics/04-object-methods/8-chain-calls/solution.md b/1-js/04-object-basics/04-object-methods/8-chain-calls/solution.md index 2b47873fce..f42c4e0eb1 100644 --- a/1-js/04-object-basics/04-object-methods/8-chain-calls/solution.md +++ b/1-js/04-object-basics/04-object-methods/8-chain-calls/solution.md @@ -1,4 +1,4 @@ -The solution is to return the object itself from every call. +Решение состоит в том, чтобы возвращать сам объект из каждого вызова. ```js run demo let ladder = { @@ -21,19 +21,19 @@ let ladder = { return this; */!* } -} +}; -ladder.up().up().down().up().down().showStep(); // 1 +ladder.up().up().down().showStep().down().showStep(); // показывает 1 затем 0 ``` -We also can write a single call per line. For long chains it's more readable: +Мы также можем записать один вызов на одной строке. Для длинных цепей вызовов это более читабельно: ```js ladder .up() .up() .down() - .up() + .showStep() // 1 .down() - .showStep(); // 1 + .showStep(); // 0 ``` diff --git a/1-js/04-object-basics/04-object-methods/8-chain-calls/task.md b/1-js/04-object-basics/04-object-methods/8-chain-calls/task.md index eca9f4e92f..e24f15ce83 100644 --- a/1-js/04-object-basics/04-object-methods/8-chain-calls/task.md +++ b/1-js/04-object-basics/04-object-methods/8-chain-calls/task.md @@ -2,9 +2,9 @@ importance: 2 --- -# Chaining +# Цепь вызовов -There's a `ladder` object that allows to go up and down: +У нас есть объект `ladder` (лестница), который позволяет подниматься и спускаться: ```js let ladder = { @@ -15,25 +15,27 @@ let ladder = { down() { this.step--; }, - showStep: function() { // shows the current step + showStep: function() { // показывает текущую ступеньку alert( this.step ); } }; ``` -Now, if we need to make several calls in sequence, can do it like this: +Теперь, если нам нужно выполнить несколько последовательных вызовов, мы можем сделать это так: ```js ladder.up(); ladder.up(); ladder.down(); ladder.showStep(); // 1 +ladder.down(); +ladder.showStep(); // 0 ``` -Modify the code of `up`, `down` and `showStep` to make the calls chainable, like this: +Измените код методов `up`, `down` и `showStep` таким образом, чтобы их вызов можно было сделать по цепочке, например так: ```js -ladder.up().up().down().showStep(); // 1 +ladder.up().up().down().showStep().down().showStep(); // показывает 1 затем 0 ``` -Such approach is widely used across JavaScript libraries. +Такой подход широко используется в библиотеках JavaScript. diff --git a/1-js/04-object-basics/04-object-methods/article.md b/1-js/04-object-basics/04-object-methods/article.md index 0418adee01..2596e90361 100644 --- a/1-js/04-object-basics/04-object-methods/article.md +++ b/1-js/04-object-basics/04-object-methods/article.md @@ -1,21 +1,22 @@ -# Object methods, "this" +# Методы объекта, "this" -Objects are usually created to represent entities of the real world, like users, orders and so on: +Объекты обычно создаются, чтобы представлять сущности реального мира, будь то пользователи, заказы и так далее: ```js +// Объект пользователя let user = { name: "John", age: 30 }; ``` -And, in the real world, a user can *act*: select something from the shopping cart, login, logout etc. +И так же, как и в реальном мире, пользователь может *совершать действия*: выбирать что-то из корзины покупок, авторизовываться, выходить из системы, оплачивать и т.п. -Actions are represented in JavaScript by functions in properties. +Такие действия в JavaScript представлены функциями в свойствах. -## Method examples +## Примеры методов -For the start, let's teach the `user` to say hello: +Для начала давайте научим нашего пользователя `user` здороваться: ```js run let user = { @@ -25,22 +26,22 @@ let user = { *!* user.sayHi = function() { - alert("Hello!"); + alert("Привет!"); }; */!* -user.sayHi(); // Hello! +user.sayHi(); // Привет! ``` -Here we've just used a Function Expression to create the function and assign it to the property `user.sayHi` of the object. +Здесь мы просто использовали Function Expression (функциональное выражение), чтобы создать функцию приветствия, и присвоили её свойству `user.sayHi` нашего объекта. -Then we can call it. The user can now speak! +Затем мы можем вызвать ee как `user.sayHi()`. Теперь пользователь может говорить! -A function that is the property of an object is called its *method*. +Функцию, которая является свойством объекта, называют *методом* этого объекта. -So, here we've got a method `sayHi` of the object `user`. +Итак, мы получили метод `sayHi` объекта `user`. -Of course, we could use a pre-declared function as a method, like this: +Конечно, мы могли бы использовать заранее объявленную функцию в качестве метода, вот так: ```js run let user = { @@ -48,61 +49,61 @@ let user = { }; *!* -// first, declare +// сначала, объявляем function sayHi() { - alert("Hello!"); -}; + alert("Привет!"); +} -// then add as a method +// затем добавляем в качестве метода user.sayHi = sayHi; */!* -user.sayHi(); // Hello! +user.sayHi(); // Привет! ``` -```smart header="Object-oriented programming" -When we write our code using objects to represent entities, that's called an [object-oriented programming](https://en.wikipedia.org/wiki/Object-oriented_programming), in short: "OOP". +```smart header="Объектно-ориентированное программирование" +Когда мы пишем наш код, используя объекты для представления сущностей реального мира, - это называется [объектно-ориентированным программированием](https://ru.wikipedia.org/wiki/%D0%9E%D0%B1%D1%8A%D0%B5%D0%BA%D1%82%D0%BD%D0%BE-%D0%BE%D1%80%D0%B8%D0%B5%D0%BD%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%BD%D0%BE%D0%B5_%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) или сокращённо: "ООП". -OOP is a big thing, an interesting science of its own. How to choose the right entities? How to organize the interaction between them? That's architecture, and there are great books on that topic, like "Design Patterns: Elements of Reusable Object-Oriented Software" by E.Gamma, R.Helm, R.Johnson, J.Vissides or "Object-Oriented Analysis and Design with Applications" by G.Booch, and more. +ООП является большой предметной областью и интересной наукой самой по себе. Как выбрать правильные сущности? Как организовать взаимодействие между ними? Это -- создание архитектуры, и на эту тему есть отличные книги, такие как "Приёмы объектно-ориентированного проектирования. Паттерны проектирования" авторов Эрих Гамма, Ричард Хелм, Ральф Джонсон, Джон Влиссидес или "Объектно-ориентированный анализ и проектирование с примерами приложений" Гради Буча, а также ещё множество других книг. ``` -### Method shorthand +### Сокращённая запись метода -There exists a shorter syntax for methods in an object literal: +Существует более короткий синтаксис для методов в литерале объекта: ```js -// these objects do the same +// эти объекты делают одно и то же -let user = { +user = { sayHi: function() { - alert("Hello"); + alert("Привет"); } }; -// method shorthand looks better, right? -let user = { +// сокращённая запись выглядит лучше, не так ли? +user = { *!* - sayHi() { // same as "sayHi: function()" + sayHi() { // то же самое, что и "sayHi: function(){...}" */!* - alert("Hello"); + alert("Привет"); } }; ``` -As demonstrated, we can omit `"function"` and just write `sayHi()`. +Как было показано, мы можем пропустить ключевое слово `"function"` и просто написать `sayHi()`. -To tell the truth, the notations are not fully identical. There are subtle differences related to object inheritance (to be covered later), but for now they do not matter. In almost all cases the shorter syntax is preferred. +Нужно отметить, что эти две записи не полностью эквивалентны. Есть тонкие различия, связанные с наследованием объектов (что будет рассмотрено позже), но на данном этапе изучения это неважно. Почти во всех случаях сокращённый синтаксис предпочтителен. -## "this" in methods +## Ключевое слово "this" в методах -It's common that an object method needs to access the information stored in the object to do its job. +Как правило, методу объекта обычно требуется доступ к информации, хранящейся в объекте, для выполнения своей работы. -For instance, the code inside `user.sayHi()` may need the name of the `user`. +Например, коду внутри `user.sayHi()` может потребоваться имя пользователя, которое хранится в объекте `user`. -**To access the object, a method can use the `this` keyword.** +**Для доступа к информации внутри объекта метод может использовать ключевое слово `this`.** -The value of `this` is the object "before dot", the one used to call the method. +Значение `this` - это объект "перед точкой", который используется для вызова метода. -For instance: +Например: ```js run let user = { @@ -111,6 +112,7 @@ let user = { sayHi() { *!* + // "this" - это "текущий объект". alert(this.name); */!* } @@ -120,9 +122,9 @@ let user = { user.sayHi(); // John ``` -Here during the execution of `user.sayHi()`, the value of `this` will be `user`. +Здесь во время выполнения кода `user.sayHi()` значением `this` будет являться `user` (ссылка на объект `user`). -Technically, it's also possible to access the object without `this`, by referencing it via the outer variable: +Технически также возможно получить доступ к объекту без ключевого слова `this`, обратившись к нему через внешнюю переменную (в которой хранится ссылка на этот объект): ```js let user = { @@ -131,16 +133,16 @@ let user = { sayHi() { *!* - alert(user.name); // "user" instead of "this" + alert(user.name); // "user" вместо "this" */!* } }; ``` -...But such code is unreliable. If we decide to copy `user` to another variable, e.g. `admin = user` and overwrite `user` with something else, then it will access the wrong object. +...Но такой код ненадёжен. Если мы решим скопировать ссылку на объект `user` в другую переменную, например, `admin = user`, и перезапишем переменную `user` чем-то другим, тогда будет осуществлён доступ к неправильному объекту при вызове метода из `admin`. -That's demonstrated below: +Это показано ниже: ```js run let user = { @@ -149,7 +151,7 @@ let user = { sayHi() { *!* - alert( user.name ); // leads to an error + alert( user.name ); // приведёт к ошибке */!* } @@ -157,18 +159,20 @@ let user = { let admin = user; -user = null; // overwrite to make things obvious +user = null; // перезапишем переменную для наглядности, теперь она не хранит ссылку на объект. -admin.sayHi(); // Whoops! inside sayHi(), the old name is used! error! +*!* +admin.sayHi(); // TypeError: Cannot read property 'name' of null +*/!* ``` -If we used `this.name` instead of `user.name` inside the `alert`, then the code would work. +Если бы мы использовали `this.name` вместо `user.name` внутри `alert`, тогда этот код бы сработал. -## "this" is not bound +## "this" не является фиксированным -In JavaScript, "this" keyword behaves unlike most other programming languages. First, it can be used in any function. +В JavaScript ключевое слово "this" ведёт себя иначе, чем в большинстве других языков программирования. Его можно использовать в любой функции, даже если это не метод объекта. -There's no syntax error in the code like that: +В следующем примере нет синтаксической ошибки: ```js function sayHi() { @@ -176,9 +180,9 @@ function sayHi() { } ``` -The value of `this` is evaluated during the run-time. And it can be anything. +Значение `this` вычисляется во время выполнения кода, в зависимости от контекста. -For instance, the same function may have different "this" when called from different objects: +Например, здесь одна и та же функция назначена двум разным объектам и имеет различное значение "this" в вызовах: ```js run let user = { name: "John" }; @@ -189,20 +193,23 @@ function sayHi() { } *!* -// use the same functions in two objects +// используем одну и ту же функцию в двух объектах user.f = sayHi; admin.f = sayHi; */!* -// these calls have different this -// "this" inside the function is the object "before the dot" +// эти вызовы имеют разное значение this +// "this" внутри функции - это объект "перед точкой" user.f(); // John (this == user) admin.f(); // Admin (this == admin) -admin['f'](); // Admin (dot or square brackets access the method – doesn't matter) +admin['f'](); // Admin (нет разницы между использованием точки или квадратных скобок для доступа к объекту) ``` -Actually, we can call the function without an object at all: +Правило простое: если вызывается `obj.f()`, то во время вызова `f`, `this` - это `obj`. Так что, в приведённом выше примере это либо `user`, либо `admin`. + +````smart header="Вызов без объекта: `this == undefined`" +Мы даже можем вызвать функцию вообще без объекта: ```js run function sayHi() { @@ -212,117 +219,28 @@ function sayHi() { sayHi(); // undefined ``` -In this case `this` is `undefined` in strict mode. If we try to access `this.name`, there will be an error. +В строгом режиме (`"use strict"`) в таком коде значением `this` будет являться `undefined`. Если мы попытаемся получить доступ к `this.name` - это вызовет ошибку. -In non-strict mode the value of `this` in such case will be the *global object* (`window` in a browser, we'll get to it later in the chapter [](info:global-object)). This is a historical behavior that `"use strict"` fixes. +В нестрогом режиме значением `this` в таком случае будет *глобальный объект* (`window` в браузерe, мы вернёмся к этому позже в главе [Глобальный объект](info:global-object)). Это -- исторически сложившееся поведение `this`, которое исправляется использованием строгого режима (`"use strict"`). -Please note that usually a call of a function that uses `this` without an object is not normal, but rather a programming mistake. If a function has `this`, then it is usually meant to be called in the context of an object. +Обычно подобный вызов является ошибкой программирования. Если внутри функции используется `this`, тогда она ожидает, что будет вызвана в контексте какого-либо объекта. +```` -```smart header="The consequences of unbound `this`" -If you come from another programming language, then you are probably used to the idea of a "bound `this`", where methods defined in an object always have `this` referencing that object. +```smart header="Последствия свободного `this`" +Если вы до этого изучали другие языки программирования, то вы, вероятно, привыкли к идее "фиксированного`this`" - когда методы, определённые в объекте, всегда имеют `this`, ссылающееся на этот объект. -In JavaScript `this` is "free", its value is evaluated at call-time and does not depend on where the method was declared, but rather on what's the object "before the dot". +В JavaScript `this` является "свободным", его значение вычисляется в момент вызова метода и не зависит от того, где этот метод был объявлен, а скорее от того, какой объект вызывает метод (какой объект стоит "перед точкой"). -The concept of run-time evaluated `this` has both pluses and minuses. On the one hand, a function can be reused for different objects. On the other hand, greater flexibility opens a place for mistakes. +Эта концепция вычисления `this` в момент исполнения имеет как свои плюсы, так и минусы. С одной стороны, функция может быть повторно использована в качестве метода у различных объектов (что повышает гибкость). С другой стороны, большая гибкость увеличивает вероятность ошибок. -Here our position is not to judge whether this language design decision is good or bad. We'll understand how to work with it, how to get benefits and evade problems. +Здесь наша позиция заключается не в том, чтобы судить, является ли это архитектурное решение в языке хорошим или плохим. Скоро мы поймем, как с этим работать, как получить выгоду и избежать проблем. ``` -## Internals: Reference Type - -```warn header="In-depth language feature" -This section covers an advanced topic, to understand certain edge-cases better. - -If you want to go on faster, it can be skipped or postponed. -``` - -An intricate method call can lose `this`, for instance: - -```js run -let user = { - name: "John", - hi() { alert(this.name); }, - bye() { alert("Bye"); } -}; - -user.hi(); // John (the simple call works) - -*!* -// now let's call user.hi or user.bye depending on the name -(user.name == "John" ? user.hi : user.bye)(); // Error! -*/!* -``` - -On the last line there is a ternary operator that chooses either `user.hi` or `user.bye`. In this case the result is `user.hi`. - -The method is immediately called with parentheses `()`. But it doesn't work right! - -You can see that the call results in an error, because the value of `"this"` inside the call becomes `undefined`. - -This works (object dot method): -```js -user.hi(); -``` - -This doesn't (evaluated method): -```js -(user.name == "John" ? user.hi : user.bye)(); // Error! -``` - -Why? If we want to understand why it happens, let's get under the hood of how `obj.method()` call works. - -Looking closely, we may notice two operations in `obj.method()` statement: - -1. First, the dot `'.'` retrieves the property `obj.method`. -2. Then parentheses `()` execute it. - -So, how does the information about `this` get passed from the first part to the second one? - -If we put these operations on separate lines, then `this` will be lost for sure: - -```js run -let user = { - name: "John", - hi() { alert(this.name); } -} - -*!* -// split getting and calling the method in two lines -let hi = user.hi; -hi(); // Error, because this is undefined -*/!* -``` - -Here `hi = user.hi` puts the function into the variable, and then on the last line it is completely standalone, and so there's no `this`. - -**To make `user.hi()` calls work, JavaScript uses a trick -- the dot `'.'` returns not a function, but a value of the special [Reference Type](https://tc39.github.io/ecma262/#sec-reference-specification-type).** - -The Reference Type is a "specification type". We can't explicitly use it, but it is used internally by the language. - -The value of Reference Type is a three-value combination `(base, name, strict)`, where: - -- `base` is the object. -- `name` is the property. -- `strict` is true if `use strict` is in effect. - -The result of a property access `user.hi` is not a function, but a value of Reference Type. For `user.hi` in strict mode it is: - -```js -// Reference Type value -(user, "hi", true) -``` - -When parentheses `()` are called on the Reference Type, they receive the full information about the object and its method, and can set the right `this` (`=user` in this case). - -Any other operation like assignment `hi = user.hi` discards the reference type as a whole, takes the value of `user.hi` (a function) and passes it on. So any further operation "loses" `this`. - -So, as the result, the value of `this` is only passed the right way if the function is called directly using a dot `obj.method()` or square brackets `obj['method']()` syntax (they do the same here). Later in this tutorial, we will learn various ways to solve this problem such as [func.bind()](/bind#solution-2-bind). - -## Arrow functions have no "this" +## У стрелочных функций нет "this" -Arrow functions are special: they don't have their "own" `this`. If we reference `this` from such a function, it's taken from the outer "normal" function. +Стрелочные функции особенные: у них нет своего "собственного" `this`. Если мы ссылаемся на `this` внутри такой функции, то оно берётся из внешней "нормальной" функции. -For instance, here `arrow()` uses `this` from the outer `user.sayHi()` method: +Например, здесь `arrow()` использует значение `this` из внешнего метода `user.sayHi()`: ```js run let user = { @@ -336,18 +254,18 @@ let user = { user.sayHi(); // Ilya ``` -That's a special feature of arrow functions, it's useful when we actually do not want to have a separate `this`, but rather to take it from the outer context. Later in the chapter we'll go more deeply into arrow functions. +Это особенность стрелочных функций. Она полезна, когда мы на самом деле не хотим иметь отдельное `this`, а скорее хотим взять его из внешнего контекста. Позже в главе мы увидим больше примеров на эту тему. -## Summary +## Итого -- Functions that are stored in object properties are called "methods". -- Methods allow objects to "act" like `object.doSomething()`. -- Methods can reference the object as `this`. +- Функции, которые находятся в свойствах объекта, называются "методами". +- Методы позволяют объектам "действовать": `object.doSomething()`. +- Методы могут ссылаться на объект через `this`. -The value of `this` is defined at run-time. -- When a function is declared, it may use `this`, but that `this` has no value until the function is called. -- That function can be copied between objects. -- When a function is called in the "method" syntax: `object.method()`, the value of `this` during the call is `object`. +Значение `this` определяется во время исполнения кода. +- При объявлении любой функции в ней можно использовать `this`, но этот `this` не имеет значения до тех пор, пока функция не будет вызвана. +- Функция может быть скопирована между объектами (из одного объекта в другой). +- Когда функция вызывается синтаксисом "метода" - `object.method()`, значением `this` во время вызова является `object`. -Please note that arrow functions are special: they have no `this`. When `this` is accessed inside an arrow function, it is taken from outside. +Также ещё раз заметим, что стрелочные функции являются особенными - у них нет `this`. Когда внутри стрелочной функции обращаются к `this`, то его значение берётся извне. diff --git a/1-js/04-object-basics/05-object-toprimitive/article.md b/1-js/04-object-basics/05-object-toprimitive/article.md deleted file mode 100644 index a44cf4f4d4..0000000000 --- a/1-js/04-object-basics/05-object-toprimitive/article.md +++ /dev/null @@ -1,236 +0,0 @@ - -# Object to primitive conversion - -What happens when objects are added `obj1 + obj2`, subtracted `obj1 - obj2` or printed using `alert(obj)`? - -There are special methods in objects that do the conversion. - -In the chapter we've seen the rules for numeric, string and boolean conversions of primitives. But we left a gap for objects. Now, as we know about methods and symbols it becomes possible to close it. - -For objects, there's no to-boolean conversion, because all objects are `true` in a boolean context. So there are only string and numeric conversions. - -The numeric conversion happens when we subtract objects or apply mathematical functions. For instance, `Date` objects (to be covered in the chapter ) can be subtracted, and the result of `date1 - date2` is the time difference between two dates. - -As for the string conversion -- it usually happens when we output an object like `alert(obj)` and in similar contexts. - -## ToPrimitive - -When an object is used in the context where a primitive is required, for instance, in an `alert` or mathematical operations, it's converted to a primitive value using the `ToPrimitive` algorithm ([specification](https://tc39.github.io/ecma262/#sec-toprimitive)). - -That algorithm allows us to customize the conversion using a special object method. - -Depending on the context, the conversion has a so-called "hint". - -There are three variants: - -`"string"` -: When an operation expects a string, for object-to-string conversions, like `alert`: - - ```js - // output - alert(obj); - - // using object as a property key - anotherObj[obj] = 123; - ``` - -`"number"` -: When an operation expects a number, for object-to-number conversions, like maths: - - ```js - // explicit conversion - let num = Number(obj); - - // maths (except binary plus) - let n = +obj; // unary plus - let delta = date1 - date2; - - // less/greater comparison - let greater = user1 > user2; - ``` - -`"default"` -: Occurs in rare cases when the operator is "not sure" what type to expect. - - For instance, binary plus `+` can work both with strings (concatenates them) and numbers (adds them), so both strings and numbers would do. Or when an object is compared using `==` with a string, number or a symbol. - - ```js - // binary plus - let total = car1 + car2; - - // obj == string/number/symbol - if (user == 1) { ... }; - ``` - - The greater/less operator `<>` can work with both strings and numbers too. Still, it uses "number" hint, not "default". That's for historical reasons. - - In practice, all built-in objects except for one case (`Date` object, we'll learn it later) implement `"default"` conversion the same way as `"number"`. And probably we should do the same. - -Please note -- there are only three hints. It's that simple. There is no "boolean" hint (all objects are `true` in boolean context) or anything else. And if we treat `"default"` and `"number"` the same, like most built-ins do, then there are only two conversions. - -**To do the conversion, JavaScript tries to find and call three object methods:** - -1. Call `obj[Symbol.toPrimitive](hint)` if the method exists, -2. Otherwise if hint is `"string"` - - try `obj.toString()` and `obj.valueOf()`, whatever exists. -3. Otherwise if hint is `"number"` or `"default"` - - try `obj.valueOf()` and `obj.toString()`, whatever exists. - -## Symbol.toPrimitive - -Let's start from the first method. There's a built-in symbol named `Symbol.toPrimitive` that should be used to name the conversion method, like this: - -```js -obj[Symbol.toPrimitive] = function(hint) { - // return a primitive value - // hint = one of "string", "number", "default" -} -``` - -For instance, here `user` object implements it: - -```js run -let user = { - name: "John", - money: 1000, - - [Symbol.toPrimitive](hint) { - alert(`hint: ${hint}`); - return hint == "string" ? `{name: "${this.name}"}` : this.money; - } -}; - -// conversions demo: -alert(user); // hint: string -> {name: "John"} -alert(+user); // hint: number -> 1000 -alert(user + 500); // hint: default -> 1500 -``` - -As we can see from the code, `user` becomes a self-descriptive string or a money amount depending on the conversion. The single method `user[Symbol.toPrimitive]` handles all conversion cases. - - -## toString/valueOf - -Methods `toString` and `valueOf` come from ancient times. They are not symbols (symbols did not exist that long ago), but rather "regular" string-named methods. They provide an alternative "old-style" way to implement the conversion. - -If there's no `Symbol.toPrimitive` then JavaScript tries to find them and try in the order: - -- `toString -> valueOf` for "string" hint. -- `valueOf -> toString` otherwise. - -For instance, here `user` does the same as above using a combination of `toString` and `valueOf`: - -```js run -let user = { - name: "John", - money: 1000, - - // for hint="string" - toString() { - return `{name: "${this.name}"}`; - }, - - // for hint="number" or "default" - valueOf() { - return this.money; - } - -}; - -alert(user); // toString -> {name: "John"} -alert(+user); // valueOf -> 1000 -alert(user + 500); // valueOf -> 1500 -``` - -Often we want a single "catch-all" place to handle all primitive conversions. In this case we can implement `toString` only, like this: - -```js run -let user = { - name: "John", - - toString() { - return this.name; - } -}; - -alert(user); // toString -> John -alert(user + 500); // toString -> John500 -``` - -In the absence of `Symbol.toPrimitive` and `valueOf`, `toString` will handle all primitive conversions. - - -## ToPrimitive and ToString/ToNumber - -The important thing to know about all primitive-conversion methods is that they do not necessarily return the "hinted" primitive. - -There is no control whether `toString()` returns exactly a string, or whether `Symbol.toPrimitive` method returns a number for a hint "number". - -**The only mandatory thing: these methods must return a primitive.** - -An operation that initiated the conversion gets that primitive, and then continues to work with it, applying further conversions if necessary. - -For instance: - -- Mathematical operations (except binary plus) perform `ToNumber` conversion: - - ```js run - let obj = { - toString() { // toString handles all conversions in the absence of other methods - return "2"; - } - }; - - alert(obj * 2); // 4, ToPrimitive gives "2", then it becomes 2 - ``` - -- Binary plus checks the primitive -- if it's a string, then it does concatenation, otherwise it performs `ToNumber` and works with numbers. - - String example: - ```js run - let obj = { - toString() { - return "2"; - } - }; - - alert(obj + 2); // 22 (ToPrimitive returned string => concatenation) - ``` - - Number example: - ```js run - let obj = { - toString() { - return true; - } - }; - - alert(obj + 2); // 3 (ToPrimitive returned boolean, not string => ToNumber) - ``` - -```smart header="Historical notes" -For historical reasons, methods `toString` or `valueOf` *should* return a primitive: if any of them returns an object, then there's no error, but that object is ignored (like if the method didn't exist). - -In contrast, `Symbol.toPrimitive` *must* return a primitive, otherwise, there will be an error. -``` - -## Summary - -The object-to-primitive conversion is called automatically by many built-in functions and operators that expect a primitive as a value. - -There are 3 types (hints) of it: -- `"string"` (for `alert` and other string conversions) -- `"number"` (for maths) -- `"default"` (few operators) - -The specification describes explicitly which operator uses which hint. There are very few operators that "don't know what to expect" and use the `"default"` hint. Usually for built-in objects `"default"` hint is handled the same way as `"number"`, so in practice the last two are often merged together. - -The conversion algorithm is: - -1. Call `obj[Symbol.toPrimitive](hint)` if the method exists, -2. Otherwise if hint is `"string"` - - try `obj.toString()` and `obj.valueOf()`, whatever exists. -3. Otherwise if hint is `"number"` or `"default"` - - try `obj.valueOf()` and `obj.toString()`, whatever exists. - -In practice, it's often enough to implement only `obj.toString()` as a "catch-all" method for all conversions that return a "human-readable" representation of an object, for logging or debugging purposes. diff --git a/1-js/04-object-basics/06-constructor-new/1-two-functions-one-object/solution.md b/1-js/04-object-basics/06-constructor-new/1-two-functions-one-object/solution.md index 85c8558a25..06167834b4 100644 --- a/1-js/04-object-basics/06-constructor-new/1-two-functions-one-object/solution.md +++ b/1-js/04-object-basics/06-constructor-new/1-two-functions-one-object/solution.md @@ -1,8 +1,8 @@ Да, возможно. -Если функция возвращает объект, то вместо `this` будет возвращен этот объект. +Если функция возвращает объект, то `new` вернёт его вместо `this`. -Например, они могут вернуть один и тот же объект `obj`, определённый снаружи: +Таким образом, они могут, к примеру, возвращать один и тот же внешне определённый объект `obj`: ```js run no-beautify let obj = {}; diff --git a/1-js/04-object-basics/06-constructor-new/1-two-functions-one-object/task.md b/1-js/04-object-basics/06-constructor-new/1-two-functions-one-object/task.md index 5ecac1f57b..33b9cf13d6 100644 --- a/1-js/04-object-basics/06-constructor-new/1-two-functions-one-object/task.md +++ b/1-js/04-object-basics/06-constructor-new/1-two-functions-one-object/task.md @@ -4,17 +4,16 @@ importance: 2 # Две функции - один объект -Возможно ли создать функции `A` и `B` в примере ниже, где объекты равны `new A()==new B()`? - +Возможно ли создать функции `A` и `B`, чтобы `new A() == new B()`? ```js no-beautify function A() { ... } function B() { ... } -let a = new A; -let b = new B; +let a = new A(); +let b = new B(); alert( a == b ); // true ``` -Если да - приведите пример вашего кода. +Если да – приведите пример вашего кода. diff --git a/1-js/04-object-basics/06-constructor-new/2-calculator-constructor/_js.view/test.js b/1-js/04-object-basics/06-constructor-new/2-calculator-constructor/_js.view/test.js index 036053927c..bba80e5c2e 100644 --- a/1-js/04-object-basics/06-constructor-new/2-calculator-constructor/_js.view/test.js +++ b/1-js/04-object-basics/06-constructor-new/2-calculator-constructor/_js.view/test.js @@ -10,6 +10,11 @@ describe("calculator", function() { calculator = new Calculator(); calculator.read(); }); + + it("the read method asks for two values using prompt and remembers them in object properties", function() { + assert.equal(calculator.a, 2); + assert.equal(calculator.b, 3); + }); it("when 2 and 3 are entered, the sum is 5", function() { assert.equal(calculator.sum(), 5); diff --git a/1-js/04-object-basics/06-constructor-new/2-calculator-constructor/task.md b/1-js/04-object-basics/06-constructor-new/2-calculator-constructor/task.md index ed1fba33bc..cdc406e6f4 100644 --- a/1-js/04-object-basics/06-constructor-new/2-calculator-constructor/task.md +++ b/1-js/04-object-basics/06-constructor-new/2-calculator-constructor/task.md @@ -2,14 +2,13 @@ importance: 5 --- -# Создание калькулятора при помощи конструктора +# Создайте калькулятор при помощи конструктора, new Calculator -Создайте функцию-конструктор `Calculator`, который создаёт объекты с тремя методами: +Создайте функцию-конструктор `Calculator`, которая создаёт объекты с тремя методами: - `read()` запрашивает два значения при помощи `prompt` и сохраняет их значение в свойствах объекта. -- `sum()` возвращает сумму введенных свойств. -- `mul()` возвращает произведение введённых свойств. - +- `sum()` возвращает сумму этих свойств. +- `mul()` возвращает произведение этих свойств. Например: diff --git a/1-js/04-object-basics/06-constructor-new/3-accumulator/_js.view/solution.js b/1-js/04-object-basics/06-constructor-new/3-accumulator/_js.view/solution.js index 585287c542..3c9983aeb1 100644 --- a/1-js/04-object-basics/06-constructor-new/3-accumulator/_js.view/solution.js +++ b/1-js/04-object-basics/06-constructor-new/3-accumulator/_js.view/solution.js @@ -2,7 +2,7 @@ function Accumulator(startingValue) { this.value = startingValue; this.read = function() { - this.value += +prompt('How much to add?', 0); + this.value += +prompt('Сколько нужно добавить?', 0); }; } diff --git a/1-js/04-object-basics/06-constructor-new/3-accumulator/task.md b/1-js/04-object-basics/06-constructor-new/3-accumulator/task.md index 12ef9bcc46..ddf7f15797 100644 --- a/1-js/04-object-basics/06-constructor-new/3-accumulator/task.md +++ b/1-js/04-object-basics/06-constructor-new/3-accumulator/task.md @@ -2,24 +2,26 @@ importance: 5 --- -# Создаем Accumulator +# Создайте new Accumulator Создайте функцию-конструктор `Accumulator(startingValue)`. -Созданный объект должен уметь следующее: +Объект, который она создаёт, должен уметь следующее: - Хранить "текущее значение" в свойстве `value`. Начальное значение устанавливается в аргументе конструктора `startingValue`. -- Метод `read()` использует `prompt` для получения числа и прибавляет его к свойству `value`. +- Метод `read()` должен использовать `prompt` для считывания нового числа и прибавления его к `value`. -Таким образом, свойство `value` является текущей суммой всего, что ввёл пользователь при вызовах метода `read()`, с учётом начального значения `startingValue`. +Другими словами, свойство `value` представляет собой сумму всех введённых пользователем значений, с учётом начального значения `startingValue`. Ниже вы можете посмотреть работу кода: ```js let accumulator = new Accumulator(1); // начальное значение 1 -accumulator.read(); // прибавит ввод prompt к текущему значению -accumulator.read(); // прибавит ввод prompt к текущему значению -alert(accumulator.value); // выведет текущее значение + +accumulator.read(); // прибавляет введённое пользователем значение к текущему значению +accumulator.read(); // прибавляет введённое пользователем значение к текущему значению + +alert(accumulator.value); // выведет сумму этих значений ``` [demo] diff --git a/1-js/04-object-basics/06-constructor-new/article.md b/1-js/04-object-basics/06-constructor-new/article.md index 4d77a77231..a7a785ce90 100644 --- a/1-js/04-object-basics/06-constructor-new/article.md +++ b/1-js/04-object-basics/06-constructor-new/article.md @@ -1,15 +1,15 @@ -# Конструкторы, создание объектов через "new" +# Конструктор, оператор "new" -Обычный синтаксис `{...}` позволяет создать только один объект. Но зачастую нам нужно создать множество однотипных объектов, таких как пользователи, элементы меню и т.д. +Обычный синтаксис `{...}` позволяет создать только один объект. Но зачастую нам нужно создать множество похожих, однотипных объектов, таких как пользователи, элементы меню и так далее. Это можно сделать при помощи функции-конструктора и оператора `"new"`. ## Функция-конструктор -Функции-конструкторы являются обычными функциями. Но есть два соглашения: +Функции-конструкторы технически являются обычными функциями. Но есть два соглашения: 1. Имя функции-конструктора должно начинаться с большой буквы. -2. Функция-конструктор должна вызываться при помощи оператора `"new"`. +2. Функция-конструктор должна выполняться только с помощью оператора `"new"`. Например: @@ -20,20 +20,20 @@ function User(name) { } *!* -let user = new User("Вася"); +let user = new User("Jack"); */!* -alert(user.name); // Вася +alert(user.name); // Jack alert(user.isAdmin); // false ``` Когда функция вызывается как `new User(...)`, происходит следующее: -1. Создаётся новый пустой объект и присваивается `this`. -2. Выполняется код функции. Обычно он модифицирует `this`, добавляет туда новые свойства. +1. Создаётся новый пустой объект, и он присваивается `this`. +2. Выполняется тело функции. Обычно оно модифицирует `this`, добавляя туда новые свойства. 3. Возвращается значение `this`. -Другими словами, вызов `new User(...)` делает примерно вот что: +Другими словами, `new User(...)` делает что-то вроде: ```js function User(name) { @@ -41,7 +41,7 @@ function User(name) { // this = {}; (неявно) */!* - // add properties to this + // добавляет свойства к this this.name = name; this.isAdmin = false; @@ -51,45 +51,48 @@ function User(name) { } ``` -То есть, результат вызова `new User("Вася")` - это тот же объект, что и: +Таким образом, `let user = new User("Jack")` возвращает тот же результат, что и: ```js let user = { - name: "Вася", + name: "Jack", isAdmin: false }; ``` -Теперь, когда нам необходимо будет создать других пользователей, мы можем использовать `new User("Маша")`, -`new User("Даша")` и т.д. Данная конструкция гораздо удобнее и читабельнее, чем каждый раз создавать литерал объекта. Это и является основной целью конструкторов - удобное повторное создание однотипных объектов. +Теперь, если нам будет необходимо создать других пользователей, мы можем просто вызвать `new User("Ann")`, `new User("Alice")` и так далее. Данная конструкция гораздо удобнее и читабельнее, чем многократное создание литерала объекта. -Еще раз заметим: технически, любая функция может быть использована как конструктор. Поэтому каждая функция может быть вызвана при помощи оператора `new` и выполнит алгоритм, указанный выше в примере. Заглавная буква в названии функции является всеобщим соглашением по именованию, она как бы подсказывает разработчику, что данная функция является функцией-конструктором и вызывается при помощи оператора `new`. +Это и является основной целью конструкторов - реализовать код для многократного создания однотипных объектов. + +Давайте ещё раз отметим - технически любая функция (кроме стрелочных функций, поскольку у них нет `this`) может использоваться в качестве конструктора. Его можно запустить с помощью `new`, и он выполнит выше указанный алгоритм. Подобные функции должны начинаться с заглавной буквы - это общепринятое соглашение, чтобы было ясно, что функция должна вызываться с помощью "new". ````smart header="new function() { ... }" -Если в нашем коде большое количество строк, создающих один сложный объект, мы можем обернуть их в функцию-конструктор следующим образом: +Если в нашем коде присутствует большое количество строк, создающих один сложный объект, то мы можем обернуть их в функцию-конструктор, которая будет немедленно вызвана, вот так: ```js -let user = new function() { - this.name = "Вася"; +// создаём функцию и сразу же вызываем её с помощью new +let user = new function() { + this.name = "John"; this.isAdmin = false; // ...другой код для создания пользователя - // возможна любая сложная логика и выражения - // локальные переменные и т. д. + // возможна любая сложная логика и инструкции + // локальные переменные и так далее }; ``` -Такой конструктор не может быть вызван дважды, так как он нигде не сохраняется, просто создается и тут же вызывается. Таким образом, такой метод создания позволяет инкапсулировать код, который создаёт отдельный объект без дальнейшего пере использования. + +Такой конструктор не может быть вызван снова, так как он нигде не сохраняется, просто создаётся и тут же вызывается. Таким образом, этот трюк направлен на инкапсуляцию кода, который создаёт отдельный объект, без возможности повторного использования в будущем. ```` -## Проверка вызова конструктора: new.target +## Проверка на вызов в режиме конструктора: new.target -```smart header="Проверка вызова конструктора: new.target" -Данный метод используется очень редко. Вы можете пропустить эту секцию, если не хотите углубляться в детали языка. +```smart header="Продвинутая возможность" +Синтаксис из этого раздела используется крайне редко. Вы можете пропустить его, если не хотите углубляться в детали языка. ``` Используя специальное свойство `new.target` внутри функции, мы можем проверить, вызвана ли функция при помощи оператора `new` или без него. -В случае, если функция вызвана при помощи `new`, то в new.target будет сама функция, в противном случае вызов вернёт undefined. +В случае обычного вызова функции `new.target` будет `undefined`. Если же она была вызвана при помощи `new`, `new.target` будет равен самой функции. ```js run function User() { @@ -107,88 +110,85 @@ new User(); // function User { ... } */!* ``` -Это можно использовать, чтобы отличить обычный вызов от вызова "в режиме конструктора". В частности, вот так можно сделать, чтобы функцию можно было вызывать как с, так и без `new`: +Это можно использовать внутри функции, чтобы узнать, была ли она вызвана при помощи `new`, "в режиме конструктора", или без него, "в обычном режиме". + +Также мы можем сделать, чтобы вызовы с `new` и без него делали одно и то же: ```js run function User(name) { - if (!new.target) { // в случае если вызов без оператора new - return new User(name); // ...добавили оператор new за вас + if (!new.target) { // в случае, если вы вызвали меня без оператора new + return new User(name); // ...я добавлю new за вас } - + this.name = name; } -let vasya = User("Вася"); // переадресовываем вызовы на new User -alert(john.name); // Вася +let john = User("John"); // переадресовывает вызов на new User +alert(john.name); // John ``` -Такой подход иногда используется в библиотеках для создания более гибкого синтаксиса, который позволяет разработчикам вызывать функции при помощи оператора `new` или без него. - -Впрочем, это не очень хорошая практика, так как отсутствие new может ввести разработчика в заблуждение. С оператором `new`, мы точно знаем, что в итоге будет создан новый объект. +Такой подход иногда используется в библиотеках, чтобы сделать синтаксис более гибким. Чтобы люди могли вызывать функцию с `new` и без него, и она все ещё могла работать. +Впрочем, вероятно, это не очень хорошая практика использовать этот трюк везде, так как отсутствие `new` может ввести разработчика в заблуждение. С `new` мы точно знаем, что создаётся новый объект. -## Возврат значения из конструктора return +## Возврат значения из конструктора, return -Обычно, конструкторы ничего не возвращают. Их задача, записать все необходимое в this, который в итоге станет результатом. +Обычно конструкторы не имеют оператора `return`. Их задача - записать все необходимое в `this`, и это автоматически становится результатом. -Но если явный вызов `return` всё же есть, то применяется простое правило: +Но если `return` всё же есть, то применяется простое правило: -- При вызове `return` с объектом, будет возвращён объект, а не `this`. -- При вызове `return` с примитивным значением, примитивное значение будет отброшено. +- При вызове `return` с объектом, вместо `this` вернётся объект. +- При вызове `return` с примитивным значением, оно проигнорируется. -Другими словами, `return` с объектом возвращает объект, в любом другом случае конструктор вернёт `this`. +Другими словами, `return` с объектом возвращает этот объект, во всех остальных случаях возвращается `this`. -В примере ниже, `return` переопределяет `this` возвращая объект: +К примеру, здесь `return` замещает `this`, возвращая объект: ```js run function BigUser() { - this.name = "Вася"; + this.name = "John"; - return { name: "Godzilla" }; // <-- возвращает объект + return { name: "Godzilla" }; // <-- возвращает этот объект } -alert( new BigUser().name ); // Godzilla, получили этот объект ^^ +alert( new BigUser().name ); // Godzilla, получили этот объект ``` -А вот пример с пустым `return` (или мы могли бы поставить примитив после return, не важно) +А вот пример с пустым `return` (или мы могли бы поставить примитив после `return`, неважно): ```js run function SmallUser() { - this.name = "Вася"; - - return; // завершает выполнение, возвращает this - - // ... + this.name = "John"; + return; // <-- возвращает this } -alert( new SmallUser().name ); // Вася +alert( new SmallUser().name ); // John ``` -Обычно у конструкторов отсутствует `return`. В данном блоке мы упомянули особое поведение с возвращаемыми объектами, чтобы не оставлять пробелов в изучении языка. - +Обычно у конструкторов отсутствует `return`. Здесь мы упомянули особое поведение с возвращаемыми объектами в основном для полноты картины. -````smart header="Отсутствие скобок" -Кстати, мы можем не ставить скобки после `new`, если вызов конструктора идёт без аргументов. +````smart header="Пропуск скобок" +Кстати, мы можем не ставить круглые скобки после `new`: ```js let user = new User; // <-- без скобок -// same as +// то же, что и let user = new User(); ``` -Пропуск скобок считается Плохой практикой, но синтаксис языка этого не запрещает. +Пропуск скобок считается плохой практикой, но просто чтобы вы знали, такой синтаксис разрешён спецификацией. ```` ## Создание методов в конструкторе -Использование конструкторов для создания объектов даёт большую гибкость. Можно передавать конструктору параметры, определяющие, как создавать объект, и что в него записывать. +Использование конструкторов для создания объектов даёт большую гибкость. Функции-конструкторы могут иметь параметры, определяющие, как создавать объект и что в него записывать. -В `this` мы можем добавлять не только свойства, но и методы. +Конечно, мы можем добавить к `this` не только свойства, но и методы. -Например, в примере ниже, `new User(name)` создаёт объект с данным именем `name` и методом `sayHi`: +Например, `new User(name)` ниже создаёт объект с заданным `name` и методом `sayHi`: ```js run function User(name) { @@ -200,29 +200,32 @@ function User(name) { } *!* -let vasya = new User("Вася"); +let john = new User("John"); -john.sayHi(); // Меня зовут: Вася +john.sayHi(); // Меня зовут: John */!* /* -vasya = { - name: "Вася", +john = { + name: "John", sayHi: function() { ... } } */ ``` +Для создания сложных объектов есть и более продвинутый синтаксис - [классы](info:classes), который мы рассмотрим позже. + ## Итого -- Функции-конструкторы или просто конструкторы являются обычными функциями, именовать которые следует с заглавной буквы. -- Конструкторы необходимо вызывать при помощи оператора `new`. Такой вызов создает пустой `this` в начале выполнения и возвращает заполненный в конце. +- Функции-конструкторы или просто конструкторы, являются обычными функциями, но существует общепринятое соглашение именовать их с заглавной буквы. +- Функции-конструкторы следует вызывать только с помощью `new`. Такой вызов подразумевает создание пустого `this` в начале и возврат заполненного в конце. Мы можем использовать конструкторы для создания множества похожих объектов. -JavaScript предоставляет функции-конструкторы для множества встроенных объектов языка: например, `Date`, `Set` и других, которые нам ещё предстоит изучить. +JavaScript предоставляет функции-конструкторы для множества встроенных объектов языка: таких как `Date`, `Set`, и других, которые нам ещё предстоит изучить. + +```smart header="Мы ещё вернёмся к объектам!" +В этой главе мы рассмотрели только основы объектов и конструкторов. Данная информация необходима нам для дальнейшего изучения типов данных и функций в последующих главах. -```smart header="Объекты, мы к ним еще вернемся!" -В этой главе мы рассмотрели базовые принципы объектов и конструкторов. Данная информация необходима нам для дальнейшего изучения типов данных и функций в следующих главах. Как только мы с ними разберемся, мы вернемся к объектам для более детального изучения в -главах и . +Как только мы с ними разберёмся, мы вернёмся к объектам для более детального изучения в главах и . ``` diff --git a/1-js/04-object-basics/07-optional-chaining/article.md b/1-js/04-object-basics/07-optional-chaining/article.md new file mode 100644 index 0000000000..53005e7356 --- /dev/null +++ b/1-js/04-object-basics/07-optional-chaining/article.md @@ -0,0 +1,235 @@ + +# Опциональная цепочка '?.' + +[recent browser="new"] + +Опциональная цепочка `?.` — это безопасный способ доступа к свойствам вложенных объектов, даже если какое-либо из промежуточных свойств не существует. + +## Проблема "несуществующего свойства" + +Если вы только начали читать учебник и изучать JavaScript, то, возможно, проблема вас ещё не коснулась, но она довольно распространена. + +В качестве примера предположим, что у нас есть объекты `user`, которые содержат информацию о наших пользователях. + +У большинства наших пользователей есть адреса в свойстве `user.address` с улицей `user.address.street`, но некоторые из них их не указали. + +В таком случае, когда мы попытаемся получить `user.address.street`, а пользователь окажется без адреса, мы получим ошибку: + +```js run +let user = {}; // пользователь без свойства "address" + +alert(user.address.street); // Ошибка! +``` + +Это ожидаемый результат. JavaScript работает следующим образом. Поскольку `user.address` имеет значение `undefined`, попытка получить `user.address.street` завершается ошибкой. + +Во многих практических случаях мы бы предпочли получить здесь `undefined` вместо ошибки (что означало бы "улицы нет"). + +...Или ещё один пример. В веб-разработке мы можем получить объект, соответствующий элементу веб-страницы, с помощью специального вызова метода, такого как `document.querySelector('.elem')`, и он возвращает `null`, когда такого элемента нет. + +```js run +// document.querySelector('.elem') равен null, если элемента нет +let html = document.querySelector('.elem').innerHTML; // ошибка, если он равен null +``` + +Ещё раз, если элемент не существует, мы получим сообщение об ошибке доступа к свойству `.innerHTML` у `null`. И в некоторых случаях, когда отсутствие элемента является нормальным, мы хотели бы избежать ошибки и просто принять `html = null` в качестве результата. + +Как мы можем это сделать? + +Очевидным решением было бы проверить значение с помощью `if` или условного оператора `?`, прежде чем обращаться к его свойству, вот так: + +```js +let user = {}; + +alert(user.address ? user.address.street : undefined); +``` + +Это работает, тут нет ошибки... Но это довольно неэлегантно. Как вы можете видеть, `"user.address"` появляется в коде дважды. + +Вот как то же самое выглядело бы для `document.querySelector`: + +```js run +let html = document.querySelector('.elem') ? document.querySelector('.elem').innerHTML : null; +``` + +Как видно, поиск элемента `document.querySelector('.elem')` здесь вызывается дважды, что не очень хорошо. + +Для более глубоко вложенных свойств это ещё менее красиво, поскольку потребуется больше повторений. + +К примеру, давайте аналогично вычислим `user.address.street.name`. + +Нам нужно проверить как `user.address`, так и `user.address.street`: + +```js +let user = {}; // у пользователя нет адреса + +alert(user.address ? user.address.street ? user.address.street.name : null : null); +``` + +Это просто ужасно, у кого-то могут даже возникнуть проблемы с пониманием такого кода. + +Есть немного лучший способ написать это, используя оператор `&&`: + +```js run +let user = {}; // пользователь без адреса + +alert( user.address && user.address.street && user.address.street.name ); // undefined (без ошибки) +``` + +Проход при помощи логического оператора И `&&` через весь путь к свойству гарантирует, что все компоненты существуют (если нет, вычисление прекращается), но также не является идеальным. + +Как вы можете видеть, имена свойств по-прежнему дублируются в коде. Например, в приведённом выше коде `user.address` появляется три раза. + +Вот почему в язык была добавлена опциональная цепочка `?.`. Чтобы решить эту проблему - раз и навсегда! + +## Опциональная цепочка + +Опциональная цепочка `?.` останавливает вычисление и возвращает `undefined`, если значение перед `?.` равно `undefined` или `null`. + +**Далее в этой статье, для краткости, мы будем говорить, что что-то "существует", если оно не является `null` и не `undefined`.** + +Другими словами, `value?.prop`: +- работает как `value.prop`, если значение `value` существует, +- в противном случае (когда `value` равно `undefined/null`) он возвращает `undefined`. + +Вот безопасный способ получить доступ к `user.address.street`, используя `?.`: + +```js run +let user = {}; // пользователь без адреса + +alert( user?.address?.street ); // undefined (без ошибки) +``` + +Код лаконичный и понятный, в нем вообще нет дублирования. + +А вот пример с `document.querySelector`: + +```js run +let html = document.querySelector('.elem')?.innerHTML; // будет undefined, если элемента нет +``` + +Считывание адреса с помощью `user?.address` работает, даже если объект `user` не существует: + +```js run +let user = null; + +alert( user?.address ); // undefined +alert( user?.address.street ); // undefined +``` + +Обратите внимание: синтаксис `?.` делает необязательным значение перед ним, но не какое-либо последующее. + +Так например, в записи `user?.address.street.name` `?.` позволяет `user` безопасно быть `null/undefined` (и в этом случае возвращает `undefined`), но это так только для `user`. Доступ к последующим свойствам осуществляется обычным способом. Если мы хотим, чтобы некоторые из них были необязательными, тогда нам нужно будет заменить больше `.` на `?.`. + +```warn header="Не злоупотребляйте опциональной цепочкой" +Нам следует использовать `?.` только там, где нормально, что чего-то не существует. + +К примеру, если, в соответствии с логикой нашего кода, объект `user` должен существовать, но `address` является необязательным, то нам следует писать `user.address?.street`, но не `user?.address?.street`. + +В этом случае, если вдруг `user` окажется `undefined`, мы увидим программную ошибку по этому поводу и исправим её. В противном случае, если слишком часто использовать `?.`, ошибки могут замалчиваться там, где это неуместно, и их будет сложнее отлаживать. +``` + +````warn header="Переменная перед `?.` должна быть объявлена" +Если переменной `user` вообще нет, то `user?.anything` приведёт к ошибке: + +```js run +// ReferenceError: user is not defined +user?.address; +``` +Переменная должна быть объявлена (к примеру, как `let/const/var user` или как параметр функции). Опциональная цепочка работает только с объявленными переменными. +```` + +## Сокращённое вычисление + +Как было сказано ранее, `?.` немедленно останавливает вычисление, если левая часть не существует. + +Так что если после `?.` есть какие-то вызовы функций или операции, то они не произойдут. + +Например: + +```js run +let user = null; +let x = 0; + +user?.sayHi(x++); // нет "user", поэтому выполнение не достигает вызова sayHi и x++ + +alert(x); // 0, значение не увеличилось +``` + +## Другие варианты применения: ?.(), ?.[] + +Опциональная цепочка `?.` — это не оператор, а специальная синтаксическая конструкция, которая также работает с функциями и квадратными скобками. + +Например, `?.()` используется для вызова функции, которая может не существовать. + +В приведённом ниже коде у некоторых наших пользователей есть метод `admin`, а у некоторых его нет: + +```js run +let userAdmin = { + admin() { + alert("Я админ"); + } +}; + +let userGuest = {}; + +*!* +userAdmin.admin?.(); // Я админ +*/!* + +*!* +userGuest.admin?.(); // ничего не произойдет (такого метода нет) +*/!* +``` + +Здесь в обеих строках мы сначала используем точку (`userAdmin.admin`), чтобы получить свойство `admin`, потому что мы предполагаем, что объект `userAdmin` существует, так что читать из него безопасно. + +Затем `?.()` проверяет левую часть: если функция `admin` существует, то она запускается (это так для `userAdmin`). В противном случае (для `userGuest`) вычисление остановится без ошибок. + +Синтаксис `?.[]` также работает, если мы хотим использовать скобки `[]` для доступа к свойствам вместо точки `.`. Как и в предыдущих случаях, он позволяет безопасно считывать свойство из объекта, который может не существовать. + +```js run +let key = "firstName"; + +let user1 = { + firstName: "John" +}; + +let user2 = null; + +alert( user1?.[key] ); // John +alert( user2?.[key] ); // undefined +``` + +Также мы можем использовать `?.` с `delete`: + +```js run +delete user?.name; // удаляет user.name если пользователь существует +``` + +````warn header="Мы можем использовать `?.` для безопасного чтения и удаления, но не для записи" +Опциональная цепочка `?.` не имеет смысла в левой части присваивания. + +Например: +```js run +let user = null; + +user?.name = "John"; // Ошибка, не работает +// то же самое что написать undefined = "John" +``` + +```` + +## Итого + +Синтаксис опциональной цепочки `?.` имеет три формы: + +1. `obj?.prop` -- возвращает `obj.prop` если `obj` существует, в противном случае `undefined`. +2. `obj?.[prop]` -- возвращает `obj[prop]` если `obj` существует, в противном случае `undefined`. +3. `obj.method?.()` -- вызывает `obj.method()`, если `obj.method` существует, в противном случае возвращает `undefined`. + +Как мы видим, все они просты и понятны в использовании. `?.` проверяет левую часть на `null/undefined` и позволяет продолжить вычисление, если это не так. + +Цепочка `?.` позволяет безопасно получать доступ к вложенным свойствам. + +Тем не менее, мы должны использовать `?.` осторожно, только там, где по логике кода допустимо, что левая часть не существует. Чтобы он не скрывал от нас ошибки программирования, если они возникнут. diff --git a/1-js/04-object-basics/08-symbol/article.md b/1-js/04-object-basics/08-symbol/article.md new file mode 100644 index 0000000000..6edf9869ce --- /dev/null +++ b/1-js/04-object-basics/08-symbol/article.md @@ -0,0 +1,281 @@ + +# Тип данных Symbol + +По спецификации, в качестве ключей для свойств объекта могут использоваться только строки или символы. Ни числа, ни логические значения не подходят, разрешены только эти два типа данных. + +До сих пор мы видели только строки. Теперь давайте разберём символы, увидим, что хорошего они нам дают. + +## Символы + +"Символ" представляет собой уникальный идентификатор. + +Создаются новые символы с помощью функции `Symbol()`: + +```js +// Создаём новый символ - id +let id = Symbol(); +``` + +При создании, символу можно дать описание (также называемое имя), в основном использующееся для отладки кода: + +```js run +// Создаём символ id с описанием (именем) "id" +let id = Symbol("id"); +``` + +Символы гарантированно уникальны. Даже если мы создадим множество символов с одинаковым описанием, это всё равно будут разные символы. Описание -- это просто метка, которая ни на что не влияет. + +Например, вот два символа с одинаковым описанием -- но они не равны: + +```js run +let id1 = Symbol("id"); +let id2 = Symbol("id"); + +*!* +alert(id1 == id2); // false +*/!* +``` + +Если вы знаете Ruby или какой-то другой язык программирования, в котором есть своего рода "символы" -- пожалуйста, будьте внимательны. Символы в JavaScript имеют свои особенности, и не стоит думать о них, как о символах в Ruby или в других языках. + +````warn header="Символы не преобразуются автоматически в строки" +Большинство типов данных в JavaScript могут быть неявно преобразованы в строку. Например, функция `alert` принимает практически любое значение, автоматически преобразовывает его в строку, а затем выводит это значение, не сообщая об ошибке. Символы же особенные и не преобразуются автоматически. + +К примеру, `alert` ниже выдаст ошибку: + +```js run +let id = Symbol("id"); +*!* +alert(id); // TypeError: Cannot convert a Symbol value to a string +*/!* +``` + +Это -- языковая "защита" от путаницы, ведь строки и символы -- принципиально разные типы данных и не должны неконтролируемо преобразовываться друг в друга. + +Если же мы действительно хотим вывести символ с помощью `alert`, то необходимо явно преобразовать его с помощью метода `.toString()`, вот так: + +```js run +let id = Symbol("id"); +*!* +alert(id.toString()); // Symbol(id), теперь работает +*/!* +``` + +Или мы можем обратиться к свойству `symbol.description`, чтобы вывести только описание: + +```js run +let id = Symbol("id"); +*!* +alert(id.description); // id +*/!* +``` + +```` + +## "Скрытые" свойства + +Символы позволяют создавать "скрытые" свойства объектов, к которым нельзя нечаянно обратиться и перезаписать их из других частей программы. + +Например, мы работаем с объектами `user`, которые принадлежат стороннему коду. Мы хотим добавить к ним идентификаторы. + +Используем для этого символьный ключ: + +```js run +let user = { + name: "Вася" +}; + +let id = Symbol("id"); + +user[id] = 1; + +alert( user[id] ); // мы можем получить доступ к данным по ключу-символу +``` + +Почему же лучше использовать `Symbol("id")`, а не строку `"id"`? + +Так как объект `user` принадлежит стороннему коду, и этот код также работает с ним, то нам не следует добавлять к нему какие-либо поля. Это небезопасно. Но к символу сложно нечаянно обратиться, сторонний код вряд ли его вообще увидит, и, скорее всего, добавление поля к объекту не вызовет никаких проблем. + +Кроме того, предположим, что другой скрипт для каких-то своих целей хочет записать собственный идентификатор в объект `user`. Этот скрипт может быть какой-то JavaScript-библиотекой, абсолютно не связанной с нашим скриптом. + +Сторонний код может создать для этого свой символ `Symbol("id")`: + +```js +// ... +let id = Symbol("id"); + +user[id] = "Их идентификатор"; +``` + +Конфликта между их и нашим идентификатором не будет, так как символы всегда уникальны, даже если их имена совпадают. + +А вот если бы мы использовали строку `"id"` вместо символа, то тогда *был бы* конфликт: + +```js run +let user = { name: "Вася" }; + +// Объявляем в нашем скрипте свойство "id" +user.id = "Наш идентификатор"; + +// ...другой скрипт тоже хочет свой идентификатор... + +user.id = "Их идентификатор" +// Ой! Свойство перезаписано сторонней библиотекой! +``` + +### Символы в литеральном объекте + +Если мы хотим использовать символ при литеральном объявлении объекта `{...}`, его необходимо заключить в квадратные скобки. + +Вот так: + +```js +let id = Symbol("id"); + +let user = { + name: "Вася", +*!* + [id]: 123 // просто "id: 123" не сработает +*/!* +}; +``` +Это вызвано тем, что нам нужно использовать значение переменной `id` в качестве ключа, а не строку "id". + +### Символы игнорируются циклом for..in + +Свойства, чьи ключи -- символы, не перебираются циклом `for..in`. + +Например: + +```js run +let id = Symbol("id"); +let user = { + name: "Вася", + age: 30, + [id]: 123 +}; + +*!* +for (let key in user) alert(key); // name, age (свойства с ключом-символом нет среди перечисленных) +*/!* + +// хотя прямой доступ по символу работает +alert( "Напрямую: " + user[id] ); +``` + +Это -- часть общего принципа "сокрытия символьных свойств". Если другая библиотека или скрипт будут работать с нашим объектом, то при переборе они не получат ненароком наше символьное свойство. `Object.keys(user)` также игнорирует символы. + +А вот [Object.assign](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Object/assign), в отличие от цикла `for..in`, копирует и строковые, и символьные свойства: + +```js run +let id = Symbol("id"); +let user = { + [id]: 123 +}; + +let clone = Object.assign({}, user); + +alert( clone[id] ); // 123 +``` + + +Здесь нет никакого парадокса или противоречия. Так и задумано. Идея заключается в том, что, когда мы клонируем или объединяем объекты, мы обычно хотим скопировать *все* свойства (включая такие свойства с ключами-символами, как, например, `id` в примере выше). + +## Глобальные символы + +Итак, как мы видели, обычно все символы уникальны, даже если их имена совпадают. Но иногда мы наоборот хотим, чтобы символы с одинаковыми именами были одной сущностью. Например, разные части нашего приложения хотят получить доступ к символу `"id"`, подразумевая именно одно и то же свойство. + +Для этого существует *глобальный реестр символов*. Мы можем создавать в нём символы и обращаться к ним позже, и при каждом обращении нам гарантированно будет возвращаться один и тот же символ. + +Для чтения (или, при отсутствии, создания) символа из реестра используется вызов `Symbol.for(key)`. + +Он проверяет глобальный реестр и, при наличии в нём символа с именем `key`, возвращает его, иначе же создаётся новый символ `Symbol(key)` и записывается в реестр под ключом `key`. + +Например: + +```js run +// читаем символ из глобального реестра и записываем его в переменную +let id = Symbol.for("id"); // если символа не существует, он будет создан + +// читаем его снова и записываем в другую переменную (возможно, из другого места кода) +let idAgain = Symbol.for("id"); + +// проверяем -- это один и тот же символ +alert( id === idAgain ); // true +``` + +Символы, содержащиеся в реестре, называются *глобальными символами*. Если вам нужен символ, доступный везде в коде - используйте глобальные символы. + +```smart header="Похоже на Ruby" +В некоторых языках программирования, например, Ruby, на одно имя (описание) приходится один символ, и не могут существовать разные символы с одинаковым именем. + +В JavaScript, как мы видим, это утверждение верно только для глобальных символов. +``` + +### Symbol.keyFor + +Для глобальных символов, кроме `Symbol.for(key)`, который ищет символ по имени, существует обратный метод: `Symbol.keyFor(sym)`, который, наоборот, принимает глобальный символ и возвращает его имя. + +К примеру: + +```js run +// получаем символ по имени +let sym = Symbol.for("name"); +let sym2 = Symbol.for("id"); + +// получаем имя по символу +alert( Symbol.keyFor(sym) ); // name +alert( Symbol.keyFor(sym2) ); // id +``` + +Внутри метода `Symbol.keyFor` используется глобальный реестр символов для нахождения имени символа. Так что этот метод не будет работать для неглобальных символов. Если символ неглобальный, метод не сможет его найти и вернёт `undefined`. + +Впрочем, для любых символов доступно свойство `description`. + +Например: + +```js run +let globalSymbol = Symbol.for("name"); +let localSymbol = Symbol("name"); + +alert( Symbol.keyFor(globalSymbol) ); // name, глобальный символ +alert( Symbol.keyFor(localSymbol) ); // undefined для неглобального символа + +alert( localSymbol.description ); // name +``` + +## Системные символы + +Существует множество "системных" символов, использующихся внутри самого JavaScript, и мы можем использовать их, чтобы настраивать различные аспекты поведения объектов. + +Эти символы перечислены в спецификации в таблице [Well-known symbols](https://tc39.github.io/ecma262/#sec-well-known-symbols): + +- `Symbol.hasInstance` +- `Symbol.isConcatSpreadable` +- `Symbol.iterator` +- `Symbol.toPrimitive` +- ...и так далее. + +В частности, `Symbol.toPrimitive` позволяет описать правила для объекта, согласно которым он будет преобразовываться к примитиву. Мы скоро увидим его применение. + +С другими системными символами мы тоже скоро познакомимся, когда будем изучать соответствующие возможности языка. + +## Итого + +Символ (symbol) - примитивный тип данных, использующийся для создания уникальных идентификаторов. + +Символы создаются вызовом функции `Symbol()`, в которую можно передать описание (имя) символа. + +Даже если символы имеют одно и то же имя, это -- разные символы. Если мы хотим, чтобы одноимённые символы были равны, то следует использовать глобальный реестр: вызов `Symbol.for(key)` возвращает (или создаёт) глобальный символ с `key` в качестве имени. Многократные вызовы команды `Symbol.for` с одним и тем же аргументом возвращают один и тот же символ. + +Символы имеют два основных варианта использования: + +1. "Скрытые" свойства объектов. + + Если мы хотим добавить свойство в объект, который "принадлежит" другому скрипту или библиотеке, мы можем создать символ и использовать его в качестве ключа. Символьное свойство не появится в `for..in`, так что оно не будет нечаянно обработано вместе с другими. Также оно не будет модифицировано прямым обращением, так как другой скрипт не знает о нашем символе. Таким образом, свойство будет защищено от случайной перезаписи или использования. + + Так что, используя символьные свойства, мы можем спрятать что-то нужное нам, но что другие видеть не должны. + +2. Существует множество системных символов, используемых внутри JavaScript, доступных как `Symbol.*`. Мы можем использовать их, чтобы изменять встроенное поведение ряда объектов. Например, в дальнейших главах мы будем использовать `Symbol.iterator` для [итераторов](info:iterable), `Symbol.toPrimitive` для настройки [преобразования объектов в примитивы](info:object-toprimitive) и так далее. + +Технически символы скрыты не на 100%. Существует встроенный метод [Object.getOwnPropertySymbols(obj)](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertySymbols) -- с его помощью можно получить все свойства объекта с ключами-символами. Также существует метод [Reflect.ownKeys(obj)](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Reflect/ownKeys), который возвращает *все* ключи объекта, включая символьные. Так что они не совсем спрятаны. Но большинство библиотек, встроенных методов и синтаксических конструкций не используют эти методы. diff --git a/1-js/04-object-basics/09-object-toprimitive/article.md b/1-js/04-object-basics/09-object-toprimitive/article.md new file mode 100644 index 0000000000..ed6a70e668 --- /dev/null +++ b/1-js/04-object-basics/09-object-toprimitive/article.md @@ -0,0 +1,279 @@ + +# Преобразование объектов в примитивы + +Что произойдёт, если сложить два объекта `obj1 + obj2`, вычесть один из другого `obj1 - obj2` или вывести их на экран, воспользовавшись `alert(obj)`? + +JavaScript совершенно не позволяет настраивать, как операторы работают с объектами. В отличие от некоторых других языков программирования, таких как Ruby или C++, мы не можем реализовать специальный объектный метод для обработки сложения (или других операторов). + +В случае таких операций, объекты автоматически преобразуются в примитивы, затем выполняется сама операция над этими примитивами, и на выходе мы получим примитивное значение. + +Это важное ограничение: результатом `obj1 + obj2` (или другой математической операции) не может быть другой объект! + +К примеру, мы не можем создавать объекты, представляющие векторы или матрицы (или достижения или может ещё что-то), складывать их и ожидать в качестве результата "суммированный" объект. Такие архитектурные ходы автоматически оказываются "за бортом". + +Итак, поскольку мы технически здесь мало что можем сделать, в реальных проектах нет математики с объектами. Если она всё же происходит, то за редким исключением, это из-за ошибок в коде. + +В этой главе мы рассмотрим, как объект преобразуется в примитив и как это можно настроить. + +У нас есть две цели: + +1. Это позволит нам понять, что происходит в случае ошибок в коде, когда такая операция произошла случайно. +2. Есть исключения, когда такие операции возможны и вполне уместны. Например, вычитание или сравнение дат (`Date` объекты). Мы встретимся с ними позже. + +## Правила преобразования + +В главе мы рассмотрели правила для числовых, строковых и логических преобразований примитивов. Но мы оставили пробел для объектов. Теперь, когда мы уже знаем о методах и символах, пришло время заполнить этот пробел. + +1. Не существует преобразования к логическому значению. В логическом контексте все объекты являются `true`, всё просто. Существует лишь их числовое и строковое преобразование. +2. Числовое преобразование происходит, когда мы вычитаем объекты или применяем математические функции. Например, объекты `Date` (которые будут рассмотрены в главе ) могут быть вычтены, и результатом `date1 - date2` будет разница во времени между двумя датами. +3. Что касается преобразований к строке -- оно обычно происходит, когда мы выводим на экран объект при помощи `alert(obj)` и в подобных контекстах. + +Мы можем реализовать свои преобразования к строкам и числам, используя специальные объектные методы. + +Теперь давайте углубимся в детали. Это единственный путь для того, чтобы разобраться в нюансах этой темы. + +## Хинты + +Как JavaScript решает, какое преобразование применить? + +Существует три варианта преобразования типов, которые происходят в различных ситуациях. Они называются "хинтами", как описано в [спецификации](https://tc39.github.io/ecma262/#sec-toprimitive): + +`"string"` +: Для преобразования объекта к строке, когда мы выполняем операцию над объектом, которая ожидает строку, например `alert`: + + ```js + // вывод + alert(obj); + + // используем объект в качестве ключа + anotherObj[obj] = 123; + ``` + +`"number"` +: Для преобразования объекта к числу, в случае математических операций: + + ```js + // явное преобразование + let num = Number(obj); + + // математические (не считая бинарного плюса) + let n = +obj; // унарный плюс + let delta = date1 - date2; + + // сравнения больше/меньше + let greater = user1 > user2; + ``` + + Большинство встроенных математических функций также включают в себя такое преобразование. + +`"default"` +: Происходит редко, когда оператор "не уверен", какой тип ожидать. + + Например, бинарный плюс `+` может работать как со строками (объединяя их в одну), так и с числами (складывая их). Поэтому, если бинарный плюс получает объект в качестве аргумента, он использует хинт `"default"` для его преобразования. + + Также, если объект сравнивается с помощью `==` со строкой, числом или символом, тоже неясно, какое преобразование следует выполнить, поэтому используется хинт `"default"`. + + ```js + // бинарный плюс использует хинт "default" + let total = obj1 + obj2; + + // obj == number использует хинт "default" + if (user == 1) { ... }; + ``` + + Операторы сравнения больше/меньше, такие как `<` `>`, также могут работать как со строками, так и с числами. Тем не менее, по историческим причинам, они используют хинт `"number"`, а не `"default"`. + +Впрочем на практике, всё немного проще. + +Все встроенные объекты, за исключением одного (объект `Date`, который мы рассмотрим позже), реализуют `"default"` преобразование тем же способом, что и `"number"`. И нам следует поступать так же. + +**Чтобы выполнить преобразование, JavaScript пытается найти и вызвать три следующих метода объекта:** + +1. Вызвать `obj[Symbol.toPrimitive](hint)` - метод с символьным ключом `Symbol.toPrimitive` (системный символ), если такой метод существует, +2. Иначе, если хинт равен `"string"` + - попробовать вызвать `obj.toString()` или `obj.valueOf()`, смотря какой из них существует. +3. Иначе, если хинт равен `"number"` или `"default"` + - попробовать вызвать `obj.valueOf()` или `obj.toString()`, смотря какой из них существует. + +## Symbol.toPrimitive + +Давайте начнём с первого метода. Есть встроенный символ с именем `Symbol.toPrimitive`, который следует использовать для обозначения метода преобразования, вот так: + +```js +obj[Symbol.toPrimitive] = function(hint) { + // вот код для преобразования этого объекта в примитив + // он должен вернуть примитивное значение + // hint = чему-то из "string", "number", "default" +}; +``` + +Если метод `Symbol.toPrimitive` существует, он используется для всех хинтов, и больше никаких методов не требуется. + +Например, здесь объект `user` реализует его: + +```js run +let user = { + name: "John", + money: 1000, + + [Symbol.toPrimitive](hint) { + alert(`hint: ${hint}`); + return hint == "string" ? `{name: "${this.name}"}` : this.money; + } +}; + +// демонстрация результатов преобразований: +alert(user); // hint: string -> {name: "John"} +alert(+user); // hint: number -> 1000 +alert(user + 500); // hint: default -> 1500 +``` + +Как мы можем видеть из кода, `user` становится либо строкой со своим описанием, либо суммой денег в зависимости от преобразования. Единый метод `user[Symbol.toPrimitive]` обрабатывает все случаи преобразования. + + +## toString/valueOf + +Если нет `Symbol.toPrimitive`, тогда JavaScript пытается найти методы `toString` и `valueOf`: + +- Для хинта `"string"`: вызвать метод `toString`, а если он не существует или возвращает объект вместо примитивного значения, то `valueOf` (таким образом, `toString` имеет приоритет при строковом преобразовании). +- Для других хинтов: вызвать метод `valueOf`, а если он не существует или возвращает объект вместо примитивного значения, то `toString` (таким образом, `valueOf` имеет приоритет для математических операций). + +Методы `toString` и `valueOf` берут своё начало с древних времён. Это не символы (символов тогда ещё не было), а скорее просто "обычные" методы со строковыми именами. Они предоставляют альтернативный "старомодный" способ реализации преобразования. + +Эти методы должны возвращать примитивное значение. Если `toString` или `valueOf` возвращает объект, то он игнорируется (так же, как если бы метода не было). + +По умолчанию обычный объект имеет следующие методы `toString` и `valueOf`: + +- Метод `toString` возвращает строку `"[object Object]"`. +- Метод `valueOf` возвращает сам объект. + +Взгляните на пример: + +```js run +let user = {name: "John"}; + +alert(user); // [object Object] +alert(user.valueOf() === user); // true +``` + +Таким образом, если мы попытаемся использовать объект в качестве строки, как например в `alert` или вроде того, то по умолчанию мы увидим `[object Object]`. + +Значение по умолчанию `valueOf` упоминается здесь только для полноты картины, чтобы избежать какой-либо путаницы. Как вы можете видеть, он возвращает сам объект и поэтому игнорируется. Не спрашивайте меня почему, это по историческим причинам. Так что мы можем предположить, что его не существует. + +Давайте применим эти методы для настройки преобразования. + +Для примера, используем их в реализации всё того же объекта `user`. Но уже используя комбинацию `toString` и `valueOf` вместо `Symbol.toPrimitive`: + +```js run +let user = { + name: "John", + money: 1000, + + // для хинта равного "string" + toString() { + return `{name: "${this.name}"}`; + }, + + // для хинта равного "number" или "default" + valueOf() { + return this.money; + } + +}; + +alert(user); // toString -> {name: "John"} +alert(+user); // valueOf -> 1000 +alert(user + 500); // valueOf -> 1500 +``` + +Как видим, получилось то же поведение, что и в предыдущем примере с `Symbol.toPrimitive`. + +Довольно часто нам нужно единое "универсальное" место для обработки всех примитивных преобразований. В этом случае мы можем реализовать только `toString`: + +```js run +let user = { + name: "John", + + toString() { + return this.name; + } +}; + +alert(user); // toString -> John +alert(user + 500); // toString -> John500 +``` + +В отсутствие `Symbol.toPrimitive` и `valueOf`, `toString` обработает все примитивные преобразования. + +### Преобразование может вернуть любой примитивный тип + +Важная вещь, которую следует знать обо всех методах преобразования примитивов, заключается в том, что они не обязательно возвращают подсказанный хинтом примитив. + +Нет никакого контроля над тем, вернёт ли `toString` именно строку, или чтобы метод `Symbol.toPrimitive` возвращал именно число для хинта `"number"`. + +Единственное обязательное условие: эти методы должны возвращать примитив, а не объект. + +```smart header="Историческая справка" +По историческим причинам, если `toString` или `valueOf` вернёт объект, то ошибки не будет, но такое значение будет проигнорировано (как если бы метода вообще не существовало). Это всё потому, что в древние времена в JavaScript не было хорошей концепции "ошибки". + +А вот `Symbol.toPrimitive` уже "четче", этот метод *обязан* возвращать примитив, иначе будет ошибка. +``` + +## Дальнейшие преобразования + +Как мы уже знаем, многие операторы и функции выполняют преобразования типов, например, умножение `*` преобразует операнды в числа. + +Если мы передаём объект в качестве аргумента, то в вычислениях будут две стадии: +1. Объект преобразуется в примитив (с использованием правил, описанных выше). +2. Если необходимо для дальнейших вычислений, этот примитив преобразуется дальше. + +Например: + +```js run +let obj = { + // toString обрабатывает все преобразования в случае отсутствия других методов + toString() { + return "2"; + } +}; + +alert(obj * 2); // 4, объект был преобразован к примитиву "2", затем умножение сделало его числом +``` + +1. Умножение `obj * 2` сначала преобразует объект в примитив (это строка `"2"`). +2. Затем `"2" * 2` становится `2 * 2` (строка преобразуется в число). + +А вот, к примеру, бинарный плюс в подобной ситуации соединил бы строки, так как он совсем не брезгует строк: + +```js run +let obj = { + toString() { + return "2"; + } +}; + +alert(obj + 2); // "22" ("2" + 2), преобразование к примитиву вернуло строку => конкатенация +``` + +## Итого + +Преобразование объекта в примитив вызывается автоматически многими встроенными функциями и операторами, которые ожидают примитив в качестве значения. + +Существует всего 3 типа (хинта) для этого: +- `"string"` (для `alert` и других операций, которым нужна строка) +- `"number"` (для математических операций) +- `"default"` (для некоторых других операторов, обычно объекты реализуют его как `"number"`) + +Спецификация явно описывает для каждого оператора, какой ему следует использовать хинт. + +Алгоритм преобразования таков: + +1. Сначала вызывается метод `obj[Symbol.toPrimitive](hint)`, если он существует, +2. В случае, если хинт равен `"string"` + - происходит попытка вызвать `obj.toString()` и `obj.valueOf()`, смотря что есть. +3. В случае, если хинт равен `"number"` или `"default"` + - происходит попытка вызвать `obj.valueOf()` и `obj.toString()`, смотря что есть. + +Все эти методы должны возвращать примитив (если определены). + +На практике часто бывает достаточно реализовать только `obj.toString()` в качестве универсального метода для преобразований к строке, который должен возвращать удобочитаемое представление объекта для целей логирования или отладки. diff --git a/1-js/04-object-basics/index.md b/1-js/04-object-basics/index.md index d2387aafa7..804e1da057 100644 --- a/1-js/04-object-basics/index.md +++ b/1-js/04-object-basics/index.md @@ -1 +1 @@ -# Objects: the basics +# Объекты: основы diff --git a/1-js/05-data-types/01-primitives-methods/1-string-new-property/solution.md b/1-js/05-data-types/01-primitives-methods/1-string-new-property/solution.md index c8d824ff99..f0892c22c0 100644 --- a/1-js/05-data-types/01-primitives-methods/1-string-new-property/solution.md +++ b/1-js/05-data-types/01-primitives-methods/1-string-new-property/solution.md @@ -9,17 +9,15 @@ str.test = 5; // (*) alert(str.test); ``` -В зависимости от того, используете ли вы cтрогий режим (`use strict`) или нет, результат может быть: +В зависимости от того, используете ли вы строгий режим (`use strict`) или нет, результат может быть: 1. `undefined` (без strict) 2. Ошибка (strict mode) Почему? Давайте посмотрим что происходит в строке кода, отмеченной `(*)`: -1. В момент обращения к свойству `str` создается "объект-обертка". -2. В cтрогом режиме, попытка изменения этого объекта выдает ошибку. -3. В стандартном режиме, операция продолжается, объект получает свойство `test`, но после этого "объект-обертка" удаляется. - -Выходит, в стандартном режиме на последней линии `str` больше не имеет свойства `test`. +1. В момент обращения к свойству `str` создаётся "объект-обёртка". +2. В строгом режиме, попытка изменения этого объекта выдаёт ошибку. +3. Без строгого режима, операция продолжается, объект получает свойство `test`, но после этого он удаляется, так что на последней линии `str` больше не имеет свойства `test`. **Данный пример наглядно показывает, что примитивы не являются объектами.** diff --git a/1-js/05-data-types/01-primitives-methods/article.md b/1-js/05-data-types/01-primitives-methods/article.md index dc0c569de7..ba94c8f491 100644 --- a/1-js/05-data-types/01-primitives-methods/article.md +++ b/1-js/05-data-types/01-primitives-methods/article.md @@ -1,22 +1,20 @@ -# Методы с примитивными типами данных +# Методы примитивов -JavaScript позволяет нам работать с примитивными типами данных (далее: примитивы) - строки, числа, и т.д., так, как если бы они были объектами. - -Они даже предоставляют методы. Мы изучим их позже, а сначала разберем, как это все работает, потому что, конечно, примитивы - не объекты. +JavaScript позволяет нам работать с примитивными типами данных - строками, числами и т.д., как будто они являются объектами. У них есть и методы. Мы изучим их позже, а сначала разберём, как это всё работает, потому что, конечно, примитивы - не объекты. Давайте взглянем на ключевые различия между примитивами и объектами. Примитив -- Это значение «примитивного» типа. -- Есть 6 примитивных типов: `string`, `number`, `boolean`, `symbol`, `null` и `undefined`. +- Это - значение «примитивного» типа. +- Есть 7 примитивных типов: `string`, `number`, `boolean`, `symbol`, `null`, `undefined` и `bigint`. Объект - Может хранить множество значений как свойства. - Объявляется при помощи фигурных скобок `{}`, например: `{name: "Рома", age: 30}`. В JavaScript есть и другие виды объектов: например, функции тоже являются объектами. -Одна из лучших особенностей объектов это то, что мы можем хранить функцию как одно из свойств объекта. +Одна из лучших особенностей объектов - это то, что мы можем хранить функцию как одно из свойств объекта. ```js run let roma = { @@ -35,26 +33,26 @@ roma.sayHi(); // Привет, дружище! Однако у этих возможностей есть обратная сторона! -Объекты «тяжелее» примитивов. Они нуждаются в дополнительных ресурсах для поддержания внутренней структуры. Но так как свойства и методы очень полезны в программировании, движки JavaScript оптимизируют их для уменьшения дополнительной нагрузки. +Объекты «тяжелее» примитивов. Они нуждаются в дополнительных ресурсах для поддержания внутренней структуры. ## Примитив как объект Вот парадокс, с которым столкнулся создатель JavaScript: -- Есть много всего, что хотелось бы сделать с примитивами, такими как строка или число. Было бы замечательно, если бы мы могли работать с ними через вызовы методов. -- Примитивы должны быть как можно легкими и быстрыми. +- Есть много всего, что хотелось бы сделать с примитивами, такими как строка или число. Было бы замечательно, если бы мы могли обращаться к ним при помощи методов. +- Примитивы должны быть лёгкими и быстрыми насколько это возможно. -Вот решение, хотя выглядит оно немного неуклюже: +Выбранное решение, хотя выглядит оно немного неуклюже: 1. Примитивы остаются примитивами. Одно значение, как и хотелось. -2. Язык позволяет иметь доступ к методам и свойствам строк, чисел, булевых значений и символов. -3. Чтобы это работало, создается специальный "объект-обертка", который предоставляет дополнительную функциональность, а после удаляется. +2. Язык позволяет осуществлять доступ к методам и свойствам строк, чисел, булевых значений и символов. +3. Чтобы это работало, при таком доступе создаётся специальный "объект-обёртка", который предоставляет нужную функциональность, а после удаляется. -Каждый примитив имеет свой собственный "объект-обертку", которые называются: `String`, `Number`, `Boolean` и `Symbol`. Таким образом, они имеют разный набор методов. +Каждый примитив имеет свой собственный "объект-обёртку", которые называются: `String`, `Number`, `Boolean`, `Symbol` и `BigInt`. Таким образом, они имеют разный набор методов. -К примеру, существует метод [str.toUpperCase()](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/String/toUpperCase), который возвращает cтроку в верхнем регистре. +К примеру, существует метод [str.toUpperCase()](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/String/toUpperCase), который возвращает строку в верхнем регистре. -Вот как он работает: +Вот, как он работает: ```js run let str = "Привет"; @@ -64,27 +62,27 @@ alert( str.toUpperCase() ); // ПРИВЕТ Очень просто, не правда ли? Вот, что на самом деле происходит в `str.toUpperCase()`: -1. Строка `str` - примитив. В момент обращения к его свойству, создается специальный объект, который знает значение строки и имеет такие полезные методы, как `toUpperCase()`. +1. Строка `str` - примитив. В момент обращения к его свойству, создаётся специальный объект, который знает значение строки и имеет такие полезные методы, как `toUpperCase()`. 2. Этот метод запускается и возвращает новую строку (показывается в `alert`). 3. Специальный объект удаляется, оставляя только примитив `str`. -Получается, что примитивы могут предоставлять методы, и в то же время оставаться «лёгкими». +Получается, что примитивы могут предоставлять методы, и в то же время оставаться "лёгкими". -Движок JavaScript очень оптимизирует этот процесс. Он даже может пропустить создание специального объекта. Однако, он все же должен придерживаться спецификаций и работать так, как будто он его создает. +Движок JavaScript сильно оптимизирует этот процесс. Он даже может пропустить создание специального объекта. Однако, он всё же должен придерживаться спецификаций и работать так, как будто он его создаёт. Число имеет собственный набор методов. Например, [toFixed(n)](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Number/toFixed) округляет число до n знаков после запятой. ```js run -let n = 1.23456; +let num = 1.23456; -alert( n.toFixed(2) ); // 1.23 +alert( num.toFixed(2) ); // 1.23 ``` Более подробно с различными свойствами и методами мы познакомимся в главах и . ````warn header="Конструкторы `String/Number/Boolean` предназначены только для внутреннего пользования" -Некоторые языки, такие как Java позволяют создание "объектов-оберток" для примитивов при помощи такого синтаксиса как `new Number(1)` или `new Boolean(false)`. +Некоторые языки, такие как Java, позволяют явное создание "объектов-обёрток" для примитивов при помощи такого синтаксиса как `new Number(1)` или `new Boolean(false)`. В JavaScript, это тоже возможно по историческим причинам, но очень **не рекомендуется**. В некоторых местах последствия могут быть катастрофическими. @@ -96,7 +94,7 @@ alert( typeof 0 ); // "число" alert( typeof new Number(0) ); // "object"! ``` -Объекты в if всегда дают true, так что в примере ниже будет показан alert: +Объекты в `if` всегда дают `true`, так что в нижеприведённом примере будет показан `alert`: ```js run let zero = new Number(0); @@ -107,7 +105,7 @@ if (zero) { } ``` -С другой стороны, использование функций `String/Number/Boolean` без оператора `new` - вполне разумно и полезно. Они превращают значение в соответствующий примитивный тип: в строку, в число, в булевой тип. +С другой стороны, использование функций `String/Number/Boolean` без оператора `new` - вполне разумно и полезно. Они превращают значение в соответствующий примитивный тип: в строку, в число, в булевый тип. К примеру, следующее вполне допустимо: @@ -118,7 +116,7 @@ let num = Number("123"); // превращает строку в число ````warn header="null/undefined не имеют методов" -Особенные примитивы `null` and `undefined` являются исключениями. У них нет соответствующих "объектов-оберток" и они не имеют никаких методов. В некотором смысле, они "самые примитивные". +Особенные примитивы `null` и `undefined` являются исключениями. У них нет соответствующих "объектов-обёрток", и они не имеют никаких методов. В некотором смысле, они "самые примитивные". Попытка доступа к свойствам такого значения возвратит ошибку: ```js run @@ -127,5 +125,5 @@ alert(null.test); // ошибка ## Итого -- Все примитивы кроме `null` и `undefined` предоставляют множество полезных методов. Мы познакомимся с ними поближе в следующих главах. -- Формально, эти методы работают с помощью временных объектов, но движки JavaScript очень хорошо оптимизируют этот процесс внутренне, так что их вызов не требует много ресурсов. +- Все примитивы, кроме `null` и `undefined`, предоставляют множество полезных методов. Мы познакомимся с ними поближе в следующих главах. +- Формально эти методы работают с помощью временных объектов, но движки JavaScript внутренне очень хорошо оптимизируют этот процесс, так что их вызов не требует много ресурсов. diff --git a/1-js/05-data-types/02-number/1-sum-interface/solution.md b/1-js/05-data-types/02-number/1-sum-interface/solution.md index d00deffd6c..e41013d317 100644 --- a/1-js/05-data-types/02-number/1-sum-interface/solution.md +++ b/1-js/05-data-types/02-number/1-sum-interface/solution.md @@ -9,4 +9,4 @@ alert( a + b ); Обратите внимание, что мы использовали унарный оператор `+` перед `prompt`, он преобразует значение в числовой формат. -В противном случае, `a` и `b` будут строками, и после суммирования произойдет конкатенация двух строк, а именно: `"1" + "2" = "12"`. +В противном случае, `a` и `b` будут строками, и после суммирования произойдёт конкатенация двух строк, а именно: `"1" + "2" = "12"`. diff --git a/1-js/05-data-types/02-number/2-why-rounded-down/solution.md b/1-js/05-data-types/02-number/2-why-rounded-down/solution.md index 020656031f..cde03da773 100644 --- a/1-js/05-data-types/02-number/2-why-rounded-down/solution.md +++ b/1-js/05-data-types/02-number/2-why-rounded-down/solution.md @@ -14,7 +14,7 @@ alert( 6.35.toFixed(20) ); // 6.34999999999999964473 alert( 1.35.toFixed(20) ); // 1.35000000000000008882 ``` -Тут потеря точности приводит к увеличению числа, поэтому округление произойдет в большую сторону. +Тут потеря точности приводит к увеличению числа, поэтому округление произойдёт в большую сторону. **Каким образом можно исправить ошибку в округлении числа `6.35`?** @@ -24,10 +24,10 @@ alert( 1.35.toFixed(20) ); // 1.35000000000000008882 alert( (6.35 * 10).toFixed(20) ); // 63.50000000000000000000 ``` -Обратите внимание, что для числа `63.5` не происходит потери точности. Дело в том, что десятичная часть `0.5` на самом деле `1/2`. Дробные числа, деленные на степень `2`, точно представлены в двоичной системе, теперь мы можем округлить число: +Обратите внимание, что для числа `63.5` не происходит потери точности. Дело в том, что десятичная часть `0.5` на самом деле `1/2`. Дробные числа, делённые на степень `2`, точно представлены в двоичной системе, теперь мы можем округлить число: ```js run -alert( Math.round(6.35 * 10) / 10); // 6.35 -> 63.5 -> 64(rounded) -> 6.4 +alert( Math.round(6.35 * 10) / 10 ); // 6.35 -> 63.5 -> 64(rounded) -> 6.4 ``` diff --git a/1-js/05-data-types/02-number/3-repeat-until-number/_js.view/test.js b/1-js/05-data-types/02-number/3-repeat-until-number/_js.view/test.js index 219fa8068e..6bd0123dba 100644 --- a/1-js/05-data-types/02-number/3-repeat-until-number/_js.view/test.js +++ b/1-js/05-data-types/02-number/3-repeat-until-number/_js.view/test.js @@ -18,7 +18,7 @@ describe("readNumber", function() { assert.strictEqual(readNumber(), 0); }); - it("continues the loop unti meets a number", function() { + it("continues the loop until meets a number", function() { prompt.onCall(0).returns("not a number"); prompt.onCall(1).returns("not a number again"); prompt.onCall(2).returns("1"); @@ -35,4 +35,4 @@ describe("readNumber", function() { assert.isNull(readNumber()); }); -}); \ No newline at end of file +}); diff --git a/1-js/05-data-types/02-number/3-repeat-until-number/solution.md b/1-js/05-data-types/02-number/3-repeat-until-number/solution.md index 55dcf46dd8..d9ce49b99b 100644 --- a/1-js/05-data-types/02-number/3-repeat-until-number/solution.md +++ b/1-js/05-data-types/02-number/3-repeat-until-number/solution.md @@ -17,7 +17,7 @@ alert(`Число: ${readNumber()}`); Решение немного сложнее, чем могло бы быть, потому что нам надо обрабатывать `null` и пустую строку. -Следовательно, запрашиваем ввод числового значения, пока посетитель его не введет. И `null` (отмена) и пустая строка также соответствуют данному условию, потому что при приведении к числу они равны `0`. +Следовательно, запрашиваем ввод числового значения, пока посетитель его не введёт. И `null` (отмена) и пустая строка также соответствуют данному условию, потому что при приведении к числу они равны `0`. -После того, как цикл завершится, нам нужно проверить введенное значение на `null` и пустую строку (вернуть `null`), потому что после преобразования `null` в число, функция вернет `0`. +После того, как цикл завершится, нам нужно проверить введённое значение на `null` и пустую строку (вернуть `null`), потому что после преобразования `null` в число, функция вернёт `0`. diff --git a/1-js/05-data-types/02-number/3-repeat-until-number/task.md b/1-js/05-data-types/02-number/3-repeat-until-number/task.md index 0dc51acaa5..c5f201fa0d 100644 --- a/1-js/05-data-types/02-number/3-repeat-until-number/task.md +++ b/1-js/05-data-types/02-number/3-repeat-until-number/task.md @@ -4,7 +4,7 @@ importance: 5 # Ввод числового значения -Создайте функцию `readNumber`, которая будет запрашивать ввод числового значения до тех пор, пока посетитель его не введет. +Создайте функцию `readNumber`, которая будет запрашивать ввод числового значения до тех пор, пока посетитель его не введёт. Функция должна возвращать числовое значение. diff --git a/1-js/05-data-types/02-number/9-random-int-min-max/solution.md b/1-js/05-data-types/02-number/9-random-int-min-max/solution.md index 88336cf218..da814a2789 100644 --- a/1-js/05-data-types/02-number/9-random-int-min-max/solution.md +++ b/1-js/05-data-types/02-number/9-random-int-min-max/solution.md @@ -13,7 +13,7 @@ alert( randomInteger(1, 3) ); Функция будет работать, но неправильно. Вероятность получить `min` и `max` значения в 2 раза меньше, чем любое другое число. -Если вы запустите приведенный выше пример, то заметите, что `2` появляется чаще всего. +Если вы запустите приведённый выше пример, то заметите, что `2` появляется чаще всего. Это происходит потому, что метод `Math.round()` получает случайные числа из интервала `1..3` и округляет их следующим образом: @@ -23,25 +23,11 @@ alert( randomInteger(1, 3) ); число от 2.5 ... до 2.9999999999 округлится до 3 ``` -Теперь становится понятно, что `1` получает в 2 раза меньше значений, чем `2`. Тоже самое с `3`. +Теперь становится понятно, что `1` получает в 2 раза меньше значений, чем `2`. То же самое с `3`. # Правильное решение задачи -Есть много правильных решений этой задачи. Одно из них - правильно указать границы интервала. Чтобы выровнять интервалы, мы можем генерировать числа от `0.5 до 3.5`, это позволит добавить *необходимые вероятности* к `min` и `max`: - -```js run -*!* -function randomInteger(min, max) { - // получить случайное число от (min-0.5) до (max+0.5) - let rand = min - 0.5 + Math.random() * (max - min + 1); - return Math.round(rand); -} -*/!* - -alert( randomInteger(1, 3) ); -``` - -Другое правильное решение - это использовать `Math.floor` для получения случайного числа от `min` до `max+1`: +Есть много правильных решений этой задачи. Одно из них - использовать `Math.floor` для получения случайного числа от `min` до `max+1`: ```js run *!* diff --git a/1-js/05-data-types/02-number/article.md b/1-js/05-data-types/02-number/article.md index f6aefe4839..28bf6154db 100644 --- a/1-js/05-data-types/02-number/article.md +++ b/1-js/05-data-types/02-number/article.md @@ -1,8 +1,10 @@ # Числа -Все числа в JavaScript хранятся в 64-битном формате [IEEE-754](http://en.wikipedia.org/wiki/IEEE_754-1985), который также называют "числа с плавающей точкой двойной точности" (double precision floating point numbers). +В современном JavaScript существует два типа чисел: +1. Обычные числа в JavaScript хранятся в 64-битном формате [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754-1985), который также называют "числа с плавающей точкой двойной точности" (double precision floating point numbers). Это числа, которые мы будем использовать чаще всего. Мы поговорим о них в этой главе. +2. `BigInt` числа дают возможность работать с целыми числами произвольной длины. Они нужны достаточно редко и используются в случаях, когда необходимо работать со значениями более чем (253-1) или менее чем -(253-1). Так как `BigInt` числа нужны достаточно редко, мы рассмотрим их в отдельной главе . -Давайте повторим и глубже изучим, как работать с числами в JavaScript. +В данной главе мы рассмотрим только первый тип чисел: числа типа `number`. Давайте глубже изучим, как с ними работать в JavaScript. ## Способы записи числа @@ -12,70 +14,76 @@ let billion = 1000000000; ``` -Но в реальной жизни мы обычно опускаем запись множества нулей, так как можно легко ошибиться. Укороченная запись может выглядеть как `"1млрд"` или `"7.3млрд"` для 7 миллиардов 300 миллионов. Такой принцип работает для всех больших чисел. +Мы также можем использовать символ нижнего подчёркивания `_` в качестве разделителя: -В JavaScript можно использовать букву `"e"`, чтобы укоротить запись числа. Она добавляется к числу и заменяет указанное количество нулей: +```js +let billion = 1_000_000_000 +``` + +Символ нижнего подчёркивания `_` -- это «[синтаксический сахар](https://ru.wikipedia.org/wiki/Синтаксический_сахар)», он делает число более читабельным. Движок JavaScript попросту игнорирует `_` между цифрами, поэтому в примере выше получается точно такой же миллиард, как и в первом случае. + +Однако в реальной жизни мы в основном стараемся не писать длинные последовательности нулей, так как можно легко ошибиться. Укороченная запись может выглядеть как `"1млрд"` или `"7.3млрд"` для 7 миллиардов 300 миллионов. Такой принцип работает для всех больших чисел. + +В JavaScript, чтобы укоротить запись числа, мы можем добавить к нему букву `"e"` и указать необходимое количество нулей: ```js run let billion = 1e9; // 1 миллиард, буквально: 1 и 9 нулей -alert( 7.3e9 ); // 7.3 миллиардов (7,300,000,000) +alert( 7.3e9 ); // 7.3 миллиарда (7,300,000,000) ``` -Другими словами, `"e"` производит операцию умножения числа на 1 с указанным количеством нулей. +Другими словами, `"e"` умножает число на `1` с указанным количеством нулей. ```js -1e3 = 1 * 1000 -1.23e6 = 1.23 * 1000000 +1e3 === 1 * 1000 // e3 означает *1000 +1.23e6 === 1.23 * 1000000 // e6 означает *1000000 ``` - -Сейчас давайте запишем что-нибудь очень маленькое. К примеру, 1 микросекунду (одна милионная секунды): +А сейчас давайте запишем что-нибудь очень маленькое. К примеру, 1 микросекунду (одна миллионная секунды): ```js -let ms = 0.000001; +let mcs = 0.000001; ``` -Записать микросекунду в укороченном виде нам поможет `"e"`. +В этом случае нам также поможет `"e"`. Если мы хотим избежать записи длинной последовательности из нулей, мы можем сделать так: ```js -let ms = 1e-6; // шесть нулей, слева от 1 +let ms = 1e-6; // шесть нулей слева от 1 ``` -Если мы подсчитаем количество нулей `0.000001`, их будет 6. Естественно, верная запись `1e-6`. +Если мы подсчитаем количество нулей в `0.000001`, их будет 6. Естественно, верная запись `1e-6`. -Другими словами, отрицательное число после `"e"` подразумевает деление на 1 с указанным количеством нулей: +Другими словами, отрицательное число после `"e"` подразумевает деление на `1` с указанным количеством нулей: ```js -// -3 делится на 1 с 3 нулями -1e-3 = 1 / 1000 (=0.001) +// 1 делится на 1 с 3 нулями +1e-3 === 1 / 1000 (=0.001) -// -6 делится на 1 с 6 нулями -1.23e-6 = 1.23 / 1000000 (=0.00000123) +// 1.23 делится на 1 с 6 нулями +1.23e-6 === 1.23 / 1000000 (=0.00000123) ``` ### Шестнадцатеричные, двоичные и восьмеричные числа -[Шестнадцатеричные](https://ru.wikipedia.org/wiki/%D0%A8%D0%B5%D1%81%D1%82%D0%BD%D0%B0%D0%B4%D1%86%D0%B0%D1%82%D0%B5%D1%80%D0%B8%D1%87%D0%BD%D0%B0%D1%8F_%D1%81%D0%B8%D1%81%D1%82%D0%B5%D0%BC%D0%B0_%D1%81%D1%87%D0%B8%D1%81%D0%BB%D0%B5%D0%BD%D0%B8%D1%8F) числа широко используются в JavaScript для представления цветов, кодировки символов и многое другое. Естественно, есть короткий стиль записи: `0x`, после которого указывается число. +[Шестнадцатеричные](https://ru.wikipedia.org/wiki/%D0%A8%D0%B5%D1%81%D1%82%D0%BD%D0%B0%D0%B4%D1%86%D0%B0%D1%82%D0%B5%D1%80%D0%B8%D1%87%D0%BD%D0%B0%D1%8F_%D1%81%D0%B8%D1%81%D1%82%D0%B5%D0%BC%D0%B0_%D1%81%D1%87%D0%B8%D1%81%D0%BB%D0%B5%D0%BD%D0%B8%D1%8F) числа широко используются в JavaScript для представления цветов, кодировки символов и многого другого. Естественно, есть короткий стиль записи: `0x`, после которого указывается число. Например: ```js run alert( 0xff ); // 255 -alert( 0xFF ); // 255 (тоже самое, регистр не имеет значения) +alert( 0xFF ); // 255 (то же самое, регистр не имеет значения) ``` -Не так часто используются двоичные и восьмеричные числа, но они также поддерживаются `0b` для двоичных и `0o` для восьмеричных: - +Двоичные и восьмеричные числа используются не так часто, но они также поддерживаются: `0b` для двоичных и `0o` для восьмеричных: ```js run -let a = 0b11111111; // бинарная форма записи числа 255 +let a = 0b11111111; // двоичная (бинарная) форма записи числа 255 let b = 0o377; // восьмеричная форма записи числа 255 alert( a == b ); // true, с двух сторон число 255 ``` -Есть только 3 системы счисления с такой поддержкой. Для других систем счисления мы рекомендуем использовать функцию `parseInt` (подробнее рассмотрим позже в этой главе). +Есть только 3 системы счисления с такой поддержкой. Для других систем счисления мы рекомендуем использовать функцию `parseInt` (рассмотрим позже в этой главе). ## toString(base) @@ -102,9 +110,9 @@ alert( num.toString(2) ); // 11111111 ``` ```warn header="Две точки для вызова метода" -Внимание! Две точки в `123456..toString(36)` это не опечатка. Если нам надо использовать метод непосредственно на числе, как `toString` в примере выше, то нам надо поставить две точки `..` после числа. +Внимание! Две точки в `123456..toString(36)` это не опечатка. Если нам надо вызвать метод непосредственно на числе, как `toString` в примере выше, то нам надо поставить две точки `..` после числа. -Если мы поставим одну точку: `123456.toString(36)`, тогда это будет ошибкой, поскольку синтаксис JavaScript предполагает, что начинается десятичная часть после первой точки. А если поставить две точки, то JavaScript понимает, что десятичная часть отсутствует и начинается метод. +Если мы поставим одну точку: `123456.toString(36)`, тогда это будет ошибкой, поскольку синтаксис JavaScript предполагает, что после первой точки начинается десятичная часть. А если поставить две точки, то JavaScript понимает, что десятичная часть отсутствует, и начинается метод. Также можно записать как `(123456).toString(36)`. ``` @@ -149,7 +157,7 @@ alert( num.toString(2) ); // 11111111 ```js run let num = 1.23456; - alert( Math.floor(num * 100) / 100 ); // 1.23456 -> 123.456 -> 123 -> 1.23 + alert( Math.round(num * 100) / 100 ); // 1.23456 -> 123.456 -> 123 -> 1.23 ``` 2. Метод [toFixed(n)](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Number/toFixed) округляет число до `n` знаков после запятой и возвращает строковое представление результата. @@ -177,9 +185,9 @@ alert( num.toString(2) ); // 11111111 ## Неточные вычисления -Внутри JavaScript число представлено в виде 64-битного формата [IEEE-754](https://ru.wikipedia.org/wiki/IEEE_754-1985). Итак, для хранения числа используется 64 бита: 52 из них используется для хранения цифр, 11 из них для хранения положения десятичной точки (если число целое, то хранится 0), и один бит отведен на хранения знака. +Внутри JavaScript число представлено в виде 64-битного формата [IEEE-754](https://ru.wikipedia.org/wiki/IEEE_754-1985). Для хранения числа используется 64 бита: 52 из них используется для хранения цифр, 11 для хранения положения десятичной точки и один бит отведён на хранение знака. -Если число слишком большое, оно переполнит 64-битное хранилище, JavaScript вернет бесконечность: +Если число слишком большое, оно переполнит 64-битное хранилище, JavaScript вернёт бесконечность: ```js run alert( 1e500 ); // Infinity @@ -205,15 +213,21 @@ alert( 0.1 + 0.2 ); // 0.30000000000000004 Но почему это происходит? -Число хранится в памяти в бинарной форме, как последовательность бит - единиц и нулей. Но дроби как `0.1`, `0.2`, которые выглядят довольно просто в десятичной системе счисления, на самом деле являются бесконечной дробью в двоичной форме. +Число хранится в памяти в бинарной форме, как последовательность бит - единиц и нулей. Но дроби, такие как `0.1`, `0.2`, которые выглядят довольно просто в десятичной системе счисления, на самом деле являются бесконечной дробью в двоичной форме. -Другими словами, что такое `0.1`? Это единица делённая на десять — `1/10`, одна десятая. В десятичной системе счисления такие числа легко представимы. По сравнению с одной третьей: `1/3`, которая становится бесконечной дробью `0.33333(3)`. +```js run +alert(0.1.toString(2)); // 0.0001100110011001100110011001100110011001100110011001101 +alert(0.2.toString(2)); // 0.001100110011001100110011001100110011001100110011001101 +alert((0.1 + 0.2).toString(2)); // 0.0100110011001100110011001100110011001100110011001101 +``` + +Другими словами, что такое `0.1`? Это единица делённая на десять — `1/10`, одна десятая. В десятичной системе счисления такие числа легко представимы, по сравнению с одной третьей: `1/3`, которая становится бесконечной дробью `0.33333(3)`. -Таким образом, деление на `10` гарантированно хорошо работает в десятичной системе, но деление на `3` нет. По той же причине и в двоичной системе счисления, деление на `2` обязательно сработает, а `1/10` становится бесконечной дробью. +Деление на `10` гарантированно хорошо работает в десятичной системе, но деление на `3` - нет. По той же причине и в двоичной системе счисления, деление на `2` обязательно сработает, а `1/10` становится бесконечной дробью. -В JavaScript нет возможности для хранения точных значений *0.1* или *0.2*, используя двоичную систему, точно также, как нет возможности хранить одну третью в десятичной системе счисления. +В JavaScript нет возможности для хранения точных значений 0.1 или 0.2, используя двоичную систему, точно также, как нет возможности хранить одну третью в десятичной системе счисления. -Числовой формат IEEE-754 решает эту проблему путём округления до ближайшего возможного числа. Эти правила округления обычно не позволяют нам увидеть эту "крошечную потерю точности", поэтому число отображается как `0.3`. Но имейте в виду: неточность сохраняется. +Числовой формат IEEE-754 решает эту проблему путём округления до ближайшего возможного числа. Правила округления обычно не позволяют нам увидеть эту "крошечную потерю точности", но она существует. Пример: ```js run @@ -222,17 +236,17 @@ alert( 0.1.toFixed(20) ); // 0.10000000000000000555 И когда мы суммируем 2 числа, их "неточности" тоже суммируются. -Вот почему `0.1 + 0.2` это не совсем `0.3`. +Вот почему `0.1 + 0.2` - это не совсем `0.3`. ```smart header="Не только в JavaScript" -Справедливости ради заметим, что ошибка в точности вычислений для чисел с плавающей точкой сохраняется в любом другом языке, где используется формат IEEE 754, включая PHP, Java, C, Perl, Ruby. +Справедливости ради заметим, что ошибка в точности вычислений для чисел с плавающей точкой сохраняется в любом другом языке, где используется формат IEEE 754, включая PHP, Java, C, Perl и Ruby. ``` -Можно ли обойти проблему? Конечно, наиболее надежный способ — это округлить результат используя метод [toFixed(n)](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Number/toFixed): +Можно ли обойти проблему? Конечно, наиболее надёжный способ — это округлить результат используя метод [toFixed(n)](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Number/toFixed): ```js run let sum = 0.1 + 0.2; -alert( sum.toFixed(2) ); // 0.30 +alert( sum.toFixed(2) ); // "0.30" ``` Помните, что метод `toFixed` всегда возвращает строку. Это гарантирует, что результат будет с заданным количеством цифр в десятичной части. Также это удобно для форматирования цен в интернет-магазине `$0.30`. В других случаях можно использовать унарный оператор `+`, чтобы преобразовать строку в число: @@ -242,16 +256,16 @@ let sum = 0.1 + 0.2; alert( +sum.toFixed(2) ); // 0.3 ``` -Также можно временно умножить число на 100 (или на большее), чтобы привести его к целому, выполнить математические действия, а после разделить обратно. Суммируя целые числа, мы уменьшаем погрешность, но она все равно появляется при финальном делении: +Также можно временно умножить число на 100 (или на большее), чтобы привести его к целому, выполнить математические действия, а после разделить обратно. Суммируя целые числа, мы уменьшаем погрешность, но она всё равно появляется при финальном делении: ```js run alert( (0.1 * 10 + 0.2 * 10) / 10 ); // 0.3 alert( (0.28 * 100 + 0.14 * 100) / 100); // 0.4200000000000001 ``` -Таким образом, метод умножения/деления уменьшает погрешность, но полностью ее не решает. +Таким образом, метод умножения/деления уменьшает погрешность, но полностью её не решает. -Иногда можно попробовать полностью отказаться от дробей. Например, если мы в нашем интернет-магазине начнем использовать центы вместо долларов. Но что будет, если мы применим скидку 30%? На практике нам не получится полностью избавиться от дроби. Просто используйте округление, чтобы отрезать "хвосты", когда надо. +Иногда можно попробовать полностью отказаться от дробей. Например, если мы в нашем интернет-магазине начнём использовать центы вместо долларов. Но что будет, если мы применим скидку 30%? На практике у нас не получится полностью избавиться от дроби. Просто используйте округление, чтобы отрезать "хвосты", когда надо. ````smart header="Забавный пример" Попробуйте выполнить его: @@ -280,7 +294,7 @@ alert( 9999999999999999 ); // покажет 10000000000000000 Помните эти специальные числовые значения? -- `Infinity` (и `-Infinity`) — особенное численное значение, которое ведет себя в точности как математическая бесконечность ∞. +- `Infinity` (и `-Infinity`) — особенное численное значение, которое ведёт себя в точности как математическая бесконечность ∞. - `NaN` представляет ошибку. Эти числовые значения принадлежат типу `number`, но они не являются "обычными" числами, поэтому есть функции для их проверки: @@ -293,7 +307,7 @@ alert( 9999999999999999 ); // покажет 10000000000000000 alert( isNaN("str") ); // true ``` - Нужна ли нам эта функция? Разве не можем ли мы просто сравнить `=== NaN`? К сожалению, нет. Значение `NaN` уникально тем, что оно не является равным ни чему другому, даже самому себе: + Нужна ли нам эта функция? Разве не можем ли мы просто сравнить `=== NaN`? К сожалению, нет. Значение `NaN` уникально тем, что оно не является равным ничему другому, даже самому себе: ```js run alert( NaN === NaN ); // false @@ -311,17 +325,46 @@ alert( 9999999999999999 ); // покажет 10000000000000000 ```js run -let num = +prompt("Enter a number", ''); +let num = +prompt("Введите число:", ''); -// вернет true всегда, кроме ситуаций, когда аргумент - Infinity/-Infinity или не число +// вернёт true всегда, кроме ситуаций, когда аргумент - Infinity/-Infinity или не число alert( isFinite(num) ); ``` -Помните, что пустая строка интерпретируется как `0` во всех числовых функциях, включая`isFinite`. +Помните, что пустая строка интерпретируется как `0` во всех числовых функциях, включая`isFinite`. -```smart header="Сравнить с `Object.is`" +````smart header="`Number.isNaN` и `Number.isFinite`" +Методы [Number.isNaN](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Number/isNaN) и [Number.isFinite](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Number/isNaN) - это более "строгие" версии функций `isNaN` и `isFinite`. Они не преобразуют аргумент в число, а наоборот - первым делом проверяют, является ли аргумент числом (принадлежит ли он к типу `number`). -Существует специальный метод [Object.is](mdn:js/Object/is), который сравнивает значения примерно как `===`, но более надежен в двух особых ситуациях: +- `Number.isNaN(value)` возвращает `true` только в том случае, если аргумент принадлежит к типу `number` и является `NaN`. Во всех остальных случаях возвращает `false`. + + ```js run + alert( Number.isNaN(NaN) ); // true + alert( Number.isNaN("str" / 2) ); // true + + // Обратите внимание на разный результат: + alert( Number.isNaN("str") ); // false, так как "str" является строкой, а не числом + alert( isNaN("str") ); // true, так как isNaN сначала преобразует строку "str" в число и в результате преобразования получает NaN + ``` + +- `Number.isFinite(value)` возвращает `true` только в том случае, если аргумент принадлежит к типу `number` и не является `NaN/Infinity/-Infinity`. Во всех остальных случаях возвращает `false`. + + ```js run + alert( Number.isFinite(123) ); // true + alert( Number.isFinite(Infinity) ); // false + alert( Number.isFinite(2 / 0) ); // false + + // Обратите внимание на разный результат: + alert( Number.isFinite("123") ); // false, так как "123" является строкой, а не числом + alert( isFinite("123") ); // true, так как isFinite сначала преобразует строку "123" в число 123 + ``` + +Не стоит считать `Number.isNaN` и `Number.isFinite` более "корректными" версиями функций `isNaN` и `isFinite`. Это дополняющие друг-друга инструменты для разных задач. +```` + +```smart header="Сравнение `Object.is`" + +Существует специальный метод [Object.is](mdn:js/Object/is), который сравнивает значения примерно как `===`, но более надёжен в двух особых ситуациях: 1. Работает с `NaN`: `Object.is(NaN, NaN) === true`, здесь он хорош. 2. Значения `0` и `-0` разные: `Object.is(0, -0) === false`, это редко используется, но технически эти значения разные. @@ -352,8 +395,8 @@ alert( +"100px" ); // NaN alert( parseInt('100px') ); // 100 alert( parseFloat('12.5em') ); // 12.5 -alert( parseInt('12.3') ); // 12, вернется только целая часть -alert( parseFloat('12.3.4') ); // 12.3, произойдет остановка чтения на второй точке +alert( parseInt('12.3') ); // 12, вернётся только целая часть +alert( parseFloat('12.3.4') ); // 12.3, произойдёт остановка чтения на второй точке ``` Функции `parseInt/parseFloat` вернут `NaN`, если не смогли прочитать ни одну цифру: @@ -397,7 +440,7 @@ alert( parseInt('2n9c', 36) ); // 123456 ``` `Math.pow(n, power)` -: Возвращает число `n`, возведенное в степень `power` +: Возвращает число `n`, возведённое в степень `power` ```js run alert( Math.pow(2, 10) ); // 2 в степени 10 = 1024 @@ -407,17 +450,24 @@ alert( parseInt('2n9c', 36) ); // 123456 ## Итого -Чтобы писать большие числа: +Чтобы писать числа с большим количеством нулей: -- Используйте краткую форму записи больших чисел - `"e"`, с указанным количеством нулей. Например: `123e6` это `123` с 6-ю нулями. -- Отрицательное число после `"e"` приводит к делению числа на 1 с указанным количеством нулей. +- Используйте краткую форму записи чисел - `"e"`, с указанным количеством нулей. Например: `123e6` это `123` с 6-ю нулями `123000000`. +- Отрицательное число после `"e"` приводит к делению числа на 1 с указанным количеством нулей. Например: `123e-6` это `0.000123` (`123` миллионных). Для других систем счисления: - Можно записывать числа сразу в шестнадцатеричной (`0x`), восьмеричной (`0o`) и бинарной (`0b`) системах счисления -- `parseInt(str, base)` преобразует целочисленный тип в указанную систему счисления: `2 ≤ base ≤ 36`. +- `parseInt(str, base)` преобразует строку в целое число в соответствии с указанной системой счисления: `2 ≤ base ≤ 36`. - `num.toString(base)` представляет число в строковом виде в указанной системе счисления `base`. +Для проверки на `NaN` и `Infinity`: + +- `isNaN(value)` преобразует аргумент в число и проверяет, является ли оно `NaN` +- `Number.isNaN(value)` проверяет, является ли аргумент числом, и если да, то проверяет, является ли оно `NaN` +- `isFinite(value)` преобразует аргумент в число и проверяет, что оно не является `NaN/Infinity/-Infinity` +- `Number.isFinite(value)` проверяет, является ли аргумент числом, и если да, то проверяет, что оно не является `NaN/Infinity/-Infinity` + Для преобразования значений типа `12pt` и `100px` в число: - Используйте `parseInt/parseFloat` для "мягкого" преобразования строки в число, данные функции по порядку считывают число из строки до тех пор пока не возникнет ошибка. @@ -427,6 +477,6 @@ alert( parseInt('2n9c', 36) ); // 123456 - Используйте округления `Math.floor`, `Math.ceil`, `Math.trunc`, `Math.round` или `num.toFixed(precision)`. - Помните, что при работе с дробями происходит потеря точности. -Еще больше математических функций: +Ещё больше математических функций: - Документация по объекту [Math](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Math). Библиотека маленькая, но содержит всё самое важное. diff --git a/1-js/05-data-types/03-string/1-ucfirst/_js.view/test.js b/1-js/05-data-types/03-string/1-ucfirst/_js.view/test.js index d5c50ff574..dd5d3cfcf8 100644 --- a/1-js/05-data-types/03-string/1-ucfirst/_js.view/test.js +++ b/1-js/05-data-types/03-string/1-ucfirst/_js.view/test.js @@ -1,9 +1,9 @@ describe("ucFirst", function() { - it('Uppercases the first symbol', function() { + it('Переводит первый символ в верхний регистр', function() { assert.strictEqual(ucFirst("john"), "John"); }); - it("Doesn't die on an empty string", function() { + it("Не умирает на пустых строках", function() { assert.strictEqual(ucFirst(""), ""); }); -}); \ No newline at end of file +}); diff --git a/1-js/05-data-types/03-string/1-ucfirst/solution.md b/1-js/05-data-types/03-string/1-ucfirst/solution.md index 4809cf1238..859ae516b2 100644 --- a/1-js/05-data-types/03-string/1-ucfirst/solution.md +++ b/1-js/05-data-types/03-string/1-ucfirst/solution.md @@ -1,27 +1,26 @@ -We can't "replace" the first character, because strings in JavaScript are immutable. +Мы не можем просто заменить первый символ, так как строки в JavaScript неизменяемы. -But we can make a new string based on the existing one, with the uppercased first character: +Но можно пересоздать строку на основе существующей, с заглавным первым символом: ```js let newStr = str[0].toUpperCase() + str.slice(1); ``` -There's a small problem though. If `str` is empty, then `str[0]` is undefined, so we'll get an error. +Однако есть небольшая проблемка. Если строка пуста, `str[0]` вернёт `undefined`, а у `undefined` нет метода `toUpperCase()`, поэтому мы получим ошибку. -There are two variants here: +Выхода два: -1. Use `str.charAt(0)`, as it always returns a string (maybe empty). -2. Add a test for an empty string. +1. Использовать `str.charAt(0)`, поскольку этот метод всегда возвращает строку (для пустой строки — пустую). +2. Добавить проверку на пустую строку. -Here's the 2nd variant: +Вот второй вариант: -```js run +```js run demo function ucFirst(str) { if (!str) return str; return str[0].toUpperCase() + str.slice(1); } -alert( ucFirst("john") ); // John +alert( ucFirst("вася") ); // Вася ``` - diff --git a/1-js/05-data-types/03-string/1-ucfirst/task.md b/1-js/05-data-types/03-string/1-ucfirst/task.md index c0e6ecac44..f91ab171b1 100644 --- a/1-js/05-data-types/03-string/1-ucfirst/task.md +++ b/1-js/05-data-types/03-string/1-ucfirst/task.md @@ -2,11 +2,10 @@ importance: 5 --- -# Uppercast the first character +# Сделать первый символ заглавным -Write a function `ucFirst(str)` that returns the string `str` with the uppercased first character, for instance: +Напишите функцию `ucFirst(str)`, возвращающую строку `str` с заглавным первым символом. Например: ```js -ucFirst("john") == "John"; +ucFirst("вася") == "Вася"; ``` - diff --git a/1-js/05-data-types/03-string/2-check-spam/_js.view/test.js b/1-js/05-data-types/03-string/2-check-spam/_js.view/test.js index 85eb24fcb9..7ed7764233 100644 --- a/1-js/05-data-types/03-string/2-check-spam/_js.view/test.js +++ b/1-js/05-data-types/03-string/2-check-spam/_js.view/test.js @@ -1,13 +1,13 @@ describe("checkSpam", function() { - it('finds spam in "buy ViAgRA now"', function() { + it('считает спамом "buy ViAgRA now"', function() { assert.isTrue(checkSpam('buy ViAgRA now')); }); - it('finds spam in "free xxxxx"', function() { + it('считает спамом "free xxxxx"', function() { assert.isTrue(checkSpam('free xxxxx')); }); - it('no spam in "innocent rabbit"', function() { + it('не считает спамом "innocent rabbit"', function() { assert.isFalse(checkSpam('innocent rabbit')); }); -}); \ No newline at end of file +}); diff --git a/1-js/05-data-types/03-string/2-check-spam/solution.md b/1-js/05-data-types/03-string/2-check-spam/solution.md index 893c264970..1104716bcc 100644 --- a/1-js/05-data-types/03-string/2-check-spam/solution.md +++ b/1-js/05-data-types/03-string/2-check-spam/solution.md @@ -1,6 +1,6 @@ -To make the search case-insensitive, let's bring the string to lower case and then search: +Для поиска без учёта регистра символов переведём всю строку в нижний регистр, а потом проверим, есть ли в ней искомые подстроки: -```js run +```js run demo function checkSpam(str) { let lowerStr = str.toLowerCase(); @@ -11,4 +11,3 @@ alert( checkSpam('buy ViAgRA now') ); alert( checkSpam('free xxxxx') ); alert( checkSpam("innocent rabbit") ); ``` - diff --git a/1-js/05-data-types/03-string/2-check-spam/task.md b/1-js/05-data-types/03-string/2-check-spam/task.md index d073adc059..3206a68c15 100644 --- a/1-js/05-data-types/03-string/2-check-spam/task.md +++ b/1-js/05-data-types/03-string/2-check-spam/task.md @@ -2,15 +2,14 @@ importance: 5 --- -# Check for spam +# Проверка на спам -Write a function `checkSpam(str)` that returns `true` if `str` contains 'viagra' or 'XXX', otherwise `false. +Напишите функцию `checkSpam(str)`, возвращающую `true`, если `str` содержит `'viagra'` или `'XXX'`, а иначе `false`. -The function must be case-insensitive: +Функция должна быть нечувствительна к регистру: ```js checkSpam('buy ViAgRA now') == true checkSpam('free xxxxx') == true checkSpam("innocent rabbit") == false ``` - diff --git a/1-js/05-data-types/03-string/3-truncate/_js.view/test.js b/1-js/05-data-types/03-string/3-truncate/_js.view/test.js index 9914923315..1c4ad524d5 100644 --- a/1-js/05-data-types/03-string/3-truncate/_js.view/test.js +++ b/1-js/05-data-types/03-string/3-truncate/_js.view/test.js @@ -1,15 +1,15 @@ describe("truncate", function() { - it("truncate the long string to the given length (including the ellipsis)", function() { + it("усекает строку до заданной длины (включая многоточие)", function() { assert.equal( - truncate("What I'd like to tell on this topic is:", 20), - "What I'd like to te…" + truncate("Вот, что мне хотелось бы сказать на эту тему:", 20), + "Вот, что мне хотело…" ); }); - it("doesn't change short strings", function() { + it("не меняет и так короткие строки", function() { assert.equal( - truncate("Hi everyone!", 20), - "Hi everyone!" + truncate("Всем привет!", 20), + "Всем привет!" ); }); diff --git a/1-js/05-data-types/03-string/3-truncate/solution.md b/1-js/05-data-types/03-string/3-truncate/solution.md index 5546c47eea..4f53946cb8 100644 --- a/1-js/05-data-types/03-string/3-truncate/solution.md +++ b/1-js/05-data-types/03-string/3-truncate/solution.md @@ -1,6 +1,6 @@ -The maximal length must be `maxlength`, so we need to cut it a little shorter, to give space for the ellipsis. +Строка, которую мы возвращаем, должна быть не длиннее `maxlength`, поэтому, если мы обрезаем строку, то мы должны убрать на один символ больше, чем `maxlength` — чтобы хватило места на многоточие. -Note that there is actually a single unicode character for an ellipsis. That's not three dots. +Имейте в виду, что в качестве многоточия здесь используется `…` — ровно один специальный Юникодный символ. Это не то же самое, что `...` — три точки. ```js run demo function truncate(str, maxlength) { diff --git a/1-js/05-data-types/03-string/3-truncate/task.md b/1-js/05-data-types/03-string/3-truncate/task.md index 6382029f40..ea8e09caaf 100644 --- a/1-js/05-data-types/03-string/3-truncate/task.md +++ b/1-js/05-data-types/03-string/3-truncate/task.md @@ -2,16 +2,16 @@ importance: 5 --- -# Truncate the text +# Усечение строки -Create a function `truncate(str, maxlength)` that checks the length of the `str` and, if it exceeds `maxlength` -- replaces the end of `str` with the ellipsis character `"…"`, to make its length equal to `maxlength`. +Создайте функцию `truncate(str, maxlength)`, которая проверяет длину строки `str` и, если она превосходит `maxlength`, заменяет конец `str` на `"…"`, так, чтобы её длина стала равна `maxlength`. -The result of the function should be the truncated (if needed) string. +Результатом функции должна быть та же строка, если усечение не требуется, либо, если необходимо, усечённая строка. -For instance: +Например: ```js -truncate("What I'd like to tell on this topic is:", 20) = "What I'd like to te…" +truncate("Вот, что мне хотелось бы сказать на эту тему:", 20) = "Вот, что мне хотело…" -truncate("Hi everyone!", 20) = "Hi everyone!" +truncate("Всем привет!", 20) = "Всем привет!" ``` diff --git a/1-js/05-data-types/03-string/4-extract-currency/_js.view/test.js b/1-js/05-data-types/03-string/4-extract-currency/_js.view/test.js index 1c3f0bbc19..fc5a89abda 100644 --- a/1-js/05-data-types/03-string/4-extract-currency/_js.view/test.js +++ b/1-js/05-data-types/03-string/4-extract-currency/_js.view/test.js @@ -1,8 +1,8 @@ describe("extractCurrencyValue", function() { - it("for the string $120 returns the number 120", function() { + it("возвращает для строки $120 число 120", function() { assert.strictEqual(extractCurrencyValue('$120'), 120); }); -}); \ No newline at end of file +}); diff --git a/1-js/05-data-types/03-string/4-extract-currency/task.md b/1-js/05-data-types/03-string/4-extract-currency/task.md index feb16e6421..93b87d6caf 100644 --- a/1-js/05-data-types/03-string/4-extract-currency/task.md +++ b/1-js/05-data-types/03-string/4-extract-currency/task.md @@ -2,15 +2,14 @@ importance: 4 --- -# Extract the money +# Выделить число -We have a cost in the form `"$120"`. That is: the dollar sign goes first, and then the number. +Есть стоимость в виде строки `"$120"`. То есть сначала идёт знак валюты, а затем – число. -Create a function `extractCurrencyValue(str)` that would extract the numeric value from such string and return it. +Создайте функцию `extractCurrencyValue(str)`, которая будет из такой строки выделять числовое значение и возвращать его. -The example: +Например: ```js alert( extractCurrencyValue('$120') === 120 ); // true ``` - diff --git a/1-js/05-data-types/03-string/article.md b/1-js/05-data-types/03-string/article.md index e748d65f02..11acb7b03c 100644 --- a/1-js/05-data-types/03-string/article.md +++ b/1-js/05-data-types/03-string/article.md @@ -1,14 +1,15 @@ -# Strings -In JavaScript, the textual data is stored as strings. There is no separate type for a single character. +# Строки -The internal format for strings is always [UTF-16](https://en.wikipedia.org/wiki/UTF-16), it is not tied to the page encoding. +В JavaScript любые текстовые данные являются строками. Не существует отдельного типа "символ", который есть в ряде других языков. -## Quotes +Внутренний формат для строк — всегда [UTF-16](https://ru.wikipedia.org/wiki/UTF-16), вне зависимости от кодировки страницы. -Let's recall the kinds of quotes. +## Кавычки -Strings can be enclosed within either single quotes, double quotes or backticks: +В JavaScript есть разные типы кавычек. + +Строку можно создать с помощью одинарных, двойных либо обратных кавычек: ```js let single = 'single-quoted'; @@ -17,7 +18,7 @@ let double = "double-quoted"; let backticks = `backticks`; ``` -Single and double quotes are essentially the same. Backticks, however, allow us to embed any expression into the string, including function calls: +Одинарные и двойные кавычки работают, по сути, одинаково, а если использовать обратные кавычки, то в такую строку мы сможем вставлять произвольные выражения, обернув их в `${…}`: ```js run function sum(a, b) { @@ -27,7 +28,7 @@ function sum(a, b) { alert(`1 + 2 = ${sum(1, 2)}.`); // 1 + 2 = 3. ``` -Another advantage of using backticks is that they allow a string to span multiple lines: +Ещё одно преимущество обратных кавычек — они могут занимать более одной строки, вот так: ```js run let guestList = `Guests: @@ -36,205 +37,201 @@ let guestList = `Guests: * Mary `; -alert(guestList); // a list of guests, multiple lines +alert(guestList); // список гостей, состоящий из нескольких строк ``` -If we try to use single or double quotes in the same way, there will be an error: +Выглядит вполне естественно, не правда ли? Что тут такого? Но если попытаться использовать точно так же одинарные или двойные кавычки, то будет ошибка: + ```js run -let guestList = "Guests: // Error: Unexpected token ILLEGAL +let guestList = "Guests: // Error: Unexpected token ILLEGAL * John"; ``` -Single and double quotes come from ancient times of language creation when the need for multiline strings was not taken into account. Backticks appeared much later and thus are more versatile. - -Backticks also allow us to specify a "template function" before the first backtick. The syntax is: func`string`. The function `func` is called automatically, receives the string and embedded expressions and can process them. You can read more about it in the [docs](mdn:/JavaScript/Reference/Template_literals#Tagged_template_literals). This is called "tagged templates". This feature makes it easier to wrap strings into custom templating or other functionality, but it is rarely used. +Одинарные и двойные кавычки в языке с незапамятных времён: тогда потребность в многострочных строках не учитывалась. Что касается обратных кавычек, они появились существенно позже, и поэтому они гибче. +Обратные кавычки также позволяют задавать "шаблонную функцию" перед первой обратной кавычкой. Используемый синтаксис: func`string`. Автоматически вызываемая функция `func` получает строку и встроенные в неё выражения и может их обработать. Подробнее об этом можно прочитать в [документации](mdn:/JavaScript/Reference/template_strings#%D0%A2%D0%B5%D0%B3%D0%BE%D0%B2%D1%8B%D0%B5_%D1%88%D0%B0%D0%B1%D0%BB%D0%BE%D0%BD%D1%8B). Если перед строкой есть выражение, то шаблонная строка называется "теговым шаблоном". Это позволяет использовать свою шаблонизацию для строк, но на практике теговые шаблоны применяются редко. -## Special characters +## Спецсимволы -It is still possible to create multiline strings with single quotes by using a so-called "newline character", written as `\n`, which denotes a line break: +Многострочные строки также можно создавать с помощью одинарных и двойных кавычек, используя так называемый "символ перевода строки", который записывается как `\n`: ```js run let guestList = "Guests:\n * John\n * Pete\n * Mary"; -alert(guestList); // a multiline list of guests +alert(guestList); // список гостей, состоящий из нескольких строк ``` -For example, these two lines describe the same: +В частности, эти две строки эквивалентны, просто записаны по-разному: ```js run -alert( "Hello\nWorld" ); // two lines using a "newline symbol" +// перевод строки добавлен с помощью символа перевода строки +let str1 = "Hello\nWorld"; -// two lines using a normal newline and backticks -alert( `Hello -World` ); +// многострочная строка, созданная с использованием обратных кавычек +let str2 = `Hello +World`; + +alert(str1 == str2); // true ``` -There are other, less common "special" characters as well. Here's the list: +Есть и другие, реже используемые спецсимволы. Вот список: -| Character | Description | +| Символ | Описание | |-----------|-------------| -|`\b`|Backspace| -|`\f`|Form feed| -|`\n`|New line| -|`\r`|Carriage return| -|`\t`|Tab| -|`\uNNNN`|A unicode symbol with the hex code `NNNN`, for instance `\u00A9` -- is a unicode for the copyright symbol `©`. It must be exactly 4 hex digits. | -|`\u{NNNNNNNN}`|Some rare characters are encoded with two unicode symbols, taking up to 4 bytes. This long unicode requires braces around it.| +|`\n`|Перевод строки| +|`\r`|В текстовых файлах Windows для перевода строки используется комбинация символов `\r\n`, а на других ОС это просто `\n`. Это так по историческим причинам, ПО под Windows обычно понимает и просто `\n`. | +|`\'`, `\"`, \\`|Кавычки| +|`\\`|Обратный слеш| +|`\t`|Знак табуляции| +|`\b`, `\f`, `\v`| Backspace, Form Feed и Vertical Tab — оставлены для обратной совместимости, сейчас не используются. | -Examples with unicode: +Как вы можете видеть, все спецсимволы начинаются с обратного слеша, `\` — так называемого "символа экранирования". -```js run -alert( "\u00A9" ); // © -alert( "\u{20331}" ); // 佫, a rare chinese hieroglyph (long unicode) -alert( "\u{1F60D}" ); // 😍, a smiling face symbol (another long unicode) -``` +Он также используется, если необходимо вставить в строку кавычку. -All special characters start with a backslash character `\`. It is also called an "escape character". - -We would also use it if we want to insert a quote into the string. - -For instance: +К примеру: ```js run alert( 'I*!*\'*/!*m the Walrus!' ); // *!*I'm*/!* the Walrus! ``` -As you can see, we have to prepend the inner quote by the backslash `\'`, because otherwise it would indicate the string end. +Здесь перед входящей в строку кавычкой необходимо добавить обратный слеш — `\'` — иначе она бы обозначала окончание строки. -Of course, that refers only to the quotes that are same as the enclosing ones. So, as a more elegant solution, we could switch to double quotes or backticks instead: +Разумеется, требование экранировать относится только к таким же кавычкам, как те, в которые заключена строка. Так что мы можем применить и более элегантное решение, использовав для этой строки двойные или обратные кавычки: ```js run alert( `I'm the Walrus!` ); // I'm the Walrus! ``` -Note that the backslash `\` serves for the correct reading of the string by JavaScript, then disappears. The in-memory string has no `\`. You can clearly see that in `alert` from the examples above. +Заметим, что обратный слеш `\` служит лишь для корректного прочтения строки интерпретатором, но он не записывается в строку после её прочтения. Когда строка сохраняется в оперативную память, в неё не добавляется символ `\`. Вы можете явно видеть это в выводах `alert` в примерах выше. -But what if we need to show an actual backslash `\` within the string? +Но что, если нам надо добавить в строку собственно сам обратный слеш `\`? -That's possible, but we need to double it like `\\`: +Это можно сделать, добавив перед ним… ещё один обратный слеш! ```js run alert( `The backslash: \\` ); // The backslash: \ ``` -## String length - +## Длина строки -The `length` property has the string length: +Свойство `length` содержит длину строки: ```js run alert( `My\n`.length ); // 3 ``` -Note that `\n` is a single "special" character, so the length is indeed `3`. +Обратите внимание, `\n` — это один спецсимвол, поэтому тут всё правильно: длина строки `3`. -```warn header="`length` is a property" -People with a background in some other languages sometimes mistype by calling `str.length()` instead of just `str.length`. That doesn't work. +```warn header="`length` — это свойство" +Бывает так, что люди с практикой в других языках случайно пытаются вызвать его, добавляя круглые скобки: они пишут `str.length()` вместо `str.length`. Это не работает. -Please note that `str.length` is a numeric property, not a function. There is no need to add parenthesis after it. +Так как `str.length` — это числовое свойство, а не функция, добавлять скобки не нужно. ``` -## Accessing characters +## Доступ к символам -To get a character at position `pos`, use square brackets `[pos]` or call the method [str.charAt(pos)](mdn:js/String/charAt). The first character starts from the zero position: +Получить символ, который занимает позицию `pos`, можно с помощью квадратных скобок: `[pos]`. Также можно использовать метод [str.at(pos)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/at). Первый символ занимает нулевую позицию: ```js run let str = `Hello`; -// the first character +// получаем первый символ alert( str[0] ); // H -alert( str.charAt(0) ); // H +alert( str.at(0) ); // H -// the last character +// получаем последний символ alert( str[str.length - 1] ); // o +alert( str.at(-1) ); // o ``` -The square brackets are a modern way of getting a character, while `charAt` exists mostly for historical reasons. +Как вы можете видеть, преимущество метода `.at(pos)` заключается в том, что он допускает отрицательную позицию. Если `pos` -- отрицательное число, то отсчет ведется от конца строки. -The only difference between them is that if no character is found, `[]` returns `undefined`, and `charAt` returns an empty string: +Таким образом, `.at(-1)` означает последний символ, а `.at(-2)` -- тот, что перед ним, и т.д. + +Квадратные скобки всегда возвращают `undefined` для отрицательных индексов. Например: ```js run let str = `Hello`; -alert( str[1000] ); // undefined -alert( str.charAt(1000) ); // '' (an empty string) +alert( str[-2] ); // undefined +alert( str.at(-2) ); // l ``` -We can also iterate over characters using `for..of`: +Также можно перебрать строку посимвольно, используя `for..of`: ```js run for (let char of "Hello") { - alert(char); // H,e,l,l,o (char becomes "H", then "e", then "l" etc) + alert(char); // H,e,l,l,o (char — сначала "H", потом "e", потом "l" и т.д.) } ``` -## Strings are immutable +## Строки неизменяемы -Strings can't be changed in JavaScript. It is impossible to change a character. +Содержимое строки в JavaScript нельзя изменить. Нельзя взять символ посередине и заменить его. Как только строка создана — она такая навсегда. -Let's try it to show that it doesn't work: +Давайте попробуем так сделать, и убедимся, что это не работает: ```js run let str = 'Hi'; -str[0] = 'h'; // error -alert( str[0] ); // doesn't work +str[0] = 'h'; // ошибка +alert( str[0] ); // не работает ``` -The usual workaround is to create a whole new string and assign it to `str` instead of the old one. +Можно создать новую строку и записать её в ту же самую переменную вместо старой. -For instance: +Например: ```js run let str = 'Hi'; -str = 'h' + str[1]; // replace the string +str = 'h' + str[1]; // заменяем строку alert( str ); // hi ``` -In the following sections we'll see more examples of this. +В последующих разделах мы увидим больше примеров. -## Changing the case +## Изменение регистра -Methods [toLowerCase()](mdn:js/String/toLowerCase) and [toUpperCase()](mdn:js/String/toUpperCase) change the case: +Методы [toLowerCase()](mdn:js/String/toLowerCase) и [toUpperCase()](mdn:js/String/toUpperCase) меняют регистр символов: ```js run alert( 'Interface'.toUpperCase() ); // INTERFACE alert( 'Interface'.toLowerCase() ); // interface ``` -Or, if we want a single character lowercased: +Если мы захотим перевести в нижний регистр какой-то конкретный символ: -```js +```js run alert( 'Interface'[0].toLowerCase() ); // 'i' ``` -## Searching for a substring +## Поиск подстроки -There are multiple ways to look for a substring within a string. +Существует несколько способов поиска подстроки. ### str.indexOf -The first method is [str.indexOf(substr, pos)](mdn:js/String/indexOf). +Первый метод — [str.indexOf(substr, pos)](mdn:js/String/indexOf). -It looks for the `substr` in `str`, starting from the given position `pos`, and returns the position where the match was found or `-1` if nothing can be found. +Он ищет подстроку `substr` в строке `str`, начиная с позиции `pos`, и возвращает позицию, на которой располагается совпадение, либо `-1` при отсутствии совпадений. -For instance: +Например: ```js run let str = 'Widget with id'; -alert( str.indexOf('Widget') ); // 0, because 'Widget' is found at the beginning -alert( str.indexOf('widget') ); // -1, not found, the search is case-sensitive +alert( str.indexOf('Widget') ); // 0, потому что подстрока 'Widget' найдена в начале +alert( str.indexOf('widget') ); // -1, совпадений нет, поиск чувствителен к регистру -alert( str.indexOf("id") ); // 1, "id" is found at the position 1 (..idget with id) +alert( str.indexOf("id") ); // 1, подстрока "id" найдена на позиции 1 (..idget with id) ``` -The optional second parameter allows us to search starting from the given position. +Необязательный второй аргумент позволяет начать поиск с определённой позиции. -For instance, the first occurrence of `"id"` is at position `1`. To look for the next occurrence, let's start the search from position `2`: +Например, первое вхождение `"id"` — на позиции `1`. Для того, чтобы найти следующее, начнём поиск с позиции `2`: ```js run let str = 'Widget with id'; @@ -242,30 +239,28 @@ let str = 'Widget with id'; alert( str.indexOf('id', 2) ) // 12 ``` - -If we're interested in all occurrences, we can run `indexOf` in a loop. Every new call is made with the position after the previous match: - +Чтобы найти все вхождения подстроки, нужно запустить `indexOf` в цикле. Каждый раз, получив очередную позицию, начинаем новый поиск со следующей: ```js run -let str = 'As sly as a fox, as strong as an ox'; +let str = 'Ослик Иа-Иа посмотрел на виадук'; -let target = 'as'; // let's look for it +let target = 'Иа'; // цель поиска let pos = 0; while (true) { let foundPos = str.indexOf(target, pos); if (foundPos == -1) break; - alert( `Found at ${foundPos}` ); - pos = foundPos + 1; // continue the search from the next position + alert( `Найдено тут: ${foundPos}` ); + pos = foundPos + 1; // продолжаем со следующей позиции } ``` -The same algorithm can be layed out shorter: +Тот же алгоритм можно записать и короче: ```js run -let str = "As sly as a fox, as strong as an ox"; -let target = "as"; +let str = "Ослик Иа-Иа посмотрел на виадук"; +let target = "Иа"; *!* let pos = -1; @@ -276,24 +271,24 @@ while ((pos = str.indexOf(target, pos + 1)) != -1) { ``` ```smart header="`str.lastIndexOf(substr, position)`" -There is also a similar method [str.lastIndexOf(substr, position)](mdn:js/String/lastIndexOf) that searches from the end of a string to its beginning. +Также есть похожий метод [str.lastIndexOf(substr, position)](mdn:js/String/lastIndexOf), который ищет с конца строки к её началу. -It would list the occurrences in the reverse order. +Он используется тогда, когда нужно получить самое последнее вхождение: перед концом строки или начинающееся до (включительно) определённой позиции. ``` -There is a slight inconvenience with `indexOf` in the `if` test. We can't put it in the `if` like this: +При проверке `indexOf` в условии `if` есть небольшое неудобство. Такое условие не будет работать: ```js run let str = "Widget with id"; if (str.indexOf("Widget")) { - alert("We found it"); // doesn't work! + alert("Совпадение есть"); // не работает } ``` -The `alert` in the example above doesn't show because `str.indexOf("Widget")` returns `0` (meaning that it found the match at the starting position). Right, but `if` considers `0` to be `false`. +Мы ищем подстроку `"Widget"`, и она здесь есть, прямо на позиции `0`. Но `alert` не показывается, т. к. `str.indexOf("Widget")` возвращает `0`, и `if` решает, что тест не пройден. -So, we should actually check for `-1`, like this: +Поэтому надо делать проверку на `-1`: ```js run let str = "Widget with id"; @@ -301,50 +296,53 @@ let str = "Widget with id"; *!* if (str.indexOf("Widget") != -1) { */!* - alert("We found it"); // works now! + alert("Совпадение есть"); // теперь работает } ``` -````smart header="The bitwise NOT trick" -One of the old tricks used here is the [bitwise NOT](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators#Bitwise_NOT) `~` operator. It converts the number to a 32-bit integer (removes the decimal part if exists) and then reverses all bits in its binary representation. +#### Трюк с побитовым НЕ +Существует старый трюк с использованием [побитового оператора НЕ](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_NOT) — `~`. Он преобразует число в 32-разрядное целое со знаком (signed 32-bit integer). Дробная часть, в случае, если она присутствует, отбрасывается. Затем все биты числа инвертируются. -For 32-bit integers the call `~n` means exactly the same as `-(n+1)` (due to IEEE-754 format). +На практике это означает простую вещь: для 32-разрядных целых чисел значение `~n` равно `-(n+1)`. -For instance: +В частности: ```js run -alert( ~2 ); // -3, the same as -(2+1) -alert( ~1 ); // -2, the same as -(1+1) -alert( ~0 ); // -1, the same as -(0+1) +alert( ~2 ); // -3, то же, что -(2+1) +alert( ~1 ); // -2, то же, что -(1+1) +alert( ~0 ); // -1, то же, что -(0+1) *!* -alert( ~-1 ); // 0, the same as -(-1+1) +alert( ~-1 ); // 0, то же, что -(-1+1) */!* ``` -As we can see, `~n` is zero only if `n == -1`. +Таким образом, `~n` равняется 0 только при `n == -1` (для любого `n`, входящего в 32-разрядные целые числа со знаком). -So, the test `if ( ~str.indexOf("...") )` is truthy that the result of `indexOf` is not `-1`. In other words, when there is a match. +Соответственно, прохождение проверки `if ( ~str.indexOf("…") )` означает, что результат `indexOf` отличен от `-1`, совпадение есть. -People use it to shorten `indexOf` checks: +Это иногда применяют, чтобы сделать проверку `indexOf` компактнее: ```js run let str = "Widget"; if (~str.indexOf("Widget")) { - alert( 'Found it!' ); // works + alert( 'Совпадение есть' ); // работает } ``` -It is usually not recommended to use language features in a non-obvious way, but this particular trick is widely used in old code, so we should understand it. +Обычно использовать возможности языка каким-либо неочевидным образом не рекомендуется, но этот трюк широко используется в старом коде, поэтому его важно понимать. + +Просто запомните: `if (~str.indexOf(…))` означает "если найдено". + +Впрочем, если быть точнее, из-за того, что большие числа обрезаются до 32 битов оператором `~`, существуют другие числа, для которых результат тоже будет `0`, самое маленькое из которых — `~4294967295=0`. Поэтому такая проверка будет правильно работать только для строк меньшей длины. -Just remember: `if (~str.indexOf(...))` reads as "if found". -```` +На данный момент такой трюк можно встретить только в старом коде, потому что в новом он просто не нужен: есть метод `.includes` (см. ниже). ### includes, startsWith, endsWith -The more modern method [str.includes(substr, pos)](mdn:js/String/includes) returns `true/false` depending on whether `str` contains `substr` within. +Более современный метод [str.includes(substr, pos)](mdn:js/String/includes) возвращает `true`, если в строке `str` есть подстрока `substr`, либо `false`, если нет. -It's the right choice if we need to test for the match, but don't need its position: +Это — правильный выбор, если нам необходимо проверить, есть ли совпадение, но позиция не нужна: ```js run alert( "Widget with id".includes("Widget") ); // true @@ -352,156 +350,153 @@ alert( "Widget with id".includes("Widget") ); // true alert( "Hello".includes("Bye") ); // false ``` -The optional second argument of `str.includes` is the position to start searching from: +Необязательный второй аргумент `str.includes` позволяет начать поиск с определённой позиции: ```js run alert( "Midget".includes("id") ); // true -alert( "Midget".includes("id", 3) ); // false, from position 3 there is no "id" +alert( "Midget".includes("id", 3) ); // false, поиск начат с позиции 3 ``` -The methods [str.startsWith](mdn:js/String/startsWith) and [str.endsWith](mdn:js/String/endsWith) do exactly what they say: +Методы [str.startsWith](mdn:js/String/startsWith) и [str.endsWith](mdn:js/String/endsWith) проверяют, соответственно, начинается ли и заканчивается ли строка определённой строкой: ```js run -alert( "Widget".startsWith("Wid") ); // true, "Widget" starts with "Wid" -alert( "Widget".endsWith("get") ); // true, "Widget" ends with "get" +alert( "*!*Wid*/!*get".startsWith("Wid") ); // true, "Wid" — начало "Widget" +alert( "Wid*!*get*/!*".endsWith("get") ); // true, "get" — окончание "Widget" ``` -## Getting a substring +## Получение подстроки -There are 3 methods in JavaScript to get a substring: `substring`, `substr` and `slice`. +В JavaScript есть 3 метода для получения подстроки: `substring`, `substr` и `slice`. `str.slice(start [, end])` -: Returns the part of the string from `start` to (but not including) `end`. +: Возвращает часть строки от `start` до (не включая) `end`. - For instance: + Например: ```js run let str = "stringify"; - alert( str.slice(0, 5) ); // 'strin', the substring from 0 to 5 (not including 5) - alert( str.slice(0, 1) ); // 's', from 0 to 1, but not including 1, so only character at 0 + // 'strin', символы от 0 до 5 (не включая 5) + alert( str.slice(0, 5) ); + // 's', от 0 до 1, не включая 1, т. е. только один символ на позиции 0 + alert( str.slice(0, 1) ); ``` - If there is no second argument, then `slice` goes till the end of the string: + Если аргумент `end` отсутствует, `slice` возвращает символы до конца строки: ```js run let str = "st*!*ringify*/!*"; - alert( str.slice(2) ); // ringify, from the 2nd position till the end + alert( str.slice(2) ); // ringify, с позиции 2 и до конца ``` - Negative values for `start/end` are also possible. They mean the position is counted from the string end: + Также для `start/end` можно задавать отрицательные значения. Это означает, что позиция определена как заданное количество символов *с конца строки*: ```js run let str = "strin*!*gif*/!*y"; - // start at the 4th position from the right, end at the 1st from the right + // начинаем с позиции 4 справа, а заканчиваем на позиции 1 справа alert( str.slice(-4, -1) ); // gif ``` - `str.substring(start [, end])` -: Returns the part of the string *between* `start` and `end`. - - This is almost the same as `slice`, but it allows `start` to be greater than `end`. +: Возвращает часть строки *между* `start` и `end` (не включая) `end`. - For instance: + Это — почти то же, что и `slice`, но можно задавать `start` больше `end`. + Если `start` больше `end`, то метод `substring` сработает так, как если бы аргументы были поменяны местами. + Например: ```js run let str = "st*!*ring*/!*ify"; - // these are same for substring + // для substring эти два примера — одинаковы alert( str.substring(2, 6) ); // "ring" alert( str.substring(6, 2) ); // "ring" - // ...but not for slice: - alert( str.slice(2, 6) ); // "ring" (the same) - alert( str.slice(6, 2) ); // "" (an empty string) + // …но не для slice: + alert( str.slice(2, 6) ); // "ring" (то же самое) + alert( str.slice(6, 2) ); // "" (пустая строка) ``` - Negative arguments are (unlike slice) not supported, they are treated as `0`. - + Отрицательные значения `substring`, в отличие от `slice`, не поддерживает, они интерпретируются как `0`. `str.substr(start [, length])` -: Returns the part of the string from `start`, with the given `length`. +: Возвращает часть строки от `start` длины `length`. - In contrast with the previous methods, this one allows us to specify the `length` instead of the ending position: + В противоположность предыдущим методам, этот позволяет указать длину вместо конечной позиции: ```js run let str = "st*!*ring*/!*ify"; - alert( str.substr(2, 4) ); // ring, from the 2nd position get 4 characters + // ring, получаем 4 символа, начиная с позиции 2 + alert( str.substr(2, 4) ); ``` - The first argument may be negative, to count from the end: + Значение первого аргумента может быть отрицательным, тогда позиция определяется с конца: ```js run let str = "strin*!*gi*/!*fy"; - alert( str.substr(-4, 2) ); // gi, from the 4th position get 2 characters + // gi, получаем 2 символа, начиная с позиции 4 с конца строки + alert( str.substr(-4, 2) ); ``` -Let's recap these methods to avoid any confusion: + Этот метод находится в [Annex B](https://tc39.es/ecma262/#sec-string.prototype.substr) спецификации языка. Это означает, что его должны поддерживать только браузерные движки JavaScript, и использовать его не рекомендуется. Но на практике он поддерживается везде. -| method | selects... | negatives | -|--------|-----------|-----------| -| `slice(start, end)` | from `start` to `end` (not including `end`) | allows negatives | -| `substring(start, end)` | between `start` and `end` | negative values mean `0` | -| `substr(start, length)` | from `start` get `length` characters | allows negative `start` | +Давайте подытожим, как работают эти методы, чтобы не запутаться: +| метод | выбирает… | отрицательные значения | +|--------|-----------|-----------| +| `slice(start, end)` | от `start` до `end` (не включая `end`) | можно передавать отрицательные значения | +| `substring(start, end)` | между `start` и `end` (не включая `end`) | отрицательные значения равнозначны `0` | +| `substr(start, length)` | `length` символов, начиная от `start` | значение `start` может быть отрицательным | -```smart header="Which one to choose?" -All of them can do the job. Formally, `substr` has a minor drawback: it is described not in the core JavaScript specification, but in Annex B, which covers browser-only features that exist mainly for historical reasons. So, non-browser environments may fail to support it. But in practice it works everywhere. +```smart header="Какой метод выбрать?" +Все эти методы эффективно выполняют задачу. Формально у метода `substr` есть небольшой недостаток: он описан не в собственно спецификации JavaScript, а в приложении к ней — Annex B. Это приложение описывает возможности языка для использования в браузерах, существующие в основном по историческим причинам. Таким образом, в другом окружении, отличном от браузера, он может не поддерживаться. Однако на практике он работает везде. -The author finds themself using `slice` almost all the time. +Из двух других вариантов, `slice` более гибок, он поддерживает отрицательные аргументы, и его короче писать. Так что, в принципе, можно запомнить только его. ``` -## Comparing strings +## Сравнение строк -As we know from the chapter , strings are compared character-by-character in alphabetical order. +Как мы знаем из главы , строки сравниваются посимвольно в алфавитном порядке. -Although, there are some oddities. +Тем не менее, есть некоторые нюансы. -1. A lowercase letter is always greater than the uppercase: +1. Строчные буквы больше заглавных: ```js run alert( 'a' > 'Z' ); // true ``` -2. Letters with diacritical marks are "out of order": +2. Буквы, имеющие диакритические знаки, идут "не по порядку": ```js run alert( 'Österreich' > 'Zealand' ); // true ``` - This may lead to strange results if we sort these country names. Usually people would expect `Zealand` to come after `Österreich` in the list. + Это может привести к своеобразным результатам при сортировке названий стран: нормально было бы ожидать, что `Zealand` будет после `Österreich` в списке. -To understand what happens, let's review the internal representation of strings in JavaScript. +Чтобы разобраться, что происходит, давайте ознакомимся с внутренним представлением строк в JavaScript. -All strings are encoded using [UTF-16](https://en.wikipedia.org/wiki/UTF-16). That is: each character has a corresponding numeric code. There are special methods that allow to get the character for the code and back. +Строки кодируются в [UTF-16](https://ru.wikipedia.org/wiki/UTF-16). Таким образом, у любого символа есть соответствующий код. Есть специальные методы, позволяющие получить символ по его коду и наоборот. `str.codePointAt(pos)` -: Returns the code for the character at position `pos`: +: Возвращает код для символа, находящегося на позиции `pos`: ```js run - // different case letters have different codes + // одна и та же буква в нижнем и верхнем регистре + // будет иметь разные коды alert( "z".codePointAt(0) ); // 122 alert( "Z".codePointAt(0) ); // 90 ``` `String.fromCodePoint(code)` -: Creates a character by its numeric `code` +: Создаёт символ по его коду `code` ```js run alert( String.fromCodePoint(90) ); // Z ``` - We can also add unicode characters by their codes using `\u` followed by the hex code: - - ```js run - // 90 is 5a in hexadecimal system - alert( '\u005a' ); // Z - ``` - -Now let's see the characters with codes `65..220` (the latin alphabet and a little bit extra) by making a string of them: +Давайте сделаем строку, содержащую символы с кодами от `65` до `220` — это латиница и ещё некоторые распространённые символы: ```js run let str = ''; @@ -514,161 +509,56 @@ alert( str ); // ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜ ``` -See? Capital characters go first, then a few special ones, then lowercase characters. - -Now it becomes obvious why `a > Z`. +Как видите, сначала идут заглавные буквы, затем несколько спецсимволов, затем строчные и `Ö` ближе к концу вывода. -The characters are compared by their numeric code. The greater code means that the character is greater. The code for `a` (97) is greater than the code for `Z` (90). +Теперь очевидно, почему `a > Z`. -- All lowercase letters go after uppercase letters because their codes are greater. -- Some letters like `Ö` stand apart from the main alphabet. Here, it's code is greater than anything from `a` to `z`. +Символы сравниваются по их кодам. Больший код — больший символ. Код `a` (97) больше кода `Z` (90). +- Все строчные буквы идут после заглавных, так как их коды больше. +- Некоторые буквы, такие как `Ö`, вообще находятся вне основного алфавита. У этой буквы код больше, чем у любой буквы от `a` до `z`. -### Correct comparisons +### Правильное сравнение [#correct-comparisons] -The "right" algorithm to do string comparisons is more complex than it may seem, because alphabets are different for different languages. The same-looking letter may be located differently in different alphabets. +"Правильный" алгоритм сравнения строк сложнее, чем может показаться, так как разные языки используют разные алфавиты. -So, the browser needs to know the language to compare. +Поэтому браузеру нужно знать, какой язык использовать для сравнения. -Luckily, all modern browsers (IE10- requires the additional library [Intl.JS](https://github.com/andyearnshaw/Intl.js/)) support the internationalization standard [ECMA 402](http://www.ecma-international.org/ecma-402/1.0/ECMA-402.pdf). +К счастью, все современные браузеры (для IE10− нужна дополнительная библиотека [Intl.JS](https://github.com/andyearnshaw/Intl.js/)) поддерживают стандарт [ECMA 402](https://www.ecma-international.org/ecma-402/1.0/ECMA-402.pdf), обеспечивающий правильное сравнение строк на разных языках с учётом их правил. -It provides a special method to compare strings in different languages, following their rules. +Для этого есть соответствующий метод. -The call [str.localeCompare(str2)](mdn:js/String/localeCompare): +Вызов [str.localeCompare(str2)](mdn:js/String/localeCompare) возвращает число, которое показывает, какая строка больше в соответствии с правилами языка: -- Returns `1` if `str` is greater than `str2` according to the language rules. -- Returns `-1` if `str` is less than `str2`. -- Returns `0` if they are equal. +- Отрицательное число, если `str` меньше `str2`. +- Положительное число, если `str` больше `str2`. +- `0`, если строки равны. -For instance: +Например: ```js run alert( 'Österreich'.localeCompare('Zealand') ); // -1 ``` -This method actually has two additional arguments specified in [the documentation](mdn:js/String/localeCompare), which allows it to specify the language (by default taken from the environment) and setup additional rules like case sensitivity or should `"a"` and `"á"` be treated as the same etc. - -## Internals, Unicode - -```warn header="Advanced knowledge" -The section goes deeper into string internals. This knowledge will be useful for you if you plan to deal with emoji, rare mathematical or hieroglyphic characters or other rare symbols. - -You can skip the section if you don't plan to support them. -``` - -### Surrogate pairs - -Most symbols have a 2-byte code. Letters in most european languages, numbers, and even most hieroglyphs, have a 2-byte representation. - -But 2 bytes only allow 65536 combinations and that's not enough for every possible symbol. So rare symbols are encoded with a pair of 2-byte characters called "a surrogate pair". - -The length of such symbols is `2`: - -```js run -alert( '𝒳'.length ); // 2, MATHEMATICAL SCRIPT CAPITAL X -alert( '😂'.length ); // 2, FACE WITH TEARS OF JOY -alert( '𩷶'.length ); // 2, a rare chinese hieroglyph -``` - -Note that surrogate pairs did not exist at the time when JavaScript was created, and thus are not correctly processed by the language! - -We actually have a single symbol in each of the strings above, but the `length` shows a length of `2`. - -`String.fromCodePoint` and `str.codePointAt` are few rare methods that deal with surrogate pairs right. They recently appeared in the language. Before them, there were only [String.fromCharCode](mdn:js/String/fromCharCode) and [str.charCodeAt](mdn:js/String/charCodeAt). These methods are actually the same as `fromCodePoint/codePointAt`, but don't work with surrogate pairs. - -But, for instance, getting a symbol can be tricky, because surrogate pairs are treated as two characters: - -```js run -alert( '𝒳'[0] ); // strange symbols... -alert( '𝒳'[1] ); // ...pieces of the surrogate pair -``` - -Note that pieces of the surrogate pair have no meaning without each other. So the alerts in the example above actually display garbage. - -Technically, surrogate pairs are also detectable by their codes: if a character has the code in the interval of `0xd800..0xdbff`, then it is the first part of the surrogate pair. The next character (second part) must have the code in interval `0xdc00..0xdfff`. These intervals are reserved exclusively for surrogate pairs by the standard. - -In the case above: - -```js run -// charCodeAt is not surrogate-pair aware, so it gives codes for parts - -alert( '𝒳'.charCodeAt(0).toString(16) ); // d835, between 0xd800 and 0xdbff -alert( '𝒳'.charCodeAt(1).toString(16) ); // dcb3, between 0xdc00 and 0xdfff -``` - -You will find more ways to deal with surrogate pairs later in the chapter . There are probably special libraries for that too, but nothing famous enough to suggest here. - -### Diacritical marks and normalization - -In many languages there are symbols that are composed of the base character with a mark above/under it. - -For instance, the letter `a` can be the base character for: `àáâäãåā`. Most common "composite" character have their own code in the UTF-16 table. But not all of them, because there are too many possible combinations. - -To support arbitrary compositions, UTF-16 allows us to use several unicode characters. The base character and one or many "mark" characters that "decorate" it. - -For instance, if we have `S` followed by the special "dot above" character (code `\u0307`), it is shown as Ṡ. - -```js run -alert( 'S\u0307' ); // Ṡ -``` - -If we need an additional mark above the letter (or below it) -- no problem, just add the necessary mark character. - -For instance, if we append a character "dot below" (code `\u0323`), then we'll have "S with dots above and below": `Ṩ`. - -For example: - -```js run -alert( 'S\u0307\u0323' ); // Ṩ -``` - -This provides great flexibility, but also an interesting problem: two characters may visually look the same, but be represented with different unicode compositions. - -For instance: - -```js run -alert( 'S\u0307\u0323' ); // Ṩ, S + dot above + dot below -alert( 'S\u0323\u0307' ); // Ṩ, S + dot below + dot above - -alert( 'S\u0307\u0323' == 'S\u0323\u0307' ); // false -``` - -To solve this, there exists a "unicode normalization" algorithm that brings each string to the single "normal" form. - -It is implemented by [str.normalize()](mdn:js/String/normalize). - -```js run -alert( "S\u0307\u0323".normalize() == "S\u0323\u0307".normalize() ); // true -``` - -It's funny that in our situation `normalize()` actually brings together a sequence of 3 characters to one: `\u1e68` (S with two dots). - -```js run -alert( "S\u0307\u0323".normalize().length ); // 1 - -alert( "S\u0307\u0323".normalize() == "\u1e68" ); // true -``` - -In reality, this is not always the case. The reason being that the symbol `Ṩ` is "common enough", so UTF-16 creators included it in the main table and gave it the code. - -If you want to learn more about normalization rules and variants -- they are described in the appendix of the Unicode standard: [Unicode Normalization Forms](http://www.unicode.org/reports/tr15/), but for most practical purposes the information from this section is enough. +У этого метода есть два дополнительных аргумента, которые указаны в [документации](mdn:js/String/localeCompare). Первый позволяет указать язык (по умолчанию берётся из окружения) — от него зависит порядок букв. Второй — определить дополнительные правила, такие как чувствительность к регистру, а также следует ли учитывать различия между `"a"` и `"á"`. +## Итого -## Summary +- Есть три типа кавычек. Строки, использующие обратные кавычки, могут занимать более одной строки в коде и включать выражения `${…}`. +- Строки в JavaScript кодируются в UTF-16. +- Есть специальные символы, такие как разрыв строки `\n`. +- Для получения символа используйте `[]` или метод `at`. +- Для получения подстроки используйте `slice` или `substring`. +- Для того, чтобы перевести строку в нижний или верхний регистр, используйте `toLowerCase/toUpperCase`. +- Для поиска подстроки используйте `indexOf` или `includes/startsWith/endsWith`, когда надо только проверить, есть ли вхождение. +- Чтобы сравнить строки с учётом правил языка, используйте `localeCompare`. -- There are 3 types of quotes. Backticks allow a string to span multiple lines and embed expressions. -- Strings in JavaScript are encoded using UTF-16. -- We can use special characters like `\n` and insert letters by their unicode using `\u...`. -- To get a character, use: `[]`. -- To get a substring, use: `slice` or `substring`. -- To lowercase/uppercase a string, use: `toLowerCase/toUpperCase`. -- To look for a substring, use: `indexOf`, or `includes/startsWith/endsWith` for simple checks. -- To compare strings according to the language, use: `localeCompare`, otherwise they are compared by character codes. +Строки также имеют ещё кое-какие полезные методы: -There are several other helpful methods in strings: +- `str.trim()` — убирает пробелы в начале и конце строки. +- `str.repeat(n)` — повторяет строку `n` раз. +- …и другие, которые вы можете найти в [справочнике](mdn:js/String). -- `str.trim()` -- removes ("trims") spaces from the beginning and end of the string. -- `str.repeat(n)` -- repeats the string `n` times. -- ...and more. See the [manual](mdn:js/String) for details. +Для строк предусмотрены методы для поиска и замены с использованием регулярных выражений. Но это отдельная большая тема, поэтому ей посвящена отдельная глава учебника . -Strings also have methods for doing search/replace with regular expressions. But that topic deserves a separate chapter, so we'll return to that later. +Также, на данный момент важно знать, что строки основаны на кодировке Юникод, и поэтому иногда могут возникать проблемы со сравнениями. Подробнее о Юникоде в главе . diff --git a/1-js/05-data-types/04-array/1-item-value/solution.md b/1-js/05-data-types/04-array/1-item-value/solution.md index e631f1c708..5025966a4f 100644 --- a/1-js/05-data-types/04-array/1-item-value/solution.md +++ b/1-js/05-data-types/04-array/1-item-value/solution.md @@ -1,17 +1,17 @@ -The result is `4`: +Выведется `4`: ```js run -let fruits = ["Apples", "Pear", "Orange"]; +let fruits = ["Яблоки", "Груша", "Апельсин"]; let shoppingCart = fruits; -shoppingCart.push("Banana"); +shoppingCart.push("Банан"); *!* alert( fruits.length ); // 4 */!* ``` -That's because arrays are objects. So both `shoppingCart` and `fruits` are the references to the same array. +Потому, что массивы - это объекты. Обе переменные `shoppingCart` и `fruits` являются ссылками на один и тот же массив. diff --git a/1-js/05-data-types/04-array/1-item-value/task.md b/1-js/05-data-types/04-array/1-item-value/task.md index 4fcf384fbb..9c09426fb1 100644 --- a/1-js/05-data-types/04-array/1-item-value/task.md +++ b/1-js/05-data-types/04-array/1-item-value/task.md @@ -2,18 +2,18 @@ importance: 3 --- -# Is array copied? +# Скопирован ли массив? -What is this code going to show? +Что выведет следующий код? ```js -let fruits = ["Apples", "Pear", "Orange"]; +let fruits = ["Яблоки", "Груша", "Апельсин"]; -// push a new value into the "copy" +// добавляем новое значение в "копию" let shoppingCart = fruits; -shoppingCart.push("Banana"); +shoppingCart.push("Банан"); -// what's in fruits? +// что в fruits? alert( fruits.length ); // ? ``` diff --git a/1-js/05-data-types/04-array/10-maximal-subarray/solution.md b/1-js/05-data-types/04-array/10-maximal-subarray/solution.md index daadf494b9..3abf9e3a72 100644 --- a/1-js/05-data-types/04-array/10-maximal-subarray/solution.md +++ b/1-js/05-data-types/04-array/10-maximal-subarray/solution.md @@ -1,43 +1,43 @@ -# Slow solution +# Медленное решение -We can calculate all possible subsums. +Можно посчитать все возможные подсуммы. -The simplest way is to take every element and calculate sums of all subarrays starting from it. +Самый простой путь - посчитать суммы подмассивов, начиная с каждого элемента по очереди. -For instance, for `[-1, 2, 3, -9, 11]`: +Например, для `[-1, 2, 3, -9, 11]`: ```js no-beautify -// Starting from -1: +// Начиная с -1: -1 -1 + 2 -1 + 2 + 3 -1 + 2 + 3 + (-9) -1 + 2 + 3 + (-9) + 11 -// Starting from 2: +// Начиная с 2: 2 2 + 3 2 + 3 + (-9) 2 + 3 + (-9) + 11 -// Starting from 3: +// Начиная с 3: 3 3 + (-9) 3 + (-9) + 11 -// Starting from -9 +// Начиная с -9 -9 -9 + 11 -// Starting from 11 +// Начиная с 11 11 -``` +``` -The code is actually a nested loop: the external loop over array elements, and the internal counts subsums starting with the current element. +Реализуется с помощью вложенного цикла: внешний цикл проходит по элементам массива, а внутренний считает подсумму, начиная с текущего элемента. ```js run function getMaxSubSum(arr) { - let maxSum = 0; // if we take no elements, zero will be returned + let maxSum = 0; // если элементов не будет - возвращаем 0 for (let i = 0; i < arr.length; i++) { let sumFixedStart = 0; @@ -57,25 +57,25 @@ alert( getMaxSubSum([1, 2, 3]) ); // 6 alert( getMaxSubSum([100, -9, 2, -3, 5]) ); // 100 ``` -The solution has a time complexety of [O(n2)](https://en.wikipedia.org/wiki/Big_O_notation). In other words, if we increase the array size 2 times, the algorithm will work 4 times longer. +Это решение имеет [оценку сложности](https://ru.wikipedia.org/wiki/«O»_большое_и_«o»_малое) O(n2). Другими словами, если мы увеличим размер массива в 2 раза, время выполнения алгоритма увеличится в 4 раза. -For big arrays (1000, 10000 or more items) such algorithms can lead to a serious sluggishness. +Для больших массивов(1000, 10000 или больше элементов) такие алгоритмы могут приводить к серьёзным "тормозам". -# Fast solution +# Быстрое решение -Let's walk the array and keep the current partial sum of elements in the variable `s`. If `s` becomes negative at some point, then assign `s=0`. The maximum of all such `s` will be the answer. +Идём по массиву и накапливаем текущую частичную сумму элементов в переменной `s`. Если `s` в какой-то момент становится отрицательной - присваиваем `s=0`. Максимальный из всех `s` и будет ответом. -If the description is too vague, please see the code, it's short enough: +Если объяснение недостаточно понятно, посмотрите на код, он вполне лаконичен: ```js run demo function getMaxSubSum(arr) { let maxSum = 0; let partialSum = 0; - for (let item of arr) { // for each item of arr - partialSum += item; // add it to partialSum - maxSum = Math.max(maxSum, partialSum); // remember the maximum - if (partialSum < 0) partialSum = 0; // zero if negative + for (let item of arr) { // для каждого элемента массива + partialSum += item; // добавляем значение элемента к partialSum + maxSum = Math.max(maxSum, partialSum); // запоминаем максимум на данный момент + if (partialSum < 0) partialSum = 0; // ноль если отрицательное } return maxSum; @@ -89,6 +89,6 @@ alert( getMaxSubSum([1, 2, 3]) ); // 6 alert( getMaxSubSum([-1, -2, -3]) ); // 0 ``` -The algorithm requires exactly 1 array pass, so the time complexity is O(n). +Этот алгоритм требует ровно 1 проход по массиву и его оценка сложности O(n). -You can find more detail information about the algorithm here: [Maximum subarray problem](http://en.wikipedia.org/wiki/Maximum_subarray_problem). If it's still not obvious why that works, then please trace the algorithm on the examples above, see how it works, that's better than any words. +Больше информации об алгоритме тут: [Задача поиска максимальной суммы подмассива](https://en.wikipedia.org/wiki/Maximum_subarray_problem). Если всё ещё не очевидно как это работает, просмотрите алгоритм в примерах выше, это будет лучше всяких слов. diff --git a/1-js/05-data-types/04-array/10-maximal-subarray/task.md b/1-js/05-data-types/04-array/10-maximal-subarray/task.md index e63c4e625d..788913ec45 100644 --- a/1-js/05-data-types/04-array/10-maximal-subarray/task.md +++ b/1-js/05-data-types/04-array/10-maximal-subarray/task.md @@ -2,29 +2,29 @@ importance: 2 --- -# A maximal subarray +# Подмассив наибольшей суммы -The input is an array of numbers, e.g. `arr = [1, -2, 3, 4, -9, 6]`. +На входе массив чисел, например: `arr = [1, -2, 3, 4, -9, 6]`. -The task is: find the contiguous subarray of `arr` with the maximal sum of items. +Задача: найти непрерывный подмассив в `arr`, сумма элементов в котором максимальна. -Write the function `getMaxSubSum(arr)` that will return that sum. +Функция `getMaxSubSum(arr)` должна возвращать эту сумму. -For instance: +Например: ```js -getMaxSubSum([-1, *!*2, 3*/!*, -9]) = 5 (the sum of highlighted items) -getMaxSubSum([*!*2, -1, 2, 3*/!*, -9]) = 6 -getMaxSubSum([-1, 2, 3, -9, *!*11*/!*]) = 11 -getMaxSubSum([-2, -1, *!*1, 2*/!*]) = 3 -getMaxSubSum([*!*100*/!*, -9, 2, -3, 5]) = 100 -getMaxSubSum([*!*1, 2, 3*/!*]) = 6 (take all) +getMaxSubSum([-1, *!*2, 3*/!*, -9]) == 5 (сумма выделенных элементов) +getMaxSubSum([*!*2, -1, 2, 3*/!*, -9]) == 6 +getMaxSubSum([-1, 2, 3, -9, *!*11*/!*]) == 11 +getMaxSubSum([-2, -1, *!*1, 2*/!*]) == 3 +getMaxSubSum([*!*100*/!*, -9, 2, -3, 5]) == 100 +getMaxSubSum([*!*1, 2, 3*/!*]) == 6 (берём все) ``` -If all items are negative, it means that we take none (the subarray is empty), so the sum is zero: +Если все элементы отрицательные - ничего не берём(подмассив пустой) и сумма равна "0": ```js getMaxSubSum([-1, -2, -3]) = 0 ``` -Please try to think of a fast solution: [O(n2)](https://en.wikipedia.org/wiki/Big_O_notation) or even O(n) if you can. +Попробуйте придумать быстрое решение: [O(n2)](https://ru.wikipedia.org/wiki/«O»_большое_и_«o»_малое), а лучше за О(n) операций. diff --git a/1-js/05-data-types/04-array/2-create-array/solution.md b/1-js/05-data-types/04-array/2-create-array/solution.md index f032b55f0c..619b0dc05c 100644 --- a/1-js/05-data-types/04-array/2-create-array/solution.md +++ b/1-js/05-data-types/04-array/2-create-array/solution.md @@ -1,10 +1,10 @@ ```js run -let styles = ["Jazz", "Blues"]; -styles.push("Rock-n-Roll"); -styles[Math.floor((styles.length - 1) / 2)] = "Classics"; +let styles = ["Джаз", "Блюз"]; +styles.push("Рок-н-ролл"); +styles[Math.floor((styles.length - 1) / 2)] = "Классика"; alert( styles.shift() ); -styles.unshift("Rap", "Reggae"); +styles.unshift("Рэп", "Регги"); ``` diff --git a/1-js/05-data-types/04-array/2-create-array/task.md b/1-js/05-data-types/04-array/2-create-array/task.md index 3e93007939..630f777a1b 100644 --- a/1-js/05-data-types/04-array/2-create-array/task.md +++ b/1-js/05-data-types/04-array/2-create-array/task.md @@ -2,23 +2,23 @@ importance: 5 --- -# Array operations. +# Операции с массивами -Let's try 5 array operations. +Давайте произведём 5 операций с массивом. -1. Create an array `styles` with items "Jazz" and "Blues". -2. Append "Rock-n-Roll" to the end. -3. Replace the value in the middle by "Classics". Your code for finding the middle value should work for any arrays with odd length. -4. Strip off the first value of the array and show it. -5. Prepend `Rap` and `Reggae` to the array. +1. Создайте массив `styles` с элементами "Джаз" и "Блюз". +2. Добавьте "Рок-н-ролл" в конец. +3. Замените значение в середине на "Классика". Ваш код для поиска значения в середине должен работать для массивов с любой длиной. +4. Удалите первый элемент массива и покажите его. +5. Вставьте `Рэп` и `Регги` в начало массива. -The array in the process: +Массив по ходу выполнения операций: ```js no-beautify -Jazz, Blues -Jazz, Bues, Rock-n-Roll -Jazz, Classics, Rock-n-Roll -Classics, Rock-n-Roll -Rap, Reggae, Classics, Rock-n-Roll +Джаз, Блюз +Джаз, Блюз, Рок-н-ролл +Джаз, Классика, Рок-н-ролл +Классика, Рок-н-ролл +Рэп, Регги, Классика, Рок-н-ролл ``` diff --git a/1-js/05-data-types/04-array/3-call-array-this/solution.md b/1-js/05-data-types/04-array/3-call-array-this/solution.md index e994ae078b..bc8bab16b9 100644 --- a/1-js/05-data-types/04-array/3-call-array-this/solution.md +++ b/1-js/05-data-types/04-array/3-call-array-this/solution.md @@ -1,6 +1,6 @@ -The call `arr[2]()` is syntactically the good old `obj[method]()`, in the role of `obj` we have `arr`, and in the role of `method` we have `2`. +Вызов `arr[2]()` синтаксически - старый добрый `obj[method]()`, в роли `obj` - `arr`, а в роли `method` - `2`. -So we have a call of the function `arr[2]` as an object method. Naturally, it receives `this` referencing the object `arr` and outputs the array: +Итак, у нас есть вызов функции `arr[2]` как метода объекта. Соответственно, он получает в качестве `this` объект `arr` и выводит массив: ```js run let arr = ["a", "b"]; @@ -9,7 +9,7 @@ arr.push(function() { alert( this ); }) -arr[2](); // "a","b",function +arr[2](); // a,b,function(){...} ``` -The array has 3 values: initially it had two, plus the function. +У массива в итоге 3 элемента: сначала их было 2, плюс функция. diff --git a/1-js/05-data-types/04-array/3-call-array-this/task.md b/1-js/05-data-types/04-array/3-call-array-this/task.md index 340c5feefc..7016df653a 100644 --- a/1-js/05-data-types/04-array/3-call-array-this/task.md +++ b/1-js/05-data-types/04-array/3-call-array-this/task.md @@ -2,16 +2,16 @@ importance: 5 --- -# Calling in an array context +# Вызов в контексте массива -What is the result? Why? +Каков результат? Почему? ```js let arr = ["a", "b"]; arr.push(function() { alert( this ); -}) +}); arr[2](); // ? ``` diff --git a/1-js/05-data-types/04-array/5-array-input-sum/solution.md b/1-js/05-data-types/04-array/5-array-input-sum/solution.md index 75bd683b55..ab2cbb94a7 100644 --- a/1-js/05-data-types/04-array/5-array-input-sum/solution.md +++ b/1-js/05-data-types/04-array/5-array-input-sum/solution.md @@ -1,4 +1,4 @@ -Please note the subtle, but important detail of the solution. We don't convert `value` to number instantly after `prompt`, because after `value = +value` we would not be able to tell an empty string (stop sign) from the zero (valid number). We do it later instead. +Обратите внимание на малозаметную, но важную деталь решения. Мы не преобразуем `value` в число сразу после `prompt`, потому что после `value = +value` мы не сможем отличить пустую строку (конец записи) от "0" (разрешённое число). Мы сделаем это позже. ```js run demo @@ -8,9 +8,9 @@ function sumInput() { while (true) { - let value = prompt("A number please?", 0); + let value = prompt("Введите число", 0); - // should we cancel? + // Прекращаем ввод? if (value === "" || value === null || !isFinite(value)) break; numbers.push(+value); diff --git a/1-js/05-data-types/04-array/5-array-input-sum/task.md b/1-js/05-data-types/04-array/5-array-input-sum/task.md index 4af8e7c95f..408307c75c 100644 --- a/1-js/05-data-types/04-array/5-array-input-sum/task.md +++ b/1-js/05-data-types/04-array/5-array-input-sum/task.md @@ -2,14 +2,14 @@ importance: 4 --- -# Sum input numbers +# Сумма введённых чисел -Write the function `sumInput()` that: +Напишите функцию `sumInput()`, которая: -- Asks the user for values using `prompt` and stores the values in the array. -- Finishes asking when the user enters a non-numeric value, an empty string, or presses "Cancel". -- Calculates and returns the sum of array items. +- Просит пользователя ввести значения, используя `prompt` и сохраняет их в массив. +- Заканчивает запрашивать значения, когда пользователь введёт не числовое значение, пустую строку или нажмёт "Отмена". +- Подсчитывает и возвращает сумму элементов массива. -P.S. A zero `0` is a valid number, please don't stop the input on zero. +P.S. Ноль `0` - считается числом, не останавливайте ввод значений при вводе "0". [demo] diff --git a/1-js/05-data-types/04-array/array-pop.png b/1-js/05-data-types/04-array/array-pop.png deleted file mode 100644 index 4d6867b14b..0000000000 Binary files a/1-js/05-data-types/04-array/array-pop.png and /dev/null differ diff --git a/1-js/05-data-types/04-array/array-pop.svg b/1-js/05-data-types/04-array/array-pop.svg new file mode 100644 index 0000000000..b97b0c5c97 --- /dev/null +++ b/1-js/05-data-types/04-array/array-pop.svg @@ -0,0 +1 @@ +0123"Apple""Orange""Pear""Lemon"length = 4очистить012"Apple""Orange""Pear"length = 3 \ No newline at end of file diff --git a/1-js/05-data-types/04-array/array-pop@2x.png b/1-js/05-data-types/04-array/array-pop@2x.png deleted file mode 100644 index c65ef94467..0000000000 Binary files a/1-js/05-data-types/04-array/array-pop@2x.png and /dev/null differ diff --git a/1-js/05-data-types/04-array/array-shift.png b/1-js/05-data-types/04-array/array-shift.png deleted file mode 100644 index 5798a64760..0000000000 Binary files a/1-js/05-data-types/04-array/array-shift.png and /dev/null differ diff --git a/1-js/05-data-types/04-array/array-shift.svg b/1-js/05-data-types/04-array/array-shift.svg new file mode 100644 index 0000000000..5319324c5b --- /dev/null +++ b/1-js/05-data-types/04-array/array-shift.svg @@ -0,0 +1 @@ +123"Orange""Pear""Lemon"length = 423"Orange""Pear""Lemon"length = 3очиститьпередвинуть элементы влево0"Apple"012"Orange""Pear""Lemon"11 \ No newline at end of file diff --git a/1-js/05-data-types/04-array/array-shift@2x.png b/1-js/05-data-types/04-array/array-shift@2x.png deleted file mode 100644 index ba95f2651e..0000000000 Binary files a/1-js/05-data-types/04-array/array-shift@2x.png and /dev/null differ diff --git a/1-js/05-data-types/04-array/array-speed.png b/1-js/05-data-types/04-array/array-speed.png deleted file mode 100644 index a400d0c1d8..0000000000 Binary files a/1-js/05-data-types/04-array/array-speed.png and /dev/null differ diff --git a/1-js/05-data-types/04-array/array-speed.svg b/1-js/05-data-types/04-array/array-speed.svg new file mode 100644 index 0000000000..41f7d998b4 --- /dev/null +++ b/1-js/05-data-types/04-array/array-speed.svg @@ -0,0 +1 @@ +0123popunshiftpushshift \ No newline at end of file diff --git a/1-js/05-data-types/04-array/array-speed@2x.png b/1-js/05-data-types/04-array/array-speed@2x.png deleted file mode 100644 index 11a3e67c91..0000000000 Binary files a/1-js/05-data-types/04-array/array-speed@2x.png and /dev/null differ diff --git a/1-js/05-data-types/04-array/article.md b/1-js/05-data-types/04-array/article.md index 2fad1601dd..0229729c0c 100644 --- a/1-js/05-data-types/04-array/article.md +++ b/1-js/05-data-types/04-array/article.md @@ -1,286 +1,316 @@ -# Arrays +# Массивы -Objects allow you to store keyed collections of values. That's fine. +Объекты позволяют хранить данные со строковыми ключами. Это замечательно. -But quite often we find that we need an *ordered collection*, where we have a 1st, a 2nd, a 3rd element and so on. For example, we need that to store a list of something: users, goods, HTML elements etc. +Но довольно часто мы понимаем, что нам необходима *упорядоченная коллекция* данных, в которой присутствуют 1-й, 2-й, 3-й элементы и т.д. Например, она понадобится нам для хранения списка чего-либо: пользователей, товаров, элементов HTML и т.д. -It is not convenient to use an object here, because it provides no methods to manage the order of elements. We can’t insert a new property “between” the existing ones. Objects are just not meant for such use. +В этом случае использовать объект неудобно, так как он не предоставляет методов управления порядком элементов. Мы не можем вставить новое свойство "между" уже существующими. Объекты просто не предназначены для этих целей. -There exists a special data structure named `Array`, to store ordered collections. +Для хранения упорядоченных коллекций существует особая структура данных, которая называется массив, `Array`. -## Declaration +## Объявление -There are two syntaxes for creating an empty array: +Существует два варианта синтаксиса для создания пустого массива: ```js let arr = new Array(); let arr = []; ``` -Almost all the time, the second syntax is used. We can supply initial elements in the brackets: +Практически всегда используется второй вариант синтаксиса. В скобках мы можем указать начальные значения элементов: ```js -let fruits = ["Apple", "Orange", "Plum"]; +let fruits = ["Яблоко", "Апельсин", "Слива"]; ``` -Array elements are numbered, starting with zero. +Элементы массива нумеруются, начиная с нуля. -We can get an element by its number in square brackets: +Мы можем получить элемент, указав его номер в квадратных скобках: ```js run -let fruits = ["Apple", "Orange", "Plum"]; +let fruits = ["Яблоко", "Апельсин", "Слива"]; -alert( fruits[0] ); // Apple -alert( fruits[1] ); // Orange -alert( fruits[2] ); // Plum +alert( fruits[0] ); // Яблоко +alert( fruits[1] ); // Апельсин +alert( fruits[2] ); // Слива ``` -We can replace an element: +Мы можем заменить элемент: ```js -fruits[2] = 'Pear'; // now ["Apple", "Orange", "Pear"] +fruits[2] = 'Груша'; // теперь ["Яблоко", "Апельсин", "Груша"] ``` -...Or add a new one to the array: +…Или добавить новый к существующему массиву: ```js -fruits[3] = 'Lemon'; // now ["Apple", "Orange", "Pear", "Lemon"] +fruits[3] = 'Лимон'; // теперь ["Яблоко", "Апельсин", "Груша", "Лимон"] ``` -The total count of the elements in the array is its `length`: +Общее число элементов массива содержится в его свойстве `length`: ```js run -let fruits = ["Apple", "Orange", "Plum"]; +let fruits = ["Яблоко", "Апельсин", "Слива"]; alert( fruits.length ); // 3 ``` -We can also use `alert` to show the whole array. +Вывести массив целиком можно при помощи `alert`. ```js run -let fruits = ["Apple", "Orange", "Plum"]; +let fruits = ["Яблоко", "Апельсин", "Слива"]; -alert( fruits ); // Apple,Orange,Plum +alert( fruits ); // Яблоко, Апельсин, Слива ``` -An array can store elements of any type. +В массиве могут храниться элементы любого типа. -For instance: +Например: ```js run no-beautify -// mix of values -let arr = [ 'Apple', { name: 'John' }, true, function() { alert('hello'); } ]; +// разные типы значений +let arr = [ 'Яблоко', { name: 'Джон' }, true, function() { alert('привет'); } ]; -// get the object at index 1 and then show its name -alert( arr[1].name ); // John +// получить элемент с индексом 1 (объект) и затем показать его свойство +alert( arr[1].name ); // Джон -// get the function at index 3 and run it -arr[3](); // hello +// получить элемент с индексом 3 (функция) и выполнить её +arr[3](); // привет ``` - -````smart header="Trailing comma" -An array, just like an object, may end with a comma: -```js +````smart header="Висячая запятая" +Список элементов массива, как и список свойств объекта, может оканчиваться запятой: +```js let fruits = [ - "Apple", - "Orange", - "Plum"*!*,*/!* + "Яблоко", + "Апельсин", + "Слива"*!*,*/!* ]; ``` -The "trailing comma" style makes it easier to insert/remove items, because all lines become alike. +"Висячая запятая" упрощает процесс добавления/удаления элементов, так как все строки становятся идентичными. ```` +## Получение последних элементов при помощи "at" -## Methods pop/push, shift/unshift +[recent browser="new"] -A [queue](https://en.wikipedia.org/wiki/Queue_(abstract_data_type)) is one of most common uses of an array. In computer science, this means an ordered collection of elements which supports two operations: +Допустим, нам нужен последний элемент массива. -- `push` appends an element to the end. -- `shift` get an element from the beginning, advancing the queue, so that the 2nd element becomes the 1st. +Некоторые языки программирования позволяют использовать отрицательные индексы для той же цели, как-то так: `fruits[-1]`. -![](queue.png) +Однако, в JavaScript такая запись не сработает. Её результатом будет `undefined`, поскольку индекс в квадратных скобках понимается буквально. -Arrays support both operations. +Мы можем явно вычислить индекс последнего элемента, а затем получить к нему доступ вот так: `fruits[fruits.length - 1]`. -In practice we need it very often. For example, a queue of messages that need to be shown on-screen. +```js run +let fruits = ["Apple", "Orange", "Plum"]; +alert( fruits[fruits.length-1] ); // Plum +``` + +Немного громоздко, не так ли? Нам нужно дважды написать имя переменной. + +К счастью, есть более короткий синтаксис: `fruits.at(-1)`: + +```js run +let fruits = ["Apple", "Orange", "Plum"]; +// то же самое, что и fruits[fruits.length-1] +alert( fruits.at(-1) ); // Plum +``` -There's another use case for arrays -- the data structure named [stack](https://en.wikipedia.org/wiki/Stack_(abstract_data_type)). +Другими словами, `arr.at(i)`: -It supports two operations: +- это ровно то же самое, что и `arr[i]`, если `i >= 0`. +- для отрицательных значений `i`, он отступает от конца массива. -- `push` adds an element to the end. -- `pop` takes an element from the end. +## Методы pop/push, shift/unshift -So new elements are added or taken always from the "end". +[Очередь](https://ru.wikipedia.org/wiki/Очередь_(программирование)) – один из самых распространённых вариантов применения массива. В области компьютерных наук так называется упорядоченная коллекция элементов, поддерживающая два вида операций: -A stack is usually illustrated as a pack of cards: new cards are added to the top or taken from the top: +- `push` добавляет элемент в конец. +- `shift` удаляет элемент в начале, сдвигая очередь, так что второй элемент становится первым. -![](stack.png) +![](queue.svg) -For stacks, the latest pushed item is received first, that's also called LIFO (Last-In-First-Out) principle. For queues, we have FIFO (First-In-First-Out). +Массивы поддерживают обе операции. -Arrays in JavaScript can work both as a queue and as a stack. They allow you to add/remove elements both to/from the beginning or the end. +На практике необходимость в этом возникает очень часто. Например, очередь сообщений, которые надо показать на экране. -In computer science the data structure that allows it is called [deque](https://en.wikipedia.org/wiki/Double-ended_queue). +Существует и другой вариант применения для массивов – структура данных, называемая [стек](https://ru.wikipedia.org/wiki/Стек). -**Methods that work with the end of the array:** +Она поддерживает два вида операций: + +- `push` добавляет элемент в конец. +- `pop` удаляет последний элемент. + +Таким образом, новые элементы всегда добавляются или удаляются из "конца". + +Примером стека обычно служит колода карт: новые карты кладутся наверх и берутся тоже сверху: + +![](stack.svg) + +Массивы в JavaScript могут работать и как очередь, и как стек. Мы можем добавлять/удалять элементы как в начало, так и в конец массива. + +В компьютерных науках структура данных, делающая это возможным, называется [двусторонняя очередь](https://ru.wikipedia.org/wiki/Двухсторонняя_очередь). + +**Методы, работающие с концом массива:** `pop` -: Extracts the last element of the array and returns it: +: Удаляет последний элемент из массива и возвращает его: ```js run - let fruits = ["Apple", "Orange", "Pear"]; + let fruits = ["Яблоко", "Апельсин", "Груша"]; - alert( fruits.pop() ); // remove "Pear" and alert it + alert( fruits.pop() ); // удаляем "Груша" и выводим его - alert( fruits ); // Apple, Orange + alert( fruits ); // Яблоко, Апельсин ``` + И `fruits.pop()` и `fruits.at(-1)` возвращают последний элемент массива, но `fruits.pop()` также изменяет массив, удаляя его. + `push` -: Append the element to the end of the array: +: Добавляет элемент в конец массива: ```js run - let fruits = ["Apple", "Orange"]; + let fruits = ["Яблоко", "Апельсин"]; - fruits.push("Pear"); + fruits.push("Груша"); - alert( fruits ); // Apple, Orange, Pear + alert( fruits ); // Яблоко, Апельсин, Груша ``` - The call `fruits.push(...)` is equal to `fruits[fruits.length] = ...`. + Вызов `fruits.push(...)` равнозначен `fruits[fruits.length] = ...`. -**Methods that work with the beginning of the array:** +**Методы, работающие с началом массива:** `shift` -: Extracts the first element of the array and returns it: +: Удаляет из массива первый элемент и возвращает его: - ```js - let fruits = ["Apple", "Orange", "Pear"]; + ```js run + let fruits = ["Яблоко", "Апельсин", "Груша"]; - alert( fruits.shift() ); // remove Apple and alert it + alert( fruits.shift() ); // удаляем Яблоко и выводим его - alert( fruits ); // Orange, Pear + alert( fruits ); // Апельсин, Груша ``` `unshift` -: Add the element to the beginning of the array: +: Добавляет элемент в начало массива: - ```js - let fruits = ["Orange", "Pear"]; + ```js run + let fruits = ["Апельсин", "Груша"]; - fruits.unshift('Apple'); + fruits.unshift('Яблоко'); - alert( fruits ); // Apple, Orange, Pear + alert( fruits ); // Яблоко, Апельсин, Груша ``` -Methods `push` and `unshift` can add multiple elements at once: +Методы `push` и `unshift` могут добавлять сразу несколько элементов: ```js run -let fruits = ["Apple"]; +let fruits = ["Яблоко"]; -fruits.push("Orange", "Peach"); -fruits.unshift("Pineapple", "Lemon"); +fruits.push("Апельсин", "Груша"); +fruits.unshift("Ананас", "Лимон"); -// ["Pineapple", "Lemon", "Apple", "Orange", "Peach"] +// ["Ананас", "Лимон", "Яблоко", "Апельсин", "Груша"] alert( fruits ); ``` -## Internals +## Внутреннее устройство массива -An array is a special kind of object. The square brackets used to access a property `arr[0]` actually come from the object syntax. Numbers are used as keys. +Массив – это особый подвид объектов. Квадратные скобки, используемые для того, чтобы получить доступ к свойству ```arr[0]``` - это по сути обычный синтаксис доступа по ключу, как ```obj[key]```, где в роли ```obj``` у нас ```arr```, а в качестве ключа - числовой индекс. -They extend objects providing special methods to work with ordered collections of data and also the `length` property. But at the core it's still an object. +Массивы расширяют объекты, так как предусматривают специальные методы для работы с упорядоченными коллекциями данных, а также свойство `length`. Но в основе всё равно лежит объект. -Remember, there are only 7 basic types in JavaScript. Array is an object and thus behaves like an object. +Следует помнить, что в JavaScript существует 8 основных типов данных. Массив является объектом и, следовательно, ведёт себя как объект. -For instance, it is copied by reference: +Например, копируется по ссылке: ```js run -let fruits = ["Banana"] +let fruits = ["Банан"] -let arr = fruits; // copy by reference (two variables reference the same array) +let arr = fruits; // копируется по ссылке (две переменные ссылаются на один и тот же массив) alert( arr === fruits ); // true - -arr.push("Pear"); // modify the array by reference -alert( fruits ); // Banana, Pear - 2 items now +arr.push("Груша"); // массив меняется по ссылке + +alert( fruits ); // Банан, Груша - теперь два элемента ``` -...But what makes arrays really special is their internal representation. The engine tries to store its elements in the contiguous memory area, one after another, just as depicted on the illustrations in this chapter, and there are other optimizations as well, to make arrays work really fast. +...Но то, что действительно делает массивы особенными - это их внутреннее представление. Движок JavaScript старается хранить элементы массива в непрерывной области памяти, один за другим, так, как это показано на иллюстрациях к этой главе. Существуют и другие способы оптимизации, благодаря которым массивы работают очень быстро. -But they all break if we quit working with an array as with an "ordered collection" and start working with it as if it were a regular object. +Но все они утратят эффективность, если мы перестанем работать с массивом как с "упорядоченной коллекцией данных" и начнём использовать его как обычный объект. -For instance, technically we can do this: +Например, технически мы можем сделать следующее: ```js -let fruits = []; // make an array +let fruits = []; // создаём массив -fruits[99999] = 5; // assign a property with the index far greater than its length +fruits[99999] = 5; // создаём свойство с индексом, намного превышающим длину массива -fruits.age = 25; // create a property with an arbitrary name +fruits.age = 25; // создаём свойство с произвольным именем ``` -That's possible, because arrays are objects at their base. We can add any properties to them. +Это возможно, потому что в основе массива лежит объект. Мы можем присвоить ему любые свойства. -But the engine will see that we're working with the array as with a regular object. Array-specific optimizations are not suited for such cases and will be turned off, their benefits disappear. +Но движок поймёт, что мы работаем с массивом, как с обычным объектом. Способы оптимизации, используемые для массивов, в этом случае не подходят, поэтому они будут отключены и никакой выгоды не принесут. -The ways to misuse an array: +Варианты неправильного применения массива: -- Add a non-numeric property like `arr.test = 5`. -- Make holes, like: add `arr[0]` and then `arr[1000]` (and nothing between them). -- Fill the array in the reverse order, like `arr[1000]`, `arr[999]` and so on. +- Добавление нечислового свойства, например: `arr.test = 5`. +- Создание "дыр", например: добавление `arr[0]`, затем `arr[1000]` (между ними ничего нет). +- Заполнение массива в обратном порядке, например: `arr[1000]`, `arr[999]` и т.д. -Please think of arrays as special structures to work with the *ordered data*. They provide special methods for that. Arrays are carefully tuned inside JavaScript engines to work with contiguous ordered data, please use them this way. And if you need arbitrary keys, chances are high that you actually require a regular object `{}`. +Массив следует считать особой структурой, позволяющей работать с *упорядоченными данными*. Для этого массивы предоставляют специальные методы. Массивы тщательно настроены в движках JavaScript для работы с однотипными упорядоченными данными, поэтому, пожалуйста, используйте их именно в таких случаях. Если вам нужны произвольные ключи, вполне возможно, лучше подойдёт обычный объект `{}`. -## Performance +## Эффективность -Methods `push/pop` run fast, while `shift/unshift` are slow. +Методы `push/pop` выполняются быстро, а методы `shift/unshift` – медленно. -![](array-speed.png) +![](array-speed.svg) -Why is it faster to work with the end of an array than with its beginning? Let's see what happens during the execution: +Почему работать с концом массива быстрее, чем с его началом? Давайте посмотрим, что происходит во время выполнения: ```js -fruits.shift(); // take 1 element from the start +fruits.shift(); // удаляем первый элемент с начала ``` -It's not enough to take and remove the element with the number `0`. Other elements need to be renumbered as well. +Просто взять и удалить элемент с номером `0` недостаточно. Нужно также заново пронумеровать остальные элементы. -The `shift` operation must do 3 things: +Операция `shift` должна выполнить 3 действия: -1. Remove the element with the index `0`. -2. Move all elements to the left, renumber them from the index `1` to `0`, from `2` to `1` and so on. -3. Update the `length` property. +1. Удалить элемент с индексом `0`. +2. Сдвинуть все элементы влево, заново пронумеровать их, заменив `1` на `0`, `2` на `1` и т.д. +3. Обновить свойство `length` . -![](array-shift.png) +![](array-shift.svg) -**The more elements in the array, the more time to move them, more in-memory operations.** +**Чем больше элементов содержит массив, тем больше времени потребуется для того, чтобы их переместить, больше операций с памятью.** -The similar thing happens with `unshift`: to add an element to the beginning of the array, we need first to move existing elements to the right, increasing their indexes. +То же самое происходит с `unshift`: чтобы добавить элемент в начало массива, нам нужно сначала сдвинуть существующие элементы вправо, увеличивая их индексы. -And what's with `push/pop`? They do not need to move anything. To extract an element from the end, the `pop` method cleans the index and shortens `length`. +А что же с `push/pop`? Им не нужно ничего перемещать. Чтобы удалить элемент в конце массива, метод `pop` очищает индекс и уменьшает значение `length`. -The actions for the `pop` operation: +Действия при операции `pop`: ```js -fruits.pop(); // take 1 element from the end +fruits.pop(); // удаляем один элемент с конца ``` -![](array-pop.png) +![](array-pop.svg) -**The `pop` method does not need to move anything, because other elements keep their indexes. That's why it's blazingly fast.** +**Метод `pop` не требует перемещения, потому что остальные элементы остаются с теми же индексами. Именно поэтому он выполняется очень быстро.** -The similar thing with the `push` method. +Аналогично работает метод `push`. -## Loops +## Перебор элементов -One of the oldest ways to cycle array items is the `for` loop over indexes: +Одним из самых старых способов перебора элементов массива является цикл for по цифровым индексам: ```js run -let arr = ["Apple", "Orange", "Pear"]; +let arr = ["Яблоко", "Апельсин", "Груша"]; *!* for (let i = 0; i < arr.length; i++) { @@ -289,103 +319,102 @@ for (let i = 0; i < arr.length; i++) { } ``` -But for arrays there is another form of loop, `for..of`: +Но для массивов возможен и другой вариант цикла, `for..of`: ```js run -let fruits = ["Apple", "Orange", "Plum"]; +let fruits = ["Яблоко", "Апельсин", "Слива"]; -// iterates over array elements +// проходит по значениям for (let fruit of fruits) { - alert( fruit ); + alert( fruit ); } ``` -The `for..of` doesn't give access to the number of the current element, just its value, but in most cases that's enough. And it's shorter. +Цикл `for..of` не предоставляет доступа к номеру текущего элемента, только к его значению, но в большинстве случаев этого достаточно. А также это короче. -Technically, because arrays are objects, it is also possible to use `for..in`: +Технически, так как массив является объектом, можно использовать и вариант `for..in`: ```js run -let arr = ["Apple", "Orange", "Pear"]; +let arr = ["Яблоко", "Апельсин", "Груша"]; *!* for (let key in arr) { */!* - alert( arr[key] ); // Apple, Orange, Pear + alert( arr[key] ); // Яблоко, Апельсин, Груша } ``` -But that's actually a bad idea. There are potential problems with it: - -1. The loop `for..in` iterates over *all properties*, not only the numeric ones. +Но на самом деле это - плохая идея. Существуют скрытые недостатки этого способа: - There are so-called "array-like" objects in the browser and in other environments, that *look like arrays*. That is, they have `length` and indexes properties, but they may also have other non-numeric properties and methods, which we usually don't need. The `for..in` loop will list them though. So if we need to work with array-like objects, then these "extra" properties can become a problem. +1. Цикл `for..in` выполняет перебор *всех свойств* объекта, а не только цифровых. -2. The `for..in` loop is optimized for generic objects, not arrays, and thus is 10-100 times slower. Of course, it's still very fast. The speedup may only matter in bottlenecks or seem irrelevant. But still we should be aware of the difference. + В браузере и других программных средах также существуют так называемые "псевдомассивы" - объекты, которые *выглядят, как массив*. То есть, у них есть свойство `length` и индексы, но они также могут иметь дополнительные нечисловые свойства и методы, которые нам обычно не нужны. Тем не менее, цикл `for..in` выведет и их. Поэтому, если нам приходится иметь дело с объектами, похожими на массив, такие "лишние" свойства могут стать проблемой. -Generally, we shouldn't use `for..in` for arrays. +2. Цикл `for..in` оптимизирован под произвольные объекты, не массивы, и поэтому в 10-100 раз медленнее. Увеличение скорости выполнения может иметь значение только при возникновении узких мест. Но мы всё же должны представлять разницу. +В общем, не следует использовать цикл `for..in` для массивов. -## A word about "length" +## Немного о "length" -The `length` property automatically updates when we modify the array. To be precise, it is actually not the count of values in the array, but the greatest numeric index plus one. +Свойство `length` автоматически обновляется при изменении массива. Если быть точными, это не количество элементов массива, а наибольший цифровой индекс плюс один. -For instance, a single element with a large index gives a big length: +Например, единственный элемент, имеющий большой индекс, даёт большую длину: ```js run let fruits = []; -fruits[123] = "Apple"; +fruits[123] = "Яблоко"; alert( fruits.length ); // 124 ``` -Note that we usually don't use arrays like that. +Обратите внимание, что обычно мы не используем массивы таким образом. -Another interesting thing about the `length` property is that it's writable. +Ещё один интересный факт о свойстве `length` – его можно перезаписать. -If we increase it manually, nothing interesting happens. But if we decrease it, the array is truncated. The process is irreversible, here's the example: +Если мы вручную увеличим его, ничего интересного не произойдёт. Зато, если мы уменьшим его, массив станет короче. Этот процесс необратим, как мы можем понять из примера: ```js run let arr = [1, 2, 3, 4, 5]; -arr.length = 2; // truncate to 2 elements +arr.length = 2; // укорачиваем до двух элементов alert( arr ); // [1, 2] -arr.length = 5; // return length back -alert( arr[3] ); // undefined: the values do not return +arr.length = 5; // возвращаем length как было +alert( arr[3] ); // undefined: значения не восстановились ``` -So, the simplest way to clear the array is: `arr.length = 0;`. +Таким образом, самый простой способ очистить массив – это `arr.length = 0;`. ## new Array() [#new-array] -There is one more syntax to create an array: +Существует ещё один вариант синтаксиса для создания массива: ```js -let arr = *!*new Array*/!*("Apple", "Pear", "etc"); +let arr = *!*new Array*/!*("Яблоко", "Груша", "и тд"); ``` -It's rarely used, because square brackets `[]` are shorter. Also there's a tricky feature with it. +Он редко применяется, так как квадратные скобки `[]` короче. Кроме того, у него есть хитрая особенность. -If `new Array` is called with a single argument which is a number, then it creates an array *without items, but with the given length*. +Если `new Array` вызывается с одним аргументом, который представляет собой число, он создаёт массив *без элементов, но с заданной длиной*. -Let's see how one can shoot themself in the foot: +Давайте посмотрим, как можно оказать себе медвежью услугу: ```js run -let arr = new Array(2); // will it create an array of [2] ? +let arr = new Array(2); // создастся ли массив [2]? -alert( arr[0] ); // undefined! no elements. +alert( arr[0] ); // undefined! нет элементов. alert( arr.length ); // length 2 ``` -In the code above, `new Array(number)` has all elements `undefined`. +Как мы видим, в коде, представленном выше, в `new Array(number)` все элементы равны `undefined`. -To evade such surprises, we usually use square brackets, unless we really know what we're doing. +Чтобы избежать появления таких неожиданных ситуаций, мы обычно используем квадратные скобки, если, конечно, не знаем точно, что по какой-то причине нужен именно `Array`. -## Multidimensional arrays +## Многомерные массивы -Arrays can have items that are also arrays. We can use it for multidimensional arrays, to store matrices: +Массивы могут содержать элементы, которые тоже являются массивами. Это можно использовать для создания многомерных массивов, например, для хранения матриц: ```js run let matrix = [ @@ -394,14 +423,14 @@ let matrix = [ [7, 8, 9] ]; -alert( matrix[1][1] ); // the central element +alert( matrix[1][1] ); // 5, центральный элемент ``` ## toString -Arrays have their own implementation of `toString` method that returns a comma-separated list of elements. +Массивы по-своему реализуют метод `toString`, который возвращает список элементов, разделённых запятыми. -For instance: +Например: ```js run @@ -411,7 +440,7 @@ alert( arr ); // 1,2,3 alert( String(arr) === '1,2,3' ); // true ``` -Also, let's try this: +Давайте теперь попробуем следующее: ```js run alert( [] + 1 ); // "1" @@ -419,9 +448,9 @@ alert( [1] + 1 ); // "11" alert( [1,2] + 1 ); // "1,21" ``` -Arrays do not have `Symbol.toPrimitive`, neither a viable `valueOf`, they implement only `toString` conversion, so here `[]` becomes an empty string, `[1]` becomes `"1"` and `[1,2]` becomes `"1,2"`. +Массивы не имеют ни `Symbol.toPrimitive`, ни функционирующего `valueOf`, они реализуют только преобразование `toString`, таким образом, здесь `[]` становится пустой строкой, `[1]` становится `"1"`, а `[1,2]` становится `"1,2"`. -When the binary plus `"+"` operator adds something to a string, it converts it to a string as well, so the next step looks like this: +Когда бинарный оператор плюс `"+"` добавляет что-либо к строке, он тоже преобразует это в строку, таким образом: ```js run alert( "" + 1 ); // "1" @@ -429,36 +458,88 @@ alert( "1" + 1 ); // "11" alert( "1,2" + 1 ); // "1,21" ``` -## Summary +## Не сравнивайте массивы при помощи == -Array is a special kind of object, suited to storing and managing ordered data items. +В JavaScript, в отличие от некоторых других языков программирования, массивы не следует сравнивать при помощи оператора `==`. -- The declaration: +У этого оператора нет специального подхода к массивам, он работает с ними, как и с любыми другими объектами. - ```js - // square brackets (usual) - let arr = [item1, item2...]; +Давайте ещё раз напомним правила: - // new Array (exceptionally rare) - let arr = new Array(item1, item2...); - ``` +- Два объекта равны друг другу `==` только в том случае, если они ссылаются на один и тот же объект. +- Если один из аргументов `==` является объектом, а другой - примитивом, то объект преобразуется в примитив, как описано в главе . +- ...За исключением `null` и `undefined`, которые равны `==` друг другу и ничему больше. + +Оператор строгого равенства `===` ещё проще, так как он не преобразует типы. + +Итак, если мы всё же сравниваем массивы с помощью `==`, то они никогда не будут одинаковыми, если только мы не сравним две переменные, которые ссылаются на один и тот же массив + +Например: +```js run +alert( [] == [] ); // false +alert( [0] == [0] ); // false +``` + +Технически эти массивы являются разными объектами. Так что они не равны. Оператор `==` не выполняет поэлементное сравнение. + +Сравнение с примитивами также может дать, казалось бы, странные результаты: + +```js run +alert( 0 == [] ); // true + +alert('0' == [] ); // false +``` + +Здесь, в обоих случаях, мы сравниваем примитив с объектом массива. Таким образом, массив `[]` преобразуется в примитив с целью сравнения и становится пустой строкой `''`. + +Затем продолжается процесс сравнения с примитивами, как описано в главе : + +```js run +// после того, как [] был преобразован в '' +alert( 0 == '' ); // true, так как '' преобразуется в число 0 + +alert('0' == '' ); // false, нет преобразования типов, разные строки +``` + +Так как же сравнить массивы? + +Это просто: не используйте оператор `==`. Вместо этого сравните их по элементам в цикле или используя методы итерации, описанные в следующей главе. + + +## Итого + +Массив – это особый тип объекта, предназначенный для работы с упорядоченным набором элементов. + +Объявление: + +```js +// квадратные скобки (обычно) +let arr = [item1, item2...]; + +// new Array (очень редко) +let arr = new Array(item1, item2...); +``` + +Вызов `new Array(number)` создаёт массив с заданной длиной, но без элементов. - The call to `new Array(number)` creates an array with the given length, but without elements. +- Свойство `length` отражает длину массива или, если точнее, его последний цифровой индекс плюс один. Длина корректируется автоматически методами массива. +- Если мы уменьшаем `length` вручную, массив укорачивается. -- The `length` property is the array length or, to be precise, its last numeric index plus one. It is auto-adjusted by array methods. -- If we shorten `length` manually, the array is truncated. +Получение элементов: -We can use an array as a deque with the following operations: +- Мы можем получить элемент по его индексу, например `arr[0]`. +- Также мы можем использовать метод `at(i)` для получения элементов с отрицательным индексом, для отрицательных значений `i`, он отступает от конца массива. В остальном он работает так же, как `arr[i]`, если `i >= 0`. -- `push(...items)` adds `items` to the end. -- `pop()` removes the element from the end and returns it. -- `shift()` removes the element from the beginning and returns it. -- `unshift(...items)` adds items to the beginning. +Мы можем использовать массив как двустороннюю очередь, используя следующие операции: -To loop over the elements of the array: - - `for (let i=0; i. +Чтобы пройтись по элементам массива: + - `for (let i=0; i. diff --git a/1-js/05-data-types/04-array/queue.png b/1-js/05-data-types/04-array/queue.png deleted file mode 100644 index 39af3beda4..0000000000 Binary files a/1-js/05-data-types/04-array/queue.png and /dev/null differ diff --git a/1-js/05-data-types/04-array/queue.svg b/1-js/05-data-types/04-array/queue.svg new file mode 100644 index 0000000000..e89a3dcd2d --- /dev/null +++ b/1-js/05-data-types/04-array/queue.svg @@ -0,0 +1 @@ +pushshift \ No newline at end of file diff --git a/1-js/05-data-types/04-array/queue@2x.png b/1-js/05-data-types/04-array/queue@2x.png deleted file mode 100644 index 75045a51dc..0000000000 Binary files a/1-js/05-data-types/04-array/queue@2x.png and /dev/null differ diff --git a/1-js/05-data-types/04-array/stack.png b/1-js/05-data-types/04-array/stack.png deleted file mode 100644 index 7d2599355b..0000000000 Binary files a/1-js/05-data-types/04-array/stack.png and /dev/null differ diff --git a/1-js/05-data-types/04-array/stack.svg b/1-js/05-data-types/04-array/stack.svg new file mode 100644 index 0000000000..ae0c474d0f --- /dev/null +++ b/1-js/05-data-types/04-array/stack.svg @@ -0,0 +1 @@ +pushpop \ No newline at end of file diff --git a/1-js/05-data-types/04-array/stack@2x.png b/1-js/05-data-types/04-array/stack@2x.png deleted file mode 100644 index 16c6225186..0000000000 Binary files a/1-js/05-data-types/04-array/stack@2x.png and /dev/null differ diff --git a/1-js/05-data-types/05-array-methods/1-camelcase/_js.view/solution.js b/1-js/05-data-types/05-array-methods/1-camelcase/_js.view/solution.js index 490f570ada..c420d5da52 100644 --- a/1-js/05-data-types/05-array-methods/1-camelcase/_js.view/solution.js +++ b/1-js/05-data-types/05-array-methods/1-camelcase/_js.view/solution.js @@ -1,10 +1,10 @@ function camelize(str) { return str - .split('-') // splits 'my-long-word' into array ['my', 'long', 'word'] + .split('-') // разбивает 'my-long-word' на массив ['my', 'long', 'word'] .map( - // capitalizes first letters of all array items except the first one - // converts ['my', 'long', 'word'] into ['my', 'Long', 'Word'] + // Переводит в верхний регистр первые буквы всех элементом массива за исключением первого + // превращает ['my', 'long', 'word'] в ['my', 'Long', 'Word'] (word, index) => index == 0 ? word : word[0].toUpperCase() + word.slice(1) ) - .join(''); // joins ['my', 'Long', 'Word'] into 'myLongWord' + .join(''); // соединяет ['my', 'Long', 'Word'] в 'myLongWord' } diff --git a/1-js/05-data-types/05-array-methods/1-camelcase/solution.md b/1-js/05-data-types/05-array-methods/1-camelcase/solution.md index e69de29bb2..8b13789179 100644 --- a/1-js/05-data-types/05-array-methods/1-camelcase/solution.md +++ b/1-js/05-data-types/05-array-methods/1-camelcase/solution.md @@ -0,0 +1 @@ + diff --git a/1-js/05-data-types/05-array-methods/1-camelcase/task.md b/1-js/05-data-types/05-array-methods/1-camelcase/task.md index ef5944636e..befb07fa4f 100644 --- a/1-js/05-data-types/05-array-methods/1-camelcase/task.md +++ b/1-js/05-data-types/05-array-methods/1-camelcase/task.md @@ -2,13 +2,13 @@ importance: 5 --- -# Translate border-left-width to borderLeftWidth +# Переведите текст вида border-left-width в borderLeftWidth -Write the function `camelize(str)` that changes dash-separated words like "my-short-string" into camel-cased "myShortString". +Напишите функцию `camelize(str)`, которая преобразует строки вида "my-short-string" в "myShortString". -That is: removes all dashes, each word after dash becomes uppercased. +То есть дефисы удаляются, а все слова после них получают заглавную букву. -Examples: +Примеры: ```js camelize("background-color") == 'backgroundColor'; @@ -16,4 +16,4 @@ camelize("list-style-image") == 'listStyleImage'; camelize("-webkit-transition") == 'WebkitTransition'; ``` -P.S. Hint: use `split` to split the string into an array, transform it and `join` back. +P.S. Подсказка: используйте `split`, чтобы разбить строку на массив символов, потом переделайте всё как нужно и методом `join` соедините обратно. diff --git a/1-js/05-data-types/05-array-methods/10-average-age/solution.md b/1-js/05-data-types/05-array-methods/10-average-age/solution.md index f5d4df9318..bba79afceb 100644 --- a/1-js/05-data-types/05-array-methods/10-average-age/solution.md +++ b/1-js/05-data-types/05-array-methods/10-average-age/solution.md @@ -3,11 +3,11 @@ function getAverageAge(users) { return users.reduce((prev, user) => prev + user.age, 0) / users.length; } -let john = { name: "John", age: 25 }; -let pete = { name: "Pete", age: 30 }; -let mary = { name: "Mary", age: 29 }; +let vasya = { name: "Вася", age: 25 }; +let petya = { name: "Петя", age: 30 }; +let masha = { name: "Маша", age: 29 }; -let arr = [ john, pete, mary ]; +let arr = [ vasya, petya, masha ]; alert( getAverageAge(arr) ); // 28 ``` diff --git a/1-js/05-data-types/05-array-methods/10-average-age/task.md b/1-js/05-data-types/05-array-methods/10-average-age/task.md index a991c156b9..4e3ad6259b 100644 --- a/1-js/05-data-types/05-array-methods/10-average-age/task.md +++ b/1-js/05-data-types/05-array-methods/10-average-age/task.md @@ -2,21 +2,20 @@ importance: 4 --- -# Get average age +# Получить средний возраст -Write the function `getAverageAge(users)` that gets an array of objects with property `age` and gets the average. +Напишите функцию `getAverageAge(users)`, которая принимает массив объектов со свойством `age` и возвращает средний возраст. -The formula for the average is `(age1 + age2 + ... + ageN) / N`. +Формула вычисления среднего арифметического значения: `(age1 + age2 + ... + ageN) / N`. -For instance: +Например: ```js no-beautify -let john = { name: "John", age: 25 }; -let pete = { name: "Pete", age: 30 }; -let mary = { name: "Mary", age: 29 }; +let vasya = { name: "Вася", age: 25 }; +let petya = { name: "Петя", age: 30 }; +let masha = { name: "Маша", age: 29 }; -let arr = [ john, pete, mary ]; +let arr = [ vasya, petya, masha ]; alert( getAverageAge(arr) ); // (25 + 30 + 29) / 3 = 28 ``` - diff --git a/1-js/05-data-types/05-array-methods/11-array-unique/solution.md b/1-js/05-data-types/05-array-methods/11-array-unique/solution.md index 32d3b2679c..cdc5571a5a 100644 --- a/1-js/05-data-types/05-array-methods/11-array-unique/solution.md +++ b/1-js/05-data-types/05-array-methods/11-array-unique/solution.md @@ -1,6 +1,6 @@ -Let's walk the array items: -- For each item we'll check if the resulting array already has that item. -- If it is so, then ignore, otherwise add to results. +Давайте пройдёмся по элементам массива: +- Для каждого элемента мы проверим, есть ли он в массиве с результатом. +- Если есть, то игнорируем его, а если нет - добавляем к результатам. ```js run demo function unique(arr) { @@ -15,25 +15,25 @@ function unique(arr) { return result; } -let strings = ["Hare", "Krishna", "Hare", "Krishna", - "Krishna", "Krishna", "Hare", "Hare", ":-O" +let strings = ["кришна", "кришна", "харе", "харе", + "харе", "харе", "кришна", "кришна", ":-O" ]; -alert( unique(strings) ); // Hare, Krishna, :-O +alert( unique(strings) ); // кришна, харе, :-O ``` -The code works, but there's a potential performance problem in it. +Код работает, но в нём есть потенциальная проблема с производительностью. -The method `result.includes(str)` internally walks the array `result` and compares each element against `str` to find the match. +Метод `result.includes(str)` внутри себя обходит массив `result` и сравнивает каждый элемент с `str`, чтобы найти совпадение. -So if there are `100` elements in `result` and no one matches `str`, then it will walk the whole `result` and do exactly `100` comparisons. And if `result` is large, like `10000`, then there would be `10000` comparisons. +Таким образом, если `result` содержит `100` элементов и ни один не совпадает со `str`, тогда он обойдёт весь `result` и сделает ровно `100` сравнений. А если `result` большой, например, `10000`, то будет произведено `10000` сравнений. -That's not a problem by itself, because JavaScript engines are very fast, so walk `10000` array is a matter of microseconds. +Само по себе это не проблема, потому что движки JavaScript очень быстрые, поэтому обход `10000` элементов массива занимает считанные микросекунды. -But we do such test for each element of `arr`, in the `for` loop. +Но мы делаем такую проверку для каждого элемента `arr` в цикле `for`. -So if `arr.length` is `10000` we'll have something like `10000*10000` = 100 millions of comparisons. That's a lot. +Поэтому, если `arr.length` равен `10000`, у нас будет что-то вроде `10000*10000` = 100 миллионов сравнений. Это многовато. -So the solution is only good for small arrays. +Вот почему данное решение подходит только для небольших массивов. -Further in the chapter we'll see how to optimize it. +Далее в главе мы увидим, как его оптимизировать. diff --git a/1-js/05-data-types/05-array-methods/11-array-unique/task.md b/1-js/05-data-types/05-array-methods/11-array-unique/task.md index 5b56d36218..ddfdae46c5 100644 --- a/1-js/05-data-types/05-array-methods/11-array-unique/task.md +++ b/1-js/05-data-types/05-array-methods/11-array-unique/task.md @@ -2,22 +2,23 @@ importance: 4 --- -# Filter unique array members +# Оставить уникальные элементы массива -Let `arr` be an array. +Пусть `arr` – массив строк. -Create a function `unique(arr)` that should return an array with unique items of `arr`. +Напишите функцию `unique(arr)`, которая возвращает массив, содержащий только уникальные элементы `arr`. -For instance: +Например: ```js function unique(arr) { - /* your code */ + /* ваш код */ } -let strings = ["Hare", "Krishna", "Hare", "Krishna", - "Krishna", "Krishna", "Hare", "Hare", ":-O" +let strings = ["кришна", "кришна", "харе", "харе", + "харе", "харе", "кришна", "кришна", ":-O" ]; -alert( unique(strings) ); // Hare, Krishna, :-O +alert( unique(strings) ); // кришна, харе, :-O ``` + diff --git a/1-js/05-data-types/05-array-methods/12-reduce-object/_js.view/solution.js b/1-js/05-data-types/05-array-methods/12-reduce-object/_js.view/solution.js new file mode 100644 index 0000000000..8dea23a06a --- /dev/null +++ b/1-js/05-data-types/05-array-methods/12-reduce-object/_js.view/solution.js @@ -0,0 +1,6 @@ +function groupById(array) { + return array.reduce((obj, value) => { + obj[value.id] = value; + return obj; + }, {}) +} diff --git a/1-js/05-data-types/05-array-methods/12-reduce-object/_js.view/test.js b/1-js/05-data-types/05-array-methods/12-reduce-object/_js.view/test.js new file mode 100644 index 0000000000..e48ba138db --- /dev/null +++ b/1-js/05-data-types/05-array-methods/12-reduce-object/_js.view/test.js @@ -0,0 +1,21 @@ +describe("groupById", function() { + + it("creates an object grouped by id", function() { + let users = [ + {id: 'john', name: "John Smith", age: 20}, + {id: 'ann', name: "Ann Smith", age: 24}, + {id: 'pete', name: "Pete Peterson", age: 31}, + ]; + + assert.deepEqual(groupById(users), { + john: {id: 'john', name: "John Smith", age: 20}, + ann: {id: 'ann', name: "Ann Smith", age: 24}, + pete: {id: 'pete', name: "Pete Peterson", age: 31}, + }); + }); + + it("works with an empty array", function() { + users = []; + assert.deepEqual(groupById(users), {}); + }); +}); diff --git a/1-js/05-data-types/05-array-methods/12-reduce-object/solution.md b/1-js/05-data-types/05-array-methods/12-reduce-object/solution.md new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/1-js/05-data-types/05-array-methods/12-reduce-object/solution.md @@ -0,0 +1 @@ + diff --git a/1-js/05-data-types/05-array-methods/12-reduce-object/task.md b/1-js/05-data-types/05-array-methods/12-reduce-object/task.md new file mode 100644 index 0000000000..9164e39e33 --- /dev/null +++ b/1-js/05-data-types/05-array-methods/12-reduce-object/task.md @@ -0,0 +1,37 @@ +importance: 4 + +--- + +# Создайте объект с ключами из массива + +Допустим, мы получили массив пользователей в виде `{id:..., name:..., age:... }`. + +Создайте функцию `groupById(arr)`, которая создаст из него объект с `id` в качестве ключа и элементами массива в качестве значений. + +Например: + +```js +let users = [ + {id: 'john', name: "John Smith", age: 20}, + {id: 'ann', name: "Ann Smith", age: 24}, + {id: 'pete', name: "Pete Peterson", age: 31}, +]; + +let usersById = groupById(users); + +/* +после вызова у нас должно получиться: + +usersById = { + john: {id: 'john', name: "John Smith", age: 20}, + ann: {id: 'ann', name: "Ann Smith", age: 24}, + pete: {id: 'pete', name: "Pete Peterson", age: 31}, +} +*/ +``` + +Такая функция очень удобна при работе с данными, которые приходят с сервера. + +В этой задаче мы предполагаем, что `id` уникален. Не может быть двух элементов массива с одинаковым `id`. + +Используйте метод `.reduce` в решении. diff --git a/1-js/05-data-types/05-array-methods/2-filter-range/_js.view/solution.js b/1-js/05-data-types/05-array-methods/2-filter-range/_js.view/solution.js index 0bdfbae5a6..ae09382d95 100644 --- a/1-js/05-data-types/05-array-methods/2-filter-range/_js.view/solution.js +++ b/1-js/05-data-types/05-array-methods/2-filter-range/_js.view/solution.js @@ -1,5 +1,5 @@ function filterRange(arr, a, b) { - // added brackets around the expression for better readability + // добавлены скобки вокруг выражения для лучшей читаемости return arr.filter(item => (a <= item && item <= b)); } \ No newline at end of file diff --git a/1-js/05-data-types/05-array-methods/2-filter-range/solution.md b/1-js/05-data-types/05-array-methods/2-filter-range/solution.md index 73993a07a0..478cfbf344 100644 --- a/1-js/05-data-types/05-array-methods/2-filter-range/solution.md +++ b/1-js/05-data-types/05-array-methods/2-filter-range/solution.md @@ -1,6 +1,6 @@ ```js run demo function filterRange(arr, a, b) { - // added brackets around the expression for better readability + // добавлены скобки вокруг выражения для улучшения читабельности return arr.filter(item => (a <= item && item <= b)); } @@ -8,7 +8,7 @@ let arr = [5, 3, 8, 1]; let filtered = filterRange(arr, 1, 4); -alert( filtered ); // 3,1 (matching values) +alert( filtered ); // 3,1 (совпадающие значения) -alert( arr ); // 5,3,8,1 (not modified) +alert( arr ); // 5,3,8,1 (без изменений) ``` diff --git a/1-js/05-data-types/05-array-methods/2-filter-range/task.md b/1-js/05-data-types/05-array-methods/2-filter-range/task.md index 18b2c1d9b5..3fb9dd3be9 100644 --- a/1-js/05-data-types/05-array-methods/2-filter-range/task.md +++ b/1-js/05-data-types/05-array-methods/2-filter-range/task.md @@ -2,21 +2,21 @@ importance: 4 --- -# Filter range +# Фильтрация по диапазону -Write a function `filterRange(arr, a, b)` that gets an array `arr`, looks for elements between `a` and `b` in it and returns an array of them. +Напишите функцию `filterRange(arr, a, b)`, которая принимает массив `arr`, ищет элементы со значениями больше или равными `a` и меньше или равными `b` и возвращает результат в виде массива. -The function should not modify the array. It should return the new array. +Функция должна возвращать новый массив и не изменять исходный. -For instance: +Например: ```js let arr = [5, 3, 8, 1]; let filtered = filterRange(arr, 1, 4); -alert( filtered ); // 3,1 (matching values) +alert( filtered ); // 3,1 (совпадающие значения) -alert( arr ); // 5,3,8,1 (not modified) +alert( arr ); // 5,3,8,1 (без изменений) ``` diff --git a/1-js/05-data-types/05-array-methods/3-filter-range-in-place/_js.view/solution.js b/1-js/05-data-types/05-array-methods/3-filter-range-in-place/_js.view/solution.js index 488db3755b..83296d724f 100644 --- a/1-js/05-data-types/05-array-methods/3-filter-range-in-place/_js.view/solution.js +++ b/1-js/05-data-types/05-array-methods/3-filter-range-in-place/_js.view/solution.js @@ -4,7 +4,7 @@ function filterRangeInPlace(arr, a, b) { for (let i = 0; i < arr.length; i++) { let val = arr[i]; - // remove if outside of the interval + // удалить, если за пределами интервала if (val < a || val > b) { arr.splice(i, 1); i--; diff --git a/1-js/05-data-types/05-array-methods/3-filter-range-in-place/_js.view/test.js b/1-js/05-data-types/05-array-methods/3-filter-range-in-place/_js.view/test.js index db32d9a115..241b74c6ed 100644 --- a/1-js/05-data-types/05-array-methods/3-filter-range-in-place/_js.view/test.js +++ b/1-js/05-data-types/05-array-methods/3-filter-range-in-place/_js.view/test.js @@ -4,13 +4,13 @@ describe("filterRangeInPlace", function() { let arr = [5, 3, 8, 1]; - filterRangeInPlace(arr, 1, 4); + filterRangeInPlace(arr, 2, 5); - assert.deepEqual(arr, [3, 1]); + assert.deepEqual(arr, [5, 3]); }); it("doesn't return anything", function() { assert.isUndefined(filterRangeInPlace([1,2,3], 1, 4)); }); -}); \ No newline at end of file +}); diff --git a/1-js/05-data-types/05-array-methods/3-filter-range-in-place/solution.md b/1-js/05-data-types/05-array-methods/3-filter-range-in-place/solution.md index 36e3130ff0..f212f24528 100644 --- a/1-js/05-data-types/05-array-methods/3-filter-range-in-place/solution.md +++ b/1-js/05-data-types/05-array-methods/3-filter-range-in-place/solution.md @@ -4,7 +4,7 @@ function filterRangeInPlace(arr, a, b) { for (let i = 0; i < arr.length; i++) { let val = arr[i]; - // remove if outside of the interval + // удалить, если за пределами интервала if (val < a || val > b) { arr.splice(i, 1); i--; @@ -15,7 +15,7 @@ function filterRangeInPlace(arr, a, b) { let arr = [5, 3, 8, 1]; -filterRangeInPlace(arr, 1, 4); // removed the numbers except from 1 to 4 +filterRangeInPlace(arr, 1, 4); // удалены числа вне диапазона 1..4 alert( arr ); // [3, 1] ``` diff --git a/1-js/05-data-types/05-array-methods/3-filter-range-in-place/task.md b/1-js/05-data-types/05-array-methods/3-filter-range-in-place/task.md index 7066a51ab2..5f2f537e1f 100644 --- a/1-js/05-data-types/05-array-methods/3-filter-range-in-place/task.md +++ b/1-js/05-data-types/05-array-methods/3-filter-range-in-place/task.md @@ -2,17 +2,17 @@ importance: 4 --- -# Filter range "in place" +# Фильтрация по диапазону "на месте" -Write a function `filterRangeInPlace(arr, a, b)` that gets an array `arr` and removes from it all values except those that are between `a` and `b`. The test is: `a ≤ arr[i] ≤ b`. +Напишите функцию `filterRangeInPlace(arr, a, b)`, которая принимает массив `arr` и удаляет из него все значения кроме тех, которые находятся между `a` и `b`. То есть, проверка имеет вид `a ≤ arr[i] ≤ b`. -The function should only modify the array. It should not return anything. +Функция должна изменять принимаемый массив и ничего не возвращать. -For instance: +Например: ```js let arr = [5, 3, 8, 1]; -filterRangeInPlace(arr, 1, 4); // removed the numbers except from 1 to 4 +filterRangeInPlace(arr, 1, 4); // удалены числа вне диапазона 1..4 alert( arr ); // [3, 1] ``` diff --git a/1-js/05-data-types/05-array-methods/4-sort-back/task.md b/1-js/05-data-types/05-array-methods/4-sort-back/task.md index 05a08aad0b..382c207219 100644 --- a/1-js/05-data-types/05-array-methods/4-sort-back/task.md +++ b/1-js/05-data-types/05-array-methods/4-sort-back/task.md @@ -2,12 +2,12 @@ importance: 4 --- -# Sort in the reverse order +# Сортировать в порядке по убыванию ```js let arr = [5, 2, 1, -10, 8]; -// ... your code to sort it in the reverse order +// ... ваш код для сортировки по убыванию alert( arr ); // 8, 5, 2, 1, -10 ``` diff --git a/1-js/05-data-types/05-array-methods/5-copy-sort-array/solution.md b/1-js/05-data-types/05-array-methods/5-copy-sort-array/solution.md index 8537b129e6..f3d0f226d2 100644 --- a/1-js/05-data-types/05-array-methods/5-copy-sort-array/solution.md +++ b/1-js/05-data-types/05-array-methods/5-copy-sort-array/solution.md @@ -1,4 +1,4 @@ -We can use `slice()` to make a copy and run the sort on it: +Для копирования массива используем `slice()` и тут же – сортировку: ```js run function copySorted(arr) { diff --git a/1-js/05-data-types/05-array-methods/5-copy-sort-array/task.md b/1-js/05-data-types/05-array-methods/5-copy-sort-array/task.md index c1395b4adf..a682a4e30b 100644 --- a/1-js/05-data-types/05-array-methods/5-copy-sort-array/task.md +++ b/1-js/05-data-types/05-array-methods/5-copy-sort-array/task.md @@ -2,11 +2,11 @@ importance: 5 --- -# Copy and sort array +# Скопировать и отсортировать массив -We have an array of strings `arr`. We'd like to have a sorted copy of it, but keep `arr` unmodified. +У нас есть массив строк `arr`. Нужно получить отсортированную копию, но оставить `arr` неизменённым. -Create a function `copySorted(arr)` that returns such a copy. +Создайте функцию `copySorted(arr)`, которая будет возвращать такую копию. ```js let arr = ["HTML", "JavaScript", "CSS"]; @@ -14,5 +14,5 @@ let arr = ["HTML", "JavaScript", "CSS"]; let sorted = copySorted(arr); alert( sorted ); // CSS, HTML, JavaScript -alert( arr ); // HTML, JavaScript, CSS (no changes) +alert( arr ); // HTML, JavaScript, CSS (без изменений) ``` diff --git a/1-js/05-data-types/05-array-methods/6-array-get-names/solution.md b/1-js/05-data-types/05-array-methods/6-array-get-names/solution.md index f44a2b8129..3dce753c8a 100644 --- a/1-js/05-data-types/05-array-methods/6-array-get-names/solution.md +++ b/1-js/05-data-types/05-array-methods/6-array-get-names/solution.md @@ -1,13 +1,13 @@ ```js run -let john = { name: "John", age: 25 }; -let pete = { name: "Pete", age: 30 }; -let mary = { name: "Mary", age: 28 }; +let vasya = { name: "Вася", age: 25 }; +let petya = { name: "Петя", age: 30 }; +let masha = { name: "Маша", age: 28 }; -let users = [ john, pete, mary ]; +let users = [ vasya, petya, masha ]; let names = users.map(item => item.name); -alert( names ); // John, Pete, Mary +alert( names ); // Вася, Петя, Маша ``` diff --git a/1-js/05-data-types/05-array-methods/6-array-get-names/task.md b/1-js/05-data-types/05-array-methods/6-array-get-names/task.md index 74c8a9d749..3d9986c6ee 100644 --- a/1-js/05-data-types/05-array-methods/6-array-get-names/task.md +++ b/1-js/05-data-types/05-array-methods/6-array-get-names/task.md @@ -2,21 +2,21 @@ importance: 5 --- -# Map to names +# Трансформировать в массив имён -You have an array of `user` objects, each one has `user.name`. Write the code that converts it into an array of names. +У вас есть массив объектов `user`, и в каждом из них есть `user.name`. Напишите код, который преобразует их в массив имён. -For instance: +Например: ```js no-beautify -let john = { name: "John", age: 25 }; -let pete = { name: "Pete", age: 30 }; -let mary = { name: "Mary", age: 28 }; +let vasya = { name: "Вася", age: 25 }; +let petya = { name: "Петя", age: 30 }; +let masha = { name: "Маша", age: 28 }; -let users = [ john, pete, mary ]; +let users = [ vasya, petya, masha ]; -let names = /* ... your code */ +let names = /* ... ваш код */ -alert( names ); // John, Pete, Mary +alert( names ); // Вася, Петя, Маша ``` diff --git a/1-js/05-data-types/05-array-methods/6-calculator-extendable/_js.view/solution.js b/1-js/05-data-types/05-array-methods/6-calculator-extendable/_js.view/solution.js index 50c40e8042..45ef1619da 100644 --- a/1-js/05-data-types/05-array-methods/6-calculator-extendable/_js.view/solution.js +++ b/1-js/05-data-types/05-array-methods/6-calculator-extendable/_js.view/solution.js @@ -1,6 +1,6 @@ function Calculator() { - let methods = { + this.methods = { "-": (a, b) => a - b, "+": (a, b) => a + b }; @@ -12,14 +12,14 @@ function Calculator() { op = split[1], b = +split[2] - if (!methods[op] || isNaN(a) || isNaN(b)) { + if (!this.methods[op] || isNaN(a) || isNaN(b)) { return NaN; } - return methods[op](a, b); + return this.methods[op](a, b); } this.addMethod = function(name, func) { - methods[name] = func; + this.methods[name] = func; }; } diff --git a/1-js/05-data-types/05-array-methods/6-calculator-extendable/solution.md b/1-js/05-data-types/05-array-methods/6-calculator-extendable/solution.md index 41178663dd..85c8fd622c 100644 --- a/1-js/05-data-types/05-array-methods/6-calculator-extendable/solution.md +++ b/1-js/05-data-types/05-array-methods/6-calculator-extendable/solution.md @@ -1,3 +1,3 @@ -- Please note how methods are stored. They are simply added to the internal object. -- All tests and numeric conversions are done in the `calculate` method. In future it may be extended to support more complex expressions. +- Обратите внимание, как хранятся методы. Они просто добавляются к внутреннему объекту. +- Все тесты и числовые преобразования выполняются в методе `calculate`. В будущем он может быть расширен для поддержки более сложных выражений. diff --git a/1-js/05-data-types/05-array-methods/6-calculator-extendable/task.md b/1-js/05-data-types/05-array-methods/6-calculator-extendable/task.md index cc5453cebf..fbf228e282 100644 --- a/1-js/05-data-types/05-array-methods/6-calculator-extendable/task.md +++ b/1-js/05-data-types/05-array-methods/6-calculator-extendable/task.md @@ -2,24 +2,24 @@ importance: 5 --- -# Create an extendable calculator +# Создать расширяемый калькулятор -Create a constructor function `Calculator` that creates "extendable" calculator objects. +Создайте функцию конструктор `Calculator`, которая создаёт "расширяемые" объекты калькулятора. -The task consists of two parts. +Задание состоит из двух частей. -1. First, implement the method `calculate(str)` that takes a string like `"1 + 2"` in the format "NUMBER operator NUMBER" (space-delimited) and returns the result. Should understand plus `+` and minus `-`. +1. Во-первых, реализуйте метод `calculate(str)`, который принимает строку типа `"1 + 2"` в формате "ЧИСЛО оператор ЧИСЛО" (разделено пробелами) и возвращает результат. Метод должен понимать плюс `+` и минус `-`. - Usage example: + Пример использования: ```js let calc = new Calculator; alert( calc.calculate("3 + 7") ); // 10 ``` -2. Then add the method `addMethod(name, func)` that teaches the calculator a new operation. It takes the operator `name` and the two-argument function `func(a,b)` that implements it. +2. Затем добавьте метод `addMethod(name, func)`, который добавляет в калькулятор новые операции. Он принимает оператор `name` и функцию с двумя аргументами `func(a,b)`, которая описывает его. - For instance, let's add the multiplication `*`, division `/` and power `**`: + Например, давайте добавим умножение `*`, деление `/` и возведение в степень `**`: ```js let powerCalc = new Calculator; @@ -31,6 +31,6 @@ The task consists of two parts. alert( result ); // 8 ``` -- No brackets or complex expressions in this task. -- The numbers and the operator are delimited with exactly one space. -- There may be error handling if you'd like to add it. +- Для этой задачи не нужны скобки или сложные выражения. +- Числа и оператор разделены ровно одним пробелом. +- Не лишним будет добавить обработку ошибок. diff --git a/1-js/05-data-types/05-array-methods/7-map-objects/solution.md b/1-js/05-data-types/05-array-methods/7-map-objects/solution.md index 5d8bf4a13d..b5dadd5e65 100644 --- a/1-js/05-data-types/05-array-methods/7-map-objects/solution.md +++ b/1-js/05-data-types/05-array-methods/7-map-objects/solution.md @@ -1,10 +1,10 @@ ```js run no-beautify -let john = { name: "John", surname: "Smith", id: 1 }; -let pete = { name: "Pete", surname: "Hunt", id: 2 }; -let mary = { name: "Mary", surname: "Key", id: 3 }; +let vasya = { name: "Вася", surname: "Пупкин", id: 1 }; +let petya = { name: "Петя", surname: "Иванов", id: 2 }; +let masha = { name: "Маша", surname: "Петрова", id: 3 }; -let users = [ john, pete, mary ]; +let users = [ vasya, petya, masha ]; *!* let usersMapped = users.map(user => ({ @@ -15,19 +15,19 @@ let usersMapped = users.map(user => ({ /* usersMapped = [ - { fullName: "John Smith", id: 1 }, - { fullName: "Pete Hunt", id: 2 }, - { fullName: "Mary Key", id: 3 } + { fullName: "Вася Пупкин", id: 1 }, + { fullName: "Петя Иванов", id: 2 }, + { fullName: "Маша Петрова", id: 3 } ] */ alert( usersMapped[0].id ); // 1 -alert( usersMapped[0].fullName ); // John Smith +alert( usersMapped[0].fullName ); // Вася Пупкин ``` -Please note that in for the arrow functions we need to use additional brackets. +Обратите внимание, что для стрелочных функций мы должны использовать дополнительные скобки. -We can't write like this: +Мы не можем написать вот так: ```js let usersMapped = users.map(user => *!*{*/!* fullName: `${user.name} ${user.surname}`, @@ -35,9 +35,9 @@ let usersMapped = users.map(user => *!*{*/!* }); ``` -As we remember, there are two arrow functions: without body `value => expr` and with body `value => {...}`. +Как мы помним, есть две функции со стрелками: без тела `value => expr` и с телом `value => {...}`. -Here JavaScript would treat `{` as the start of function body, not the start of the object. The workaround is to wrap them in the "normal" brackets: +Здесь JavaScript будет трактовать `{` как начало тела функции, а не начало объекта. Чтобы обойти это, нужно заключить их в "нормальные" скобки: ```js let usersMapped = users.map(user => *!*({*/!* @@ -46,6 +46,6 @@ let usersMapped = users.map(user => *!*({*/!* })); ``` -Now fine. +Теперь всё хорошо. diff --git a/1-js/05-data-types/05-array-methods/7-map-objects/task.md b/1-js/05-data-types/05-array-methods/7-map-objects/task.md index b11d121554..c6e285fe2a 100644 --- a/1-js/05-data-types/05-array-methods/7-map-objects/task.md +++ b/1-js/05-data-types/05-array-methods/7-map-objects/task.md @@ -2,35 +2,35 @@ importance: 5 --- -# Map to objects +# Трансформировать в объекты -You have an array of `user` objects, each one has `name`, `surname` and `id`. +У вас есть массив объектов `user`, и у каждого из объектов есть `name`, `surname` и `id`. -Write the code to create another array from it, of objects with `id` and `fullName`, where `fullName` is generated from `name` and `surname`. +Напишите код, который создаст ещё один массив объектов с параметрами `id` и `fullName`, где `fullName` - состоит из `name` и `surname`. -For instance: +Например: ```js no-beautify -let john = { name: "John", surname: "Smith", id: 1 }; -let pete = { name: "Pete", surname: "Hunt", id: 2 }; -let mary = { name: "Mary", surname: "Key", id: 3 }; +let vasya = { name: "Вася", surname: "Пупкин", id: 1 }; +let petya = { name: "Петя", surname: "Иванов", id: 2 }; +let masha = { name: "Маша", surname: "Петрова", id: 3 }; -let users = [ john, pete, mary ]; +let users = [ vasya, petya, masha ]; *!* -let usersMapped = /* ... your code ... */ +let usersMapped = /* ... ваш код ... */ */!* /* usersMapped = [ - { fullName: "John Smith", id: 1 }, - { fullName: "Pete Hunt", id: 2 }, - { fullName: "Mary Key", id: 3 } + { fullName: "Вася Пупкин", id: 1 }, + { fullName: "Петя Иванов", id: 2 }, + { fullName: "Маша Петрова", id: 3 } ] */ alert( usersMapped[0].id ) // 1 -alert( usersMapped[0].fullName ) // John Smith +alert( usersMapped[0].fullName ) // Вася Пупкин ``` -So, actually you need to map one array of objects to another. Try using `=>` here. There's a small catch. \ No newline at end of file +Итак, на самом деле вам нужно трансформировать один массив объектов в другой. Попробуйте использовать `=>`. Это небольшая уловка. \ No newline at end of file diff --git a/1-js/05-data-types/05-array-methods/8-sort-objects/solution.md b/1-js/05-data-types/05-array-methods/8-sort-objects/solution.md index 9f1ade7075..554f38bc15 100644 --- a/1-js/05-data-types/05-array-methods/8-sort-objects/solution.md +++ b/1-js/05-data-types/05-array-methods/8-sort-objects/solution.md @@ -1,18 +1,18 @@ ```js run no-beautify function sortByAge(arr) { - arr.sort((a, b) => a.age > b.age ? 1 : -1); + arr.sort((a, b) => a.age - b.age); } -let john = { name: "John", age: 25 }; -let pete = { name: "Pete", age: 30 }; -let mary = { name: "Mary", age: 28 }; +let vasya = { name: "Вася", age: 25 }; +let petya = { name: "Петя", age: 30 }; +let masha = { name: "Маша", age: 28 }; -let arr = [ pete, john, mary ]; +let arr = [ vasya, petya, masha ]; sortByAge(arr); -// now sorted is: [john, mary, pete] -alert(arr[0].name); // John -alert(arr[1].name); // Mary -alert(arr[2].name); // Pete +// теперь отсортировано: [vasya, masha, petya] +alert(arr[0].name); // Вася +alert(arr[1].name); // Маша +alert(arr[2].name); // Петя ``` diff --git a/1-js/05-data-types/05-array-methods/8-sort-objects/task.md b/1-js/05-data-types/05-array-methods/8-sort-objects/task.md index 9a215c9f4d..f3322433ea 100644 --- a/1-js/05-data-types/05-array-methods/8-sort-objects/task.md +++ b/1-js/05-data-types/05-array-methods/8-sort-objects/task.md @@ -2,23 +2,23 @@ importance: 5 --- -# Sort users by age +# Отсортировать пользователей по возрасту -Write the function `sortByAge(users)` that gets an array of objects with the `age` property and sorts them by `age`. +Напишите функцию `sortByAge(users)`, которая принимает массив объектов со свойством `age` и сортирует их по нему. -For instance: +Например: ```js no-beautify -let john = { name: "John", age: 25 }; -let pete = { name: "Pete", age: 30 }; -let mary = { name: "Mary", age: 28 }; +let vasya = { name: "Вася", age: 25 }; +let petya = { name: "Петя", age: 30 }; +let masha = { name: "Маша", age: 28 }; -let arr = [ pete, john, mary ]; +let arr = [ vasya, petya, masha ]; sortByAge(arr); -// now: [john, mary, pete] -alert(arr[0].name); // John -alert(arr[1].name); // Mary -alert(arr[2].name); // Pete +// теперь: [vasya, masha, petya] +alert(arr[0].name); // Вася +alert(arr[1].name); // Маша +alert(arr[2].name); // Петя ``` diff --git a/1-js/05-data-types/05-array-methods/9-shuffle/solution.md b/1-js/05-data-types/05-array-methods/9-shuffle/solution.md index a43715db87..69408b7e3c 100644 --- a/1-js/05-data-types/05-array-methods/9-shuffle/solution.md +++ b/1-js/05-data-types/05-array-methods/9-shuffle/solution.md @@ -1,4 +1,4 @@ -The simple solution could be: +Простым решением может быть: ```js run *!* @@ -12,18 +12,18 @@ shuffle(arr); alert(arr); ``` -That somewhat works, because `Math.random() - 0.5` is a random number that may be positive or negative, so the sorting function reorders elements randomly. +Это, конечно, будет работать, потому что `Math.random() - 0.5` отдаёт случайное число, которое может быть положительным или отрицательным, следовательно, функция сортировки меняет порядок элементов случайным образом. -But because the sorting function is not meant to be used this way, not all permutations have the same probability. +Но поскольку метод `sort` не предназначен для использования в таких случаях, не все возможные варианты имеют одинаковую вероятность. -For instance, consider the code below. It runs `shuffle` 1000000 times and counts appearances of all possible results: +Например, рассмотрим код ниже. Он запускает `shuffle` 1000000 раз и считает вероятность появления для всех возможных вариантов `arr`: ```js run function shuffle(array) { array.sort(() => Math.random() - 0.5); } -// counts of appearances for all possible permutations +// подсчёт вероятности для всех возможных вариантов let count = { '123': 0, '132': 0, @@ -39,13 +39,13 @@ for (let i = 0; i < 1000000; i++) { count[array.join('')]++; } -// show counts of all possible permutations +// показать количество всех возможных вариантов for (let key in count) { alert(`${key}: ${count[key]}`); } ``` -An example result (for V8, July 2017): +Результат примера (зависят от движка JS): ```js 123: 250706 @@ -56,24 +56,30 @@ An example result (for V8, July 2017): 321: 125223 ``` -We can see the bias clearly: `123` and `213` appear much more often than others. +Теперь мы отчётливо видим допущенное отклонение: `123` и `213` появляются намного чаще, чем остальные варианты. -The result of the code may vary between JavaScript engines, but we can already see that the approach is unreliable. +Результаты этого кода могут варьироваться при запуске на разных движках JavaScript, но очевидно, что такой подход не надёжен. -Why it doesn't work? Generally speaking, `sort` is a "black box": we throw an array and a comparison function into it and expect the array to be sorted. But due to the utter randomness of the comparison the black box goes mad, and how exactly it goes mad depends on the concrete implementation that differs between engines. +Так почему это не работает? Если говорить простыми словами, то `sort` это "чёрный ящик": мы бросаем в него массив и функцию сравнения, ожидая получить отсортированный массив. Но из-за абсолютной хаотичности сравнений чёрный ящик сходит с ума, и как именно он сходит с ума, зависит от конкретной его реализации, которая различна в разных движках JavaScript. -There are other good ways to do the task. For instance, there's a great algorithm called [Fisher-Yates shuffle](https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle). The idea is to walk the array in the reverse order and swap each element with a random one before it: +Есть и другие хорошие способы решить эту задачу. Например, есть отличный алгоритм под названием [Тасование Фишера — Йетса](https://ru.wikipedia.org/wiki/%D0%A2%D0%B0%D1%81%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5_%D0%A4%D0%B8%D1%88%D0%B5%D1%80%D0%B0_%E2%80%94_%D0%99%D0%B5%D1%82%D1%81%D0%B0). Суть заключается в том, чтобы проходить по массиву в обратном порядке и менять местами каждый элемент со случайным элементом, который находится перед ним. ```js function shuffle(array) { for (let i = array.length - 1; i > 0; i--) { - let j = Math.floor(Math.random() * (i + 1)); // random index from 0 to i - [array[i], array[j]] = [array[j], array[i]]; // swap elements + let j = Math.floor(Math.random() * (i + 1)); // случайный индекс от 0 до i + + // поменять элементы местами + // мы используем для этого синтаксис "деструктурирующее присваивание" + // подробнее о нём - в следующих главах + // то же самое можно записать как: + // let t = array[i]; array[i] = array[j]; array[j] = t + [array[i], array[j]] = [array[j], array[i]]; } } ``` -Let's test it the same way: +Давайте проверим эту реализацию на том же примере: ```js run function shuffle(array) { @@ -83,7 +89,7 @@ function shuffle(array) { } } -// counts of appearances for all possible permutations +// подсчёт вероятности для всех возможных вариантов let count = { '123': 0, '132': 0, @@ -99,13 +105,13 @@ for (let i = 0; i < 1000000; i++) { count[array.join('')]++; } -// show counts of all possible permutations +// показать количество всех возможных вариантов for (let key in count) { alert(`${key}: ${count[key]}`); } ``` -The example output: +Пример вывода: ```js 123: 166693 @@ -116,6 +122,6 @@ The example output: 321: 166316 ``` -Looks good now: all permutations appear with the same probability. +Теперь всё в порядке: все варианты появляются с одинаковой вероятностью. -Also, performance-wise the Fisher-Yates algorithm is much better, there's no "sorting" overhead. +Кроме того, если посмотреть с точки зрения производительности, то алгоритм "Тасование Фишера — Йетса" намного быстрее, так как в нём нет лишних затрат на сортировку. diff --git a/1-js/05-data-types/05-array-methods/9-shuffle/task.md b/1-js/05-data-types/05-array-methods/9-shuffle/task.md index 970c534172..16f932c3d8 100644 --- a/1-js/05-data-types/05-array-methods/9-shuffle/task.md +++ b/1-js/05-data-types/05-array-methods/9-shuffle/task.md @@ -2,11 +2,11 @@ importance: 3 --- -# Shuffle an array +# Перемешайте массив -Write the function `shuffle(array)` that shuffles (randomly reorders) elements of the array. +Напишите функцию `shuffle(array)`, которая перемешивает (переупорядочивает случайным образом) элементы массива. -Multiple runs of `shuffle` may lead to different orders of elements. For instance: +Многократные прогоны через `shuffle` могут привести к разным последовательностям элементов. Например: ```js let arr = [1, 2, 3]; @@ -22,4 +22,4 @@ shuffle(arr); // ... ``` -All element orders should have an equal probability. For instance, `[1,2,3]` can be reordered as `[1,2,3]` or `[1,3,2]` or `[3,1,2]` etc, with equal probability of each case. +Все последовательности элементов должны иметь одинаковую вероятность. Например, `[1,2,3]` может быть переупорядочено как `[1,2,3]` или `[1,3,2]`, или `[3,1,2]` и т.д., с равной вероятностью каждого случая. diff --git a/1-js/05-data-types/05-array-methods/article.md b/1-js/05-data-types/05-array-methods/article.md index 1e26855801..5d1354e183 100644 --- a/1-js/05-data-types/05-array-methods/article.md +++ b/1-js/05-data-types/05-array-methods/article.md @@ -1,111 +1,111 @@ -# Array methods +# Методы массивов -Arrays provide a lot of methods. To make things easier, in this chapter they are split into groups. +Массивы предоставляют множество методов. Чтобы было проще, в этой главе они разбиты на группы. -## Add/remove items +## Добавление/удаление элементов -We already know methods that add and remove items from the beginning or the end: +Мы уже знаем методы, которые добавляют и удаляют элементы из начала или конца: -- `arr.push(...items)` -- adds items to the end, -- `arr.pop()` -- extracts an item from the end, -- `arr.shift()` -- extracts an item from the beginning, -- `arr.unshift(...items)` -- adds items to the beginning. +- `arr.push(...items)` -- добавляет элементы в конец, +- `arr.pop()` -- извлекает элемент из конца, +- `arr.shift()` -- извлекает элемент из начала, +- `arr.unshift(...items)` -- добавляет элементы в начало. -Here are few others. +Есть и другие. ### splice -How to delete an element from the array? +Как удалить элемент из массива? -The arrays are objects, so we can try to use `delete`: +Так как массивы - это объекты, то можно попробовать `delete`: ```js run let arr = ["I", "go", "home"]; -delete arr[1]; // remove "go" +delete arr[1]; // удалить "go" alert( arr[1] ); // undefined -// now arr = ["I", , "home"]; +// теперь arr = ["I", , "home"]; alert( arr.length ); // 3 ``` -The element was removed, but the array still has 3 elements, we can see that `arr.length == 3`. +Элемент был удалён, но в массиве всё ещё три элемента, мы можем увидеть, что `arr.length == 3`. -That's natural, because `delete obj.key` removes a value by the `key`. It's all it does. Fine for objects. But for arrays we usually want the rest of elements to shift and occupy the freed place. We expect to have a shorter array now. +Это естественно, потому что `delete obj.key` удаляет значение по ключу `key`. Это всё, что он делает. Хорошо для объектов. Но для массивов мы обычно хотим, чтобы оставшиеся элементы сдвинулись и заняли освободившееся место. Мы ждём, что массив станет короче. -So, special methods should be used. +Поэтому нужно использовать специальные методы. -The [arr.splice(str)](mdn:js/Array/splice) method is a swiss army knife for arrays. It can do everything: insert, remove and replace elements. +Метод [arr.splice](mdn:js/Array/splice) – это универсальный «швейцарский нож» для работы с массивами. Умеет всё: добавлять, удалять и заменять элементы. -The syntax is: +Синтаксис: ```js -arr.splice(index[, deleteCount, elem1, ..., elemN]) +arr.splice(start[, deleteCount, elem1, ..., elemN]) ``` -It starts from the position `index`: removes `deleteCount` elements and then inserts `elem1, ..., elemN` at their place. Returns the array of removed elements. +Он изменяет `arr` начиная с индекса `start`: удаляет `deleteCount` элементов и затем вставляет `elem1, ..., elemN` на их место. Возвращает массив из удалённых элементов. -This method is easy to grasp by examples. +Этот метод легко понять, рассмотрев примеры. -Let's start with the deletion: +Начнём с удаления: ```js run -let arr = ["I", "study", "JavaScript"]; +let arr = ["Я", "изучаю", "JavaScript"]; *!* -arr.splice(1, 1); // from index 1 remove 1 element +arr.splice(1, 1); // начиная с индекса 1, удалить 1 элемент */!* -alert( arr ); // ["I", "JavaScript"] +alert( arr ); // осталось ["Я", "JavaScript"] ``` -Easy, right? Starting from the index `1` it removed `1` element. +Легко, правда? Начиная с индекса `1`, он убрал `1` элемент. -In the next example we remove 3 elements and replace them with the other two: +В следующем примере мы удалим 3 элемента и заменим их двумя другими. ```js run -let arr = [*!*"I", "study", "JavaScript",*/!* "right", "now"]; +let arr = [*!*"Я", "изучаю", "JavaScript",*/!* "прямо", "сейчас"]; -// remove 3 first elements and replace them with another -arr.splice(0, 3, "Let's", "dance"); +// удалить 3 первых элемента и заменить их другими +arr.splice(0, 3, "Давай", "танцевать"); -alert( arr ) // now [*!*"Let's", "dance"*/!*, "right", "now"] +alert( arr ) // теперь [*!*"Давай", "танцевать"*/!*, "прямо", "сейчас"] ``` -Here we can see that `splice` returns the array of removed elements: +Здесь видно, что `splice` возвращает массив из удалённых элементов: ```js run -let arr = [*!*"I", "study",*/!* "JavaScript", "right", "now"]; +let arr = [*!*"Я", "изучаю",*/!* "JavaScript", "прямо", "сейчас"]; -// remove 2 first elements +// удалить 2 первых элемента let removed = arr.splice(0, 2); -alert( removed ); // "I", "study" <-- array of removed elements +alert( removed ); // "Я", "изучаю" <-- массив из удалённых элементов ``` -The `splice` method is also able to insert the elements without any removals. For that we need to set `deleteCount` to `0`: +Метод `splice` также может вставлять элементы без удаления, для этого достаточно установить `deleteCount` в `0`: ```js run -let arr = ["I", "study", "JavaScript"]; +let arr = ["Я", "изучаю", "JavaScript"]; -// from index 2 -// delete 0 -// then insert "complex" and "language" -arr.splice(2, 0, "complex", "language"); +// с индекса 2 +// удалить 0 элементов +// вставить "сложный", "язык" +arr.splice(2, 0, "сложный", "язык"); -alert( arr ); // "I", "study", "complex", "language", "JavaScript" +alert( arr ); // "Я", "изучаю", "сложный", "язык", "JavaScript" ``` -````smart header="Negative indexes allowed" -Here and in other array methods, negative indexes are allowed. They specify the position from the end of the array, like here: +````smart header="Отрицательные индексы разрешены" +В этом и в других методах массива допускается использование отрицательных индексов. Они определяют позицию с конца массива, как тут: ```js run let arr = [1, 2, 5]; -// from index -1 (one step from the end) -// delete 0 elements, -// then insert 3 and 4 +// начиная с индекса -1 (перед последним элементом) +// удалить 0 элементов, +// затем вставить числа 3 и 4 arr.splice(-1, 0, 3, 4); alert( arr ); // 1,2,3,4,5 @@ -114,135 +114,134 @@ alert( arr ); // 1,2,3,4,5 ### slice -The method [arr.slice](mdn:js/Array/slice) is much simpler than similar-looking `arr.splice`. +Метод [arr.slice](mdn:js/Array/slice) намного проще, чем похожий на него `arr.splice`. -The syntax is: +Синтаксис: ```js -arr.slice(start, end) +arr.slice([start], [end]) ``` -It returns a new array containing all items from index `"start"` to `"end"` (not including `"end"`). Both `start` and `end` can be negative, in that case position from array end is assumed. +Он возвращает новый массив, в который копирует все элементы с индекса `start` до `end` (не включая `end`). `start` и `end` могут быть отрицательными, в этом случае отсчёт позиции будет вестись с конца массива. -It works like `str.slice`, but makes subarrays instead of substrings. +Это похоже на строковый метод `str.slice`, но вместо подстрок возвращает подмассивы. -For instance: +Например: ```js run -let str = "test"; let arr = ["t", "e", "s", "t"]; -alert( str.slice(1, 3) ); // es -alert( arr.slice(1, 3) ); // e,s +alert( arr.slice(1, 3) ); // e,s (копирует с 1 до 3) -alert( str.slice(-2) ); // st -alert( arr.slice(-2) ); // s,t +alert( arr.slice(-2) ); // s,t (копирует с -2 до конца) ``` +Можно вызвать `slice` без аргументов: `arr.slice()` создаёт копию `arr`. Это часто используют, чтобы создать копию массива для дальнейших преобразований, которые не должны менять исходный массив. + ### concat -The method [arr.concat](mdn:js/Array/concat) joins the array with other arrays and/or items. +Метод [arr.concat](mdn:js/Array/concat) создаёт новый массив, в который копирует данные из других массивов и дополнительные значения. -The syntax is: +Синтаксис: ```js arr.concat(arg1, arg2...) ``` -It accepts any number of arguments -- either arrays or values. +Он принимает любое количество аргументов, которые могут быть как массивами, так и простыми значениями. -The result is a new array containing items from `arr`, then `arg1`, `arg2` etc. +В результате -- новый массив, включающий в себя элементы из `arr`, затем `arg1`, `arg2` и так далее. -If an argument is an array or has `Symbol.isConcatSpreadable` property, then all its elements are copied. Otherwise, the argument itself is copied. +Если аргумент `argN` -- массив, то копируются все его элементы. Иначе копируется сам аргумент. -For instance: +Например: ```js run let arr = [1, 2]; -// merge arr with [3,4] -alert( arr.concat([3, 4])); // 1,2,3,4 +// создать массив из: arr и [3,4] +alert( arr.concat([3, 4]) ); // 1,2,3,4 -// merge arr with [3,4] and [5,6] -alert( arr.concat([3, 4], [5, 6])); // 1,2,3,4,5,6 +// создать массив из: arr и [3,4] и [5,6] +alert( arr.concat([3, 4], [5, 6]) ); // 1,2,3,4,5,6 -// merge arr with [3,4], then add values 5 and 6 -alert( arr.concat([3, 4], 5, 6)); // 1,2,3,4,5,6 +// создать массив из: arr и [3,4], потом добавить значения 5 и 6 +alert( arr.concat([3, 4], 5, 6) ); // 1,2,3,4,5,6 ``` -Normally, it only copies elements from arrays ("spreads" them). Other objects, even if they look like arrays, added as a whole: +Обычно он копирует только элементы из массивов. Другие объекты, даже если они выглядят как массивы, добавляются как есть: ```js run let arr = [1, 2]; let arrayLike = { - 0: "something", + 0: "что-то", length: 1 }; alert( arr.concat(arrayLike) ); // 1,2,[object Object] -//[1, 2, arrayLike] ``` -...But if an array-like object has `Symbol.isConcatSpreadable` property, then its elements are added instead: +...Но если массивоподобный объект имеет специальное свойство `Symbol.isConcatSpreadable`, то он обрабатывается как массив, с помощью `concat`: вместо него добавляются его элементы: ```js run let arr = [1, 2]; let arrayLike = { - 0: "something", - 1: "else", + 0: "что-то", + 1: "ещё", *!* [Symbol.isConcatSpreadable]: true, */!* length: 2 }; -alert( arr.concat(arrayLike) ); // 1,2,something,else +alert( arr.concat(arrayLike) ); // 1,2,что-то,ещё ``` -## Iterate: forEach +## Перебор: forEach -The [arr.forEach](mdn:js/Array/forEach) method allows to run a function for every element of the array. +Метод [arr.forEach](mdn:js/Array/forEach) позволяет запускать функцию для каждого элемента массива. -The syntax: +Синтаксис: ```js arr.forEach(function(item, index, array) { - // ... do something with item + // ... делать что-то с item }); ``` -For instance, this shows each element of the array: +Например, этот код выведет на экран каждый элемент массива: ```js run -// for each element call alert -["Bilbo", "Gandalf", "Nazgul"].forEach(alert); +// Вызов alert для каждого элемента +["Бильбо", "Гэндальф", "Назгул"].forEach(alert); ``` -And this code is more elaborate about their positions in the target array: +А этот вдобавок расскажет и о позиции элемента в целевом массиве: ```js run -["Bilbo", "Gandalf", "Nazgul"].forEach((item, index, array) => { - alert(`${item} is at index ${index} in ${array}`); +["Бильбо", "Гэндальф", "Назгул"].forEach((item, index, array) => { + alert(`У ${item} индекс ${index} в ${array}`); }); ``` -The result of the function (if it returns any) is thrown away and ignored. +Результат функции (если она что-то возвращает) отбрасывается и игнорируется. + +## Поиск в массиве -## Searching in array +Теперь рассмотрим методы поиска в массиве. -These are methods to search for something in an array. +### indexOf/lastIndexOf и includes -### indexOf/lastIndexOf and includes +У методов [arr.indexOf](mdn:js/Array/indexOf) и [arr.includes](mdn:js/Array/includes) одинаковый синтаксис и они делают по сути то же самое, что и их строковые аналоги, но работают с элементами вместо символов: -The methods [arr.indexOf](mdn:js/Array/indexOf), [arr.lastIndexOf](mdn:js/Array/lastIndexOf) and [arr.includes](mdn:js/Array/includes) have the same syntax and do essentially the same as their string counterparts, but operate on items instead of characters: +- `arr.indexOf(item, from)` ищет `item` начиная с индекса `from` и возвращает номер индекса, на котором был найден искомый элемент, в противном случае `-1`. +- `arr.includes(item, from)` ищет `item` начиная с индекса `from` и возвращает `true`, если поиск успешен. -- `arr.indexOf(item, from)` looks for `item` starting from index `from`, and returns the index where it was found, otherwise `-1`. -- `arr.lastIndexOf(item, from)` -- same, but looks for from right to left. -- `arr.includes(item, from)` -- looks for `item` starting from index `from`, returns `true` if found. +Обычно эти методы используются только с одним аргументом: искомым `item`. По умолчанию поиск ведется с начала. -For instance: +Например: ```js run let arr = [1, 0, false]; @@ -254,151 +253,184 @@ alert( arr.indexOf(null) ); // -1 alert( arr.includes(1) ); // true ``` -Note that the methods use `===` comparison. So, if we look for `false`, it finds exactly `false` and not the zero. +Пожалуйста, обратите внимание, что методы используют строгое сравнение `===`. Таким образом, если мы ищем `false`, он находит именно `false`, а не ноль. + +Если мы хотим проверить наличие элемента в массиве и нет необходимости знать его индекс, предпочтительно использовать `arr.includes`. + +Метод [arr.lastIndexOf](mdn:js/Array/lastIndexOf) похож на `indexOf`, но ищет справа налево. -If we want to check for inclusion, and don't want to know the exact index, then `arr.includes` is preferred. +```js run +let fruits = ['Яблоко', 'Апельсин', 'Яблоко'] + +alert( fruits.indexOf('Яблоко') ); // 0 (первый 'Яблоко') +alert( fruits.lastIndexOf('Яблоко') ); // 2 (последний 'Яблоко') +``` -Also, a very minor difference of `includes` is that it correctly handles `NaN`, unlike `indexOf/lastIndexOf`: +````smart header="Метод `includes` правильно обрабатывает `NaN`" +Незначительная, но заслуживающая внимания особенность `includes` -- он правильно обрабатывает `NaN`, в отличие от `indexOf`: ```js run const arr = [NaN]; -alert( arr.indexOf(NaN) ); // -1 (should be 0, but === equality doesn't work for NaN) -alert( arr.includes(NaN) );// true (correct) +alert( arr.indexOf(NaN) ); // -1 (неверно, должен быть 0) +alert( arr.includes(NaN) );// true (верно) ``` +Это связано с тем, что `includes` был добавлен в JavaScript гораздо позже и использует более современный алгоритм сравнения. +```` -### find and findIndex +### find и findIndex/findLastIndex -Imagine we have an array of objects. How do we find an object with the specific condition? +Представьте, что у нас есть массив объектов. Как нам найти объект с определённым условием? -Here the [arr.find](mdn:js/Array/find) method comes in handy. +Здесь пригодится метод [arr.find](mdn:js/Array/find). -The syntax is: +Синтаксис: ```js let result = arr.find(function(item, index, array) { - // if true is returned, item is returned and iteration is stopped - // for falsy scenario returns undefined + // если true - возвращается текущий элемент и перебор прерывается + // если все итерации оказались ложными, возвращается undefined }); ``` -The function is called repetitively for each element of the array: +Функция вызывается по очереди для каждого элемента массива: -- `item` is the element. -- `index` is its index. -- `array` is the array itself. +- `item` - очередной элемент. +- `index` - его индекс. +- `array` - сам массив. -If it returns `true`, the search is stopped, the `item` is returned. If nothing found, `undefined` is returned. +Если функция возвращает `true`, поиск прерывается и возвращается `item`. Если ничего не найдено, возвращается `undefined`. -For example, we have an array of users, each with the fields `id` and `name`. Let's find the one with `id == 1`: +Например, у нас есть массив пользователей, каждый из которых имеет поля `id` и `name`. Найдем пользователя с `id == 1`: ```js run let users = [ - {id: 1, name: "John"}, - {id: 2, name: "Pete"}, - {id: 3, name: "Mary"} + {id: 1, name: "Вася"}, + {id: 2, name: "Петя"}, + {id: 3, name: "Маша"} ]; let user = users.find(item => item.id == 1); -alert(user.name); // John +alert(user.name); // Вася ``` -In real life arrays of objects is a common thing, so the `find` method is very useful. +В реальной жизни массивы объектов -- обычное дело, поэтому метод `find` крайне полезен. + +Обратите внимание, что в данном примере мы передаём `find` функцию `item => item.id == 1` с одним аргументом. Это типично, другие аргументы этой функции используются редко. + +У метода [arr.findIndex](mdn:js/Array/findIndex) такой же синтаксис, но он возвращает индекс, на котором был найден элемент, а не сам элемент. Значение `-1` возвращается, если ничего не найдено. -Note that in the example we provide to `find` the function `item => item.id == 1` with one argument. Other arguments of this function are rarely used. +Метод [arr.findLastIndex](mdn:js/Array/findLastIndex) похож на `findIndex`, но ищет справа налево, наподобие `lastIndexOf`. -The [arr.findIndex](mdn:js/Array/findIndex) method is essentially the same, but it returns the index where the element was found instead of the element itself and `-1` is returned when nothing is found. +Например: + +```js run +let users = [ + {id: 1, name: "Вася"}, + {id: 2, name: "Петя"}, + {id: 3, name: "Маша"}, + {id: 4, name: "Вася"} +]; + +// Найти индекс первого Васи +alert(users.findIndex(user => user.name == 'Вася')); // 0 + +// Найти индекс последнего Васи +alert(users.findLastIndex(user => user.name == 'Вася')); // 3 +``` ### filter -The `find` method looks for a single (first) element that makes the function return `true`. +Метод `find` ищет один (первый) элемент, который заставит функцию вернуть `true`. -If there may be many, we can use [arr.filter(fn)](mdn:js/Array/filter). +Если найденных элементов может быть много, можно использовать [arr.filter(fn)](mdn:js/Array/filter). -The syntax is similar to `find`, but filter continues to iterate for all array elements even if `true` is already returned: +Синтаксис схож с `find`, но `filter` возвращает массив из всех подходящих элементов: ```js let results = arr.filter(function(item, index, array) { - // if true item is pushed to results and iteration continues - // returns empty array for complete falsy scenario + // если `true` -- элемент добавляется к results и перебор продолжается + // возвращается пустой массив в случае, если ничего не найдено }); ``` -For instance: +Например: ```js run let users = [ - {id: 1, name: "John"}, - {id: 2, name: "Pete"}, - {id: 3, name: "Mary"} + {id: 1, name: "Вася"}, + {id: 2, name: "Петя"}, + {id: 3, name: "Маша"} ]; -// returns array of the first two users +// возвращает массив, состоящий из двух первых пользователей let someUsers = users.filter(item => item.id < 3); alert(someUsers.length); // 2 ``` -## Transform an array - -This section is about the methods transforming or reordering the array. +## Преобразование массива +Перейдём к методам преобразования и упорядочения массива. ### map -The [arr.map](mdn:js/Array/map) method is one of the most useful and often used. +Метод [arr.map](mdn:js/Array/map) является одним из наиболее полезных и часто используемых. + +Он вызывает функцию для каждого элемента массива и возвращает массив результатов выполнения этой функции. -The syntax is: +Синтаксис: ```js let result = arr.map(function(item, index, array) { - // returns the new value instead of item -}) + // возвращается новое значение вместо элемента +}); ``` -It calls the function for each element of the array and returns the array of results. - -For instance, here we transform each element into its length: +Например, здесь мы преобразуем каждый элемент в его длину: ```js run -let lengths = ["Bilbo", "Gandalf", "Nazgul"].map(item => item.length); -alert(lengths); // 5,7,6 +let lengths = ["Бильбо", "Гэндальф", "Назгул"].map(item => item.length); +alert(lengths); // 6,8,6 ``` ### sort(fn) -The method [arr.sort](mdn:js/Array/sort) sorts the array *in place*. +Вызов [arr.sort()](mdn:js/Array/sort) сортирует массив *на месте*, меняя в нём порядок элементов. + +Он также возвращает отсортированный массив, но обычно возвращаемое значение игнорируется, так как изменяется сам `arr`. -For instance: +Например: ```js run let arr = [ 1, 2, 15 ]; -// the method reorders the content of arr (and returns it) +// метод сортирует содержимое arr arr.sort(); alert( arr ); // *!*1, 15, 2*/!* ``` -Did you notice anything strange in the outcome? +Не заметили ничего странного в этом примере? -The order became `1, 15, 2`. Incorrect. But why? +Порядок стал `1, 15, 2`. Это неправильно. Но почему? -**The items are sorted as strings by default.** +**По умолчанию элементы сортируются как строки.** -Literally, all elements are converted to strings and then compared. So, the lexicographic ordering is applied and indeed `"2" > "15"`. +Буквально, элементы преобразуются в строки при сравнении. Для строк применяется лексикографический порядок, и действительно выходит, что `"2" > "15"`. -To use our own sorting order, we need to supply a function of two arguments as the argument of `arr.sort()`. +Чтобы использовать наш собственный порядок сортировки, нам нужно предоставить функцию в качестве аргумента `arr.sort()`. + +Функция должна для пары значений возвращать: -The function should work like this: ```js function compare(a, b) { - if (a > b) return 1; - if (a == b) return 0; - if (a < b) return -1; + if (a > b) return 1; // если первое значение больше второго + if (a == b) return 0; // если равны + if (a < b) return -1; // если первое значение меньше второго } ``` -For instance: +Например, для сортировки чисел: ```js run function compareNumeric(a, b) { @@ -416,27 +448,27 @@ arr.sort(compareNumeric); alert(arr); // *!*1, 2, 15*/!* ``` -Now it works as intended. +Теперь всё работает как надо. -Let's step aside and think what's happening. The `arr` can be array of anything, right? It may contain numbers or strings or html elements or whatever. We have a set of *something*. To sort it, we need an *ordering function* that knows how to compare its elements. The default is a string order. +Сделаем отступление и подумаем, что происходит. `arr` может быть массивом чего угодно, верно? Он может содержать числа, строки, объекты или что-то ещё. У нас есть набор *каких-то элементов*. Чтобы отсортировать его, нам нужна *упорядочивающая функция*, которая знает, как сравнивать его элементы. По умолчанию элементы сортируются как строки. -The `arr.sort(fn)` method has a built-in implementation of sorting algorithm. We don't need to care how it exactly works (an optimized [quicksort](https://en.wikipedia.org/wiki/Quicksort) most of the time). It will walk the array, compare its elements using the provided function and reorder them, all we need is to provide the `fn` which does the comparison. +Метод `arr.sort(fn)` реализует общий алгоритм сортировки. Нам не нужно заботиться о том, как он работает внутри (в большинстве случаев это оптимизированная [быстрая сортировка](https://ru.wikipedia.org/wiki/%D0%91%D1%8B%D1%81%D1%82%D1%80%D0%B0%D1%8F_%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0) или [Timsort](https://ru.wikipedia.org/wiki/Timsort)). Она проходится по массиву, сравнивает его элементы с помощью предоставленной функции и переупорядочивает их. Всё, что нам нужно, -- предоставить `fn`, которая делает сравнение. -By the way, if we ever want to know which elements are compared -- nothing prevents from alerting them: +Кстати, если мы когда-нибудь захотим узнать, какие элементы сравниваются -- ничто не мешает нам вывести их на экран: ```js run [1, -2, 15, 2, 0, 8].sort(function(a, b) { alert( a + " <> " + b ); + return a - b; }); ``` -The algorithm may compare an element multiple times in the process, but it tries to make as few comparisons as possible. - +В процессе работы алгоритм может сравнивать элемент со множеством других, но он старается сделать как можно меньше сравнений. -````smart header="A comparison function may return any number" -Actually, a comparison function is only required to return a positive number to say "greater" and a negative number to say "less". +````smart header="Функция сравнения может вернуть любое число" +На самом деле от функции сравнения требуется любое положительное число, чтобы сказать «больше», и отрицательное число, чтобы сказать «меньше». -That allows to write shorter functions: +Это позволяет писать более короткие функции: ```js run let arr = [ 1, 2, 15 ]; @@ -447,21 +479,37 @@ alert(arr); // *!*1, 2, 15*/!* ``` ```` -````smart header="Arrow functions for the best" -Remember [arrow functions](info:function-expressions-arrows#arrow-functions)? We can use them here for neater sorting: +````smart header="Лучше использовать стрелочные функции" +Помните [стрелочные функции](info:arrow-functions-basics)? Можно использовать их здесь для того, чтобы сортировка выглядела более аккуратной: ```js arr.sort( (a, b) => a - b ); ``` -This works exactly the same as the other, longer, version above. +Будет работать точно так же, как и более длинная версия выше. +```` + +````smart header="Используйте `localeCompare` для строк" +Помните алгоритм сравнения [строк](info:string#correct-comparisons)? По умолчанию, он сравнивает буквы по их кодам. + +Для многих алфавитов лучше использовать метод `str.localeCompare`, для правильной сортировки букв, таких как `Ö`. + +Например, отсортируем несколько стран на немецком языке: + +```js run +let countries = ['Österreich', 'Andorra', 'Vietnam']; + +alert( countries.sort( (a, b) => a > b ? 1 : -1) ); // Andorra, Vietnam, Österreich (неправильно) + +alert( countries.sort( (a, b) => a.localeCompare(b) ) ); // Andorra,Österreich,Vietnam (правильно!) +``` ```` ### reverse -The method [arr.reverse](mdn:js/Array/reverse) reverses the order of elements in `arr`. +Метод [arr.reverse](mdn:js/Array/reverse) меняет порядок элементов в `arr` на обратный. -For instance: +Например: ```js run let arr = [1, 2, 3, 4, 5]; @@ -470,85 +518,90 @@ arr.reverse(); alert( arr ); // 5,4,3,2,1 ``` -It also returns the array `arr` after the reversal. +Он также возвращает массив `arr` с изменённым порядком элементов. -### split and join +### split и join -Here's the situation from the real life. We are writing a messaging app, and the person enters the comma-delimited list of receivers: `John, Pete, Mary`. But for us an array of names would be much more comfortable than a single string. How to get it? +Ситуация из реальной жизни. Мы пишем приложение для обмена сообщениями, и посетитель вводит имена тех, кому его отправить, через запятую: `Вася, Петя, Маша`. Но нам-то гораздо удобнее работать с массивом имён, чем с одной строкой. Как его получить? -The [str.split(delim)](mdn:js/String/split) method does exactly that. It splits the string into an array by the given delimiter `delim`. +Метод [str.split(delim)](mdn:js/String/split) именно это и делает. Он разбивает строку на массив по заданному разделителю `delim`. -In the example below, we split by a comma followed by space: +В примере ниже таким разделителем является строка из запятой и пробела. ```js run -let names = 'Bilbo, Gandalf, Nazgul'; +let names = 'Вася, Петя, Маша'; let arr = names.split(', '); for (let name of arr) { - alert( `A message to ${name}.` ); // A message to Bilbo (and other names) + alert( `Сообщение получат: ${name}.` ); // Сообщение получат: Вася (и другие имена) } ``` -The `split` method has an optional second numeric argument -- a limit on the array length. If it is provided, then the extra elements are ignored. In practice it is rarely used though: +У метода `split` есть необязательный второй числовой аргумент -- ограничение на количество элементов в массиве. Если их больше, чем указано, то остаток массива будет отброшен. На практике это редко используется: ```js run -let arr = 'Bilbo, Gandalf, Nazgul, Saruman'.split(', ', 2); +let arr = 'Вася, Петя, Маша, Саша'.split(', ', 2); -alert(arr); // Bilbo, Gandalf +alert(arr); // Вася, Петя ``` -````smart header="Split into letters" -The call to `split(s)` with an empty `s` would split the string into an array of letters: +````smart header="Разбивка по буквам" +Вызов `split(s)` с пустым аргументом `s` разбил бы строку на массив букв: ```js run -let str = "test"; +let str = "тест"; -alert( str.split('') ); // t,e,s,t +alert( str.split('') ); // т,е,с,т ``` ```` -The call [arr.join(separator)](mdn:js/Array/join) does the reverse to `split`. It creates a string of `arr` items glued by `separator` between them. +Вызов [arr.join(glue)](mdn:js/Array/join) делает в точности противоположное `split`. Он создаёт строку из элементов `arr`, вставляя `glue` между ними. -For instance: +Например: ```js run -let arr = ['Bilbo', 'Gandalf', 'Nazgul']; +let arr = ['Вася', 'Петя', 'Маша']; -let str = arr.join(';'); +let str = arr.join(';'); // объединить массив в строку через ; -alert( str ); // Bilbo;Gandalf;Nazgul +alert( str ); // Вася;Петя;Маша ``` ### reduce/reduceRight -When we need to iterate over an array -- we can use `forEach`, `for` or `for..of`. +Когда нам нужно перебрать массив -- мы можем использовать `forEach`, `for` или `for..of`. -When we need to iterate and return the data for each element -- we can use `map`. +Когда нам нужно перебрать массив и вернуть данные для каждого элемента -- мы можем использовать `map`. -The methods [arr.reduce](mdn:js/Array/reduce) and [arr.reduceRight](mdn:js/Array/reduceRight) also belong to that breed, but are a little bit more intricate. They are used to calculate a single value based on the array. +Методы [arr.reduce](mdn:js/Array/reduce) и [arr.reduceRight](mdn:js/Array/reduceRight) похожи на методы выше, но они немного сложнее. Они используются для вычисления единого значения на основе всего массива. -The syntax is: +Синтаксис: ```js -let value = arr.reduce(function(previousValue, item, index, array) { +let value = arr.reduce(function(accumulator, item, index, array) { // ... -}, initial); +}, [initial]); ``` -The function is applied to the elements. You may notice the familiar arguments, starting from the 2nd: +Функция применяется по очереди ко всем элементам массива и «переносит» свой результат на следующий вызов. + +Аргументы: + +- `accumulator` -- результат предыдущего вызова этой функции, равен `initial` при первом вызове (если передан `initial`), +- `item` -- очередной элемент массива, +- `index` -- его позиция, +- `array` -- сам массив. -- `item` -- is the current array item. -- `index` -- is its position. -- `array` -- is the array. +При вызове функции результат её предыдущего вызова передаётся на следующий вызов в качестве первого аргумента. -So far, like `forEach/map`. But there's one more argument: +Так, первый аргумент является по сути аккумулятором, который хранит объединённый результат всех предыдущих вызовов функции. По окончании он становится результатом `reduce`. -- `previousValue` -- is the result of the previous function call, `initial` for the first call. +Звучит сложно? -The easiest way to grasp that is by example. +Этот метод проще всего понять на примере. -Here we get a sum of array in one line: +Тут мы получим сумму всех элементов массива одной строкой: ```js run let arr = [1, 2, 3, 4, 5]; @@ -558,76 +611,73 @@ let result = arr.reduce((sum, current) => sum + current, 0); alert(result); // 15 ``` -Here we used the most common variant of `reduce` which uses only 2 arguments. +Функция, переданная в `reduce`, использует только два аргумента, этого обычно достаточно. -Let's see the details of what's going on. +Разберём детально как это работает. -1. On the first run, `sum` is the initial value (the last argument of `reduce`), equals `0`, and `current` is the first array element, equals `1`. So the result is `1`. -2. On the second run, `sum = 1`, we add the second array element (`2`) to it and return. -3. On the 3rd run, `sum = 3` and we add one more element to it, and so on... +1. При первом запуске `sum` равен `initial` (последний аргумент `reduce`), то есть `0`, а `current` -- первый элемент массива, равный `1`. Таким образом, результат функции равен `1`. +2. При втором запуске `sum = 1`, к нему мы добавляем второй элемент массива (`2`) и возвращаем. +3. При третьем запуске `sum = 3`, к которому мы добавляем следующий элемент, и так далее... -The calculation flow: +Поток вычислений получается такой: -![](reduce.png) +![](reduce.svg) -Or in the form of a table, where each row represents a function call on the next array element: +Или в виде таблицы, где каждая строка показывает вызов функции на очередном элементе массива: | |`sum`|`current`|`result`| |---|-----|---------|---------| -|the first call|`0`|`1`|`1`| -|the second call|`1`|`2`|`3`| -|the third call|`3`|`3`|`6`| -|the fourth call|`6`|`4`|`10`| -|the fifth call|`10`|`5`|`15`| +|первый вызов|`0`|`1`|`1`| +|второй вызов|`1`|`2`|`3`| +|третий вызов|`3`|`3`|`6`| +|четвёртый вызов|`6`|`4`|`10`| +|пятый вызов|`10`|`5`|`15`| +Здесь отчётливо видно, как результат предыдущего вызова передаётся в первый аргумент следующего. -As we can see, the result of the previous call becomes the first argument of the next one. - -We also can omit the initial value: +Мы также можем опустить начальное значение: ```js run let arr = [1, 2, 3, 4, 5]; -// removed initial value from reduce (no 0) +// убрано начальное значение (нет 0 в конце) let result = arr.reduce((sum, current) => sum + current); alert( result ); // 15 ``` -The result is the same. That's because if there's no initial, then `reduce` takes the first element of the array as the initial value and starts the iteration from the 2nd element. +Результат – точно такой же! Это потому, что при отсутствии `initial` в качестве первого значения берётся первый элемент массива, а перебор стартует со второго. -The calculation table is the same as above, minus the first row. +Таблица вычислений будет такая же за вычетом первой строки. -But such use requires an extreme care. If the array is empty, then `reduce` call without initial value gives an error. +Но такое использование требует крайней осторожности. Если массив пуст, то вызов `reduce` без начального значения выдаст ошибку. -Here's an example: +Вот пример: ```js run let arr = []; // Error: Reduce of empty array with no initial value -// if the initial value existed, reduce would return it for the empty arr. +// если бы существовало начальное значение, reduce вернул бы его для пустого массива. arr.reduce((sum, current) => sum + current); ``` +Поэтому рекомендуется всегда указывать начальное значение. -So it's advised to always specify the initial value. - -The method [arr.reduceRight](mdn:js/Array/reduceRight) does the same, but goes from right to left. - +Метод [arr.reduceRight](mdn:js/Array/reduceRight) работает аналогично, но проходит по массиву справа налево. ## Array.isArray -Arrays do not form a separate language type. They are based on objects. +Массивы не образуют отдельный тип данных. Они основаны на объектах. -So `typeof` does not help to distinguish a plain object from an array: +Поэтому `typeof` не может отличить простой объект от массива: ```js run alert(typeof {}); // object -alert(typeof []); // same +alert(typeof []); // тоже object ``` -...But arrays are used so often that there's a special method for that: [Array.isArray(value)](mdn:js/Array/isArray). It returns `true` if the `value` is an array, and `false` otherwise. +...Но массивы используются настолько часто, что для этого придумали специальный метод: [Array.isArray(value)](mdn:js/Array/isArray). Он возвращает `true`, если `value` массив, и `false`, если нет. ```js run alert(Array.isArray({})); // false @@ -635,98 +685,118 @@ alert(Array.isArray({})); // false alert(Array.isArray([])); // true ``` -## Most methods support "thisArg" +## Большинство методов поддерживают «thisArg» -Almost all array methods that call functions -- like `find`, `filter`, `map`, with a notable exception of `sort`, accept an optional additional parameter `thisArg`. +Почти все методы массива, которые вызывают функции -- такие как `find`, `filter`, `map`, за исключением метода `sort`, принимают необязательный параметр `thisArg`. -That parameter is not explained in the sections above, because it's rarely used. But for completeness we have to cover it. +Этот параметр не объяснялся выше, так как очень редко используется, но для наиболее полного понимания темы мы обязаны его рассмотреть. -Here's the full syntax of these methods: +Вот полный синтаксис этих методов: ```js arr.find(func, thisArg); arr.filter(func, thisArg); arr.map(func, thisArg); // ... -// thisArg is the optional last argument +// thisArg -- необязательный последний аргумент ``` -The value of `thisArg` parameter becomes `this` for `func`. +Значение параметра `thisArg` становится `this` для `func`. -For instance, here we use an object method as a filter and `thisArg` comes in handy: +Например, тут мы используем метод объекта `army` как фильтр, и `thisArg` передаёт ему контекст: ```js run -let user = { - age: 18, - younger(otherUser) { - return otherUser.age < this.age; +let army = { + minAge: 18, + maxAge: 27, + canJoin(user) { + return user.age >= this.minAge && user.age < this.maxAge; } }; let users = [ - {age: 12}, {age: 16}, - {age: 32} + {age: 20}, + {age: 23}, + {age: 30} ]; *!* -// find all users younger than user -let youngerUsers = users.filter(user.younger, user); +// найти пользователей, для которых army.canJoin возвращает true +let soldiers = users.filter(army.canJoin, army); */!* -alert(youngerUsers.length); // 2 +alert(soldiers.length); // 2 +alert(soldiers[0].age); // 20 +alert(soldiers[1].age); // 23 ``` -In the call above, we use `user.younger` as a filter and also provide `user` as the context for it. If we didn't provide the context, `users.filter(user.younger)` would call `user.younger` as a standalone function, with `this=undefined`. That would mean an instant error. +Если бы мы в примере выше использовали просто `users.filter(army.canJoin)`, то вызов `army.canJoin` был бы в режиме отдельной функции, с `this=undefined`. Это тут же привело бы к ошибке. -## Summary +Вызов `users.filter(army.canJoin, army)` можно заменить на `users.filter(user => army.canJoin(user))`, который делает то же самое. Последняя запись используется даже чаще, так как функция-стрелка более наглядна. -A cheatsheet of array methods: +## Итого -- To add/remove elements: - - `push(...items)` -- adds items to the end, - - `pop()` -- extracts an item from the end, - - `shift()` -- extracts an item from the beginning, - - `unshift(...items)` -- adds items to the beginning. - - `splice(pos, deleteCount, ...items)` -- at index `pos` delete `deleteCount` elements and insert `items`. - - `slice(start, end)` -- creates a new array, copies elements from position `start` till `end` (not inclusive) into it. - - `concat(...items)` -- returns a new array: copies all members of the current one and adds `items` to it. If any of `items` is an array, then its elements are taken. +Шпаргалка по методам массива: -- To search among elements: - - `indexOf/lastIndexOf(item, pos)` -- look for `item` starting from position `pos`, return the index or `-1` if not found. - - `includes(value)` -- returns `true` if the array has `value`, otherwise `false`. - - `find/filter(func)` -- filter elements through the function, return first/all values that make it return `true`. - - `findIndex` is like `find`, but returns the index instead of a value. - -- To iterate over elements: - - `forEach(func)` -- calls `func` for every element, does not return anything. +- Для добавления/удаления элементов: + - `push(...items)` -- добавляет элементы в конец, + - `pop()` -- извлекает элемент с конца, + - `shift()` -- извлекает элемент с начала, + - `unshift(...items)` -- добавляет элементы в начало. + - `splice(pos, deleteCount, ...items)` -- начиная с индекса `pos` удаляет `deleteCount` элементов и вставляет `items`. + - `slice(start, end)` -- создаёт новый массив, копируя в него элементы с индекса `start` до `end` (не включая `end`). + - `concat(...items)` -- возвращает новый массив: копирует все члены текущего массива и добавляет к нему `items`. Если какой-то из `items` является массивом, тогда берутся его элементы. + +- Для поиска среди элементов: + - `indexOf/lastIndexOf(item, pos)` -- ищет `item`, начиная с позиции `pos`, и возвращает его индекс или `-1`, если ничего не найдено. + - `includes(value)` -- возвращает `true`, если в массиве имеется элемент `value`, в противном случае `false`. + - `find/filter(func)` -- фильтрует элементы через функцию и отдаёт первое/все значения, при прохождении которых через функцию возвращается `true`. + - `findIndex` похож на `find`, но возвращает индекс вместо значения. + +- Для перебора элементов: + - `forEach(func)` -- вызывает `func` для каждого элемента. Ничего не возвращает. -- To transform the array: - - `map(func)` -- creates a new array from results of calling `func` for every element. - - `sort(func)` -- sorts the array in-place, then returns it. - - `reverse()` -- reverses the array in-place, then returns it. - - `split/join` -- convert a string to array and back. - - `reduce(func, initial)` -- calculate a single value over the array by calling `func` for each element and passing an intermediate result between the calls. +- Для преобразования массива: + - `map(func)` -- создаёт новый массив из результатов вызова `func` для каждого элемента. + - `sort(func)` -- сортирует массив «на месте», а потом возвращает его. + - `reverse()` -- «на месте» меняет порядок следования элементов на противоположный и возвращает изменённый массив. + - `split/join` -- преобразует строку в массив и обратно. + - `reduce/reduceRight(func, initial)` -- вычисляет одно значение на основе всего массива, вызывая `func` для каждого элемента и передавая промежуточный результат между вызовами. -- Additionally: - - `Array.isArray(arr)` checks `arr` for being an array. +- Дополнительно: + - `Array.isArray(arr)` проверяет, является ли `arr` массивом. -Please note that methods `sort`, `reverse` and `splice` modify the array itself. +Пожалуйста, обратите внимание, что методы `push`, `pop`, `shift`, `unshift`, `sort`, `reverse` и `splice` изменяют исходный массив. -These methods are the most used ones, they cover 99% of use cases. But there are few others: +Эти методы -- самые используемые, их достаточно в 99% случаев. Но существуют и другие: + +- [arr.some(fn)](mdn:js/Array/some)/[arr.every(fn)](mdn:js/Array/every) проверяет массив. + + Функция `fn` вызывается для каждого элемента массива аналогично `map`. Если какие-либо/все результаты вызовов являются `true`, то метод возвращает `true`, иначе `false`. + + Эти методы ведут себя примерно так же, как операторы `||` и `&&`: если `fn` возвращает истинное значение, `arr.some()` немедленно возвращает `true` и останавливает перебор остальных элементов; если `fn` возвращает ложное значение, `arr.every()` немедленно возвращает `false` и также прекращает перебор остальных элементов. + + Мы можем использовать `every` для сравнения массивов: + + ```js run + function arraysEqual(arr1, arr2) { + return arr1.length === arr2.length && arr1.every((value, index) => value === arr2[index]); + } -- [arr.some(fn)](mdn:js/Array/some)/[arr.every(fn)](mdn:js/Array/every) checks the array. + alert( arraysEqual([1, 2], [1, 2])); // true + ``` - The function `fn` is called on each element of the array similar to `map`. If any/all results are `true`, returns `true`, otherwise `false`. +- [arr.fill(value, start, end)](mdn:js/Array/fill) -- заполняет массив повторяющимися `value`, начиная с индекса `start` до `end`. -- [arr.fill(value, start, end)](mdn:js/Array/fill) -- fills the array with repeating `value` from index `start` to `end`. +- [arr.copyWithin(target, start, end)](mdn:js/Array/copyWithin) -- копирует свои элементы, начиная с позиции `start` и заканчивая `end`, в *себя*, на позицию `target` (перезаписывая существующие). -- [arr.copyWithin(target, start, end)](mdn:js/Array/copyWithin) -- copies its elements from position `start` till position `end` into *itself*, at position `target` (overwrites existing). +- [arr.flat(depth)](mdn:js/Array/flat)/[arr.flatMap(fn)](mdn:js/Array/flatMap) создаёт новый плоский массив из многомерного массива. -For the full list, see the [manual](mdn:js/Array). +Полный список есть в [справочнике MDN](mdn:js/Array). -From the first sight it may seem that there are so many methods, quite difficult to remember. But actually that's much easier than it seems. +На первый взгляд может показаться, что существует очень много разных методов, которые довольно сложно запомнить. Но это гораздо проще, чем кажется. -Look through the cheatsheet just to be aware of them. Then solve the tasks of this chapter to practice, so that you have experience with array methods. +Внимательно изучите шпаргалку, представленную выше, а затем, чтобы попрактиковаться, решите задачи, предложенные в данной главе. Так вы получите необходимый опыт в правильном использовании методов массива. -Afterwards whenever you need to do something with an array, and you don't know how -- come here, look at the cheatsheet and find the right method. Examples will help you to write it correctly. Soon you'll automatically remember the methods, without specific efforts from your side. +Всякий раз, когда вам будет необходимо что-то сделать с массивом, а вы не знаете, как это сделать -- приходите сюда, смотрите на таблицу и ищите правильный метод. Примеры помогут вам всё сделать правильно, и вскоре вы быстро запомните методы без особых усилий. diff --git a/1-js/05-data-types/05-array-methods/reduce.png b/1-js/05-data-types/05-array-methods/reduce.png deleted file mode 100644 index 7566c4d84c..0000000000 Binary files a/1-js/05-data-types/05-array-methods/reduce.png and /dev/null differ diff --git a/1-js/05-data-types/05-array-methods/reduce.svg b/1-js/05-data-types/05-array-methods/reduce.svg new file mode 100644 index 0000000000..180941dc29 --- /dev/null +++ b/1-js/05-data-types/05-array-methods/reduce.svg @@ -0,0 +1 @@ +1sum 0 current 12sum 0+1 current 23sum 0+1+2 current 34sum 0+1+2+3 current 45sum 0+1+2+3+4 current 50+1+2+3+4+5 = 15 \ No newline at end of file diff --git a/1-js/05-data-types/05-array-methods/reduce@2x.png b/1-js/05-data-types/05-array-methods/reduce@2x.png deleted file mode 100644 index 7c9fd6692b..0000000000 Binary files a/1-js/05-data-types/05-array-methods/reduce@2x.png and /dev/null differ diff --git a/1-js/05-data-types/06-iterable/article.md b/1-js/05-data-types/06-iterable/article.md index bbe4e77d6f..cfd643aeb3 100644 --- a/1-js/05-data-types/06-iterable/article.md +++ b/1-js/05-data-types/06-iterable/article.md @@ -1,15 +1,15 @@ -# Итерируемые объекты +# Перебираемые объекты -*Итерируемые* (или *перебираемые*) объекты - это применение принципов работы массивов к другим объектам. Эта концепция позволяет использовать любой объект в цикле `for..of`. +*Перебираемые* (или *итерируемые*) объекты - это обобщение массивов. Концепция, которая позволяет использовать любой объект в цикле `for..of`. -Конечно же, сами массивы являются итерируемыми объектами. Но есть и много других встроенных итерируемых объектов. Например, строки тоже можно перебирать. Как мы увидим далее, работа многих встроенных операторов и методов основана на них. +Конечно же, сами массивы являются перебираемыми объектами. Но есть и много других встроенных перебираемых объектов, например, строки. -Если объект представляет собой коллекцию, то можно использовать `for..of` для ее перебора, даже если это не массив. Давайте посмотрим, как это сделать. +Если объект не является массивом, но представляет собой коллекцию каких-то элементов (список, набор), то удобно использовать цикл `for..of` для их перебора, так что давайте посмотрим, как это сделать. ## Symbol.iterator -Мы легко поймём принцип работы итерируемых объектов, создав один из них. +Мы легко поймём принцип устройства перебираемых объектов, создав один из них. Например, у нас есть объект. Это не массив, но он выглядит подходящим для `for..of`. @@ -25,14 +25,14 @@ let range = { // for(let num of range) ... num=1,2,3,4,5 ``` -Чтобы сделать `range` итерируемым (и позволить ему работать в `for..of`), нам нужно добавить в объект метод с именем `Symbol.iterator` (специальный встроенный `Symbol`, созданный как раз для этого). +Чтобы сделать `range` итерируемым (и позволить `for..of` работать с ним), нам нужно добавить в объект метод с именем `Symbol.iterator` (специальный встроенный `Symbol`, созданный как раз для этого). -1. Когда цикл `for..of` запускается, он вызывает этот метод один раз (или выдает ошибку, если метод не найден). Этот метод должен вернуть *итератор* -- объект с методом `next`. -2. Дальше, `for..of` работает *только с этим возвращенным объектом*. +1. Когда цикл `for..of` запускается, он вызывает этот метод один раз (или выдаёт ошибку, если метод не найден). Этот метод должен вернуть *итератор* -- объект с методом `next`. +2. Дальше `for..of` работает *только с этим возвращённым объектом*. 3. Когда `for..of` хочет получить следующее значение, он вызывает метод `next()` этого объекта. -4. Результат вызова `next()` должен иметь вид `{done: Boolean, value: any}`, где `done=true` означает, что итерация закончена, в противном случае `value` должен содержать следующее значение. +4. Результат вызова `next()` должен иметь вид `{done: Boolean, value: any}`, где `done=true` означает, что цикл завершён, в противном случае `value` содержит очередное значение. -Вот полная реализация `range`: +Вот полная реализация `range` с пояснениями: ```js run let range = { @@ -44,10 +44,11 @@ let range = { range[Symbol.iterator] = function() { // ...она возвращает объект итератора: - // 2. Далее, for..of работает только с этим итератором, запрашивая у него новые значения + // 2. Далее, for..of работает только с этим итератором, + // запрашивая у него новые значения return { current: this.from, - last: this.to, + last: this.to, // 3. next() вызывается на каждой итерации цикла for..of next() { @@ -67,14 +68,14 @@ for (let num of range) { } ``` -Обратите внимание на ключевую особенность итераторов: разделение ответственности: +Обратите внимание на ключевую особенность итераторов: разделение ответственности. - У самого `range` нет метода `next()`. -- Вместо этого другой объект, так называемый "итератор", создается вызовом `range[Symbol.iterator]()`, и именно он управляет всей итерацией. +- Вместо этого другой объект, так называемый "итератор", создаётся вызовом `range[Symbol.iterator]()`, и именно его `next()` генерирует значения. -Таким образом, итератор отделен от самого итерируемого объекта. +Таким образом, объект итератор отделён от самого итерируемого объекта. -Технически, мы можем объединить их и использовать сам `range` как итератор, чтобы упростить код. +Технически мы можем объединить их и использовать сам `range` как итератор, чтобы упростить код. Например, вот так: @@ -102,22 +103,21 @@ for (let num of range) { } ``` -Теперь `range[Symbol.iterator]()` возвращает сам объект `range`: у него есть необходимый метод `next()` и он запоминает текущее состояние итерации в `this.current`. Короче? Да. И иногда такой способ тоже хорош. +Теперь `range[Symbol.iterator]()` возвращает сам объект `range`: у него есть необходимый метод `next()`, и он запоминает текущее состояние итерации в `this.current`. Короче? Да. И иногда такой способ тоже хорош. Недостаток такого подхода в том, что теперь мы не можем использовать этот объект в двух параллельных циклах `for..of`: у них будет общее текущее состояние итерации, потому что теперь существует лишь один итератор -- сам объект. Но необходимость в двух циклах `for..of`, выполняемых одновременно, возникает редко, даже при наличии асинхронных операций. ```smart header="Бесконечные итераторы" Можно сделать бесконечный итератор. Например, `range` будет бесконечным при `range.to = Infinity`. Или мы можем создать итерируемый объект, который генерирует бесконечную последовательность псевдослучайных чисел. Это бывает полезно. -Метод `next` не имеет ограничений, он может возвращать все новые и новые значения, это нормально. +Метод `next` не имеет ограничений, он может возвращать всё новые и новые значения, это нормально. Конечно же, цикл `for..of` с таким итерируемым объектом будет бесконечным. Но мы всегда можем прервать его, используя `break`. ``` +## Строка - перебираемый объект -## Строка - итерируемый объект - -Среди встроенных итерируемых объектов наиболее широко используются массивы и строки. +Среди встроенных перебираемых объектов наиболее широко используются массивы и строки. Для строки `for..of` перебирает символы: @@ -128,7 +128,7 @@ for (let char of "test") { } ``` -Работает корректно даже с суррогатными парами! +И он работает корректно даже с суррогатными парами! ```js run let str = '𝒳😂'; @@ -139,11 +139,9 @@ for (let char of str) { ## Явный вызов итератора -Обычно внутреннее устройство итерируемых объектов спрятано от внешнего кода. Есть цикл `for..of`, он работает, и это все, что нужно извне. - -Но чтобы понять устройство итераторов чуть глубже, давайте посмотрим как создать итератор явно. +Чтобы понять устройство итераторов чуть глубже, давайте посмотрим, как их использовать явно. -Мы будем перебирать строку в точности так же, как цикл `for..of`, но вручную, прямыми вызовами. Код ниже получает строковой итератор и получает из него значения: +Мы будем перебирать строку точно так же, как цикл `for..of`, но вручную, прямыми вызовами. Нижеприведённый код получает строковый итератор и берёт из него значения: ```js run let str = "Hello"; @@ -151,7 +149,9 @@ let str = "Hello"; // делает то же самое, что и // for (let char of str) alert(char); +*!* let iterator = str[Symbol.iterator](); +*/!* while (true) { let result = iterator.next(); @@ -160,22 +160,22 @@ while (true) { } ``` -Такое редко бывает необходимо, но это дает нам больше контроля над процессом, чем `for..of`. Например, мы можем разбить процесс итерации на части: перебрать немного элементов, затем остановиться, сделать что-то еще, и потом продолжить. +Такое редко бывает необходимо, но это даёт нам больше контроля над процессом, чем `for..of`. Например, мы можем разбить процесс итерации на части: перебрать немного элементов, затем остановиться, сделать что-то ещё и потом продолжить. ## Итерируемые объекты и псевдомассивы [#array-like] Есть два официальных термина, которые очень похожи, но в то же время сильно различаются. Поэтому убедитесь, что вы как следует поняли их, чтобы избежать путаницы. - *Итерируемые объекты* - это объекты, которые реализуют метод `Symbol.iterator`, как было описано выше. -- *Псевдомассивы* - это объекты, у которых есть индексы и свойство `length`, то есть они выглядят как массивы. +- *Псевдомассивы* - это объекты, у которых есть индексы и свойство `length`, то есть, они выглядят как массивы. -При использовании JavaScript в браузере или других окружениях, мы можем встретить объекты, которые являются итерируемыми или псевдомассивами или и тем и другим. +При использовании JavaScript в браузере или других окружениях мы можем встретить объекты, которые являются итерируемыми или псевдомассивами, или и тем, и другим. -Например, строки итерируемы (для них работает `for..of`) и являются псевдомасссивами (они индексированы и есть `length`). +Например, строки итерируемы (для них работает `for..of`) и являются псевдомассивами (они индексированы и есть `length`). Но итерируемый объект может не быть псевдомассивом. И наоборот: псевдомассив может не быть итерируемым. -Например, объект `range` из примера выше - итерируемый, но не является псевдомассивом, потому что у него нет индексированных свойств и `length`. +Например, объект `range` из примера выше - итерируемый, но не является псевдомассивом, потому что у него нет индексированных свойств и `length`. А вот объект, который является псевдомассивом, но его нельзя итерировать: @@ -213,17 +213,19 @@ let arr = Array.from(arrayLike); // (*) alert(arr.pop()); // World (метод работает) ``` -`Array.from` в строке `(*)` принимает объект, проверяет, является ли он итерируемым объектом или псевдомассивом, затем создает новый массив и копирует туда все элементы. +`Array.from` в строке `(*)` принимает объект, проверяет, является ли он итерируемым объектом или псевдомассивом, затем создаёт новый массив и копирует туда все элементы. То же самое происходит с итерируемым объектом: -```js -// range взят из примера выше +```js run +// range взят из примера в начале статьи + let arr = Array.from(range); alert(arr); // 1,2,3,4,5 (преобразование массива через toString работает) ``` Полный синтаксис `Array.from` позволяет указать необязательную "трансформирующую" функцию: + ```js Array.from(obj[, mapFn, thisArg]) ``` @@ -232,8 +234,8 @@ Array.from(obj[, mapFn, thisArg]) Например: -```js -// range взят из примера выше +```js run +// range взят из примера в начале статьи // возводим каждое число в квадрат let arr = Array.from(range, num => num * num); @@ -246,7 +248,7 @@ alert(arr); // 1,4,9,16,25 ```js run let str = '𝒳😂'; -// разбивает строку в массив её элементов +// разбивает строку на массив её элементов let chars = Array.from(str); alert(chars[0]); // 𝒳 @@ -254,9 +256,9 @@ alert(chars[1]); // 😂 alert(chars.length); // 2 ``` -В отличие от `str.split`, этот метод в работе опирается на итеративный характер строки и поэтому, совсем как `for..of`, он корректно работает с суррогатными парами. +В отличие от `str.split`, этот метод в работе опирается на итерируемость строки, и поэтому, как и `for..of`, он корректно работает с суррогатными парами. -Технически, это то же самое, что и: +Технически это то же самое, что и: ```js run let str = '𝒳😂'; @@ -269,7 +271,7 @@ for (let char of str) { alert(chars); ``` -...Но гораздо короче. +...Но гораздо короче. Мы можем даже создать `slice`, который поддерживает суррогатные пары: @@ -282,25 +284,24 @@ let str = '𝒳😂𩷶'; alert( slice(str, 1, 3) ); // 😂𩷶 -// встроенный метод не поддерживает суррогатные пары +// а вот встроенный метод не поддерживает суррогатные пары alert( str.slice(1, 3) ); // мусор (две части различных суррогатных пар) ``` - ## Итого Объекты, которые можно использовать в цикле `for..of`, называются *итерируемыми*. -- Технически, итерируемые объекты должны иметь метод `Symbol.iterator`. +- Технически итерируемые объекты должны иметь метод `Symbol.iterator`. - Результат вызова `obj[Symbol.iterator]` называется *итератором*. Он управляет процессом итерации. - - Итератор должен иметь метод `next()`, который возвращает объект `{done: Boolean, value: any}`, где `done:true` сигнализирует об окончании итерации, в противном случае `value` - следующее значение. + - Итератор должен иметь метод `next()`, который возвращает объект `{done: Boolean, value: any}`, где `done:true` сигнализирует об окончании процесса итерации, в противном случае `value` - следующее значение. - Метод `Symbol.iterator` автоматически вызывается циклом `for..of`, но можно вызвать его и напрямую. - Встроенные итерируемые объекты, такие как строки или массивы, также реализуют метод `Symbol.iterator`. -- Строковой итератор знает про суррогатные пары. +- Строковый итератор знает про суррогатные пары. Объекты, имеющие индексированные свойства и `length`, называются *псевдомассивами*. Они также могут иметь другие свойства и методы, но у них нет встроенных методов массивов. Если мы заглянем в спецификацию, мы увидим, что большинство встроенных методов рассчитывают на то, что они будут работать с итерируемыми объектами или псевдомассивами вместо "настоящих" массивов, потому что эти объекты более абстрактны. -`Array.from(obj[, mapFn, thisArg])` создает настоящий `Array` из итерируемого объекта или псевдомассива `obj`, и затем мы можем применять к нему методы массивов. Необязательные аргументы `mapFn` и `thisArg` позволяют применять функцию к каждому элементу. +`Array.from(obj[, mapFn, thisArg])` создаёт настоящий `Array` из итерируемого объекта или псевдомассива `obj`, и затем мы можем применять к нему методы массивов. Необязательные аргументы `mapFn` и `thisArg` позволяют применять функцию с задаваемым контекстом к каждому элементу. diff --git a/1-js/05-data-types/06-iterable/head.html b/1-js/05-data-types/06-iterable/head.html new file mode 100644 index 0000000000..14edae5dcd --- /dev/null +++ b/1-js/05-data-types/06-iterable/head.html @@ -0,0 +1,24 @@ + \ No newline at end of file diff --git a/1-js/05-data-types/07-map-set-weakmap-weakset/01-array-unique-map/task.md b/1-js/05-data-types/07-map-set-weakmap-weakset/01-array-unique-map/task.md deleted file mode 100644 index d68030032e..0000000000 --- a/1-js/05-data-types/07-map-set-weakmap-weakset/01-array-unique-map/task.md +++ /dev/null @@ -1,27 +0,0 @@ -importance: 5 - ---- - -# Filter unique array members - -Let `arr` be an array. - -Create a function `unique(arr)` that should return an array with unique items of `arr`. - -For instance: - -```js -function unique(arr) { - /* your code */ -} - -let values = ["Hare", "Krishna", "Hare", "Krishna", - "Krishna", "Krishna", "Hare", "Hare", ":-O" -]; - -alert( unique(values) ); // Hare, Krishna, :-O -``` - -P.S. Here strings are used, but can be values of any type. - -P.P.S. Use `Set` to store unique values. diff --git a/1-js/05-data-types/07-map-set-weakmap-weakset/02-filter-anagrams/solution.md b/1-js/05-data-types/07-map-set-weakmap-weakset/02-filter-anagrams/solution.md deleted file mode 100644 index 4c8af1f248..0000000000 --- a/1-js/05-data-types/07-map-set-weakmap-weakset/02-filter-anagrams/solution.md +++ /dev/null @@ -1,77 +0,0 @@ -To find all anagrams, let's split every word to letters and sort them. When letter-sorted, all anagrams are same. - -For instance: - -``` -nap, pan -> anp -ear, era, are -> aer -cheaters, hectares, teachers -> aceehrst -... -``` - -We'll use the letter-sorted variants as map keys to store only one value per each key: - -```js run -function aclean(arr) { - let map = new Map(); - - for (let word of arr) { - // split the word by letters, sort them and join back -*!* - let sorted = word.toLowerCase().split('').sort().join(''); // (*) -*/!* - map.set(sorted, word); - } - - return Array.from(map.values()); -} - -let arr = ["nap", "teachers", "cheaters", "PAN", "ear", "era", "hectares"]; - -alert( aclean(arr) ); -``` - -Letter-sorting is done by the chain of calls in the line `(*)`. - -For convenience let's split it into multiple lines: - -```js -let sorted = arr[i] // PAN - .toLowerCase() // pan - .split('') // ['p','a','n'] - .sort() // ['a','n','p'] - .join(''); // anp -``` - -Two different words `'PAN'` and `'nap'` receive the same letter-sorted form `'anp'`. - -The next line put the word into the map: - -```js -map.set(sorted, word); -``` - -If we ever meet a word the same letter-sorted form again, then it would overwrite the previous value with the same key in the map. So we'll always have at maximum one word per letter-form. - -At the end `Array.from(map.values())` takes an iterable over map values (we don't need keys in the result) and returns an array of them. - -Here we could also use a plain object instead of the `Map`, because keys are strings. - -That's how the solution can look: - -```js run demo -function aclean(arr) { - let obj = {}; - - for (let i = 0; i < arr.length; i++) { - let sorted = arr[i].toLowerCase().split("").sort().join(""); - obj[sorted] = arr[i]; - } - - return Object.values(obj); -} - -let arr = ["nap", "teachers", "cheaters", "PAN", "ear", "era", "hectares"]; - -alert( aclean(arr) ); -``` diff --git a/1-js/05-data-types/07-map-set-weakmap-weakset/02-filter-anagrams/task.md b/1-js/05-data-types/07-map-set-weakmap-weakset/02-filter-anagrams/task.md deleted file mode 100644 index 731fd2c256..0000000000 --- a/1-js/05-data-types/07-map-set-weakmap-weakset/02-filter-anagrams/task.md +++ /dev/null @@ -1,28 +0,0 @@ -importance: 4 - ---- - -# Filter anagrams - -[Anagrams](https://en.wikipedia.org/wiki/Anagram) are words that have the same number of same letters, but in different order. - -For instance: - -``` -nap - pan -ear - are - era -cheaters - hectares - teachers -``` - -Write a function `aclean(arr)` that returns an array cleaned from anagrams. - -For instance: - -```js -let arr = ["nap", "teachers", "cheaters", "PAN", "ear", "era", "hectares"]; - -alert( aclean(arr) ); // "nap,teachers,ear" or "PAN,cheaters,era" -``` - -From every anagram group should remain only one word, no matter which one. - diff --git a/1-js/05-data-types/07-map-set-weakmap-weakset/03-iterable-keys/solution.md b/1-js/05-data-types/07-map-set-weakmap-weakset/03-iterable-keys/solution.md deleted file mode 100644 index 7310d1d36f..0000000000 --- a/1-js/05-data-types/07-map-set-weakmap-weakset/03-iterable-keys/solution.md +++ /dev/null @@ -1,19 +0,0 @@ - -That's because `map.keys()` returns an iterable, but not an array. - -We can convert it into an array using `Array.from`: - - -```js run -let map = new Map(); - -map.set("name", "John"); - -*!* -let keys = Array.from(map.keys()); -*/!* - -keys.push("more"); - -alert(keys); // name, more -``` diff --git a/1-js/05-data-types/07-map-set-weakmap-weakset/03-iterable-keys/task.md b/1-js/05-data-types/07-map-set-weakmap-weakset/03-iterable-keys/task.md deleted file mode 100644 index b1ccbd0ac2..0000000000 --- a/1-js/05-data-types/07-map-set-weakmap-weakset/03-iterable-keys/task.md +++ /dev/null @@ -1,24 +0,0 @@ -importance: 5 - ---- - -# Iterable keys - -We want to get an array of `map.keys()` and go on working with it (apart from the map itself). - -But there's a problem: - -```js run -let map = new Map(); - -map.set("name", "John"); - -let keys = map.keys(); - -*!* -// Error: keys.push is not a function -keys.push("more"); -*/!* -``` - -Why? How can we fix the code to make `keys.push` work? diff --git a/1-js/05-data-types/07-map-set-weakmap-weakset/04-recipients-read/solution.md b/1-js/05-data-types/07-map-set-weakmap-weakset/04-recipients-read/solution.md deleted file mode 100644 index ce56f593a2..0000000000 --- a/1-js/05-data-types/07-map-set-weakmap-weakset/04-recipients-read/solution.md +++ /dev/null @@ -1,41 +0,0 @@ -The sane choice here is a `WeakSet`: - -```js -let messages = [ - {text: "Hello", from: "John"}, - {text: "How goes?", from: "John"}, - {text: "See you soon", from: "Alice"} -]; - -let readMessages = new WeakSet(); - -// two messages have been read -readMessages.add(messages[0]); -readMessages.add(messages[1]); -// readMessages has 2 elements - -// ...let's read the first message again! -readMessages.add(messages[0]); -// readMessages still has 2 unique elements - -// answer: was the message[0] read? -alert("Read message 0: " + readMessages.has(messages[0])); // true - -messages.shift(); -// now readMessages has 1 element (technically memory may be cleaned later) -``` - -The `WeakSet` allows to store a set of messages and easily check for the existance of a message in it. - -It cleans up itself automatically. The tradeoff is that we can't iterate over it. We can't get "all read messages" directly. But we can do it by iterating over all messages and filtering those that are in the set. - -P.S. Adding a property of our own to each message may be dangerous if messages are managed by someone else's code, but we can make it a symbol to evade conflicts. - -Like this: -```js -// the symbolic property is only known to our code -let isRead = Symbol("isRead"); -messages[0][isRead] = true; -``` - -Now even if someone else's code uses `for..in` loop for message properties, our secret flag won't appear. diff --git a/1-js/05-data-types/07-map-set-weakmap-weakset/04-recipients-read/task.md b/1-js/05-data-types/07-map-set-weakmap-weakset/04-recipients-read/task.md deleted file mode 100644 index 7ec1faf16c..0000000000 --- a/1-js/05-data-types/07-map-set-weakmap-weakset/04-recipients-read/task.md +++ /dev/null @@ -1,23 +0,0 @@ -importance: 5 - ---- - -# Store "unread" flags - -There's an array of messages: - -```js -let messages = [ - {text: "Hello", from: "John"}, - {text: "How goes?", from: "John"}, - {text: "See you soon", from: "Alice"} -]; -``` - -Your code can access it, but the messages are managed by someone else's code. New messages are added, old ones are removed regularly by that code, and you don't know the exact moments when it happens. - -Now, which data structure you could use to store information whether the message "have been read"? The structure must be well-suited to give the answer "was it read?" for the given message object. - -P.S. When a message is removed from `messages`, it should disappear from your structure as well. - -P.P.S. We shouldn't modify message objects directly. If they are managed by someone else's code, then adding extra properties to them may have bad consequences. diff --git a/1-js/05-data-types/07-map-set-weakmap-weakset/05-recipients-when-read/solution.md b/1-js/05-data-types/07-map-set-weakmap-weakset/05-recipients-when-read/solution.md deleted file mode 100644 index 7f387b4da0..0000000000 --- a/1-js/05-data-types/07-map-set-weakmap-weakset/05-recipients-when-read/solution.md +++ /dev/null @@ -1,15 +0,0 @@ - -To store a date, we can use `WeakMap`: - -```js -let messages = [ - {text: "Hello", from: "John"}, - {text: "How goes?", from: "John"}, - {text: "See you soon", from: "Alice"} -]; - -let readMap = new WeakMap(); - -readMap.set(messages[0], new Date(2017, 1, 1)); -// Date object we'll study later -``` diff --git a/1-js/05-data-types/07-map-set-weakmap-weakset/05-recipients-when-read/task.md b/1-js/05-data-types/07-map-set-weakmap-weakset/05-recipients-when-read/task.md deleted file mode 100644 index 22b51a382e..0000000000 --- a/1-js/05-data-types/07-map-set-weakmap-weakset/05-recipients-when-read/task.md +++ /dev/null @@ -1,19 +0,0 @@ -importance: 5 - ---- - -# Store read dates - -There's an array of messages as in the [previous task](info:task/recipients-read). The situation is similar. - -```js -let messages = [ - {text: "Hello", from: "John"}, - {text: "How goes?", from: "John"}, - {text: "See you soon", from: "Alice"} -]; -``` - -The question now is: which data structure you'd suggest to store the information: "when the message was read?". - -In the previous task we only needed to store the "yes/no" fact. Now we need to store the date and it, once again, should disappear if the message is gone. diff --git a/1-js/05-data-types/07-map-set-weakmap-weakset/article.md b/1-js/05-data-types/07-map-set-weakmap-weakset/article.md deleted file mode 100644 index 034ad22c34..0000000000 --- a/1-js/05-data-types/07-map-set-weakmap-weakset/article.md +++ /dev/null @@ -1,458 +0,0 @@ - -# Map, Set, WeakMap and WeakSet - -Now we've learned about the following complex data structures: - -- Objects for storing keyed collections. -- Arrays for storing ordered collections. - -But that's not enough for real life. That's why `Map` and `Set` also exist. - -## Map - -[Map](mdn:js/Map) is a collection of keyed data items, just like an `Object`. But the main difference is that `Map` allows keys of any type. - -The main methods are: - -- `new Map()` -- creates the map. -- `map.set(key, value)` -- stores the value by the key. -- `map.get(key)` -- returns the value by the key, `undefined` if `key` doesn't exist in map. -- `map.has(key)` -- returns `true` if the `key` exists, `false` otherwise. -- `map.delete(key)` -- removes the value by the key. -- `map.clear()` -- clears the map -- `map.size` -- returns the current element count. - -For instance: - -```js run -let map = new Map(); - -map.set('1', 'str1'); // a string key -map.set(1, 'num1'); // a numeric key -map.set(true, 'bool1'); // a boolean key - -// remember the regular Object? it would convert keys to string -// Map keeps the type, so these two are different: -alert( map.get(1) ); // 'num1' -alert( map.get('1') ); // 'str1' - -alert( map.size ); // 3 -``` - -As we can see, unlike objects, keys are not converted to strings. Any type of key is possible. - -**Map can also use objects as keys.** - -For instance: -```js run -let john = { name: "John" }; - -// for every user, let's store their visits count -let visitsCountMap = new Map(); - -// john is the key for the map -visitsCountMap.set(john, 123); - -alert( visitsCountMap.get(john) ); // 123 -``` - -Using objects as keys is one of most notable and important `Map` features. For string keys, `Object` can be fine, but it would be difficult to replace the `Map` with a regular `Object` in the example above. - -In the old times, before `Map` existed, people added unique identifiers to objects for that: - -```js run -// we add the id field -let john = { name: "John", *!*id: 1*/!* }; - -let visitsCounts = {}; - -// now store the value by id -visitsCounts[john.id] = 123; - -alert( visitsCounts[john.id] ); // 123 -``` - -...But `Map` is much more elegant. - - -```smart header="How `Map` compares keys" -To test values for equivalence, `Map` uses the algorithm [SameValueZero](https://tc39.github.io/ecma262/#sec-samevaluezero). It is roughly the same as strict equality `===`, but the difference is that `NaN` is considered equal to `NaN`. So `NaN` can be used as the key as well. - -This algorithm can't be changed or customized. -``` - - -````smart header="Chaining" - -Every `map.set` call returns the map itself, so we can "chain" the calls: - -```js -map.set('1', 'str1') - .set(1, 'num1') - .set(true, 'bool1'); -``` -```` - -## Map from Object - -When a `Map` is created, we can pass an array (or another iterable) with key-value pairs, like this: - -```js -// array of [key, value] pairs -let map = new Map([ - ['1', 'str1'], - [1, 'num1'], - [true, 'bool1'] -]); -``` - -There is a built-in method [Object.entries(obj)](mdn:js/Object/entries) that returns an array of key/value pairs for an object exactly in that format. - -So we can initialize a map from an object like this: - -```js -let map = new Map(Object.entries({ - name: "John", - age: 30 -})); -``` - -Here, `Object.entries` returns the array of key/value pairs: `[ ["name","John"], ["age", 30] ]`. That's what `Map` needs. - -## Iteration over Map - -For looping over a `map`, there are 3 methods: - -- `map.keys()` -- returns an iterable for keys, -- `map.values()` -- returns an iterable for values, -- `map.entries()` -- returns an iterable for entries `[key, value]`, it's used by default in `for..of`. - -For instance: - -```js run -let recipeMap = new Map([ - ['cucumber', 500], - ['tomatoes', 350], - ['onion', 50] -]); - -// iterate over keys (vegetables) -for (let vegetable of recipeMap.keys()) { - alert(vegetable); // cucumber, tomatoes, onion -} - -// iterate over values (amounts) -for (let amount of recipeMap.values()) { - alert(amount); // 500, 350, 50 -} - -// iterate over [key, value] entries -for (let entry of recipeMap) { // the same as of recipeMap.entries() - alert(entry); // cucumber,500 (and so on) -} -``` - -```smart header="The insertion order is used" -The iteration goes in the same order as the values were inserted. `Map` preserves this order, unlike a regular `Object`. -``` - -Besides that, `Map` has a built-in `forEach` method, similar to `Array`: - -```js -// runs the function for each (key, value) pair -recipeMap.forEach( (value, key, map) => { - alert(`${key}: ${value}`); // cucumber: 500 etc -}); -``` - - -## Set - -A `Set` is a collection of values, where each value may occur only once. - -Its main methods are: - -- `new Set(iterable)` -- creates the set, optionally from an array of values (any iterable will do). -- `set.add(value)` -- adds a value, returns the set itself. -- `set.delete(value)` -- removes the value, returns `true` if `value` existed at the moment of the call, otherwise `false`. -- `set.has(value)` -- returns `true` if the value exists in the set, otherwise `false`. -- `set.clear()` -- removes everything from the set. -- `set.size` -- is the elements count. - -For example, we have visitors coming, and we'd like to remember everyone. But repeated visits should not lead to duplicates. A visitor must be "counted" only once. - -`Set` is just the right thing for that: - -```js run -let set = new Set(); - -let john = { name: "John" }; -let pete = { name: "Pete" }; -let mary = { name: "Mary" }; - -// visits, some users come multiple times -set.add(john); -set.add(pete); -set.add(mary); -set.add(john); -set.add(mary); - -// set keeps only unique values -alert( set.size ); // 3 - -for (let user of set) { - alert(user.name); // John (then Pete and Mary) -} -``` - -The alternative to `Set` could be an array of users, and the code to check for duplicates on every insertion using [arr.find](mdn:js/Array/find). But the performance would be much worse, because this method walks through the whole array checking every element. `Set` is much better optimized internally for uniqueness checks. - -## Iteration over Set - -We can loop over a set either with `for..of` or using `forEach`: - -```js run -let set = new Set(["oranges", "apples", "bananas"]); - -for (let value of set) alert(value); - -// the same with forEach: -set.forEach((value, valueAgain, set) => { - alert(value); -}); -``` - -Note the funny thing. The `forEach` function in the `Set` has 3 arguments: a value, then *again a value*, and then the target object. Indeed, the same value appears in the arguments twice. - -That's for compatibility with `Map` where `forEach` has three arguments. Looks a bit strange, for sure. But may help to replace `Map` with `Set` in certain cases with ease, and vice versa. - -The same methods `Map` has for iterators are also supported: - -- `set.keys()` -- returns an iterable object for values, -- `set.values()` -- same as `set.keys`, for compatibility with `Map`, -- `set.entries()` -- returns an iterable object for entries `[value, value]`, exists for compatibility with `Map`. - -## WeakMap and WeakSet - -`WeakSet` is a special kind of `Set` that does not prevent JavaScript from removing its items from memory. `WeakMap` is the same thing for `Map`. - -As we know from the chapter , JavaScript engine stores a value in memory while it is reachable (and can potentially be used). - -For instance: -```js -let john = { name: "John" }; - -// the object can be accessed, john is the reference to it - -// overwrite the reference -john = null; - -*!* -// the object will be removed from memory -*/!* -``` - -Usually, properties of an object or elements of an array or another data structure are considered reachable and kept in memory while that data structure is in memory. - -For instance, if we put an object into an array, then while the array is alive, the object will be alive as well, even if there are no other references to it. - -Like this: - -```js -let john = { name: "John" }; - -let array = [ john ]; - -john = null; // overwrite the reference - -*!* -// john is stored inside the array, so it won't be garbage-collected -// we can get it as array[0] -*/!* -``` - -Or, if we use an object as the key in a regular `Map`, then while the `Map` exists, that object exists as well. It occupies memory and may not be garbage collected. - -For instance: - -```js -let john = { name: "John" }; - -let map = new Map(); -map.set(john, "..."); - -john = null; // overwrite the reference - -*!* -// john is stored inside the map, -// we can get it by using map.keys() -*/!* -``` - -`WeakMap/WeakSet` are fundamentally different in this aspect. They do not prevent garbage-collection of key objects. - -Let's explain it starting with `WeakMap`. - -The first difference from `Map` is that `WeakMap` keys must be objects, not primitive values: - -```js run -let weakMap = new WeakMap(); - -let obj = {}; - -weakMap.set(obj, "ok"); // works fine (object key) - -*!* -// can't use a string as the key -weakMap.set("test", "Whoops"); // Error, because "test" is not an object -*/!* -``` - -Now, if we use an object as the key in it, and there are no other references to that object -- it will be removed from memory (and from the map) automatically. - -```js -let john = { name: "John" }; - -let weakMap = new WeakMap(); -weakMap.set(john, "..."); - -john = null; // overwrite the reference - -// john is removed from memory! -``` - -Compare it with the regular `Map` example above. Now if `john` only exists as the key of `WeakMap` -- it is to be automatically deleted. - -`WeakMap` does not support iteration and methods `keys()`, `values()`, `entries()`, so there's no way to get all keys or values from it. - -`WeakMap` has only the following methods: - -- `weakMap.get(key)` -- `weakMap.set(key, value)` -- `weakMap.delete(key)` -- `weakMap.has(key)` - -Why such a limitation? That's for technical reasons. If an object has lost all other references (like `john` in the code above), then it is to be garbage-collected automatically. But technically it's not exactly specified *when the cleanup happens*. - -The JavaScript engine decides that. It may choose to perform the memory cleanup immediately or to wait and do the cleaning later when more deletions happen. So, technically the current element count of a `WeakMap` is not known. The engine may have cleaned it up or not, or did it partially. For that reason, methods that access `WeakMap` as a whole are not supported. - -Now where do we need such thing? - -The idea of `WeakMap` is that we can store something for an object that should exist only while the object exists. But we do not force the object to live by the mere fact that we store something for it. - -```js -weakMap.set(john, "secret documents"); -// if john dies, secret documents will be destroyed automatically -``` - -That's useful for situations when we have a main storage for the objects somewhere and need to keep additional information, that is only relevant while the object lives. - -Let's look at an example. - -For instance, we have code that keeps a visit count for each user. The information is stored in a map: a user is the key and the visit count is the value. When a user leaves, we don't want to store their visit count anymore. - -One way would be to keep track of users, and when they leave -- clean up the map manually: - -```js run -let john = { name: "John" }; - -// map: user => visits count -let visitsCountMap = new Map(); - -// john is the key for the map -visitsCountMap.set(john, 123); - -// now john leaves us, we don't need him anymore -john = null; - -*!* -// but it's still in the map, we need to clean it! -*/!* -alert( visitsCountMap.size ); // 1 -// and john is also in the memory, because Map uses it as the key -``` - -Another way would be to use `WeakMap`: - -```js -let john = { name: "John" }; - -let visitsCountMap = new WeakMap(); - -visitsCountMap.set(john, 123); - -// now john leaves us, we don't need him anymore -john = null; - -// there are no references except WeakMap, -// so the object is removed both from the memory and from visitsCountMap automatically -``` - -With a regular `Map`, cleaning up after a user has left becomes a tedious task: we not only need to remove the user from its main storage (be it a variable or an array), but also need to clean up the additional stores like `visitsCountMap`. And it can become cumbersome in more complex cases when users are managed in one place of the code and the additional structure is in another place and is getting no information about removals. - -```summary -`WeakMap` can make things simpler, because it is cleaned up automatically. The information in it like visits count in the example above lives only while the key object exists. -``` - -`WeakSet` behaves similarly: - -- It is analogous to `Set`, but we may only add objects to `WeakSet` (not primitives). -- An object exists in the set while it is reachable from somewhere else. -- Like `Set`, it supports `add`, `has` and `delete`, but not `size`, `keys()` and no iterations. - -For instance, we can use it to keep track of whether a message is read: - -```js -let messages = [ - {text: "Hello", from: "John"}, - {text: "How goes?", from: "John"}, - {text: "See you soon", from: "Alice"} -]; - -// fill it with array elements (3 items) -let unreadSet = new WeakSet(messages); - -// use unreadSet to see whether a message is unread -alert(unreadSet.has(messages[1])); // true - -// remove it from the set after reading -unreadSet.delete(messages[1]); // true - -// and when we shift our messages history, the set is cleaned up automatically -messages.shift(); - -*!* -// no need to clean unreadSet, it now has 2 items -*/!* -// (though technically we don't know for sure when the JS engine clears it) -``` - -The most notable limitation of `WeakMap` and `WeakSet` is the absence of iterations, and inability to get all current content. That may appear inconvenient, but does not prevent `WeakMap/WeakSet` from doing their main job -- be an "additional" storage of data for objects which are stored/managed at another place. - -## Summary - -Regular collections: -- `Map` -- is a collection of keyed values. - - The differences from a regular `Object`: - - - Any keys, objects can be keys. - - Iterates in the insertion order. - - Additional convenient methods, the `size` property. - -- `Set` -- is a collection of unique values. - - - Unlike an array, does not allow to reorder elements. - - Keeps the insertion order. - -Collections that allow garbage-collection: - -- `WeakMap` -- a variant of `Map` that allows only objects as keys and removes them once they become inaccessible by other means. - - - It does not support operations on the structure as a whole: no `size`, no `clear()`, no iterations. - -- `WeakSet` -- is a variant of `Set` that only stores objects and removes them once they become inaccessible by other means. - - - Also does not support `size/clear()` and iterations. - -`WeakMap` and `WeakSet` are used as "secondary" data structures in addition to the "main" object storage. Once the object is removed from the main storage, if it is only found in the `WeakMap/WeakSet`, it will be cleaned up automatically. diff --git a/1-js/05-data-types/07-map-set-weakmap-weakset/01-array-unique-map/_js.view/solution.js b/1-js/05-data-types/07-map-set/01-array-unique-map/_js.view/solution.js similarity index 100% rename from 1-js/05-data-types/07-map-set-weakmap-weakset/01-array-unique-map/_js.view/solution.js rename to 1-js/05-data-types/07-map-set/01-array-unique-map/_js.view/solution.js diff --git a/1-js/05-data-types/07-map-set-weakmap-weakset/01-array-unique-map/_js.view/test.js b/1-js/05-data-types/07-map-set/01-array-unique-map/_js.view/test.js similarity index 100% rename from 1-js/05-data-types/07-map-set-weakmap-weakset/01-array-unique-map/_js.view/test.js rename to 1-js/05-data-types/07-map-set/01-array-unique-map/_js.view/test.js diff --git a/1-js/05-data-types/07-map-set-weakmap-weakset/01-array-unique-map/solution.md b/1-js/05-data-types/07-map-set/01-array-unique-map/solution.md similarity index 100% rename from 1-js/05-data-types/07-map-set-weakmap-weakset/01-array-unique-map/solution.md rename to 1-js/05-data-types/07-map-set/01-array-unique-map/solution.md diff --git a/1-js/05-data-types/07-map-set/01-array-unique-map/task.md b/1-js/05-data-types/07-map-set/01-array-unique-map/task.md new file mode 100644 index 0000000000..e8d2294bec --- /dev/null +++ b/1-js/05-data-types/07-map-set/01-array-unique-map/task.md @@ -0,0 +1,27 @@ +importance: 5 + +--- + +# Фильтрация уникальных элементов массива + +Допустим, у нас есть массив `arr`. + +Создайте функцию `unique(arr)`, которая вернёт массив уникальных, не повторяющихся значений массива `arr`. + +Например: + +```js +function unique(arr) { + /* ваш код */ +} + +let values = ["Hare", "Krishna", "Hare", "Krishna", + "Krishna", "Krishna", "Hare", "Hare", ":-O" +]; + +alert( unique(values) ); // Hare,Krishna,:-O +``` + +P.S. Здесь мы используем строки, но значения могут быть любого типа. + +P.P.S. Используйте `Set` для хранения уникальных значений. diff --git a/1-js/05-data-types/07-map-set-weakmap-weakset/02-filter-anagrams/_js.view/solution.js b/1-js/05-data-types/07-map-set/02-filter-anagrams/_js.view/solution.js similarity index 100% rename from 1-js/05-data-types/07-map-set-weakmap-weakset/02-filter-anagrams/_js.view/solution.js rename to 1-js/05-data-types/07-map-set/02-filter-anagrams/_js.view/solution.js diff --git a/1-js/05-data-types/07-map-set-weakmap-weakset/02-filter-anagrams/_js.view/test.js b/1-js/05-data-types/07-map-set/02-filter-anagrams/_js.view/test.js similarity index 100% rename from 1-js/05-data-types/07-map-set-weakmap-weakset/02-filter-anagrams/_js.view/test.js rename to 1-js/05-data-types/07-map-set/02-filter-anagrams/_js.view/test.js diff --git a/1-js/05-data-types/07-map-set/02-filter-anagrams/solution.md b/1-js/05-data-types/07-map-set/02-filter-anagrams/solution.md new file mode 100644 index 0000000000..aaec82046f --- /dev/null +++ b/1-js/05-data-types/07-map-set/02-filter-anagrams/solution.md @@ -0,0 +1,77 @@ +Чтобы найти все анаграммы, давайте разобьём каждое слово на буквы и отсортируем их, а потом объединим получившийся массив снова в строку. После этого все анаграммы будут одинаковы. + +Например: + +``` +nap, pan -> anp +ear, era, are -> aer +cheaters, hectares, teachers -> aceehrst +... +``` + +Мы будем использовать отсортированные строки как ключи в коллекции Map, для того чтобы сопоставить каждому ключу только одно значение: + +```js run +function aclean(arr) { + let map = new Map(); + + for (let word of arr) { + // разбиваем слово на буквы, сортируем и объединяем снова в строку +*!* + let sorted = word.toLowerCase().split("").sort().join(""); // (*) +*/!* + map.set(sorted, word); + } + + return Array.from(map.values()); +} + +let arr = ["nap", "teachers", "cheaters", "PAN", "ear", "era", "hectares"]; + +alert( aclean(arr) ); +``` + +Строка с отсортированными буквами получается в результате цепочки вызовов в строке `(*)`. + +Для удобства, давайте разделим это на несколько строк: + +```js +let sorted = arr[i] // PAN + .toLowerCase() // pan + .split("") // ["p","a","n"] + .sort() // ["a","n","p"] + .join(""); // anp +``` + +Два разных слова `'PAN'` и `'nap'` принимают ту же самую форму после сортировки букв -- `'anp'`. + +Следующая строчка кода помещает слово в объект Map: + +```js +map.set(sorted, word); +``` + +Если мы когда-либо ещё встретим слово в той же отсортированной форме, тогда это слово перезапишет значение с тем же ключом в объекте. Таким образом, нескольким словам у нас будет всегда соответствовать одна отсортированная форма. + +В конце `Array.from(map.values())` принимает итерируемый объект значений объекта Map (в данном случае нам не нужны ключи) и возвращает их в виде массива. + +Также в этом случае вместо `Map` мы можем использовать простой объект, потому что ключи являются строками. + +В этом случае решение может выглядеть так: + +```js run demo +function aclean(arr) { + let obj = {}; + + for (let i = 0; i < arr.length; i++) { + let sorted = arr[i].toLowerCase().split("").sort().join(""); + obj[sorted] = arr[i]; + } + + return Object.values(obj); +} + +let arr = ["nap", "teachers", "cheaters", "PAN", "ear", "era", "hectares"]; + +alert( aclean(arr) ); +``` diff --git a/1-js/05-data-types/07-map-set/02-filter-anagrams/task.md b/1-js/05-data-types/07-map-set/02-filter-anagrams/task.md new file mode 100644 index 0000000000..8cfbbab8ca --- /dev/null +++ b/1-js/05-data-types/07-map-set/02-filter-anagrams/task.md @@ -0,0 +1,28 @@ +importance: 4 + +--- + +# Отфильтруйте анаграммы + +[Анаграммы](https://ru.wikipedia.org/wiki/%D0%90%D0%BD%D0%B0%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B0) -- это слова, у которых те же буквы в том же количестве, но они располагаются в другом порядке. + +Например: + +``` +nap - pan +ear - are - era +cheaters - hectares - teachers +``` + +Напишите функцию `aclean(arr)`, которая возвращает массив слов, очищенный от анаграмм. + +Например: + +```js +let arr = ["nap", "teachers", "cheaters", "PAN", "ear", "era", "hectares"]; + +alert( aclean(arr) ); // "nap,teachers,ear" или "PAN,cheaters,era" +``` + +Из каждой группы анаграмм должно остаться только одно слово, не важно какое. + diff --git a/1-js/05-data-types/07-map-set/03-iterable-keys/solution.md b/1-js/05-data-types/07-map-set/03-iterable-keys/solution.md new file mode 100644 index 0000000000..b13444af78 --- /dev/null +++ b/1-js/05-data-types/07-map-set/03-iterable-keys/solution.md @@ -0,0 +1,18 @@ + +Это потому что `map.keys()` возвращает итерируемый объект, а не массив. + +Мы можем конвертировать его в массив с помощью `Array.from`: + +```js run +let map = new Map(); + +map.set("name", "John"); + +*!* +let keys = Array.from(map.keys()); +*/!* + +keys.push("more"); + +alert(keys); // name, more +``` diff --git a/1-js/05-data-types/07-map-set/03-iterable-keys/task.md b/1-js/05-data-types/07-map-set/03-iterable-keys/task.md new file mode 100644 index 0000000000..44fe215147 --- /dev/null +++ b/1-js/05-data-types/07-map-set/03-iterable-keys/task.md @@ -0,0 +1,25 @@ +importance: 5 + +--- + +# Перебираемые ключи + +Мы хотели бы получить массив ключей `map.keys()` в переменную и далее работать с ними, например, применить метод `.push`. + +Но это не выходит: + +```js run +let map = new Map(); + +map.set("name", "John"); + +let keys = map.keys(); + +*!* +// Error: keys.push is not a function +// Ошибка: keys.push -- это не функция +keys.push("more"); +*/!* +``` + +Почему? Что нужно поправить в коде, чтобы вызов `keys.push` сработал? diff --git a/1-js/05-data-types/07-map-set/article.md b/1-js/05-data-types/07-map-set/article.md new file mode 100644 index 0000000000..efff26e8f9 --- /dev/null +++ b/1-js/05-data-types/07-map-set/article.md @@ -0,0 +1,331 @@ + +# Map и Set + +Сейчас мы знаем о следующих сложных структурах данных: + +- Объекты для хранения именованных коллекций. +- Массивы для хранения упорядоченных коллекций. + +Но этого не всегда достаточно для решения повседневных задач. Поэтому также существуют `Map` и `Set`. + +## Map + +[Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) -- это коллекция ключ/значение, как и `Object`. Но основное отличие в том, что `Map` позволяет использовать ключи любого типа. + +Методы и свойства: + +- [`new Map()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/Map) -- создаёт коллекцию. +- [`map.set(key, value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/set) -- записывает по ключу `key` значение `value`. +- [`map.get(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/get) -- возвращает значение по ключу или `undefined`, если ключ `key` отсутствует. +- [`map.has(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/has) -- возвращает `true`, если ключ `key` присутствует в коллекции, иначе `false`. +- [`map.delete(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/delete) -- удаляет элемент (пару "ключ/значение") по ключу `key`. +- [`map.clear()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/clear) -- очищает коллекцию от всех элементов. +- [`map.size`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/size) -- возвращает текущее количество элементов. + +Например: + +```js run +let map = new Map(); + +map.set("1", "str1"); // строка в качестве ключа +map.set(1, "num1"); // цифра как ключ +map.set(true, "bool1"); // булево значение как ключ + +// помните, обычный объект Object приводит ключи к строкам? +// Map сохраняет тип ключей, так что в этом случае сохранится 2 разных значения: +alert(map.get(1)); // "num1" +alert(map.get("1")); // "str1" + +alert(map.size); // 3 +``` + +Как мы видим, в отличие от объектов, ключи не были приведены к строкам. Можно использовать любые типы данных для ключей. + +```smart header="`map[key]` это не совсем правильный способ использования `Map`" +Хотя `map[key]` также работает, например, мы можем установить `map[key] = 2`, в этом случае`map` рассматривался бы как обычный JavaScript объект, таким образом это ведёт ко всем соответствующим ограничениям (только строки/символьные ключи и так далее). + +Поэтому нам следует использовать методы `map`: `set`, `get` и так далее. +``` + +**Map может использовать объекты в качестве ключей.** + +Например: + +```js run +let john = { name: "John" }; + +// давайте сохраним количество посещений для каждого пользователя +let visitsCountMap = new Map(); + +// объект john - это ключ для значения в объекте Map +visitsCountMap.set(john, 123); + +alert(visitsCountMap.get(john)); // 123 +``` + +Использование объектов в качестве ключей - одна из наиболее заметных и важных функций `Map`. Это то что невозможно для `Object`. Строка в качестве ключа в `Object` - это нормально, но мы не можем использовать другой `Object` в качестве ключа в `Object`. + +Давайте попробуем заменить `Map` на `Object`: + +```js run +let john = { name: "John" }; +let ben = { name: "Ben" }; + +let visitsCountObj = {}; // попробуем использовать объект + +visitsCountObj[ben] = 234; // пробуем использовать объект ben в качестве ключа +visitsCountObj[john] = 123; // пробуем использовать объект john в качестве ключа, при этом объект ben будет замещён + +*!* +// Вот что там было записано! +alert( visitsCountObj["[object Object]"] ); // 123 +*/!* +``` + +Так как `visitsCountObj` является объектом, он преобразует все ключи `Object`, такие как `john` и `ben`, в одну и ту же строку `"[object Object]"`. Это определенно не то, чего мы хотим. + +```smart header="Как объект `Map` сравнивает ключи" +Чтобы сравнивать ключи, объект `Map` использует алгоритм [SameValueZero](https://tc39.github.io/ecma262/#sec-samevaluezero). Это почти такое же сравнение, что и `===`, с той лишь разницей, что `NaN` считается равным `NaN`. Так что `NaN` также может использоваться в качестве ключа. + +Этот алгоритм не может быть заменён или модифицирован. +``` + +````smart header="Цепочка вызовов" +Каждый вызов `map.set` возвращает объект map, так что мы можем объединить вызовы в цепочку: + +```js +map.set("1", "str1") + .set(1, "num1") + .set(true, "bool1"); +``` +```` + +## Перебор Map + +Для перебора коллекции `Map` есть 3 метода: + +- [`map.keys()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/keys) -- возвращает итерируемый объект по ключам, +- [`map.values()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/values) -- возвращает итерируемый объект по значениям, +- [`map.entries()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/entries) -- возвращает итерируемый объект по парам вида `[ключ, значение]`, этот вариант используется по умолчанию в `for..of`. + +Например: + +```js run +let recipeMap = new Map([ + ["огурец", 500], + ["помидор", 350], + ["лук", 50] +]); + +// перебор по ключам (овощи) +for (let vegetable of recipeMap.keys()) { + alert(vegetable); // огурец, помидор, лук +} + +// перебор по значениям (числа) +for (let amount of recipeMap.values()) { + alert(amount); // 500, 350, 50 +} + +// перебор по элементам в формате [ключ, значение] +for (let entry of recipeMap) { // то же самое, что и recipeMap.entries() + alert(entry); // огурец,500 (и так далее) +} +``` + +```smart header="Используется порядок вставки" +В отличие от обычных объектов `Object`, в `Map` перебор происходит в том же порядке, в каком происходило добавление элементов. +``` + +Кроме этого, `Map` имеет встроенный метод `forEach`, схожий со встроенным методом массивов `Array`: + +```js +// выполняем функцию для каждой пары (ключ, значение) +recipeMap.forEach((value, key, map) => { + alert(`${key}: ${value}`); // огурец: 500 и так далее +}); +``` + +## Object.entries: Map из Object + +При создании `Map` мы можем указать массив (или другой итерируемый объект) с парами ключ-значение для инициализации, как здесь: + +```js run +// массив пар [ключ, значение] +let map = new Map([ + ['1', 'str1'], + [1, 'num1'], + [true, 'bool1'] +]); + +alert( map.get('1') ); // str1 +``` + +Если у нас уже есть обычный объект, и мы хотели бы создать `Map` из него, то поможет встроенный метод [Object.entries(obj)](mdn:js/Object/entries), который получает объект и возвращает массив пар ключ-значение для него, как раз в этом формате. + +Так что мы можем создать `Map` из обычного объекта следующим образом: + +```js run +let obj = { + name: "John", + age: 30 +}; + +*!* +let map = new Map(Object.entries(obj)); +*/!* + +alert( map.get('name') ); // John +``` + +Здесь `Object.entries` возвращает массив пар ключ-значение: `[ ["name","John"], ["age", 30] ]`. Это именно то, что нужно для создания `Map`. + +## Object.fromEntries: Object из Map + +Мы только что видели, как создать `Map` из обычного объекта при помощи `Object.entries(obj)`. + +Есть метод `Object.fromEntries`, который делает противоположное: получив массив пар вида `[ключ, значение]`, он создаёт из них объект: + +```js run +let prices = Object.fromEntries([ + ['banana', 1], + ['orange', 2], + ['meat', 4] +]); + +// prices = { banana: 1, orange: 2, meat: 4 } + +alert(prices.orange); // 2 +``` + +Мы можем использовать `Object.fromEntries`, чтобы получить обычный объект из `Map`. + +К примеру, у нас данные в `Map`, но их нужно передать в сторонний код, который ожидает обычный объект. + +Вот как это сделать: + +```js run +let map = new Map(); +map.set('banana', 1); +map.set('orange', 2); +map.set('meat', 4); + +*!* +let obj = Object.fromEntries(map.entries()); // создаём обычный объект (*) +*/!* + +// готово! +// obj = { banana: 1, orange: 2, meat: 4 } + +alert(obj.orange); // 2 +``` + +Вызов `map.entries()` возвращает итерируемый объект пар ключ/значение, как раз в нужном формате для `Object.fromEntries`. + +Мы могли бы написать строку `(*)` ещё короче: + +```js +let obj = Object.fromEntries(map); // убрать .entries() +``` + +Это то же самое, так как `Object.fromEntries` ожидает перебираемый объект в качестве аргумента, не обязательно массив. А перебор `map` как раз возвращает пары ключ/значение, так же, как и `map.entries()`. Так что в итоге у нас будет обычный объект с теми же ключами/значениями, что и в `map`. + +## Set + +Объект [`Set`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set) -- это особый вид коллекции: "множество" значений (без ключей), где каждое значение может появляться только один раз. + +Его основные методы это: + +- [`new Set(iterable)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/Set) -- создаёт `Set`, и если в качестве аргумента был предоставлен итерируемый объект (обычно это массив), то копирует его значения в новый `Set`. +- [`set.add(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/add) -- добавляет значение (если оно уже есть, то ничего не делает), возвращает тот же объект `set`. +- [`set.delete(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/delete) -- удаляет значение, возвращает `true`, если `value` было в множестве на момент вызова, иначе `false`. +- [`set.has(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/has) -- возвращает `true`, если значение присутствует в множестве, иначе `false`. +- [`set.clear()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/clear) -- удаляет все имеющиеся значения. +- [`set.size`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/size) -- возвращает количество элементов в множестве. + +Основная "изюминка" - это то, что при повторных вызовах `set.add()` с одним и тем же значением ничего не происходит, за счёт этого как раз и получается, что каждое значение появляется один раз. + +Например, мы ожидаем посетителей, и нам необходимо составить их список. Но повторные визиты не должны приводить к дубликатам. Каждый посетитель должен появиться в списке только один раз. + +Множество `Set` - как раз то, что нужно для этого: + +```js run +let set = new Set(); + +let john = { name: "John" }; +let pete = { name: "Pete" }; +let mary = { name: "Mary" }; + +// считаем гостей, некоторые приходят несколько раз +set.add(john); +set.add(pete); +set.add(mary); +set.add(john); +set.add(mary); + +// set хранит только 3 уникальных значения +alert(set.size); // 3 + +for (let user of set) { + alert(user.name); // John (потом Pete и Mary) +} +``` + +Альтернативой множеству `Set` может выступать массив для хранения гостей и дополнительный код для проверки уже имеющегося элемента с помощью [arr.find](mdn:js/Array/find). Но в этом случае будет хуже производительность, потому что `arr.find` проходит весь массив для проверки наличия элемента. Множество `Set` лучше оптимизировано для добавлений, оно автоматически проверяет на уникальность. + +## Перебор объекта Set + +Мы можем перебрать содержимое объекта set как с помощью метода `for..of`, так и используя `forEach`: + +```js run +let set = new Set(["апельсин", "яблоко", "банан"]); + +for (let value of set) alert(value); + +// то же самое с forEach: +set.forEach((value, valueAgain, set) => { + alert(value); +}); +``` + +Заметим забавную вещь. Функция в `forEach` у `Set` имеет 3 аргумента: значение `value`, потом *снова то же самое значение* `valueAgain`, и только потом целевой объект. Это действительно так, значение появляется в списке аргументов дважды. + +Это сделано для совместимости с объектом `Map`, в котором колбэк `forEach` имеет 3 аргумента. Выглядит немного странно, но в некоторых случаях может помочь легко заменить `Map` на `Set` и наоборот. + +`Set` имеет те же встроенные методы, что и `Map`: + +- [`set.values()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/values) -- возвращает перебираемый объект для значений, +- [`set.keys()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/keys) -- то же самое, что и `set.values()`, присутствует для обратной совместимости с `Map`, +- [`set.entries()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/entries) -- возвращает перебираемый объект для пар вида `[значение, значение]`, присутствует для обратной совместимости с `Map`. + +## Итого + +[`Map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) -- коллекция пар ключ-значение. + +Методы и свойства: + +- [`new Map([iterable])`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/Map) -- создаёт коллекцию, можно указать перебираемый объект (обычно массив) из пар `[ключ,значение]` для инициализации. +- [`map.set(key, value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/set) -- записывает по ключу `key` значение `value`. +- [`map.get(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/get) -- возвращает значение по ключу или `undefined`, если ключ `key` отсутствует. +- [`map.has(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/has) -- возвращает `true`, если ключ `key` присутствует в коллекции, иначе `false`. +- [`map.delete(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/delete) -- удаляет элемент по ключу `key`. +- [`map.clear()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/clear) -- очищает коллекцию от всех элементов. +- [`map.size`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/size) -- возвращает текущее количество элементов. + +Отличия от обычного объекта `Object`: + +- Что угодно может быть ключом, в том числе и объекты. +- Есть дополнительные методы, свойство `size`. + +[`Set`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set) -- коллекция уникальных значений, так называемое "множество". + +Методы и свойства: + +- [`new Set(iterable)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/Set) -- создаёт `Set`, можно указать перебираемый объект со значениями для инициализации. +- [`set.add(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/add) -- добавляет значение (если оно уже есть, то ничего не делает), возвращает тот же объект `set`. +- [`set.delete(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/delete) -- удаляет значение, возвращает `true` если `value` было в множестве на момент вызова, иначе `false`. +- [`set.has(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/has) -- возвращает `true`, если значение присутствует в множестве, иначе `false`. +- [`set.clear()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/clear) -- удаляет все имеющиеся значения. +- [`set.size`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/size) -- возвращает количество элементов в множестве. + +Перебор `Map` и `Set` всегда осуществляется в порядке добавления элементов, так что нельзя сказать, что это -- неупорядоченные коллекции, но поменять порядок элементов или получить элемент напрямую по его номеру нельзя. diff --git a/1-js/05-data-types/08-keys-values-entries/01-sum-salaries/solution.md b/1-js/05-data-types/08-keys-values-entries/01-sum-salaries/solution.md deleted file mode 100644 index 27a7b418ab..0000000000 --- a/1-js/05-data-types/08-keys-values-entries/01-sum-salaries/solution.md +++ /dev/null @@ -1,29 +0,0 @@ -```js run demo -function sumSalaries(salaries) { - - let sum = 0; - for (let salary of Object.values(salaries)) { - sum += salary; - } - - return sum; // 650 -} - -let salaries = { - "John": 100, - "Pete": 300, - "Mary": 250 -}; - -alert( sumSalaries(salaries) ); // 650 -``` -Or, optionally, we could also get the sum using `Object.values` and `reduce`: - -```js -// reduce loops over array of salaries, -// adding them up -// and returns the result -function sumSalaries(salaries) { - return Object.values(salaries).reduce((a, b) => a + b, 0) // 650 -} -``` diff --git a/1-js/05-data-types/08-keys-values-entries/01-sum-salaries/task.md b/1-js/05-data-types/08-keys-values-entries/01-sum-salaries/task.md deleted file mode 100644 index 211357d03a..0000000000 --- a/1-js/05-data-types/08-keys-values-entries/01-sum-salaries/task.md +++ /dev/null @@ -1,24 +0,0 @@ -importance: 5 - ---- - -# Sum the properties - -There is a `salaries` object with arbitrary number of salaries. - -Write the function `sumSalaries(salaries)` that returns the sum of all salaries using `Object.values` and the `for..of` loop. - -If `salaries` is empty, then the result must be `0`. - -For instance: - -```js -let salaries = { - "John": 100, - "Pete": 300, - "Mary": 250 -}; - -alert( sumSalaries(salaries) ); // 650 -``` - diff --git a/1-js/05-data-types/08-keys-values-entries/02-count-properties/task.md b/1-js/05-data-types/08-keys-values-entries/02-count-properties/task.md deleted file mode 100644 index d7aebb1faa..0000000000 --- a/1-js/05-data-types/08-keys-values-entries/02-count-properties/task.md +++ /dev/null @@ -1,21 +0,0 @@ -importance: 5 - ---- - -# Count properties - -Write a function `count(obj)` that returns the number of properties in the object: - -```js -let user = { - name: 'John', - age: 30 -}; - -alert( count(user) ); // 2 -``` - -Try to make the code as short as possible. - -P.S. Ignore symbolic properties, count only "regular" ones. - diff --git a/1-js/05-data-types/08-keys-values-entries/article.md b/1-js/05-data-types/08-keys-values-entries/article.md deleted file mode 100644 index 66ca3ca927..0000000000 --- a/1-js/05-data-types/08-keys-values-entries/article.md +++ /dev/null @@ -1,70 +0,0 @@ - -# Object.keys, values, entries - -Let's step away from the individual data structures and talk about the iterations over them. - -In the previous chapter we saw methods `map.keys()`, `map.values()`, `map.entries()`. - -These methods are generic, there is a common agreement to use them for data structures. If we ever create a data structure of our own, we should implement them too. - -They are supported for: - -- `Map` -- `Set` -- `Array` (except `arr.values()`) - -Plain objects also support similar methods, but the syntax is a bit different. - -## Object.keys, values, entries - -For plain objects, the following methods are available: - -- [Object.keys(obj)](mdn:js/Object/keys) -- returns an array of keys. -- [Object.values(obj)](mdn:js/Object/values) -- returns an array of values. -- [Object.entries(obj)](mdn:js/Object/entries) -- returns an array of `[key, value]` pairs. - -...But please note the distinctions (compared to map for example): - -| | Map | Object | -|-------------|------------------|--------------| -| Call syntax | `map.keys()` | `Object.keys(obj)`, but not `obj.keys()` | -| Returns | iterable | "real" Array | - -The first difference is that we have to call `Object.keys(obj)`, and not `obj.keys()`. - -Why so? The main reason is flexibility. Remember, objects are a base of all complex structures in JavaScript. So we may have an object of our own like `order` that implements its own `order.values()` method. And we still can call `Object.values(order)` on it. - -The second difference is that `Object.*` methods return "real" array objects, not just an iterable. That's mainly for historical reasons. - -For instance: - -```js -let user = { - name: "John", - age: 30 -}; -``` - -- `Object.keys(user) = ["name", "age"]` -- `Object.values(user) = ["John", 30]` -- `Object.entries(user) = [ ["name","John"], ["age",30] ]` - -Here's an example of using `Object.values` to loop over property values: - -```js run -let user = { - name: "John", - age: 30 -}; - -// loop over values -for (let value of Object.values(user)) { - alert(value); // John, then 30 -} -``` - -## Object.keys/values/entries ignore symbolic properties - -Just like a `for..in` loop, these methods ignore properties that use `Symbol(...)` as keys. - -Usually that's convenient. But if we want symbolic keys too, then there's a separate method [Object.getOwnPropertySymbols](mdn:js/Object/getOwnPropertySymbols) that returns an array of only symbolic keys. Also, the method [Reflect.ownKeys(obj)](mdn:js/Reflect/ownKeys) returns *all* keys. diff --git a/1-js/05-data-types/08-weakmap-weakset/01-recipients-read/solution.md b/1-js/05-data-types/08-weakmap-weakset/01-recipients-read/solution.md new file mode 100644 index 0000000000..8032287a7d --- /dev/null +++ b/1-js/05-data-types/08-weakmap-weakset/01-recipients-read/solution.md @@ -0,0 +1,43 @@ +Можно хранить прочитанные сообщения в `WeakSet`: + +```js +let messages = [ + {text: "Hello", from: "John"}, + {text: "How goes?", from: "John"}, + {text: "See you soon", from: "Alice"} +]; + +let readMessages = new WeakSet(); + +// Два сообщения были прочитаны +readMessages.add(messages[0]); +readMessages.add(messages[1]); +// readMessages содержит 2 элемента + +// ...давайте снова прочитаем первое сообщение! +readMessages.add(messages[0]); +// readMessages до сих пор содержит 2 элемента + +// Вопрос: было ли сообщение message[0] прочитано? +alert("Read message 0: " + readMessages.has(messages[0])); // true + +messages.shift(); +// теперь readMessages содержит 1 элемент (хотя технически память может быть очищена позже) +``` + +`WeakSet` позволяет хранить набор сообщений и легко проверять наличие сообщения в нём. + +Он очищается автоматически. Минус в том, что мы не можем перебрать его содержимое, не можем получить "все прочитанные сообщения" напрямую. Но мы можем сделать это, перебирая все сообщения и фильтруя те, которые находятся в `WeakSet`. + +Альтернативным решением может быть добавление свойства вида `message.isRead=true` к сообщению после его прочтения. Так как сообщения принадлежат чужому коду, то это не очень хорошо, но если использовать свойство-символ, то вероятность конфликтов будет небольшой. + +Например: +```js +// символьное свойство вместо имени, которое известно только нашему коду +let isRead = Symbol("isRead"); +messages[0][isRead] = true; +``` + +Теперь чужой код вряд ли увидит наше дополнительное свойство. + +Хотя символы и позволяют уменьшить вероятность проблем, использование здесь `WeakSet` лучше с архитектурной точки зрения. diff --git a/1-js/05-data-types/08-weakmap-weakset/01-recipients-read/task.md b/1-js/05-data-types/08-weakmap-weakset/01-recipients-read/task.md new file mode 100644 index 0000000000..215aa27cfd --- /dev/null +++ b/1-js/05-data-types/08-weakmap-weakset/01-recipients-read/task.md @@ -0,0 +1,23 @@ +importance: 5 + +--- + +# Хранение отметок "не прочитано" + +Есть массив сообщений: + +```js +let messages = [ + {text: "Hello", from: "John"}, + {text: "How goes?", from: "John"}, + {text: "See you soon", from: "Alice"} +]; +``` + +У вас есть к ним доступ, но управление этим массивом происходит где-то ещё. Добавляются новые сообщения и удаляются старые, и вы не знаете в какой момент это может произойти. + +Имея такую вводную информацию, решите, какую структуру данных вы могли бы использовать для ответа на вопрос "было ли сообщение прочитано?". Структура должна быть подходящей, чтобы можно было однозначно сказать, было ли прочитано это сообщение для каждого объекта сообщения. + +P.S. Когда сообщение удаляется из массива `messages`, оно должно также исчезать из структуры данных. + +P.P.S. Нам не следует модифицировать сами объекты сообщений, добавлять туда свойства. Если сообщения принадлежат какому-то другому коду, то это может привести к плохим последствиям. diff --git a/1-js/05-data-types/08-weakmap-weakset/02-recipients-when-read/solution.md b/1-js/05-data-types/08-weakmap-weakset/02-recipients-when-read/solution.md new file mode 100644 index 0000000000..d166c8db30 --- /dev/null +++ b/1-js/05-data-types/08-weakmap-weakset/02-recipients-when-read/solution.md @@ -0,0 +1,15 @@ + +Для хранения даты мы можем использовать `WeakMap`: + +```js +let messages = [ + {text: "Hello", from: "John"}, + {text: "How goes?", from: "John"}, + {text: "See you soon", from: "Alice"} +]; + +let readMap = new WeakMap(); + +readMap.set(messages[0], new Date(2017, 1, 1)); +// Объект Date мы рассмотрим позднее +``` diff --git a/1-js/05-data-types/08-weakmap-weakset/02-recipients-when-read/task.md b/1-js/05-data-types/08-weakmap-weakset/02-recipients-when-read/task.md new file mode 100644 index 0000000000..f4ea1dac92 --- /dev/null +++ b/1-js/05-data-types/08-weakmap-weakset/02-recipients-when-read/task.md @@ -0,0 +1,21 @@ +importance: 5 + +--- + +# Хранение времени прочтения + +Есть массив сообщений такой же, как и в [предыдущем задании](info:task/recipients-read). + +```js +let messages = [ + { text: "Hello", from: "John" }, + { text: "How goes?", from: "John" }, + { text: "See you soon", from: "Alice" } +]; +``` + +Теперь вопрос стоит так: какую структуру данных вы бы предложили использовать для хранения информации о том, когда сообщение было прочитано? + +В предыдущем задании нам нужно было сохранить только факт прочтения "да или нет". Теперь же нам нужно сохранить дату, и она должна исчезнуть из памяти при удалении "сборщиком мусора" сообщения. + +P.S. Даты в JavaScript можно хранить как объекты встроенного класса `Date`, которые мы разберём позднее. diff --git a/1-js/05-data-types/08-weakmap-weakset/article.md b/1-js/05-data-types/08-weakmap-weakset/article.md new file mode 100644 index 0000000000..4c7f717587 --- /dev/null +++ b/1-js/05-data-types/08-weakmap-weakset/article.md @@ -0,0 +1,290 @@ + +# WeakMap и WeakSet + +Как мы знаем из главы , движок JavaScript хранит значения в памяти до тех пор, пока они достижимы (то есть, эти значения могут быть использованы). + +Например: + +```js +let john = { name: "John" }; + +// объект доступен, переменная john — это ссылка на него + +// перепишем ссылку +john = null; + +*!* +// объект будет удалён из памяти +*/!* +``` + +Обычно свойства объекта, элементы массива или другой структуры данных считаются достижимыми и сохраняются в памяти до тех пор, пока эта структура данных содержится в памяти. + +Например, если мы поместим объект в массив, то до тех пор, пока массив существует, объект также будет существовать в памяти, несмотря на то, что других ссылок на него нет. + +Например: + +```js +let john = { name: "John" }; + +let array = [ john ]; + +john = null; // перезаписываем ссылку на объект + +*!* +// объект john хранится в массиве, поэтому он не будет удалён сборщиком мусора +// мы можем взять его значение как array[0] +*/!* +``` + +Аналогично, если мы используем объект как ключ в `Map`, то до тех пор, пока существует `Map`, также будет существовать и этот объект. Он занимает место в памяти и не может быть удалён сборщиком мусора. + +Например: + +```js +let john = { name: "John" }; + +let map = new Map(); +map.set(john, "..."); + +john = null; // перезаписываем ссылку на объект + +*!* +// объект john сохранён внутри объекта `Map`, +// он доступен через map.keys() +*/!* +``` + +`WeakMap` - принципиально другая структура в этом аспекте. Она не предотвращает удаление объектов сборщиком мусора, когда эти объекты выступают в качестве ключей. + +Давайте посмотрим, что это означает, на примерах. + +## WeakMap + +Первое его отличие от `Map` в том, что ключи в `WeakMap` должны быть объектами, а не примитивными значениями: + +```js run +let weakMap = new WeakMap(); + +let obj = {}; + +weakMap.set(obj, "ok"); // работает (объект в качестве ключа) + +*!* +// нельзя использовать строку в качестве ключа +weakMap.set("test", "Whoops"); // Ошибка, потому что "test" не объект +*/!* +``` + +Теперь, если мы используем объект в качестве ключа и если больше нет ссылок на этот объект, то он будет удалён из памяти (и из объекта `WeakMap`) автоматически. + +```js +let john = { name: "John" }; + +let weakMap = new WeakMap(); +weakMap.set(john, "..."); + +john = null; // перезаписываем ссылку на объект + +// объект john удалён из памяти! +``` + +Сравните это поведение с поведением обычного `Map`, пример которого был приведён ранее. Теперь `john` существует только как ключ в `WeakMap` и может быть удалён оттуда автоматически. + +`WeakMap` не поддерживает перебор и методы `keys()`, `values()`, `entries()`, так что нет способа взять все ключи или значения из неё. + +В `WeakMap` присутствуют только следующие методы: + +- `weakMap.get(key)` +- `weakMap.set(key, value)` +- `weakMap.delete(key)` +- `weakMap.has(key)` + +К чему такие ограничения? Из-за особенностей технической реализации. Если объект станет недостижим (как объект `john` в примере выше), то он будет автоматически удалён сборщиком мусора. Но нет информации, *в какой момент произойдёт эта очистка*. + +Решение о том, когда делать сборку мусора, принимает движок JavaScript. Он может посчитать необходимым как удалить объект прямо сейчас, так и отложить эту операцию, чтобы удалить большее количество объектов за раз позже. Так что технически количество элементов в коллекции `WeakMap` неизвестно. Движок может произвести очистку сразу или потом, или сделать это частично. По этой причине методы для доступа ко всем сразу ключам/значениям недоступны. + +Но для чего же нам нужна такая структура данных? + +## Пример: дополнительные данные + +В основном, `WeakMap` используется в качестве *дополнительного хранилища данных*. + +Если мы работаем с объектом, который "принадлежит" другому коду, может быть даже сторонней библиотеке, и хотим сохранить у себя какие-то данные для него, которые должны существовать лишь пока существует этот объект, то `WeakMap` - как раз то, что нужно. + +Мы кладём эти данные в `WeakMap`, используя объект как ключ, и когда сборщик мусора удалит объекты из памяти, ассоциированные с ними данные тоже автоматически исчезнут. + +```js +weakMap.set(john, "секретные документы"); +// если john умрёт, "секретные документы" будут автоматически уничтожены +``` + +Давайте рассмотрим один пример. + +Предположим, у нас есть код, который ведёт учёт посещений для пользователей. Информация хранится в коллекции `Map`: объект, представляющий пользователя, является ключом, а количество визитов -- значением. Когда пользователь нас покидает (его объект удаляется сборщиком мусора), то больше нет смысла хранить соответствующий счётчик посещений. + +Вот пример реализации счётчика посещений с использованием `Map`: + +```js +// 📁 visitsCount.js +let visitsCountMap = new Map(); // map: пользователь => число визитов + +// увеличиваем счётчик +function countUser(user) { + let count = visitsCountMap.get(user) || 0; + visitsCountMap.set(user, count + 1); +} +``` + +А вот другая часть кода, возможно, в другом файле, которая использует `countUser`: + +```js +// 📁 main.js +let john = { name: "John" }; + +countUser(john); // ведём подсчёт посещений + +// пользователь покинул нас +john = null; +``` + +Теперь объект `john` должен быть удалён сборщиком мусора, но он продолжает оставаться в памяти, так как является ключом в `visitsCountMap`. + +Нам нужно очищать `visitsCountMap` при удалении объекта пользователя, иначе коллекция будет бесконечно расти. Подобная очистка может быть неудобна в реализации при сложной архитектуре приложения. + +Проблемы можно избежать, если использовать `WeakMap`: + +```js +// 📁 visitsCount.js +let visitsCountMap = new WeakMap(); // map: пользователь => число визитов + +// увеличиваем счётчик +function countUser(user) { + let count = visitsCountMap.get(user) || 0; + visitsCountMap.set(user, count + 1); +} +``` + +Теперь нет необходимости вручную очищать `visitsCountMap`. После того, как объект `john` стал недостижим другими способами, кроме как через `WeakMap`, он удаляется из памяти вместе с информацией по такому ключу из `WeakMap`. + +## Применение для кеширования + +Другая частая сфера применения -- это кеширование, когда результат вызова функции должен где-то запоминаться ("кешироваться") для того, чтобы дальнейшие её вызовы на том же объекте могли просто брать уже готовый результат, повторно используя его. + +Для хранения результатов мы можем использовать `Map`, вот так: + +```js run +// 📁 cache.js +let cache = new Map(); + +// вычисляем и запоминаем результат +function process(obj) { + if (!cache.has(obj)) { + let result = /* тут какие-то вычисления результата для объекта */ obj; + + cache.set(obj, result); + } + + return cache.get(obj); +} + +*!* +// Теперь используем process() в другом файле: +*/!* + +// 📁 main.js +let obj = {/* допустим, у нас есть какой-то объект */}; + +let result1 = process(obj); // вычислен результат + +// ...позже, из другого места в коде... +let result2 = process(obj); // ранее вычисленный результат взят из кеша + +// ...позже, когда объект больше не нужен: +obj = null; + +alert(cache.size); // 1 (Упс! Объект всё ещё в кеше, занимает память!) +``` + +Многократные вызовы `process(obj)` с тем же самым объектом в качестве аргумента ведут к тому, что результат вычисляется только в первый раз, а затем последующие вызовы берут его из кеша. Недостатком является то, что необходимо вручную очищать `cache` от ставших ненужными объектов. + +Но если мы будем использовать `WeakMap` вместо `Map`, то эта проблема исчезнет: закешированные результаты будут автоматически удалены из памяти сборщиком мусора. + +```js run +// 📁 cache.js +*!* +let cache = new WeakMap(); +*/!* + +// вычисляем и запоминаем результат +function process(obj) { + if (!cache.has(obj)) { + let result = /* вычисляем результат для объекта */ obj; + + cache.set(obj, result); + } + + return cache.get(obj); +} + +// 📁 main.js +let obj = {/* какой-то объект */}; + +let result1 = process(obj); +let result2 = process(obj); + +// ...позже, когда объект больше не нужен: +obj = null; + +// Нет возможности получить cache.size, так как это WeakMap, +// но он равен 0 или скоро будет равен 0 +// Когда сборщик мусора удаляет obj, связанные с ним данные из кеша тоже удаляются +``` + +## WeakSet + +Коллекция `WeakSet` ведёт себя похоже: + +- Она аналогична `Set`, но мы можем добавлять в `WeakSet` только объекты (не примитивные значения). +- Объект присутствует в множестве только до тех пор, пока доступен где-то ещё. +- Как и `Set`, она поддерживает `add`, `has` и `delete`, но не `size`, `keys()` и не является перебираемой. + +Будучи "слабой" версией оригинальной структуры данных, она тоже служит в качестве дополнительного хранилища. Но не для произвольных данных, а скорее для значений типа "да/нет". Присутствие во множестве `WeakSet` может что-то сказать нам об объекте. + +Например, мы можем добавлять пользователей в `WeakSet` для учёта тех, кто посещал наш сайт: + +```js run +let visitedSet = new WeakSet(); + +let john = { name: "John" }; +let pete = { name: "Pete" }; +let mary = { name: "Mary" }; + +visitedSet.add(john); // John заходил к нам +visitedSet.add(pete); // потом Pete +visitedSet.add(john); // John снова + +// visitedSet сейчас содержит двух пользователей + +// проверим, заходил ли John? +alert(visitedSet.has(john)); // true + +// проверим, заходила ли Mary? +alert(visitedSet.has(mary)); // false + +john = null; + +// структура данных visitedSet будет очищена автоматически (объект john будет удалён из visitedSet) +``` + +Наиболее значительным ограничением `WeakMap` и `WeakSet` является то, что их нельзя перебрать или взять всё содержимое. Это может доставлять неудобства, но не мешает `WeakMap/WeakSet` выполнять их главную задачу -- быть дополнительным хранилищем данных для объектов, управляемых из каких-то других мест в коде. + +## Итого + +`WeakMap` -- это `Map`-подобная коллекция, позволяющая использовать в качестве ключей только объекты, и автоматически удаляющая их вместе с соответствующими значениями, как только они становятся недостижимыми иными путями. + +`WeakSet` -- это `Set`-подобная коллекция, которая хранит только объекты и удаляет их, как только они становятся недостижимыми иными путями. + +Обе этих структуры данных не поддерживают методы и свойства, работающие со всем содержимым сразу или возвращающие информацию о размере коллекции. Возможны только операции на отдельном элементе коллекции. + +`WeakMap` и `WeakSet` используются как вспомогательные структуры данных в дополнение к "основному" месту хранения объекта. Если объект удаляется из основного хранилища и нигде не используется, кроме как в качестве ключа в `WeakMap` или в `WeakSet`, то он будет удалён автоматически. diff --git a/1-js/05-data-types/09-destructuring-assignment/1-destruct-user/task.md b/1-js/05-data-types/09-destructuring-assignment/1-destruct-user/task.md deleted file mode 100644 index b2213323ac..0000000000 --- a/1-js/05-data-types/09-destructuring-assignment/1-destruct-user/task.md +++ /dev/null @@ -1,33 +0,0 @@ -importance: 5 - ---- - -# Destructuring assignment - -We have an object: - -```js -let user = { - name: "John", - years: 30 -}; -``` - -Write the destructuring assignment that reads: - -- `name` property into the variable `name`. -- `years` property into the variable `age`. -- `isAdmin` property into the variable `isAdmin` (false if absent) - -The values after the assignment should be: - -```js -let user = { name: "John", years: 30 }; - -// your code to the left side: -// ... = user - -alert( name ); // John -alert( age ); // 30 -alert( isAdmin ); // false -``` diff --git a/1-js/05-data-types/09-destructuring-assignment/6-max-salary/task.md b/1-js/05-data-types/09-destructuring-assignment/6-max-salary/task.md deleted file mode 100644 index 9f33de0897..0000000000 --- a/1-js/05-data-types/09-destructuring-assignment/6-max-salary/task.md +++ /dev/null @@ -1,22 +0,0 @@ -importance: 5 - ---- - -# The maximal salary - -There is a `salaries` object: - -```js -let salaries = { - "John": 100, - "Pete": 300, - "Mary": 250 -}; -``` - -Create the function `topSalary(salaries)` that returns the name of the top-paid person. - -- If `salaries` is empty, it should return `null`. -- If there are multiple top-paid persons, return any of them. - -P.S. Use `Object.entries` and destructuring to iterate over key/value pairs. diff --git a/1-js/05-data-types/09-destructuring-assignment/article.md b/1-js/05-data-types/09-destructuring-assignment/article.md deleted file mode 100644 index 7c60b274a4..0000000000 --- a/1-js/05-data-types/09-destructuring-assignment/article.md +++ /dev/null @@ -1,519 +0,0 @@ -# Destructuring assignment - -The two most used data structures in JavaScript are `Object` and `Array`. - -Objects allow us to pack many pieces of information into a single entity and arrays allow us to store ordered collections. So we can make an object or an array and handle it as a single entity, or maybe pass it to a function call. - -*Destructuring assignment* is a special syntax that allows us to "unpack" arrays or objects into a bunch of variables, as sometimes they are more convenient. Destructuring also works great with complex functions that have a lot of parameters, default values, and soon we'll see how these are handled too. - -## Array destructuring - -An example of how the array is destructured into variables: - -```js -// we have an array with the name and surname -let arr = ["Ilya", "Kantor"] - -*!* -// destructuring assignment -let [firstName, surname] = arr; -*/!* - -alert(firstName); // Ilya -alert(surname); // Kantor -``` - -Now we can work with variables instead of array members. - -It looks great when combined with `split` or other array-returning methods: - -```js -let [firstName, surname] = "Ilya Kantor".split(' '); -``` - -````smart header="\"Destructuring\" does not mean \"destructive\"." -It's called "destructuring assignment," because it "destructurizes" by copying items into variables. But the array itself is not modified. - -It's just a shorter way to write: -```js -// let [firstName, surname] = arr; -let firstName = arr[0]; -let surname = arr[1]; -``` -```` - -````smart header="Ignore elements using commas" -Unwanted elements of the array can also be thrown away via an extra comma: - -```js run -*!* -// second element is not needed -let [firstName, , title] = ["Julius", "Caesar", "Consul", "of the Roman Republic"]; -*/!* - -alert( title ); // Consul -``` - -In the code above, the second element of the array is skipped, the third one is assigned to `title`, and the rest of the array is also skipped. -```` - -````smart header="Works with any iterable on the right-side" - -...Actually, we can use it with any iterable, not only arrays: - -```js -let [a, b, c] = "abc"; // ["a", "b", "c"] -let [one, two, three] = new Set([1, 2, 3]); -``` - -```` - - -````smart header="Assign to anything at the left-side" - -We can use any "assignables" at the left side. - -For instance, an object property: -```js run -let user = {}; -[user.name, user.surname] = "Ilya Kantor".split(' '); - -alert(user.name); // Ilya -``` - -```` - -````smart header="Looping with .entries()" - -In the previous chapter we saw the [Object.entries(obj)](mdn:js/Object/entries) method. - -We can use it with destructuring to loop over keys-and-values of an object: - -```js run -let user = { - name: "John", - age: 30 -}; - -// loop over keys-and-values -*!* -for (let [key, value] of Object.entries(user)) { -*/!* - alert(`${key}:${value}`); // name:John, then age:30 -} -``` - -...And the same for a map: - -```js run -let user = new Map(); -user.set("name", "John"); -user.set("age", "30"); - -*!* -for (let [key, value] of user.entries()) { -*/!* - alert(`${key}:${value}`); // name:John, then age:30 -} -``` -```` -### The rest '...' - -If we want not just to get first values, but also to gather all that follows -- we can add one more parameter that gets "the rest" using three dots `"..."`: - -```js run -let [name1, name2, *!*...rest*/!*] = ["Julius", "Caesar", *!*"Consul", "of the Roman Republic"*/!*]; - -alert(name1); // Julius -alert(name2); // Caesar - -*!* -// Note that type of `rest` is Array. -alert(rest[0]); // Consul -alert(rest[1]); // of the Roman Republic -alert(rest.length); // 2 -*/!* -``` - -The value of `rest` is the array of the remaining array elements. We can use any other variable name in place of `rest`, just make sure it has three dots before it and goes last in the destructuring assignment. - -### Default values - -If there are fewer values in the array than variables in the assignment, there will be no error. Absent values are considered undefined: - -```js run -*!* -let [firstName, surname] = []; -*/!* - -alert(firstName); // undefined -alert(surname); // undefined -``` - -If we want a "default" value to replace the missing one, we can provide it using `=`: - -```js run -*!* -// default values -let [name = "Guest", surname = "Anonymous"] = ["Julius"]; -*/!* - -alert(name); // Julius (from array) -alert(surname); // Anonymous (default used) -``` - -Default values can be more complex expressions or even function calls. They are evaluated only if the value is not provided. - -For instance, here we use the `prompt` function for two defaults. But it will run only for the missing one: - -```js run -// runs only prompt for surname -let [name = prompt('name?'), surname = prompt('surname?')] = ["Julius"]; - -alert(name); // Julius (from array) -alert(surname); // whatever prompt gets -``` - - - -## Object destructuring - -The destructuring assignment also works with objects. - -The basic syntax is: - -```js -let {var1, var2} = {var1:…, var2…} -``` - -We have an existing object at the right side, that we want to split into variables. The left side contains a "pattern" for corresponding properties. In the simple case, that's a list of variable names in `{...}`. - -For instance: - -```js run -let options = { - title: "Menu", - width: 100, - height: 200 -}; - -*!* -let {title, width, height} = options; -*/!* - -alert(title); // Menu -alert(width); // 100 -alert(height); // 200 -``` - -Properties `options.title`, `options.width` and `options.height` are assigned to the corresponding variables. The order does not matter. This works too: - -```js -// changed the order of properties in let {...} -let {height, width, title} = { title: "Menu", height: 200, width: 100 } -``` - -The pattern on the left side may be more complex and specify the mapping between properties and variables. - -If we want to assign a property to a variable with another name, for instance, `options.width` to go into the variable named `w`, then we can set it using a colon: - -```js run -let options = { - title: "Menu", - width: 100, - height: 200 -}; - -*!* -// { sourceProperty: targetVariable } -let {width: w, height: h, title} = options; -*/!* - -// width -> w -// height -> h -// title -> title - -alert(title); // Menu -alert(w); // 100 -alert(h); // 200 -``` - -The colon shows "what : goes where". In the example above the property `width` goes to `w`, property `height` goes to `h`, and `title` is assigned to the same name. - -For potentially missing properties we can set default values using `"="`, like this: - -```js run -let options = { - title: "Menu" -}; - -*!* -let {width = 100, height = 200, title} = options; -*/!* - -alert(title); // Menu -alert(width); // 100 -alert(height); // 200 -``` - -Just like with arrays or function parameters, default values can be any expressions or even function calls. They will be evaluated if the value is not provided. - -The code below asks for width, but not the title. - -```js run -let options = { - title: "Menu" -}; - -*!* -let {width = prompt("width?"), title = prompt("title?")} = options; -*/!* - -alert(title); // Menu -alert(width); // (whatever you the result of prompt is) -``` - -We also can combine both the colon and equality: - -```js run -let options = { - title: "Menu" -}; - -*!* -let {width: w = 100, height: h = 200, title} = options; -*/!* - -alert(title); // Menu -alert(w); // 100 -alert(h); // 200 -``` - -### The rest operator - -What if the object has more properties than we have variables? Can we take some and then assign the "rest" somewhere? - -The specification for using the rest operator (three dots) here is almost in the standard, but most browsers do not support it yet. - -It looks like this: - -```js run -let options = { - title: "Menu", - height: 200, - width: 100 -}; - -*!* -let {title, ...rest} = options; -*/!* - -// now title="Menu", rest={height: 200, width: 100} -alert(rest.height); // 200 -alert(rest.width); // 100 -``` - - - -````smart header="Gotcha without `let`" -In the examples above variables were declared right before the assignment: `let {…} = {…}`. Of course, we could use existing variables too. But there's a catch. - -This won't work: -```js run -let title, width, height; - -// error in this line -{title, width, height} = {title: "Menu", width: 200, height: 100}; -``` - -The problem is that JavaScript treats `{...}` in the main code flow (not inside another expression) as a code block. Such code blocks can be used to group statements, like this: - -```js run -{ - // a code block - let message = "Hello"; - // ... - alert( message ); -} -``` - -To show JavaScript that it's not a code block, we can wrap the whole assignment in parentheses `(...)`: - -```js run -let title, width, height; - -// okay now -*!*(*/!*{title, width, height} = {title: "Menu", width: 200, height: 100}*!*)*/!*; - -alert( title ); // Menu -``` - -```` - -## Nested destructuring - -If an object or an array contain other objects and arrays, we can use more complex left-side patterns to extract deeper portions. - -In the code below `options` has another object in the property `size` and an array in the property `items`. The pattern at the left side of the assignment has the same structure: - -```js run -let options = { - size: { - width: 100, - height: 200 - }, - items: ["Cake", "Donut"], - extra: true // something extra that we will not destruct -}; - -// destructuring assignment on multiple lines for clarity -let { - size: { // put size here - width, - height - }, - items: [item1, item2], // assign items here - title = "Menu" // not present in the object (default value is used) -} = options; - -alert(title); // Menu -alert(width); // 100 -alert(height); // 200 -alert(item1); // Cake -alert(item2); // Donut -``` - -The whole `options` object except `extra` that was not mentioned, is assigned to corresponding variables. - -Note that `size` and `items` itself is not destructured. - -![](destructuring-complex.png) - -Finally, we have `width`, `height`, `item1`, `item2` and `title` from the default value. - -That often happens with destructuring assignments. We have a complex object with many properties and want to extract only what we need. - -Even here it happens: -```js -// take size as a whole into a variable, ignore the rest -let { size } = options; -``` - -## Smart function parameters - -There are times when a function may have many parameters, most of which are optional. That's especially true for user interfaces. Imagine a function that creates a menu. It may have a width, a height, a title, items list and so on. - -Here's a bad way to write such function: - -```js -function showMenu(title = "Untitled", width = 200, height = 100, items = []) { - // ... -} -``` - -In real-life, the problem is how to remember the order of arguments. Usually IDEs try to help us, especially if the code is well-documented, but still... Another problem is how to call a function when most parameters are ok by default. - -Like this? - -```js -showMenu("My Menu", undefined, undefined, ["Item1", "Item2"]) -``` - -That's ugly. And becomes unreadable when we deal with more parameters. - -Destructuring comes to the rescue! - -We can pass parameters as an object, and the function immediately destructurizes them into variables: - -```js run -// we pass object to function -let options = { - title: "My menu", - items: ["Item1", "Item2"] -}; - -// ...and it immediately expands it to variables -function showMenu(*!*{title = "Untitled", width = 200, height = 100, items = []}*/!*) { - // title, items – taken from options, - // width, height – defaults used - alert( `${title} ${width} ${height}` ); // My Menu 200 100 - alert( items ); // Item1, Item2 -} - -showMenu(options); -``` - -We can also use more complex destructuring with nested objects and colon mappings: - -```js run -let options = { - title: "My menu", - items: ["Item1", "Item2"] -}; - -*!* -function showMenu({ - title = "Untitled", - width: w = 100, // width goes to w - height: h = 200, // height goes to h - items: [item1, item2] // items first element goes to item1, second to item2 -}) { -*/!* - alert( `${title} ${w} ${h}` ); // My Menu 100 200 - alert( item1 ); // Item1 - alert( item2 ); // Item2 -} - -showMenu(options); -``` - -The syntax is the same as for a destructuring assignment: -```js -function({ - incomingProperty: parameterName = defaultValue - ... -}) -``` - -Please note that such destructuring assumes that `showMenu()` does have an argument. If we want all values by default, then we should specify an empty object: - -```js -showMenu({}); - - -showMenu(); // this would give an error -``` - -We can fix this by making `{}` the default value for the whole destructuring thing: - - -```js run -// simplified parameters a bit for clarity -function showMenu(*!*{ title = "Menu", width = 100, height = 200 } = {}*/!*) { - alert( `${title} ${width} ${height}` ); -} - -showMenu(); // Menu 100 200 -``` - -In the code above, the whole arguments object is `{}` by default, so there's always something to destructurize. - -## Summary - -- Destructuring assignment allows for instantly mapping an object or array onto many variables. -- The object syntax: - ```js - let {prop : varName = default, ...} = object - ``` - - This means that property `prop` should go into the variable `varName` and, if no such property exists, then the `default` value should be used. - -- The array syntax: - - ```js - let [item1 = default, item2, ...rest] = array - ``` - - The first item goes to `item1`; the second goes into `item2`, all the rest makes the array `rest`. - -- For more complex cases, the left side must have the same structure as the right one. diff --git a/1-js/05-data-types/09-destructuring-assignment/destructuring-complex.png b/1-js/05-data-types/09-destructuring-assignment/destructuring-complex.png deleted file mode 100644 index c46bf6e45c..0000000000 Binary files a/1-js/05-data-types/09-destructuring-assignment/destructuring-complex.png and /dev/null differ diff --git a/1-js/05-data-types/09-destructuring-assignment/destructuring-complex@2x.png b/1-js/05-data-types/09-destructuring-assignment/destructuring-complex@2x.png deleted file mode 100644 index 26032a3d62..0000000000 Binary files a/1-js/05-data-types/09-destructuring-assignment/destructuring-complex@2x.png and /dev/null differ diff --git a/1-js/05-data-types/08-keys-values-entries/01-sum-salaries/_js.view/solution.js b/1-js/05-data-types/09-keys-values-entries/01-sum-salaries/_js.view/solution.js similarity index 100% rename from 1-js/05-data-types/08-keys-values-entries/01-sum-salaries/_js.view/solution.js rename to 1-js/05-data-types/09-keys-values-entries/01-sum-salaries/_js.view/solution.js diff --git a/1-js/05-data-types/08-keys-values-entries/01-sum-salaries/_js.view/test.js b/1-js/05-data-types/09-keys-values-entries/01-sum-salaries/_js.view/test.js similarity index 100% rename from 1-js/05-data-types/08-keys-values-entries/01-sum-salaries/_js.view/test.js rename to 1-js/05-data-types/09-keys-values-entries/01-sum-salaries/_js.view/test.js diff --git a/1-js/05-data-types/09-keys-values-entries/01-sum-salaries/solution.md b/1-js/05-data-types/09-keys-values-entries/01-sum-salaries/solution.md new file mode 100644 index 0000000000..4c5c9d6eb9 --- /dev/null +++ b/1-js/05-data-types/09-keys-values-entries/01-sum-salaries/solution.md @@ -0,0 +1,29 @@ +```js run demo +function sumSalaries(salaries) { + + let sum = 0; + for (let salary of Object.values(salaries)) { + sum += salary; + } + + return sum; // 650 +} + +let salaries = { + "John": 100, + "Pete": 300, + "Mary": 250 +}; + +alert( sumSalaries(salaries) ); // 650 +``` +Или, как вариант, мы можем получить сумму, используя методы `Object.values` и `reduce`: + +```js +// reduce перебирает массив значений salaries, +// складывает их +// и возвращает результат +function sumSalaries(salaries) { + return Object.values(salaries).reduce((a, b) => a + b, 0) // 650 +} +``` diff --git a/1-js/05-data-types/09-keys-values-entries/01-sum-salaries/task.md b/1-js/05-data-types/09-keys-values-entries/01-sum-salaries/task.md new file mode 100644 index 0000000000..7246c8225f --- /dev/null +++ b/1-js/05-data-types/09-keys-values-entries/01-sum-salaries/task.md @@ -0,0 +1,24 @@ +importance: 5 + +--- + +# Сумма свойств объекта + +Есть объект `salaries` с произвольным количеством свойств, содержащих заработные платы. + +Напишите функцию `sumSalaries(salaries)`, которая возвращает сумму всех зарплат с помощью метода `Object.values` и цикла `for..of`. + +Если объект `salaries` пуст, то результат должен быть `0`. + +Например: + +```js +let salaries = { + "John": 100, + "Pete": 300, + "Mary": 250 +}; + +alert( sumSalaries(salaries) ); // 650 +``` + diff --git a/1-js/05-data-types/08-keys-values-entries/02-count-properties/_js.view/solution.js b/1-js/05-data-types/09-keys-values-entries/02-count-properties/_js.view/solution.js similarity index 100% rename from 1-js/05-data-types/08-keys-values-entries/02-count-properties/_js.view/solution.js rename to 1-js/05-data-types/09-keys-values-entries/02-count-properties/_js.view/solution.js diff --git a/1-js/05-data-types/08-keys-values-entries/02-count-properties/_js.view/test.js b/1-js/05-data-types/09-keys-values-entries/02-count-properties/_js.view/test.js similarity index 100% rename from 1-js/05-data-types/08-keys-values-entries/02-count-properties/_js.view/test.js rename to 1-js/05-data-types/09-keys-values-entries/02-count-properties/_js.view/test.js diff --git a/1-js/05-data-types/08-keys-values-entries/02-count-properties/solution.md b/1-js/05-data-types/09-keys-values-entries/02-count-properties/solution.md similarity index 100% rename from 1-js/05-data-types/08-keys-values-entries/02-count-properties/solution.md rename to 1-js/05-data-types/09-keys-values-entries/02-count-properties/solution.md diff --git a/1-js/05-data-types/09-keys-values-entries/02-count-properties/task.md b/1-js/05-data-types/09-keys-values-entries/02-count-properties/task.md new file mode 100644 index 0000000000..9a450021fb --- /dev/null +++ b/1-js/05-data-types/09-keys-values-entries/02-count-properties/task.md @@ -0,0 +1,21 @@ +importance: 5 + +--- + +# Подсчёт количества свойств объекта + +Напишите функцию `count(obj)`, которая возвращает количество свойств объекта: + +```js +let user = { + name: 'John', + age: 30 +}; + +alert( count(user) ); // 2 +``` + +Постарайтесь сделать код как можно короче. + +P.S. Игнорируйте символьные свойства, подсчитывайте только "обычные". + diff --git a/1-js/05-data-types/09-keys-values-entries/article.md b/1-js/05-data-types/09-keys-values-entries/article.md new file mode 100644 index 0000000000..fd5d88041b --- /dev/null +++ b/1-js/05-data-types/09-keys-values-entries/article.md @@ -0,0 +1,103 @@ + +# Object.keys, values, entries + +Давайте отойдём от отдельных структур данных и поговорим об их переборе вообще. + +В предыдущей главе мы видели методы `map.keys()`, `map.values()`, `map.entries()`. + +Это универсальные методы, и существует общее соглашение использовать их для структур данных. Если бы мы делали собственную структуру данных, нам также следовало бы их реализовать. + +Методы поддерживаются для структур: + +- `Map` +- `Set` +- `Array` + +Простые объекты также можно перебирать похожими методами, но синтаксис немного отличается. + +## Object.keys, values, entries + +Для простых объектов доступны следующие методы: + +- [Object.keys(obj)](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Object/keys) -- возвращает массив ключей. +- [Object.values(obj)](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Object/values) -- возвращает массив значений. +- [Object.entries(obj)](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Object/entries) -- возвращает массив пар `[ключ, значение]`. + +Обратите внимание на различия (по сравнению с `map`, например): + +| | Map | Object | +|------------------|--------------------|----------------------------------------| +| Синтаксис вызова | `map.keys()` | `Object.keys(obj)`, не `obj.keys()` | +| Возвращает | перебираемый объект | "реальный" массив | + +Первое отличие в том, что мы должны вызвать `Object.keys(obj)`, а не `obj.keys()`. + +Почему так? Основная причина - гибкость. Помните, что объекты являются основой всех сложных структур в JavaScript. У нас может быть объект `data`, который реализует свой собственный метод `data.values()`. И мы всё ещё можем применять к нему стандартный метод `Object.values(data)`. + +Второе отличие в том, что методы вида `Object.*` возвращают "реальные" массивы, а не просто итерируемые объекты. Это в основном по историческим причинам. + +Например: + +```js +let user = { + name: "John", + age: 30 +}; +``` + +- `Object.keys(user) = ["name", "age"]` +- `Object.values(user) = ["John", 30]` +- `Object.entries(user) = [ ["name","John"], ["age",30] ]` + +Вот пример использования `Object.values` ​​для перебора значений свойств в цикле: + +```js run +let user = { + name: "John", + age: 30 +}; + +// перебор значений +for (let value of Object.values(user)) { + alert(value); // John, затем 30 +} +``` + +```warn header="Object.keys/values/entries игнорируют символьные свойства" +Так же, как и цикл `for..in`, эти методы игнорируют свойства, использующие `Symbol(...)` в качестве ключей. + +Обычно это удобно. Но если требуется учитывать и символьные ключи, то для этого существует отдельный метод [Object.getOwnPropertySymbols](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertySymbols), возвращающий массив только символьных ключей. Также, существует метод [Reflect.ownKeys(obj)](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Reflect/ownKeys), который возвращает *все* ключи. +``` + +## Трансформации объекта + +У объектов нет множества методов, которые есть в массивах, например `map`, `filter` и других. + +Если мы хотели бы их применить, то можно использовать `Object.entries` с последующим вызовом `Object.fromEntries`: + +1. Вызов `Object.entries(obj)` возвращает массив пар ключ/значение для `obj`. +2. На нём вызываем методы массива, например, `map`. +3. Используем `Object.fromEntries(array)` на результате, чтобы преобразовать его обратно в объект. + +Например, у нас есть объект с ценами, и мы хотели бы их удвоить: + +```js run +let prices = { + banana: 1, + orange: 2, + meat: 4, +}; + +*!* +let doublePrices = Object.fromEntries( + // преобразовать в массив, затем map, затем fromEntries (обратно в объект) + Object.entries(prices).map(([key, value]) => [key, value * 2]) +); +*/!* + +alert(doublePrices.meat); // 8 +``` + +Это может выглядеть сложным на первый взгляд, но становится лёгким для понимания после нескольких раз использования. + +Можно делать и более сложные "однострочные" преобразования таким путём. Важно только сохранять баланс, чтобы код при этом был достаточно простым для понимания. diff --git a/1-js/05-data-types/10-date/1-new-date/solution.md b/1-js/05-data-types/10-date/1-new-date/solution.md deleted file mode 100644 index eb271a91a8..0000000000 --- a/1-js/05-data-types/10-date/1-new-date/solution.md +++ /dev/null @@ -1,8 +0,0 @@ -The `new Date` constructor uses the local time zone by default. So the only important thing to remember is that months start from zero. - -So February has number 1. - -```js run -let d = new Date(2012, 1, 20, 3, 12); -alert( d ); -``` diff --git a/1-js/05-data-types/10-date/1-new-date/task.md b/1-js/05-data-types/10-date/1-new-date/task.md deleted file mode 100644 index 1b40d5ac04..0000000000 --- a/1-js/05-data-types/10-date/1-new-date/task.md +++ /dev/null @@ -1,9 +0,0 @@ -importance: 5 - ---- - -# Create a date - -Create a `Date` object for the date: Feb 20, 2012, 3:12am. The time zone is local. - -Show it using `alert`. diff --git a/1-js/05-data-types/10-date/2-get-week-day/_js.view/solution.js b/1-js/05-data-types/10-date/2-get-week-day/_js.view/solution.js deleted file mode 100644 index 642c376a94..0000000000 --- a/1-js/05-data-types/10-date/2-get-week-day/_js.view/solution.js +++ /dev/null @@ -1,5 +0,0 @@ -function getWeekDay(date) { - let days = ['SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA']; - - return days[date.getDay()]; -} diff --git a/1-js/05-data-types/10-date/2-get-week-day/_js.view/test.js b/1-js/05-data-types/10-date/2-get-week-day/_js.view/test.js deleted file mode 100644 index 3cdc91830e..0000000000 --- a/1-js/05-data-types/10-date/2-get-week-day/_js.view/test.js +++ /dev/null @@ -1,29 +0,0 @@ -describe("getWeekDay", function() { - it("3 January 2014 - friday", function() { - assert.equal(getWeekDay(new Date(2014, 0, 3)), 'FR'); - }); - - it("4 January 2014 - saturday", function() { - assert.equal(getWeekDay(new Date(2014, 0, 4)), 'SA'); - }); - - it("5 January 2014 - sunday", function() { - assert.equal(getWeekDay(new Date(2014, 0, 5)), 'SU'); - }); - - it("6 January 2014 - monday", function() { - assert.equal(getWeekDay(new Date(2014, 0, 6)), 'MO'); - }); - - it("7 January 2014 - tuesday", function() { - assert.equal(getWeekDay(new Date(2014, 0, 7)), 'TU'); - }); - - it("8 January 2014 - wednesday", function() { - assert.equal(getWeekDay(new Date(2014, 0, 8)), 'WE'); - }); - - it("9 January 2014 - thursday", function() { - assert.equal(getWeekDay(new Date(2014, 0, 9)), 'TH'); - }); -}); diff --git a/1-js/05-data-types/10-date/2-get-week-day/solution.md b/1-js/05-data-types/10-date/2-get-week-day/solution.md deleted file mode 100644 index 58d75c1c31..0000000000 --- a/1-js/05-data-types/10-date/2-get-week-day/solution.md +++ /dev/null @@ -1,14 +0,0 @@ -The method `date.getDay()` returns the number of the weekday, starting from sunday. - -Let's make an array of weekdays, so that we can get the proper day name by its number: - -```js run demo -function getWeekDay(date) { - let days = ['SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA']; - - return days[date.getDay()]; -} - -let date = new Date(2014, 0, 3); // 3 Jan 2014 -alert( getWeekDay(date) ); // FR -``` diff --git a/1-js/05-data-types/10-date/2-get-week-day/task.md b/1-js/05-data-types/10-date/2-get-week-day/task.md deleted file mode 100644 index 5cf31565da..0000000000 --- a/1-js/05-data-types/10-date/2-get-week-day/task.md +++ /dev/null @@ -1,14 +0,0 @@ -importance: 5 - ---- - -# Show a weekday - -Write a function `getWeekDay(date)` to show the weekday in short format: 'MO', 'TU', 'WE', 'TH', 'FR', 'SA', 'SU'. - -For instance: - -```js no-beautify -let date = new Date(2012, 0, 3); // 3 Jan 2012 -alert( getWeekDay(date) ); // should output "TU" -``` diff --git a/1-js/05-data-types/10-date/3-weekday/_js.view/solution.js b/1-js/05-data-types/10-date/3-weekday/_js.view/solution.js deleted file mode 100644 index fb9e3d2a4f..0000000000 --- a/1-js/05-data-types/10-date/3-weekday/_js.view/solution.js +++ /dev/null @@ -1,10 +0,0 @@ -function getLocalDay(date) { - - let day = date.getDay(); - - if (day == 0) { // weekday 0 (sunday) is 7 in european - day = 7; - } - - return day; -} diff --git a/1-js/05-data-types/10-date/3-weekday/_js.view/test.js b/1-js/05-data-types/10-date/3-weekday/_js.view/test.js deleted file mode 100644 index 57032154f0..0000000000 --- a/1-js/05-data-types/10-date/3-weekday/_js.view/test.js +++ /dev/null @@ -1,29 +0,0 @@ -describe("getLocalDay returns the \"european\" weekday", function() { - it("3 January 2014 - friday", function() { - assert.equal(getLocalDay(new Date(2014, 0, 3)), 5); - }); - - it("4 January 2014 - saturday", function() { - assert.equal(getLocalDay(new Date(2014, 0, 4)), 6); - }); - - it("5 January 2014 - sunday", function() { - assert.equal(getLocalDay(new Date(2014, 0, 5)), 7); - }); - - it("6 January 2014 - monday", function() { - assert.equal(getLocalDay(new Date(2014, 0, 6)), 1); - }); - - it("7 January 2014 - tuesday", function() { - assert.equal(getLocalDay(new Date(2014, 0, 7)), 2); - }); - - it("8 January 2014 - wednesday", function() { - assert.equal(getLocalDay(new Date(2014, 0, 8)), 3); - }); - - it("9 January 2014 - thursday", function() { - assert.equal(getLocalDay(new Date(2014, 0, 9)), 4); - }); -}); diff --git a/1-js/05-data-types/10-date/3-weekday/task.md b/1-js/05-data-types/10-date/3-weekday/task.md deleted file mode 100644 index ba62790cf7..0000000000 --- a/1-js/05-data-types/10-date/3-weekday/task.md +++ /dev/null @@ -1,12 +0,0 @@ -importance: 5 - ---- - -# European weekday - -European countries have days of week starting with Monday (number 1), then Tuesday (number 2) and till Sunday (number 7). Write a function `getLocalDay(date)` that returns the "European" day of week for `date`. - -```js no-beautify -let date = new Date(2012, 0, 3); // 3 Jan 2012 -alert( getLocalDay(date) ); // tuesday, should show 2 -``` diff --git a/1-js/05-data-types/10-date/4-get-date-ago/_js.view/test.js b/1-js/05-data-types/10-date/4-get-date-ago/_js.view/test.js deleted file mode 100644 index 255acffe01..0000000000 --- a/1-js/05-data-types/10-date/4-get-date-ago/_js.view/test.js +++ /dev/null @@ -1,27 +0,0 @@ -describe("getDateAgo", function() { - - it("1 day before 02.01.2015 -> day 1", function() { - assert.equal(getDateAgo(new Date(2015, 0, 2), 1), 1); - }); - - - it("2 days before 02.01.2015 -> day 31", function() { - assert.equal(getDateAgo(new Date(2015, 0, 2), 2), 31); - }); - - it("100 days before 02.01.2015 -> day 24", function() { - assert.equal(getDateAgo(new Date(2015, 0, 2), 100), 24); - }); - - it("365 days before 02.01.2015 -> day 2", function() { - assert.equal(getDateAgo(new Date(2015, 0, 2), 365), 2); - }); - - it("does not modify the given date", function() { - let date = new Date(2015, 0, 2); - let dateCopy = new Date(date); - getDateAgo(dateCopy, 100); - assert.equal(date.getTime(), dateCopy.getTime()); - }); - -}); diff --git a/1-js/05-data-types/10-date/4-get-date-ago/solution.md b/1-js/05-data-types/10-date/4-get-date-ago/solution.md deleted file mode 100644 index 5c394c100b..0000000000 --- a/1-js/05-data-types/10-date/4-get-date-ago/solution.md +++ /dev/null @@ -1,27 +0,0 @@ -The idea is simple: to substract given number of days from `date`: - -```js -function getDateAgo(date, days) { - date.setDate(date.getDate() - days); - return date.getDate(); -} -``` - -...But the function should not change `date`. That's an important thing, because the outer code which gives us the date does not expect it to change. - -To implement it let's clone the date, like this: - -```js run demo -function getDateAgo(date, days) { - let dateCopy = new Date(date); - - dateCopy.setDate(date.getDate() - days); - return dateCopy.getDate(); -} - -let date = new Date(2015, 0, 2); - -alert( getDateAgo(date, 1) ); // 1, (1 Jan 2015) -alert( getDateAgo(date, 2) ); // 31, (31 Dec 2014) -alert( getDateAgo(date, 365) ); // 2, (2 Jan 2014) -``` diff --git a/1-js/05-data-types/10-date/4-get-date-ago/task.md b/1-js/05-data-types/10-date/4-get-date-ago/task.md deleted file mode 100644 index 40dcd926dc..0000000000 --- a/1-js/05-data-types/10-date/4-get-date-ago/task.md +++ /dev/null @@ -1,21 +0,0 @@ -importance: 4 - ---- - -# Which day of month was many days ago? - -Create a function `getDateAgo(date, days)` to return the day of month `days` ago from the `date`. - -For instance, if today is 20th, then `getDateAgo(new Date(), 1)` should be 19th and `getDateAgo(new Date(), 2)` should be 18th. - -Should also work over months/years reliably: - -```js -let date = new Date(2015, 0, 2); - -alert( getDateAgo(date, 1) ); // 1, (1 Jan 2015) -alert( getDateAgo(date, 2) ); // 31, (31 Dec 2014) -alert( getDateAgo(date, 365) ); // 2, (2 Jan 2014) -``` - -P.S. The function should not modify the given `date`. diff --git a/1-js/05-data-types/10-date/5-last-day-of-month/_js.view/test.js b/1-js/05-data-types/10-date/5-last-day-of-month/_js.view/test.js deleted file mode 100644 index 4ff3e116a1..0000000000 --- a/1-js/05-data-types/10-date/5-last-day-of-month/_js.view/test.js +++ /dev/null @@ -1,13 +0,0 @@ -describe("getLastDayOfMonth", function() { - it("last day of 01.01.2012 - 31", function() { - assert.equal(getLastDayOfMonth(2012, 0), 31); - }); - - it("last day of 01.02.2012 - 29 (leap year)", function() { - assert.equal(getLastDayOfMonth(2012, 1), 29); - }); - - it("last day of 01.02.2013 - 28", function() { - assert.equal(getLastDayOfMonth(2013, 1), 28); - }); -}); diff --git a/1-js/05-data-types/10-date/5-last-day-of-month/solution.md b/1-js/05-data-types/10-date/5-last-day-of-month/solution.md deleted file mode 100644 index 4f642536e1..0000000000 --- a/1-js/05-data-types/10-date/5-last-day-of-month/solution.md +++ /dev/null @@ -1,13 +0,0 @@ -Let's create a date using the next month, but pass zero as the day: -```js run demo -function getLastDayOfMonth(year, month) { - let date = new Date(year, month + 1, 0); - return date.getDate(); -} - -alert( getLastDayOfMonth(2012, 0) ); // 31 -alert( getLastDayOfMonth(2012, 1) ); // 29 -alert( getLastDayOfMonth(2013, 1) ); // 28 -``` - -Normally, dates start from 1, but technically we can pass any number, the date will autoadjust itself. So when we pass 0, then it means "one day before 1st day of the month", in other words: "the last day of the previous month". diff --git a/1-js/05-data-types/10-date/5-last-day-of-month/task.md b/1-js/05-data-types/10-date/5-last-day-of-month/task.md deleted file mode 100644 index 10dfb7a7a2..0000000000 --- a/1-js/05-data-types/10-date/5-last-day-of-month/task.md +++ /dev/null @@ -1,14 +0,0 @@ -importance: 5 - ---- - -# Last day of month? - -Write a function `getLastDayOfMonth(year, month)` that returns the last day of month. Sometimes it is 30th, 31st or even 28/29th for Feb. - -Parameters: - -- `year` -- four-digits year, for instance 2012. -- `month` -- month, from 0 to 11. - -For instance, `getLastDayOfMonth(2012, 1) = 29` (leap year, Feb). diff --git a/1-js/05-data-types/10-date/6-get-seconds-today/solution.md b/1-js/05-data-types/10-date/6-get-seconds-today/solution.md deleted file mode 100644 index a483afe931..0000000000 --- a/1-js/05-data-types/10-date/6-get-seconds-today/solution.md +++ /dev/null @@ -1,26 +0,0 @@ -To get the number of seconds, we can generate a date using the current day and time 00:00:00, then substract it from "now". - -The difference is the number of milliseconds from the beginning of the day, that we should divide by 1000 to get seconds: - -```js run -function getSecondsToday() { - let now = new Date(); - - // create an object using the current day/month/year - let today = new Date(now.getFullYear(), now.getMonth(), now.getDate()); - - let diff = now - today; // ms difference - return Math.round(diff / 1000); // make seconds -} - -alert( getSecondsToday() ); -``` - -An alternative solution would be to get hours/minutes/seconds and convert them to seconds: - -```js run -function getSecondsToday() { - let d = new Date(); - return d.getHours() * 3600 + d.getMinutes() * 60 + d.getSeconds(); -} -``` diff --git a/1-js/05-data-types/10-date/6-get-seconds-today/task.md b/1-js/05-data-types/10-date/6-get-seconds-today/task.md deleted file mode 100644 index 3fbe132868..0000000000 --- a/1-js/05-data-types/10-date/6-get-seconds-today/task.md +++ /dev/null @@ -1,15 +0,0 @@ -importance: 5 - ---- - -# How many seconds has passed today? - -Write a function `getSecondsToday()` that returns the number of seconds from the beginning of today. - -For instance, if now `10:00 am`, and there was no daylight savings shift, then: - -```js -getSecondsToday() == 36000 // (3600 * 10) -``` - -The function should work in any day. That is, it should not have a hard-coded value of "today". diff --git a/1-js/05-data-types/10-date/7-get-seconds-to-tomorrow/solution.md b/1-js/05-data-types/10-date/7-get-seconds-to-tomorrow/solution.md deleted file mode 100644 index c337d11995..0000000000 --- a/1-js/05-data-types/10-date/7-get-seconds-to-tomorrow/solution.md +++ /dev/null @@ -1,32 +0,0 @@ -To get the number of milliseconds till tomorrow, we can from "tomorrow 00:00:00" substract the current date. - -First, we generate that "tomorrow", and then do it: - -```js run -function getSecondsToTomorrow() { - let now = new Date(); - - // tomorrow date - let tomorrow = new Date(now.getFullYear(), now.getMonth(), *!*now.getDate()+1*/!*); - - let diff = tomorrow - now; // difference in ms - return Math.round(diff / 1000); // convert to seconds -} -``` - -Alternative solution: - -```js run -function getSecondsToTomorrow() { - let now = new Date(); - let hour = now.getHours(); - let minutes = now.getMinutes(); - let seconds = now.getSeconds(); - let totalSecondsToday = (hour * 60 + minutes) * 60 + seconds; - let totalSecondsInADay = 86400; - - return totalSecondsInADay - totalSecondsToday; -} -``` - -Please note that many countries have Daylight Savings Time (DST), so there may be days with 23 or 25 hours. We may want to treat such days separately. diff --git a/1-js/05-data-types/10-date/7-get-seconds-to-tomorrow/task.md b/1-js/05-data-types/10-date/7-get-seconds-to-tomorrow/task.md deleted file mode 100644 index e059030268..0000000000 --- a/1-js/05-data-types/10-date/7-get-seconds-to-tomorrow/task.md +++ /dev/null @@ -1,15 +0,0 @@ -importance: 5 - ---- - -# How many seconds till tomorrow? - -Create a function `getSecondsToTomorrow()` that returns the number of seconds till tomorrow. - -For instance, if now is `23:00`, then: - -```js -getSecondsToTomorrow() == 3600 -``` - -P.S. The function should work at any day, the "today" is not hardcoded. diff --git a/1-js/05-data-types/10-date/8-format-date-relative/_js.view/solution.js b/1-js/05-data-types/10-date/8-format-date-relative/_js.view/solution.js deleted file mode 100644 index 4695354a5c..0000000000 --- a/1-js/05-data-types/10-date/8-format-date-relative/_js.view/solution.js +++ /dev/null @@ -1,33 +0,0 @@ - -function formatDate(date) { - let diff = new Date() - date; // the difference in milliseconds - - if (diff < 1000) { // less than 1 second - return 'right now'; - } - - let sec = Math.floor(diff / 1000); // convert diff to seconds - - if (sec < 60) { - return sec + ' sec. ago'; - } - - let min = Math.floor(diff / 60000); // convert diff to minutes - if (min < 60) { - return min + ' min. ago'; - } - - // format the date - // add leading zeroes to single-digit day/month/hours/minutes - let d = date; - d = [ - '0' + d.getDate(), - '0' + (d.getMonth() + 1), - '' + d.getFullYear(), - '0' + d.getHours(), - '0' + d.getMinutes() - ].map(component => component.slice(-2)); // take last 2 digits of every component - - // join the components into date - return d.slice(0, 3).join('.') + ' ' + d.slice(3).join(':'); -} diff --git a/1-js/05-data-types/10-date/8-format-date-relative/_js.view/test.js b/1-js/05-data-types/10-date/8-format-date-relative/_js.view/test.js deleted file mode 100644 index 9b4cb2f584..0000000000 --- a/1-js/05-data-types/10-date/8-format-date-relative/_js.view/test.js +++ /dev/null @@ -1,18 +0,0 @@ -describe("formatDate", function() { - it("shows 1ms ago as \"right now\"", function() { - assert.equal(formatDate(new Date(new Date - 1)), 'right now'); - }); - - it('"30 seconds ago"', function() { - assert.equal(formatDate(new Date(new Date - 30 * 1000)), "30 sec. ago"); - }); - - it('"5 minutes ago"', function() { - assert.equal(formatDate(new Date(new Date - 5 * 60 * 1000)), "5 min. ago"); - }); - - it("older dates as DD.MM.YY HH:mm", function() { - assert.equal(formatDate(new Date(2014, 2, 1, 11, 22, 33)), "01.03.14 11:22"); - }); - -}); diff --git a/1-js/05-data-types/10-date/8-format-date-relative/solution.md b/1-js/05-data-types/10-date/8-format-date-relative/solution.md deleted file mode 100644 index 2507c840c7..0000000000 --- a/1-js/05-data-types/10-date/8-format-date-relative/solution.md +++ /dev/null @@ -1,76 +0,0 @@ -To get the time from `date` till now -- let's substract the dates. - -```js run demo -function formatDate(date) { - let diff = new Date() - date; // the difference in milliseconds - - if (diff < 1000) { // less than 1 second - return 'right now'; - } - - let sec = Math.floor(diff / 1000); // convert diff to seconds - - if (sec < 60) { - return sec + ' sec. ago'; - } - - let min = Math.floor(diff / 60000); // convert diff to minutes - if (min < 60) { - return min + ' min. ago'; - } - - // format the date - // add leading zeroes to single-digit day/month/hours/minutes - let d = date; - d = [ - '0' + d.getDate(), - '0' + (d.getMonth() + 1), - '' + d.getFullYear(), - '0' + d.getHours(), - '0' + d.getMinutes() - ].map(component => component.slice(-2)); // take last 2 digits of every component - - // join the components into date - return d.slice(0, 3).join('.') + ' ' + d.slice(3).join(':'); -} - -alert( formatDate(new Date(new Date - 1)) ); // "right now" - -alert( formatDate(new Date(new Date - 30 * 1000)) ); // "30 sec. ago" - -alert( formatDate(new Date(new Date - 5 * 60 * 1000)) ); // "5 min. ago" - -// yesterday's date like 31.12.2016, 20:00 -alert( formatDate(new Date(new Date - 86400 * 1000)) ); -``` - -Alternative solution: - -```js run -function formatDate(date) { - let dayOfMonth = date.getDate(); - let month = date.getMonth() + 1; - let year = date.getFullYear(); - let hour = date.getHours(); - let minutes = date.getMinutes(); - let diffMs = new Date() - date; - let diffSec = Math.round(diffMs / 1000); - let diffMin = diffSec / 60; - let diffHour = diffMin / 60; - - // formatting - year = year.toString().slice(-2); - month = month < 10 ? '0' + month : month; - dayOfMonth = dayOfMonth < 10 ? '0' + dayOfMonth : dayOfMonth; - - if (diffSec < 1) { - return 'right now'; - } else if (diffMin < 1) { - return `${diffSec} sec. ago` - } else if (diffHour < 1) { - return `${diffMin} min. ago` - } else { - return `${dayOfMonth}.${month}.${year} ${hour}:${minutes}` - } -} -``` diff --git a/1-js/05-data-types/10-date/8-format-date-relative/task.md b/1-js/05-data-types/10-date/8-format-date-relative/task.md deleted file mode 100644 index 7b341ca2e6..0000000000 --- a/1-js/05-data-types/10-date/8-format-date-relative/task.md +++ /dev/null @@ -1,25 +0,0 @@ -importance: 4 - ---- - -# Format the relative date - -Write a function `formatDate(date)` that should format `date` as follows: - -- If since `date` passed less than 1 second, then `"right now"`. -- Otherwise, if since `date` passed less than 1 minute, then `"n sec. ago"`. -- Otherwise, if less than an hour, then `"m min. ago"`. -- Otherwise, the full date in the format `"DD.MM.YY HH:mm"`. That is: `"day.month.year hours:minutes"`, all in 2-digit format, e.g. `31.12.16 10:00`. - -For instance: - -```js -alert( formatDate(new Date(new Date - 1)) ); // "right now" - -alert( formatDate(new Date(new Date - 30 * 1000)) ); // "30 sec. ago" - -alert( formatDate(new Date(new Date - 5 * 60 * 1000)) ); // "5 min. ago" - -// yesterday's date like 31.12.2016, 20:00 -alert( formatDate(new Date(new Date - 86400 * 1000)) ); -``` diff --git a/1-js/05-data-types/10-date/article.md b/1-js/05-data-types/10-date/article.md deleted file mode 100644 index 8a75f1cbda..0000000000 --- a/1-js/05-data-types/10-date/article.md +++ /dev/null @@ -1,427 +0,0 @@ -# Date and time - -Let's meet a new built-in object: [Date](mdn:js/Date). It stores the date, time and provides methods for date/time management. - -For instance, we can use it to store creation/modification times, to measure time, or just to print out the current date. - -## Creation - -To create a new `Date` object call `new Date()` with one of the following arguments: - -`new Date()` -: Without arguments -- create a `Date` object for the current date and time: - - ```js run - let now = new Date(); - alert( now ); // shows current date/time - ``` - -`new Date(milliseconds)` -: Create a `Date` object with the time equal to number of milliseconds (1/1000 of a second) passed after the Jan 1st of 1970 UTC+0. - - ```js run - // 0 means 01.01.1970 UTC+0 - let Jan01_1970 = new Date(0); - alert( Jan01_1970 ); - - // now add 24 hours, get 02.01.1970 UTC+0 - let Jan02_1970 = new Date(24 * 3600 * 1000); - alert( Jan02_1970 ); - ``` - - The number of milliseconds that has passed since the beginning of 1970 is called a *timestamp*. - - It's a lightweight numeric representation of a date. We can always create a date from a timestamp using `new Date(timestamp)` and convert the existing `Date` object to a timestamp using the `date.getTime()` method (see below). - -`new Date(datestring)` -: If there is a single argument, and it's a string, then it is parsed with the `Date.parse` algorithm (see below). - - - ```js run - let date = new Date("2017-01-26"); - alert(date); - // The time portion of the date is assumed to be midnight GMT and - // is adjusted according to the timezone the code is run in - // So the result could be - // Thu Jan 26 2017 11:00:00 GMT+1100 (Australian Eastern Daylight Time) - // or - // Wed Jan 25 2017 16:00:00 GMT-0800 (Pacific Standard Time) - ``` - -`new Date(year, month, date, hours, minutes, seconds, ms)` -: Create the date with the given components in the local time zone. Only two first arguments are obligatory. - - Note: - - - The `year` must have 4 digits: `2013` is okay, `98` is not. - - The `month` count starts with `0` (Jan), up to `11` (Dec). - - The `date` parameter is actually the day of month, if absent then `1` is assumed. - - If `hours/minutes/seconds/ms` is absent, they are assumed to be equal `0`. - - For instance: - - ```js - new Date(2011, 0, 1, 0, 0, 0, 0); // // 1 Jan 2011, 00:00:00 - new Date(2011, 0, 1); // the same, hours etc are 0 by default - ``` - - The minimal precision is 1 ms (1/1000 sec): - - ```js run - let date = new Date(2011, 0, 1, 2, 3, 4, 567); - alert( date ); // 1.01.2011, 02:03:04.567 - ``` - -## Access date components - -There are many methods to access the year, month and so on from the `Date` object. But they can be easily remembered when categorized. - -[getFullYear()](mdn:js/Date/getFullYear) -: Get the year (4 digits) - -[getMonth()](mdn:js/Date/getMonth) -: Get the month, **from 0 to 11**. - -[getDate()](mdn:js/Date/getDate) -: Get the day of month, from 1 to 31, the name of the method does look a little bit strange. - -[getHours()](mdn:js/Date/getHours), [getMinutes()](mdn:js/Date/getMinutes), [getSeconds()](mdn:js/Date/getSeconds), [getMilliseconds()](mdn:js/Date/getMilliseconds) -: Get the corresponding time components. - -```warn header="Not `getYear()`, but `getFullYear()`" -Many JavaScript engines implement a non-standard method `getYear()`. This method is deprecated. It returns 2-digit year sometimes. Please never use it. There is `getFullYear()` for the year. -``` - -Additionally, we can get a day of week: - -[getDay()](mdn:js/Date/getDay) -: Get the day of week, from `0` (Sunday) to `6` (Saturday). The first day is always Sunday, in some countries that's not so, but can't be changed. - -**All the methods above return the components relative to the local time zone.** - -There are also their UTC-counterparts, that return day, month, year and so on for the time zone UTC+0: [getUTCFullYear()](mdn:js/Date/getUTCFullYear), [getUTCMonth()](mdn:js/Date/getUTCMonth), [getUTCDay()](mdn:js/Date/getUTCDay). Just insert the `"UTC"` right after `"get"`. - -If your local time zone is shifted relative to UTC, then the code below shows different hours: - -```js run -// current date -let date = new Date(); - -// the hour in your current time zone -alert( date.getHours() ); - -// the hour in UTC+0 time zone (London time without daylight savings) -alert( date.getUTCHours() ); -``` - -Besides the given methods, there are two special ones that do not have a UTC-variant: - -[getTime()](mdn:js/Date/getTime) -: Returns the timestamp for the date -- a number of milliseconds passed from the January 1st of 1970 UTC+0. - -[getTimezoneOffset()](mdn:js/Date/getTimezoneOffset) -: Returns the difference between the local time zone and UTC, in minutes: - - ```js run - // if you are in timezone UTC-1, outputs 60 - // if you are in timezone UTC+3, outputs -180 - alert( new Date().getTimezoneOffset() ); - - ``` - -## Setting date components - -The following methods allow to set date/time components: - -- [`setFullYear(year [, month, date])`](mdn:js/Date/setFullYear) -- [`setMonth(month [, date])`](mdn:js/Date/setMonth) -- [`setDate(date)`](mdn:js/Date/setDate) -- [`setHours(hour [, min, sec, ms])`](mdn:js/Date/setHours) -- [`setMinutes(min [, sec, ms])`](mdn:js/Date/setMinutes) -- [`setSeconds(sec [, ms])`](mdn:js/Date/setSeconds) -- [`setMilliseconds(ms)`](mdn:js/Date/setMilliseconds) -- [`setTime(milliseconds)`](mdn:js/Date/setTime) (sets the whole date by milliseconds since 01.01.1970 UTC) - -Every one of them except `setTime()` has a UTC-variant, for instance: `setUTCHours()`. - -As we can see, some methods can set multiple components at once, for example `setHours`. The components that are not mentioned are not modified. - -For instance: - -```js run -let today = new Date(); - -today.setHours(0); -alert(today); // still today, but the hour is changed to 0 - -today.setHours(0, 0, 0, 0); -alert(today); // still today, now 00:00:00 sharp. -``` - -## Autocorrection - -The *autocorrection* is a very handy feature of `Date` objects. We can set out-of-range values, and it will auto-adjust itself. - -For instance: - -```js run -let date = new Date(2013, 0, *!*32*/!*); // 32 Jan 2013 ?!? -alert(date); // ...is 1st Feb 2013! -``` - -Out-of-range date components are distributed automatically. - -Let's say we need to increase the date "28 Feb 2016" by 2 days. It may be "2 Mar" or "1 Mar" in case of a leap-year. We don't need to think about it. Just add 2 days. The `Date` object will do the rest: - -```js run -let date = new Date(2016, 1, 28); -*!* -date.setDate(date.getDate() + 2); -*/!* - -alert( date ); // 1 Mar 2016 -``` - -That feature is often used to get the date after the given period of time. For instance, let's get the date for "70 seconds after now": - -```js run -let date = new Date(); -date.setSeconds(date.getSeconds() + 70); - -alert( date ); // shows the correct date -``` - -We can also set zero or even negative values. For example: - -```js run -let date = new Date(2016, 0, 2); // 2 Jan 2016 - -date.setDate(1); // set day 1 of month -alert( date ); - -date.setDate(0); // min day is 1, so the last day of the previous month is assumed -alert( date ); // 31 Dec 2015 -``` - -## Date to number, date diff - -When a `Date` object is converted to number, it becomes the timestamp same as `date.getTime()`: - -```js run -let date = new Date(); -alert(+date); // the number of milliseconds, same as date.getTime() -``` - -The important side effect: dates can be subtracted, the result is their difference in ms. - -That can be used for time measurements: - -```js run -let start = new Date(); // start counting - -// do the job -for (let i = 0; i < 100000; i++) { - let doSomething = i * i * i; -} - -let end = new Date(); // done - -alert( `The loop took ${end - start} ms` ); -``` - -## Date.now() - -If we only want to measure the difference, we don't need the `Date` object. - -There's a special method `Date.now()` that returns the current timestamp. - -It is semantically equivalent to `new Date().getTime()`, but it doesn't create an intermediate `Date` object. So it's faster and doesn't put pressure on garbage collection. - -It is used mostly for convenience or when performance matters, like in games in JavaScript or other specialized applications. - -So this is probably better: - -```js run -*!* -let start = Date.now(); // milliseconds count from 1 Jan 1970 -*/!* - -// do the job -for (let i = 0; i < 100000; i++) { - let doSomething = i * i * i; -} - -*!* -let end = Date.now(); // done -*/!* - -alert( `The loop took ${end - start} ms` ); // subtract numbers, not dates -``` - -## Benchmarking - -If we want a reliable benchmark of CPU-hungry function, we should be careful. - -For instance, let's measure two functions that calculate the difference between two dates: which one is faster? - -```js -// we have date1 and date2, which function faster returns their difference in ms? -function diffSubtract(date1, date2) { - return date2 - date1; -} - -// or -function diffGetTime(date1, date2) { - return date2.getTime() - date1.getTime(); -} -``` - -These two do exactly the same thing, but one of them uses an explicit `date.getTime()` to get the date in ms, and the other one relies on a date-to-number transform. Their result is always the same. - -So, which one is faster? - -The first idea may be to run them many times in a row and measure the time difference. For our case, functions are very simple, so we have to do it around 100000 times. - -Let's measure: - -```js run -function diffSubtract(date1, date2) { - return date2 - date1; -} - -function diffGetTime(date1, date2) { - return date2.getTime() - date1.getTime(); -} - -function bench(f) { - let date1 = new Date(0); - let date2 = new Date(); - - let start = Date.now(); - for (let i = 0; i < 100000; i++) f(date1, date2); - return Date.now() - start; -} - -alert( 'Time of diffSubtract: ' + bench(diffSubtract) + 'ms' ); -alert( 'Time of diffGetTime: ' + bench(diffGetTime) + 'ms' ); -``` - -Wow! Using `getTime()` is so much faster! That's because there's no type conversion, it is much easier for engines to optimize. - -Okay, we have something. But that's not a good benchmark yet. - -Imagine that at the time of running `bench(diffSubtract)` CPU was doing something in parallel, and it was taking resources. And by the time of running `bench(diffGetTime)` the work has finished. - -A pretty real scenario for a modern multi-process OS. - -As a result, the first benchmark will have less CPU resources than the second. That may lead to wrong results. - -**For more reliable benchmarking, the whole pack of benchmarks should be rerun multiple times.** - -Here's the code example: - -```js run -function diffSubtract(date1, date2) { - return date2 - date1; -} - -function diffGetTime(date1, date2) { - return date2.getTime() - date1.getTime(); -} - -function bench(f) { - let date1 = new Date(0); - let date2 = new Date(); - - let start = Date.now(); - for (let i = 0; i < 100000; i++) f(date1, date2); - return Date.now() - start; -} - -let time1 = 0; -let time2 = 0; - -*!* -// run bench(upperSlice) and bench(upperLoop) each 10 times alternating -for (let i = 0; i < 10; i++) { - time1 += bench(diffSubtract); - time2 += bench(diffGetTime); -} -*/!* - -alert( 'Total time for diffSubtract: ' + time1 ); -alert( 'Total time for diffGetTime: ' + time2 ); -``` - -Modern JavaScript engines start applying advanced optimizations only to "hot code" that executes many times (no need to optimize rarely executed things). So, in the example above, first executions are not well-optimized. We may want to add a heat-up run: - -```js -// added for "heating up" prior to the main loop -bench(diffSubtract); -bench(diffGetTime); - -// now benchmark -for (let i = 0; i < 10; i++) { - time1 += bench(diffSubtract); - time2 += bench(diffGetTime); -} -``` - -```warn header="Be careful doing microbenchmarking" -Modern JavaScript engines perform many optimizations. They may tweak results of "artificial tests" compared to "normal usage", especially when we benchmark something very small. So if you seriously want to understand performance, then please study how the JavaScript engine works. And then you probably won't need microbenchmarks at all. - -The great pack of articles about V8 can be found at . -``` - -## Date.parse from a string - -The method [Date.parse(str)](mdn:js/Date/parse) can read a date from a string. - -The string format should be: `YYYY-MM-DDTHH:mm:ss.sssZ`, where: - -- `YYYY-MM-DD` -- is the date: year-month-day. -- The character `"T"` is used as the delimiter. -- `HH:mm:ss.sss` -- is the time: hours, minutes, seconds and milliseconds. -- The optional `'Z'` part denotes the time zone in the format `+-hh:mm`. A single letter `Z` that would mean UTC+0. - -Shorter variants are also possible, like `YYYY-MM-DD` or `YYYY-MM` or even `YYYY`. - -The call to `Date.parse(str)` parses the string in the given format and returns the timestamp (number of milliseconds from 1 Jan 1970 UTC+0). If the format is invalid, returns `NaN`. - -For instance: - -```js run -let ms = Date.parse('2012-01-26T13:51:50.417-07:00'); - -alert(ms); // 1327611110417 (timestamp) -``` - -We can instantly create a `new Date` object from the timestamp: - -```js run -let date = new Date( Date.parse('2012-01-26T13:51:50.417-07:00') ); - -alert(date); -``` - -## Summary - -- Date and time in JavaScript are represented with the [Date](mdn:js/Date) object. We can't create "only date" or "only time": `Date` objects always carry both. -- Months are counted from zero (yes, January is a zero month). -- Days of week in `getDay()` are also counted from zero (that's Sunday). -- `Date` auto-corrects itself when out-of-range components are set. Good for adding/subtracting days/months/hours. -- Dates can be subtracted, giving their difference in milliseconds. That's because a `Date` becomes the timestamp when converted to a number. -- Use `Date.now()` to get the current timestamp fast. - -Note that unlike many other systems, timestamps in JavaScript are in milliseconds, not in seconds. - -Also, sometimes we need more precise time measurements. JavaScript itself does not have a way to measure time in microseconds (1 millionth of a second), but most environments provide it. For instance, browser has [performance.now()](mdn:api/Performance/now) that gives the number of milliseconds from the start of page loading with microsecond precision (3 digits after the point): - -```js run -alert(`Loading started ${performance.now()}ms ago`); -// Something like: "Loading started 34731.26000000001ms ago" -// .26 is microseconds (260 microseconds) -// more than 3 digits after the decimal point are precision errors, but only the first 3 are correct -``` - -Node.js has `microtime` module and other ways. Technically, any device and environment allows to get more precision, it's just not in `Date`. diff --git a/1-js/05-data-types/09-destructuring-assignment/1-destruct-user/solution.md b/1-js/05-data-types/10-destructuring-assignment/1-destruct-user/solution.md similarity index 100% rename from 1-js/05-data-types/09-destructuring-assignment/1-destruct-user/solution.md rename to 1-js/05-data-types/10-destructuring-assignment/1-destruct-user/solution.md diff --git a/1-js/05-data-types/10-destructuring-assignment/1-destruct-user/task.md b/1-js/05-data-types/10-destructuring-assignment/1-destruct-user/task.md new file mode 100644 index 0000000000..b0b30dd811 --- /dev/null +++ b/1-js/05-data-types/10-destructuring-assignment/1-destruct-user/task.md @@ -0,0 +1,33 @@ +importance: 5 + +--- + +# Деструктурирующее присваивание + +У нас есть объект: + +```js +let user = { + name: "John", + years: 30 +}; +``` + +Напишите деструктурирующее присваивание, которое: + +- свойство `name` присвоит в переменную `name`. +- свойство `years` присвоит в переменную `age`. +- свойство `isAdmin` присвоит в переменную `isAdmin` (false, если нет такого свойства) + +Пример переменных после вашего присваивания: + +```js +let user = { name: "John", years: 30 }; + +// ваш код должен быть с левой стороны: +// ... = user + +alert( name ); // John +alert( age ); // 30 +alert( isAdmin ); // false +``` diff --git a/1-js/05-data-types/09-destructuring-assignment/6-max-salary/_js.view/solution.js b/1-js/05-data-types/10-destructuring-assignment/6-max-salary/_js.view/solution.js similarity index 100% rename from 1-js/05-data-types/09-destructuring-assignment/6-max-salary/_js.view/solution.js rename to 1-js/05-data-types/10-destructuring-assignment/6-max-salary/_js.view/solution.js diff --git a/1-js/05-data-types/09-destructuring-assignment/6-max-salary/_js.view/test.js b/1-js/05-data-types/10-destructuring-assignment/6-max-salary/_js.view/test.js similarity index 100% rename from 1-js/05-data-types/09-destructuring-assignment/6-max-salary/_js.view/test.js rename to 1-js/05-data-types/10-destructuring-assignment/6-max-salary/_js.view/test.js diff --git a/1-js/05-data-types/09-destructuring-assignment/6-max-salary/solution.md b/1-js/05-data-types/10-destructuring-assignment/6-max-salary/solution.md similarity index 100% rename from 1-js/05-data-types/09-destructuring-assignment/6-max-salary/solution.md rename to 1-js/05-data-types/10-destructuring-assignment/6-max-salary/solution.md diff --git a/1-js/05-data-types/10-destructuring-assignment/6-max-salary/task.md b/1-js/05-data-types/10-destructuring-assignment/6-max-salary/task.md new file mode 100644 index 0000000000..bfdfb9b87e --- /dev/null +++ b/1-js/05-data-types/10-destructuring-assignment/6-max-salary/task.md @@ -0,0 +1,22 @@ +importance: 5 + +--- + +# Максимальная зарплата + +У нас есть объект `salaries` с зарплатами: + +```js +let salaries = { + "John": 100, + "Pete": 300, + "Mary": 250 +}; +``` + +Создайте функцию `topSalary(salaries)`, которая возвращает имя самого высокооплачиваемого сотрудника. + +- Если объект `salaries` пустой, то нужно вернуть `null`. +- Если несколько высокооплачиваемых сотрудников, можно вернуть любого из них. + +P.S. Используйте `Object.entries` и деструктурирование, чтобы перебрать пары ключ/значение. diff --git a/1-js/05-data-types/10-destructuring-assignment/article.md b/1-js/05-data-types/10-destructuring-assignment/article.md new file mode 100644 index 0000000000..0983421102 --- /dev/null +++ b/1-js/05-data-types/10-destructuring-assignment/article.md @@ -0,0 +1,581 @@ + +# Деструктурирующее присваивание + +В JavaScript есть две чаще всего используемые структуры данных - это `Object` и `Array`. + +- Объекты позволяют нам создавать одну сущность, которая хранит элементы данных по ключам. +- Массивы позволяют нам собирать элементы данных в упорядоченный список. + +Но когда мы передаём их в функцию, то ей может понадобиться не объект/массив целиком, а элементы по отдельности. + +*Деструктурирующее присваивание* -- это специальный синтаксис, который позволяет нам "распаковать" массивы или объекты в несколько переменных, так как иногда они более удобны. + +Деструктуризация также прекрасно работает со сложными функциями, которые имеют много параметров, значений по умолчанию и так далее. Скоро мы увидим это. + +## Деструктуризация массива + +Вот пример деструктуризации массива на переменные: + +```js +// у нас есть массив с именем и фамилией +let arr = ["Ilya", "Kantor"]; + +*!* +// деструктурирующее присваивание +// записывает firstName = arr[0] +// и surname = arr[1] +let [firstName, surname] = arr; +*/!* + +alert(firstName); // Ilya +alert(surname); // Kantor +``` + +Теперь мы можем использовать переменные вместо элементов массива. + +Отлично смотрится в сочетании со `split` или другими методами, возвращающими массив: + +```js +let [firstName, surname] = "Ilya Kantor".split(' '); +alert(firstName); // Ilya +alert(surname); // Kantor +``` + +Как вы можете видеть, синтаксис прост. Однако есть несколько странных моментов. Давайте посмотрим больше примеров, чтобы лучше понять это. + +````smart header="\"Деструктуризация\" не означает \"разрушение\"." +"Деструктурирующее присваивание" не уничтожает массив. Оно вообще ничего не делает с правой частью присваивания, его задача - только скопировать нужные значения в переменные. + +Это просто короткий вариант записи: + +```js +// let [firstName, surname] = arr; +let firstName = arr[0]; +let surname = arr[1]; +``` +```` + +````smart header="Пропускайте элементы, используя запятые" +Нежелательные элементы массива также могут быть отброшены с помощью дополнительной запятой: + +```js run +*!* +// второй элемент не нужен +let [firstName, , title] = ["Julius", "Caesar", "Consul", "of the Roman Republic"]; +*/!* + +alert( title ); // Consul +``` + +В примере выше второй элемент массива пропускается, а третий присваивается переменной `title`, оставшиеся элементы массива также пропускаются (так как для них нет переменных). +```` + +````smart header="Работает с любым перебираемым объектом с правой стороны" +...На самом деле мы можем использовать любой перебираемый объект, не только массивы: + +```js +let [a, b, c] = "abc"; +let [one, two, three] = new Set([1, 2, 3]); +``` +```` + + +````smart header="Присваивайте чему угодно с левой стороны" +Мы можем использовать что угодно "присваивающее" с левой стороны. + +Например, можно присвоить свойству объекта: + +```js run +let user = {}; +[user.name, user.surname] = "Ilya Kantor".split(' '); + +alert(user.name); // Ilya +alert(user.surname); // Kantor +``` +```` + +````smart header="Цикл с .entries()" +В предыдущей главе мы видели метод [Object.entries(obj)](mdn:js/Object/entries). + +Мы можем использовать его с деструктуризацией для цикличного перебора ключей и значений объекта: + +```js run +let user = { + name: "John", + age: 30 +}; + +// цикл по ключам и значениям +*!* +for (let [key, value] of Object.entries(user)) { +*/!* + alert(`${key}:${value}`); // name:John, затем age:30 +} +``` + +...то же самое для map: + +```js run +let user = new Map(); +user.set("name", "John"); +user.set("age", "30"); + +*!* +// Map перебирает как пары [ключ, значение], что очень удобно для деструктурирования +for (let [key, value] of user) { +*/!* + alert(`${key}:${value}`); // name:John, затем age:30 +} +``` +```` + +````smart header="Трюк обмена переменных" +Существует хорошо известный трюк для обмена значений двух переменных с использованием деструктурирующего присваивания: + +```js run +let guest = "Jane"; +let admin = "Pete"; + +// Давайте поменяем местами значения: сделаем guest = "Pete", а admin = "Jane" +*!* +[guest, admin] = [admin, guest]; +*/!* + +alert(`${guest} ${admin}`); // Pete Jane (успешно заменено!) +``` + +Здесь мы создаём временный массив из двух переменных и немедленно деструктурируем его в порядке замены. + +Таким образом, мы можем поменять местами даже более двух переменных. +```` + +### Остаточные параметры "..." + +Обычно, если массив длиннее, чем список слева, "лишние" элементы опускаются. + +Например, здесь берутся только первые два элемента, а остальные просто игнорируются: + +```js run +let [name1, name2] = ["Julius", "Caesar", "Consul", "of the Roman Republic"]; + +alert(name1); // Julius +alert(name2); // Caesar +// Дальнейшие элементы нигде не присваиваются +``` + +Если мы хотим не просто получить первые значения, но и собрать все остальные, то мы можем добавить ещё один параметр, который получает остальные значения, используя оператор "остаточные параметры" -- троеточие (`"..."`): + +```js run +let [name1, name2, *!*...rest*/!*] = ["Julius", "Caesar", *!*"Consul", "of the Roman Republic"*/!*]; + +*!* +// rest это массив элементов, начиная с 3-го +alert(rest[0]); // Consul +alert(rest[1]); // of the Roman Republic +alert(rest.length); // 2 +*/!* +``` + +Переменная `rest` является массивом из оставшихся элементов. + +Вместо `rest` можно использовать любое другое название переменной, просто убедитесь, что перед переменной есть три точки и она стоит на последнем месте в деструктурирующем присваивании. + +```js run +let [name1, name2, *!*...titles*/!*] = ["Julius", "Caesar", "Consul", "of the Roman Republic"]; +// теперь titles = ["Consul", "of the Roman Republic"] +``` + +### Значения по умолчанию + +Если в массиве меньше значений, чем в присваивании, то ошибки не будет. Отсутствующие значения считаются неопределёнными: + +```js run +*!* +let [firstName, surname] = []; +*/!* + +alert(firstName); // undefined +alert(surname); // undefined +``` + +Если мы хотим, чтобы значение "по умолчанию" заменило отсутствующее, мы можем указать его с помощью `=`: + +```js run +*!* +// значения по умолчанию +let [name = "Guest", surname = "Anonymous"] = ["Julius"]; +*/!* + +alert(name); // Julius (из массива) +alert(surname); // Anonymous (значение по умолчанию) +``` + +Значения по умолчанию могут быть гораздо более сложными выражениями или даже функциями. Они выполняются, только если значения отсутствуют. + +Например, здесь мы используем функцию `prompt` для указания двух значений по умолчанию. + +```js run +// prompt запустится только для surname +let [name = prompt('name?'), surname = prompt('surname?')] = ["Julius"]; + +alert(name); // Julius (из массива) +alert(surname); // результат prompt +``` + +Обратите внимание, `prompt` будет запущен только для пропущенного значения (`surname`). + +## Деструктуризация объекта + +Деструктурирующее присваивание также работает с объектами. + +Синтаксис: + +```js +let {var1, var2} = {var1:…, var2:…} +``` + +У нас есть существующий объект с правой стороны, который мы хотим разделить на переменные. Левая сторона содержит "шаблон" для соответствующих свойств. В простом случае это список названий переменных в `{...}`. + +Например: + +```js run +let options = { + title: "Menu", + width: 100, + height: 200 +}; + +*!* +let {title, width, height} = options; +*/!* + +alert(title); // Menu +alert(width); // 100 +alert(height); // 200 +``` + +Свойства `options.title`, `options.width` и `options.height` присваиваются соответствующим переменным. + +Порядок не имеет значения. Вот так - тоже работает: + +```js +// изменён порядок в let {...} +let {height, width, title} = { title: "Menu", height: 200, width: 100 } +``` + +Шаблон с левой стороны может быть более сложным и определять соответствие между свойствами и переменными. + +Если мы хотим присвоить свойство объекта переменной с другим названием, например, свойство `options.width` присвоить переменной `w`, то мы можем использовать двоеточие: + +```js run +let options = { + title: "Menu", + width: 100, + height: 200 +}; + +*!* +// { sourceProperty: targetVariable } +let {width: w, height: h, title} = options; +*/!* + +// width -> w +// height -> h +// title -> title + +alert(title); // Menu +alert(w); // 100 +alert(h); // 200 +``` + +Двоеточие показывает "что : куда идёт". В примере выше свойство `width` сохраняется в переменную `w`, свойство `height` сохраняется в `h`, а `title` присваивается одноимённой переменной. + +Для потенциально отсутствующих свойств мы можем установить значения по умолчанию, используя `"="`, как здесь: + +```js run +let options = { + title: "Menu" +}; + +*!* +let {width = 100, height = 200, title} = options; +*/!* + +alert(title); // Menu +alert(width); // 100 +alert(height); // 200 +``` + +Как и в случае с массивами, значениями по умолчанию могут быть любые выражения или даже функции. Они выполнятся, если значения отсутствуют. + +В коде ниже `prompt` запросит `width`, но не `title`: + +```js run +let options = { + title: "Menu" +}; + +*!* +let {width = prompt("width?"), title = prompt("title?")} = options; +*/!* + +alert(title); // Menu +alert(width); // (результат prompt) +``` + +Мы также можем совмещать `:` и `=`: + +```js run +let options = { + title: "Menu" +}; + +*!* +let {width: w = 100, height: h = 200, title} = options; +*/!* + +alert(title); // Menu +alert(w); // 100 +alert(h); // 200 +``` + +Если у нас есть большой объект с множеством свойств, можно взять только то, что нужно: + +```js +let options = { + title: "Menu", + width: 100, + height: 200 +}; + +// взять только title, игнорировать остальное +let { title } = options; + +alert(title); // Menu +``` + +### Остаток объекта "..." + +Что если в объекте больше свойств, чем у нас переменных? Можем ли мы взять необходимые нам, а остальные присвоить куда-нибудь? + +Можно использовать троеточие, как и для массивов. В некоторых старых браузерах (IE) это не поддерживается, используйте Babel для полифила. + +Выглядит так: + +```js run +let options = { + title: "Menu", + height: 200, + width: 100 +}; + +*!* +// title = свойство с именем title +// rest = объект с остальными свойствами +let {title, ...rest} = options; +*/!* + +// сейчас title="Menu", rest={height: 200, width: 100} +alert(rest.height); // 200 +alert(rest.width); // 100 +``` + +````smart header="Обратите внимание на `let`" +В примерах выше переменные были объявлены в присваивании: `let {…} = {…}`. Конечно, мы могли бы использовать существующие переменные и не указывать `let`, но тут есть подвох. + +Вот так не будет работать: +```js run +let title, width, height; + +// ошибка будет в этой строке +{title, width, height} = {title: "Menu", width: 200, height: 100}; +``` + +Проблема в том, что JavaScript обрабатывает `{...}` в основном потоке кода (не внутри другого выражения) как блок кода. Такие блоки кода могут быть использованы для группировки операторов, например: + +```js run +{ + // блок кода + let message = "Hello"; + // ... + alert( message ); +} +``` + +Так что здесь JavaScript считает, что видит блок кода, отсюда и ошибка. На самом-то деле у нас деструктуризация. + +Чтобы показать JavaScript, что это не блок кода, мы можем заключить выражение в скобки `(...)`: + +```js run +let title, width, height; + +// сейчас всё работает +*!*(*/!*{title, width, height} = {title: "Menu", width: 200, height: 100}*!*)*/!*; + +alert( title ); // Menu +``` +```` + +## Вложенная деструктуризация + +Если объект или массив содержит другие вложенные объекты или массивы, то мы можем использовать более сложные шаблоны с левой стороны, чтобы извлечь более глубокие свойства. + +В приведённом ниже коде `options` хранит другой объект в свойстве `size` и массив в свойстве `items`. Шаблон в левой части присваивания имеет такую же структуру, чтобы извлечь данные из них: + +```js run +let options = { + size: { + width: 100, + height: 200 + }, + items: ["Cake", "Donut"], + extra: true +}; + +// деструктуризация разбита на несколько строк для ясности +let { + size: { // положим size сюда + width, + height + }, + items: [item1, item2], // добавим элементы к items + title = "Menu" // отсутствует в объекте (используется значение по умолчанию) +} = options; + +alert(title); // Menu +alert(width); // 100 +alert(height); // 200 +alert(item1); // Cake +alert(item2); // Donut +``` + +Весь объект `options`, кроме свойства `extra`, которое в левой части отсутствует, присваивается в соответствующие переменные: + +![](destructuring-complex.svg) + +В итоге у нас есть `width`, `height`, `item1`, `item2` и `title` со значением по умолчанию. + +Заметим, что переменные для `size` и `items` отсутствуют, так как мы взяли сразу их содержимое. + +## Умные параметры функций + +Есть ситуации, когда функция имеет много параметров, большинство из которых не обязательны. Это особенно верно для пользовательских интерфейсов. Представьте себе функцию, которая создаёт меню. Она может иметь ширину, высоту, заголовок, список элементов и так далее. + +Вот так - плохой способ писать подобные функции: + +```js +function showMenu(title = "Untitled", width = 200, height = 100, items = []) { + // ... +} +``` + +В реальной жизни проблема заключается в том, как запомнить порядок всех аргументов. Обычно IDE пытаются помочь нам, особенно если код хорошо документирован, но всё же... Другая проблема заключается в том, как вызвать функцию, когда большинство параметров передавать не надо, и значения по умолчанию вполне подходят. + +Разве что вот так? + +```js +// undefined там, где подходят значения по умолчанию +showMenu("My Menu", undefined, undefined, ["Item1", "Item2"]) +``` + +Это выглядит ужасно. И становится нечитаемым, когда мы имеем дело с большим количеством параметров. + +На помощь приходит деструктуризация! + +Мы можем передать параметры как объект, и функция немедленно деструктурирует его в переменные: + +```js run +// мы передаём объект в функцию +let options = { + title: "My menu", + items: ["Item1", "Item2"] +}; + +// ...и она немедленно извлекает свойства в переменные +function showMenu(*!*{title = "Untitled", width = 200, height = 100, items = []}*/!*) { + // title, items – взято из options, + // width, height – используются значения по умолчанию + alert( `${title} ${width} ${height}` ); // My Menu 200 100 + alert( items ); // Item1, Item2 +} + +showMenu(options); +``` + +Мы также можем использовать более сложное деструктурирование с вложенными объектами и двоеточием: + +```js run +let options = { + title: "My menu", + items: ["Item1", "Item2"] +}; + +*!* +function showMenu({ + title = "Untitled", + width: w = 100, // width присваиваем в w + height: h = 200, // height присваиваем в h + items: [item1, item2] // первый элемент items присваивается в item1, второй в item2 +}) { +*/!* + alert( `${title} ${w} ${h}` ); // My Menu 100 200 + alert( item1 ); // Item1 + alert( item2 ); // Item2 +} + +showMenu(options); +``` + +Полный синтаксис - такой же, как для деструктурирующего присваивания: + +```js +function showMenu({ + incomingProperty: varName = defaultValue + ... +}) +``` + +Тогда для объекта с параметрами будет создана переменная `varName` для свойства с именем `incomingProperty` по умолчанию равная `defaultValue`. + +Пожалуйста, обратите внимание, что такое деструктурирование подразумевает, что в `showMenu()` будет обязательно передан аргумент. Если нам нужны все значения по умолчанию, то нам следует передать пустой объект: + +```js +showMenu({}); // ок, все значения - по умолчанию + +showMenu(); // так была бы ошибка +``` + +Мы можем исправить это, сделав `{}` значением по умолчанию для всего объекта параметров: + +```js run +function showMenu({ title = "Menu", width = 100, height = 200 }*!* = {}*/!*) { + alert( `${title} ${width} ${height}` ); +} + +showMenu(); // Menu 100 200 +``` + +В приведённом выше коде весь объект аргументов по умолчанию равен `{}`, поэтому всегда есть что-то, что можно деструктурировать. + +## Итого + +- Деструктуризация позволяет разбивать объект или массив на переменные при присвоении. +- Полный синтаксис для объекта: + + ```js + let {prop : varName = defaultValue, ...rest} = object + ``` + + Cвойство `prop` объекта `object` здесь должно быть присвоено переменной `varName`. Если в объекте отсутствует такое свойство, переменной `varName` присваивается значение по умолчанию. + + Свойства, которые не были упомянуты, копируются в объект `rest`. + +- Полный синтаксис для массива: + + ```js + let [item1 = defaultValue, item2, ...rest] = array + ``` + + Первый элемент отправляется в `item1`; второй отправляется в `item2`, все остальные элементы попадают в массив `rest`. + +- Можно извлекать данные из вложенных объектов и массивов, для этого левая сторона должна иметь ту же структуру, что и правая. diff --git a/1-js/05-data-types/10-destructuring-assignment/destructuring-complex.svg b/1-js/05-data-types/10-destructuring-assignment/destructuring-complex.svg new file mode 100644 index 0000000000..8a1ff1a934 --- /dev/null +++ b/1-js/05-data-types/10-destructuring-assignment/destructuring-complex.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/1-js/05-data-types/11-date/1-new-date/solution.md b/1-js/05-data-types/11-date/1-new-date/solution.md new file mode 100644 index 0000000000..c479b01055 --- /dev/null +++ b/1-js/05-data-types/11-date/1-new-date/solution.md @@ -0,0 +1,8 @@ +Конструктор `new Date` стандартно использует местную временную зону. Единственная важная вещь, которую нужно запомнить - это то, что месяцы начинаются с нуля. + +Поэтому февраль обозначается числом 1. + +```js run +let d = new Date(2012, 1, 20, 3, 12); +alert( d ); +``` diff --git a/1-js/05-data-types/11-date/1-new-date/task.md b/1-js/05-data-types/11-date/1-new-date/task.md new file mode 100644 index 0000000000..adf9ab6075 --- /dev/null +++ b/1-js/05-data-types/11-date/1-new-date/task.md @@ -0,0 +1,9 @@ +importance: 5 + +--- + +# Создайте дату + +Создайте объект `Date` для даты: 20 февраля 2012 года, 3 часа 12 минут. Временная зона -- местная. + +Для вывода используйте `alert`. diff --git a/1-js/05-data-types/11-date/2-get-week-day/_js.view/solution.js b/1-js/05-data-types/11-date/2-get-week-day/_js.view/solution.js new file mode 100644 index 0000000000..0f07d52a9c --- /dev/null +++ b/1-js/05-data-types/11-date/2-get-week-day/_js.view/solution.js @@ -0,0 +1,5 @@ +function getWeekDay(date) { + let days = ['ВС', 'ПН', 'ВТ', 'СР', 'ЧТ', 'ПТ', 'СБ']; + + return days[date.getDay()]; +} diff --git a/1-js/05-data-types/11-date/2-get-week-day/_js.view/test.js b/1-js/05-data-types/11-date/2-get-week-day/_js.view/test.js new file mode 100644 index 0000000000..46c3890433 --- /dev/null +++ b/1-js/05-data-types/11-date/2-get-week-day/_js.view/test.js @@ -0,0 +1,29 @@ +describe("getWeekDay", function() { + it("3 января 2014 года - пятница", function() { + assert.equal(getWeekDay(new Date(2014, 0, 3)), 'ПТ'); + }); + + it("4 января 2014 года - суббота", function() { + assert.equal(getWeekDay(new Date(2014, 0, 4)), 'СБ'); + }); + + it("5 января 2014 года - воскресенье", function() { + assert.equal(getWeekDay(new Date(2014, 0, 5)), 'ВС'); + }); + + it("6 января 2014 года - понедельник", function() { + assert.equal(getWeekDay(new Date(2014, 0, 6)), 'ПН'); + }); + + it("7 января 2014 года - вторник", function() { + assert.equal(getWeekDay(new Date(2014, 0, 7)), 'ВТ'); + }); + + it("8 января 2014 года - среда", function() { + assert.equal(getWeekDay(new Date(2014, 0, 8)), 'СР'); + }); + + it("9 января 2014 - четверг", function() { + assert.equal(getWeekDay(new Date(2014, 0, 9)), 'ЧТ'); + }); +}); diff --git a/1-js/05-data-types/11-date/2-get-week-day/solution.md b/1-js/05-data-types/11-date/2-get-week-day/solution.md new file mode 100644 index 0000000000..789e7235ae --- /dev/null +++ b/1-js/05-data-types/11-date/2-get-week-day/solution.md @@ -0,0 +1,14 @@ +Метод `date.getDay()` возвращает номер дня недели, начиная с воскресенья. + +Создадим массив дней недели, чтобы получить имя нужного дня по его номеру: + +```js run demo +function getWeekDay(date) { + let days = ['ВС', 'ПН', 'ВТ', 'СР', 'ЧТ', 'ПТ', 'СБ']; + + return days[date.getDay()]; +} + +let date = new Date(2014, 0, 3); // 3 января 2014 года +alert( getWeekDay(date) ); // ПТ +``` diff --git a/1-js/05-data-types/11-date/2-get-week-day/task.md b/1-js/05-data-types/11-date/2-get-week-day/task.md new file mode 100644 index 0000000000..42b350544d --- /dev/null +++ b/1-js/05-data-types/11-date/2-get-week-day/task.md @@ -0,0 +1,14 @@ +importance: 5 + +--- + +# Покажите день недели + +Напишите функцию `getWeekDay(date)`, показывающую день недели в коротком формате: "ПН", "ВТ", "СР", "ЧТ", "ПТ", "СБ", "ВС". + +Например: + +```js no-beautify +let date = new Date(2012, 0, 3); // 3 января 2012 года +alert( getWeekDay(date) ); // нужно вывести "ВТ" +``` diff --git a/1-js/05-data-types/11-date/3-weekday/_js.view/solution.js b/1-js/05-data-types/11-date/3-weekday/_js.view/solution.js new file mode 100644 index 0000000000..3cc8a62549 --- /dev/null +++ b/1-js/05-data-types/11-date/3-weekday/_js.view/solution.js @@ -0,0 +1,10 @@ +function getLocalDay(date) { + + let day = date.getDay(); + + if (day == 0) { // день недели 0 (воскресенье) в европейской нумерации будет 7 + day = 7; + } + + return day; +} diff --git a/1-js/05-data-types/11-date/3-weekday/_js.view/test.js b/1-js/05-data-types/11-date/3-weekday/_js.view/test.js new file mode 100644 index 0000000000..d32bc2aea2 --- /dev/null +++ b/1-js/05-data-types/11-date/3-weekday/_js.view/test.js @@ -0,0 +1,29 @@ +describe("getLocalDay возвращает \"европейский\" день недели", function() { + it("3 января 2014 года - пятница", function() { + assert.equal(getLocalDay(new Date(2014, 0, 3)), 5); + }); + + it("4 января 2014 года - суббота", function() { + assert.equal(getLocalDay(new Date(2014, 0, 4)), 6); + }); + + it("5 января 2014 года - воскресенье", function() { + assert.equal(getLocalDay(new Date(2014, 0, 5)), 7); + }); + + it("6 января 2014 года - понедельник", function() { + assert.equal(getLocalDay(new Date(2014, 0, 6)), 1); + }); + + it("7 января 2014 года - вторник", function() { + assert.equal(getLocalDay(new Date(2014, 0, 7)), 2); + }); + + it("8 января 2014 года - среда", function() { + assert.equal(getLocalDay(new Date(2014, 0, 8)), 3); + }); + + it("9 января 2014 года - четверг", function() { + assert.equal(getLocalDay(new Date(2014, 0, 9)), 4); + }); +}); diff --git a/1-js/05-data-types/10-date/3-weekday/solution.md b/1-js/05-data-types/11-date/3-weekday/solution.md similarity index 100% rename from 1-js/05-data-types/10-date/3-weekday/solution.md rename to 1-js/05-data-types/11-date/3-weekday/solution.md diff --git a/1-js/05-data-types/11-date/3-weekday/task.md b/1-js/05-data-types/11-date/3-weekday/task.md new file mode 100644 index 0000000000..02874d100f --- /dev/null +++ b/1-js/05-data-types/11-date/3-weekday/task.md @@ -0,0 +1,12 @@ +importance: 5 + +--- + +# День недели в европейской нумерации + +В Европейских странах неделя начинается с понедельника (день номер 1), затем идёт вторник (номер 2) и так до воскресенья (номер 7). Напишите функцию `getLocalDay(date)`, которая возвращает "европейский" день недели для даты `date`. + +```js no-beautify +let date = new Date(2012, 0, 3); // 3 января 2012 года +alert( getLocalDay(date) ); // вторник, нужно показать 2 +``` diff --git a/1-js/05-data-types/10-date/4-get-date-ago/_js.view/solution.js b/1-js/05-data-types/11-date/4-get-date-ago/_js.view/solution.js similarity index 100% rename from 1-js/05-data-types/10-date/4-get-date-ago/_js.view/solution.js rename to 1-js/05-data-types/11-date/4-get-date-ago/_js.view/solution.js diff --git a/1-js/05-data-types/11-date/4-get-date-ago/_js.view/test.js b/1-js/05-data-types/11-date/4-get-date-ago/_js.view/test.js new file mode 100644 index 0000000000..e09c5e8d6e --- /dev/null +++ b/1-js/05-data-types/11-date/4-get-date-ago/_js.view/test.js @@ -0,0 +1,27 @@ +describe("getDateAgo", function() { + + it("1 день до 02.01.2015 -> день 1", function() { + assert.equal(getDateAgo(new Date(2015, 0, 2), 1), 1); + }); + + + it("2 дня до 02.01.2015 -> день 31", function() { + assert.equal(getDateAgo(new Date(2015, 0, 2), 2), 31); + }); + + it("100 дней до 02.01.2015 -> день 24", function() { + assert.equal(getDateAgo(new Date(2015, 0, 2), 100), 24); + }); + + it("365 дней до 02.01.2015 -> день 2", function() { + assert.equal(getDateAgo(new Date(2015, 0, 2), 365), 2); + }); + + it("переданный объект date не модифицируется", function() { + let date = new Date(2015, 0, 2); + let dateCopy = new Date(date); + getDateAgo(dateCopy, 100); + assert.equal(date.getTime(), dateCopy.getTime()); + }); + +}); diff --git a/1-js/05-data-types/11-date/4-get-date-ago/solution.md b/1-js/05-data-types/11-date/4-get-date-ago/solution.md new file mode 100644 index 0000000000..9e3a6e5a0b --- /dev/null +++ b/1-js/05-data-types/11-date/4-get-date-ago/solution.md @@ -0,0 +1,27 @@ +Идея проста: нужно вычесть заданное количество дней из `date`: + +```js +function getDateAgo(date, days) { + date.setDate(date.getDate() - days); + return date.getDate(); +} +``` + +...Но функция не должна изменять объект `date`. Это очень важно, поскольку внешний код, передающий нам объект, не ожидает его изменения. + +Это можно осуществить путём клонирования даты: + +```js run demo +function getDateAgo(date, days) { + let dateCopy = new Date(date); + + dateCopy.setDate(date.getDate() - days); + return dateCopy.getDate(); +} + +let date = new Date(2015, 0, 2); + +alert( getDateAgo(date, 1) ); // 1, (1 Jan 2015) +alert( getDateAgo(date, 2) ); // 31, (31 Dec 2014) +alert( getDateAgo(date, 365) ); // 2, (2 Jan 2014) +``` diff --git a/1-js/05-data-types/11-date/4-get-date-ago/task.md b/1-js/05-data-types/11-date/4-get-date-ago/task.md new file mode 100644 index 0000000000..d194be145b --- /dev/null +++ b/1-js/05-data-types/11-date/4-get-date-ago/task.md @@ -0,0 +1,21 @@ +importance: 4 + +--- + +# Какой день месяца был много дней назад? + +Создайте функцию `getDateAgo(date, days)`, возвращающую число, которое было `days` дней назад от даты `date`. + +К примеру, если сегодня двадцатое число, то `getDateAgo(new Date(), 1)` вернёт девятнадцатое и `getDateAgo(new Date(), 2)` – восемнадцатое. + +Функция должна надёжно работать при значении `days=365` и больших значениях: + +```js +let date = new Date(2015, 0, 2); + +alert( getDateAgo(date, 1) ); // 1, (1 Jan 2015) +alert( getDateAgo(date, 2) ); // 31, (31 Dec 2014) +alert( getDateAgo(date, 365) ); // 2, (2 Jan 2014) +``` + +P.S. Функция не должна изменять переданный ей объект `date`. diff --git a/1-js/05-data-types/10-date/5-last-day-of-month/_js.view/solution.js b/1-js/05-data-types/11-date/5-last-day-of-month/_js.view/solution.js similarity index 100% rename from 1-js/05-data-types/10-date/5-last-day-of-month/_js.view/solution.js rename to 1-js/05-data-types/11-date/5-last-day-of-month/_js.view/solution.js diff --git a/1-js/05-data-types/11-date/5-last-day-of-month/_js.view/test.js b/1-js/05-data-types/11-date/5-last-day-of-month/_js.view/test.js new file mode 100644 index 0000000000..264d609415 --- /dev/null +++ b/1-js/05-data-types/11-date/5-last-day-of-month/_js.view/test.js @@ -0,0 +1,13 @@ +describe("getLastDayOfMonth", function() { + it("последнее число 01.01.2012 - 31", function() { + assert.equal(getLastDayOfMonth(2012, 0), 31); + }); + + it("последнее число 01.02.2012 - 29 (високосный год)", function() { + assert.equal(getLastDayOfMonth(2012, 1), 29); + }); + + it("последнее число 01.02.2013 - 28", function() { + assert.equal(getLastDayOfMonth(2013, 1), 28); + }); +}); diff --git a/1-js/05-data-types/11-date/5-last-day-of-month/solution.md b/1-js/05-data-types/11-date/5-last-day-of-month/solution.md new file mode 100644 index 0000000000..8f0d9ed524 --- /dev/null +++ b/1-js/05-data-types/11-date/5-last-day-of-month/solution.md @@ -0,0 +1,16 @@ +Создадим дату из следующего месяца, но в день передадим 0: +```js run demo +function getLastDayOfMonth(year, month) { + let date = new Date(year, month + 1, 0); + return date.getDate(); +} + +alert( getLastDayOfMonth(2012, 0) ); // 31 +alert( getLastDayOfMonth(2012, 1) ); // 29 +alert( getLastDayOfMonth(2013, 1) ); // 28 +``` + +Обычно даты начинаются с 1, но технически возможно передать любое число, и дата сама себя поправит. Так что если передать 0, то это значение будет соответствовать "один день перед первым числом месяца", другими словами: "последнее число прошлого месяца". + + + diff --git a/1-js/05-data-types/11-date/5-last-day-of-month/task.md b/1-js/05-data-types/11-date/5-last-day-of-month/task.md new file mode 100644 index 0000000000..db9d1cc016 --- /dev/null +++ b/1-js/05-data-types/11-date/5-last-day-of-month/task.md @@ -0,0 +1,14 @@ +importance: 5 + +--- + +# Последнее число месяца? + +Напишите функцию `getLastDayOfMonth(year, month)`, возвращающую последнее число месяца. Иногда это 30, 31 или даже февральские 28/29. + +Параметры: + +- `year` -- год из четырёх цифр, например, 2012. +- `month` -- месяц от 0 до 11. + +К примеру, `getLastDayOfMonth(2012, 1) = 29` (високосный год, февраль). diff --git a/1-js/05-data-types/11-date/6-get-seconds-today/solution.md b/1-js/05-data-types/11-date/6-get-seconds-today/solution.md new file mode 100644 index 0000000000..05412bd44c --- /dev/null +++ b/1-js/05-data-types/11-date/6-get-seconds-today/solution.md @@ -0,0 +1,26 @@ +Чтобы получить количество секунд, нужно сгенерировать объект `date` на самое начало текущего дня -- 00:00:00, а затем вычесть полученное значение из "сейчас". + +Разность даст нам количество миллисекунд с начала дня, делим его на 1000 и получаем секунды: + +```js run +function getSecondsToday() { + let now = new Date(); + + // создаём объект с текущими днём/месяцем/годом + let today = new Date(now.getFullYear(), now.getMonth(), now.getDate()); + + let diff = now - today; // разница в миллисекундах + return Math.round(diff / 1000); // получаем секунды +} + +alert( getSecondsToday() ); +``` + +В качестве альтернативного решения можно получить часы/минуты и преобразовать их в секунды: + +```js run +function getSecondsToday() { + let d = new Date(); + return d.getHours() * 3600 + d.getMinutes() * 60 + d.getSeconds(); +} +``` diff --git a/1-js/05-data-types/11-date/6-get-seconds-today/task.md b/1-js/05-data-types/11-date/6-get-seconds-today/task.md new file mode 100644 index 0000000000..46df738e26 --- /dev/null +++ b/1-js/05-data-types/11-date/6-get-seconds-today/task.md @@ -0,0 +1,15 @@ +importance: 5 + +--- + +# Сколько сегодня прошло секунд? + +Напишите функцию `getSecondsToday()`, возвращающую количество секунд с начала сегодняшнего дня. + +Например, если сейчас `10:00`, и не было перехода на зимнее/летнее время, то: + +```js +getSecondsToday() == 36000 // (3600 * 10) +``` + +Функция должна работать в любой день, т.е. в ней не должно быть конкретного значения сегодняшней даты. diff --git a/1-js/05-data-types/11-date/7-get-seconds-to-tomorrow/solution.md b/1-js/05-data-types/11-date/7-get-seconds-to-tomorrow/solution.md new file mode 100644 index 0000000000..f60fb4e17b --- /dev/null +++ b/1-js/05-data-types/11-date/7-get-seconds-to-tomorrow/solution.md @@ -0,0 +1,32 @@ +Чтобы получить количество миллисекунд до завтра, можно из "завтра 00:00:00" вычесть текущую дату. + +Сперва сгенерируем дату на "завтра" и сделаем следующее: + +```js run +function getSecondsToTomorrow() { + let now = new Date(); + + // завтрашняя дата + let tomorrow = new Date(now.getFullYear(), now.getMonth(), *!*now.getDate()+1*/!*); + + let diff = tomorrow - now; // разница в миллисекундах + return Math.round(diff / 1000); // преобразуем в секунды +} +``` + +Альтернативное решение: + +```js run +function getSecondsToTomorrow() { + let now = new Date(); + let hour = now.getHours(); + let minutes = now.getMinutes(); + let seconds = now.getSeconds(); + let totalSecondsToday = (hour * 60 + minutes) * 60 + seconds; + let totalSecondsInADay = 86400; + + return totalSecondsInADay - totalSecondsToday; +} +``` + +Учтите, что многие страны переходят с зимнего времени на летнее и обратно, так что могут быть дни длительностью в 23 или 25 часов. Такие дни, если это важно, можно обрабатывать отдельно. diff --git a/1-js/05-data-types/11-date/7-get-seconds-to-tomorrow/task.md b/1-js/05-data-types/11-date/7-get-seconds-to-tomorrow/task.md new file mode 100644 index 0000000000..6a67edec4c --- /dev/null +++ b/1-js/05-data-types/11-date/7-get-seconds-to-tomorrow/task.md @@ -0,0 +1,15 @@ +importance: 5 + +--- + +# Сколько секунд осталось до завтра? + +Создайте функцию `getSecondsToTomorrow()`, возвращающую количество секунд до завтрашней даты. + +Например, если сейчас `23:00`, то: + +```js +getSecondsToTomorrow() == 3600 +``` + +P.S. Функция должна работать в любой день, т.е. в ней не должно быть конкретного значения сегодняшней даты. diff --git a/1-js/05-data-types/11-date/8-format-date-relative/_js.view/solution.js b/1-js/05-data-types/11-date/8-format-date-relative/_js.view/solution.js new file mode 100644 index 0000000000..e1b65613de --- /dev/null +++ b/1-js/05-data-types/11-date/8-format-date-relative/_js.view/solution.js @@ -0,0 +1,33 @@ + +function formatDate(date) { + let diff = new Date() - date; // разница в миллисекундах + + if (diff < 1000) { // менее 1 секунды + return 'right now'; + } + + let sec = Math.floor(diff / 1000); // преобразовать разницу в секунды + + if (sec < 60) { + return sec + ' sec. ago'; + } + + let min = Math.floor(diff / 60000); // преобразовать разницу в минуты + if (min < 60) { + return min + ' min. ago'; + } + + // отформатировать дату + // добавить ведущие нули к единственной цифре дню/месяцу/часам/минутам + let d = date; + d = [ + '0' + d.getDate(), + '0' + (d.getMonth() + 1), + '' + d.getFullYear(), + '0' + d.getHours(), + '0' + d.getMinutes() + ].map(component => component.slice(-2)); // взять последние 2 цифры из каждой компоненты + + // соединить компоненты в дату + return d.slice(0, 3).join('.') + ' ' + d.slice(3).join(':'); +} diff --git a/1-js/05-data-types/11-date/8-format-date-relative/_js.view/test.js b/1-js/05-data-types/11-date/8-format-date-relative/_js.view/test.js new file mode 100644 index 0000000000..8e00baaa9b --- /dev/null +++ b/1-js/05-data-types/11-date/8-format-date-relative/_js.view/test.js @@ -0,0 +1,18 @@ +describe("formatDate", function() { + it("отображает 1 мс назад как \"прямо сейчас\"", function() { + assert.equal(formatDate(new Date(new Date - 1)), 'прямо сейчас'); + }); + + it('"30 секунд назад"', function() { + assert.equal(formatDate(new Date(new Date - 30 * 1000)), "30 сек. назад"); + }); + + it('"5 минут назад"', function() { + assert.equal(formatDate(new Date(new Date - 5 * 60 * 1000)), "5 мин. назад"); + }); + + it("более поздние даты в виде DD.MM.YY HH:mm", function() { + assert.equal(formatDate(new Date(2014, 2, 1, 11, 22, 33)), "01.03.14 11:22"); + }); + +}); diff --git a/1-js/05-data-types/11-date/8-format-date-relative/solution.md b/1-js/05-data-types/11-date/8-format-date-relative/solution.md new file mode 100644 index 0000000000..a98fe45c8a --- /dev/null +++ b/1-js/05-data-types/11-date/8-format-date-relative/solution.md @@ -0,0 +1,78 @@ +Чтобы получить время с `date` по текущий момент, нужно вычесть даты. + +```js run demo +function formatDate(date) { + let diff = new Date() - date; // разница в миллисекундах + + if (diff < 1000) { // меньше 1 секунды + return 'прямо сейчас'; + } + + let sec = Math.floor(diff / 1000); // преобразовать разницу в секунды + + if (sec < 60) { + return sec + ' сек. назад'; + } + + let min = Math.floor(diff / 60000); // преобразовать разницу в минуты + if (min < 60) { + return min + ' мин. назад'; + } + + // отформатировать дату + // добавить ведущие нули к единственной цифре дню/месяцу/часам/минутам + let d = date; + d = [ + '0' + d.getDate(), + '0' + (d.getMonth() + 1), + '' + d.getFullYear(), + '0' + d.getHours(), + '0' + d.getMinutes() + ].map(component => component.slice(-2)); // взять последние 2 цифры из каждой компоненты + + // соединить компоненты в дату + return d.slice(0, 3).join('.') + ' ' + d.slice(3).join(':'); +} + +alert( formatDate(new Date(new Date - 1)) ); // "прямо сейчас" + +alert( formatDate(new Date(new Date - 30 * 1000)) ); // "30 сек. назад" + +alert( formatDate(new Date(new Date - 5 * 60 * 1000)) ); // "5 мин. назад" + +// вчерашняя дата вроде 31.12.2016, 20:00 +alert( formatDate(new Date(new Date - 86400 * 1000)) ); +``` + +Альтернативное решение: + +```js run +function formatDate(date) { + let dayOfMonth = date.getDate(); + let month = date.getMonth() + 1; + let year = date.getFullYear(); + let hour = date.getHours(); + let minutes = date.getMinutes(); + let diffMs = new Date() - date; + let diffSec = Math.round(diffMs / 1000); + let diffMin = diffSec / 60; + let diffHour = diffMin / 60; + + // форматирование + year = year.toString().slice(-2); + month = month < 10 ? '0' + month : month; + dayOfMonth = dayOfMonth < 10 ? '0' + dayOfMonth : dayOfMonth; + hour = hour < 10 ? '0' + hour : hour; + minutes = minutes < 10 ? '0' + minutes : minutes; + + if (diffSec < 1) { + return 'прямо сейчас'; + } else if (diffMin < 1) { + return `${diffSec} сек. назад` + } else if (diffHour < 1) { + return `${diffMin} мин. назад` + } else { + return `${dayOfMonth}.${month}.${year} ${hour}:${minutes}` + } +} +``` diff --git a/1-js/05-data-types/11-date/8-format-date-relative/task.md b/1-js/05-data-types/11-date/8-format-date-relative/task.md new file mode 100644 index 0000000000..0ecf640e2c --- /dev/null +++ b/1-js/05-data-types/11-date/8-format-date-relative/task.md @@ -0,0 +1,25 @@ +importance: 4 + +--- + +# Форматирование относительной даты + +Напишите функцию `formatDate(date)`, форматирующую `date` по следующему принципу: + +- Если спустя `date` прошло менее 1 секунды, вывести `"прямо сейчас"`. +- В противном случае, если с `date` прошло меньше 1 минуты, вывести `"n сек. назад"`. +- В противном случае, если меньше часа, вывести `"m мин. назад"`. +- В противном случае, полная дата в формате `"DD.MM.YY HH:mm"`. А именно: `"день.месяц.год часы:минуты"`, всё в виде двух цифр, т.е. `31.12.16 10:00`. + +Например: + +```js +alert( formatDate(new Date(new Date - 1)) ); // "прямо сейчас" + +alert( formatDate(new Date(new Date - 30 * 1000)) ); // "30 сек. назад" + +alert( formatDate(new Date(new Date - 5 * 60 * 1000)) ); // "5 мин. назад" + +// вчерашняя дата вроде 31.12.2016, 20:00 +alert( formatDate(new Date(new Date - 86400 * 1000)) ); +``` diff --git a/1-js/05-data-types/11-date/article.md b/1-js/05-data-types/11-date/article.md new file mode 100644 index 0000000000..40402e0f92 --- /dev/null +++ b/1-js/05-data-types/11-date/article.md @@ -0,0 +1,433 @@ +# Дата и время + +Встречайте новый встроенный объект: [Date](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Date). Он содержит дату и время, а также предоставляет методы управления ими. + +Например, его можно использовать для хранения времени создания/изменения, для измерения времени или просто для вывода текущей даты. + +## Создание + +Для создания нового объекта `Date` нужно вызвать конструктор `new Date()` с одним из следующих аргументов: + +`new Date()` +: Без аргументов -- создать объект `Date` с текущими датой и временем: + + ```js run + let now = new Date(); + alert( now ); // показывает текущие дату и время + ``` + +`new Date(milliseconds)` +: Создать объект `Date` с временем, равным количеству миллисекунд (тысячная доля секунды), прошедших с 1 января 1970 года UTC+0. + + ```js run + // 0 соответствует 01.01.1970 UTC+0 + let Jan01_1970 = new Date(0); + alert( Jan01_1970 ); + + // теперь добавим 24 часа и получим 02.01.1970 UTC+0 + let Jan02_1970 = new Date(24 * 3600 * 1000); + alert( Jan02_1970 ); + ``` + + Целое число, представляющее собой количество миллисекунд, прошедших с начала 1970 года, называется *таймстамп* (англ. timestamp). + + Это -- легковесное численное представление даты. Из таймстампа всегда можно получить дату с помощью `new Date(timestamp)` и преобразовать существующий объект `Date` в таймстамп, используя метод `date.getTime()` (см. ниже). + + Датам до 1 января 1970 будут соответствовать отрицательные таймстампы, например: + ```js run + // 31 декабря 1969 года + let Dec31_1969 = new Date(-24 * 3600 * 1000); + alert( Dec31_1969 ); + ``` + +`new Date(datestring)` +: Если аргумент всего один, и это строка, то из неё "прочитывается" дата. Алгоритм разбора - такой же, как в `Date.parse`, который мы рассмотрим позже. + + ```js run + let date = new Date("2017-01-26"); + alert(date); + // Время не указано, поэтому оно ставится в полночь по Гринвичу и + // меняется в соответствии с часовым поясом места выполнения кода + // Так что в результате можно получить + // Thu Jan 26 2017 11:00:00 GMT+1100 (восточно-австралийское время) + // или + // Wed Jan 25 2017 16:00:00 GMT-0800 (тихоокеанское время) + ``` + +`new Date(year, month, date, hours, minutes, seconds, ms)` +: Создать объект `Date` с заданными компонентами в местном часовом поясе. Обязательны только первые два аргумента. + + - `year` должен состоять из четырёх цифр. Для совместимости также принимаются 2 цифры и рассматриваются как `19xx`, к примеру, `98` здесь это тоже самое, что и `1998`, но настоятельно рекомендуется всегда использовать 4 цифры. + - `month` начинается с `0` (январь) по `11` (декабрь). + - Параметр `date` здесь представляет собой день месяца. Если параметр не задан, то принимается значение `1`. + - Если параметры `hours/minutes/seconds/ms` отсутствуют, их значением становится `0`. + + Например: + + ```js + new Date(2011, 0, 1, 0, 0, 0, 0); // // 1 Jan 2011, 00:00:00 + new Date(2011, 0, 1); // то же самое, так как часы и проч. равны 0 + ``` + + Максимальная точность – 1 мс (до 1/1000 секунды): + + ```js run + let date = new Date(2011, 0, 1, 2, 3, 4, 567); + alert( date ); // 1.01.2011, 02:03:04.567 + ``` + +## Получение компонентов даты + +Существуют методы получения года, месяца и т.д. из объекта `Date`: + +[getFullYear()](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Date/getFullYear) +: Получить год (4 цифры) + +[getMonth()](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Date/getMonth) +: Получить месяц, **от 0 до 11**. + +[getDate()](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Date/getDate) +: Получить день месяца, от 1 до 31, что несколько противоречит названию метода. + +[getHours()](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Date/getHours), [getMinutes()](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Date/getMinutes), [getSeconds()](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Date/getSeconds), [getMilliseconds()](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Date/getMilliseconds) +: Получить, соответственно, часы, минуты, секунды или миллисекунды. + +```warn header="Никакого `getYear()`. Только `getFullYear()`" +Многие интерпретаторы JavaScript реализуют нестандартный и устаревший метод `getYear()`, который порой возвращает год в виде двух цифр. Пожалуйста, обходите его стороной. Если нужно значение года, используйте `getFullYear()`. +``` + +Кроме того, можно получить определённый день недели: + +[getDay()](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Date/getDay) +: Вернуть день недели от `0` (воскресенье) до `6` (суббота). Несмотря на то, что в ряде стран за первый день недели принят понедельник, в JavaScript начало недели приходится на воскресенье. + +**Все вышеперечисленные методы возвращают значения в соответствии с местным часовым поясом.** + +Однако существуют и их UTC-варианты, возвращающие день, месяц, год для временной зоны UTC+0: [getUTCFullYear()](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Date/getUTCFullYear), [getUTCMonth()](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Date/getUTCMonth), [getUTCDay()](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Date/getUTCDay). Для их использования требуется после `"get"` подставить `"UTC"`. + +Если ваш местный часовой пояс смещён относительно UTC, то следующий код покажет разные часы: + +```js run +// текущая дата +let date = new Date(); + +// час в вашем текущем часовом поясе +alert( date.getHours() ); + +// час в часовом поясе UTC+0 (лондонское время без перехода на летнее время) +alert( date.getUTCHours() ); +``` + +Помимо вышеприведённых методов, существуют два особых метода без UTC-варианта: + +[getTime()](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Date/getTime) +: Для заданной даты возвращает таймстамп - количество миллисекунд, прошедших с 1 января 1970 года UTC+0. + +[getTimezoneOffset()](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Date/getTimezoneOffset) +: Возвращает разницу в минутах между UTC и местным часовым поясом: + + ```js run + // если вы в часовом поясе UTC-1, то выводится 60 + // если вы в часовом поясе UTC+3, выводится -180 + alert( new Date().getTimezoneOffset() ); + + ``` + +## Установка компонентов даты + +Следующие методы позволяют установить компоненты даты и времени: + +- [`setFullYear(year, [month], [date])`](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Date/setFullYear) +- [`setMonth(month, [date])`](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Date/setMonth) +- [`setDate(date)`](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Date/setDate) +- [`setHours(hour, [min], [sec], [ms])`](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Date/setHours) +- [`setMinutes(min, [sec], [ms])`](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Date/setMinutes) +- [`setSeconds(sec, [ms])`](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Date/setSeconds) +- [`setMilliseconds(ms)`](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Date/setMilliseconds) +- [`setTime(milliseconds)`](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Date/setTime) (устанавливает дату в виде целого количества миллисекунд, прошедших с 01.01.1970 UTC) + +У всех этих методов, кроме `setTime()`, есть UTC-вариант, например: `setUTCHours()`. + +Как мы видим, некоторые методы могут устанавливать сразу несколько компонентов даты, например: `setHours`. Если какая-то компонента не указана, она не меняется. + +Пример: + +```js run +let today = new Date(); + +today.setHours(0); +alert(today); // выводится сегодняшняя дата, но значение часа будет 0 + +today.setHours(0, 0, 0, 0); +alert(today); // всё ещё выводится сегодняшняя дата, но время будет ровно 00:00:00. +``` + +## Автоисправление даты + +*Автоисправление* -- это очень полезная особенность объектов `Date`. Можно устанавливать компоненты даты вне обычного диапазона значений, а объект сам себя исправит. + +Пример: + +```js run +let date = new Date(2013, 0, *!*32*/!*); // 32 Jan 2013 ?!? +alert(date); // ...1st Feb 2013! +``` + +Неправильные компоненты даты автоматически распределяются по остальным. + +Предположим, нам требуется увеличить дату "28 февраля 2016" на два дня. В зависимости от того, високосный это год или нет, результатом будет "2 марта" или "1 марта". Нам об этом думать не нужно. Просто прибавляем два дня. Объект `Date` позаботится об остальном: + +```js run +let date = new Date(2016, 1, 28); +*!* +date.setDate(date.getDate() + 2); +*/!* + +alert( date ); // 1 Mar 2016 +``` + +Эту возможность часто используют, чтобы получить дату по прошествии заданного отрезка времени. Например, получим дату "спустя 70 секунд с текущего момента": + +```js run +let date = new Date(); +date.setSeconds(date.getSeconds() + 70); + +alert( date ); // выводит правильную дату +``` + +Также можно установить нулевые или даже отрицательные значения. Например: + +```js run +let date = new Date(2016, 0, 2); // 2 Jan 2016 + +date.setDate(1); // задать первое число месяца +alert( date ); + +date.setDate(0); // первый день месяца -- это 1, так что выводится последнее число предыдущего месяца +alert( date ); // 31 Dec 2015 +``` + +## Преобразование к числу, разность дат + +Если объект `Date` преобразовать в число, то получим таймстамп по аналогии с `date.getTime()`: + +```js run +let date = new Date(); +alert(+date); // количество миллисекунд, то же самое, что date.getTime() +``` + +Важный побочный эффект: даты можно вычитать, в результате получаем разность в миллисекундах. + +Этот приём можно использовать для измерения времени: + +```js run +let start = new Date(); // начинаем отсчёт времени + +// выполняем некоторые действия +for (let i = 0; i < 100000; i++) { + let doSomething = i * i * i; +} + +let end = new Date(); // заканчиваем отсчёт времени + +alert( `Цикл отработал за ${end - start} миллисекунд` ); +``` + +## Date.now() + +Если нужно просто измерить время, объект `Date` нам не нужен. + +Существует особый метод `Date.now()`, возвращающий текущую метку времени. + +Семантически он эквивалентен `new Date().getTime()`, однако метод не создаёт промежуточный объект `Date`. Так что этот способ работает быстрее и не нагружает сборщик мусора. + +Данный метод используется из соображений удобства или когда важно быстродействие, например, при разработке игр на JavaScript или других специализированных приложений. + +Вероятно, предыдущий пример лучше переписать так: + +```js run +*!* +let start = Date.now(); // количество миллисекунд с 1 января 1970 года +*/!* + +// выполняем некоторые действия +for (let i = 0; i < 100000; i++) { + let doSomething = i * i * i; +} + +*!* +let end = Date.now(); // заканчиваем отсчёт времени +*/!* + +alert( `Цикл отработал за ${end - start} миллисекунд` ); // вычитаются числа, а не даты +``` + +## Бенчмаркинг + +Будьте внимательны, если хотите точно протестировать производительность функции, которая зависит от процессора. + +Например, сравним две функции, вычисляющие разницу между двумя датами: какая сработает быстрее? + +Подобные вычисления, замеряющие производительность, также называют "бенчмарками" (benchmark). + +```js +// есть date1 и date2, какая функция быстрее вернёт разницу между ними в миллисекундах? +function diffSubtract(date1, date2) { + return date2 - date1; +} + +// или +function diffGetTime(date1, date2) { + return date2.getTime() - date1.getTime(); +} +``` + +Обе функции делают буквально одно и то же, только одна использует явный метод `date.getTime()` для получения даты в миллисекундах, а другая полагается на преобразование даты в число. Результат их работы всегда один и тот же. + +Но какая функция быстрее? + +Для начала можно запустить их много раз подряд и засечь разницу. В нашем случае функции очень простые, так что потребуется хотя бы 100000 повторений. + +Проведём измерения: + +```js run +function diffSubtract(date1, date2) { + return date2 - date1; +} + +function diffGetTime(date1, date2) { + return date2.getTime() - date1.getTime(); +} + +function bench(f) { + let date1 = new Date(0); + let date2 = new Date(); + + let start = Date.now(); + for (let i = 0; i < 100000; i++) f(date1, date2); + return Date.now() - start; +} + +alert( 'Время diffSubtract: ' + bench(diffSubtract) + 'мс' ); +alert( 'Время diffGetTime: ' + bench(diffGetTime) + 'мс' ); +``` + +Вот это да! Метод `getTime()` работает ощутимо быстрее! Всё потому, что не производится преобразование типов, и интерпретаторам такое намного легче оптимизировать. + +Замечательно, это уже что-то. Но до хорошего бенчмарка нам ещё далеко. + +Представьте, что при выполнении `bench(diffSubtract)` процессор параллельно делал что-то ещё, также потребляющее ресурсы. А к началу выполнения `bench(diffGetTime)` он это уже завершил. + +Достаточно реалистичный сценарий в современных многопроцессорных операционных системах. + +В итоге у первого бенчмарка окажется меньше ресурсов процессора, чем у второго. Это может исказить результаты. + +**Для получения наиболее достоверных результатов тестирования производительности весь набор бенчмарков нужно запускать по нескольку раз.** + +Например, так: + +```js run +function diffSubtract(date1, date2) { + return date2 - date1; +} + +function diffGetTime(date1, date2) { + return date2.getTime() - date1.getTime(); +} + +function bench(f) { + let date1 = new Date(0); + let date2 = new Date(); + + let start = Date.now(); + for (let i = 0; i < 100000; i++) f(date1, date2); + return Date.now() - start; +} + +let time1 = 0; +let time2 = 0; + +*!* +// bench(diffSubtract) и bench(diffGetTime) поочерёдно запускаются 10 раз +for (let i = 0; i < 10; i++) { + time1 += bench(diffSubtract); + time2 += bench(diffGetTime); +} +*/!* + +alert( 'Итоговое время diffSubtract: ' + time1 ); +alert( 'Итоговое время diffGetTime: ' + time2 ); +``` + +Современные интерпретаторы JavaScript начинают применять продвинутые оптимизации только к "горячему коду", выполняющемуся несколько раз (незачем оптимизировать то, что редко выполняется). Так что в примере выше первые запуски не оптимизированы должным образом. Нелишним будет добавить предварительный запуск для "разогрева": + +```js +// добавляем для "разогрева" перед основным циклом +bench(diffSubtract); +bench(diffGetTime); + +// а теперь тестируем производительность +for (let i = 0; i < 10; i++) { + time1 += bench(diffSubtract); + time2 += bench(diffGetTime); +} +``` + +```warn header="Будьте осторожны с микробенчмарками" +Современные интерпретаторы JavaScript выполняют множество оптимизаций. Они могут повлиять на результаты "искусственных тестов" по сравнению с "нормальным использованием", особенно если мы тестируем что-то очень маленькое, например, работу оператора или встроенной функции. Поэтому если хотите серьёзно понять производительность, пожалуйста, изучите, как работают интерпретаторы JavaScript. И тогда вам, вероятно, уже не понадобятся микробенчмарки. + +Отличный набор статей о V8 можно найти на . +``` + +## Разбор строки с датой + +Метод [Date.parse(str)](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Date/parse) считывает дату из строки. + +Формат строки должен быть следующим: `YYYY-MM-DDTHH:mm:ss.sssZ`, где: + +- `YYYY-MM-DD` -- это дата: год-месяц-день. +- Символ `"T"` используется в качестве разделителя. +- `HH:mm:ss.sss` -- время: часы, минуты, секунды и миллисекунды. +- Необязательная часть `'Z'` обозначает часовой пояс в формате `+-hh:mm`. Если указать просто букву `Z`, то получим UTC+0. + +Возможны и более короткие варианты, например, `YYYY-MM-DD` или `YYYY-MM`, или даже `YYYY`. + +Вызов `Date.parse(str)` обрабатывает строку в заданном формате и возвращает таймстамп (количество миллисекунд с 1 января 1970 года UTC+0). Если формат неправильный, возвращается `NaN`. + +Например: + +```js run +let ms = Date.parse('2012-01-26T13:51:50.417-07:00'); + +alert(ms); // 1327611110417 (таймстамп) +``` + +Можно тут же создать объект `new Date` из таймстампа: + +```js run +let date = new Date( Date.parse('2012-01-26T13:51:50.417-07:00') ); + +alert(date); +``` + +## Итого + +- Дата и время в JavaScript представлены объектом [Date](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Date). Нельзя создать "только дату" или "только время": объекты `Date` всегда содержат и то, и другое. +- Счёт месяцев начинается с нуля (да, январь -- это нулевой месяц). +- Дни недели в `getDay()` также отсчитываются с нуля, что соответствует воскресенью. +- Объект `Date` самостоятельно корректируется при введении значений, выходящих за рамки допустимых. Это полезно для сложения/вычитания дней/месяцев/недель. +- Даты можно вычитать, их разность возвращается в миллисекундах. Так происходит, потому что при преобразовании в число объект `Date` становится таймстампом. +- Используйте `Date.now()` для быстрого получения текущего времени в формате таймстампа. + +Учтите, что, в отличие от некоторых других систем, в JavaScript таймстамп в миллисекундах, а не в секундах. + +Порой нам нужно измерить время с большей точностью. Собственными средствами JavaScript измерять время в микросекундах (одна миллионная секунды) нельзя, но в большинстве сред такая возможность есть. К примеру, в браузерах есть метод [performance.now()](https://developer.mozilla.org/ru/docs/Web/API/Performance/now), возвращающий количество миллисекунд с начала загрузки страницы с точностью до микросекунд (3 цифры после точки): + +```js run +alert(`Загрузка началась ${performance.now()}мс назад`); +// Получаем что-то вроде: "Загрузка началась 34731.26000000001мс назад" +// .26 –- это микросекунды (260 микросекунд) +// корректными являются только первые три цифры после точки, а остальные -- это ошибка точности +``` + +В Node.js для этого предусмотрен модуль `microtime` и ряд других способов. Технически почти любое устройство или среда позволяет добиться большей точности, просто её нет в объекте `Date`. diff --git a/1-js/05-data-types/11-json/1-serialize-object/task.md b/1-js/05-data-types/11-json/1-serialize-object/task.md deleted file mode 100644 index 5a949d6968..0000000000 --- a/1-js/05-data-types/11-json/1-serialize-object/task.md +++ /dev/null @@ -1,14 +0,0 @@ -importance: 5 - ---- - -# Преобразуйте объект в JSON, а затем обратно в обычный объект - -Преобразуйте `user` в JSON, затем поместите в новую переменную изначальный объект. - -```js -let user = { - name: "Василий Иванович", - age: 35 -}; -``` diff --git a/1-js/05-data-types/11-json/2-serialize-event-circular/solution.md b/1-js/05-data-types/11-json/2-serialize-event-circular/solution.md deleted file mode 100644 index 05e9365d39..0000000000 --- a/1-js/05-data-types/11-json/2-serialize-event-circular/solution.md +++ /dev/null @@ -1,29 +0,0 @@ - -```js run -let room = { - number: 23 -}; - -let meetup = { - title: "Совещание", - occupiedBy: [{name: "Иванов"}, {name: "Петров"}], - place: room -}; - -room.occupiedBy = meetup; -meetup.self = meetup; - -alert( JSON.stringify(meetup, function replacer(key, value) { - return (key != "" && value == meetup) ? undefined : value; -})); - -/* -{ - "title":"Совещание", - "occupiedBy":[{"name":"Иванов"},{"name":"Петров"}], - "place":{"number":23} -} -*/ -``` - -Здесь нам также нужно проверить `key ==""`, чтобы исключить первый вызов, где значение `value` равно `meetup`. diff --git a/1-js/05-data-types/11-json/2-serialize-event-circular/task.md b/1-js/05-data-types/11-json/2-serialize-event-circular/task.md deleted file mode 100644 index 60373a464a..0000000000 --- a/1-js/05-data-types/11-json/2-serialize-event-circular/task.md +++ /dev/null @@ -1,41 +0,0 @@ -importance: 5 - ---- - -# Исключить обратные ссылки - -В простых случаях циклических ссылок мы можем исключить нарушающее свойство из сериализации по его имени. - -Но иногда есть много обратных ссылок. И имена могут использоваться как в циклических ссылках, так и в обычных свойствах. - -Напишите функцию `replacer` для JSON-преобразования, которая удалит свойства, ссылающиеся на `meetup`: - -```js run -let room = { - number: 23 -}; - -let meetup = { - title: "Совещание", - occupiedBy: [{name: "Иванов"}, {name: "Петров"}], - place: room -}; - -*!* -// цикличные ссылки -room.occupiedBy = meetup; -meetup.self = meetup; -*/!* - -alert( JSON.stringify(meetup, function replacer(key, value) { - /* ваш код */ -})); - -/* в результате должно быть: -{ - "title":"Совещание", - "occupiedBy":[{"name":"Иванов"},{"name":"Петров"}], - "place":{"number":23} -} -*/ -``` diff --git a/1-js/05-data-types/11-json/article.md b/1-js/05-data-types/11-json/article.md deleted file mode 100644 index f5de1a7756..0000000000 --- a/1-js/05-data-types/11-json/article.md +++ /dev/null @@ -1,527 +0,0 @@ -# Формат JSON, метод toJSON - -Допустим, у нас есть сложный объект, и мы хотели бы преобразовать его в строку, чтобы отправить по сети или просто вывести для логирования. - -Естественно, такая строка должна включать в себя все важные свойства. - -Мы могли бы реализовать преобразование следующим образом: - -```js run -let user = { - name: "John", - age: 30, - -*!* - toString() { - return `{name: "${this.name}", age: ${this.age}}`; - } -*/!* -}; - -alert(user); // {name: "John", age: 30} -``` - -...Но в процессе разработки добавляются новые свойства, старые свойства переименовываются и удаляются. Обновление такого `toString` каждый раз может стать проблемой. Мы могли бы попытаться перебрать свойства в нем, но что, если объект является сложным и имеет вложенные объекты в свойствах? Мы должны были бы также осуществить их преобразование. И, если мы отправляем объект по сети, нам также нужно предоставить код для "чтения" нашего объекта на принимающей стороне. - -К счастью, нет необходимости писать код для обработки всего этого. У задачи есть простое решение. - -## JSON.stringify - -[JSON](https://ru.wikipedia.org/wiki/JSON) (JavaScript Object Notation) - это общий формат для представления значений и объектов. Его описание задокументировано в стандарте [RFC 4627](http://tools.ietf.org/html/rfc4627). Первоначально он был создан для JavaScript, но многие другие языки также имеют библиотеки, которые могут работать с ним. Таким образом, JSON легко использовать для обмена данными, когда клиент использует JavaScript, а сервер написан на Ruby/PHP/Java/любой другой язык. - -JavaScript предоставляет методы: - -- `JSON.stringify` для преобразования объектов в JSON. -- `JSON.parse` для преобразования JSON обратно в объект. - -Например, здесь мы преобразуем через `JSON.stringify` данные студента: -```js run -let student = { - name: 'John', - age: 30, - isAdmin: false, - courses: ['html', 'css', 'js'], - wife: null -}; - -*!* -let json = JSON.stringify(student); -*/!* - -alert(typeof json); // мы получили строку! - -alert(json); -*!* -/* выведет объект в формате JSON: -{ - "name": "John", - "age": 30, - "isAdmin": false, - "courses": ["html", "css", "js"], - "wife": null -} -*/ -*/!* -``` - -Метод `JSON.stringify(student)` берет объект и преобразует его в строку. - -Полученная строка `json` называется *JSON-форматированным* или *сериализованным* объектом. Мы можем отправить его по сети или поместить в обычное хранилище данных. - - -Обратите внимание, что объект в формате JSON имеет несколько важных отличий от объектного литерала: - -- Строки используют двойные кавычки. Никаких одинарных кавычек или обратных кавычек в JSON. Так `'John'` становится `"John"`. -- Имена свойств объекта также заключаются в двойные кавычки. Это обязательно. Так `age:30` становится `"age":30`. - -`JSON.stringify` может быть применен и к примитивам. - -Собственно поддерживаемые типы JSON: - -- Объекты `{ ... }` -- Массивы `[ ... ]` -- Примитивы: - - строки, - - числа, - - логические значения `true/false`, - - `null`. - -Например: - -```js run -// число в JSON остается числом -alert( JSON.stringify(1) ) // 1 - -// строка в JSON по-прежнему остается строкой, но в двойных кавычках -alert( JSON.stringify('test') ) // "test" - -alert( JSON.stringify(true) ); // true - -alert( JSON.stringify([1, 2, 3]) ); // [1,2,3] -``` - -JSON является кросс-языковой спецификацией для данных, поэтому `JSON.stringify` пропускает некоторые специфические свойства объектов JavaScript. - -А именно: - -- Свойства-функции (методы). -- Символические свойства. -- Свойства, содержащие `undefined`. - -```js run -let user = { - sayHi() { // будет пропущено - alert("Hello"); - }, - [Symbol("id")]: 123, // также будет пропущено - something: undefined // как и это - пропущено -}; - -alert( JSON.stringify(user) ); // {} (пустой объект) -``` - -Обычно это нормально. Если это не то, что мы хотим, то скоро мы увидим, как можно настроить этот процесс. - -Самое замечательное, что вложенные объекты поддерживаются и конвертируются автоматически. - -Например: - -```js run -let meetup = { - title: "Conference", -*!* - room: { - number: 23, - participants: ["john", "ann"] - } -*/!* -}; - -alert( JSON.stringify(meetup) ); -/* вся структура преобразована в строку: -{ - "title":"Conference", - "room":{"number":23,"participants":["john","ann"]}, -} -*/ -``` - -Важное ограничение: не должно быть циклических ссылок. - -Например: - -```js run -let room = { - number: 23 -}; - -let meetup = { - title: "Conference", - participants: ["john", "ann"] -}; - -meetup.place = room; // meetup ссылается на room -room.occupiedBy = meetup; // room ссылается на meetup - -*!* -JSON.stringify(meetup); // Ошибка: Преобразование цикличной структуры в JSON -*/!* -``` - -Здесь преобразование завершается неудачно из-за циклической ссылки: `room.occupiedBy` ссылается на `meetup`, и `meetup.place` ссылается на `room`: - -![](json-meetup.png) - - -## Исключение и преобразование: replacer - -Полный синтаксис `JSON.stringify`: - -```js -let json = JSON.stringify(value[, replacer, space]) -``` - -value -: Значение для кодирования. - -replacer -: Массив свойств для кодирования или функции соответствия `function(key, value)`. - -space -: Дополнительное пространство (отступы), используемое для форматирования. - -В большинстве случаев `JSON.stringify` используется только с первым аргументом. Но если нам нужно настроить процесс замены, например, отфильтровать циклические ссылки, то можно использовать второй аргумент `JSON.stringify`. - -Если мы передадим ему массив свойств, будут закодированы только эти свойства. - -Например: - -```js run -let room = { - number: 23 -}; - -let meetup = { - title: "Conference", - participants: [{name: "John"}, {name: "Alice"}], - place: room // meetup ссылается на room -}; - -room.occupiedBy = meetup; // room ссылается на meetup - -alert( JSON.stringify(meetup, *!*['title', 'participants']*/!*) ); -// {"title":"Conference","participants":[{},{}]} -``` - -Здесь мы, наверно, слишком строги. Список свойств применяется ко всей структуре объекта. Так что свойство `participants` пустое, потому что `name` нет в списке. - -Давайте включим все свойства, кроме `room.occupiedBy`, из-за которого появляется цикличная ссылка: - -```js run -let room = { - number: 23 -}; - -let meetup = { - title: "Conference", - participants: [{name: "John"}, {name: "Alice"}], - place: room // meetup ссылается на room -}; - -room.occupiedBy = meetup; // room ссылается на meetup - -alert( JSON.stringify(meetup, *!*['title', 'participants', 'place', 'name', 'number']*/!*) ); -/* -{ - "title":"Conference", - "participants":[{"name":"John"},{"name":"Alice"}], - "place":{"number":23} -} -*/ -``` - -Теперь все, кроме `occupiedBy`, сериализовано. Но список свойств довольно длинный. - -К счастью, в качестве `replacer` мы можем использовать функцию, а не массив. - -Функция будет вызываться для каждой пары `(key, value)` и должна возвращать "замененное" значение, которое будет использоваться вместо исходного. - -В нашем случае мы можем вернуть `value` "как есть" для всего, кроме `occupiedBy`. Чтобы игнорировать `occupiedBy`, код ниже возвращает `undefined`: - -```js run -let room = { - number: 23 -}; - -let meetup = { - title: "Conference", - participants: [{name: "John"}, {name: "Alice"}], - place: room // meetup ссылается на room -}; - -room.occupiedBy = meetup; // room ссылается на meetup - -alert( JSON.stringify(meetup, function replacer(key, value) { - alert(`${key}: ${value}`); // чтобы посмотреть, что получит replacer - return (key == 'occupiedBy') ? undefined : value; -})); - -/* пары ключ:значение, которые приходят в replacer: -: [object Object] -title: Conference -participants: [object Object],[object Object] -0: [object Object] -name: John -1: [object Object] -name: Alice -place: [object Object] -number: 23 -*/ -``` - -Обратите внимание, что функция `replacer` получает каждую пару ключ/значение, включая вложенные объекты и элементы массива. И она применяется рекурсивно. Значение `this` внутри `replacer` - это объект, который содержит текущее свойство. - -Первый вызов - особенный. Ему передается специальный "объект-обертка": `{"": meetup}`. Другими словами, первая `(key, value)` пара имеет пустой ключ, а значением является целевой объект в общем. Вот почему первая строка из примера выше будет `":[object Object]"`. - -Идея состоит в том, чтобы переложить как можно больше действий на `replacer` - у него есть возможность проанализировать и заменить/пропустить весь объект целиком, если это необходимо. - - -## Форматирование: spacer - -Третий аргумент в `JSON.stringify(value, replacer, spaces)` - это количество пробелов, используемых для удобного форматирования. - -Ранее все JSON-форматированные объекты не имели отступов и лишних пробелов. Это нормально, если мы хотим отправить объект по сети. Аргумент `spacer` используется исключительно для вывода в удобочитаемом виде. - -Ниже `spacer = 2` указывает JavaScript отображать вложенные объекты в несколько строк с отступом в 2 пробела внутри объекта: - -```js run -let user = { - name: "John", - age: 25, - roles: { - isAdmin: false, - isEditor: true - } -}; - -alert(JSON.stringify(user, null, 2)); -/* отступ в 2 пробела: -{ - "name": "John", - "age": 25, - "roles": { - "isAdmin": false, - "isEditor": true - } -} -*/ - -/* для JSON.stringify(user, null, 4) результат содержал больше отступов: -{ - "name": "John", - "age": 25, - "roles": { - "isAdmin": false, - "isEditor": true - } -} -*/ -``` - -Параметр `spaces` применяется для логирования и красивого вывода. - -## Пользовательский "toJSON" - -Как и `toString` для преобразования строк, объект может предоставлять метод `toJSON` для преобразования в JSON. `JSON.stringify` автоматически вызывает его, если он есть. - -Например: - -```js run -let room = { - number: 23 -}; - -let meetup = { - title: "Conference", - date: new Date(Date.UTC(2017, 0, 1)), - room -}; - -alert( JSON.stringify(meetup) ); -/* - { - "title":"Conference", -*!* - "date":"2017-01-01T00:00:00.000Z", // (1) -*/!* - "room": {"number":23} // (2) - } -*/ -``` - -Как видим, `date` `(1)` стал строкой. Это потому, что все объекты имеют встроенный метод `toJSON`, который возвращает такую строку. - -Теперь давайте добавим собственную реализацию метода `toJSON` в наш объект `room` `(2)`: - -```js run -let room = { - number: 23, -*!* - toJSON() { - return this.number; - } -*/!* -}; - -let meetup = { - title: "Conference", - room -}; - -*!* -alert( JSON.stringify(room) ); // 23 -*/!* - -alert( JSON.stringify(meetup) ); -/* - { - "title":"Conference", -*!* - "room": 23 -*/!* - } -*/ -``` - -Теперь получается, что `toJSON` используется как при прямом вызове `JSON.stringify(room)`, так и на вложенном объекте. - - -## JSON.parse - -Чтобы декодировать JSON-строку, нам нужен другой метод с именем [JSON.parse](mdn:js/JSON/parse). - -Синтаксис: -```js -let value = JSON.parse(str[, reviver]); -``` - -str -: JSON для преобразования в объект. - -reviver -: Необязательная функция с параметрами (ключ, значение), которая будет вызываться для каждой пары `(key, value)` и может преобразовывать значение. - -Например: - -```js run -// строковый массив -let numbers = "[0, 1, 2, 3]"; - -numbers = JSON.parse(numbers); - -alert( numbers[1] ); // 1 -``` - -Или для вложенных объектов: - -```js run -let user = '{ "name": "John", "age": 35, "isAdmin": false, "friends": [0,1,2,3] }'; - -user = JSON.parse(user); - -alert( user.friends[1] ); // 1 -``` - -JSON может быть настолько сложным, насколько это необходимо, объекты и массивы могут включать другие объекты и массивы. Но они должны быть в том же формате. - -Вот типичные ошибки в написанном от руки JSON (иногда приходится писать его для отладки): - -```js -let json = `{ - *!*name*/!*: "John", // Ошибка: имя свойства без кавычек - "surname": *!*'Smith'*/!*, // Ошибка: одинарные кавычки в значении (должны быть двойными) - *!*'isAdmin'*/!*: false // Ошибка: одинарные кавычки в ключе (должны быть двойными) - "birthday": *!*new Date(2000, 2, 3)*/!*, // Ошибка: не допускается конструктор "new", только значения. - "friends": [0,1,2,3] // Здесь все в порядке -}`; -``` - -Кроме того, JSON не поддерживает комментарии. Добавление комментария в JSON делает его недействительным. - -Существует еще один формат [JSON5](http://json5.org/), который поддерживает ключи без кавычек, комментарии и т.д. Но это самостоятельная библиотека, а не спецификация языка. - -Обычный JSON настолько строг не потому, что его разработчики ленивы, а потому, что позволяет легко, надежно и очень быстро реализовывать алгоритм парсинга. - -## Использование reviver - -Представьте себе, мы получили объект `meetup` с сервера в виде строчки данных. - -Вот такой: - -```js -// title: (meetup title), date: (meetup date) -let str = '{"title":"Conference","date":"2017-11-30T12:00:00.000Z"}'; -``` - -...А теперь нам нужно *десериализовать* его, т.е. снова превратить в объект JavaScript. - -Давайте сделаем это, вызвав `JSON.parse`: - -```js run -let str = '{"title":"Conference","date":"2017-11-30T12:00:00.000Z"}'; - -let meetup = JSON.parse(str); - -*!* -alert( meetup.date.getDate() ); // Error! -*/!* -``` - -Оп, ошибка! - -Значением `meetup.date` является строка, а не `Date` объект. Как мог `JSON.parse` знать, что он должен преобразовать эту строку в `Date`? - -Давайте перейдем к функции восстановления `JSON.parse`, которая возвращает все значения "как есть", но `date` станет `Date`: - -```js run -let str = '{"title":"Conference","date":"2017-11-30T12:00:00.000Z"}'; - -*!* -let meetup = JSON.parse(str, function(key, value) { - if (key == 'date') return new Date(value); - return value; -}); -*/!* - -alert( meetup.date.getDate() ); // 30 - теперь работает! -``` - -Кстати, это работает и для вложенных объектов: - -```js run -let schedule = `{ - "meetups": [ - {"title":"Conference","date":"2017-11-30T12:00:00.000Z"}, - {"title":"Birthday","date":"2017-04-18T12:00:00.000Z"} - ] -}`; - -schedule = JSON.parse(schedule, function(key, value) { - if (key == 'date') return new Date(value); - return value; -}); - -*!* -alert( schedule.meetups[1].date.getDate() ); // 18 - отлично! -*/!* -``` - - - -## Итого - -- JSON - это формат данных, который имеет собственный независимый стандарт и библиотеки для большинства языков программирования. -- JSON поддерживает простые объекты, массивы, строки, числа, логические значения и `null`. -- JavaScript предоставляет методы [JSON.stringify](mdn:js/JSON/stringify) для сериализации в JSON и [JSON.parse](mdn:js/JSON/parse) для чтения из JSON. -- Оба метода поддерживают функции преобразования для интеллектуального чтения/записи. -- Если объект имеет метод `toJSON`, то он вызывается через `JSON.stringify`. diff --git a/1-js/05-data-types/11-json/json-meetup.png b/1-js/05-data-types/11-json/json-meetup.png deleted file mode 100644 index 2686664249..0000000000 Binary files a/1-js/05-data-types/11-json/json-meetup.png and /dev/null differ diff --git a/1-js/05-data-types/11-json/json-meetup@2x.png b/1-js/05-data-types/11-json/json-meetup@2x.png deleted file mode 100644 index a8f2cd6094..0000000000 Binary files a/1-js/05-data-types/11-json/json-meetup@2x.png and /dev/null differ diff --git a/1-js/05-data-types/11-json/1-serialize-object/solution.md b/1-js/05-data-types/12-json/1-serialize-object/solution.md similarity index 100% rename from 1-js/05-data-types/11-json/1-serialize-object/solution.md rename to 1-js/05-data-types/12-json/1-serialize-object/solution.md diff --git a/1-js/05-data-types/12-json/1-serialize-object/task.md b/1-js/05-data-types/12-json/1-serialize-object/task.md new file mode 100644 index 0000000000..c49b3f99e1 --- /dev/null +++ b/1-js/05-data-types/12-json/1-serialize-object/task.md @@ -0,0 +1,14 @@ +importance: 5 + +--- + +# Преобразуйте объект в JSON, а затем обратно в обычный объект + +Преобразуйте `user` в JSON, затем прочитайте этот JSON в другую переменную. + +```js +let user = { + name: "Василий Иванович", + age: 35 +}; +``` diff --git a/1-js/05-data-types/12-json/2-serialize-event-circular/solution.md b/1-js/05-data-types/12-json/2-serialize-event-circular/solution.md new file mode 100644 index 0000000000..0a6ebca77d --- /dev/null +++ b/1-js/05-data-types/12-json/2-serialize-event-circular/solution.md @@ -0,0 +1,31 @@ + +```js run +let room = { + number: 23 +}; + +let meetup = { + title: "Совещание", + occupiedBy: [{name: "Иванов"}, {name: "Петров"}], + place: room +}; + +room.occupiedBy = meetup; +meetup.self = meetup; + +alert( JSON.stringify(meetup, function replacer(key, value) { + return (key != "" && value == meetup) ? undefined : value; +})); + +/* +{ + "title":"Совещание", + "occupiedBy":[{"name":"Иванов"},{"name":"Петров"}], + "place":{"number":23} +} +*/ +``` + +Функция `replacer` будет вызвана для каждой пары `(key, value)`, и в первом вызове будет передан специальный «объект-обёртка»: `{"": meetup}`. + +Если мы реализуем только проверку `value == meetup`, то в результате получим `undefined`. Чтобы в первом вызове `replacer` не было удалено свойство, ссылающееся на `meetup`, нам также нужно добавить проверку `key != ""`. diff --git a/1-js/05-data-types/12-json/2-serialize-event-circular/task.md b/1-js/05-data-types/12-json/2-serialize-event-circular/task.md new file mode 100644 index 0000000000..930f49d63c --- /dev/null +++ b/1-js/05-data-types/12-json/2-serialize-event-circular/task.md @@ -0,0 +1,41 @@ +importance: 5 + +--- + +# Исключить обратные ссылки + +В простых случаях циклических ссылок мы можем исключить свойство, из-за которого они возникают, из сериализации по его имени. + +Но иногда мы не можем использовать имя, так как могут быть и другие, нужные, свойства с этим именем во вложенных объектах. Поэтому можно проверять свойство по значению. + +Напишите функцию `replacer` для JSON-преобразования, которая удалит свойства, ссылающиеся на `meetup`: + +```js run +let room = { + number: 23 +}; + +let meetup = { + title: "Совещание", + occupiedBy: [{name: "Иванов"}, {name: "Петров"}], + place: room +}; + +*!* +// цикличные ссылки +room.occupiedBy = meetup; +meetup.self = meetup; +*/!* + +alert( JSON.stringify(meetup, function replacer(key, value) { + /* ваш код */ +})); + +/* в результате должно быть: +{ + "title":"Совещание", + "occupiedBy":[{"name":"Иванов"},{"name":"Петров"}], + "place":{"number":23} +} +*/ +``` diff --git a/1-js/05-data-types/12-json/article.md b/1-js/05-data-types/12-json/article.md new file mode 100644 index 0000000000..8865c342d7 --- /dev/null +++ b/1-js/05-data-types/12-json/article.md @@ -0,0 +1,530 @@ +# Формат JSON, метод toJSON + +Допустим, у нас есть сложный объект, и мы хотели бы преобразовать его в строку, чтобы отправить по сети или просто вывести для логирования. + +Естественно, такая строка должна включать в себя все важные свойства. + +Мы могли бы реализовать преобразование следующим образом: + +```js run +let user = { + name: "John", + age: 30, + +*!* + toString() { + return `{name: "${this.name}", age: ${this.age}}`; + } +*/!* +}; + +alert(user); // {name: "John", age: 30} +``` + +...Но в процессе разработки добавляются новые свойства, старые свойства переименовываются и удаляются. Обновление такого `toString` каждый раз может стать проблемой. Мы могли бы попытаться перебрать свойства в нём, но что, если объект сложный, и в его свойствах имеются вложенные объекты? Мы должны были бы осуществить их преобразование тоже. + +К счастью, нет необходимости писать код для обработки всего этого. У задачи есть простое решение. + +## JSON.stringify + +[JSON](https://ru.wikipedia.org/wiki/JSON) (JavaScript Object Notation) - это общий формат для представления значений и объектов. Его описание задокументировано в стандарте [RFC 4627](https://datatracker.ietf.org/doc/html/rfc4627). Первоначально он был создан для JavaScript, но многие другие языки также имеют библиотеки, которые могут работать с ним. Таким образом, JSON легко использовать для обмена данными, когда клиент использует JavaScript, а сервер написан на Ruby/PHP/Java или любом другом языке. + +JavaScript предоставляет методы: + +- `JSON.stringify` для преобразования объектов в JSON. +- `JSON.parse` для преобразования JSON обратно в объект. + +Например, здесь мы преобразуем через `JSON.stringify` данные студента: +```js run +let student = { + name: 'John', + age: 30, + isAdmin: false, + courses: ['html', 'css', 'js'], + wife: null +}; + +*!* +let json = JSON.stringify(student); +*/!* + +alert(typeof json); // мы получили строку! + +alert(json); +*!* +/* выведет объект в формате JSON: +{ + "name": "John", + "age": 30, + "isAdmin": false, + "courses": ["html", "css", "js"], + "wife": null +} +*/ +*/!* +``` + +Метод `JSON.stringify(student)` берёт объект и преобразует его в строку. + +Полученная строка `json` называется *JSON-форматированным* или *сериализованным* объектом. Мы можем отправить его по сети или поместить в обычное хранилище данных. + +Обратите внимание, что объект в формате JSON имеет несколько важных отличий от объектного литерала: + +- Строки используют двойные кавычки. Никаких одинарных кавычек или обратных кавычек в JSON. Так `'John'` становится `"John"`. +- Имена свойств объекта также заключаются в двойные кавычки. Это обязательно. Так `age:30` становится `"age":30`. + +`JSON.stringify` может быть применён и к примитивам. + +JSON поддерживает следующие типы данных: + +- Объекты `{ ... }` +- Массивы `[ ... ]` +- Примитивы: + - строки, + - числа, + - логические значения `true/false`, + - `null`. + +Например: + +```js run +// число в JSON остаётся числом +alert( JSON.stringify(1) ) // 1 + +// строка в JSON по-прежнему остаётся строкой, но в двойных кавычках +alert( JSON.stringify('test') ) // "test" + +alert( JSON.stringify(true) ); // true + +alert( JSON.stringify([1, 2, 3]) ); // [1,2,3] +``` + +JSON является независимой от языка спецификацией для данных, поэтому `JSON.stringify` пропускает некоторые специфические свойства объектов JavaScript. + +А именно: + +- Свойства-функции (методы). +- Символьные ключи и значения. +- Свойства, содержащие `undefined`. + +```js run +let user = { + sayHi() { // будет пропущено + alert("Hello"); + }, + [Symbol("id")]: 123, // также будет пропущено + something: undefined // как и это - пропущено +}; + +alert( JSON.stringify(user) ); // {} (пустой объект) +``` + +Обычно это нормально. Если это не то, чего мы хотим, то скоро мы увидим, как можно настроить этот процесс. + +Самое замечательное, что вложенные объекты поддерживаются и конвертируются автоматически. + +Например: + +```js run +let meetup = { + title: "Conference", +*!* + room: { + number: 23, + participants: ["john", "ann"] + } +*/!* +}; + +alert( JSON.stringify(meetup) ); +/* вся структура преобразована в строку: +{ + "title":"Conference", + "room":{"number":23,"participants":["john","ann"]}, +} +*/ +``` + +Важное ограничение: не должно быть циклических ссылок. + +Например: + +```js run +let room = { + number: 23 +}; + +let meetup = { + title: "Conference", + participants: ["john", "ann"] +}; + +meetup.place = room; // meetup ссылается на room +room.occupiedBy = meetup; // room ссылается на meetup + +*!* +JSON.stringify(meetup); // Ошибка: Преобразование цикличной структуры в JSON +*/!* +``` + +Здесь преобразование завершается неудачно из-за циклической ссылки: `room.occupiedBy` ссылается на `meetup`, и `meetup.place` ссылается на `room`: + +![](json-meetup.svg) + + +## Исключаем и преобразуем: replacer + +Полный синтаксис `JSON.stringify`: + +```js +let json = JSON.stringify(value[, replacer, space]) +``` + +value +: Значение для кодирования. + +replacer +: Массив свойств для кодирования или функция соответствия `function(key, value)`. + +space +: Дополнительное пространство (отступы), используемое для форматирования. + +В большинстве случаев `JSON.stringify` используется только с первым аргументом. Но если нам нужно настроить процесс замены, например, отфильтровать циклические ссылки, то можно использовать второй аргумент `JSON.stringify`. + +Если мы передадим ему массив свойств, будут закодированы только эти свойства. + +Например: + +```js run +let room = { + number: 23 +}; + +let meetup = { + title: "Conference", + participants: [{name: "John"}, {name: "Alice"}], + place: room // meetup ссылается на room +}; + +room.occupiedBy = meetup; // room ссылается на meetup + +alert( JSON.stringify(meetup, *!*['title', 'participants']*/!*) ); +// {"title":"Conference","participants":[{},{}]} +``` + +Здесь мы, наверное, слишком строги. Список свойств применяется ко всей структуре объекта. Так что внутри `participants` -- пустые объекты, потому что `name` нет в списке. + +Давайте включим в список все свойства, кроме `room.occupiedBy`, из-за которого появляется цикличная ссылка: + +```js run +let room = { + number: 23 +}; + +let meetup = { + title: "Conference", + participants: [{name: "John"}, {name: "Alice"}], + place: room // meetup ссылается на room +}; + +room.occupiedBy = meetup; // room ссылается на meetup + +alert( JSON.stringify(meetup, *!*['title', 'participants', 'place', 'name', 'number']*/!*) ); +/* +{ + "title":"Conference", + "participants":[{"name":"John"},{"name":"Alice"}], + "place":{"number":23} +} +*/ +``` + +Теперь всё, кроме `occupiedBy`, сериализовано. Но список свойств довольно длинный. + +К счастью, в качестве `replacer` мы можем использовать функцию, а не массив. + +Функция будет вызываться для каждой пары `(key, value)`, и она должна возвращать заменённое значение, которое будет использоваться вместо исходного. Или `undefined`, чтобы пропустить значение. + +В нашем случае мы можем вернуть `value` "как есть" для всего, кроме `occupiedBy`. Чтобы игнорировать `occupiedBy`, код ниже возвращает `undefined`: + +```js run +let room = { + number: 23 +}; + +let meetup = { + title: "Conference", + participants: [{name: "John"}, {name: "Alice"}], + place: room // meetup ссылается на room +}; + +room.occupiedBy = meetup; // room ссылается на meetup + +alert( JSON.stringify(meetup, function replacer(key, value) { + alert(`${key}: ${value}`); + return (key == 'occupiedBy') ? undefined : value; +})); + +/* пары ключ:значение, которые приходят в replacer: +: [object Object] +title: Conference +participants: [object Object],[object Object] +0: [object Object] +name: John +1: [object Object] +name: Alice +place: [object Object] +number: 23 +occupiedBy: [object Object] +*/ +``` + +Обратите внимание, что функция `replacer` получает каждую пару ключ/значение, включая вложенные объекты и элементы массива. И она применяется рекурсивно. Значение `this` внутри `replacer` - это объект, который содержит текущее свойство. + +Первый вызов - особенный. Ему передаётся специальный "объект-обёртка": `{"": meetup}`. Другими словами, первая `(key, value)` пара имеет пустой ключ, а значением является целевой объект в общем. Вот почему первая строка из примера выше будет `":[object Object]"`. + +Идея состоит в том, чтобы дать как можно больше возможностей `replacer` - у него есть возможность проанализировать и заменить/пропустить даже весь объект целиком, если это необходимо. + + +## Форматирование: space + +Третий аргумент в `JSON.stringify(value, replacer, space)` - это количество пробелов, используемых для удобного форматирования. + +Ранее все JSON-форматированные объекты не имели отступов и лишних пробелов. Это нормально, если мы хотим отправить объект по сети. Аргумент `space` используется исключительно для вывода в удобочитаемом виде. + +Ниже `space = 2` указывает JavaScript отображать вложенные объекты в несколько строк с отступом в 2 пробела внутри объекта: + +```js run +let user = { + name: "John", + age: 25, + roles: { + isAdmin: false, + isEditor: true + } +}; + +alert(JSON.stringify(user, null, 2)); +/* отступ в 2 пробела: +{ + "name": "John", + "age": 25, + "roles": { + "isAdmin": false, + "isEditor": true + } +} +*/ + +/* для JSON.stringify(user, null, 4) результат содержит больше отступов: +{ + "name": "John", + "age": 25, + "roles": { + "isAdmin": false, + "isEditor": true + } +} +*/ +``` + +Третьим аргументом также может быть строка. В этом случае строка будет использоваться для отступа вместо ряда пробелов. + +Параметр `space` применяется исключительно для логирования и красивого вывода. + +## Пользовательский "toJSON" + +Как и `toString` для преобразования строк, объект может предоставлять метод `toJSON` для преобразования в JSON. `JSON.stringify` автоматически вызывает его, если он есть. + +Например: + +```js run +let room = { + number: 23 +}; + +let meetup = { + title: "Conference", + date: new Date(Date.UTC(2017, 0, 1)), + room +}; + +alert( JSON.stringify(meetup) ); +/* + { + "title":"Conference", +*!* + "date":"2017-01-01T00:00:00.000Z", // (1) +*/!* + "room": {"number":23} // (2) + } +*/ +``` + +Как видим, `date` `(1)` стал строкой. Это потому, что все объекты типа `Date` имеют встроенный метод `toJSON`, который возвращает такую строку. + +Теперь давайте добавим собственную реализацию метода `toJSON` в наш объект `room` `(2)`: + +```js run +let room = { + number: 23, +*!* + toJSON() { + return this.number; + } +*/!* +}; + +let meetup = { + title: "Conference", + room +}; + +*!* +alert( JSON.stringify(room) ); // 23 +*/!* + +alert( JSON.stringify(meetup) ); +/* + { + "title":"Conference", +*!* + "room": 23 +*/!* + } +*/ +``` + +Как видите, `toJSON` используется как при прямом вызове `JSON.stringify(room)`, так и когда `room` вложен в другой сериализуемый объект. + + +## JSON.parse + +Чтобы декодировать JSON-строку, нам нужен другой метод с именем [JSON.parse](mdn:js/JSON/parse). + +Синтаксис: +```js +let value = JSON.parse(str[, reviver]); +``` + +str +: JSON для преобразования в объект. + +reviver +: Необязательная функция, которая будет вызываться для каждой пары `(ключ, значение)` и может преобразовывать значение. + +Например: + +```js run +// строковый массив +let numbers = "[0, 1, 2, 3]"; + +numbers = JSON.parse(numbers); + +alert( numbers[1] ); // 1 +``` + +Или для вложенных объектов: + +```js run +let user = '{ "name": "John", "age": 35, "isAdmin": false, "friends": [0,1,2,3] }'; + +user = JSON.parse(user); + +alert( user.friends[1] ); // 1 +``` + +JSON может быть настолько сложным, насколько это необходимо, объекты и массивы могут включать другие объекты и массивы. Но они должны быть в том же JSON-формате. + +Вот типичные ошибки в написанном от руки JSON (иногда приходится писать его для отладки): + +```js +let json = `{ + *!*name*/!*: "John", // Ошибка: имя свойства без кавычек + "surname": *!*'Smith'*/!*, // Ошибка: одинарные кавычки в значении (должны быть двойными) + *!*'isAdmin'*/!*: false, // Ошибка: одинарные кавычки в ключе (должны быть двойными) + "birthday": *!*new Date(2000, 2, 3)*/!*, // Ошибка: не допускается конструктор "new", только значения + "gender": "male"*!* */!* // Ошибка: отсутствует запятая после непоследнего свойства + "friends": [0,1,2,3]*!*,*/!* // Ошибка: не должно быть запятой после последнего свойства +}`; +``` + +Кроме того, JSON не поддерживает комментарии. Добавление комментария в JSON делает его недействительным. + +Существует ещё один формат [JSON5](https://json5.org/), который поддерживает ключи без кавычек, комментарии и т.д. Но это самостоятельная библиотека, а не спецификация языка. + +Обычный JSON настолько строг не потому, что его разработчики ленивы, а потому, что позволяет легко, надёжно и очень быстро реализовывать алгоритм кодирования и чтения. + +## Использование reviver + +Представьте, что мы получили объект `meetup` с сервера в виде строки данных. + +Вот такой: + +```js +// title: (meetup title), date: (meetup date) +let str = '{"title":"Conference","date":"2017-11-30T12:00:00.000Z"}'; +``` + +...А теперь нам нужно *десериализовать* её, т.е. снова превратить в объект JavaScript. + +Давайте сделаем это, вызвав `JSON.parse`: + +```js run +let str = '{"title":"Conference","date":"2017-11-30T12:00:00.000Z"}'; + +let meetup = JSON.parse(str); + +*!* +alert( meetup.date.getDate() ); // Ошибка! +*/!* +``` + +Ой, ошибка! + +Значением `meetup.date` является строка, а не `Date` объект. Как `JSON.parse` мог знать, что он должен был преобразовать эту строку в `Date`? + +Давайте передадим `JSON.parse` функцию восстановления вторым аргументом, которая возвращает все значения "как есть", но `date` станет `Date`: + +```js run +let str = '{"title":"Conference","date":"2017-11-30T12:00:00.000Z"}'; + +*!* +let meetup = JSON.parse(str, function(key, value) { + if (key == 'date') return new Date(value); + return value; +}); +*/!* + +alert( meetup.date.getDate() ); // 30 - теперь работает! +``` + +Кстати, это работает и для вложенных объектов: + +```js run +let schedule = `{ + "meetups": [ + {"title":"Conference","date":"2017-11-30T12:00:00.000Z"}, + {"title":"Birthday","date":"2017-04-18T12:00:00.000Z"} + ] +}`; + +schedule = JSON.parse(schedule, function(key, value) { + if (key == 'date') return new Date(value); + return value; +}); + +*!* +alert( schedule.meetups[1].date.getDate() ); // 18 - отлично! +*/!* +``` + + + +## Итого + +- JSON - это формат данных, который имеет собственный независимый стандарт и библиотеки для большинства языков программирования. +- JSON поддерживает простые объекты, массивы, строки, числа, логические значения и `null`. +- JavaScript предоставляет методы [JSON.stringify](mdn:js/JSON/stringify) для сериализации в JSON и [JSON.parse](mdn:js/JSON/parse) для чтения из JSON. +- Оба метода поддерживают функции преобразования для интеллектуального чтения/записи. +- Если объект имеет метод `toJSON`, то он вызывается через `JSON.stringify`. diff --git a/1-js/05-data-types/12-json/json-meetup.svg b/1-js/05-data-types/12-json/json-meetup.svg new file mode 100644 index 0000000000..3fa32a261c --- /dev/null +++ b/1-js/05-data-types/12-json/json-meetup.svg @@ -0,0 +1 @@ +number: 23title: "Conference"...placeoccupiedByparticipants \ No newline at end of file diff --git a/1-js/06-advanced-functions/01-recursion/01-sum-to/solution.md b/1-js/06-advanced-functions/01-recursion/01-sum-to/solution.md index 237b9ef9e4..7283fe945c 100644 --- a/1-js/06-advanced-functions/01-recursion/01-sum-to/solution.md +++ b/1-js/06-advanced-functions/01-recursion/01-sum-to/solution.md @@ -1,4 +1,5 @@ -The solution using a loop: + +Решение с помощью цикла: ```js run function sumTo(n) { @@ -12,7 +13,7 @@ function sumTo(n) { alert( sumTo(100) ); ``` -The solution using recursion: +Решение через рекурсию: ```js run function sumTo(n) { @@ -23,7 +24,7 @@ function sumTo(n) { alert( sumTo(100) ); ``` -The solution using the formula: `sumTo(n) = n*(n+1)/2`: +Решение по формуле: `sumTo(n) = n*(n+1)/2`: ```js run function sumTo(n) { @@ -33,8 +34,8 @@ function sumTo(n) { alert( sumTo(100) ); ``` -P.S. Naturally, the formula is the fastest solution. It uses only 3 operations for any number `n`. The math helps! +P.S. Надо ли говорить, что решение по формуле работает быстрее всех? Это очевидно. Оно использует всего три операции для любого n, а цикл и рекурсия требуют как минимум n операций сложения. -The loop variant is the second in terms of speed. In both the recursive and the loop variant we sum the same numbers. But the recursion involves nested calls and execution stack management. That also takes resources, so it's slower. +Вариант с циклом – второй по скорости. Он быстрее рекурсии, так как операций сложения столько же, но нет дополнительных вычислительных затрат на организацию вложенных вызовов. Поэтому рекурсия в данном случае работает медленнее всех. -P.P.S. The standard describes a "tail call" optimization: if the recursive call is the very last one in the function (like in `sumTo` above), then the outer function will not need to resume the execution and we don't need to remember its execution context. In that case `sumTo(100000)` is countable. But if your JavaScript engine does not support it, there will be an error: maximum stack size exceeded, because there's usually a limitation on the total stack size. +P.P.S. Некоторые движки поддерживают оптимизацию "хвостового вызова": если рекурсивный вызов является самым последним в функции, без каких-либо других вычислений, то внешней функции не нужно будет возобновлять выполнение и не нужно запоминать контекст его выполнения. В итоге требования к памяти снижаются. Но если JavaScript-движок не поддерживает это (большинство не поддерживают), будет ошибка: максимальный размер стека превышен, так как обычно существует ограничение на максимальный размер стека. diff --git a/1-js/06-advanced-functions/01-recursion/01-sum-to/task.md b/1-js/06-advanced-functions/01-recursion/01-sum-to/task.md index cabc13290a..bda95bcaa6 100644 --- a/1-js/06-advanced-functions/01-recursion/01-sum-to/task.md +++ b/1-js/06-advanced-functions/01-recursion/01-sum-to/task.md @@ -2,11 +2,11 @@ importance: 5 --- -# Sum all numbers till the given one +# Вычислить сумму чисел до данного -Write a function `sumTo(n)` that calculates the sum of numbers `1 + 2 + ... + n`. +Напишите функцию `sumTo(n)`, которая вычисляет сумму чисел `1 + 2 + ... + n`. -For instance: +Например: ```js no-beautify sumTo(1) = 1 @@ -17,20 +17,20 @@ sumTo(4) = 4 + 3 + 2 + 1 = 10 sumTo(100) = 100 + 99 + ... + 2 + 1 = 5050 ``` -Make 3 solution variants: +Сделайте три варианта решения: -1. Using a for loop. -2. Using a recursion, cause `sumTo(n) = n + sumTo(n-1)` for `n > 1`. -3. Using the [arithmetic progression](https://en.wikipedia.org/wiki/Arithmetic_progression) formula. +1. С использованием цикла. +2. Через рекурсию, т.к. `sumTo(n) = n + sumTo(n-1)` for `n > 1`. +3. С использованием формулы [арифметической прогрессии](https://ru.wikipedia.org/wiki/Арифметическая_прогрессия). -An example of the result: +Пример работы вашей функции: ```js -function sumTo(n) { /*... your code ... */ } +function sumTo(n) { /*... ваш код ... */ } alert( sumTo(100) ); // 5050 ``` -P.S. Which solution variant is the fastest? The slowest? Why? +P.S. Какой вариант решения самый быстрый? Самый медленный? Почему? -P.P.S. Can we use recursion to count `sumTo(100000)`? +P.P.S. Можно ли при помощи рекурсии посчитать `sumTo(100000)`? diff --git a/1-js/06-advanced-functions/01-recursion/02-factorial/solution.md b/1-js/06-advanced-functions/01-recursion/02-factorial/solution.md index 59040a2b7a..9080b6b685 100644 --- a/1-js/06-advanced-functions/01-recursion/02-factorial/solution.md +++ b/1-js/06-advanced-functions/01-recursion/02-factorial/solution.md @@ -1,6 +1,6 @@ -By definition, a factorial is `n!` can be written as `n * (n-1)!`. +По определению факториал `n!` можно записать как `n * (n-1)!`. -In other words, the result of `factorial(n)` can be calculated as `n` multiplied by the result of `factorial(n-1)`. And the call for `n-1` can recursively descend lower, and lower, till `1`. +Другими словами, `factorial(n)` можно получить как `n` умноженное на результат `factorial(n-1)`. И результат для `n-1`, в свою очередь, может быть вычислен рекурсивно и так далее до `1`. ```js run function factorial(n) { @@ -10,7 +10,7 @@ function factorial(n) { alert( factorial(5) ); // 120 ``` -The basis of recursion is the value `1`. We can also make `0` the basis here, doesn't matter much, but gives one more recursive step: +Базисом рекурсии является значение `1`. А можно было бы сделать базисом и `0`, однако это добавило рекурсии дополнительный шаг: ```js run function factorial(n) { diff --git a/1-js/06-advanced-functions/01-recursion/02-factorial/task.md b/1-js/06-advanced-functions/01-recursion/02-factorial/task.md index d2aef2d903..9428816490 100644 --- a/1-js/06-advanced-functions/01-recursion/02-factorial/task.md +++ b/1-js/06-advanced-functions/01-recursion/02-factorial/task.md @@ -2,17 +2,17 @@ importance: 4 --- -# Calculate factorial +# Вычислить факториал -The [factorial](https://en.wikipedia.org/wiki/Factorial) of a natural number is a number multiplied by `"number minus one"`, then by `"number minus two"`, and so on till `1`. The factorial of `n` is denoted as `n!` +[Факториал](https://ru.wikipedia.org/wiki/Факториал) натурального числа - это число, умноженное на `"себя минус один"`, затем на `"себя минус два"`, и так далее до `1`. Факториал `n` обозначается как `n!` -We can write a definition of factorial like this: +Определение факториала можно записать как: ```js n! = n * (n - 1) * (n - 2) * ...*1 ``` -Values of factorials for different `n`: +Примеры значений для разных `n`: ```js 1! = 1 @@ -22,10 +22,10 @@ Values of factorials for different `n`: 5! = 5 * 4 * 3 * 2 * 1 = 120 ``` -The task is to write a function `factorial(n)` that calculates `n!` using recursive calls. +Задача – написать функцию `factorial(n)`, которая возвращает `n!`, используя рекурсию. ```js alert( factorial(5) ); // 120 ``` -P.S. Hint: `n!` can be written as `n * (n-1)!` For instance: `3! = 3*2! = 3*2*1! = 6` +P.S. Подсказка: `n!` можно записать как `n * (n-1)!` Например: `3! = 3*2! = 3*2*1! = 6` diff --git a/1-js/06-advanced-functions/01-recursion/03-fibonacci-numbers/fibonacci-recursion-tree.png b/1-js/06-advanced-functions/01-recursion/03-fibonacci-numbers/fibonacci-recursion-tree.png deleted file mode 100644 index d0d37e35e7..0000000000 Binary files a/1-js/06-advanced-functions/01-recursion/03-fibonacci-numbers/fibonacci-recursion-tree.png and /dev/null differ diff --git a/1-js/06-advanced-functions/01-recursion/03-fibonacci-numbers/fibonacci-recursion-tree.svg b/1-js/06-advanced-functions/01-recursion/03-fibonacci-numbers/fibonacci-recursion-tree.svg new file mode 100644 index 0000000000..1313837f30 --- /dev/null +++ b/1-js/06-advanced-functions/01-recursion/03-fibonacci-numbers/fibonacci-recursion-tree.svg @@ -0,0 +1 @@ +fib ( 5 )fib(4)fib(3)fib(3)fib(2)fib(0)fib(1)fib(1)fib(2)fib(0)fib(1)fib(1)fib(2)fib(0)fib(1) \ No newline at end of file diff --git a/1-js/06-advanced-functions/01-recursion/03-fibonacci-numbers/fibonacci-recursion-tree@2x.png b/1-js/06-advanced-functions/01-recursion/03-fibonacci-numbers/fibonacci-recursion-tree@2x.png deleted file mode 100644 index 3a937c64cd..0000000000 Binary files a/1-js/06-advanced-functions/01-recursion/03-fibonacci-numbers/fibonacci-recursion-tree@2x.png and /dev/null differ diff --git a/1-js/06-advanced-functions/01-recursion/03-fibonacci-numbers/solution.md b/1-js/06-advanced-functions/01-recursion/03-fibonacci-numbers/solution.md index 91bcecc055..78017a470e 100644 --- a/1-js/06-advanced-functions/01-recursion/03-fibonacci-numbers/solution.md +++ b/1-js/06-advanced-functions/01-recursion/03-fibonacci-numbers/solution.md @@ -1,6 +1,6 @@ -The first solution we could try here is the recursive one. +Сначала решим через рекурсию. -Fibonacci numbers are recursive by definition: +Числа Фибоначчи рекурсивны по определению: ```js run function fib(n) { @@ -9,14 +9,14 @@ function fib(n) { alert( fib(3) ); // 2 alert( fib(7) ); // 13 -// fib(77); // will be extremely slow! +// fib(77); // вычисляется очень долго ``` -...But for big values of `n` it's very slow. For instance, `fib(77)` may hang up the engine for some time eating all CPU resources. +При больших значениях `n` такое решение будет работать очень долго. Например, `fib(77)` может повесить браузер на некоторое время, съев все ресурсы процессора. -That's because the function makes too many subcalls. The same values are re-evaluated again and again. +Это потому, что функция порождает обширное дерево вложенных вызовов. При этом ряд значений вычисляется много раз снова и снова. -For instance, let's see a piece of calculations for `fib(5)`: +Например, посмотрим на отрывок вычислений для `fib(5)`: ```js no-beautify ... @@ -25,68 +25,68 @@ fib(4) = fib(3) + fib(2) ... ``` -Here we can see that the value of `fib(3)` is needed for both `fib(5)` and `fib(4)`. So `fib(3)` will be called and evaluated two times completely independently. +Здесь видно, что значение `fib(3)` нужно одновременно и для `fib(5)` и для `fib(4)`. В коде оно будет вычислено два раза, совершенно независимо. -Here's the full recursion tree: +Полное дерево рекурсии: -![fibonacci recursion tree](fibonacci-recursion-tree.png) +![fibonacci recursion tree](fibonacci-recursion-tree.svg) -We can clearly notice that `fib(3)` is evaluated two times and `fib(2)` is evaluated three times. The total amount of computations grows much faster than `n`, making it enormous even for `n=77`. +Можно заметить, что `fib(3)` вычисляется дважды, а `fib(2)` - трижды. Общее количество вычислений растёт намного быстрее, чем `n`, что делает его огромным даже для `n=77`. -We can optimize that by remembering already-evaluated values: if a value of say `fib(3)` is calculated once, then we can just reuse it in future computations. +Можно это оптимизировать, запоминая уже вычисленные значения: если значение, скажем, `fib(3)` вычислено однажды, затем мы просто переиспользуем это значение для последующих вычислений. -Another variant would be to give up recursion and use a totally different loop-based algorithm. +Другим вариантом было бы отказаться от рекурсии и использовать совершенно другой алгоритм на основе цикла. -Instead of going from `n` down to lower values, we can make a loop that starts from `1` and `2`, then gets `fib(3)` as their sum, then `fib(4)` as the sum of two previous values, then `fib(5)` and goes up and up, till it gets to the needed value. On each step we only need to remember two previous values. +Вместо того, чтобы начинать с `n` и вычислять необходимые предыдущие значения, можно написать цикл, который начнёт с `1` и `2`, затем из них получит `fib(3)` как их сумму, затем `fib(4)`как сумму предыдущих значений, затем `fib(5)` и так далее, до финального результата. На каждом шаге нам нужно помнить только значения двух предыдущих чисел последовательности. -Here are the steps of the new algorithm in details. +Вот детальные шаги нового алгоритма. -The start: +Начало: ```js -// a = fib(1), b = fib(2), these values are by definition 1 +// a = fib(1), b = fib(2), эти значения по определению равны 1 let a = 1, b = 1; -// get c = fib(3) as their sum +// получим c = fib(3) как их сумму let c = a + b; -/* we now have fib(1), fib(2), fib(3) +/* теперь у нас есть fib(1), fib(2), fib(3) a b c 1, 1, 2 */ ``` -Now we want to get `fib(4) = fib(2) + fib(3)`. +Теперь мы хотим получить `fib(4) = fib(2) + fib(3)`. -Let's shift the variables: `a,b` will get `fib(2),fib(3)`, and `c` will get their sum: +Переставим переменные: `a,b`, присвоим значения `fib(2),fib(3)`, тогда `c` можно получить как их сумму: ```js no-beautify -a = b; // now a = fib(2) -b = c; // now b = fib(3) +a = b; // теперь a = fib(2) +b = c; // теперь b = fib(3) c = a + b; // c = fib(4) -/* now we have the sequence: +/* имеем последовательность: a b c 1, 1, 2, 3 */ ``` -The next step gives another sequence number: +Следующий шаг даёт новое число последовательности: ```js no-beautify a = b; // now a = fib(3) b = c; // now b = fib(4) c = a + b; // c = fib(5) -/* now the sequence is (one more number): +/* последовательность теперь (на одно число больше): a b c 1, 1, 2, 3, 5 */ ``` -...And so on until we get the needed value. That's much faster than recursion and involves no duplicate computations. +...И так далее, пока не получим искомое значение. Это намного быстрее рекурсии и не требует повторных вычислений. -The full code: +Полный код: ```js run function fib(n) { @@ -105,6 +105,6 @@ alert( fib(7) ); // 13 alert( fib(77) ); // 5527939700884757 ``` -The loop starts with `i=3`, because the first and the second sequence values are hard-coded into variables `a=1`, `b=1`. +Цикл начинается с `i=3`, потому что первое и второе значения последовательности заданы `a=1`, `b=1`. -The approach is called [dynamic programming bottom-up](https://en.wikipedia.org/wiki/Dynamic_programming). +Такой способ называется [динамическое программирование снизу вверх](https://ru.wikipedia.org/wiki/Динамическое_программирование). diff --git a/1-js/06-advanced-functions/01-recursion/03-fibonacci-numbers/task.md b/1-js/06-advanced-functions/01-recursion/03-fibonacci-numbers/task.md index 3cdadd219b..0b9a536be6 100644 --- a/1-js/06-advanced-functions/01-recursion/03-fibonacci-numbers/task.md +++ b/1-js/06-advanced-functions/01-recursion/03-fibonacci-numbers/task.md @@ -2,24 +2,24 @@ importance: 5 --- -# Fibonacci numbers +# Числа Фибоначчи -The sequence of [Fibonacci numbers](https://en.wikipedia.org/wiki/Fibonacci_number) has the formula Fn = Fn-1 + Fn-2. In other words, the next number is a sum of the two preceding ones. +Последовательность [чисел Фибоначчи](https://ru.wikipedia.org/wiki/Числа_Фибоначчи) определяется формулой Fn = Fn-1 + Fn-2. То есть, следующее число получается как сумма двух предыдущих. -First two numbers are `1`, then `2(1+1)`, then `3(1+2)`, `5(2+3)` and so on: `1, 1, 2, 3, 5, 8, 13, 21...`. +Первые два числа равны `1`, затем `2(1+1)`, затем `3(1+2)`, `5(2+3)` и так далее: `1, 1, 2, 3, 5, 8, 13, 21...`. -Fibonacci numbers are related to the [Golden ratio](https://en.wikipedia.org/wiki/Golden_ratio) and many natural phenomena around us. +Числа Фибоначчи тесно связаны с [золотым сечением](https://ru.wikipedia.org/wiki/Золотое_сечение) и множеством природных явлений вокруг нас. -Write a function `fib(n)` that returns the `n-th` Fibonacci number. +Напишите функцию `fib(n)` которая возвращает `n-е` число Фибоначчи. -An example of work: +Пример работы: ```js -function fib(n) { /* your code */ } +function fib(n) { /* ваш код */ } alert(fib(3)); // 2 alert(fib(7)); // 13 alert(fib(77)); // 5527939700884757 ``` -P.S. The function should be fast. The call to `fib(77)` should take no more than a fraction of a second. +P.S. Все запуски функций из примера выше должны работать быстро. Вызов `fib(77)` должен занимать не более доли секунды. diff --git a/1-js/06-advanced-functions/01-recursion/04-output-single-linked-list/solution.md b/1-js/06-advanced-functions/01-recursion/04-output-single-linked-list/solution.md index 4e9de14692..bca0ca67f3 100644 --- a/1-js/06-advanced-functions/01-recursion/04-output-single-linked-list/solution.md +++ b/1-js/06-advanced-functions/01-recursion/04-output-single-linked-list/solution.md @@ -1,6 +1,6 @@ -# Loop-based solution +# Решение с использованием цикла -The loop-based variant of the solution: +Решение с использованием цикла: ```js run let list = { @@ -30,7 +30,7 @@ function printList(list) { printList(list); ``` -Please note that we use a temporary variable `tmp` to walk over the list. Technically, we could use a function parameter `list` instead: +Обратите внимание, что мы используем временную переменную `tmp` для перемещения по списку. Технически, мы могли бы использовать параметр функции `list` вместо неё: ```js function printList(list) { @@ -43,15 +43,15 @@ function printList(list) { } ``` -...But that would be unwise. In the future we may need to extend a function, do something else with the list. If we change `list`, then we loose such ability. +...Но это было бы неблагоразумно. В будущем нам может понадобиться расширить функцию, сделать что-нибудь ещё со списком. Если мы меняем `list`, то теряем такую возможность. -Talking about good variable names, `list` here is the list itself. The first element of it. And it should remain like that. That's clear and reliable. +Говоря о хороших именах для переменных, `list` здесь - это сам список, его первый элемент. Так и должно быть, это просто и понятно. -From the other side, the role of `tmp` is exclusively a list traversal, like `i` in the `for` loop. +С другой стороны, `tmp` используется исключительно для обхода списка, как `i` в цикле `for`. -# Recursive solution +# Решение через рекурсию -The recursive variant of `printList(list)` follows a simple logic: to output a list we should output the current element `list`, then do the same for `list.next`: +Рекурсивный вариант `printList(list)` следует простой логике: для вывода списка мы должны вывести текущий `list`, затем сделать то же самое для `list.next`: ```js run let list = { @@ -70,10 +70,10 @@ let list = { function printList(list) { - alert(list.value); // output the current item + alert(list.value); // выводим текущий элемент if (list.next) { - printList(list.next); // do the same for the rest of the list + printList(list.next); // делаем то же самое для остальной части списка } } @@ -81,8 +81,8 @@ function printList(list) { printList(list); ``` -Now what's better? +Какой способ лучше? -Technically, the loop is more effective. These two variants do the same, but the loop does not spend resources for nested function calls. +Технически, способ с циклом более эффективный. В обеих реализациях делается то же самое, но для цикла не тратятся ресурсы для вложенных вызовов. -From the other side, the recursive variant is shorter and sometimes easier to understand. +С другой стороны, рекурсивный вариант более короткий и, возможно, более простой для понимания. diff --git a/1-js/06-advanced-functions/01-recursion/04-output-single-linked-list/task.md b/1-js/06-advanced-functions/01-recursion/04-output-single-linked-list/task.md index 1076b952af..0909edbedb 100644 --- a/1-js/06-advanced-functions/01-recursion/04-output-single-linked-list/task.md +++ b/1-js/06-advanced-functions/01-recursion/04-output-single-linked-list/task.md @@ -2,9 +2,9 @@ importance: 5 --- -# Output a single-linked list +# Вывод односвязного списка -Let's say we have a single-linked list (as described in the chapter ): +Допустим, у нас есть односвязный список (как описано в главе ): ```js let list = { @@ -22,8 +22,8 @@ let list = { }; ``` -Write a function `printList(list)` that outputs list items one-by-one. +Напишите функцию `printList(list)`, которая выводит элементы списка по одному. -Make two variants of the solution: using a loop and using recursion. +Сделайте два варианта решения: используя цикл и через рекурсию. -What's better: with recursion or without it? +Как лучше: с рекурсией или без? diff --git a/1-js/06-advanced-functions/01-recursion/05-output-single-linked-list-reverse/solution.md b/1-js/06-advanced-functions/01-recursion/05-output-single-linked-list-reverse/solution.md index a9ba0baf58..f198f7bad4 100644 --- a/1-js/06-advanced-functions/01-recursion/05-output-single-linked-list-reverse/solution.md +++ b/1-js/06-advanced-functions/01-recursion/05-output-single-linked-list-reverse/solution.md @@ -1,8 +1,8 @@ -# Using a recursion +# С использованием рекурсии -The recursive logic is a little bit tricky here. +Рекурсивная логика в этом случае немного сложнее. -We need to first output the rest of the list and *then* output the current one: +Сначала надо вывести оставшуюся часть списка, а *затем* текущий элемент: ```js run let list = { @@ -31,13 +31,13 @@ function printReverseList(list) { printReverseList(list); ``` -# Using a loop +# С использованием цикла -The loop variant is also a little bit more complicated then the direct output. +Вариант с использованием цикла сложнее, чем в предыдущей задаче. -There is no way to get the last value in our `list`. We also can't "go back". +Нет способа сразу получить последнее значение в списке `list`. Мы также не можем "вернуться назад", к предыдущему элементу списка. -So what we can do is to first go through the items in the direct order and rememeber them in an array, and then output what we remembered in the reverse order: +Поэтому мы можем сначала перебрать элементы в прямом порядке и запомнить их в массиве, а затем вывести то, что мы запомнили, в обратном порядке: ```js run let list = { @@ -71,4 +71,4 @@ function printReverseList(list) { printReverseList(list); ``` -Please note that the recursive solution actually does exactly the same: it follows the list, remembers the items in the chain of nested calls (in the execution context stack), and then outputs them. +Обратите внимание, что рекурсивное решение на самом деле делает то же самое: проходит список, запоминает элементы в цепочке вложенных вызовов (в контексте выполнения), а затем выводит их. diff --git a/1-js/06-advanced-functions/01-recursion/05-output-single-linked-list-reverse/task.md b/1-js/06-advanced-functions/01-recursion/05-output-single-linked-list-reverse/task.md index 81b1f3e33a..b58c791dcd 100644 --- a/1-js/06-advanced-functions/01-recursion/05-output-single-linked-list-reverse/task.md +++ b/1-js/06-advanced-functions/01-recursion/05-output-single-linked-list-reverse/task.md @@ -2,8 +2,8 @@ importance: 5 --- -# Output a single-linked list in the reverse order +# Вывод односвязного списка в обратном порядке -Output a single-linked list from the previous task in the reverse order. +Выведите односвязный список из предыдущего задания в обратном порядке. -Make two solutions: using a loop and using a recursion. +Сделайте два решения: с использованием цикла и через рекурсию. diff --git a/1-js/06-advanced-functions/01-recursion/article.md b/1-js/06-advanced-functions/01-recursion/article.md index 735c75bf3f..4a8d323447 100644 --- a/1-js/06-advanced-functions/01-recursion/article.md +++ b/1-js/06-advanced-functions/01-recursion/article.md @@ -1,18 +1,19 @@ -# Recursion and stack -Let's return to functions and study them more in-depth. +# Рекурсия и стек -Our first topic will be *recursion*. +Вернёмся к функциям и изучим их более подробно. -If you are not new to programming, then it is probably familiar and you could skip this chapter. +Нашей первой темой будет *рекурсия*. -Recursion is a programming pattern that is useful in situations when a task can be naturally split into several tasks of the same kind, but simpler. Or when a task can be simplified into an easy action plus a simpler variant of the same task. Or, as we'll see soon, to deal with certain data structures. +Если вы не новичок в программировании, то, возможно, уже знакомы с рекурсией и можете пропустить эту главу. -When a function solves a task, in the process it can call many other functions. A partial case of this is when a function calls *itself*. That's called *recursion*. +Рекурсия - это приём программирования, полезный в ситуациях, когда задача может быть естественно разделена на несколько аналогичных, но более простых задач. Или когда задача может быть упрощена до несложных действий плюс простой вариант той же задачи. Или, как мы скоро увидим, для работы с определёнными структурами данных. -## Two ways of thinking +В процессе выполнения задачи в теле функции могут быть вызваны другие функции для выполнения подзадач. Частный случай подвызова – когда функция вызывает *сама себя*. Это как раз и называется *рекурсией*. -For something simple to start with -- let's write a function `pow(x, n)` that raises `x` to a natural power of `n`. In other words, multiplies `x` by itself `n` times. +## Два способа мышления + +В качестве первого примера напишем функцию `pow(x, n)`, которая возводит `x` в натуральную степень `n`. Иначе говоря, умножает `x` на само себя `n` раз. ```js pow(2, 2) = 4 @@ -20,15 +21,15 @@ pow(2, 3) = 8 pow(2, 4) = 16 ``` -There are two ways to implement it. +Рассмотрим два способа её реализации. -1. Iterative thinking: the `for` loop: +1. Итеративный способ: цикл `for`: ```js run function pow(x, n) { let result = 1; - // multiply result by x n times in the loop + // умножаем result на x n раз в цикле for (let i = 0; i < n; i++) { result *= x; } @@ -39,7 +40,7 @@ There are two ways to implement it. alert( pow(2, 3) ); // 8 ``` -2. Recursive thinking: simplify the task and call self: +2. Рекурсивный способ: упрощение задачи и вызов функцией самой себя: ```js run function pow(x, n) { @@ -53,39 +54,38 @@ There are two ways to implement it. alert( pow(2, 3) ); // 8 ``` -Please note how the recursive variant is fundamentally different. +Обратите внимание, что рекурсивный вариант отличается принципиально. -When `pow(x, n)` is called, the execution splits into two branches: +Когда функция `pow(x, n)` вызывается, исполнение делится на две ветви: ```js if n==1 = x / pow(x, n) = - \ + \ else = x * pow(x, n - 1) ``` -1. If `n == 1`, then everything is trivial. It is called *the base* of recursion, because it immediately produces the obvious result: `pow(x, 1)` equals `x`. -2. Otherwise, we can represent `pow(x, n)` as `x * pow(x, n - 1)`. In maths, one would write xn = x * xn-1. This is called *a recursive step*: we transform the task into a simpler action (multiplication by `x`) and a simpler call of the same task (`pow` with lower `n`). Next steps simplify it further and further until `n` reaches `1`. - -We can also say that `pow` *recursively calls itself* till `n == 1`. +1. Если `n == 1`, тогда всё просто. Эта ветвь называется *базой* рекурсии, потому что сразу же приводит к очевидному результату: `pow(x, 1)` равно `x`. +2. Мы можем представить `pow(x, n)` в виде: `x * pow(x, n - 1)`. Что в математике записывается как: xn = x * xn-1. Эта ветвь - *шаг рекурсии*: мы сводим задачу к более простому действию (умножение на `x`) и более простой аналогичной задаче (`pow` с меньшим `n`). Последующие шаги упрощают задачу всё больше и больше, пока `n` не достигает `1`. -![recursive diagram of pow](recursion-pow.png) +Говорят, что функция `pow` *рекурсивно вызывает саму себя* до `n == 1`. +![диаграмма рекурсии для pow](recursion-pow.svg) -For example, to calculate `pow(2, 4)` the recursive variant does these steps: +Например, рекурсивный вариант вычисления `pow(2, 4)` состоит из шагов: 1. `pow(2, 4) = 2 * pow(2, 3)` 2. `pow(2, 3) = 2 * pow(2, 2)` 3. `pow(2, 2) = 2 * pow(2, 1)` 4. `pow(2, 1) = 2` -So, the recursion reduces a function call to a simpler one, and then -- to even more simpler, and so on, until the result becomes obvious. +Итак, рекурсию используют, когда вычисление функции можно свести к её более простому вызову, а его – к ещё более простому и так далее, пока значение не станет очевидно. -````smart header="Recursion is usually shorter" -A recursive solution is usually shorter than an iterative one. +````smart header="Рекурсивное решение обычно короче" +Рекурсивное решение задачи обычно короче, чем итеративное. -Here we can rewrite the same using the ternary `?` operator instead of `if` to make `pow(x, n)` more terse and still very readable: +Используя условный оператор `?` вместо `if`, мы можем переписать `pow(x, n)`, делая код функции более лаконичным, но всё ещё легко читаемым: ```js run function pow(x, n) { @@ -94,45 +94,45 @@ function pow(x, n) { ``` ```` -The maximal number of nested calls (including the first one) is called *recursion depth*. In our case, it will be exactly `n`. +Общее количество вложенных вызовов (включая первый) называют *глубиной рекурсии*. В нашем случае она будет равна `n`. -The maximal recursion depth is limited by JavaScript engine. We can make sure about 10000, some engines allow more, but 100000 is probably out of limit for the majority of them. There are automatic optimizations that help alleviate this ("tail calls optimizations"), but they are not yet supported everywhere and work only in simple cases. +Максимальная глубина рекурсии ограничена движком JavaScript. Точно можно рассчитывать на 10000 вложенных вызовов, некоторые интерпретаторы допускают и больше, но для большинства из них 100000 вызовов -- за пределами возможностей. Существуют автоматические оптимизации, помогающие избежать переполнения стека вызовов ("оптимизация хвостовой рекурсии"), но они ещё не поддерживаются везде и работают только для простых случаев. -That limits the application of recursion, but it still remains very wide. There are many tasks where recursive way of thinking gives simpler code, easier to maintain. +Это ограничивает применение рекурсии, но она всё равно широко распространена: для решения большого числа задач рекурсивный способ решения даёт более простой код, который легче поддерживать. -## The execution stack +## Контекст выполнения, стек -Now let's examine how recursive calls work. For that we'll look under the hood of functions. +Теперь мы посмотрим, как работают рекурсивные вызовы. Для этого заглянем "под капот" функций. -The information about a function run is stored in its *execution context*. +Информация о процессе выполнения запущенной функции хранится в её *контексте выполнения* (execution context). -The [execution context](https://tc39.github.io/ecma262/#sec-execution-contexts) is an internal data structure that contains details about the execution of a function: where the control flow is now, the current variables, the value of `this` (we don't use it here) and few other internal details. +[Контекст выполнения](https://tc39.github.io/ecma262/#sec-execution-contexts) - специальная внутренняя структура данных, которая содержит информацию о вызове функции. Она включает в себя конкретное место в коде, на котором находится интерпретатор, локальные переменные функции, значение `this` (мы не используем его в данном примере) и прочую служебную информацию. -One function call has exactly one execution context associated with it. +Один вызов функции имеет ровно один контекст выполнения, связанный с ним. -When a function makes a nested call, the following happens: +Когда функция производит вложенный вызов, происходит следующее: -- The current function is paused. -- The execution context associated with it is remembered in a special data structure called *execution context stack*. -- The nested call executes. -- After it ends, the old execution context is retrieved from the stack, and the outer function is resumed from where it stopped. +- Выполнение текущей функции приостанавливается. +- Контекст выполнения, связанный с ней, запоминается в специальной структуре данных - *стеке контекстов выполнения*. +- Выполняются вложенные вызовы, для каждого из которых создаётся свой контекст выполнения. +- После их завершения старый контекст достаётся из стека, и выполнение внешней функции возобновляется с того места, где она была остановлена. -Let's see what happens during the `pow(2, 3)` call. +Разберёмся с контекстами более подробно на примере вызова функции `pow(2, 3)`. ### pow(2, 3) -In the beginning of the call `pow(2, 3)` the execution context will store variables: `x = 2, n = 3`, the execution flow is at line `1` of the function. +В начале вызова `pow(2, 3)` контекст выполнения будет хранить переменные: `x = 2, n = 3`, выполнение находится на первой строке функции. -We can sketch it as: +Можно схематически изобразить это так:
  • - Context: { x: 2, n: 3, at line 1 } + Контекст: { x: 2, n: 3, строка 1 } pow(2, 3)
-That's when the function starts to execute. The condition `n == 1` is false, so the flow continues into the second branch of `if`: +Это только начало выполнения функции. Условие `n == 1` ложно, поэтому выполнение идёт во вторую ветку `if`: ```js run function pow(x, n) { @@ -149,70 +149,70 @@ alert( pow(2, 3) ); ``` -The variables are same, but the line changes, so the context is now: +Значения переменных те же самые, но выполнение функции перешло к другой строке, актуальный контекст:
  • - Context: { x: 2, n: 3, at line 5 } + Контекст: { x: 2, n: 3, строка 5 } pow(2, 3)
-To calculate `x * pow(x, n - 1)`, we need to make a subcall of `pow` with new arguments `pow(2, 2)`. +Чтобы вычислить выражение `x * pow(x, n - 1)`, требуется произвести запуск `pow` с новыми аргументами `pow(2, 2)`. ### pow(2, 2) -To do a nested call, JavaScript remembers the current execution context in the *execution context stack*. +Для выполнения вложенного вызова JavaScript запоминает текущий контекст выполнения в *стеке контекстов выполнения*. -Here we call the same function `pow`, but it absolutely doesn't matter. The process is the same for all functions: +Здесь мы вызываем ту же функцию `pow`, однако это абсолютно неважно. Для любых функций процесс одинаков: -1. The current context is "remembered" on top of the stack. -2. The new context is created for the subcall. -3. When the subcall is finished -- the previous context is popped from the stack, and its execution continues. +1. Текущий контекст "запоминается" на вершине стека. +2. Создаётся новый контекст для вложенного вызова. +3. Когда выполнение вложенного вызова заканчивается -- контекст предыдущего вызова восстанавливается, и выполнение соответствующей функции продолжается. -Here's the context stack when we entered the subcall `pow(2, 2)`: +Вид контекста в начале выполнения вложенного вызова `pow(2, 2)`:
  • - Context: { x: 2, n: 2, at line 1 } + Контекст: { x: 2, n: 2, строка 1 } pow(2, 2)
  • - Context: { x: 2, n: 3, at line 5 } + Контекст: { x: 2, n: 3, строка 5 } pow(2, 3)
-The new current execution context is on top (and bold), and previous remembered contexts are below. +Новый контекст выполнения находится на вершине стека (и выделен жирным), а предыдущие запомненные контексты -- под ним. -When we finish the subcall -- it is easy to resume the previous context, because it keeps both variables and the exact place of the code where it stopped. Here in the picture we use the word "line", but of course it's more precise. +Когда выполнение подвызова закончится, можно будет легко вернуться назад, потому что контекст сохраняет как переменные, так и точное место кода, в котором он остановился. Слово "строка" на рисунках условно, на самом деле запоминается более точное место в цепочке команд. ### pow(2, 1) -The process repeats: a new subcall is made at line `5`, now with arguments `x=2`, `n=1`. +Процесс повторяется: производится новый вызов в строке `5`, теперь с аргументами `x=2`, `n=1`. -A new execution context is created, the previous one is pushed on top of the stack: +Создаётся новый контекст выполнения, предыдущий контекст добавляется в стек:
  • - Context: { x: 2, n: 1, at line 1 } + Контекст: { x: 2, n: 1, строка 1 } pow(2, 1)
  • - Context: { x: 2, n: 2, at line 5 } + Контекст: { x: 2, n: 2, строка 5 } pow(2, 2)
  • - Context: { x: 2, n: 3, at line 5 } + Контекст: { x: 2, n: 3, строка 5 } pow(2, 3)
-There are 2 old contexts now and 1 currently running for `pow(2, 1)`. +Теперь в стеке два старых контекста и один текущий для `pow(2, 1)`. -### The exit +### Выход -During the execution of `pow(2, 1)`, unlike before, the condition `n == 1` is truthy, so the first branch of `if` works: +При выполнении `pow(2, 1)`, в отличие от предыдущих запусков, условие `n == 1` истинно, поэтому выполняется первая ветка условия `if`: ```js function pow(x, n) { @@ -226,42 +226,42 @@ function pow(x, n) { } ``` -There are no more nested calls, so the function finishes, returning `2`. +Вложенных вызовов больше нет, поэтому функция завершается, возвращая `2`. -As the function finishes, its execution context is not needed anymore, so it's removed from the memory. The previous one is restored off the top of the stack: +Когда функция заканчивается, контекст её выполнения больше не нужен, поэтому он удаляется из памяти, а из стека восстанавливается предыдущий:
  • - Context: { x: 2, n: 2, at line 5 } + Контекст: { x: 2, n: 2, строка 5 } pow(2, 2)
  • - Context: { x: 2, n: 3, at line 5 } + Контекст: { x: 2, n: 3, строка 5 } pow(2, 3)
-The execution of `pow(2, 2)` is resumed. It has the result of the subcall `pow(2, 1)`, so it also can finish the evaluation of `x * pow(x, n - 1)`, returning `4`. +Возобновляется обработка вызова `pow(2, 2)`. Имея результат `pow(2, 1)`, он может закончить свою работу `x * pow(x, n - 1)`, вернув `4`. -Then the previous context is restored: +Восстанавливается контекст предыдущего вызова:
  • - Context: { x: 2, n: 3, at line 5 } + Контекст: { x: 2, n: 3, строка 5 } pow(2, 3)
-When it finishes, we have a result of `pow(2, 3) = 8`. +Самый внешний вызов заканчивает свою работу, его результат: `pow(2, 3) = 8`. -The recursion depth in this case was: **3**. +Глубина рекурсии в данном случае составила **3**. -As we can see from the illustrations above, recursion depth equals the maximal number of context in the stack. +Как видно из иллюстраций выше, глубина рекурсии равна максимальному числу контекстов, одновременно хранимых в стеке. -Note the memory requirements. Contexts take memory. In our case, raising to the power of `n` actually requires the memory for `n` contexts, for all lower values of `n`. +Обратим внимание на требования к памяти. Рекурсия приводит к хранению всех данных для неоконченных внешних вызовов в стеке, и в данном случае это приводит к тому, что возведение в степень `n` хранит в памяти `n` различных контекстов. -A loop-based algorithm is more memory-saving: +Реализация возведения в степень через цикл гораздо более экономна: ```js function pow(x, n) { @@ -275,19 +275,19 @@ function pow(x, n) { } ``` -The iterative `pow` uses a single context changing `i` and `result` in the process. Its memory requirements are small, fixed and do not depend on `n`. +Итеративный вариант функции `pow` использует один контекст, в котором будут последовательно меняться значения `i` и `result`. При этом объём затрачиваемой памяти небольшой, фиксированный и не зависит от `n`. -**Any recursion can be rewritten as a loop. The loop variant usually can be made more effective.** +**Любая рекурсия может быть переделана в цикл. Как правило, вариант с циклом будет эффективнее.** -...But sometimes the rewrite is non-trivial, especially when function uses different recursive subcalls depending on conditions and merges their results or when the branching is more intricate. And the optimization may be unneeded and totally not worth the efforts. +Но переделка рекурсии в цикл может быть нетривиальной, особенно когда в функции в зависимости от условий используются различные рекурсивные подвызовы, результаты которых объединяются, или когда ветвление более сложное. Оптимизация может быть ненужной и совершенно нестоящей усилий. -Recursion can give a shorter code, easier to understand and support. Optimizations are not required in every place, mostly we need a good code, that's why it's used. +Часто код с использованием рекурсии более короткий, лёгкий для понимания и поддержки. Оптимизация требуется не везде, как правило, нам важен хороший код, поэтому она и используется. -## Recursive traversals +## Рекурсивные обходы -Another great application of the recursion is a recursive traversal. +Другим отличным применением рекурсии является рекурсивный обход. -Imagine, we have a company. The staff structure can be presented as an object: +Представьте, у нас есть компания. Структура персонала может быть представлена как объект: ```js let company = { @@ -316,34 +316,34 @@ let company = { }; ``` -In other words, a company has departments. +Другими словами, в компании есть отделы. -- A department may have an array of staff. For instance, `sales` department has 2 employees: John and Alice. -- Or a department may split into subdepartments, like `development` has two branches: `sites` and `internals`. Each of them has their own staff. -- It is also possible that when a subdepartment grows, it divides into subsubdepartments (or teams). +- Отдел может состоять из массива работников. Например, в отделе `sales` работают 2 сотрудника: Джон и Алиса. +- Или отдел может быть разделён на подотделы, например, отдел `development` состоит из подотделов: `sites` и `internals`. В каждом подотделе есть свой персонал. +- Также возможно, что при росте подотдела он делится на подразделения (или команды). - For instance, the `sites` department in the future may be split into teams for `siteA` and `siteB`. And they, potentially, can split even more. That's not on the picture, just something to have in mind. + Например, подотдел `sites` в будущем может быть разделён на команды `siteA` и `siteB`. И потенциально они могут быть разделены ещё. Этого нет на картинке, просто нужно иметь это в виду. -Now let's say we want a function to get the sum of all salaries. How can we do that? +Теперь, допустим, нам нужна функция для получения суммы всех зарплат. Как мы можем это сделать? -An iterative approach is not easy, because the structure is not simple. The first idea may be to make a `for` loop over `company` with nested subloop over 1st level departments. But then we need more nested subloops to iterate over the staff in 2nd level departments like `sites`. ...And then another subloop inside those for 3rd level departments that might appear in the future? Should we stop on level 3 or make 4 levels of loops? If we put 3-4 nested subloops in the code to traverse a single object, it becomes rather ugly. +Итеративный подход не прост, потому что структура довольно сложная. Первая идея заключается в том, чтобы сделать цикл `for` поверх объекта `company` с вложенным циклом над отделами 1-го уровня вложенности. Но затем нам нужно больше вложенных циклов для итераций над сотрудниками отделов второго уровня, таких как `sites`... А затем ещё один цикл по отделам 3-го уровня, которые могут появиться в будущем? Если мы поместим в код 3-4 вложенных цикла для обхода одного объекта, то это будет довольно некрасиво. -Let's try recursion. +Давайте попробуем рекурсию. -As we can see, when our function gets a department to sum, there are two possible cases: +Как мы видим, когда наша функция получает отдел для подсчёта суммы зарплат, есть два возможных случая: -1. Either it's a "simple" department with an *array of people* -- then we can sum the salaries in a simple loop. -2. Or it's *an object with `N` subdepartments* -- then we can make `N` recursive calls to get the sum for each of the subdeps and combine the results. +1. Либо это "простой" отдел с *массивом* -- тогда мы сможем суммировать зарплаты в простом цикле. +2. Или это *объект* с `N` подотделами -- тогда мы можем сделать `N` рекурсивных вызовов, чтобы получить сумму для каждого из подотделов, и объединить результаты. -The (1) is the base of recursion, the trivial case. +Случай (1), когда мы получили массив, является базой рекурсии, тривиальным случаем. -The (2) is the recursive step. A complex task is split into subtasks for smaller departments. They may in turn split again, but sooner or later the split will finish at (1). +Случай (2), при получении объекта, является шагом рекурсии. Сложная задача разделяется на подзадачи для подотделов. Они могут, в свою очередь, снова разделиться на подотделы, но рано или поздно это разделение закончится, и решение сведётся к случаю (1). -The algorithm is probably even easier to read from the code: +Алгоритм даже проще читается в виде кода: ```js run -let company = { // the same object, compressed for brevity +let company = { // тот же самый объект, сжатый для краткости sales: [{name: 'John', salary: 1000}, {name: 'Alice', salary: 600 }], development: { sites: [{name: 'Peter', salary: 2000}, {name: 'Alex', salary: 1800 }], @@ -351,15 +351,15 @@ let company = { // the same object, compressed for brevity } }; -// The function to do the job +// Функция для подсчёта суммы зарплат *!* function sumSalaries(department) { - if (Array.isArray(department)) { // case (1) - return department.reduce((prev, current) => prev + current.salary, 0); // sum the array - } else { // case (2) + if (Array.isArray(department)) { // случай (1) + return department.reduce((prev, current) => prev + current.salary, 0); // сумма элементов массива + } else { // случай (2) let sum = 0; for (let subdep of Object.values(department)) { - sum += sumSalaries(subdep); // recursively call for subdepartments, sum the results + sum += sumSalaries(subdep); // рекурсивно вызывается для подотделов, суммируя результаты } return sum; } @@ -369,62 +369,62 @@ function sumSalaries(department) { alert(sumSalaries(company)); // 6700 ``` -The code is short and easy to understand (hopefully?). That's the power of recursion. It also works for any level of subdepartment nesting. +Код краток и прост для понимания (надеюсь?). В этом сила рекурсии. Она работает на любом уровне вложенности отделов. -Here's the diagram of calls: +Схема вызовов: -![recursive salaries](recursive-salaries.png) +![recursive salaries](recursive-salaries.svg) -We can easily see the principle: for an object `{...}` subcalls are made, while arrays `[...]` are the "leaves" of the recursion tree, they give immediate result. +Принцип прост: для объекта `{...}` используются рекурсивные вызовы, а массивы `[...]` являются "листьями" дерева рекурсии, они сразу дают результат. -Note that the code uses smart features that we've covered before: +Обратите внимание, что в коде используются возможности, о которых мы говорили ранее: -- Method `arr.reduce` explained in the chapter to get the sum of the array. -- Loop `for(val of Object.values(obj))` to iterate over object values: `Object.values` returns an array of them. +- Метод `arr.reduce` из главы для получения суммы элементов массива. +- Цикл `for(val of Object.values(obj))` для итерации по значениям объекта: `Object.values` возвращает массив значений. -## Recursive structures +## Рекурсивные структуры -A recursive (recursively-defined) data structure is a structure that replicates itself in parts. +Рекурсивная (рекурсивно определяемая) структура данных - это структура, которая повторяет саму себя в своих частях. -We've just seen it in the example of a company structure above. +Мы только что видели это на примере структуры компании выше. -A company *department* is: -- Either an array of people. -- Or an object with *departments*. +*Отдел* компании - это: +- Либо массив людей. +- Либо объект с *отделами*. -For web-developers there are much better-known examples: HTML and XML documents. +Для веб-разработчиков существуют гораздо более известные примеры: HTML- и XML-документы. -In the HTML document, an *HTML-tag* may contain a list of: -- Text pieces. -- HTML-comments. -- Other *HTML-tags* (that in turn may contain text pieces/comments or other tags etc). +В HTML-документе *HTML-тег* может содержать: +- Фрагменты текста. +- HTML-комментарии. +- Другие *HTML-теги* (которые, в свою очередь, могут содержать фрагменты текста/комментарии или другие теги и т.д.). -That's once again a recursive definition. +Это снова рекурсивное определение. -For better understanding, we'll cover one more recursive structure named "Linked list" that might be a better alternative for arrays in some cases. +Для лучшего понимания мы рассмотрим ещё одну рекурсивную структуру под названием "связный список", которая в некоторых случаях может использоваться в качестве альтернативы массиву. -### Linked list +### Связный список -Imagine, we want to store an ordered list of objects. +Представьте себе, что мы хотим хранить упорядоченный список объектов. -The natural choice would be an array: +Естественным выбором будет массив: ```js let arr = [obj1, obj2, obj3]; ``` -...But there's a problem with arrays. The "delete element" and "insert element" operations are expensive. For instance, `arr.unshift(obj)` operation has to renumber all elements to make room for a new `obj`, and if the array is big, it takes time. Same with `arr.shift()`. +...Но у массивов есть недостатки. Операции "удалить элемент" и "вставить элемент" являются дорогостоящими. Например, операция `arr.unshift(obj)` должна переиндексировать все элементы, чтобы освободить место для нового `obj`, и, если массив большой, на это потребуется время. То же самое с `arr.shift()`. -The only structural modifications that do not require mass-renumbering are those that operate with the end of array: `arr.push/pop`. So an array can be quite slow for big queues. +Единственные структурные изменения, не требующие массовой переиндексации - это изменения, которые выполняются с конца массива: `arr.push/pop`. Таким образом, массив может быть довольно медленным для больших очередей, когда нам приходится работать с его началом. -Alternatively, if we really need fast insertion/deletion, we can choose another data structure called a [linked list](https://en.wikipedia.org/wiki/Linked_list). +Или же, если нам действительно нужны быстрые вставка/удаление, мы можем выбрать другую структуру данных, называемую [связный список](https://ru.wikipedia.org/wiki/Связный_список). -The *linked list element* is recursively defined as an object with: -- `value`. -- `next` property referencing the next *linked list element* or `null` if that's the end. +Элемент *связного списка* определяется рекурсивно как объект с: +- `value`, +- `next` - свойство, ссылающееся на следующий *элемент связного списка* или `null`, если это последний элемент. -For instance: +Пример: ```js let list = { @@ -442,91 +442,97 @@ let list = { }; ``` -Graphical representation of the list: +Графическое представление списка: -![linked list](linked-list.png) +![связный список](linked-list.svg) -An alternative code for creation: +Альтернативный код для создания: ```js no-beautify let list = { value: 1 }; list.next = { value: 2 }; list.next.next = { value: 3 }; list.next.next.next = { value: 4 }; +list.next.next.next.next = null; ``` -Here we can even more clearer see that there are multiple objects, each one has the `value` and `next` pointing to the neighbour. The `list` variable is the first object in the chain, so following `next` pointers from it we can reach any element. +Здесь мы можем ещё лучше увидеть, что есть несколько объектов, каждый из которых имеет `value` и `next`, указывающий на соседа. Переменная `list` является первым объектом в цепочке, поэтому, следуя по указателям `next` из неё, мы можем попасть в любой элемент. -The list can be easily split into multiple parts and later joined back: +Список можно легко разделить на несколько частей и впоследствии объединить обратно: ```js let secondList = list.next.next; list.next.next = null; ``` -![linked list split](linked-list-split.png) +![разделение связного списка](linked-list-split.svg) -To join: +Для объединения: ```js list.next.next = secondList; ``` -And surely we can insert or remove items in any place. +И, конечно, мы можем вставить или удалить элементы из любого места. -For instance, to prepend a new value, we need to update the head of the list: +Например, для добавления нового элемента нам нужно обновить первый элемент списка: ```js let list = { value: 1 }; list.next = { value: 2 }; list.next.next = { value: 3 }; list.next.next.next = { value: 4 }; +list.next.next.next.next = null; *!* -// prepend the new value to the list +// добавление нового элемента в список list = { value: "new item", next: list }; */!* ``` -![linked list](linked-list-0.png) +![связный список](linked-list-0.svg) -To remove a value from the middle, change `next` of the previous one: +Чтобы удалить элемент из середины списка, нужно изменить значение `next` предыдущего элемента: ```js list.next = list.next.next; ``` -![linked list](linked-list-remove-1.png) +![связный список](linked-list-remove-1.svg) + +`list.next` перепрыгнуло с `1` на значение `2`. Значение `1` теперь исключено из цепочки. Если оно не хранится где-нибудь ещё, оно будет автоматически удалено из памяти. -We made `list.next` jump over `1` to value `2`. The value `1` is now excluded from the chain. If it's not stored anywhere else, it will be automatically removed from the memory. +В отличие от массивов, нет перенумерации, элементы легко переставляются. -Unlike arrays, there's no mass-renumbering, we can easily rearrange elements. +Естественно, списки не всегда лучше массивов. В противном случае все пользовались бы только списками. -Naturally, lists are not always better than arrays. Otherwise everyone would use only lists. +Главным недостатком является то, что мы не можем легко получить доступ к элементу по его индексу. В простом массиве: `arr[n]` является прямой ссылкой. Но в списке мы должны начать с первого элемента и перейти в `next` N раз, чтобы получить N-й элемент. -The main drawback is that we can't easily access an element by its number. In an array that's easy: `arr[n]` is a direct reference. But in the list we need to start from the first item and go `next` `N` times to get the Nth element. +...Но нам не всегда нужны такие операции. Например, нам может быть нужна очередь или даже [двухсторонняя очередь](https://ru.wikipedia.org/wiki/Двухсторонняя_очередь) -- это упорядоченная структура, которая позволяет очень быстро добавлять/удалять элементы с обоих концов, но там не нужен доступ в середину. -...But we don't always need such operations. For instance, when we need a queue or even a [deque](https://en.wikipedia.org/wiki/Double-ended_queue) -- the ordered structure that must allow very fast adding/removing elements from both ends. +Списки могут быть улучшены: +- Можно добавить свойство `prev` в дополнение к `next` для ссылки на предыдущий элемент, чтобы легко двигаться по списку назад. +- Можно также добавить переменную `tail`, которая будет ссылаться на последний элемент списка (и обновлять её при добавлении/удалении элементов с конца). +- ...Возможны другие изменения: главное, чтобы структура данных соответствовала нашим задачам с точки зрения производительности и удобства. -Sometimes it's worth to add another variable named `tail` to track the last element of the list (and update it when adding/removing elements from the end). For large sets of elements the speed difference versus arrays is huge. +## Итого -## Summary +Термины: -Terms: -- *Recursion* is a programming term that means a "self-calling" function. Such functions can be used to solve certain tasks in elegant ways. +- *Рекурсия* - это термин в программировании, означающий вызов функцией самой себя. Рекурсивные функции могут быть использованы для элегантного решения определённых задач. - When a function calls itself, that's called a *recursion step*. The *basis* of recursion is function arguments that make the task so simple that the function does not make further calls. + Когда функция вызывает саму себя, это называется *шагом рекурсии*. *База* рекурсии - это такие аргументы функции, которые делают задачу настолько простой, что решение не требует дальнейших вложенных вызовов. -- A [recursively-defined](https://en.wikipedia.org/wiki/Recursive_data_type) data structure is a data structure that can be defined using itself. +- [Рекурсивно определяемая](https://en.wikipedia.org/wiki/Recursive_data_type) структура данных - это структура данных, которая может быть определена с использованием самой себя. - For instance, the linked list can be defined as a data structure consisting of an object referencing a list (or null). + Например, связный список может быть определён как структура данных, состоящая из объекта, содержащего ссылку на список (или null). ```js list = { value, next -> list } ``` - Trees like HTML elements tree or the department tree from this chapter are also naturally recursive: they branch and every branch can have other branches. + Деревья, такие как дерево HTML-элементов или дерево отделов из этой главы, также являются рекурсивными: у них есть ветви, и каждая ветвь может содержать другие ветви. - Recursive functions can be used to walk them as we've seen in the `sumSalary` example. + Как мы видели в примере `sumSalary`, рекурсивные функции могут быть использованы для прохода по ним. -Any recursive function can be rewritten into an iterative one. And that's sometimes required to optimize stuff. But for many tasks a recursive solution is fast enough and easier to write and support. +Любая рекурсивная функция может быть переписана в итеративную. И это иногда требуется для оптимизации работы. Но для многих задач рекурсивное решение достаточно быстрое и простое в написании и поддержке. diff --git a/1-js/06-advanced-functions/01-recursion/head.html b/1-js/06-advanced-functions/01-recursion/head.html index bb7fe8772f..fab5e6ce7d 100644 --- a/1-js/06-advanced-functions/01-recursion/head.html +++ b/1-js/06-advanced-functions/01-recursion/head.html @@ -7,7 +7,7 @@ .function-execution-context { border: 1px solid black; - font-family: 'DejaVu Sans Mono', 'Lucida Console', 'Menlo', 'Monaco', monospace; + font-family: 'PT Mono', 'DejaVu Sans Mono', 'Lucida Console', 'Menlo', 'Monaco', monospace; padding: 4px 6px; margin: 0 4px; } @@ -17,7 +17,7 @@ } .function-execution-context-call::before { - content: ' call: '; + content: ' вызов: '; } .function-execution-context-list li:first-child { diff --git a/1-js/06-advanced-functions/01-recursion/linked-list-0.png b/1-js/06-advanced-functions/01-recursion/linked-list-0.png deleted file mode 100644 index c694e7021f..0000000000 Binary files a/1-js/06-advanced-functions/01-recursion/linked-list-0.png and /dev/null differ diff --git a/1-js/06-advanced-functions/01-recursion/linked-list-0.svg b/1-js/06-advanced-functions/01-recursion/linked-list-0.svg new file mode 100644 index 0000000000..5d23c7a4c6 --- /dev/null +++ b/1-js/06-advanced-functions/01-recursion/linked-list-0.svg @@ -0,0 +1 @@ +value1nextvalue"new item"nextvalue2nextvalue3nextvalue4nextnulllist \ No newline at end of file diff --git a/1-js/06-advanced-functions/01-recursion/linked-list-0@2x.png b/1-js/06-advanced-functions/01-recursion/linked-list-0@2x.png deleted file mode 100644 index 1fe50ace43..0000000000 Binary files a/1-js/06-advanced-functions/01-recursion/linked-list-0@2x.png and /dev/null differ diff --git a/1-js/06-advanced-functions/01-recursion/linked-list-remove-1.png b/1-js/06-advanced-functions/01-recursion/linked-list-remove-1.png deleted file mode 100644 index b4c89ecdf0..0000000000 Binary files a/1-js/06-advanced-functions/01-recursion/linked-list-remove-1.png and /dev/null differ diff --git a/1-js/06-advanced-functions/01-recursion/linked-list-remove-1.svg b/1-js/06-advanced-functions/01-recursion/linked-list-remove-1.svg new file mode 100644 index 0000000000..2f37449c47 --- /dev/null +++ b/1-js/06-advanced-functions/01-recursion/linked-list-remove-1.svg @@ -0,0 +1 @@ +value"new item"nextvalue1nextvalue2nextvalue3nextvalue4nextnulllist \ No newline at end of file diff --git a/1-js/06-advanced-functions/01-recursion/linked-list-remove-1@2x.png b/1-js/06-advanced-functions/01-recursion/linked-list-remove-1@2x.png deleted file mode 100644 index 6b5b95a0fa..0000000000 Binary files a/1-js/06-advanced-functions/01-recursion/linked-list-remove-1@2x.png and /dev/null differ diff --git a/1-js/06-advanced-functions/01-recursion/linked-list-split.png b/1-js/06-advanced-functions/01-recursion/linked-list-split.png deleted file mode 100644 index 310a360665..0000000000 Binary files a/1-js/06-advanced-functions/01-recursion/linked-list-split.png and /dev/null differ diff --git a/1-js/06-advanced-functions/01-recursion/linked-list-split.svg b/1-js/06-advanced-functions/01-recursion/linked-list-split.svg new file mode 100644 index 0000000000..6c30721307 --- /dev/null +++ b/1-js/06-advanced-functions/01-recursion/linked-list-split.svg @@ -0,0 +1 @@ +value1nextvalue2nextvalue3nextvalue4nextnullnullsecondListlist \ No newline at end of file diff --git a/1-js/06-advanced-functions/01-recursion/linked-list-split@2x.png b/1-js/06-advanced-functions/01-recursion/linked-list-split@2x.png deleted file mode 100644 index 2de39ca4f5..0000000000 Binary files a/1-js/06-advanced-functions/01-recursion/linked-list-split@2x.png and /dev/null differ diff --git a/1-js/06-advanced-functions/01-recursion/linked-list.png b/1-js/06-advanced-functions/01-recursion/linked-list.png deleted file mode 100644 index 80fabffc6a..0000000000 Binary files a/1-js/06-advanced-functions/01-recursion/linked-list.png and /dev/null differ diff --git a/1-js/06-advanced-functions/01-recursion/linked-list.svg b/1-js/06-advanced-functions/01-recursion/linked-list.svg new file mode 100644 index 0000000000..c02744f390 --- /dev/null +++ b/1-js/06-advanced-functions/01-recursion/linked-list.svg @@ -0,0 +1 @@ +value1nextvalue2nextvalue3nextvalue4nextnulllist \ No newline at end of file diff --git a/1-js/06-advanced-functions/01-recursion/linked-list@2x.png b/1-js/06-advanced-functions/01-recursion/linked-list@2x.png deleted file mode 100644 index 1e6dc14839..0000000000 Binary files a/1-js/06-advanced-functions/01-recursion/linked-list@2x.png and /dev/null differ diff --git a/1-js/06-advanced-functions/01-recursion/recursion-pow.png b/1-js/06-advanced-functions/01-recursion/recursion-pow.png deleted file mode 100644 index 354f012714..0000000000 Binary files a/1-js/06-advanced-functions/01-recursion/recursion-pow.png and /dev/null differ diff --git a/1-js/06-advanced-functions/01-recursion/recursion-pow.svg b/1-js/06-advanced-functions/01-recursion/recursion-pow.svg new file mode 100644 index 0000000000..0901f76364 --- /dev/null +++ b/1-js/06-advanced-functions/01-recursion/recursion-pow.svg @@ -0,0 +1 @@ +pow(x,n)xx * pow(x, n-1)n == 1 ?ДаНетрекурсивный вызов до n==1 \ No newline at end of file diff --git a/1-js/06-advanced-functions/01-recursion/recursion-pow@2x.png b/1-js/06-advanced-functions/01-recursion/recursion-pow@2x.png deleted file mode 100644 index 22ae944108..0000000000 Binary files a/1-js/06-advanced-functions/01-recursion/recursion-pow@2x.png and /dev/null differ diff --git a/1-js/06-advanced-functions/01-recursion/recursive-salaries.png b/1-js/06-advanced-functions/01-recursion/recursive-salaries.png deleted file mode 100644 index b40783a9b4..0000000000 Binary files a/1-js/06-advanced-functions/01-recursion/recursive-salaries.png and /dev/null differ diff --git a/1-js/06-advanced-functions/01-recursion/recursive-salaries.svg b/1-js/06-advanced-functions/01-recursion/recursive-salaries.svg new file mode 100644 index 0000000000..bd874c5ba6 --- /dev/null +++ b/1-js/06-advanced-functions/01-recursion/recursive-salaries.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/1-js/06-advanced-functions/01-recursion/recursive-salaries@2x.png b/1-js/06-advanced-functions/01-recursion/recursive-salaries@2x.png deleted file mode 100644 index e37597128f..0000000000 Binary files a/1-js/06-advanced-functions/01-recursion/recursive-salaries@2x.png and /dev/null differ diff --git a/1-js/06-advanced-functions/02-rest-parameters-spread-operator/article.md b/1-js/06-advanced-functions/02-rest-parameters-spread-operator/article.md index a98d8eddda..6aa76bddb9 100644 --- a/1-js/06-advanced-functions/02-rest-parameters-spread-operator/article.md +++ b/1-js/06-advanced-functions/02-rest-parameters-spread-operator/article.md @@ -1,20 +1,20 @@ -# Rest parameters and spread operator +# Остаточные параметры и оператор расширения -Many JavaScript built-in functions support an arbitrary number of arguments. +Многие встроенные функции JavaScript поддерживают произвольное количество аргументов. -For instance: +Например: -- `Math.max(arg1, arg2, ..., argN)` -- returns the greatest of the arguments. -- `Object.assign(dest, src1, ..., srcN)` -- copies properties from `src1..N` into `dest`. -- ...and so on. +- `Math.max(arg1, arg2, ..., argN)` – вычисляет максимальное число из переданных. +- `Object.assign(dest, src1, ..., srcN)` – копирует свойства из исходных объектов `src1..N` в целевой объект `dest`. +- ...и так далее. -In this chapter we'll learn how to do the same. And, more importantly, how to feel comfortable working with such functions and arrays. +В этой главе мы узнаем, как сделать то же самое с нашими собственными функциями и как передавать таким функциям параметры в виде массива. -## Rest parameters `...` +## Остаточные параметры (`...`) -A function can be called with any number of arguments, no matter how it is defined. +Вызывать функцию можно с любым количеством аргументов независимо от того, как она была определена. -Like here: +Например: ```js run function sum(a, b) { return a + b; @@ -23,14 +23,14 @@ function sum(a, b) { alert( sum(1, 2, 3, 4, 5) ); ``` -There will be no error because of "excessive" arguments. But of course in the result only the first two will be counted. +Лишние аргументы не вызовут ошибку. Но, конечно, посчитаются только первые два. -The rest parameters can be mentioned in a function definition with three dots `...`. They literally mean "gather the remaining parameters into an array". +*Остаточные параметры* могут быть обозначены через три точки `...`. Буквально это значит: "собери оставшиеся параметры и положи их в массив". -For instance, to gather all arguments into array `args`: +Например, соберём все аргументы в массив `args`: ```js run -function sumAll(...args) { // args is the name for the array +function sumAll(...args) { // args — имя массива let sum = 0; for (let arg of args) sum += arg; @@ -43,41 +43,41 @@ alert( sumAll(1, 2) ); // 3 alert( sumAll(1, 2, 3) ); // 6 ``` -We can choose to get the first parameters as variables, and gather only the rest. +Мы можем положить первые несколько параметров в переменные, а остальные -- собрать в массив. -Here the first two arguments go into variables and the rest go into `titles` array: +В примере ниже первые два аргумента функции станут именем и фамилией, а третий и последующие превратятся в массив `titles`: ```js run function showName(firstName, lastName, ...titles) { - alert( firstName + ' ' + lastName ); // Julius Caesar + alert( firstName + ' ' + lastName ); // Юлий Цезарь - // the rest go into titles array - // i.e. titles = ["Consul", "Imperator"] - alert( titles[0] ); // Consul - alert( titles[1] ); // Imperator + // Оставшиеся параметры пойдут в массив + // titles = ["Консул", "Император"] + alert( titles[0] ); // Консул + alert( titles[1] ); // Император alert( titles.length ); // 2 } -showName("Julius", "Caesar", "Consul", "Imperator"); +showName("Юлий", "Цезарь", "Консул", "Император"); ``` -````warn header="The rest parameters must be at the end" -The rest parameters gather all remaining arguments, so the following does not make sense and causes an error: +````warn header="Остаточные параметры должны располагаться в конце" +Остаточные параметры собирают все остальные аргументы, поэтому бессмысленно писать что-либо после них. Это вызовет ошибку: ```js -function f(arg1, ...rest, arg2) { // arg2 after ...rest ?! - // error +function f(arg1, ...rest, arg2) { // arg2 после ...rest ?! + // Ошибка } ``` -The `...rest` must always be last. +`...rest` должен всегда быть последним. ```` -## The "arguments" variable +## Переменная "arguments" [#the-arguments-variable] -There is also a special array-like object named `arguments` that contains all arguments by their index. +Все аргументы функции находятся в псевдомассиве `arguments` под своими порядковыми номерами. -For instance: +Например: ```js run function showName() { @@ -85,59 +85,58 @@ function showName() { alert( arguments[0] ); alert( arguments[1] ); - // it's iterable - // for(let arg of arguments) alert(arg); + // Объект arguments можно перебирать + // for (let arg of arguments) alert(arg); } -// shows: 2, Julius, Caesar -showName("Julius", "Caesar"); +// Вывод: 2, Юлий, Цезарь +showName("Юлий", "Цезарь"); -// shows: 1, Ilya, undefined (no second argument) -showName("Ilya"); +// Вывод: 1, Илья, undefined (второго аргумента нет) +showName("Илья"); ``` -In old times, rest parameters did not exist in the language, and using `arguments` was the only way to get all arguments of the function, no matter their total number. +Раньше в языке не было остаточных параметров, и получить все аргументы функции можно было только с помощью `arguments`. Этот способ всё ещё работает, мы можем найти его в старом коде. -And it still works, we can use it today. +Но у него есть один недостаток. Хотя `arguments` похож на массив, и его тоже можно перебирать, это всё же не массив. Он не поддерживает методы массивов, поэтому мы не можем, например, вызвать `arguments.map(...)`. -But the downside is that although `arguments` is both array-like and iterable, it's not an array. It does not support array methods, so we can't call `arguments.map(...)` for example. +К тому же, `arguments` всегда содержит все аргументы функции — мы не можем получить их часть. А остаточные параметры позволяют это сделать. -Also, it always contains all arguments. We can't capture them partially, like we did with rest parameters. +Соответственно, для более удобной работы с аргументами лучше использовать остаточные параметры. -So when we need these features, then rest parameters are preferred. +````smart header="Стрелочные функции не имеют `\"arguments\"`" +Если мы обратимся к `arguments` из стрелочной функции, то получим аргументы внешней "нормальной" функции. -````smart header="Arrow functions do not have `\"arguments\"`" -If we access the `arguments` object from an arrow function, it takes them from the outer "normal" function. - -Here's an example: +Пример: ```js run function f() { let showArg = () => alert(arguments[0]); - showArg(); + showArg(2); } f(1); // 1 ``` + +Как мы помним, у стрелочных функций нет собственного `this`. Теперь мы знаем, что нет и своего объекта `arguments`. ```` -As we remember, arrow functions don't have their own `this`. Now we know they don't have the special `arguments` object either. -## Spread operator [#spread-operator] +## Оператор расширения [#spread-operator] -We've just seen how to get an array from the list of parameters. +Мы узнали, как получить массив из списка параметров. -But sometimes we need to do exactly the reverse. +Но иногда нужно сделать в точности противоположное. -For instance, there's a built-in function [Math.max](mdn:js/Math/max) that returns the greatest number from a list: +Например, есть встроенная функция [Math.max](mdn:js/Math/max). Она возвращает наибольшее число из списка: ```js run alert( Math.max(3, 5, 1) ); // 5 ``` -Now let's say we have an array `[3, 5, 1]`. How do we call `Math.max` with it? +Допустим, у нас есть массив чисел `[3, 5, 1]`. Как вызвать для него `Math.max`? -Passing it "as is" won't work, because `Math.max` expects a list of numeric arguments, not a single array: +Просто так их не вставишь — `Math.max` ожидает получить список чисел, а не один массив. ```js run let arr = [3, 5, 1]; @@ -147,21 +146,21 @@ alert( Math.max(arr) ); // NaN */!* ``` -And surely we can't manually list items in the code `Math.max(arr[0], arr[1], arr[2])`, because we may be unsure how many there are. As our script executes, there could be a lot, or there could be none. And that would get ugly. +Конечно, мы можем вводить числа вручную : `Math.max(arr[0], arr[1], arr[2])`. Но, во-первых, это плохо выглядит, а, во-вторых, мы не всегда знаем, сколько будет аргументов. Их может быть как очень много, так и не быть совсем. -*Spread operator* to the rescue! It looks similar to rest parameters, also using `...`, but does quite the opposite. +И тут нам поможет *оператор расширения*. Он похож на остаточные параметры – тоже использует `...`, но делает совершенно противоположное. -When `...arr` is used in the function call, it "expands" an iterable object `arr` into the list of arguments. +Когда `...arr` используется при вызове функции, он "расширяет" перебираемый объект `arr` в список аргументов. -For `Math.max`: +Для `Math.max`: ```js run let arr = [3, 5, 1]; -alert( Math.max(...arr) ); // 5 (spread turns array into a list of arguments) +alert( Math.max(...arr) ); // 5 (оператор "раскрывает" массив в список аргументов) ``` -We also can pass multiple iterables this way: +Этим же способом мы можем передать несколько итерируемых объектов: ```js run let arr1 = [1, -2, 3, 4]; @@ -170,8 +169,7 @@ let arr2 = [8, 3, -8, 1]; alert( Math.max(...arr1, ...arr2) ); // 8 ``` -We can even combine the spread operator with normal values: - +Мы даже можем комбинировать оператор расширения с обычными значениями: ```js run let arr1 = [1, -2, 3, 4]; @@ -180,7 +178,7 @@ let arr2 = [8, 3, -8, 1]; alert( Math.max(1, ...arr1, 2, ...arr2, 25) ); // 25 ``` -Also, the spread operator can be used to merge arrays: +Оператор расширения можно использовать и для слияния массивов: ```js run let arr = [3, 5, 1]; @@ -190,56 +188,56 @@ let arr2 = [8, 9, 15]; let merged = [0, ...arr, 2, ...arr2]; */!* -alert(merged); // 0,3,5,1,2,8,9,15 (0, then arr, then 2, then arr2) +alert(merged); // 0,3,5,1,2,8,9,15 (0, затем arr, затем 2, в конце arr2) ``` -In the examples above we used an array to demonstrate the spread operator, but any iterable will do. +В примерах выше мы использовали массив, чтобы продемонстрировать свойства оператора расширения, но он работает с любым перебираемым объектом. -For instance, here we use the spread operator to turn the string into array of characters: +Например, оператор расширения подойдёт для того, чтобы превратить строку в массив символов: ```js run -let str = "Hello"; +let str = "Привет"; -alert( [...str] ); // H,e,l,l,o +alert( [...str] ); // П,р,и,в,е,т ``` -The spread operator internally uses iterators to gather elements, the same way as `for..of` does. +Посмотрим, что происходит. Под капотом оператор расширения использует итераторы, чтобы перебирать элементы. Так же, как это делает `for..of`. -So, for a string, `for..of` returns characters and `...str` becomes `"H","e","l","l","o"`. The list of characters is passed to array initializer `[...str]`. +Цикл `for..of` перебирает строку как последовательность символов, поэтому из `...str` получается `"П", "р", "и", "в", "е", "т"`. Получившиеся символы собираются в массив при помощи стандартного объявления массива: `[...str]`. -For this particular task we could also use `Array.from`, because it converts an iterable (like a string) into an array: +Для этой задачи мы можем использовать и `Array.from`. Он тоже преобразует перебираемый объект (такой как строка) в массив: ```js run -let str = "Hello"; +let str = "Привет"; -// Array.from converts an iterable into an array -alert( Array.from(str) ); // H,e,l,l,o +// Array.from преобразует перебираемый объект в массив +alert( Array.from(str) ); // П,р,и,в,е,т ``` -The result is the same as `[...str]`. +Результат аналогичен `[...str]`. -But there's a subtle difference between `Array.from(obj)` and `[...obj]`: +Но между `Array.from(obj)` и `[...obj]` есть разница: -- `Array.from` operates on both array-likes and iterables. -- The spread operator operates only on iterables. +- `Array.from` работает как с псевдомассивами, так и с итерируемыми объектами +- Оператор расширения работает только с итерируемыми объектами -So, for the task of turning something into an array, `Array.from` tends to be more universal. +Выходит, что если нужно сделать из чего угодно массив, то `Array.from` — более универсальный метод. -## Summary +## Итого -When we see `"..."` in the code, it is either rest parameters or the spread operator. +Когда мы видим `"..."` в коде, это могут быть как остаточные параметры, так и оператор расширения. -There's an easy way to distinguish between them: +Как отличить их друг от друга: -- When `...` is at the end of function parameters, it's "rest parameters" and gathers the rest of the list of arguments into an array. -- When `...` occurs in a function call or alike, it's called a "spread operator" and expands an array into a list. +- Если `...` располагается в конце списка параметров функции, то это "остаточные параметры". Он собирает остальные неуказанные аргументы и делает из них массив. +- Если `...` встретился в вызове функции или где-либо ещё, то это "оператор расширения". Он извлекает элементы из массива. -Use patterns: +Полезно запомнить: -- Rest parameters are used to create functions that accept any number of arguments. -- The spread operator is used to pass an array to functions that normally require a list of many arguments. +- Остаточные параметры используются, чтобы создавать новые функции с неопределённым числом аргументов. +- С помощью оператора расширения можно вставить массив в функцию, которая по умолчанию работает с обычным списком аргументов. -Together they help to travel between a list and an array of parameters with ease. +Вместе эти конструкции помогают легко преобразовывать наборы значений в массивы и обратно. -All arguments of a function call are also available in "old-style" `arguments`: array-like iterable object. +К аргументам функции можно обращаться и по-старому — через псевдомассив `arguments`. diff --git a/1-js/06-advanced-functions/03-closure/1-closure-latest-changes/solution.md b/1-js/06-advanced-functions/03-closure/1-closure-latest-changes/solution.md new file mode 100644 index 0000000000..f1119ba08e --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/1-closure-latest-changes/solution.md @@ -0,0 +1,5 @@ +Ответ: **Pete**. + +Функция получает внешние переменные в том виде, в котором они находятся сейчас, она использует самые последние значения. + +Старые значения переменных нигде не сохраняются. Когда функция обращается к переменной, она берет текущее значение из своего или внешнего лексического окружения. diff --git a/1-js/06-advanced-functions/03-closure/1-closure-latest-changes/task.md b/1-js/06-advanced-functions/03-closure/1-closure-latest-changes/task.md new file mode 100644 index 0000000000..00d5f10099 --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/1-closure-latest-changes/task.md @@ -0,0 +1,23 @@ +importance: 5 + +--- + +# Учитывает ли функция последние изменения? + +Функция `sayHi` использует имя внешней переменной. Какое значение будет использоваться при выполнении функции? + +```js +let name = "John"; + +function sayHi() { + alert("Hi, " + name); +} + +name = "Pete"; + +sayHi(); // что будет показано: "John" или "Pete"? +``` + +Такие ситуации встречаются как при разработке для браузера, так и для сервера. Функция может быть назначена на выполнение позже, чем она была создана, например, после действия пользователя или сетевого запроса. + +Итак, вопрос: учитывает ли она последние изменения? diff --git a/1-js/06-advanced-functions/03-closure/8-make-army/_js.view/solution.js b/1-js/06-advanced-functions/03-closure/10-make-army/_js.view/solution.js similarity index 100% rename from 1-js/06-advanced-functions/03-closure/8-make-army/_js.view/solution.js rename to 1-js/06-advanced-functions/03-closure/10-make-army/_js.view/solution.js diff --git a/1-js/06-advanced-functions/03-closure/8-make-army/_js.view/source.js b/1-js/06-advanced-functions/03-closure/10-make-army/_js.view/source.js similarity index 100% rename from 1-js/06-advanced-functions/03-closure/8-make-army/_js.view/source.js rename to 1-js/06-advanced-functions/03-closure/10-make-army/_js.view/source.js diff --git a/1-js/06-advanced-functions/03-closure/8-make-army/_js.view/test.js b/1-js/06-advanced-functions/03-closure/10-make-army/_js.view/test.js similarity index 100% rename from 1-js/06-advanced-functions/03-closure/8-make-army/_js.view/test.js rename to 1-js/06-advanced-functions/03-closure/10-make-army/_js.view/test.js diff --git a/1-js/06-advanced-functions/03-closure/10-make-army/lexenv-makearmy-empty.svg b/1-js/06-advanced-functions/03-closure/10-make-army/lexenv-makearmy-empty.svg new file mode 100644 index 0000000000..05a28069cf --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/10-make-army/lexenv-makearmy-empty.svg @@ -0,0 +1 @@ +outer<пусто>makeArmy() LexicalEnvironmentитерация while LexicalEnvironment<пусто><пусто><пусто>i: 10 diff --git a/1-js/06-advanced-functions/03-closure/10-make-army/lexenv-makearmy-for-fixed.svg b/1-js/06-advanced-functions/03-closure/10-make-army/lexenv-makearmy-for-fixed.svg new file mode 100644 index 0000000000..0d144be083 --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/10-make-army/lexenv-makearmy-for-fixed.svg @@ -0,0 +1 @@ +outermakeArmy() LexicalEnvironmentитерация for LexicalEnvironmenti: 0i: 1i: 2i: 10... diff --git a/1-js/06-advanced-functions/03-closure/10-make-army/lexenv-makearmy-while-fixed.svg b/1-js/06-advanced-functions/03-closure/10-make-army/lexenv-makearmy-while-fixed.svg new file mode 100644 index 0000000000..0237830d83 --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/10-make-army/lexenv-makearmy-while-fixed.svg @@ -0,0 +1 @@ +outerj: 0j: 1j: 2j: 10...makeArmy() LexicalEnvironmentитерация while LexicalEnvironment diff --git a/1-js/06-advanced-functions/03-closure/10-make-army/solution.md b/1-js/06-advanced-functions/03-closure/10-make-army/solution.md new file mode 100644 index 0000000000..fe79b85d8c --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/10-make-army/solution.md @@ -0,0 +1,128 @@ + +Давайте посмотрим, что происходит внутри `makeArmy`, и решение станет очевидным. + +1. Она создаёт пустой массив `shooters`: + + ```js + let shooters = []; + ``` +2. В цикле заполняет его `shooters.push(function...)`. + + Каждый элемент -- это функция, так что получится такой массив: + + ```js no-beautify + shooters = [ + function () { alert(i); }, + function () { alert(i); }, + function () { alert(i); }, + function () { alert(i); }, + function () { alert(i); }, + function () { alert(i); }, + function () { alert(i); }, + function () { alert(i); }, + function () { alert(i); }, + function () { alert(i); } + ]; + ``` + +3. Функция возвращает массив. + + Позже вызов `army[5]()` получит элемент `army[5]` из массива (это будет функция) и вызовет её. + + Теперь, почему все эти функции показывают одно и то же? + + Всё потому, что внутри функций `shooter` нет локальной переменной `i`. Когда вызывается такая функция, она берёт `i` из своего внешнего лексического окружения. + + Какое будет значение у `i`? + + Если мы посмотрим в исходный код: + + ```js + function makeArmy() { + ... + let i = 0; + while (i < 10) { + let shooter = function() { // функция shooter + alert( i ); // должна выводить порядковый номер + }; + shooters.push(shooter); // и добавлять стрелка в массив + i++; + } + ... + } + ``` + + ...Мы увидим, что оно живёт в лексическом окружении, связанном с текущим вызовом `makeArmy()`. Но, когда вызывается `army[5]()`, `makeArmy` уже завершила свою работу, и последнее значение `i`: `10` (конец цикла `while`). + + Как результат, все функции `shooter` получат одно и то же значение из внешнего лексического окружения: последнее значение `i=10`. + + ![](lexenv-makearmy-empty.svg) + + Как вы можете видеть выше, на каждой итерации блока `while {...}` создается новое лексическое окружение. Чтобы исправить это, мы можем скопировать значение `i` в переменную внутри блока `while {...}`, например, так: + + ```js run + function makeArmy() { + let shooters = []; + + let i = 0; + while (i < 10) { + *!* + let j = i; + */!* + let shooter = function() { // функция shooter + alert( *!*j*/!* ); // должна выводить порядковый номер + }; + shooters.push(shooter); + i++; + } + + return shooters; + } + + let army = makeArmy(); + + // теперь код работает правильно + army[0](); // 0 + army[5](); // 5 + ``` + + Здесь `let j = i` объявляет "итерационно-локальную" переменную `j` и копирует в нее `i`. Примитивы копируются "по значению", поэтому фактически мы получаем независимую копию `i`, принадлежащую текущей итерации цикла. + + Функции `shooter` работают правильно, потому что значение `i` теперь живет чуть ближе. Не в лексическом окружении `makeArmy()`, а в лексическом окружении, соответствующем текущей итерации цикла: + + ![](lexenv-makearmy-while-fixed.svg) + + Этой проблемы также можно было бы избежать, если бы мы использовали `for` в начале, например, так: + + ```js run demo + function makeArmy() { + + let shooters = []; + + *!* + for (let i = 0; i < 10; i++) { + */!* + let shooter = function() { // функция shooter + alert( i ); // должна выводить порядковый номер + }; + shooters.push(shooter); + } + + return shooters; + } + + let army = makeArmy(); + + army[0](); // 0 + army[5](); // 5 + ``` + + По сути, это то же самое, поскольку `for` на каждой итерации создает новое лексическое окружение со своей переменной `i`. Поэтому функция `shooter`, создаваемая на каждой итерации, ссылается на свою собственную переменную `i`, причем именно с этой итерации. + + ![](lexenv-makearmy-for-fixed.svg) + +Теперь, когда вы приложили столько усилий, чтобы прочитать это объяснение, а конечный вариант оказался так прост - использовать `for`, вы можете задаться вопросом -- стоило ли оно того? + +Что ж, если бы вы могли легко ответить на вопрос из задачи, вы бы не стали читать решение. Так что, должно быть, эта задача помогла вам лучше понять суть дела. + +Кроме того, действительно встречаются случаи, когда человек предпочитает `while`, а не `for`, и другие сценарии, где такие проблемы реальны. diff --git a/1-js/06-advanced-functions/03-closure/10-make-army/task.md b/1-js/06-advanced-functions/03-closure/10-make-army/task.md new file mode 100644 index 0000000000..dd1df3cbba --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/10-make-army/task.md @@ -0,0 +1,41 @@ +importance: 5 + +--- + +# Армия функций + +Следующий код создаёт массив из стрелков (`shooters`). + +Каждая функция предназначена выводить их порядковые номера. Но что-то пошло не так... + +```js run +function makeArmy() { + let shooters = []; + + let i = 0; + while (i < 10) { + let shooter = function() { // функция shooter + alert( i ); // должна выводить порядковый номер + }; + shooters.push(shooter); // и добавлять стрелка в массив + i++; + } + + // ...а в конце вернуть массив из всех стрелков + return shooters; +} + +let army = makeArmy(); + +*!* +// все стрелки выводят 10 вместо их порядковых номеров (0, 1, 2, 3...) +army[0](); // 10 от стрелка с порядковым номером 0 +army[1](); // 10 от стрелка с порядковым номером 1 +army[2](); // 10 ...и т.д. +*/!* +``` + +Почему у всех стрелков одинаковые номера? + +Почините код, чтобы он работал как задумано. + diff --git a/1-js/06-advanced-functions/03-closure/2-closure-variable-access/lexenv-nested-work.svg b/1-js/06-advanced-functions/03-closure/2-closure-variable-access/lexenv-nested-work.svg new file mode 100644 index 0000000000..1d25eb1bc8 --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/2-closure-variable-access/lexenv-nested-work.svg @@ -0,0 +1 @@ +makeWorker: function name: "John"<пусто>outerouterouternullname: "Pete" diff --git a/1-js/06-advanced-functions/03-closure/2-closure-variable-access/solution.md b/1-js/06-advanced-functions/03-closure/2-closure-variable-access/solution.md new file mode 100644 index 0000000000..f5e013d179 --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/2-closure-variable-access/solution.md @@ -0,0 +1,9 @@ +Ответ: **Pete**. + +Функция `work()` в приведенном ниже коде получает `name` из места его происхождения через ссылку на внешнее лексическое окружение: + +![](lexenv-nested-work.svg) + +Таким образом, в результате мы получаем `"Pete"`. + +Но если бы в `makeWorker()` не было `let name`, то поиск шел бы снаружи и брал глобальную переменную, что мы видим из приведенной выше цепочки. В этом случае результатом было бы `"John"`. diff --git a/1-js/06-advanced-functions/03-closure/2-closure-variable-access/task.md b/1-js/06-advanced-functions/03-closure/2-closure-variable-access/task.md new file mode 100644 index 0000000000..6623f93304 --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/2-closure-variable-access/task.md @@ -0,0 +1,29 @@ +importance: 5 + +--- + +# Какие переменные доступны? + +Приведенная ниже функция `makeWorker` создает другую функцию и возвращает ее. Эта новая функция может быть вызвана из другого места. + +Будет ли она иметь доступ к внешним переменным из места своего создания, или из места вызова, или из обоих мест? + +```js +function makeWorker() { + let name = "Pete"; + + return function() { + alert(name); + }; +} + +let name = "John"; + +// создаём функцию +let work = makeWorker(); + +// вызываем её +work(); // что будет показано? +``` + +Какое значение будет показано? "Pete" или "John"? diff --git a/1-js/06-advanced-functions/03-closure/2-counter-object-independent/solution.md b/1-js/06-advanced-functions/03-closure/2-counter-object-independent/solution.md deleted file mode 100644 index 6e745fd785..0000000000 --- a/1-js/06-advanced-functions/03-closure/2-counter-object-independent/solution.md +++ /dev/null @@ -1,24 +0,0 @@ - -Несомненно он отлично будет работать. - -Обе вложенные функции были созданы с одним и тем же внешним лексическим окружением, так что они имеют доступ к одной и той же переменной `count`: - -```js run -function Counter() { - let count = 0; - - this.up = function() { - return ++count; - }; - - this.down = function() { - return --count; - }; -} - -let counter = new Counter(); - -alert( counter.up() ); // 1 -alert( counter.up() ); // 2 -alert( counter.down() ); // 1 -``` diff --git a/1-js/06-advanced-functions/03-closure/1-counter-independent/solution.md b/1-js/06-advanced-functions/03-closure/3-counter-independent/solution.md similarity index 100% rename from 1-js/06-advanced-functions/03-closure/1-counter-independent/solution.md rename to 1-js/06-advanced-functions/03-closure/3-counter-independent/solution.md diff --git a/1-js/06-advanced-functions/03-closure/1-counter-independent/task.md b/1-js/06-advanced-functions/03-closure/3-counter-independent/task.md similarity index 100% rename from 1-js/06-advanced-functions/03-closure/1-counter-independent/task.md rename to 1-js/06-advanced-functions/03-closure/3-counter-independent/task.md diff --git a/1-js/06-advanced-functions/03-closure/3-function-in-if/task.md b/1-js/06-advanced-functions/03-closure/3-function-in-if/task.md deleted file mode 100644 index a63078f9cb..0000000000 --- a/1-js/06-advanced-functions/03-closure/3-function-in-if/task.md +++ /dev/null @@ -1,19 +0,0 @@ -# Функция в if - -Посмотрите на код. Какой будет результат у вызова на последней строке? - -```js run -let phrase = "Hello"; - -if (true) { - let user = "John"; - - function sayHi() { - alert(`${phrase}, ${user}`); - } -} - -*!* -sayHi(); -*/!* -``` diff --git a/1-js/06-advanced-functions/03-closure/4-counter-object-independent/solution.md b/1-js/06-advanced-functions/03-closure/4-counter-object-independent/solution.md new file mode 100644 index 0000000000..2c70a5c533 --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/4-counter-object-independent/solution.md @@ -0,0 +1,24 @@ + +Несомненно, он отлично будет работать. + +Обе вложенные функции были созданы с одним и тем же внешним лексическим окружением, так что они имеют доступ к одной и той же переменной `count`: + +```js run +function Counter() { + let count = 0; + + this.up = function() { + return ++count; + }; + + this.down = function() { + return --count; + }; +} + +let counter = new Counter(); + +alert( counter.up() ); // 1 +alert( counter.up() ); // 2 +alert( counter.down() ); // 1 +``` diff --git a/1-js/06-advanced-functions/03-closure/2-counter-object-independent/task.md b/1-js/06-advanced-functions/03-closure/4-counter-object-independent/task.md similarity index 100% rename from 1-js/06-advanced-functions/03-closure/2-counter-object-independent/task.md rename to 1-js/06-advanced-functions/03-closure/4-counter-object-independent/task.md diff --git a/1-js/06-advanced-functions/03-closure/3-function-in-if/solution.md b/1-js/06-advanced-functions/03-closure/5-function-in-if/solution.md similarity index 100% rename from 1-js/06-advanced-functions/03-closure/3-function-in-if/solution.md rename to 1-js/06-advanced-functions/03-closure/5-function-in-if/solution.md diff --git a/1-js/06-advanced-functions/03-closure/5-function-in-if/task.md b/1-js/06-advanced-functions/03-closure/5-function-in-if/task.md new file mode 100644 index 0000000000..9f2a9fcde6 --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/5-function-in-if/task.md @@ -0,0 +1,24 @@ +importance: 5 + +--- +# Функция внутри if + +Посмотрите на код. Какой будет результат у вызова на последней строке? + +**Обратите внимание:** результат зависит от режима выполнения кода. Здесь используется строгий режим `"use strict"`. + +```js +let phrase = "Hello"; + +if (true) { + let user = "John"; + + function sayHi() { + alert(`${phrase}, ${user}`); + } +} + +*!* +sayHi(); +*/!* +``` diff --git a/1-js/06-advanced-functions/03-closure/4-closure-sum/solution.md b/1-js/06-advanced-functions/03-closure/6-closure-sum/solution.md similarity index 100% rename from 1-js/06-advanced-functions/03-closure/4-closure-sum/solution.md rename to 1-js/06-advanced-functions/03-closure/6-closure-sum/solution.md diff --git a/1-js/06-advanced-functions/03-closure/4-closure-sum/task.md b/1-js/06-advanced-functions/03-closure/6-closure-sum/task.md similarity index 100% rename from 1-js/06-advanced-functions/03-closure/4-closure-sum/task.md rename to 1-js/06-advanced-functions/03-closure/6-closure-sum/task.md diff --git a/1-js/06-advanced-functions/03-closure/6-filter-through-function/task.md b/1-js/06-advanced-functions/03-closure/6-filter-through-function/task.md deleted file mode 100644 index 3d8ae598cb..0000000000 --- a/1-js/06-advanced-functions/03-closure/6-filter-through-function/task.md +++ /dev/null @@ -1,29 +0,0 @@ -importance: 5 - ---- - -# Фильтрация с помощью функции - -У нас есть встроенный метод `arr.filter(f)` для массивов. Он фильтрует все элементы с помощью функции `f`. Если она возвращает `true`, то элемент добавится в возвращаемый массив. - -Сделайте набор "готовых к употреблению" фильтров: - -- `inBetween(a, b)` -- между `a` и `b` или идентично им (включительно). -- `inArray([...])` -- в данном массиве. - -Они должны использоваться таким образом: - -- `arr.filter(inBetween(3,6))` -- выбирает только значения межу 3 и 6. -- `arr.filter(inArray([1,2,3]))` -- выбирает только элементы, совпадающие с одним из элементом массива - -Например: - -```js -/* .. ваш код для inBetween и inArray */ -let arr = [1, 2, 3, 4, 5, 6, 7]; - -alert( arr.filter(inBetween(3, 6)) ); // 3,4,5,6 - -alert( arr.filter(inArray([1, 2, 10])) ); // 1,2 -``` - diff --git a/1-js/06-advanced-functions/03-closure/7-let-scope/solution.md b/1-js/06-advanced-functions/03-closure/7-let-scope/solution.md new file mode 100644 index 0000000000..f2bc002d33 --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/7-let-scope/solution.md @@ -0,0 +1,40 @@ +Ответ: **ошибка**. + +Попробуйте запустить этот код: + +```js run +let x = 1; + +function func() { +*!* + console.log(x); // ReferenceError: Cannot access 'x' before initialization +*/!* + let x = 2; +} + +func(); +``` + +В этом примере мы можем наблюдать характерную разницу между "несуществующей" и "неинициализированной" переменной. + +Как вы могли прочитать в статье [](info:closure), переменная находится в неинициализированном состоянии с момента входа в блок кода (или функцию). И остается неинициализированной до соответствующего оператора `let`. + +Другими словами, переменная технически существует, но не может быть использована до `let`. + +Приведенный выше код демонстрирует это. + +```js +function func() { +*!* + // локальная переменная x известна движку с самого начала выполнения функции, + // но она является неинициализированной до let ("мёртвая зона") + // следовательно, ошибка +*/!* + + console.log(x); // ReferenceError: Cannot access 'x' before initialization + + let x = 2; +} +``` + +Эту зону временной непригодности переменной (от начала блока кода до `let`) иногда называют "мёртвой зоной". diff --git a/1-js/06-advanced-functions/03-closure/7-let-scope/task.md b/1-js/06-advanced-functions/03-closure/7-let-scope/task.md new file mode 100644 index 0000000000..134f44ec91 --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/7-let-scope/task.md @@ -0,0 +1,21 @@ +importance: 4 + +--- + +# Видна ли переменная? + +Что выведет данный код? + +```js +let x = 1; + +function func() { + console.log(x); // ? + + let x = 2; +} + +func(); +``` + +P.S. В этой задаче есть подвох. Решение не очевидно. diff --git a/1-js/06-advanced-functions/03-closure/7-sort-by-field/solution.md b/1-js/06-advanced-functions/03-closure/7-sort-by-field/solution.md deleted file mode 100644 index bd57085eaf..0000000000 --- a/1-js/06-advanced-functions/03-closure/7-sort-by-field/solution.md +++ /dev/null @@ -1,22 +0,0 @@ - - -```js run -let users = [ - { name: "John", age: 20, surname: "Johnson" }, - { name: "Pete", age: 18, surname: "Peterson" }, - { name: "Ann", age: 19, surname: "Hathaway" } -]; - -*!* -function byField(field) { - return (a, b) => a[field] > b[field] ? 1 : -1; -} -*/!* - -users.sort(byField('name')); -users.forEach(user => alert(user.name)); // Ann, John, Pete - -users.sort(byField('age')); -users.forEach(user => alert(user.name)); // Pete, Ann, John -``` - diff --git a/1-js/06-advanced-functions/03-closure/7-sort-by-field/task.md b/1-js/06-advanced-functions/03-closure/7-sort-by-field/task.md deleted file mode 100644 index 279b0d6be8..0000000000 --- a/1-js/06-advanced-functions/03-closure/7-sort-by-field/task.md +++ /dev/null @@ -1,36 +0,0 @@ -importance: 5 - ---- - -# Сортировать по полю - -У нас есть массив объектов, который нужно отсортировать: - -```js -let users = [ - { name: "John", age: 20, surname: "Johnson" }, - { name: "Pete", age: 18, surname: "Peterson" }, - { name: "Ann", age: 19, surname: "Hathaway" } -]; -``` - -Обычный способ был бы таким: - -```js -// по имени (Ann, John, Pete) -users.sort((a, b) => a.name > b.name ? 1 : -1); - -// по возрасту (Pete, Ann, John) -users.sort((a, b) => a.age > b.age ? 1 : -1); -``` - -Можем мы сделать его более кратким, например, вот так? - -```js -users.sort(byField('name')); -users.sort(byField('age')); -``` - -Таким образом, чтобы вместо функции, мы просто писали `byField(fieldName)`. - -Напишите функцию `byField`, которая может быть использована для этого. diff --git a/1-js/06-advanced-functions/03-closure/6-filter-through-function/_js.view/solution.js b/1-js/06-advanced-functions/03-closure/8-filter-through-function/_js.view/solution.js similarity index 100% rename from 1-js/06-advanced-functions/03-closure/6-filter-through-function/_js.view/solution.js rename to 1-js/06-advanced-functions/03-closure/8-filter-through-function/_js.view/solution.js diff --git a/1-js/06-advanced-functions/03-closure/6-filter-through-function/_js.view/source.js b/1-js/06-advanced-functions/03-closure/8-filter-through-function/_js.view/source.js similarity index 100% rename from 1-js/06-advanced-functions/03-closure/6-filter-through-function/_js.view/source.js rename to 1-js/06-advanced-functions/03-closure/8-filter-through-function/_js.view/source.js diff --git a/1-js/06-advanced-functions/03-closure/6-filter-through-function/_js.view/test.js b/1-js/06-advanced-functions/03-closure/8-filter-through-function/_js.view/test.js similarity index 100% rename from 1-js/06-advanced-functions/03-closure/6-filter-through-function/_js.view/test.js rename to 1-js/06-advanced-functions/03-closure/8-filter-through-function/_js.view/test.js diff --git a/1-js/06-advanced-functions/03-closure/6-filter-through-function/solution.md b/1-js/06-advanced-functions/03-closure/8-filter-through-function/solution.md similarity index 100% rename from 1-js/06-advanced-functions/03-closure/6-filter-through-function/solution.md rename to 1-js/06-advanced-functions/03-closure/8-filter-through-function/solution.md diff --git a/1-js/06-advanced-functions/03-closure/8-filter-through-function/task.md b/1-js/06-advanced-functions/03-closure/8-filter-through-function/task.md new file mode 100644 index 0000000000..1ca8244082 --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/8-filter-through-function/task.md @@ -0,0 +1,28 @@ +importance: 5 + +--- + +# Фильтрация с помощью функции + +У нас есть встроенный метод `arr.filter(f)` для массивов. Он фильтрует все элементы с помощью функции `f`. Если она возвращает `true`, то элемент добавится в возвращаемый массив. + +Сделайте набор "готовых к употреблению" фильтров: + +- `inBetween(a, b)` -- между `a` и `b` (включительно). +- `inArray([...])` -- находится в данном массиве. + +Они должны использоваться таким образом: + +- `arr.filter(inBetween(3,6))` -- выбирает только значения между 3 и 6 (включительно). +- `arr.filter(inArray([1,2,3]))` -- выбирает только элементы, совпадающие с одним из элементов массива + +Например: + +```js +/* .. ваш код для inBetween и inArray */ +let arr = [1, 2, 3, 4, 5, 6, 7]; + +alert( arr.filter(inBetween(3, 6)) ); // 3,4,5,6 + +alert( arr.filter(inArray([1, 2, 10])) ); // 1,2 +``` diff --git a/1-js/06-advanced-functions/03-closure/8-make-army/lexenv-makearmy.png b/1-js/06-advanced-functions/03-closure/8-make-army/lexenv-makearmy.png deleted file mode 100644 index 8e39564ffe..0000000000 Binary files a/1-js/06-advanced-functions/03-closure/8-make-army/lexenv-makearmy.png and /dev/null differ diff --git a/1-js/06-advanced-functions/03-closure/8-make-army/lexenv-makearmy@2x.png b/1-js/06-advanced-functions/03-closure/8-make-army/lexenv-makearmy@2x.png deleted file mode 100644 index 39cc13a422..0000000000 Binary files a/1-js/06-advanced-functions/03-closure/8-make-army/lexenv-makearmy@2x.png and /dev/null differ diff --git a/1-js/06-advanced-functions/03-closure/8-make-army/solution.md b/1-js/06-advanced-functions/03-closure/8-make-army/solution.md deleted file mode 100644 index 42f2f8046c..0000000000 --- a/1-js/06-advanced-functions/03-closure/8-make-army/solution.md +++ /dev/null @@ -1,122 +0,0 @@ - -Давайте посмотрим, что происходит внутри `makeArmy`, и решение станет очевидным. - -1. Она создаёт пустой массив `shooters`: - - ```js - let shooters = []; - ``` -2. В цикле заполняет его `shooters.push(function...)`. - - Каждый элемент -- это функция, так что получится такой массив: - - ```js no-beautify - shooters = [ - function () { alert(i); }, - function () { alert(i); }, - function () { alert(i); }, - function () { alert(i); }, - function () { alert(i); }, - function () { alert(i); }, - function () { alert(i); }, - function () { alert(i); }, - function () { alert(i); }, - function () { alert(i); } - ]; - ``` - -3. Функция возвращает массив. - -Позже вызов `army[5]()` получит элемент `army[5]` из массива (это будет функция) и вызовет её. - -Теперь, почему все эти функции показывают одно и то же? - -Всё потому, что внутри функций `shooter` нет локальной переменной `i`. Когда вызывается такая функция, она берёт `i` из своего внешнего лексического окружения. - -Какое будет значение у `i`? - -Если мы посмотрим в исходный код: - -```js -function makeArmy() { - ... - let i = 0; - while (i < 10) { - let shooter = function() { // функция shooter - alert( i ); // должна выводить порядковый номер - }; - ... - } - ... -} -``` - -...Мы увидим, что оно живёт в лексическом окружении, связанном с текущим вызовом `makeArmy()`. Но, когда вызывается `army[5]()`, `makeArmy` уже завершила свою работу, и последнее значение `i`: 10 (конец цикла `while`). - -Как результат, все функции `shooter` получат одно и то же из внешнего окружения: последнее значение `i=10`. - -Мы можем это исправить, переместив определение переменной в цикл: - -```js run demo -function makeArmy() { - - let shooters = []; - -*!* - for(let i = 0; i < 10; i++) { -*/!* - let shooter = function() { // функция shooter - alert( i ); // должна выводить порядковый номер - }; - shooters.push(shooter); - - - } - - return shooters; -} - -let army = makeArmy(); - -army[0](); // 0 -army[5](); // 5 -``` - -Теперь она работает правильно, потому что каждый раз, когда выполняется блок кода `for (let i=0...) {...}`, для него создается новое лексическое окружение с соответствующей переменной `i`. - -Так что значение `i` теперь живёт немного ближе. Не в лексическом окружении `makeArmy()`, а в лексическом окружении, которое соответствует текущей итерации цикла. Вот почему теперь она работает. - -![](lexenv-makearmy.png) - -Здесь мы переписали `while` в `for`. - -Можно использовать другой трюк, давайте рассмотрим его для лучшего понимания предмета: - -```js run -function makeArmy() { - let shooters = []; - - let i = 0; - while (i < 10) { -*!* - let j = i; -*/!* - let shooter = function() { // функция shooter - alert( *!*j*/!* ); // должна выводить порядковый номер - }; - shooters.push(shooter); - i++; - } - - return shooters; -} - -let army = makeArmy(); - -army[0](); // 0 -army[5](); // 5 -``` - -Цикл `while` так же, как и `for`, создаёт новое лексическое окружение для каждой итерации. Так что тут мы хотим убедиться, что он получит правильное значение для `shooter`. - -Мы копируем `let j = i`. Это создаёт локальную для итерации переменную `j` и копирует в неё `i`. Примитивы копируются "по значению", поэтому мы получаем совершенно независимую копию `i`, принадлежащую текущей итерации цикла. diff --git a/1-js/06-advanced-functions/03-closure/8-make-army/task.md b/1-js/06-advanced-functions/03-closure/8-make-army/task.md deleted file mode 100644 index 130909bb5e..0000000000 --- a/1-js/06-advanced-functions/03-closure/8-make-army/task.md +++ /dev/null @@ -1,35 +0,0 @@ -importance: 5 - ---- - -# Армия функций - -Следующий код создаёт массив из стрелков (`shooters`). - -Каждая функция предназначена выводить их порядковые номера. Но что-то пошло не так... - -```js run -function makeArmy() { - let shooters = []; - - let i = 0; - while (i < 10) { - let shooter = function() { // функция shooter - alert( i ); // должна выводить порядковый номер - }; - shooters.push(shooter); - i++; - } - - return shooters; -} - -let army = makeArmy(); - -army[0](); // у 0-го стрелка будет номер 10 -army[5](); // и у 5-го стрелка тоже будет номер 10 -// ... у всех стрелков будет номер 10, вместо 0, 1, 2, 3... -``` - -Почему у всех стрелков одинаковые номера? Почините код, чтобы он работал как задумано. - diff --git a/1-js/06-advanced-functions/03-closure/9-sort-by-field/_js.view/solution.js b/1-js/06-advanced-functions/03-closure/9-sort-by-field/_js.view/solution.js new file mode 100644 index 0000000000..8a71c869d9 --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/9-sort-by-field/_js.view/solution.js @@ -0,0 +1,3 @@ +function byField(fieldName){ + return (a, b) => a[fieldName] > b[fieldName] ? 1 : -1; +} diff --git a/1-js/06-advanced-functions/03-closure/9-sort-by-field/_js.view/source.js b/1-js/06-advanced-functions/03-closure/9-sort-by-field/_js.view/source.js new file mode 100644 index 0000000000..23b4338340 --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/9-sort-by-field/_js.view/source.js @@ -0,0 +1,5 @@ +function byField(fieldName){ + + // Your code goes here. + +} diff --git a/1-js/06-advanced-functions/03-closure/9-sort-by-field/_js.view/test.js b/1-js/06-advanced-functions/03-closure/9-sort-by-field/_js.view/test.js new file mode 100644 index 0000000000..802f28c4d8 --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/9-sort-by-field/_js.view/test.js @@ -0,0 +1,39 @@ +describe("byField", function(){ + + let users = [ + { name: "John", age: 20, surname: "Johnson" }, + { name: "Pete", age: 18, surname: "Peterson" }, + { name: "Ann", age: 19, surname: "Hathaway" }, + ]; + + it("sorts users by name", function(){ + let nameSortedKey = [ + { name: "Ann", age: 19, surname: "Hathaway" }, + { name: "John", age: 20, surname: "Johnson"}, + { name: "Pete", age: 18, surname: "Peterson" }, + ]; + let nameSortedAnswer = users.sort(byField("name")); + assert.deepEqual(nameSortedKey, nameSortedAnswer); + }); + + it("sorts users by age", function(){ + let ageSortedKey = [ + { name: "Pete", age: 18, surname: "Peterson" }, + { name: "Ann", age: 19, surname: "Hathaway" }, + { name: "John", age: 20, surname: "Johnson"}, + ]; + let ageSortedAnswer = users.sort(byField("age")); + assert.deepEqual(ageSortedKey, ageSortedAnswer); + }); + + it("sorts users by surname", function(){ + let surnameSortedKey = [ + { name: "Ann", age: 19, surname: "Hathaway" }, + { name: "John", age: 20, surname: "Johnson"}, + { name: "Pete", age: 18, surname: "Peterson" }, + ]; + let surnameSortedAnswer = users.sort(byField("surname")); + assert.deepEqual(surnameSortedAnswer, surnameSortedKey); + }); + +}); diff --git a/1-js/06-advanced-functions/03-closure/9-sort-by-field/solution.md b/1-js/06-advanced-functions/03-closure/9-sort-by-field/solution.md new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/9-sort-by-field/solution.md @@ -0,0 +1 @@ + diff --git a/1-js/06-advanced-functions/03-closure/9-sort-by-field/task.md b/1-js/06-advanced-functions/03-closure/9-sort-by-field/task.md new file mode 100644 index 0000000000..ca436771d9 --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/9-sort-by-field/task.md @@ -0,0 +1,36 @@ +importance: 5 + +--- + +# Сортировать по полю + +У нас есть массив объектов, который нужно отсортировать: + +```js +let users = [ + { name: "Иван", age: 20, surname: "Иванов" }, + { name: "Пётр", age: 18, surname: "Петров" }, + { name: "Анна", age: 19, surname: "Каренина" } +]; +``` + +Обычный способ был бы таким: + +```js +// по имени (Анна, Иван, Пётр) +users.sort((a, b) => a.name > b.name ? 1 : -1); + +// по возрасту (Пётр, Анна, Иван) +users.sort((a, b) => a.age > b.age ? 1 : -1); +``` + +Можем ли мы сделать его короче, например вот таким? + +```js +users.sort(byField('name')); +users.sort(byField('age')); +``` + +То есть чтобы вместо функции мы просто писали `byField(fieldName)`. + +Напишите функцию `byField`, которая может быть использована для этого. diff --git a/1-js/06-advanced-functions/03-closure/article.md b/1-js/06-advanced-functions/03-closure/article.md index 89f3b617fc..0ba1d8fb1c 100644 --- a/1-js/06-advanced-functions/03-closure/article.md +++ b/1-js/06-advanced-functions/03-closure/article.md @@ -1,7 +1,7 @@ -# Замыкание +# Область видимости переменных, замыкание -JavaScript - язык с сильным функционально-ориентированным уклоном. Это даёт нам много свободы. Функция может быть динамически создана, скопирована в другую переменную или передана как аргумент другой функции и позже вызвана из совершенно другого места. +JavaScript - язык с сильным функционально-ориентированным уклоном. Он даёт нам много свободы. Функция может быть динамически создана, скопирована в другую переменную или передана как аргумент другой функции и позже вызвана из совершенно другого места. Мы знаем, что функция может получить доступ к переменным из внешнего окружения, эта возможность используется очень часто. @@ -11,194 +11,90 @@ JavaScript - язык с сильным функционально-ориент Разные языки ведут себя по-разному в таких случаях, и в этой главе мы рассмотрим поведение JavaScript. -## Пара вопросов +```smart header="Мы будем говорить о переменных `let/const` здесь" +В JavaScript существует три способа объявить переменную: `let`, `const` (современные), и `var` (пережиток прошлого). -Для начала давайте рассмотрим две ситуации, а затем изучим внутренние механизмы шаг за шагом, чтобы вы смогли ответить на эти и более сложные вопросы в будущем. - -1. Функция `sayHi` использует внешнюю переменную `name`. Какое значение будет использовать функция при выполнении? - - ```js - let name = "John"; - - function sayHi() { - alert("Hi, " + name); - } - - name = "Pete"; - - *!* - sayHi(); // что будет показано: "John" или "Pete"? - */!* - ``` - - Такие ситуации распространены и в браузерной и в серверной разработке. Выполнение функции может быть запланировано позже, чем она была создана, например, после какого-нибудь пользовательского действия или сетевого запроса. - - Итак, вопрос в том, получит ли она доступ к последним изменениям? - - -2. Функция `makeWorker` создаёт другую функцию и возвращает её. Новая функция может быть вызвана откуда-то ещё. Получит ли она доступ к внешним переменным из места своего создания или места выполнения или из обоих? - - ```js - function makeWorker() { - let name = "Pete"; - - return function() { - alert(name); - }; - } - - let name = "John"; - - // create a function - let work = makeWorker(); - - // call it - *!* - work(); // что будет показано? "Pete" (переменная из места создания) или "John" (переменная из места выполнения) - */!* - ``` - - -## Лексическое Окружение - -Чтобы понять, что происходит, давайте для начала обсудим, что такое "переменная" на самом деле. - -В JavaScript у каждой выполняемой функции, блока кода и скрипта есть связанный с ними внутренний (скрытый) объект, называемый *лексическим окружением*. - -Объект лексического окружения состоит из двух частей: - -1. *Environment Record* -- объект, в котором как свойства хранятся все локальные переменные (а также некоторая другая информация, такая как значение `this`). - -2. Ссылка на *внешнее лексическое окружение* - то есть то, которое соответствует коду снаружи (снаружи от текущих фигурных скобок). - -**Итак, "переменная" -- это просто свойство специального внутреннего объекта: Environment Record. "Получить или изменить переменную", означает, "получить или изменить свойство этого объекта".** - -Например, в этом простом коде только одно лексическое окружение: - -![лексическое окружение](lexical-environment-global.png) - -Это, так называемое, глобальное лексическое окружение, связанное со всем скриптом. - -На картинке выше прямоугольник означает Environment Record (хранилище переменных), а стрелка означает ссылку на внешнее окружение. У глобального лексического окружения нет внешнего окружения, так что она указывает на `null`. - -Здесь показана большая картинка с тем, как работают `let` переменные: - -![лексическое окружение](lexical-environment-global-2.png) - -Прямоугольники с правой стороны демонстрируют, как глобальное лексическое окружение изменяется в процессе выполнения кода: - -1. В начале скрипта лексическое окружение пустое. -2. Появляется определение переменной `let phrase`. У неё нет присвоенного значения, поэтому присваивается `undefined`. -3. Переменной `phrase` присваивается значение. -4. Переменная `phrase` меняет значение. - -Пока что всё выглядит просто, правда? - -Итого: - -- Переменная -- это свойство специального внутреннего объекта, связанного с текущим выполняющимся блоком/функцией/скриптом. -- Работа с переменными -- это на самом деле работа со свойствами этого объекта. - -### Function Declaration - -До сих поры мы рассматривали только переменные. Теперь рассмотрим Function Declaration. - -**В отличие от переменных, объявленных с помощью `let`, они полностью инициализируются не тогда, когда выполнение доходит до них, а раньше, когда создаётся лексическое окружение.** - -Для верхнеуровневых функций это означает момент, когда скрипт начинает выполнение. - -Вот почему мы можем вызвать функцию, объявленную через Function Declaration, до того, как она определена. - -Следующий код демонстрирует, что уже с самого начала в лексическом окружении что-то есть. Там есть `say`, потому что это Function Declaration. И позже там появится `phrase`, объявленное через `let`: - -![лексическое окружение](lexical-environment-global-3.png) - - -### Внутреннее и внешнее лексическое окружение - -Теперь давайте продолжим и посмотрим, что происходит, когда функция получает доступ к внешней переменной. - -В течение вызова `say()` использует внешнюю переменную `phrase`. Давайте разберемся подробно, что происходит. - -После запуска функции первым делом для неё автоматически создаётся лексическое окружение. Это общее правило для всех функций. Это лексическое окружение используется для хранения локальных переменных и параметров вызова. - -Например, для `say("John")` это выглядит так (интерпретатор находится на строке, отмеченной стрелкой): - - - -![лексическое окружение](lexical-environment-simple.png) +- В этой статье мы будем использовать переменные `let` в примерах. +- Переменные, объявленные с помощью `const`, ведут себя так же, так что эта статья и о них. +- Старые переменные `var` имеют несколько характерных отличий, они будут рассмотрены в главе . +``` -Итак, в процессе вызова функции у нас есть два лексических окружения: внутреннее (для вызываемой функции) и внешнее (глобальное): +## Блоки кода -- внутреннее лексическое окружение соответствует текущему выполнению `say`. +Если переменная объявлена внутри блока кода `{...}`, то она видна только внутри этого блока. - В нём находится одна переменная `name`, аргумент функции. Мы вызываем `say("John")`, так что значение переменной `name` равно `"John"`. -- Внешнее лексическое окружение -- это глобальное лексическое окружение. +Например: - В нём находятся `phrase` и сама функция. +```js run +{ + // выполняем некоторые действия с локальной переменной, которые не должны быть видны снаружи -У внутреннего лексического окружения есть ссылка на внешнее. + let message = "Hello"; // переменная видна только в этом блоке -**Когда код хочет получить доступ к переменной -- сначала происходит поиск во внутреннем лексическом окружении, затем во внешнем, затем в следующем и так далее, до конца цепочки.** + alert(message); // Hello +} -Если переменная не была найдена, это будет ошибкой в `strict mode`. Без `strict mode`, для обратной совместимости, присваивание несуществующей переменной создает новую глобальную переменную с таким именем. +alert(message); // ReferenceError: message is not defined +``` -Давайте посмотрим, как происходит поиск в нашем примере: +С помощью блоков `{...}` мы можем изолировать часть кода, выполняющую свою собственную задачу, с переменными, принадлежащими только ей: -- Когда `alert` внутри `say` хочет получить доступ к `name`, он немедленно находит переменную в лексическом окружении функции. -- Когда он хочет получить доступ к `phrase`, которой нет локально, он следует дальше по ссылке к внешнему лексическому окружению и находит переменную там. +```js run +{ + // показать сообщение + let message = "Hello"; + alert(message); +} -![поиск лексического окружения](lexical-environment-simple-lookup.png) +{ + // показать другое сообщение + let message = "Goodbye"; + alert(message); +} +``` -Теперь у нас есть ответ на первый вопрос из начала главы. +````smart header="Без блоков была бы ошибка" +Обратите внимание, что без отдельных блоков возникнет ошибка, если мы используем let с существующим именем переменной: -**Функция получает текущее значение внешних переменных, то есть, их последнее значение** +```js run +// показать сообщение +let message = "Hello"; +alert(message); -Всё, как в описанном механизме. Старые значения переменных нигде не сохраняются. Когда функция хочет получить доступ к переменным, она берёт их значения из своего или внешнего лексического окружения. +// показать другое сообщение +let message = "Goodbye"; // SyntaxError: Identifier 'message' has already been declared +alert(message); +``` +```` -Так что, ответ на первый вопрос: `Pete`: +Для `if`, `for`, `while` и т.д. переменные, объявленные в блоке кода `{...}`, также видны только внутри: ```js run -let name = "John"; +if (true) { + let phrase = "Hello"; -function sayHi() { - alert("Hi, " + name); + alert(phrase); // Hello } -name = "Pete"; // (*) - -*!* -sayHi(); // Pete -*/!* +alert(phrase); // Ошибка, нет такой переменной! ``` +В этом случае после завершения работы `if` нижний `alert` не увидит `phrase`, что и приведет к ошибке. -Порядок выполнения кода, приведённого выше: +И это замечательно, поскольку это позволяет нам создавать блочно-локальные переменные, относящиеся только к ветви `if`. -1. В глобальном лексическом окружении есть `name: "John"`. -2. На строке `(*)` глобальная переменная изменяется, теперь `name: "Pete"`. -3. Момент, когда выполняется функция `sayHi()` и берёт переменную `name` извне. Теперь из глобального лексического окружения, где переменная уже равна `"Pete"`. +То же самое можно сказать и про циклы `for` и `while`: +```js run +for (let i = 0; i < 3; i++) { + // переменная i видна только внутри for + alert(i); // 0, потом 1, потом 2 +} -```smart header="Один вызов -- одно лексическое окружение" -Пожалуйста, обратите внимание, что новое лексическое окружение функции создается каждый раз, когда функция выполняется. - -И, если функция вызывается несколько раз, то для каждого вызова будет своё лексическое окружение, со своими, специфичными для этого вызова, локальными переменными и параметрами. -``` - -```smart header="Лексическое окружение -- это специальный внутренний объект" -"Лексическое окружение" -- это специальный внутренний объект. Мы не можем получить его в нашем коде и изменять напрямую. Сам движок JavaScript может оптимизировать его, уничтожать неиспользуемые переменные для освобождения памяти и выполнять другие внутренние уловки, но видимое поведение объекта должно оставаться таким, как было описано. +alert(i); // Ошибка, нет такой переменной! ``` +Визуально `let i = 0;` находится вне блока кода `{...}`, однако здесь в случае с `for` есть особенность: переменная, объявленная внутри `(...)`, считается частью блока. ## Вложенные функции @@ -226,23 +122,7 @@ function sayHiBye(firstName, lastName) { Что ещё интереснее, вложенная функция может быть возвращена: либо в качестве свойства нового объекта (если внешняя функция создаёт объект с методами), либо сама по себе. И затем может быть использована в любом месте. Не важно где, она всё так же будет иметь доступ к тем же внешним переменным. -Например, здесь, вложенная функция присваивается новому объекту в [конструкторе](info:constructor-new): - -```js run -// функция-конструктор возвращает новый объект -function User(name) { - - // методом объекта становится вложенная функция - this.sayHi = function() { - alert(name); - }; -} - -let user = new User("John"); -user.sayHi(); // у кода метода "sayHi" есть доступ к внешней переменной "name" -``` - -А здесь мы просто создаём и возвращаем функцию "счётчик": +Ниже, `makeCounter` создает функцию «счётчик», которая при каждом вызове возвращает следующее число: ```js run function makeCounter() { @@ -260,315 +140,191 @@ alert( counter() ); // 1 alert( counter() ); // 2 ``` -Давайте продолжим с примером `makeCounter`. Он создаёт функцию "counter", которая возвращает следующее число при каждом вызове. Несмотря на простоту, немного модифицированные варианты этого кода применяются на практике, например, в [генераторе псевдослучайных чисел](https://ru.wikipedia.org/wiki/Генератор_псевдослучайных_чисел) и во многих других случаях. - -Как же это работает изнутри? - -Когда внутренняя функция начинает выполняться, начинается поиск переменной `count++` изнутри-наружу. Для примера выше порядок будет такой: - -![](lexical-search-order.png) - -1. Локальные переменные вложенной функции... -2. Переменные внешней функции... -3. И так далее, пока не будут достигнуты глобальные переменные. - -В этом примере `count` будет найден на шаге `2`. Когда внешняя переменная модифицируется, она изменится там, где была найдена. Значит, `count++` найдёт внешнюю переменную и увеличит её значение в лексическом окружении, которому она принадлежит. Как если бы у нас было `let count = 1`. - -Теперь рассмотрим два вопроса: - -1. Можем ли мы каким-нибудь образом сбросить счётчик `count` из кода, который не принадлежит `makeCounter`? Например, после вызова `alert` в коде выше. -2. Если мы вызываем `makeCounter` несколько раз -- нам возвращается много функций `counter`. Они независимы или разделяют одну и ту же переменную `count`? - -Попробуйте ответить на эти вопросы перед тем, как продолжить чтение. +Несмотря на простоту этого примера, немного модифицированные его варианты применяются на практике, например, в [генераторе псевдослучайных чисел](https://ru.wikipedia.org/wiki/Генератор_псевдослучайных_чисел) и во многих других случаях. -... +Как это работает? Если мы создадим несколько таких счётчиков, будут ли они независимыми друг от друга? Что происходит с переменными? -Готовы? +Понимание таких вещей полезно для повышения общего уровня владения JavaScript и для более сложных сценариев. Так что давайте немного углубимся. -Хорошо, давайте ответим на вопросы. +## Лексическое окружение -1. Такой возможности нет: `count` -- локальная переменная функции, мы не можем получить к ней доступ извне. -2. Для каждого вызова `makeCounter()` создаётся новое лексическое окружение функции, со своим собственным `count`. Так что, получившиеся функции `counter` -- независимы. +```warn header="Здесь водятся драконы!" +Глубокое техническое описание - впереди. -Вот демо: - -```js run -function makeCounter() { - let count = 0; - return function() { - return count++; - }; -} - -let counter1 = makeCounter(); -let counter2 = makeCounter(); - -alert( counter1() ); // 0 -alert( counter1() ); // 1 - -alert( counter2() ); // 0 (независимо) +Как бы мне ни хотелось избежать низкоуровневых деталей языка, любое представление о JavaScript без них будет недостаточным и неполным, так что приготовьтесь. ``` -Надеюсь, ситуация с внешними переменными теперь ясна. Для большинства ситуаций такого понимания вполне достаточно, но в спецификации есть ряд деталей, которые мы, для простоты, опустили. Далее мы разберем происходящее ещё более подробно, чтобы уж точно ничего не упустить. - -## Окружение в деталях - -Вот что происходит в примере с `makeCounter` шаг за шагом. Пройдите их, чтобы убедиться, что вы разобрались с каждой деталью. - -Пожалуйста, обратите внимание на дополнительное свойство `[[Environment]]`, про которое здесь рассказано. Мы не упоминали о нём раньше для простоты. - -1. Когда скрипт только начинает выполняться, есть только глобальное лексическое окружение: - - ![](lexenv-nested-makecounter-1.png) - - В этот начальный момент есть только функция `makeCounter`, потому что это Function Declaration. Она ещё не выполняется. - - **Все функции "при рождении" получают скрытое свойство `[[Environment]]`, которое ссылается на лексическое окружение места, где они были созданы.** Мы ещё не говорили об этом, но это то, каким образом функции знают, где они были созданы. - - В данном случае, `makeCounter` создан в глобальном лексическом окружении, так что `[[Environment]]` содержит ссылку на него. - - Другими словами, функция навсегда запоминает ссылку на лексическое окружение, где она была создана. И `[[Environment]]` -- скрытое свойство функции, которое содержит эту ссылку. - -2. Код продолжает выполняться, объявляется новая глобальная переменная `counter`, которой присваивается вызов `makeCounter`. Вот снимок момента, когда интерпретатор находится на первой строке внутри `makeCounter()`: - - ![](lexenv-nested-makecounter-2.png) - - В момент вызова `makeCounter()` создаётся лексическое окружение, для хранения его переменных и аргументов. - - Как и все лексические окружения, оно содержит две вещи: - 1. Environment Record с локальными переменными. В нашем случае `count` -- единственная локальная переменная (появляющаяся, когда выполняется строчка с `let count`). - 2. Ссылка на внешнее окружение, которая устанавливается в `[[Environment]]` функции. В данном случае, `[[Environment]]` функции `makeCounter` ссылается на глобальное лексическое окружение. - - Итак, теперь у нас есть два лексических окружения: первое -- глобальное, второе -- для текущего вызова `makeCounter`, с внешней ссылкой на глобальный объект. -3. В процессе выполнения `makeCounter()` создаётся небольшая вложенная функция. +Для большей наглядности объяснение разбито на несколько шагов. - Не имеет значения, какой способ объявления функции используется: Function Declaration или Function Expression. Все функции получают свойство `[[Environment]]`, которое ссылается на лексическое окружение, в которым они были созданы. Тоже происходит и с нашей новой маленькой функцией. +### Шаг 1. Переменные - Для нашей новой вложенной функции значением `[[Environment]]` будет текущее лексическое окружение `makeCounter()` (где она была создана): +В JavaScript у каждой выполняемой функции, блока кода `{...}` и скрипта есть связанный с ними внутренний (скрытый) объект, называемый *лексическим окружением* `LexicalEnvironment`. - ![](lexenv-nested-makecounter-3.png) - - Пожалуйста, обратите внимание, что на этом шаге внутренняя функция была создана, но ещё не вызвана. Код внутри `function() { return count++ }` не выполняется. - -4. Выполнение продолжается, вызов `makeCounter()` завершается, и результат (небольшая вложенная функция) присваивается глобальной переменной `counter`: - - ![](lexenv-nested-makecounter-4.png) - - В этой функции есть только одна строчка: `return count++`, которая будет выполнена, когда мы вызовем функцию. - -6. При вызове `counter()` для него создаётся одно "пустое" лексическое окружение. В нём самом локальных переменных нет. Но `[[Environment]]` `counter` используется, как ссылка на внешнее лексическое окружение, которое даёт доступ к переменным предшествующего вызова `makeCounter`, где `counter` был создан. - - ![](lexenv-nested-makecounter-5.png) - - Теперь, если он пытается получить доступ к переменной, она сначала ищется в собственном лексическом окружении (пустое), а затем в лексическом окружении предшествующего вызова `makeCounter()`, после чего в глобальном окружении. +Объект лексического окружения состоит из двух частей: - Когда `counter()` ищет переменную `count`, она находится среди переменных `makeCounter`, в ближайшем внешнем лексическом окружении. +1. *Environment Record* -- объект, в котором как свойства хранятся все локальные переменные (а также некоторая другая информация, такая как значение `this`). - Пожалуйста, обратите внимание, как здесь работает управление памятью. Хотя `makeCounter()` закончил выполнение некоторое время назад, его лексическое окружение остается в памяти, потому что есть вложенная функция с `[[Environment]]`, который ссылается на него. +2. Ссылка на *внешнее лексическое окружение* - то есть то, которое соответствует коду снаружи (снаружи от текущих фигурных скобок). - В большинстве случаев, объект лексического окружения существует до того момента, пока есть функция, которая может его использовать. И только тогда, когда таких не остаётся, окружение уничтожается. +**"Переменная" -- это просто свойство специального внутреннего объекта: `Environment Record`. "Получить или изменить переменную", означает, "получить или изменить свойство этого объекта".** -6. Вызов `counter()` не только возвращает значение `count`, но также увеличивает его. Обратите внимание, что модификация происходит "на месте". Значение `count` изменяется конкретно в том окружении, где оно было найдено. +Например, в этом простом коде только одно лексическое окружение: - ![](lexenv-nested-makecounter-6.png) +![лексическое окружение](lexical-environment-global.svg) - Итак, мы вернулись к предыдущему шагу с одним только изменением -- с новым значением `count`. Следующие вызовы будут выполняться по той же схеме. +Это, так называемое, глобальное лексическое окружение, связанное со всем скриптом. -7. Следующие вызовы `counter()` сделают то же самое. +На картинке выше прямоугольник означает Environment Record (хранилище переменных), а стрелка означает ссылку на внешнее окружение. У глобального лексического окружения нет внешнего окружения, так что она указывает на `null`. -Теперь ответ на второй вопрос из начала главы должен быть очевиден. +По мере выполнения кода лексическое окружение меняется. -Функция `work()` в коде ниже использует `name` из исходного местоположения переменной с помощью ссылки на внешнее лексическое окружение: +Вот более длинный код: -![](lexenv-nested-work.png) +![лексическое окружение](closure-variable-phrase.svg) -Так что, результатом будет `"Pete"`. +Прямоугольники с правой стороны демонстрируют, как глобальное лексическое окружение изменяется в процессе выполнения кода: -Но, если бы в `makeWorker()` не было `let name`, тогда бы поиск продолжился дальше и была бы взята глобальная переменная, как мы видим из приведенной выше цепочки. В таком случае, результатом было бы `"John"`. +1. При запуске скрипта лексическое окружение предварительно заполняется всеми объявленными переменными. + - Изначально они находятся в состоянии "Uninitialized". Это особое внутреннее состояние, которое означает, что движок знает о переменной, но на нее нельзя ссылаться, пока она не будет объявлена с помощью `let`. Это почти то же самое, как если бы переменная не существовала. +2. Появляется определение переменной `let phrase`. У неё ещё нет присвоенного значения, поэтому присваивается `undefined`. С этого момента мы можем использовать переменную. +3. Переменной `phrase` присваивается значение. +4. Переменная `phrase` меняет значение. -```smart header="Замыкания" -В программировании есть общий термин: "замыкание", -- которое должен знать каждый разработчик. +Пока что всё выглядит просто, правда? -[Замыкание](https://ru.wikipedia.org/wiki/Замыкание_(программирование)) -- это функция, которая запоминает свои внешние переменные и может получить к ним доступ. В некоторых языках это невозможно, или функция должна быть написана специальным образом, чтобы получилось замыкание. Но, как было описано выше, в JavaScript, все функции изначально являются замыканиями (есть только одно исключение, про которое будет рассказано в ). +- Переменная -- это свойство специального внутреннего объекта, связанного с текущим выполняющимся блоком/функцией/скриптом. +- Работа с переменными -- это на самом деле работа со свойствами этого объекта. -То есть, они автоматически запоминают, где были созданы, с помощью скрытого свойства `[[Environment]]` и все они могут получить доступ к внешним переменным. +```smart header="Лексическое окружение - объект спецификации" +"Лексическое окружение" - это объект спецификации: он существует только "теоретически" в спецификации языка для описания того, как все работает. Мы не можем получить этот объект в нашем коде и манипулировать им напрямую. -Когда на собеседовании фронтенд-разработчик получает вопрос: "что такое замыкание?", -- правильным ответом будет определение замыкания и объяснения того факта, что все функции в JavaScript являются замыканиями, и, может быть, несколько слов о технических деталях: свойстве `[[Environment]]` и о том, как работает лексическое окружение. +JavaScript-движки также могут оптимизировать его, отбрасывать неиспользуемые переменные для экономии памяти и выполнять другие внутренние действия, но при этом видимое поведение остается таким, как описано. ``` -## Блоки кода и циклы, IIFE - -Предыдущие примеры сосредоточены на функциях. Но лексическое окружение существует для любых блоков кода `{...}`. +### Шаг 2. Function Declaration -Лексическое окружение создаётся при выполнении блока кода и содержит локальные переменные для этого блока. Вот пара примеров. +Функция - это тоже значение, как и переменная. -### If +**Разница заключается в том, что Function Declaration мгновенно инициализируется полностью.** -В следующем примере переменная `user` существует только в блоке `if`: +Когда создается лексическое окружение, Function Declaration сразу же становится функцией, готовой к использованию (в отличие от `let`, который до момента объявления не может быть использован). - +Конечно, такое поведение касается только Function Declaration, а не Function Expression, в которых мы присваиваем функцию переменной, например, `let say = function(name) {...}`. -![](lexenv-if.png) +### Шаг 3. Внутреннее и внешнее лексическое окружение -Когда выполнение попадает в блок `if`, для этого блока создаётся новое лексическое окружение. +Когда запускается функция, в начале ее вызова автоматически создается новое лексическое окружение для хранения локальных переменных и параметров вызова. -У него есть ссылка на внешнее окружение, так что `phrase` может быть найдена. Но все переменные и Function Expression, объявленные внутри `if`, остаются в его лексическом окружении и не видны снаружи. +Например, для `say("John")` это выглядит так (выполнение находится на строке, отмеченной стрелкой): -Например, после завершения `if` следующий `alert` не увидит `user`, что вызовет ошибку. +![лексическое окружение](lexical-environment-simple.svg) -### For, while +В процессе вызова функции у нас есть два лексических окружения: внутреннее (для вызываемой функции) и внешнее (глобальное): -Для цикла у каждой итерации своё отдельное лексическое окружение. Если переменная объявлена в `for`, то она тоже локальная для этого лексического окружения: +- Внутреннее лексическое окружение соответствует текущему выполнению `say`. -```js run -for (let i = 0; i < 10; i++) { - // У каждого цикла своё собственное лексическое окружение - // {i: value} -} + В нём находится одна переменная `name`, параметр функции. Мы вызываем `say("John")`, так что значение переменной `name` равно `"John"`. +- Внешнее лексическое окружение -- это глобальное лексическое окружение. -alert(i); // Ошибка, нет такой переменной -``` + В нём находятся переменная `phrase` и сама функция. -Обратите внимание: `let i` визуально находится снаружи `{...}`. Но конструкция `for` -- это нечто специальное: у каждой итерации цикла своё собственное лексическое окружение с текущим `i` в нём. +У внутреннего лексического окружения есть ссылка на внешнее `outer`. -И так же, как и в `if`, ниже цикла `i` невидима. +**Когда код хочет получить доступ к переменной – сначала происходит поиск во внутреннем лексическом окружении, затем во внешнем, затем в следующем и так далее, до глобального.** -### Блоки кода +Если переменная не была найдена, это будет ошибкой в строгом режиме (`use strict`). Без строгого режима, для обратной совместимости, присваивание несуществующей переменной создаёт новую глобальную переменную с таким же именем. -Мы также можем использовать "голые" блоки кода `{...}`, чтобы изолировать переменные в "локальной области видимости". +Давайте посмотрим, как происходит поиск в нашем примере: -Например, в браузере все скрипты (кроме `type="module"`) разделяют одну общую глобальную область. Так что, если мы создадим глобальную переменную в одном скрипте, она станет доступна и в других. Но это становится источником конфликтов, если два скрипта используют одно и тоже имя переменной и перезаписывают друга друга. +- Для переменной `name`, `alert` внутри `say` сразу же находит ее во внутреннем лексическом окружении. +- Когда `alert` хочет получить доступ к `phrase`, он не находит её локально, поэтому вынужден обратиться к внешнему лексическому окружению и находит `phrase` там. -Это может произойти, если название переменной -- широко распространённое слово, а авторы скрипта не знают друг о друге. +![поиск лексического окружения](lexical-environment-simple-lookup.svg) -Если мы хотим этого избежать, мы можем использовать блок кода для изоляции всего скрипта или какой-то его части: +### Шаг 4. Возврат функции -```js run -{ - // сделать какую-нибудь работу с локальными переменными, которые не должны быть видны снаружи +Давайте вернёмся к примеру с `makeCounter`: - let message = "Hello"; +```js +function makeCounter() { + let count = 0; - alert(message); // Hello + return function() { + return count++; + }; } -alert(message); // Ошибка: переменная message не определена +let counter = makeCounter(); ``` -Из-за того, что у блока есть собственное лексическое окружение, код снаружи него (или внутри другого скрипта) не видит переменные этого блока. +В начале каждого вызова `makeCounter()` создается новый объект лексического окружения, в котором хранятся переменные для конкретного запуска `makeCounter`. -### IIFE +Таким образом, мы имеем два вложенных лексических окружения, как в примере выше: -В прошлом в JavaScript не было лексического окружения на уровне блоков кода. +![](lexenv-nested-makecounter-2.svg) -Так что программистам пришлось что-то придумать. И то, что они сделали, называется "immediately-invoked function expressions", то есть, функция, которая выполняется сразу же после того, как была определена (аббревиатура IIFE). +Отличие заключается в том, что во время выполнения `makeCounter()` создается крошечная вложенная функция, состоящая всего из одной строки: `return count++`. Мы ее еще не запускаем, а только создаем. -Это не то, что мы должны использовать сегодня, но, так как вы можете встретить это в старых скриптах, лучше понимать принцип работы. +Все функции помнят лексическое окружение, в котором они были созданы. Технически здесь нет никакой магии: все функции имеют скрытое свойство `[[Environment]]`, которое хранит ссылку на лексическое окружение, в котором была создана функция: -IIFE выглядит так: - -```js run -(function() { - - let message = "Hello"; - - alert(message); // Hello - -})(); -``` - -Здесь создаётся и немедленно вызывается Function Expression. Так что код выполняется сразу же и у него есть свои локальные переменные. - -Function Expression обёрнуто в скобки `(function {...})`, потому что, когда JavaScript встречает `"function"` в основном потоке кода, он воспринимает это как начало Function Declaration. Но у Function Declaration должно быть имя, так что такой код вызовет ошибку: - -```js run -// Попробуйте объявить и сразу же вызвать функцию -function() { // <-- Error: Unexpected token ( +![](lexenv-nested-makecounter-3.svg) - let message = "Hello"; +Таким образом, `counter.[[Environment]]` имеет ссылку на `{count: 0}` лексического окружения. Так функция запоминает, где она была создана, независимо от того, где она вызывается. Ссылка на `[[Environment]]` устанавливается один раз и навсегда при создании функции. - alert(message); // Hello +Впоследствии, при вызове `counter()`, для этого вызова создается новое лексическое окружение, а его внешняя ссылка на лексическое окружение берется из `counter.[[Environment]]`: -}(); -``` +![](closure-makecounter-nested-call.svg) -Даже если мы скажем: "хорошо, давайте добавим имя", -- это не сработает, потому что JavaScript не позволяет вызывать Function Declaration немедленно. +Теперь, когда код внутри `counter()` ищет переменную `count`, он сначала ищет ее в собственном лексическом окружении (пустом, так как там нет локальных переменных), а затем в лексическом окружении внешнего вызова `makeCounter()`, где находит `count` и изменяет ее. -```js run -// ошибка синтаксиса из-за скобок ниже -function go() { +**Переменная обновляется в том лексическом окружении, в котором она существует.** -}(); // <-- не можете вызывать Function Declaration немедленно -``` -Так что, скобки вокруг функции -- это трюк, который позволяет показать JavaScript, что функция была создана в контексте другого выражения, и, таким образом, это функциональное выражение: ей не нужно имя и её можно вызвать немедленно. +Вот состояние после выполнения: -Кроме скобок, существуют и другие пути показать JavaScript, что мы имеем в виду Function Expression: +![](closure-makecounter-nested-call-2.svg) -```js run -// Пути создания IIFE +Если мы вызовем `counter()` несколько раз, то в одном и том же месте переменная `count` будет увеличена до `2`, `3` и т.д. -(function() { - alert("Скобки вокруг функции"); -}*!*)*/!*(); +```smart header="Замыкания" +В программировании есть общий термин: «замыкание», – который должен знать каждый разработчик. -(function() { - alert("Скобки вокруг всего"); -}()*!*)*/!*; +[Замыкание](https://ru.wikipedia.org/wiki/Замыкание_(программирование)) -- это функция, которая запоминает свои внешние переменные и может получить к ним доступ. В некоторых языках это невозможно, или функция должна быть написана специальным образом, чтобы получилось замыкание. Но, как было описано выше, в JavaScript, все функции изначально являются замыканиями (есть только одно исключение, про которое будет рассказано в ). -*!*!*/!*function() { - alert("Выражение начинается с побитового оператора NOT"); -}(); +То есть они автоматически запоминают, где были созданы, с помощью скрытого свойства `[[Environment]]`, и все они могут получить доступ к внешним переменным. -*!*+*/!*function() { - alert("Выражение начинается с унарного плюса"); -}(); +Когда на собеседовании фронтенд-разработчику задают вопрос: «что такое замыкание?», – правильным ответом будет определение замыкания и объяснения того факта, что все функции в JavaScript являются замыканиями, и, может быть, несколько слов о технических деталях: свойстве `[[Environment]]` и о том, как работает лексическое окружение. ``` -Во всех перечисленных случаях мы объявляем Function Expression и немедленно выполняем его. - ## Сборка мусора -Обычно лексическое окружение очищается и удаляется после того, как функция выполнилась. Например: +Обычно лексическое окружение удаляется из памяти вместе со всеми переменными после завершения вызова функции. Это связано с тем, что на него нет ссылок. Как и любой объект JavaScript, оно хранится в памяти только до тех пор, пока к нему можно обратиться. -```js -function f() { - let value1 = 123; - let value2 = 456; -} +Однако если существует вложенная функция, которая все еще доступна после завершения функции, то она имеет свойство `[[Environment]]`, ссылающееся на лексическое окружение. -f(); -``` +В этом случае лексическое окружение остается доступным даже после завершения работы функции. -Здесь два значения, которые технически являются свойствами лексического окружения. Но после того, как `f()` завершится, это лексическое окружение станет недоступно, поэтому оно удалится из памяти. - -...Но, если есть вложенная функция, которая всё ещё доступна после выполнения `f`, тогда eё ссылка в `[[Environment]]` будет продолжать держать внешнее лексическое окружение "живым": +Например: ```js function f() { let value = 123; - function g() { alert(value); } - -*!* - return g; -*/!* + return function() { + alert(value); + } } -let g = f(); // g доступно и продолжает держать внешнее лексическое окружение в памяти +let g = f(); // g.[[Environment]] хранит ссылку на лексическое окружение +// из соответствующего вызова f() ``` -Обратите внимание, если `f()` вызывается несколько раз и возвращаемые функции сохраняются, тогда соответствующие объекты лексического окружения продолжат держаться в памяти. Вот три такие функции в коде ниже: +Обратите внимание, что если `f()` вызывается много раз и результирующие функции сохраняются, то все соответствующие объекты лексического окружения также будут сохранены в памяти. В приведенном ниже коде -- все три: ```js function f() { @@ -577,38 +333,36 @@ function f() { return function() { alert(value); }; } -// три функции в массиве, каждая из них ссылается на лексическое окружение (коротко "ЛО") +// 3 функции в массиве, каждая из которых ссылается на лексическое окружение // из соответствующего вызова f() -// ЛО ЛО ЛО let arr = [f(), f(), f()]; ``` -Объект лексического окружения умирает, когда становится недоступным (как и любой другой объект). Другими словами, он существует только до того момента, пока есть хотя бы одна вложенная функция, которая ссылается на него. +Объект лексического окружения исчезает, когда становится недоступным (как и любой другой объект). Другими словами, он существует только до тех пор, пока на него ссылается хотя бы одна вложенная функция. -В следующем коде, после того как `g` станет недоступным, лексическое окружение функции (и, соответственно, `value`) будет удалено из памяти; +В приведенном ниже коде после удаления вложенной функции ее окружающее лексическое окружение (а значит, и `value`) очищается из памяти: ```js function f() { let value = 123; - function g() { alert(value); } - - return g; + return function() { + alert(value); + } } -let g = f(); // while g is alive -// соответствующее лексическое окружение существует +let g = f(); // пока существует функция g, value остается в памяти -g = null; // ...а теперь память очищается +g = null; // ...и теперь память очищена. ``` ### Оптимизация на практике Как мы видели, в теории, пока функция жива, все внешние переменные тоже сохраняются. -Но на практике движки JavaScript пытаются это оптимизировать. Они анализирует использование переменных и, если не сложно понять, что внешняя переменная не используется -- она удаляется. +Но на практике движки JavaScript пытаются это оптимизировать. Они анализируют использование переменных и, если легко по коду понять, что внешняя переменная не используется – она удаляется. -**Одним из важных побочных эффектов в V8 (Chrome, Opera) является то, что такая переменная становится недоступной при отладке.** +**Одним из важных побочных эффектов в V8 (Chrome, Edge, Opera) является то, что такая переменная становится недоступной при отладке.** Попробуйте запустить следующий пример в Chrome с открытой Developer Tools. @@ -619,7 +373,7 @@ function f() { let value = Math.random(); function g() { - debugger; // в консоли: напишите alert( value ); Такой переменной нет! + debugger; // в консоли: напишите alert(value); Такой переменной нет! } return g; @@ -629,18 +383,18 @@ let g = f(); g(); ``` -Как вы можете видеть -- такой переменной не существует! В теории, она должна быть доступна, но попала под оптимизацию движка. +Как вы можете видеть – такой переменной не существует! В теории, она должна быть доступна, но попала под оптимизацию движка. -Это может приводить к забавным (если удаётся решить быстро) проблемам при отладке. Одна из них -- мы можем увидеть не ту внешнюю переменную при совпадающих названиях: +Это может приводить к забавным (если удаётся решить быстро) проблемам при отладке. Одна из них – мы можем увидеть не ту внешнюю переменную при совпадающих названиях: -```js run global +```js run let value = "Сюрприз!"; function f() { - let value = "the closest value"; + let value = "ближайшее значение"; function g() { - debugger; // в консоли: напишите alert( value ); Сюрприз! + debugger; // в консоли: напишите alert(value); Сюрприз! } return g; @@ -650,9 +404,6 @@ let g = f(); g(); ``` -```warn header="До встречи!" -Эту особенность V8 полезно знать. Если вы занимаетесь отладкой в Chrome/Opera, рано или поздно вы с ней встретитесь. +Эту особенность V8 полезно знать. Если вы занимаетесь отладкой в Chrome/Edge/Opera, рано или поздно вы с ней столкнётесь. -Это не баг в отладчике, а скорее особенность V8. Возможно со временем это изменится. -Вы всегда можете проверить это, запустив пример на этой странице. -``` +Это не баг в отладчике, а скорее особенность V8. Возможно со временем это изменится. Вы всегда можете проверить это, запустив примеры на этой странице. diff --git a/1-js/06-advanced-functions/03-closure/closure-function-declaration.svg b/1-js/06-advanced-functions/03-closure/closure-function-declaration.svg new file mode 100644 index 0000000000..d18b2b71c0 --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/closure-function-declaration.svg @@ -0,0 +1 @@ +outernullначало выполненияphrase: <uninitialized> say: function... diff --git a/1-js/06-advanced-functions/03-closure/closure-makecounter-nested-call-2.svg b/1-js/06-advanced-functions/03-closure/closure-makecounter-nested-call-2.svg new file mode 100644 index 0000000000..e151fb1ba6 --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/closure-makecounter-nested-call-2.svg @@ -0,0 +1 @@ +count: 1<пусто>nullouterouteroutermakeCounter: function counter: functionизменено здесь diff --git a/1-js/06-advanced-functions/03-closure/closure-makecounter-nested-call.svg b/1-js/06-advanced-functions/03-closure/closure-makecounter-nested-call.svg new file mode 100644 index 0000000000..75ff7ae7e1 --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/closure-makecounter-nested-call.svg @@ -0,0 +1 @@ +count: 0<пусто>nullouterouteroutermakeCounter: function counter: function diff --git a/1-js/06-advanced-functions/03-closure/closure-variable-phrase.svg b/1-js/06-advanced-functions/03-closure/closure-variable-phrase.svg new file mode 100644 index 0000000000..58bcb57b8e --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/closure-variable-phrase.svg @@ -0,0 +1 @@ +phrase: "Bye"phrase: "Hello"phrase: undefinedphrase: <uninitialized>outernullначало выполнения diff --git a/1-js/06-advanced-functions/03-closure/lexenv-if.png b/1-js/06-advanced-functions/03-closure/lexenv-if.png deleted file mode 100644 index 5a11b65de9..0000000000 Binary files a/1-js/06-advanced-functions/03-closure/lexenv-if.png and /dev/null differ diff --git a/1-js/06-advanced-functions/03-closure/lexenv-if.svg b/1-js/06-advanced-functions/03-closure/lexenv-if.svg new file mode 100644 index 0000000000..3d4d6d7ccf --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/lexenv-if.svg @@ -0,0 +1 @@ +phrase: "Hello"outerouternulluser: "John" \ No newline at end of file diff --git a/1-js/06-advanced-functions/03-closure/lexenv-if@2x.png b/1-js/06-advanced-functions/03-closure/lexenv-if@2x.png deleted file mode 100644 index 794676cda5..0000000000 Binary files a/1-js/06-advanced-functions/03-closure/lexenv-if@2x.png and /dev/null differ diff --git a/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-1.png b/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-1.png deleted file mode 100644 index c6083a4520..0000000000 Binary files a/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-1.png and /dev/null differ diff --git a/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-1.svg b/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-1.svg new file mode 100644 index 0000000000..f15e77a888 --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-1.svg @@ -0,0 +1 @@ +makeCounter: function[[Environment]]outernull \ No newline at end of file diff --git a/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-1@2x.png b/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-1@2x.png deleted file mode 100644 index af57c51cdb..0000000000 Binary files a/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-1@2x.png and /dev/null differ diff --git a/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-2.png b/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-2.png deleted file mode 100644 index dce67876f2..0000000000 Binary files a/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-2.png and /dev/null differ diff --git a/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-2.svg b/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-2.svg new file mode 100644 index 0000000000..3cd8162cc1 --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-2.svg @@ -0,0 +1 @@ +makeCounter: functioncounter: undefinedcount: 0outerouternullглобальное LexicalEnvironmentLexicalEnvironment вызова makeCounter() \ No newline at end of file diff --git a/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-2@2x.png b/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-2@2x.png deleted file mode 100644 index 9bba88bc53..0000000000 Binary files a/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-2@2x.png and /dev/null differ diff --git a/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-3.png b/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-3.png deleted file mode 100644 index 4d5a787c28..0000000000 Binary files a/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-3.png and /dev/null differ diff --git a/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-3.svg b/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-3.svg new file mode 100644 index 0000000000..54f1d97ba5 --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-3.svg @@ -0,0 +1 @@ +makeCounter: functioncounter: undefinedcount: 0outerouternull[[Environment]] \ No newline at end of file diff --git a/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-3@2x.png b/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-3@2x.png deleted file mode 100644 index c7575f4d52..0000000000 Binary files a/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-3@2x.png and /dev/null differ diff --git a/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-4.png b/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-4.png deleted file mode 100644 index 70b4a09bfd..0000000000 Binary files a/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-4.png and /dev/null differ diff --git a/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-4.svg b/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-4.svg new file mode 100644 index 0000000000..fb60a785fa --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-4.svg @@ -0,0 +1 @@ +makeCounter: functioncounter: functioncount: 0outerouternull[[Environment]] \ No newline at end of file diff --git a/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-4@2x.png b/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-4@2x.png deleted file mode 100644 index 4cc762c10d..0000000000 Binary files a/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-4@2x.png and /dev/null differ diff --git a/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-5.png b/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-5.png deleted file mode 100644 index 7410dddc82..0000000000 Binary files a/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-5.png and /dev/null differ diff --git a/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-5.svg b/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-5.svg new file mode 100644 index 0000000000..79c440da7a --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-5.svg @@ -0,0 +1 @@ +makeCounter: functioncounter: functioncount: 0<empty>outerouterouternull[[Environment]] \ No newline at end of file diff --git a/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-5@2x.png b/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-5@2x.png deleted file mode 100644 index 103c66fb58..0000000000 Binary files a/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-5@2x.png and /dev/null differ diff --git a/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-6.png b/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-6.png deleted file mode 100644 index 44366ed782..0000000000 Binary files a/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-6.png and /dev/null differ diff --git a/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-6.svg b/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-6.svg new file mode 100644 index 0000000000..f5a73aa0cb --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-6.svg @@ -0,0 +1 @@ +makeCounter: functioncounter: functioncount: 1outerouternull[[Environment]]изменено здесь \ No newline at end of file diff --git a/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-6@2x.png b/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-6@2x.png deleted file mode 100644 index 36df7cadd2..0000000000 Binary files a/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-6@2x.png and /dev/null differ diff --git a/1-js/06-advanced-functions/03-closure/lexenv-nested-work.png b/1-js/06-advanced-functions/03-closure/lexenv-nested-work.png deleted file mode 100644 index 3bdf5108de..0000000000 Binary files a/1-js/06-advanced-functions/03-closure/lexenv-nested-work.png and /dev/null differ diff --git a/1-js/06-advanced-functions/03-closure/lexenv-nested-work.svg b/1-js/06-advanced-functions/03-closure/lexenv-nested-work.svg new file mode 100644 index 0000000000..e5b7f83e78 --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/lexenv-nested-work.svg @@ -0,0 +1 @@ +makeWorker: function name: "John"<empty>outerouterouternullname: "Pete" \ No newline at end of file diff --git a/1-js/06-advanced-functions/03-closure/lexenv-nested-work@2x.png b/1-js/06-advanced-functions/03-closure/lexenv-nested-work@2x.png deleted file mode 100644 index 023dba05cd..0000000000 Binary files a/1-js/06-advanced-functions/03-closure/lexenv-nested-work@2x.png and /dev/null differ diff --git a/1-js/06-advanced-functions/03-closure/lexical-environment-global-2.png b/1-js/06-advanced-functions/03-closure/lexical-environment-global-2.png deleted file mode 100644 index 342bdae98c..0000000000 Binary files a/1-js/06-advanced-functions/03-closure/lexical-environment-global-2.png and /dev/null differ diff --git a/1-js/06-advanced-functions/03-closure/lexical-environment-global-2.svg b/1-js/06-advanced-functions/03-closure/lexical-environment-global-2.svg new file mode 100644 index 0000000000..72d8f77f6b --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/lexical-environment-global-2.svg @@ -0,0 +1 @@ +phrase: "Bye"phrase: "Hello"phrase: undefined<empty>outernullначало выполнения \ No newline at end of file diff --git a/1-js/06-advanced-functions/03-closure/lexical-environment-global-2@2x.png b/1-js/06-advanced-functions/03-closure/lexical-environment-global-2@2x.png deleted file mode 100644 index 55306e6663..0000000000 Binary files a/1-js/06-advanced-functions/03-closure/lexical-environment-global-2@2x.png and /dev/null differ diff --git a/1-js/06-advanced-functions/03-closure/lexical-environment-global-3.png b/1-js/06-advanced-functions/03-closure/lexical-environment-global-3.png deleted file mode 100644 index 1cd77f8acd..0000000000 Binary files a/1-js/06-advanced-functions/03-closure/lexical-environment-global-3.png and /dev/null differ diff --git a/1-js/06-advanced-functions/03-closure/lexical-environment-global-3.svg b/1-js/06-advanced-functions/03-closure/lexical-environment-global-3.svg new file mode 100644 index 0000000000..e33d72adad --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/lexical-environment-global-3.svg @@ -0,0 +1 @@ +say: function phrase: "Hello"say: functionouternullначало выполнения \ No newline at end of file diff --git a/1-js/06-advanced-functions/03-closure/lexical-environment-global-3@2x.png b/1-js/06-advanced-functions/03-closure/lexical-environment-global-3@2x.png deleted file mode 100644 index 3e256bbb91..0000000000 Binary files a/1-js/06-advanced-functions/03-closure/lexical-environment-global-3@2x.png and /dev/null differ diff --git a/1-js/06-advanced-functions/03-closure/lexical-environment-global.png b/1-js/06-advanced-functions/03-closure/lexical-environment-global.png deleted file mode 100644 index 6899a777f4..0000000000 Binary files a/1-js/06-advanced-functions/03-closure/lexical-environment-global.png and /dev/null differ diff --git a/1-js/06-advanced-functions/03-closure/lexical-environment-global.svg b/1-js/06-advanced-functions/03-closure/lexical-environment-global.svg new file mode 100644 index 0000000000..9b032bc88b --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/lexical-environment-global.svg @@ -0,0 +1 @@ +phrase: "Hello"outernullЛексическое Окружение \ No newline at end of file diff --git a/1-js/06-advanced-functions/03-closure/lexical-environment-global@2x.png b/1-js/06-advanced-functions/03-closure/lexical-environment-global@2x.png deleted file mode 100644 index 91980cf84f..0000000000 Binary files a/1-js/06-advanced-functions/03-closure/lexical-environment-global@2x.png and /dev/null differ diff --git a/1-js/06-advanced-functions/03-closure/lexical-environment-simple-lookup.png b/1-js/06-advanced-functions/03-closure/lexical-environment-simple-lookup.png deleted file mode 100644 index 56aafaf3d0..0000000000 Binary files a/1-js/06-advanced-functions/03-closure/lexical-environment-simple-lookup.png and /dev/null differ diff --git a/1-js/06-advanced-functions/03-closure/lexical-environment-simple-lookup.svg b/1-js/06-advanced-functions/03-closure/lexical-environment-simple-lookup.svg new file mode 100644 index 0000000000..79501a5b0c --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/lexical-environment-simple-lookup.svg @@ -0,0 +1 @@ +say: function phrase: "Hello"name: "John"outerouternull \ No newline at end of file diff --git a/1-js/06-advanced-functions/03-closure/lexical-environment-simple-lookup@2x.png b/1-js/06-advanced-functions/03-closure/lexical-environment-simple-lookup@2x.png deleted file mode 100644 index f423b125ef..0000000000 Binary files a/1-js/06-advanced-functions/03-closure/lexical-environment-simple-lookup@2x.png and /dev/null differ diff --git a/1-js/06-advanced-functions/03-closure/lexical-environment-simple.png b/1-js/06-advanced-functions/03-closure/lexical-environment-simple.png deleted file mode 100644 index 2424fbf233..0000000000 Binary files a/1-js/06-advanced-functions/03-closure/lexical-environment-simple.png and /dev/null differ diff --git a/1-js/06-advanced-functions/03-closure/lexical-environment-simple.svg b/1-js/06-advanced-functions/03-closure/lexical-environment-simple.svg new file mode 100644 index 0000000000..2778c0919a --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/lexical-environment-simple.svg @@ -0,0 +1 @@ +say: function phrase: "Hello"name: "John"outerouternullЛексическое Окружение вызова \ No newline at end of file diff --git a/1-js/06-advanced-functions/03-closure/lexical-environment-simple@2x.png b/1-js/06-advanced-functions/03-closure/lexical-environment-simple@2x.png deleted file mode 100644 index b206cbe8b9..0000000000 Binary files a/1-js/06-advanced-functions/03-closure/lexical-environment-simple@2x.png and /dev/null differ diff --git a/1-js/06-advanced-functions/03-closure/lexical-search-order.png b/1-js/06-advanced-functions/03-closure/lexical-search-order.png deleted file mode 100644 index 1b641d48dc..0000000000 Binary files a/1-js/06-advanced-functions/03-closure/lexical-search-order.png and /dev/null differ diff --git a/1-js/06-advanced-functions/03-closure/lexical-search-order.svg b/1-js/06-advanced-functions/03-closure/lexical-search-order.svg new file mode 100644 index 0000000000..d9884ec4f1 --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/lexical-search-order.svg @@ -0,0 +1 @@ +123 \ No newline at end of file diff --git a/1-js/06-advanced-functions/03-closure/lexical-search-order@2x.png b/1-js/06-advanced-functions/03-closure/lexical-search-order@2x.png deleted file mode 100644 index f06bc3ed80..0000000000 Binary files a/1-js/06-advanced-functions/03-closure/lexical-search-order@2x.png and /dev/null differ diff --git a/1-js/06-advanced-functions/04-var/article.md b/1-js/06-advanced-functions/04-var/article.md index 0a36666413..b0bf9bbd81 100644 --- a/1-js/06-advanced-functions/04-var/article.md +++ b/1-js/06-advanced-functions/04-var/article.md @@ -1,53 +1,71 @@ -# The old "var" +# Устаревшее ключевое слово "var" -In the very first chapter about [variables](info:variables), we mentioned three ways of variable declaration: +```smart header="Эта статья предназначена для понимания старых скриптов" +Информация, приведенная в этой статье, полезна для понимания старых скриптов. + +Мы не пишем современный код таким образом. +``` + +В самой первой главе про [переменные](info:variables) мы ознакомились с тремя способами объявления переменных: 1. `let` 2. `const` 3. `var` -`let` and `const` behave exactly the same way in terms of Lexical Environments. +`let` и `const` ведут себя одинаково по отношению к лексическому окружению, области видимости. -But `var` is a very different beast, that originates from very old times. It's generally not used in modern scripts, but still lurks in the old ones. +Но `var` - это совершенно другой зверь, берущий своё начало с давних времён. Обычно `var` не используется в современных скриптах, но всё ещё может скрываться в старых. -If you don't plan on meeting such scripts you may even skip this chapter or postpone it, but then there's a chance that it bites you later. +Если в данный момент вы не работаете с подобными скриптами, вы можете пропустить или отложить прочтение данной главы, однако, есть шанс, что вы столкнётесь с `var` в будущем. -From the first sight, `var` behaves similar to `let`. That is, declares a variable: +На первый взгляд, поведение `var` похоже на `let`. Например, объявление переменной: ```js run function sayHi() { - var phrase = "Hello"; // local variable, "var" instead of "let" + var phrase = "Привет"; // локальная переменная, "var" вместо "let" - alert(phrase); // Hello + alert(phrase); // Привет } sayHi(); -alert(phrase); // Error, phrase is not defined +alert(phrase); // Ошибка: phrase не определена ``` -...But here are the differences. +...Однако, отличия всё же есть. -## "var" has no block scope +## Для "var" не существует блочной области видимости -`var` variables are either function-wide or global, they are visible through blocks. +Область видимости переменных `var` ограничивается либо функцией, либо, если переменная глобальная, то скриптом. Такие переменные доступны за пределами блока. -For instance: +Например: -```js +```js run if (true) { - var test = true; // use "var" instead of "let" + var test = true; // используем var вместо let } *!* -alert(test); // true, the variable lives after if +alert(test); // true, переменная существует вне блока if */!* ``` -If we used `let test` on the 2nd line, then it wouldn't be visible to `alert`. But `var` ignores code blocks, so we've got a global `test`. +Так как `var` игнорирует блоки, мы получили глобальную переменную `test`. -The same thing for loops: `var` cannot be block- or loop-local: +А если бы мы использовали `let test` вместо `var test`, тогда переменная была бы видна только внутри `if`: + +```js run +if (true) { + let test = true; // используем let +} + +*!* +alert(test); // Error: test is not defined +*/!* +``` + +Аналогично для циклов: `var` не может быть блочной или локальной внутри цикла: ```js for (var i = 0; i < 10; i++) { @@ -55,38 +73,67 @@ for (var i = 0; i < 10; i++) { } *!* -alert(i); // 10, "i" is visible after loop, it's a global variable +alert(i); // 10, переменная i доступна вне цикла, т.к. является глобальной переменной */!* ``` -If a code block is inside a function, then `var` becomes a function-level variable: +Если блок кода находится внутри функции, то `var` становится локальной переменной в этой функции: -```js +```js run function sayHi() { if (true) { - var phrase = "Hello"; + var phrase = "Привет"; } - alert(phrase); // works + alert(phrase); // срабатывает и выводит "Привет" } sayHi(); -alert(phrase); // Error: phrase is not defined +alert(phrase); // Ошибка: phrase не определена (видна в консоли разработчика) ``` -As we can see, `var` pierces through `if`, `for` or other code blocks. That's because a long time ago in JavaScript blocks had no Lexical Environments. And `var` is a remnant of that. +Как мы видим, `var` выходит за пределы блоков `if`, `for` и подобных. Это происходит потому, что на заре развития JavaScript блоки кода не имели лексического окружения. Поэтому можно сказать, что `var` - это пережиток прошлого. -## "var" are processed at the function start +## "var" допускает повторное объявление -`var` declarations are processed when the function starts (or script starts for globals). +Если в блоке кода дважды объявить одну и ту же переменную `let`, будет ошибка: -In other words, `var` variables are defined from the beginning of the function, no matter where the definition is (assuming that the definition is not in the nested function). +```js run +let user; +let user; // SyntaxError: 'user' has already been declared +``` -So this code: +Используя `var`, можно переобъявлять переменную сколько угодно раз. Повторные `var` игнорируются: +```js run +var user = "Пётр"; -```js +var user; // ничего не делает, переменная объявлена раньше +// ...нет ошибки + +alert(user); // Пётр +``` + +Если дополнительно присвоить значение, то переменная примет новое значение: + +```js run +var user = "Пётр"; + +var user = "Иван"; + +alert(user); // Иван +``` + +## "var" обрабатываются в начале запуска функции + +Объявления переменных `var` обрабатываются в начале выполнения функции (или запуска скрипта, если переменная является глобальной). + +Другими словами, переменные `var` считаются объявленными с самого начала исполнения функции вне зависимости от того, в каком месте функции реально находятся их объявления (при условии, что они не находятся во вложенной функции). + +Т.е. этот код: + +```js run function sayHi() { - phrase = "Hello"; + phrase = "Привет"; alert(phrase); @@ -94,27 +141,29 @@ function sayHi() { var phrase; */!* } +sayHi(); ``` -...Is technically the same as this (moved `var phrase` above): +...Технически полностью эквивалентен следующему (объявление переменной `var phrase` перемещено в начало функции): -```js +```js run function sayHi() { *!* var phrase; */!* - phrase = "Hello"; + phrase = "Привет"; alert(phrase); } +sayHi(); ``` -...Or even as this (remember, code blocks are ignored): +...И даже коду ниже (как вы помните, блочная область видимости игнорируется): -```js +```js run function sayHi() { - phrase = "Hello"; // (*) + phrase = "Привет"; // (*) *!* if (false) { @@ -124,62 +173,131 @@ function sayHi() { alert(phrase); } +sayHi(); ``` -People also call such behavior "hoisting" (raising), because all `var` are "hoisted" (raised) to the top of the function. +Это поведение называется "hoisting" (всплытие, поднятие), потому что все объявления переменных `var` "всплывают" в самый верх функции. -So in the example above, `if (false)` branch never executes, but that doesn't matter. The `var` inside it is processed in the beginning of the function, so at the moment of `(*)` the variable exists. +В примере выше `if (false)` условие никогда не выполнится. Но это никаким образом не препятствует созданию переменной `var phrase`, которая находится внутри него, поскольку объявления `var` "всплывают" в начало функции. Т.е. в момент присвоения значения `(*)` переменная уже существует. -**Declarations are hoisted, but assignments are not.** +**Объявления переменных "всплывают", но присваивания значений - нет.** -That's better to demonstrate with an example, like this: +Это проще всего продемонстрировать на примере: ```js run function sayHi() { alert(phrase); *!* - var phrase = "Hello"; + var phrase = "Привет"; */!* } sayHi(); ``` -The line `var phrase = "Hello"` has two actions in it: +Строка `var phrase = "Привет"` состоит из двух действий: -1. Variable declaration `var` -2. Variable assignment `=`. +1. Объявление переменной `var` +2. Присвоение значения в переменную `=`. -The declaration is processed at the start of function execution ("hoisted"), but the assignment always works at the place where it appears. So the code works essentially like this: +Объявление переменной обрабатывается в начале выполнения функции ("всплывает"), однако присвоение значения всегда происходит в той строке кода, где оно указано. Т.е. код выполняется по следующему сценарию: ```js run function sayHi() { *!* - var phrase; // declaration works at the start... + var phrase; // объявление переменной срабатывает вначале... */!* alert(phrase); // undefined *!* - phrase = "Hello"; // ...assignment - when the execution reaches it. + phrase = "Привет"; // ...присвоение - в момент, когда исполнится данная строка кода. */!* } sayHi(); ``` -Because all `var` declarations are processed at the function start, we can reference them at any place. But variables are undefined until the assignments. +Поскольку все объявления переменных `var` обрабатываются в начале функции, мы можем ссылаться на них в любом месте. Однако, переменные имеют значение `undefined` до строки с присвоением значения. + +В обоих примерах выше вызов `alert` происходил без ошибки, потому что переменная `phrase` уже существовала. Но её значение ещё не было присвоено, поэтому мы получали `undefined`. + +## IIFE + +В прошлом, поскольку существовал только `var`, а он не имел блочной области видимости, программисты придумали способ её эмулировать. Этот способ получил название "Immediately-invoked function expressions" (сокращенно IIFE). + +Это не то, что мы должны использовать сегодня, но, так как вы можете встретить это в старых скриптах, полезно понимать принцип работы. + +IIFE выглядит следующим образом: + +```js run +(function() { + + var message = "Привет"; + + alert(message); // Привет + +})(); +``` + +Здесь создаётся и немедленно вызывается Function Expression. Так что код выполняется сразу же и у него есть свои локальные переменные. + +Function Expression обёрнуто в скобки `(function {...})`, потому что, когда JavaScript встречает `"function"` в основном потоке кода, он воспринимает это как начало Function Declaration. Но у Function Declaration должно быть имя, так что такой код вызовет ошибку: + +```js run +// Пробуем объявить и сразу же вызвать функцию +function() { // <-- SyntaxError: Function statements require a function name + + var message = "Привет"; + + alert(message); // Привет + +}(); +``` + +Даже если мы скажем: "хорошо, давайте добавим имя", -- это не сработает, потому что JavaScript не позволяет вызывать Function Declaration немедленно. + +```js run +// ошибка синтаксиса из-за скобок ниже +function go() { + +}(); // <-- нельзя вызывать Function Declaration немедленно +``` + +Так что скобки вокруг функции -- это трюк, который позволяет объяснить JavaScript, что функция была создана в контексте другого выражения, а значит, что это Function Expression: ей не нужно имя и её можно вызвать немедленно. + +Помимо круглых скобок существуют и другие способы сообщить JavaScript, что мы имеем в виду Function Expression: + +```js run +// Способы создания IIFE + +*!*(*/!*function() { + alert("Круглые скобки вокруг функции"); +}*!*)*/!*(); + +*!*(*/!*function() { + alert("Круглые скобки вокруг всего выражения"); +}()*!*)*/!*; + +*!*!*/!*function() { + alert("Выражение начинается с логического оператора НЕ"); +}(); + +*!*+*/!*function() { + alert("Выражение начинается с унарного плюса"); +}(); +``` -In both examples above `alert` runs without an error, because the variable `phrase` exists. But its value is not yet assigned, so it shows `undefined`. +Во всех перечисленных случаях мы объявляем Function Expression и немедленно запускаем его. Ещё раз отметим: в настоящее время необходимости писать подобный код нет. -## Summary +## Итого -There are two main differences of `var`: +Существует 2 основных отличия `var` от `let/const`: -1. Variables have no block scope, they are visible minimum at the function level. -2. Variable declarations are processed at function start. +1. Переменные `var` не имеют блочной области видимости, они ограничены, как минимум, телом функции. +2. Объявления (инициализация) переменных `var` производится в начале исполнения функции (или скрипта для глобальных переменных). -There's one more minor difference related to the global object, we'll cover that in the next chapter. +Есть ещё одно небольшое отличие, относящееся к глобальному объекту, мы рассмотрим его в следующей главе. -These differences are actually a bad thing most of the time. Block-level variables is such a great thing. That's why `let` was introduced in the standard long ago, and is now a major way (along with `const`) to declare a variable. +Эти особенности, как правило, не очень хорошо влияют на код. Блочная область видимости - это удобно. Поэтому много лет назад `let` и `const` были введены в стандарт и сейчас являются основным способом объявления переменных. diff --git a/1-js/06-advanced-functions/05-global-object/article.md b/1-js/06-advanced-functions/05-global-object/article.md index da4adc2b6d..b1ff9361f1 100644 --- a/1-js/06-advanced-functions/05-global-object/article.md +++ b/1-js/06-advanced-functions/05-global-object/article.md @@ -1,155 +1,91 @@ -# Global object +# Глобальный объект -The global object provides variables and functions that are available anywhere. Mostly, the ones that are built into the language or the host environment. +Глобальный объект предоставляет переменные и функции, доступные в любом месте программы. По умолчанию это те, что встроены в язык или среду исполнения. -In a browser it is named "window", for Node.js it is "global", for other environments it may have another name. +В браузере он называется `window`, в Node.js — `global`, в другой среде исполнения может называться иначе. -For instance, we can call `alert` as a method of `window`: +Недавно `globalThis` был добавлен в язык как стандартизированное имя для глобального объекта, которое должно поддерживаться в любом окружении. Он поддерживается во всех основных браузерах. -```js run -alert("Hello"); - -// the same as -window.alert("Hello"); -``` - -We can reference other built-in functions like `Array` as `window.Array` and create our own properties on it. - -## Browser: the "window" object - -For historical reasons, in-browser `window` object is a bit messed up. - -1. It provides the "browser window" functionality, besides playing the role of a global object. - - We can use `window` to access properties and methods, specific to the browser window: - - ```js run - alert(window.innerHeight); // shows the browser window height - - window.open('http://google.com'); // opens a new browser window - ``` - -2. Top-level `var` variables and function declarations automatically become properties of `window`. - - For instance: - ```js untrusted run no-strict refresh - var x = 5; - - alert(window.x); // 5 (var x becomes a property of window) - - window.x = 0; - - alert(x); // 0, variable modified - ``` - - Please note, that doesn't happen with more modern `let/const` declarations: - - ```js untrusted run no-strict refresh - let x = 5; +Далее мы будем использовать `window`, полагая, что наша среда - браузер. Если скрипт может выполняться и в другом окружении, лучше будет `globalThis`. - alert(window.x); // undefined ("let" doesn't create a window property) - ``` +Ко всем свойствам глобального объекта можно обращаться напрямую: -3. Also, all scripts share the same global scope, so variables declared in one ` - - - ``` - -4. And, a minor thing, but still: the value of `this` in the global scope is `window`. - - ```js untrusted run no-strict refresh - alert(this); // window - ``` - -Why was it made like this? At the time of the language creation, the idea to merge multiple aspects into a single `window` object was to "make things simple". But since then many things changed. Tiny scripts became big applications that require proper architecture. - -Is it good that different scripts (possibly from different sources) see variables of each other? - -No, it's not, because it may lead to naming conflicts: the same variable name can be used in two scripts for different purposes, so they will conflict with each other. - -As of now, the multi-purpose `window` is considered a design mistake in the language. +```js run +alert("Привет"); +// это то же самое, что и +window.alert("Привет"); +``` -Luckily, there's a "road out of hell", called "JavaScript modules". +В браузере глобальные функции и переменные, объявленные с помощью `var` (не `let/const`!), становятся свойствами глобального объекта: -If we set `type="module"` attribute on a ` - ``` +Пожалуйста, не полагайтесь на это. Такое поведение поддерживается для совместимости. В современных проектах, использующих [JavaScript-модули](info:modules), такого не происходит. -- Two modules that do not see variables of each other: +Если бы мы объявили переменную при помощи `let`, то такого бы не произошло: - ```html run - +```js run untrusted refresh +let gLet = 5; - - ``` +alert(window.gLet); // undefined (не становится свойством глобального объекта) +``` -- And, the last minor thing, the top-level value of `this` in a module is `undefined` (why should it be `window` anyway?): +Если свойство настолько важное, что вы хотите сделать его доступным для всей программы, запишите его в глобальный объект напрямую: - ```html run - - ``` +```js run +*!* +// сделать информацию о текущем пользователе глобальной, для предоставления доступа всем скриптам +window.currentUser = { + name: "John" +}; +*/!* + +// где угодно в коде +alert(currentUser.name); // John + +// или, если у нас есть локальная переменная с именем "currentUser", +// получим её из window явно (безопасно!) +alert(window.currentUser.name); // John +``` -**Using ` -``` - -If you run it, the changes to `i` will show up after the whole count finishes. - -And if we use `setTimeout` to split it into pieces then changes are applied in-between the runs, so this looks better: - -```html run -
- - -``` - -Now the `
` shows increasing values of `i`. - -## Summary +Этого ограничения нет в серверном JavaScript. Там есть и другие способы планирования асинхронных задач. Например, [setImmediate](https://nodejs.org/api/timers.html) для Node.js. Так что это ограничение относится только к браузерам. +```` -- Methods `setInterval(func, delay, ...args)` and `setTimeout(func, delay, ...args)` allow to run the `func` regularly/once after `delay` milliseconds. -- To cancel the execution, we should call `clearInterval/clearTimeout` with the value returned by `setInterval/setTimeout`. -- Nested `setTimeout` calls is a more flexible alternative to `setInterval`. Also they can guarantee the minimal time *between* the executions. -- Zero-timeout scheduling `setTimeout(...,0)` is used to schedule the call "as soon as possible, but after the current code is complete". +## Итого -Some use cases of `setTimeout(...,0)`: -- To split CPU-hungry tasks into pieces, so that the script doesn't "hang" -- To let the browser do something else while the process is going on (paint the progress bar). +- Методы `setInterval(func, delay, ...args)` и `setTimeout(func, delay, ...args)` позволяют выполнять `func` регулярно или только один раз после задержки `delay`, заданной в мс. +- Для отмены выполнения необходимо вызвать `clearInterval/clearTimeout` со значением, которое возвращают методы `setInterval/setTimeout`. +- Вложенный вызов `setTimeout` является более гибкой альтернативой `setInterval`. Также он позволяет более точно задать интервал между выполнениями. +- Планирование с нулевой задержкой `setTimeout(func,0)` или, что то же самое, `setTimeout(func)` используется для вызовов, которые должны быть исполнены как можно скорее, после завершения исполнения текущего кода. +- Браузер ограничивает 4-мя мс минимальную задержку между пятью и более вложенными вызовами `setTimeout`, а также для `setInterval`, начиная с 5-го вызова. -Please note that all scheduling methods do not *guarantee* the exact delay. We should not rely on that in the scheduled code. +Обратим внимание, что все методы планирования *не гарантируют* точную задержку. -For example, the in-browser timer may slow down for a lot of reasons: -- The CPU is overloaded. -- The browser tab is in the background mode. -- The laptop is on battery. +Например, таймер в браузере может замедляться по многим причинам: +- Перегружен процессор. +- Вкладка браузера в фоновом режиме. +- Работа ноутбука от аккумулятора. -All that may increase the minimal timer resolution (the minimal delay) to 300ms or even 1000ms depending on the browser and settings. +Всё это может увеличивать минимальный интервал срабатывания таймера (и минимальную задержку) до 300 или даже 1000 мс в зависимости от браузера и настроек производительности ОС. diff --git a/1-js/06-advanced-functions/08-settimeout-setinterval/setinterval-interval.png b/1-js/06-advanced-functions/08-settimeout-setinterval/setinterval-interval.png deleted file mode 100644 index 844c537882..0000000000 Binary files a/1-js/06-advanced-functions/08-settimeout-setinterval/setinterval-interval.png and /dev/null differ diff --git a/1-js/06-advanced-functions/08-settimeout-setinterval/setinterval-interval.svg b/1-js/06-advanced-functions/08-settimeout-setinterval/setinterval-interval.svg new file mode 100644 index 0000000000..bce7d6a843 --- /dev/null +++ b/1-js/06-advanced-functions/08-settimeout-setinterval/setinterval-interval.svg @@ -0,0 +1 @@ +func(1)func(2)func(3)100200300 \ No newline at end of file diff --git a/1-js/06-advanced-functions/08-settimeout-setinterval/setinterval-interval@2x.png b/1-js/06-advanced-functions/08-settimeout-setinterval/setinterval-interval@2x.png deleted file mode 100644 index 4fbf122c54..0000000000 Binary files a/1-js/06-advanced-functions/08-settimeout-setinterval/setinterval-interval@2x.png and /dev/null differ diff --git a/1-js/06-advanced-functions/08-settimeout-setinterval/settimeout-interval.png b/1-js/06-advanced-functions/08-settimeout-setinterval/settimeout-interval.png deleted file mode 100644 index f59e157fb8..0000000000 Binary files a/1-js/06-advanced-functions/08-settimeout-setinterval/settimeout-interval.png and /dev/null differ diff --git a/1-js/06-advanced-functions/08-settimeout-setinterval/settimeout-interval.svg b/1-js/06-advanced-functions/08-settimeout-setinterval/settimeout-interval.svg new file mode 100644 index 0000000000..d6d233b2ba --- /dev/null +++ b/1-js/06-advanced-functions/08-settimeout-setinterval/settimeout-interval.svg @@ -0,0 +1 @@ +func(1)func(2)func(3)100100 \ No newline at end of file diff --git a/1-js/06-advanced-functions/08-settimeout-setinterval/settimeout-interval@2x.png b/1-js/06-advanced-functions/08-settimeout-setinterval/settimeout-interval@2x.png deleted file mode 100644 index ed99391cf7..0000000000 Binary files a/1-js/06-advanced-functions/08-settimeout-setinterval/settimeout-interval@2x.png and /dev/null differ diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/_js.view/solution.js b/1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/_js.view/solution.js index 9ef503703b..cdd9851be8 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/_js.view/solution.js +++ b/1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/_js.view/solution.js @@ -1,11 +1,12 @@ function spy(func) { function wrapper(...args) { + // мы используем ...args вместо arguments для хранения "реального" массива в wrapper.calls wrapper.calls.push(args); - return func.apply(this, arguments); + return func.apply(this, args); } wrapper.calls = []; return wrapper; -} \ No newline at end of file +} diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/_js.view/source.js b/1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/_js.view/source.js index 38da0105f2..d6d6596c14 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/_js.view/source.js +++ b/1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/_js.view/source.js @@ -1,5 +1,5 @@ function spy(func) { - // your code + // ваш код } diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/_js.view/test.js b/1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/_js.view/test.js index 5adfcb9787..50cb6b4107 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/_js.view/test.js +++ b/1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/_js.view/test.js @@ -1,5 +1,5 @@ describe("spy", function() { - it("records calls into its property", function() { + it("записывает вызовы в своё свойство", function() { function work() {} work = spy(work); @@ -17,7 +17,7 @@ describe("spy", function() { ]); }); - it("transparently wraps functions", function() { + it("прозрачно оборачивает функции", function() { let sum = sinon.spy((a, b) => a + b); @@ -28,7 +28,7 @@ describe("spy", function() { }); - it("transparently wraps methods", function() { + it("прозрачно оборачивает методы", function() { let calc = { sum: sinon.spy((a, b) => a + b) diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/solution.md b/1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/solution.md index 19a072014b..83a16b7914 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/solution.md +++ b/1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/solution.md @@ -1 +1,2 @@ -Here we can use `calls.push(args)` to store all arguments in the log and `f.apply(this, args)` to forward the call. + +Обертка, возвращаемая `spy(f)`, должна хранить все аргументы, и затем использовать `f.apply` для переадресации вызова. diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/task.md b/1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/task.md index a3843107c9..9256d08393 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/task.md +++ b/1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/task.md @@ -2,17 +2,17 @@ importance: 5 --- -# Spy decorator +# Декоратор-шпион -Create a decorator `spy(func)` that should return a wrapper that saves all calls to function in its `calls` property. +Создайте декоратор `spy(func)`, который должен возвращать обёртку, которая сохраняет все вызовы функции в своём свойстве `calls`. -Every call is saved as an array of arguments. +Каждый вызов должен сохраняться как массив аргументов. -For instance: +Например: ```js function work(a, b) { - alert( a + b ); // work is an arbitrary function or method + alert( a + b ); // произвольная функция или метод } *!* @@ -27,4 +27,4 @@ for (let args of work.calls) { } ``` -P.S. That decorator is sometimes useful for unit-testing. Its advanced form is `sinon.spy` in [Sinon.JS](http://sinonjs.org/) library. +P.S.: Этот декоратор иногда полезен для юнит-тестирования. Его расширенная форма - `sinon.spy` - содержится в библиотеке [Sinon.JS](https://sinonjs.org/). diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/02-delay/_js.view/test.js b/1-js/06-advanced-functions/09-call-apply-decorators/02-delay/_js.view/test.js index d9295da51d..4c6b1a7cd7 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/02-delay/_js.view/test.js +++ b/1-js/06-advanced-functions/09-call-apply-decorators/02-delay/_js.view/test.js @@ -7,7 +7,7 @@ describe("delay", function() { this.clock.restore(); }); - it("calls the function after the specified timeout", function() { + it("вызывает функцию после указанного тайм-аута", function() { let start = Date.now(); function f(x) { @@ -18,10 +18,10 @@ describe("delay", function() { let f1000 = delay(f, 1000); f1000("test"); this.clock.tick(2000); - assert(f.calledOnce, 'calledOnce check fails'); + assert(f.calledOnce, 'проверка calledOnce завершилась неудачно'); }); - it("passes arguments and this", function() { + it("передаёт arguments и this", function() { let start = Date.now(); let user = { sayHi: function(phrase, who) { @@ -41,6 +41,6 @@ describe("delay", function() { this.clock.tick(2000); - assert(spy.calledOnce, 'calledOnce check failed'); + assert(spy.calledOnce, 'проверка calledOnce завершилась неудачно'); }); }); diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/02-delay/solution.md b/1-js/06-advanced-functions/09-call-apply-decorators/02-delay/solution.md index 24bb4d4484..ced8242697 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/02-delay/solution.md +++ b/1-js/06-advanced-functions/09-call-apply-decorators/02-delay/solution.md @@ -1,4 +1,4 @@ -The solution: +Решение: ```js run demo function delay(f, ms) { @@ -11,22 +11,22 @@ function delay(f, ms) { let f1000 = delay(alert, 1000); -f1000("test"); // shows "test" after 1000ms +f1000("test"); // показывает "test" после 1000 мс ``` -Please note how an arrow function is used here. As we know, arrow functions do not have own `this` and `arguments`, so `f.apply(this, arguments)` takes `this` and `arguments` from the wrapper. +Обратите внимание, как здесь используется функция-стрелка. Как мы знаем, функция-стрелка не имеет собственных `this` и `arguments`, поэтому `f.apply(this, arguments)` берет `this` и `arguments` из обёртки. -If we pass a regular function, `setTimeout` would call it without arguments and `this=window` (assuming we're in the browser). +Если мы передадим обычную функцию, `setTimeout` вызовет её без аргументов и с `this=window` (при условии, что код выполняется в браузере). -We still can pass the right `this` by using an intermediate variable, but that's a little bit more cumbersome: +Мы всё ещё можем передать правильный `this`, используя промежуточную переменную, но это немного громоздко: ```js function delay(f, ms) { return function(...args) { - let savedThis = this; // store this into an intermediate variable + let savedThis = this; // сохраняем this в промежуточную переменную setTimeout(function() { - f.apply(savedThis, args); // use it here + f.apply(savedThis, args); // используем её }, ms); }; diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/02-delay/task.md b/1-js/06-advanced-functions/09-call-apply-decorators/02-delay/task.md index c04c68d7ef..fff67ed8f2 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/02-delay/task.md +++ b/1-js/06-advanced-functions/09-call-apply-decorators/02-delay/task.md @@ -2,25 +2,24 @@ importance: 5 --- -# Delaying decorator +# Задерживающий декоратор -Create a decorator `delay(f, ms)` that delays each call of `f` by `ms` milliseconds. - -For instance: +Создайте декоратор `delay(f, ms)`, который задерживает каждый вызов `f` на `ms` миллисекунд. +Например: ```js function f(x) { alert(x); } -// create wrappers +// создаём обёртки let f1000 = delay(f, 1000); let f1500 = delay(f, 1500); -f1000("test"); // shows "test" after 1000ms -f1500("test"); // shows "test" after 1500ms +f1000("test"); // показывает "test" после 1000 мс +f1500("test"); // показывает "test" после 1500 мс ``` -In other words, `delay(f, ms)` returns a "delayed by `ms`" variant of `f`. +Другими словами, `delay(f, ms)` возвращает вариант `f` с "задержкой на `ms` мс". -In the code above, `f` is a function of a single argument, but your solution should pass all arguments and the context `this`. +В приведённом выше коде `f` - функция с одним аргументом, но ваше решение должно передавать все аргументы и контекст `this`. diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/_js.view/solution.js b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/_js.view/solution.js index 065a77d1f9..3999156b7c 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/_js.view/solution.js +++ b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/_js.view/solution.js @@ -1,15 +1,7 @@ -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); }; - } \ No newline at end of file diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/_js.view/test.js b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/_js.view/test.js index 16dc171e1a..bceaefee24 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/_js.view/test.js +++ b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/_js.view/test.js @@ -1,41 +1,47 @@ -describe("debounce", function() { - before(function() { +describe('debounce', function () { + before(function () { this.clock = sinon.useFakeTimers(); }); - after(function() { + after(function () { this.clock.restore(); }); - it("calls the function at maximum once in ms milliseconds", function() { - let log = ''; + it('для одного вызова - запускается через "ms" миллисекунд', function () { + const f = sinon.spy(); + const debounced = debounce(f, 1000); - function f(a) { - log += a; - } + debounced('test'); + assert(f.notCalled, 'не вызывается сразу'); + this.clock.tick(1000); + assert(f.calledOnceWith('test'), 'вызывается после 1000ms'); + }); - f = debounce(f, 1000); + it('для 3 вызовов - вызывает последний через "ms" миллисекунд', function () { + const f = sinon.spy(); + const debounced = debounce(f, 1000); - f(1); // runs at once - f(2); // ignored + debounced('a'); + setTimeout(() => debounced('b'), 200); // проигнорирована + setTimeout(() => debounced('c'), 500); // вызвана + this.clock.tick(1000); - setTimeout(() => f(3), 100); // ignored (too early) - setTimeout(() => f(4), 1100); // runs (1000 ms passed) - setTimeout(() => f(5), 1500); // ignored (less than 1000 ms from the last run) + assert(f.notCalled, 'не вызывается после 1000ms'); - this.clock.tick(5000); - assert.equal(log, "14"); + this.clock.tick(500); + + assert(f.calledOnceWith('c'), 'вызывается после 1500ms'); }); - it("keeps the context of the call", function() { + it('сохраняет контекст вызова', function () { let obj = { f() { assert.equal(this, obj); - } + }, }; obj.f = debounce(obj.f, 1000); - obj.f("test"); + obj.f('test'); + this.clock.tick(5000); }); - }); \ No newline at end of file diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/debounce.svg b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/debounce.svg new file mode 100644 index 0000000000..8e128ce74d --- /dev/null +++ b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/debounce.svg @@ -0,0 +1 @@ +200мс1500мс1000мс0cf(a)f(b)f(c)500мсвремявызовы: после 1000мс \ No newline at end of file diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/debounce.view/index.html b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/debounce.view/index.html new file mode 100644 index 0000000000..aab1a471bb --- /dev/null +++ b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/debounce.view/index.html @@ -0,0 +1,24 @@ + + + +Функция handler вызывается на этом поле для ввода: +
+ + +

+ +А на этом поле функция 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 4f5867ded9..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,28 +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); }; - } -``` - -A call to `debounce` returns a wrapper. There may be two states: - -- `isCooldown = false` -- ready to run. -- `isCooldown = true` -- waiting for the timeout. -In the first call `isCooldown` is falsy, so the call proceeds, and the state changes to `true`. - -While `isCooldown` is true, all other calls are ignored. +``` -Then `setTimeout` reverts it to `false` after the given delay. +Вызов `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 466c6bc3f7..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 @@ -2,23 +2,51 @@ importance: 5 --- -# Debounce decorator +# Декоратор debounce -The result of `debounce(f, ms)` decorator should be a wrapper that passes the call to `f` at maximum once per `ms` milliseconds. +Результат декоратора `debounce(f, ms)` – это обёртка, которая откладывает вызовы `f`, пока не пройдёт `ms` миллисекунд бездействия (без вызовов, «cooldown period»), а затем вызывает `f` один раз с последними аргументами. -In other words, when we call a "debounced" function, it guarantees that all other future in the closest `ms` milliseconds will be ignored. +Другими словами, `debounce` – это так называемый секретарь, который принимает «телефонные звонки», и ждёт, пока не пройдет `ms` миллисекунд тишины. И только после этого передает «начальнику» информацию о последнем звонке (вызывает непосредственно `f`). -For instance: +Например, у нас была функция `f` и мы заменили её на `f = debounce(f, 1000)`. + +Затем, если обёрнутая функция вызывается в 0, 200 и 500 мс, а потом вызовов нет, то фактическая `f` будет вызвана только один раз, в 1500 мс. То есть: по истечению 1000 мс от последнего вызова. + +![](debounce.svg) + +...И она получит аргументы самого последнего вызова, остальные вызовы игнорируются. + +Ниже код этого примера (используется декоратор debounce из библиотеки [Lodash](https://lodash.com/docs/4.17.15#debounce)): ```js no-beautify -let f = debounce(alert, 1000); +let f = _.debounce(alert, 1000); -f(1); // runs immediately -f(2); // ignored +f("a"); +setTimeout( () => f("b"), 200); +setTimeout( () => f("c"), 500); -setTimeout( () => f(3), 100); // ignored ( only 100 ms passed ) -setTimeout( () => f(4), 1100); // runs -setTimeout( () => f(5), 1500); // ignored (less than 1000 ms from the last run) +// Обёрнутая в debounce функция ждёт 1000 мс после последнего вызова, а затем запускает: alert("c") ``` -In practice `debounce` is useful for functions that retrieve/update something when we know that nothing new can be done in such a short period of time, so it's better not to waste resources. \ No newline at end of file +Теперь практический пример. Предположим, пользователь набирает какой-то текст, и мы хотим отправить запрос на сервер, когда ввод этого текста будет завершён. + +Нет смысла отправлять запрос для каждого набранного символа. Вместо этого мы хотели бы подождать, а затем обработать весь результат. + +В браузере мы можем настроить обработчик событий – функцию, которая вызывается при каждом изменении поля для ввода. Обычно обработчик событий вызывается очень часто, для каждого набранного символа. Но если мы воспользуемся `debounce` на 1000мс, то он будет вызван только один раз, через 1000мс после последнего ввода символа. + +```online + +В этом живом примере обработчик помещает результат в поле ниже, попробуйте: + +[iframe border=1 src="debounce" height=200] + +Видите? На втором поле вызывается функция, обёрнутая в `debounce`, поэтому его содержимое обрабатывается через 1000мс с момента последнего ввода. +``` + +Таким образом, `debounce` – это отличный способ обработать последовательность событий: будь то последовательность нажатий клавиш, движений мыши или ещё что-либо. + +Он ждёт заданное время после последнего вызова, а затем запускает свою функцию, которая может обработать результат. + +Задача — реализовать декоратор `debounce`. + +Подсказка: это всего лишь несколько строк, если вдуматься :) \ No newline at end of file diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/_js.view/solution.js b/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/_js.view/solution.js index 8071be9d48..6c90d1f228 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/_js.view/solution.js +++ b/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/_js.view/solution.js @@ -7,23 +7,23 @@ function throttle(func, ms) { function wrapper() { if (isThrottled) { - // memo last arguments to call after the cooldown + // запоминаем последние аргументы для вызова после задержки savedArgs = arguments; savedThis = this; return; } - // otherwise go to cooldown state + // в противном случае переходим в состояние задержки func.apply(this, arguments); isThrottled = true; - // plan to reset isThrottled after the delay + // настройка сброса isThrottled после задержки setTimeout(function() { isThrottled = false; if (savedArgs) { - // if there were calls, savedThis/savedArgs have the last one - // recursive call runs the function and sets cooldown again + // если были вызовы, savedThis/savedArgs хранят последний из них + // рекурсивный вызов запускает функцию и снова устанавливает время задержки wrapper.apply(savedThis, savedArgs); savedArgs = savedThis = null; } @@ -31,4 +31,4 @@ function throttle(func, ms) { } return wrapper; -} \ No newline at end of file +} diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/_js.view/test.js b/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/_js.view/test.js index 5339c8d117..46aaa38f47 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/_js.view/test.js +++ b/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/_js.view/test.js @@ -11,37 +11,37 @@ describe("throttle(f, 1000)", function() { this.clock = sinon.useFakeTimers(); }); - it("the first call runs now", function() { - f1000(1); // runs now + it("1-й вызов происходит немедленно", function() { + f1000(1); // вызов происходит немедленно assert.equal(log, "1"); }); - it("then calls are ignored till 1000ms when the last call works", function() { - f1000(2); // (throttling - less than 1000ms since the last run) - f1000(3); // (throttling - less than 1000ms since the last run) - // after 1000 ms f(3) call is scheduled + it("далее вызовы игнорируются до истечения 1000 мс от последнего вызова", function() { + f1000(2); // (задержка - менее 1000 мс с момента последнего вызова) + f1000(3); // (задержка - менее 1000 мс с момента последнего вызова) + // запланирован вызов спустя f(3) 1000 мс - assert.equal(log, "1"); // right now only the 1st call done + assert.equal(log, "1"); // прямо сейчас только 1-й вызов завершён - this.clock.tick(1000); // after 1000ms... - assert.equal(log, "13"); // log==13, the call to f1000(3) is made + this.clock.tick(1000); // через 1000 мс... + assert.equal(log, "13"); // log==13, произошёл вызов f1000(3) }); - it("the third call waits 1000ms after the second call", function() { + it("3-й вызов ждет 1000 мс от 2-го", function() { this.clock.tick(100); - f1000(4); // (throttling - less than 1000ms since the last run) + f1000(4); // (задержка - менее 1000 мс с момента последнего вызов) this.clock.tick(100); - f1000(5); // (throttling - less than 1000ms since the last run) + f1000(5); // (задержка - менее 1000 мс с момента последнего вызова) this.clock.tick(700); - f1000(6); // (throttling - less than 1000ms since the last run) + f1000(6); // (задержка - менее 1000 мс с момента последнего вызова) - this.clock.tick(100); // now 100 + 100 + 700 + 100 = 1000ms passed + this.clock.tick(100); // теперь прошло 100 + 100 + 700 + 100 = 1000 мс - assert.equal(log, "136"); // the last call was f(6) + assert.equal(log, "136"); // последний вызов был f(6) }); after(function() { this.clock.restore(); }); -}); \ 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 c844016d3b..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 @@ -30,10 +30,10 @@ function throttle(func, ms) { } ``` -A call to `throttle(func, ms)` returns `wrapper`. +Вызов `throttle(func, ms)` возвращает `wrapper`. -1. During the first call, the `wrapper` just runs `func` and sets the cooldown state (`isThrottled = true`). -2. In this state all calls memorized in `savedArgs/savedThis`. Please note that both the context and the arguments are equally important and should be memorized. We need them simultaneously to reproduce the call. -3. ...Then after `ms` milliseconds pass, `setTimeout` triggers. The cooldown state is removed (`isThrottled = false`). And if we had ignored calls, then `wrapper` is executed with last memorized arguments and context. +1. Во время первого вызова обёртка просто вызывает `func` и устанавливает состояние задержки (`isThrottled = true`). +2. В этом состоянии все вызовы запоминаются в `saveArgs / saveThis`. Обратите внимание, что контекст и аргументы одинаково важны и должны быть запомнены. Они нам нужны для того, чтобы воспроизвести вызов позднее. +3. Затем по прошествии `ms` миллисекунд срабатывает `setTimeout`. Состояние задержки сбрасывается (`isThrottled = false`). И если мы проигнорировали вызовы, то "обёртка" выполняется с последними запомненными аргументами и контекстом. -The 3rd step runs not `func`, but `wrapper`, because we not only need to execute `func`, but once again enter the cooldown state and setup the timeout to reset it. +На третьем шаге выполняется не `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 8dd77368d8..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 @@ -2,47 +2,51 @@ importance: 5 --- -# Throttle decorator +# Тормозящий (throttling) декоратор -Create a "throttling" decorator `throttle(f, ms)` -- that returns a wrapper, passing the call to `f` at maximum once per `ms` milliseconds. Those calls that fall into the "cooldown" period, are ignored. +Создайте «тормозящий» декоратор `throttle(f, ms)`, который возвращает обёртку. -**The difference with `debounce` -- if an ignored call is the last during the cooldown, then it executes at the end of the delay.** +При многократном вызове он передает вызов `f` не чаще одного раза в `ms` миллисекунд. -Let's check the real-life application to better understand that requirement and to see where it comes from. +По сравнению с декоратором `debounce` поведение совершенно другое: +- `debounce` запускает функцию один раз после периода «бездействия». Подходит для обработки конечного результата. +- `throttle` запускает функцию не чаще, чем указанное время `ms`. Подходит для регулярных обновлений, которые не должны быть слишком частыми. -**For instance, we want to track mouse movements.** +Другими словами, `throttle` похож на секретаря, который принимает телефонные звонки, но при этом беспокоит начальника (вызывает непосредственно `f`) не чаще, чем один раз в `ms` миллисекунд. -In browser we can setup a function to run at every mouse movement and get the pointer location as it moves. During an active mouse usage, this function usually runs very frequently, can be something like 100 times per second (every 10 ms). +Давайте рассмотрим реальное применение, чтобы лучше понять это требование и выяснить, откуда оно взято. -**The tracking function should update some information on the web-page.** +**Например, мы хотим отслеживать движения мыши.** -Updating function `update()` is too heavy to do it on every micro-movement. There is also no sense in making it more often than once per 100ms. +В браузере мы можем реализовать функцию, которая будет запускаться при каждом перемещении указателя и получать его местоположение. Во время активного использования мыши эта функция запускается очень часто, что-то около 100 раз в секунду (каждые 10 мс). **Мы бы хотели обновлять некоторую информацию на странице при передвижении указателя.** -So we'll wrap it into the decorator: use `throttle(update, 100)` as the function to run on each mouse move instead of the original `update()`. The decorator will be called often, but `update()` will be called at maximum once per 100ms. +...Но функция обновления `update()` слишком ресурсоёмкая, чтобы делать это при каждом микродвижении. Да и нет смысла делать обновление чаще, чем один раз в 1000 мс. -Visually, it will look like this: +Поэтому мы обернём вызов в декоратор: будем использовать `throttle(update, 1000)` как функцию, которая будет запускаться при каждом перемещении указателя вместо оригинальной `update()`. Декоратор будет вызываться часто, но передавать вызов в `update()` максимум раз в 1000 мс. -1. For the first mouse movement the decorated variant passes the call to `update`. That's important, the user sees our reaction to their move immediately. -2. Then as the mouse moves on, until `100ms` nothing happens. The decorated variant ignores calls. -3. At the end of `100ms` -- one more `update` happens with the last coordinates. -4. Then, finally, the mouse stops somewhere. The decorated variant waits until `100ms` expire and then runs `update` with last coordinates. So, perhaps the most important, the final mouse coordinates are processed. +Визуально это будет выглядеть вот так: -A code example: +1. Для первого движения указателя декорированный вариант сразу передаёт вызов в `update`. Это важно, т.к. пользователь сразу видит нашу реакцию на его перемещение. +2. Затем, когда указатель продолжает движение, в течение 1000 мс ничего не происходит. Декорированный вариант игнорирует вызовы. +3. По истечению 1000 мс происходит ещё один вызов `update` с последними координатами. +4. Затем, наконец, указатель где-то останавливается. Декорированный вариант ждёт, пока не истечёт 1000 мс, и затем вызывает `update` с последними координатами. В итоге окончательные координаты указателя тоже обработаны. + +Пример кода: ```js function f(a) { console.log(a) -}; +} -// f1000 passes calls to f at maximum once per 1000 ms +// f1000 передаёт вызовы f максимум раз в 1000 мс let f1000 = throttle(f, 1000); -f1000(1); // shows 1 -f1000(2); // (throttling, 1000ms not out yet) -f1000(3); // (throttling, 1000ms not out yet) +f1000(1); // показывает 1 +f1000(2); // (ограничение, 1000 мс ещё нет) +f1000(3); // (ограничение, 1000 мс ещё нет) -// when 1000 ms time out... -// ...outputs 3, intermediate value 2 was ignored +// когда 1000 мс истекли ... +// ...выводим 3, промежуточное значение 2 было проигнорировано ``` -P.S. Arguments and the context `this` passed to `f1000` should be passed to the original `f`. +P.S. Аргументы и контекст `this`, переданные в `f1000`, должны быть переданы в оригинальную `f`. 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 75c510d1e4..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,20 +1,20 @@ -# Decorators and forwarding, call/apply +# Декораторы и переадресация вызова, call/apply -JavaScript gives exceptional flexibility when dealing with functions. They can be passed around, used as objects, and now we'll see how to *forward* calls between them and *decorate* them. +JavaScript предоставляет исключительно гибкие возможности по работе с функциями: они могут быть переданы в другие функции, использованы как объекты, и сейчас мы рассмотрим, как *перенаправлять* вызовы между ними и как их декорировать. -## Transparent caching +## Прозрачное кеширование -Let's say we have a function `slow(x)` which is CPU-heavy, but its results are stable. In other words, for the same `x` it always returns the same result. +Представим, что у нас есть функция `slow(x)`, выполняющая ресурсоёмкие вычисления, но возвращающая стабильные результаты. Другими словами, для одного и того же `x` она всегда возвращает один и тот же результат. -If the function is called often, we may want to cache (remember) the results for different `x` to avoid spending extra-time on recalculations. +Если функция вызывается часто, то, вероятно, мы захотим кешировать (запоминать) возвращаемые ею результаты, чтобы сэкономить время на повторных вычислениях. -But instead of adding that functionality into `slow()` we'll create a wrapper. As we'll see, there are many benefits of doing so. +Вместо того, чтобы усложнять `slow(x)` дополнительной функциональностью, мы заключим её в функцию-обёртку - "wrapper" (от англ. "wrap" - обёртывать), которая добавит кеширование. Далее мы увидим, что в таком подходе масса преимуществ. -Here's the code, and explanations follow: +Вот код с объяснениями: ```js run function slow(x) { - // there can be a heavy CPU-intensive job here + // здесь могут быть ресурсоёмкие вычисления alert(`Called with ${x}`); return x; } @@ -23,68 +23,66 @@ function cachingDecorator(func) { let cache = new Map(); return function(x) { - if (cache.has(x)) { // if the result is in the map - return cache.get(x); // return it + if (cache.has(x)) { // если кеш содержит такой x, + return cache.get(x); // читаем из него результат } - let result = func(x); // otherwise call func + let result = func(x); // иначе, вызываем функцию - cache.set(x, result); // and cache (remember) the result + cache.set(x, result); // и кешируем (запоминаем) результат return result; }; } slow = cachingDecorator(slow); -alert( slow(1) ); // slow(1) is cached -alert( "Again: " + slow(1) ); // the same +alert( slow(1) ); // slow(1) кешируем +alert( "Again: " + slow(1) ); // возвращаем из кеша -alert( slow(2) ); // slow(2) is cached -alert( "Again: " + slow(2) ); // the same as the previous line +alert( slow(2) ); // slow(2) кешируем +alert( "Again: " + slow(2) ); // возвращаем из кеша ``` -In the code above `cachingDecorator` is a *decorator*: a special function that takes another function and alters its behavior. +В коде выше `cachingDecorator` -- это *декоратор*, специальная функция, которая принимает другую функцию и изменяет её поведение. -The idea is that we can call `cachingDecorator` for any function, and it will return the caching wrapper. That's great, because we can have many functions that could use such a feature, and all we need to do is to apply `cachingDecorator` to them. +Идея состоит в том, что мы можем вызвать `cachingDecorator` с любой функцией, в результате чего мы получим кеширующую обёртку. Это здорово, т.к. у нас может быть множество функций, использующих такую функциональность, и всё, что нам нужно сделать - это применить к ним `cachingDecorator`. -By separating caching from the main function code we also keep the main code simpler. +Отделяя кеширующий код от основного кода, мы также сохраняем чистоту и простоту последнего. -Now let's get into details of how it works. +Результат вызова `cachingDecorator(func)` является "обёрткой", т.е. `function(x)` "оборачивает" вызов `func(x)` в кеширующую логику: -The result of `cachingDecorator(func)` is a "wrapper": `function(x)` that "wraps" the call of `func(x)` into caching logic: +![](decorator-makecaching-wrapper.svg) -![](decorator-makecaching-wrapper.png) +С точки зрения внешнего кода, обёрнутая функция `slow` по-прежнему делает то же самое. Обёртка всего лишь добавляет к её поведению аспект кеширования. -As we can see, the wrapper returns the result of `func(x)` "as is". From an outside code, the wrapped `slow` function still does the same. It just got a caching aspect added to its behavior. +Подводя итог, можно выделить несколько преимуществ использования отдельной `cachingDecorator` вместо изменения кода самой `slow`: -To summarize, there are several benefits of using a separate `cachingDecorator` instead of altering the code of `slow` itself: +- Функцию `cachingDecorator` можно использовать повторно. Мы можем применить её к другой функции. +- Логика кеширования является отдельной, она не увеличивает сложность самой `slow` (если таковая была). +- При необходимости мы можем объединить несколько декораторов (речь об этом пойдёт позже). -- The `cachingDecorator` is reusable. We can apply it to another function. -- The caching logic is separate, it did not increase the complexity of `slow` itself (if there were any). -- We can combine multiple decorators if needed (other decorators will follow). +## Применение "func.call" для передачи контекста -## Using "func.call" for the context +Упомянутый выше кеширующий декоратор не подходит для работы с методами объектов. -The caching decorator mentioned above is not suited to work with object methods. - -For instance, in the code below `worker.slow()` stops working after the decoration: +Например, в приведённом ниже коде `worker.slow()` перестаёт работать после применения декоратора: ```js run -// we'll make worker.slow caching +// сделаем worker.slow кеширующим let worker = { someMethod() { return 1; }, slow(x) { - // actually, there can be a scary CPU-heavy task here + // здесь может быть страшно тяжёлая задача для процессора alert("Called with " + x); return x * this.someMethod(); // (*) } }; -// same code as before +// тот же код, что и выше function cachingDecorator(func) { let cache = new Map(); return function(x) { @@ -99,49 +97,50 @@ function cachingDecorator(func) { }; } -alert( worker.slow(1) ); // the original method works +alert( worker.slow(1) ); // оригинальный метод работает -worker.slow = cachingDecorator(worker.slow); // now make it caching +worker.slow = cachingDecorator(worker.slow); // теперь сделаем его кеширующим *!* -alert( worker.slow(2) ); // Whoops! Error: Cannot read property 'someMethod' of undefined +alert( worker.slow(2) ); // Ой! Ошибка: не удаётся прочитать свойство 'someMethod' из 'undefined' */!* ``` -The error occurs in the line `(*)` that tries to access `this.someMethod` and fails. Can you see why? +Ошибка возникает в строке `(*)`. Функция пытается получить доступ к `this.someMethod` и завершается с ошибкой. Видите почему? -The reason is that the wrapper calls the original function as `func(x)` in the line `(**)`. And, when called like that, the function gets `this = undefined`. +Причина в том, что в строке `(**)` декоратор вызывает оригинальную функцию как `func(x)`, и она в данном случае получает `this = undefined`. -We would observe a similar symptom if we tried to run: +Мы бы наблюдали похожую ситуацию, если бы попытались запустить: ```js let func = worker.slow; func(2); ``` -So, the wrapper passes the call to the original method, but without the context `this`. Hence the error. +Т.е. декоратор передаёт вызов оригинальному методу, но без контекста. Следовательно -- ошибка. -Let's fix it. +Давайте это исправим. -There's a special built-in function method [func.call(context, ...args)](mdn:js/Function/call) that allows to call a function explicitly setting `this`. +Существует специальный встроенный метод функции [func.call(context, ...args)](mdn:js/Function/call), который позволяет вызывать функцию, явно устанавливая `this`. -The syntax is: +Синтаксис: ```js func.call(context, arg1, arg2, ...) ``` -It runs `func` providing the first argument as `this`, and the next as the arguments. +Он запускает функцию `func`, используя первый аргумент как её контекст `this`, а последующие -- как её аргументы. + +Проще говоря, эти два вызова делают почти то же самое: -To put it simply, these two calls do almost the same: ```js func(1, 2, 3); func.call(obj, 1, 2, 3) ``` -They both call `func` with arguments `1`, `2` and `3`. The only difference is that `func.call` also sets `this` to `obj`. +Они оба вызывают `func` с аргументами `1`, `2` и `3`. Единственное отличие состоит в том, что `func.call` ещё и устанавливает `this` равным `obj`. -As an example, in the code below we call `sayHi` in the context of different objects: `sayHi.call(user)` runs `sayHi` providing `this=user`, and the next line sets `this=admin`: +Например, в приведённом ниже коде мы вызываем `sayHi` в контексте различных объектов: `sayHi.call(user)` запускает `sayHi`, передавая `this=user`, а следующая строка устанавливает `this=admin`: ```js run function sayHi() { @@ -151,12 +150,12 @@ function sayHi() { let user = { name: "John" }; let admin = { name: "Admin" }; -// use call to pass different objects as "this" -sayHi.call( user ); // this = John -sayHi.call( admin ); // this = Admin +// используем 'call' для передачи различных объектов в качестве 'this' +sayHi.call( user ); // John +sayHi.call( admin ); // Admin ``` -And here we use `call` to call `say` with the given context and phrase: +Здесь мы используем `call` для вызова `say` с заданным контекстом и фразой: ```js run @@ -166,12 +165,12 @@ function say(phrase) { let user = { name: "John" }; -// user becomes this, and "Hello" becomes the first argument +// 'user' становится 'this', и "Hello" становится первым аргументом say.call( user, "Hello" ); // John: Hello ``` -In our case, we can use `call` in the wrapper to pass the context to the original function: +В нашем случае мы можем использовать `call` в обёртке для передачи контекста в исходную функцию: ```js run @@ -193,133 +192,59 @@ function cachingDecorator(func) { return cache.get(x); } *!* - let result = func.call(this, x); // "this" is passed correctly now + let result = func.call(this, x); // теперь 'this' передаётся правильно */!* cache.set(x, result); return result; }; } -worker.slow = cachingDecorator(worker.slow); // now make it caching +worker.slow = cachingDecorator(worker.slow); // теперь сделаем её кеширующей -alert( worker.slow(2) ); // works -alert( worker.slow(2) ); // works, doesn't call the original (cached) +alert( worker.slow(2) ); // работает +alert( worker.slow(2) ); // работает, не вызывая первоначальную функцию (кешируется) ``` -Now everything is fine. +Теперь всё в порядке. -To make it all clear, let's see more deeply how `this` is passed along: +Чтобы всё было понятно, давайте посмотрим глубже, как передаётся `this`: -1. After the decoration `worker.slow` is now the wrapper `function (x) { ... }`. -2. So when `worker.slow(2)` is executed, the wrapper gets `2` as an argument and `this=worker` (it's the object before dot). -3. Inside the wrapper, assuming the result is not yet cached, `func.call(this, x)` passes the current `this` (`=worker`) and the current argument (`=2`) to the original method. +1. После *декорации* `worker.slow` становится обёрткой `function (x) { ... }`. +2. Так что при выполнении `worker.slow(2)` обёртка получает `2` в качестве аргумента и `this=worker` (так как это объект перед точкой). +3. Внутри обёртки, если результат ещё не кеширован, `func.call(this, x)` передаёт текущий `this` (`=worker`) и текущий аргумент (`=2`) в оригинальную функцию. -## Going multi-argument with "func.apply" +## Переходим к нескольким аргументам с "func.apply" -Now let's make `cachingDecorator` even more universal. Till now it was working only with single-argument functions. +Теперь давайте сделаем `cachingDecorator` ещё более универсальным. До сих пор он работал только с функциями с одним аргументом. -Now how to cache the multi-argument `worker.slow` method? +Как же кешировать метод с несколькими аргументами `worker.slow`? ```js let worker = { slow(min, max) { - return min + max; // scary CPU-hogger is assumed + return min + max; // здесь может быть тяжёлая задача } }; -// should remember same-argument calls +// будет кешировать вызовы с одинаковыми аргументами worker.slow = cachingDecorator(worker.slow); ``` -We have two tasks to solve here. - -First is how to use both arguments `min` and `max` for the key in `cache` map. Previously, for a single argument `x` we could just `cache.set(x, result)` to save the result and `cache.get(x)` to retrieve it. But now we need to remember the result for a *combination of arguments* `(min,max)`. The native `Map` takes single value only as the key. - -There are many solutions possible: - -1. Implement a new (or use a third-party) map-like data structure that is more versatile and allows multi-keys. -2. Use nested maps: `cache.set(min)` will be a `Map` that stores the pair `(max, result)`. So we can get `result` as `cache.get(min).get(max)`. -3. Join two values into one. In our particular case we can just use a string `"min,max"` as the `Map` key. For flexibility, we can allow to provide a *hashing function* for the decorator, that knows how to make one value from many. - - -For many practical applications, the 3rd variant is good enough, so we'll stick to it. - -The second task to solve is how to pass many arguments to `func`. Currently, the wrapper `function(x)` assumes a single argument, and `func.call(this, x)` passes it. - -Here we can use another built-in method [func.apply](mdn:js/Function/apply). - -The syntax is: - -```js -func.apply(context, args) -``` - -It runs the `func` setting `this=context` and using an array-like object `args` as the list of arguments. - - -For instance, these two calls are almost the same: - -```js -func(1, 2, 3); -func.apply(context, [1, 2, 3]) -``` - -Both run `func` giving it arguments `1,2,3`. But `apply` also sets `this=context`. - -For instance, here `say` is called with `this=user` and `messageData` as a list of arguments: - -```js run -function say(time, phrase) { - alert(`[${time}] ${this.name}: ${phrase}`); -} - -let user = { name: "John" }; - -let messageData = ['10:00', 'Hello']; // become time and phrase - -*!* -// user becomes this, messageData is passed as a list of arguments (time, phrase) -say.apply(user, messageData); // [10:00] John: Hello (this=user) -*/!* -``` - -The only syntax difference between `call` and `apply` is that `call` expects a list of arguments, while `apply` takes an array-like object with them. - -We already know the spread operator `...` from the chapter that can pass an array (or any iterable) as a list of arguments. So if we use it with `call`, we can achieve almost the same as `apply`. - -These two calls are almost equivalent: - -```js -let args = [1, 2, 3]; - -*!* -func.call(context, ...args); // pass an array as list with spread operator -func.apply(context, args); // is same as using apply -*/!* -``` - -If we look more closely, there's a minor difference between such uses of `call` and `apply`. +Здесь у нас есть две задачи для решения. -- The spread operator `...` allows to pass *iterable* `args` as the list to `call`. -- The `apply` accepts only *array-like* `args`. +Во-первых, как использовать оба аргумента `min` и `max` для ключа в коллекции `cache`? Ранее для одного аргумента `x` мы могли просто сохранить результат `cache.set(x, result)` и вызвать `cache.get(x)`, чтобы получить его позже. Но теперь нам нужно запомнить результат для *комбинации аргументов* `(min,max)`. Встроенный `Map` принимает только одно значение как ключ. -So, these calls complement each other. Where we expect an iterable, `call` works, where we expect an array-like, `apply` works. +Есть много возможных решений: -And if `args` is both iterable and array-like, like a real array, then we technically could use any of them, but `apply` will probably be faster, because it's a single operation. Most JavaScript engines internally optimize it better than a pair `call + spread`. +1. Реализовать новую (или использовать стороннюю) структуру данных для коллекции, которая более универсальна, чем встроенный `Map`, и поддерживает множественные ключи. +2. Использовать вложенные коллекции: `cache.set(min)` будет `Map`, которая хранит пару `(max, result)`. Тогда получить `result` мы сможем, вызвав `cache.get(min).get(max)`. +3. Соединить два значения в одно. В нашем конкретном случае мы можем просто использовать строку `"min,max"` как ключ к `Map`. Для гибкости, мы можем позволить передавать *хеширующую функцию* в декоратор, которая знает, как сделать одно значение из многих. -One of the most important uses of `apply` is passing the call to another function, like this: - -```js -let wrapper = function() { - return anotherFunction.apply(this, arguments); -}; -``` +Для многих практических применений третий вариант достаточно хорош, поэтому мы будем придерживаться его. -That's called *call forwarding*. The `wrapper` passes everything it gets: the context `this` and arguments to `anotherFunction` and returns back its result. +Также нам понадобится заменить `func.call(this, x)` на `func.call(this, ...arguments)`, чтобы передавать все аргументы обёрнутой функции, а не только первый. -When an external code calls such `wrapper`, it is indistinguishable from the call of the original function. - -Now let's bake it all into the more powerful `cachingDecorator`: +Вот более мощный `cachingDecorator`: ```js run let worker = { @@ -340,7 +265,7 @@ function cachingDecorator(func, hash) { } *!* - let result = func.apply(this, arguments); // (**) + let result = func.call(this, ...arguments); // (**) */!* cache.set(key, result); @@ -354,21 +279,60 @@ function hash(args) { worker.slow = cachingDecorator(worker.slow, hash); -alert( worker.slow(3, 5) ); // works -alert( "Again " + worker.slow(3, 5) ); // same (cached) +alert( worker.slow(3, 5) ); // работает +alert( "Again " + worker.slow(3, 5) ); // аналогично (из кеша) +``` + +Теперь он работает с любым количеством аргументов. + +Есть два изменения: + +- В строке `(*)` вызываем `hash` для создания одного ключа из `arguments`. Здесь мы используем простую функцию "объединения", которая превращает аргументы `(3, 5)` в ключ `"3,5"`. В более сложных случаях могут потребоваться другие функции хеширования. +- Затем в строке `(**)` используем `func.call(this, ...arguments)` для передачи как контекста, так и всех аргументов, полученных обёрткой (независимо от их количества), в исходную функцию. + +Вместо `func.call(this, ...arguments)` мы могли бы написать `func.apply(this, arguments)`. + +Синтаксис встроенного метода [func.apply](mdn:js/Function/apply): + +```js +func.apply(context, args) +``` + +Он выполняет `func`, устанавливая `this=context` и принимая в качестве списка аргументов псевдомассив `args`. + +Единственная разница в синтаксисе между `call` и `apply` состоит в том, что `call` ожидает список аргументов, в то время как `apply` принимает псевдомассив. + +Эти два вызова почти эквивалентны: + +```js +func.call(context, ...args); // передаёт массив как список с оператором расширения +func.apply(context, args); // тот же эффект ``` -Now the wrapper operates with any number of arguments. +Есть только одна небольшая разница: -There are two changes: +- Оператор расширения `...` позволяет передавать *перебираемый* объект `args` в виде списка в `call`. +- А `apply` принимает только *псевдомассив* `args`. -- In the line `(*)` it calls `hash` to create a single key from `arguments`. Here we use a simple "joining" function that turns arguments `(3, 5)` into the key `"3,5"`. More complex cases may require other hashing functions. -- Then `(**)` uses `func.apply` to pass both the context and all arguments the wrapper got (no matter how many) to the original function. +Так что эти вызовы дополняют друг друга. Для перебираемых объектов сработает `call`, а где мы ожидаем псевдомассив - `apply`. +А если у нас объект, который и то, и другое, например, реальный массив, то технически мы могли бы использовать любой метод, но `apply`, вероятно, будет быстрее, потому что большинство движков JavaScript внутренне оптимизируют его лучше. -## Borrowing a method [#method-borrowing] +Передача всех аргументов вместе с контекстом другой функции называется "перенаправлением вызова" (call forwarding). -Now let's make one more minor improvement in the hashing function: +Простейший вид такого перенаправления: + +```js +let wrapper = function() { + return func.apply(this, arguments); +}; +``` + +При вызове `wrapper` из внешнего кода его не отличить от вызова исходной функции. + +## Заимствование метода [#method-borrowing] + +Теперь давайте сделаем ещё одно небольшое улучшение функции хеширования: ```js function hash(args) { @@ -376,9 +340,9 @@ function hash(args) { } ``` -As of now, it works only on two arguments. It would be better if it could glue any number of `args`. +На данный момент она работает только для двух аргументов. Было бы лучше, если бы она могла склеить любое количество `args`. -The natural solution would be to use [arr.join](mdn:js/Array/join) method: +Естественным решением было бы использовать метод [arr.join](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Array/join): ```js function hash(args) { @@ -386,21 +350,21 @@ function hash(args) { } ``` -...Unfortunately, that won't work. Because we are calling `hash(arguments)` and `arguments` object is both iterable and array-like, but not a real array. +...К сожалению, это не сработает, потому что мы вызываем `hash(arguments)`, а объект `arguments` является перебираемым и псевдомассивом, но не реальным массивом. -So calling `join` on it would fail, as we can see below: +Таким образом, вызов `join` для него потерпит неудачу, что мы и можем видеть ниже: ```js run function hash() { *!* - alert( arguments.join() ); // Error: arguments.join is not a function + alert( arguments.join() ); // Ошибка: arguments.join не является функцией */!* } hash(1, 2); ``` -Still, there's an easy way to use array join: +Тем не менее, есть простой способ использовать соединение массива: ```js run function hash() { @@ -412,48 +376,47 @@ function hash() { hash(1, 2); ``` -The trick is called *method borrowing*. +Этот трюк называется *заимствование метода*. -We take (borrow) a join method from a regular array `[].join`. And use `[].join.call` to run it in the context of `arguments`. +Мы берём (заимствуем) метод `join` из обычного массива `[].join`. И используем `[].join.call`, чтобы выполнить его в контексте `arguments`. -Why does it work? +Почему это работает? -That's because the internal algorithm of the native method `arr.join(glue)` is very simple. +Это связано с тем, что внутренний алгоритм встроенного метода `arr.join(glue)` очень прост. +Взято из спецификации практически "как есть": -Taken from the specification almost "as-is": +1. Пускай первым аргументом будет `glue` или, в случае отсутствия аргументов, им будет запятая `","` +2. Пускай `result` будет пустой строкой `""`. +3. Добавить `this[0]` к `result`. +4. Добавить `glue` и `this[1]`. +5. Добавить `glue` и `this[2]`. +6. ...выполнять до тех пор, пока `this.length` элементов не будет склеено. +7. Вернуть `result`. -1. Let `glue` be the first argument or, if no arguments, then a comma `","`. -2. Let `result` be an empty string. -3. Append `this[0]` to `result`. -4. Append `glue` and `this[1]`. -5. Append `glue` and `this[2]`. -6. ...Do so until `this.length` items are glued. -7. Return `result`. +Таким образом, технически он принимает `this` и объединяет `this[0]`, `this[1]`... и т.д. вместе. Он намеренно написан так, что допускает любой псевдомассив `this` (не случайно, многие методы следуют этой практике). Вот почему он также работает с `this=arguments`. -So, technically it takes `this` and joins `this[0]`, `this[1]` ...etc together. It's intentionally written in a way that allows any array-like `this` (not a coincidence, many methods follow this practice). That's why it also works with `this=arguments`. +## Итого -## Summary +*Декоратор* - это обёртка вокруг функции, которая изменяет поведение последней. Основная работа по-прежнему выполняется функцией. -*Decorator* is a wrapper around a function that alters its behavior. The main job is still carried out by the function. +Обычно безопасно заменить функцию или метод декорированным, за исключением одной мелочи. Если исходная функция предоставляет свойства, такие как `func.calledCount` или типа того, то декорированная функция их не предоставит. Потому что это обёртка. Так что нужно быть осторожным в их использовании. Некоторые декораторы предоставляют свои собственные свойства. -It is generally safe to replace a function or a method with a decorated one, except for one little thing. If the original function had properties on it, like `func.calledCount` or whatever, then the decorated one will not provide them. Because that is a wrapper. So one needs to be careful if one uses them. Some decorators provide their own properties. +Декораторы можно рассматривать как "дополнительные возможности" или "аспекты", которые можно добавить в функцию. Мы можем добавить один или несколько декораторов. И всё это без изменения кода оригинальной функции! -Decorators can be seen as "features" or "aspects" that can be added to a function. We can add one or add many. And all this without changing its code! +Для реализации `cachingDecorator` мы изучили методы: -To implement `cachingDecorator`, we studied methods: +- [func.call(context, arg1, arg2...)](mdn:js/Function/call) -- вызывает `func` с данным контекстом и аргументами. +- [func.apply(context, args)](mdn:js/Function/apply) -- вызывает `func`, передавая `context` как `this` и псевдомассив `args` как список аргументов. -- [func.call(context, arg1, arg2...)](mdn:js/Function/call) -- calls `func` with given context and arguments. -- [func.apply(context, args)](mdn:js/Function/apply) -- calls `func` passing `context` as `this` and array-like `args` into a list of arguments. - -The generic *call forwarding* is usually done with `apply`: +В основном *переадресация вызова* выполняется с помощью `apply`: ```js -let wrapper = function() { +let wrapper = function(original, arguments) { return original.apply(this, arguments); -} +}; ``` -We also saw an example of *method borrowing* when we take a method from an object and `call` it in the context of another object. It is quite common to take array methods and apply them to arguments. The alternative is to use rest parameters object that is a real array. +Мы также рассмотрели пример *заимствования метода*, когда мы вызываем метод у объекта в контексте другого объекта. Весьма распространено заимствовать методы массива и применять их к `arguments`. В качестве альтернативы можно использовать объект с остаточными параметрами `...args`, который является реальным массивом. -There are many decorators there in the wild. Check how well you got them by solving the tasks of this chapter. +На практике декораторы используются для самых разных задач. Проверьте, насколько хорошо вы их освоили, решая задачи этой главы. diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/decorator-makecaching-wrapper.png b/1-js/06-advanced-functions/09-call-apply-decorators/decorator-makecaching-wrapper.png deleted file mode 100644 index d9541c7681..0000000000 Binary files a/1-js/06-advanced-functions/09-call-apply-decorators/decorator-makecaching-wrapper.png and /dev/null differ 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 new file mode 100644 index 0000000000..3d8428b273 --- /dev/null +++ b/1-js/06-advanced-functions/09-call-apply-decorators/decorator-makecaching-wrapper.svg @@ -0,0 +1 @@ +обёрткавокруг функции \ No newline at end of file diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/decorator-makecaching-wrapper@2x.png b/1-js/06-advanced-functions/09-call-apply-decorators/decorator-makecaching-wrapper@2x.png deleted file mode 100644 index 17eef3f18c..0000000000 Binary files a/1-js/06-advanced-functions/09-call-apply-decorators/decorator-makecaching-wrapper@2x.png and /dev/null differ diff --git a/1-js/06-advanced-functions/10-bind/2-write-to-object-after-bind/solution.md b/1-js/06-advanced-functions/10-bind/2-write-to-object-after-bind/solution.md index 737a14481f..b3f2827dd9 100644 --- a/1-js/06-advanced-functions/10-bind/2-write-to-object-after-bind/solution.md +++ b/1-js/06-advanced-functions/10-bind/2-write-to-object-after-bind/solution.md @@ -1,4 +1,4 @@ -The answer: `null`. +Ответ: `null`. ```js run @@ -13,6 +13,10 @@ let user = { user.g(); ``` -The context of a bound function is hard-fixed. There's just no way to further change it. +Контекст связанной функции жёстко фиксирован. Изменить однажды привязанный контекст уже нельзя. -So even while we run `user.g()`, the original function is called with `this=null`. +Так что хоть мы и вызываем `user.g()`, внутри исходная функция будет вызвана с `this=null`. +Однако, функции `g` совершенно без разницы, какой `this` она получила. +Её единственное предназначение – это передать вызов в `f` вместе с аргументами и ранее указанным контекстом `null`, что она и делает. + +Таким образом, когда мы запускаем `user.g()`, исходная функция вызывается с `this=null`. diff --git a/1-js/06-advanced-functions/10-bind/2-write-to-object-after-bind/task.md b/1-js/06-advanced-functions/10-bind/2-write-to-object-after-bind/task.md index 6d7e1fb245..e1ee9aded4 100644 --- a/1-js/06-advanced-functions/10-bind/2-write-to-object-after-bind/task.md +++ b/1-js/06-advanced-functions/10-bind/2-write-to-object-after-bind/task.md @@ -2,9 +2,9 @@ importance: 5 --- -# Bound function as a method +# Связанная функция как метод -What will be the output? +Что выведет функция? ```js function f() { diff --git a/1-js/06-advanced-functions/10-bind/3-second-bind/solution.md b/1-js/06-advanced-functions/10-bind/3-second-bind/solution.md index 97e1c28098..83dcf953ff 100644 --- a/1-js/06-advanced-functions/10-bind/3-second-bind/solution.md +++ b/1-js/06-advanced-functions/10-bind/3-second-bind/solution.md @@ -1,15 +1,17 @@ -The answer: **John**. +Ответ: **Вася**. ```js run no-beautify function f() { alert(this.name); } -f = f.bind( {name: "John"} ).bind( {name: "Pete"} ); +f = f.bind( {name: "Вася"} ).bind( {name: "Петя"} ); -f(); // John +f(); // Вася ``` -The exotic [bound function](https://tc39.github.io/ecma262/#sec-bound-function-exotic-objects) object returned by `f.bind(...)` remembers the context (and arguments if provided) only at creation time. +Экзотический объект [bound function](https://tc39.github.io/ecma262/#sec-bound-function-exotic-objects), возвращаемый при первом вызове `f.bind(...)`, запоминает контекст (и аргументы, если они были переданы) только во время создания. -A function cannot be re-bound. +Следующий вызов `bind` будет устанавливать контекст уже для этого объекта. Это ни на что не повлияет. + +Можно сделать новую привязку, но нельзя изменить существующую. diff --git a/1-js/06-advanced-functions/10-bind/3-second-bind/task.md b/1-js/06-advanced-functions/10-bind/3-second-bind/task.md index 5daf053c65..0b9802b1a2 100644 --- a/1-js/06-advanced-functions/10-bind/3-second-bind/task.md +++ b/1-js/06-advanced-functions/10-bind/3-second-bind/task.md @@ -2,18 +2,18 @@ importance: 5 --- -# Second bind +# Повторный bind -Can we change `this` by additional binding? +Можем ли мы изменить `this` дополнительным связыванием? -What will be the output? +Что выведет этот код? ```js no-beautify function f() { alert(this.name); } -f = f.bind( {name: "John"} ).bind( {name: "Ann" } ); +f = f.bind( {name: "Вася"} ).bind( {name: "Петя" } ); f(); ``` diff --git a/1-js/06-advanced-functions/10-bind/4-function-property-after-bind/solution.md b/1-js/06-advanced-functions/10-bind/4-function-property-after-bind/solution.md index 181555d95f..646f18ea52 100644 --- a/1-js/06-advanced-functions/10-bind/4-function-property-after-bind/solution.md +++ b/1-js/06-advanced-functions/10-bind/4-function-property-after-bind/solution.md @@ -1,4 +1,4 @@ -The answer: `undefined`. +Ответ: `undefined`. -The result of `bind` is another object. It does not have the `test` property. +Результатом работы `bind` является другой объект. У него уже нет свойства `test`. diff --git a/1-js/06-advanced-functions/10-bind/4-function-property-after-bind/task.md b/1-js/06-advanced-functions/10-bind/4-function-property-after-bind/task.md index 8cd18ec56c..dca617ca54 100644 --- a/1-js/06-advanced-functions/10-bind/4-function-property-after-bind/task.md +++ b/1-js/06-advanced-functions/10-bind/4-function-property-after-bind/task.md @@ -2,9 +2,9 @@ importance: 5 --- -# Function property after bind +# Свойство функции после bind -There's a value in the property of a function. Will it change after `bind`? Why, elaborate? +В свойство функции записано значение. Изменится ли оно после применения `bind`? Обоснуйте ответ. ```js run function sayHi() { @@ -14,10 +14,10 @@ sayHi.test = 5; *!* let bound = sayHi.bind({ - name: "John" + name: "Вася" }); -alert( bound.test ); // what will be the output? why? +alert( bound.test ); // что выведет? почему? */!* ``` diff --git a/1-js/06-advanced-functions/10-bind/5-question-use-bind/solution.md b/1-js/06-advanced-functions/10-bind/5-question-use-bind/solution.md index 0cb673b12b..b82cfd0cd2 100644 --- a/1-js/06-advanced-functions/10-bind/5-question-use-bind/solution.md +++ b/1-js/06-advanced-functions/10-bind/5-question-use-bind/solution.md @@ -1,9 +1,9 @@ -The error occurs because `ask` gets functions `loginOk/loginFail` without the object. +Ошибка происходит потому, что `askPassword` получает функции `loginOk/loginFail` без контекста. -When it calls them, they naturally assume `this=undefined`. +Когда они вызываются, то, естественно, `this=undefined`. -Let's `bind` the context: +Используем `bind`, чтобы передать в `askPassword` функции `loginOk/loginFail` с уже привязанным контекстом: ```js run function askPassword(ok, fail) { @@ -13,7 +13,7 @@ function askPassword(ok, fail) { } let user = { - name: 'John', + name: 'Вася', loginOk() { alert(`${this.name} logged in`); @@ -30,14 +30,12 @@ askPassword(user.loginOk.bind(user), user.loginFail.bind(user)); */!* ``` -Now it works. +Теперь всё работает корректно. -An alternative solution could be: +Альтернативное решение - сделать функции-обёртки над `user.loginOk/loginFail`: ```js //... askPassword(() => user.loginOk(), () => user.loginFail()); ``` -Usually that also works, but may fail in more complex situations where `user` has a chance of being overwritten between the moments of asking and running `() => user.loginOk()`. - - +Обычно это также работает и хорошо выглядит. Но может не сработать в более сложных ситуациях, а именно - когда значение переменной `user` меняется между вызовом `askPassword` и выполнением `() => user.loginOk()`. diff --git a/1-js/06-advanced-functions/10-bind/5-question-use-bind/task.md b/1-js/06-advanced-functions/10-bind/5-question-use-bind/task.md index eb19e6644d..57dc344d96 100644 --- a/1-js/06-advanced-functions/10-bind/5-question-use-bind/task.md +++ b/1-js/06-advanced-functions/10-bind/5-question-use-bind/task.md @@ -2,13 +2,13 @@ importance: 5 --- -# Ask losing this +# Исправьте функцию, теряющую "this" -The call to `askPassword()` in the code below should check the password and then call `user.loginOk/loginFail` depending on the answer. +Вызов `askPassword()` в приведённом ниже коде должен проверить пароль и затем вызвать `user.loginOk/loginFail` в зависимости от ответа. -But it leads to an error. Why? +Однако, его вызов приводит к ошибке. Почему? -Fix the highlighted line for everything to start working right (other lines are not to be changed). +Исправьте выделенную строку, чтобы всё работало (других строк изменять не надо). ```js run function askPassword(ok, fail) { @@ -18,7 +18,7 @@ function askPassword(ok, fail) { } let user = { - name: 'John', + name: 'Вася', loginOk() { alert(`${this.name} logged in`); @@ -34,5 +34,3 @@ let user = { askPassword(user.loginOk, user.loginFail); */!* ``` - - diff --git a/1-js/06-advanced-functions/10-bind/6-ask-partial/solution.md b/1-js/06-advanced-functions/10-bind/6-ask-partial/solution.md new file mode 100644 index 0000000000..08cadcaab6 --- /dev/null +++ b/1-js/06-advanced-functions/10-bind/6-ask-partial/solution.md @@ -0,0 +1,16 @@ + + +1. Можно использовать стрелочную функцию-обёртку: + + ```js + askPassword(() => user.login(true), () => user.login(false)); + ``` + + Теперь она получает `user` извне и нормально выполняется. + +2. Или же можно создать частично применённую функцию на основе `user.login`, которая использует объект `user` в качестве контекста и получает соответствующий первый аргумент: + + + ```js + askPassword(user.login.bind(user, true), user.login.bind(user, false)); + ``` diff --git a/1-js/06-advanced-functions/10-bind/6-ask-partial/task.md b/1-js/06-advanced-functions/10-bind/6-ask-partial/task.md new file mode 100644 index 0000000000..7a1ce20c8b --- /dev/null +++ b/1-js/06-advanced-functions/10-bind/6-ask-partial/task.md @@ -0,0 +1,33 @@ +importance: 5 + +--- + +# Использование частично применённой функции для логина + +Это задание является немного усложнённым вариантом одного из предыдущих -- . + +Объект `user` был изменён. Теперь вместо двух функций `loginOk/loginFail` у него есть только одна -- `user.login(true/false)`. + +Что нужно передать в вызов функции `askPassword` в коде ниже, чтобы она могла вызывать функцию `user.login(true)` как `ok` и функцию `user.login(false)` как `fail`? + +```js +function askPassword(ok, fail) { + let password = prompt("Password?", ''); + if (password == "rockstar") ok(); + else fail(); +} + +let user = { + name: 'John', + + login(result) { + alert( this.name + (result ? ' logged in' : ' failed to log in') ); + } +}; + +*!* +askPassword(?, ?); // ? +*/!* +``` + +Ваши изменения должны затрагивать только выделенный фрагмент кода. diff --git a/1-js/06-advanced-functions/10-bind/article.md b/1-js/06-advanced-functions/10-bind/article.md index 06e2000ff1..1c597cdf12 100644 --- a/1-js/06-advanced-functions/10-bind/article.md +++ b/1-js/06-advanced-functions/10-bind/article.md @@ -3,114 +3,113 @@ libs: --- -# Function binding +# Привязка контекста к функции -When using `setTimeout` with object methods or passing object methods along, there's a known problem: "losing `this`". +При передаче методов объекта в качестве колбэков, например для `setTimeout`, возникает известная проблема - потеря `this`. -Suddenly, `this` just stops working right. The situation is typical for novice developers, but happens with experienced ones as well. +В этой главе мы посмотрим, как её можно решить. -## Losing "this" +## Потеря "this" -We already know that in JavaScript it's easy to lose `this`. Once a method is passed somewhere separately from the object -- `this` is lost. +Мы уже видели примеры потери `this`. Как только метод передаётся отдельно от объекта - `this` теряется. -Here's how it may happen with `setTimeout`: +Вот как это может произойти в случае с `setTimeout`: ```js run let user = { - firstName: "John", + firstName: "Вася", sayHi() { - alert(`Hello, ${this.firstName}!`); + alert(`Привет, ${this.firstName}!`); } }; *!* -setTimeout(user.sayHi, 1000); // Hello, undefined! +setTimeout(user.sayHi, 1000); // Привет, undefined! */!* ``` -As we can see, the output shows not "John" as `this.firstName`, but `undefined`! +При запуске этого кода мы видим, что вызов `this.firstName` возвращает не "Вася", а `undefined`! -That's because `setTimeout` got the function `user.sayHi`, separately from the object. The last line can be rewritten as: +Это произошло потому, что `setTimeout` получил функцию `sayHi` отдельно от объекта `user` (именно здесь функция и потеряла контекст). То есть последняя строка может быть переписана как: ```js let f = user.sayHi; -setTimeout(f, 1000); // lost user context +setTimeout(f, 1000); // контекст user потеряли ``` -The method `setTimeout` in-browser is a little special: it sets `this=window` for the function call (for Node.js, `this` becomes the timer object, but doesn't really matter here). So for `this.firstName` it tries to get `window.firstName`, which does not exist. In other similar cases as we'll see, usually `this` just becomes `undefined`. +Метод `setTimeout` в браузере имеет особенность: он устанавливает `this=window` для вызова функции (в Node.js `this` становится объектом таймера, но здесь это не имеет значения). Таким образом, для `this.firstName` он пытается получить `window.firstName`, которого не существует. В других подобных случаях `this` обычно просто становится `undefined`. -The task is quite typical -- we want to pass an object method somewhere else (here -- to the scheduler) where it will be called. How to make sure that it will be called in the right context? +Задача довольно типичная - мы хотим передать метод объекта куда-то ещё (в этом конкретном случае - в планировщик), где он будет вызван. Как бы сделать так, чтобы он вызывался в правильном контексте? -## Solution 1: a wrapper +## Решение 1: сделать функцию-обёртку -The simplest solution is to use a wrapping function: +Самый простой вариант решения – это обернуть вызов в анонимную функцию, создав замыкание: ```js run let user = { - firstName: "John", + firstName: "Вася", sayHi() { - alert(`Hello, ${this.firstName}!`); + alert(`Привет, ${this.firstName}!`); } }; *!* setTimeout(function() { - user.sayHi(); // Hello, John! + user.sayHi(); // Привет, Вася! }, 1000); */!* ``` -Now it works, because it receives `user` from the outer lexical environment, and then calls the method normally. +Теперь код работает корректно, так как объект `user` достаётся из замыкания, а затем вызывается его метод `sayHi`. -The same, but shorter: +То же самое, только короче: ```js -setTimeout(() => user.sayHi(), 1000); // Hello, John! +setTimeout(() => user.sayHi(), 1000); // Привет, Вася! ``` -Looks fine, but a slight vulnerability appears in our code structure. - -What if before `setTimeout` triggers (there's one second delay!) `user` changes value? Then, suddenly, it will call the wrong object! +Выглядит хорошо, но теперь в нашем коде появилась небольшая уязвимость. +Что произойдёт, если до момента срабатывания `setTimeout` (ведь задержка составляет целую секунду!) в переменную `user` будет записано другое значение? Тогда вызов неожиданно будет совсем не тот! ```js run let user = { - firstName: "John", + firstName: "Вася", sayHi() { - alert(`Hello, ${this.firstName}!`); + alert(`Привет, ${this.firstName}!`); } }; setTimeout(() => user.sayHi(), 1000); -// ...within 1 second -user = { sayHi() { alert("Another user in setTimeout!"); } }; +// ...в течение 1 секунды +user = { sayHi() { alert("Другой пользователь в 'setTimeout'!"); } }; -// Another user in setTimeout?!? +// Другой пользователь в 'setTimeout'! ``` -The next solution guarantees that such thing won't happen. +Следующее решение гарантирует, что такого не случится. -## Solution 2: bind +## Решение 2: привязать контекст с помощью bind -Functions provide a built-in method [bind](mdn:js/Function/bind) that allows to fix `this`. +В современном JavaScript у функций есть встроенный метод [bind](mdn:js/Function/bind), который позволяет зафиксировать `this`. -The basic syntax is: +Базовый синтаксис `bind`: ```js -// more complex syntax will be little later +// полный синтаксис будет представлен немного позже let boundFunc = func.bind(context); ``` -The result of `func.bind(context)` is a special function-like "exotic object", that is callable as function and transparently passes the call to `func` setting `this=context`. +Результатом вызова `func.bind(context)` является особый "экзотический объект" (термин взят из спецификации), который вызывается как функция и прозрачно передаёт вызов в `func`, при этом устанавливая `this=context`. -In other words, calling `boundFunc` is like `func` with fixed `this`. +Другими словами, вызов `boundFunc` подобен вызову `func` с фиксированным `this`. -For instance, here `funcUser` passes a call to `func` with `this=user`: +Например, здесь `funcUser` передаёт вызов в `func`, фиксируя `this=user`: ```js run let user = { - firstName: "John" + firstName: "Вася" }; function func() { @@ -119,39 +118,38 @@ function func() { *!* let funcUser = func.bind(user); -funcUser(); // John +funcUser(); // Вася */!* ``` -Here `func.bind(user)` as a "bound variant" of `func`, with fixed `this=user`. +Здесь `func.bind(user)` - это "связанный вариант" `func`, с фиксированным `this=user`. -All arguments are passed to the original `func` "as is", for instance: +Все аргументы передаются исходному методу `func` как есть, например: ```js run let user = { - firstName: "John" + firstName: "Вася" }; function func(phrase) { alert(phrase + ', ' + this.firstName); } -// bind this to user +// привязка this к user let funcUser = func.bind(user); *!* -funcUser("Hello"); // Hello, John (argument "Hello" is passed, and this=user) +funcUser("Привет"); // Привет, Вася (аргумент "Привет" передан, при этом this = user) */!* ``` -Now let's try with an object method: - +Теперь давайте попробуем с методом объекта: ```js run let user = { - firstName: "John", + firstName: "Вася", sayHi() { - alert(`Hello, ${this.firstName}!`); + alert(`Привет, ${this.firstName}!`); } }; @@ -159,18 +157,18 @@ let user = { let sayHi = user.sayHi.bind(user); // (*) */!* -sayHi(); // Hello, John! +sayHi(); // Привет, Вася! -setTimeout(sayHi, 1000); // Hello, John! +setTimeout(sayHi, 1000); // Привет, Вася! ``` -In the line `(*)` we take the method `user.sayHi` and bind it to `user`. The `sayHi` is a "bound" function, that can be called alone or passed to `setTimeout` -- doesn't matter, the context will be right. +В строке `(*)` мы берём метод `user.sayHi` и привязываем его к `user`. Теперь `sayHi` - это "связанная" функция, которая может быть вызвана отдельно или передана в `setTimeout` (контекст всегда будет правильным). -Here we can see that arguments are passed "as is", only `this` is fixed by `bind`: +Здесь мы можем увидеть, что `bind` исправляет только `this`, а аргументы передаются как есть: ```js run let user = { - firstName: "John", + firstName: "Вася", say(phrase) { alert(`${phrase}, ${this.firstName}!`); } @@ -178,12 +176,12 @@ let user = { let say = user.say.bind(user); -say("Hello"); // Hello, John ("Hello" argument is passed to say) -say("Bye"); // Bye, John ("Bye" is passed to say) +say("Привет"); // Привет, Вася (аргумент "Привет" передан в функцию "say") +say("Пока"); // Пока, Вася (аргумент "Пока" передан в функцию "say") ``` -````smart header="Convenience method: `bindAll`" -If an object has many methods and we plan to actively pass it around, then we could bind them all in a loop: +````smart header="Удобный метод: `bindAll`" +Если у объекта много методов и мы планируем их активно передавать, то можно привязать контекст для них всех в цикле: ```js for (let key in user) { @@ -193,11 +191,126 @@ for (let key in user) { } ``` -JavaScript libraries also provide functions for convenient mass binding , e.g. [_.bindAll(obj)](http://lodash.com/docs#bindAll) in lodash. +Некоторые JS-библиотеки предоставляют встроенные функции для удобной массовой привязки контекста, например [_.bindAll(obj)](https://lodash.com/docs#bindAll) в lodash. ```` -## Summary +## Частичное применение + +До сих пор мы говорили только о привязывании `this`. Давайте шагнём дальше. + +Мы можем привязать не только `this`, но и аргументы. Это делается редко, но иногда может быть полезно. + +Полный синтаксис `bind`: + +```js +let bound = func.bind(context, [arg1], [arg2], ...); +``` +Это позволяет привязать контекст `this` и начальные аргументы функции. + +Например, у нас есть функция умножения `mul(a, b)`: + +```js +function mul(a, b) { + return a * b; +} +``` + +Давайте воспользуемся `bind`, чтобы создать функцию `double` на её основе: + +```js run +function mul(a, b) { + return a * b; +} + +*!* +let double = mul.bind(null, 2); +*/!* + +alert( double(3) ); // = mul(2, 3) = 6 +alert( double(4) ); // = mul(2, 4) = 8 +alert( double(5) ); // = mul(2, 5) = 10 +``` + +Вызов `mul.bind(null, 2)` создаёт новую функцию `double`, которая передаёт вызов `mul`, фиксируя `null` как контекст, и `2` -- как первый аргумент. Следующие аргументы передаются как есть. + +Это называется [частичное применение](https://ru.wikipedia.org/wiki/Частичное_применение) -- мы создаём новую функцию, фиксируя некоторые из существующих параметров. + +Обратите внимание, что в данном случае мы на самом деле не используем `this`. Но для `bind` это обязательный параметр, так что мы должны передать туда что-нибудь вроде `null`. + +В следующем коде функция `triple` умножает значение на три: + +```js run +function mul(a, b) { + return a * b; +} + +*!* +let triple = mul.bind(null, 3); +*/!* + +alert( triple(3) ); // = mul(3, 3) = 9 +alert( triple(4) ); // = mul(3, 4) = 12 +alert( triple(5) ); // = mul(3, 5) = 15 +``` + +Для чего мы обычно создаём частично применённую функцию? + +Польза от этого в том, что возможно создать независимую функцию с понятным названием (`double`, `triple`). Мы можем использовать её и не передавать каждый раз первый аргумент, т.к. он зафиксирован с помощью `bind`. + +В других случаях частичное применение полезно, когда у нас есть очень общая функция и для удобства мы хотим создать её более специализированный вариант. + +Например, у нас есть функция `send(from, to, text)`. Потом внутри объекта `user` мы можем захотеть использовать её частный вариант: `sendTo(to, text)`, который отправляет текст от имени текущего пользователя. + +## Частичное применение без контекста + +Что если мы хотим зафиксировать некоторые аргументы, но не контекст `this`? Например, для метода объекта. + +Встроенный `bind` не позволяет этого. Мы не можем просто опустить контекст и перейти к аргументам. + +К счастью, легко создать вспомогательную функцию `partial`, которая привязывает только аргументы. + +Вот так: + +```js run +*!* +function partial(func, ...argsBound) { + return function(...args) { // (*) + return func.call(this, ...argsBound, ...args); + } +} +*/!* + +// использование: +let user = { + firstName: "John", + say(time, phrase) { + alert(`[${time}] ${this.firstName}: ${phrase}!`); + } +}; + +// добавляем частично применённый метод с фиксированным временем +user.sayNow = partial(user.say, new Date().getHours() + ':' + new Date().getMinutes()); + +user.sayNow("Hello"); +// Что-то вроде этого: +// [10:00] John: Hello! +``` + +Результатом вызова `partial(func[, arg1, arg2...])` будет обёртка `(*)`, которая вызывает `func` с: +- Тем же `this`, который она получает (для вызова `user.sayNow` -- это будет `user`) +- Затем передаёт ей `...argsBound` -- аргументы из вызова `partial` (`"10:00"`) +- Затем передаёт ей `...args` -- аргументы, полученные обёрткой (`"Hello"`) + +Благодаря оператору расширения `...` реализовать это очень легко, не правда ли? + +Также есть готовый вариант [_.partial](https://lodash.com/docs#partial) из библиотеки lodash. + +## Итого + +Метод `bind` возвращает "привязанный вариант" функции `func`, фиксируя контекст `this` и первые аргументы `arg1`, `arg2`..., если они заданы. + +Обычно `bind` применяется для фиксации `this` в методе объекта, чтобы передать его в качестве колбэка. Например, для `setTimeout`. -Method `func.bind(context, ...args)` returns a "bound variant" of function `func` that fixes the context `this` and first arguments if given. +Когда мы привязываем аргументы, такая функция называется "частично применённой" или "частичной". -Usually we apply `bind` to fix `this` in an object method, so that we can pass it somewhere. For example, to `setTimeout`. There are more reasons to `bind` in the modern development, we'll meet them later. +Частичное применение удобно, когда мы не хотим повторять один и тот же аргумент много раз. Например, если у нас есть функция `send(from, to)` и `from` всё время будет одинаков для нашей задачи, то мы можем создать частично применённую функцию и дальше работать с ней. diff --git a/1-js/06-advanced-functions/11-currying-partials/1-ask-currying/solution.md b/1-js/06-advanced-functions/11-currying-partials/1-ask-currying/solution.md deleted file mode 100644 index 3284c943b0..0000000000 --- a/1-js/06-advanced-functions/11-currying-partials/1-ask-currying/solution.md +++ /dev/null @@ -1,16 +0,0 @@ - - -1. Either use a wrapper function, an arrow to be concise: - - ```js - askPassword(() => user.login(true), () => user.login(false)); - ``` - - Now it gets `user` from outer variables and runs it the normal way. - -2. Or create a partial function from `user.login` that uses `user` as the context and has the correct first argument: - - - ```js - askPassword(user.login.bind(user, true), user.login.bind(user, false)); - ``` diff --git a/1-js/06-advanced-functions/11-currying-partials/1-ask-currying/task.md b/1-js/06-advanced-functions/11-currying-partials/1-ask-currying/task.md deleted file mode 100644 index f8b83d7a20..0000000000 --- a/1-js/06-advanced-functions/11-currying-partials/1-ask-currying/task.md +++ /dev/null @@ -1,34 +0,0 @@ -importance: 5 - ---- - -# Partial application for login - -The task is a little more complex variant of . - -The `user` object was modified. Now instead of two functions `loginOk/loginFail`, it has a single function `user.login(true/false)`. - -What to pass `askPassword` in the code below, so that it calls `user.login(true)` as `ok` and `user.login(false)` as `fail`? - -```js -function askPassword(ok, fail) { - let password = prompt("Password?", ''); - if (password == "rockstar") ok(); - else fail(); -} - -let user = { - name: 'John', - - login(result) { - alert( this.name + (result ? ' logged in' : ' failed to log in') ); - } -}; - -*!* -askPassword(?, ?); // ? -*/!* -``` - -Your changes should only modify the highlighted fragment. - diff --git a/1-js/06-advanced-functions/11-currying-partials/article.md b/1-js/06-advanced-functions/11-currying-partials/article.md deleted file mode 100644 index b97e6a5fce..0000000000 --- a/1-js/06-advanced-functions/11-currying-partials/article.md +++ /dev/null @@ -1,309 +0,0 @@ -libs: - - lodash - ---- - -# Currying and partials - -Until now we have only been talking about binding `this`. Let's take it a step further. - -We can bind not only `this`, but also arguments. That's rarely done, but sometimes can be handy. - -The full syntax of `bind`: - -```js -let bound = func.bind(context, arg1, arg2, ...); -``` - -It allows to bind context as `this` and starting arguments of the function. - -For instance, we have a multiplication function `mul(a, b)`: - -```js -function mul(a, b) { - return a * b; -} -``` - -Let's use `bind` to create a function `double` on its base: - -```js run -function mul(a, b) { - return a * b; -} - -*!* -let double = mul.bind(null, 2); -*/!* - -alert( double(3) ); // = mul(2, 3) = 6 -alert( double(4) ); // = mul(2, 4) = 8 -alert( double(5) ); // = mul(2, 5) = 10 -``` - -The call to `mul.bind(null, 2)` creates a new function `double` that passes calls to `mul`, fixing `null` as the context and `2` as the first argument. Further arguments are passed "as is". - -That's called [partial function application](https://en.wikipedia.org/wiki/Partial_application) -- we create a new function by fixing some parameters of the existing one. - -Please note that here we actually don't use `this` here. But `bind` requires it, so we must put in something like `null`. - -The function `triple` in the code below triples the value: - -```js run -function mul(a, b) { - return a * b; -} - -*!* -let triple = mul.bind(null, 3); -*/!* - -alert( triple(3) ); // = mul(3, 3) = 9 -alert( triple(4) ); // = mul(3, 4) = 12 -alert( triple(5) ); // = mul(3, 5) = 15 -``` - -Why do we usually make a partial function? - -The benefit is that we can create an independent function with a readable name (`double`, `triple`). We can use it and not provide first argument of every time as it's fixed with `bind`. - -In other cases, partial application is useful when we have a very generic function and want a less universal variant of it for convenience. - -For instance, we have a function `send(from, to, text)`. Then, inside a `user` object we may want to use a partial variant of it: `sendTo(to, text)` that sends from the current user. - -## Going partial without context - -What if we'd like to fix some arguments, but not bind `this`? - -The native `bind` does not allow that. We can't just omit the context and jump to arguments. - -Fortunately, a `partial` function for binding only arguments can be easily implemented. - -Like this: - -```js run -*!* -function partial(func, ...argsBound) { - return function(...args) { // (*) - return func.call(this, ...argsBound, ...args); - } -} -*/!* - -// Usage: -let user = { - firstName: "John", - say(time, phrase) { - alert(`[${time}] ${this.firstName}: ${phrase}!`); - } -}; - -// add a partial method that says something now by fixing the first argument -user.sayNow = partial(user.say, new Date().getHours() + ':' + new Date().getMinutes()); - -user.sayNow("Hello"); -// Something like: -// [10:00] John: Hello! -``` - -The result of `partial(func[, arg1, arg2...])` call is a wrapper `(*)` that calls `func` with: -- Same `this` as it gets (for `user.sayNow` call it's `user`) -- Then gives it `...argsBound` -- arguments from the `partial` call (`"10:00"`) -- Then gives it `...args` -- arguments given to the wrapper (`"Hello"`) - -So easy to do it with the spread operator, right? - -Also there's a ready [_.partial](https://lodash.com/docs#partial) implementation from lodash library. - -## Currying - -Sometimes people mix up partial function application mentioned above with another thing named "currying". That's another interesting technique of working with functions that we just have to mention here. - -[Currying](https://en.wikipedia.org/wiki/Currying) is a transformation of functions that translates a function from callable as `f(a, b, c)` into callable as `f(a)(b)(c)`. In JavaScript, we usually make a wrapper to keep the original function. - -Currying doesn't call a function. It just transforms it. - -Let's create a helper `curry(f)` function that performs currying for a two-argument `f`. In other words, `curry(f)` for two-argument `f(a, b)` translates it into `f(a)(b)` - -```js run -*!* -function curry(f) { // curry(f) does the currying transform - return function(a) { - return function(b) { - return f(a, b); - }; - }; -} -*/!* - -// usage -function sum(a, b) { - return a + b; -} - -let carriedSum = curry(sum); - -alert( carriedSum(1)(2) ); // 3 -``` - -As you can see, the implementation is a series of wrappers. - -- The result of `curry(func)` is a wrapper `function(a)`. -- When it is called like `sum(1)`, the argument is saved in the Lexical Environment, and a new wrapper is returned `function(b)`. -- Then `sum(1)(2)` finally calls `function(b)` providing `2`, and it passes the call to the original multi-argument `sum`. - -More advanced implementations of currying like [_.curry](https://lodash.com/docs#curry) from lodash library do something more sophisticated. They return a wrapper that allows a function to be called normally when all arguments are supplied *or* returns a partial otherwise. - -```js -function curry(f) { - return function(...args) { - // if args.length == f.length (as many arguments as f has), - // then pass the call to f - // otherwise return a partial function that fixes args as first arguments - }; -} -``` - -## Currying? What for? - -To understand the benefits we definitely need a worthy real-life example. - -Advanced currying allows the function to be both callable normally and partially. - -For instance, we have the logging function `log(date, importance, message)` that formats and outputs the information. In real projects such functions also have many other useful features like sending logs over the network: - -```js -function log(date, importance, message) { - alert(`[${date.getHours()}:${date.getMinutes()}] [${importance}] ${message}`); -} -``` - -Let's curry it! - -```js -log = _.curry(log); -``` - -After that `log` still works the normal way: - -```js -log(new Date(), "DEBUG", "some debug"); -``` - -...But also can be called in the curried form: - -```js -log(new Date())("DEBUG")("some debug"); // log(a)(b)(c) -``` - -Let's get a convenience function for today's logs: - -```js -// todayLog will be the partial of log with fixed first argument -let todayLog = log(new Date()); - -// use it -todayLog("INFO", "message"); // [HH:mm] INFO message -``` - -And now a convenience function for today's debug messages: - -```js -let todayDebug = todayLog("DEBUG"); - -todayDebug("message"); // [HH:mm] DEBUG message -``` - -So: -1. We didn't lose anything after currying: `log` is still callable normally. -2. We were able to generate partial functions such as for today's logs. - -## Advanced curry implementation - -In case you'd like to get in details (not obligatory!), here's the "advanced" curry implementation that we could use above. - -It's pretty short: - -```js -function curry(func) { - - return function curried(...args) { - if (args.length >= func.length) { - return func.apply(this, args); - } else { - return function(...args2) { - return curried.apply(this, args.concat(args2)); - } - } - }; - -} -``` - -Usage examples: - -```js -function sum(a, b, c) { - return a + b + c; -} - -let curriedSum = curry(sum); - -alert( curriedSum(1, 2, 3) ); // 6, still callable normally -alert( curriedSum(1)(2,3) ); // 6, currying of 1st arg -alert( curriedSum(1)(2)(3) ); // 6, full currying -``` - -The new `curry` may look complicated, but it's actually easy to understand. - -The result of `curry(func)` is the wrapper `curried` that looks like this: - -```js -// func is the function to transform -function curried(...args) { - if (args.length >= func.length) { // (1) - return func.apply(this, args); - } else { - return function pass(...args2) { // (2) - return curried.apply(this, args.concat(args2)); - } - } -}; -``` - -When we run it, there are two branches: - -1. Call now: if passed `args` count is the same as the original function has in its definition (`func.length`) or longer, then just pass the call to it. -2. Get a partial: otherwise, `func` is not called yet. Instead, another wrapper `pass` is returned, that will re-apply `curried` providing previous arguments together with the new ones. Then on a new call, again, we'll get either a new partial (if not enough arguments) or, finally, the result. - -For instance, let's see what happens in the case of `sum(a, b, c)`. Three arguments, so `sum.length = 3`. - -For the call `curried(1)(2)(3)`: - -1. The first call `curried(1)` remembers `1` in its Lexical Environment, and returns a wrapper `pass`. -2. The wrapper `pass` is called with `(2)`: it takes previous args (`1`), concatenates them with what it got `(2)` and calls `curried(1, 2)` with them together. - - As the argument count is still less than 3, `curry` returns `pass`. -3. The wrapper `pass` is called again with `(3)`, for the next call `pass(3)` takes previous args (`1`, `2`) and adds `3` to them, making the call `curried(1, 2, 3)` -- there are `3` arguments at last, they are given to the original function. - -If that's still not obvious, just trace the calls sequence in your mind or on the paper. - -```smart header="Fixed-length functions only" -The currying requires the function to have a known fixed number of arguments. -``` - -```smart header="A little more than currying" -By definition, currying should convert `sum(a, b, c)` into `sum(a)(b)(c)`. - -But most implementations of currying in JavaScript are advanced, as described: they also keep the function callable in the multi-argument variant. -``` - -## Summary - -- When we fix some arguments of an existing function, the resulting (less universal) function is called *a partial*. We can use `bind` to get a partial, but there are other ways also. - - Partials are convenient when we don't want to repeat the same argument over and over again. Like if we have a `send(from, to)` function, and `from` should always be the same for our task, we can get a partial and go on with it. - -- *Currying* is a transform that makes `f(a,b,c)` callable as `f(a)(b)(c)`. JavaScript implementations usually both keep the function callable normally and return the partial if arguments count is not enough. - - Currying is great when we want easy partials. As we've seen in the logging example: the universal function `log(date, importance, message)` after currying gives us partials when called with one argument like `log(date)` or two arguments `log(date, importance)`. 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 cb4f5566d1..ab10c47396 100644 --- a/1-js/06-advanced-functions/12-arrow-functions/article.md +++ b/1-js/06-advanced-functions/12-arrow-functions/article.md @@ -1,10 +1,10 @@ -# Снова стрелочные функции +# Повторяем стрелочные функции -Давайте вернемся к стрелочным функциям. +Давайте вернёмся к стрелочным функциям. -Стрелочные функции это не просто "сокращение" чтобы меньше писать. +Стрелочные функции - это не просто "сокращение", чтобы меньше писать. У них есть ряд других полезных особенностей. -При написании JavaScript-кода часто возникают ситуации, когда нам нужно написать небольшую функцию, которая будет выполнена где-то еще. +При написании JavaScript-кода часто возникают ситуации, когда нам нужно написать небольшую функцию, которая будет выполнена где-то ещё. Например: @@ -12,13 +12,13 @@ - `setTimeout(func)` -- `func` выполняется встроенным планировщиком. - ...и так далее. -Это очень в духе JavaScript - создать функцию и передать ее куда-нибудь. +Это очень в духе JavaScript - создать функцию и передать её куда-нибудь. -И в таких функциях мы обычно не хотим выходить из текущего контекста. +И в таких функциях мы обычно не хотим выходить из текущего контекста. Здесь как раз и полезны стрелочные функции. ## У стрелочных функций нет "this" -Как мы помним из главы , у стрелочных функций нет `this`. Если происходит обращение к `this`, его значение берется снаружи. +Как мы помним из главы , у стрелочных функций нет `this`. Если происходит обращение к `this`, его значение берётся снаружи. Например, мы можем использовать это для итерации внутри метода объекта: @@ -39,7 +39,7 @@ let group = { group.showList(); ``` -Здесь внутри `forEach` использована стрелочная функции, таким образом `this.title` в ней будет иметь точно такое же значение, как в методе `showList`: `group.title`. +Здесь внутри `forEach` использована стрелочная функция, таким образом `this.title` в ней будет иметь точно такое же значение, как в методе `showList`: `group.title`. Если бы мы использовали "обычную" функцию, была бы ошибка: @@ -61,29 +61,29 @@ let group = { group.showList(); ``` -Ошибка возникает потому, что `forEach` по умолчанию выполняет функции с `this`, равным `undefined`, и поэтому мы пытаемся обратиться к `undefined.title`. +Ошибка возникает потому, что `forEach` по умолчанию выполняет функции с `this`, равным `undefined`, и в итоге мы пытаемся обратиться к `undefined.title`. Это не влияет на стрелочные функции, потому что у них просто нет `this`. ```warn header="Стрелочные функции нельзя использовать с `new`" -Отсутствие `this` естественно ведет к другому ограничению: стрелочные функции не могут быть использованы как конструкторы. Они не могут быть вызваны с `new`. +Отсутствие `this` естественным образом ведёт к другому ограничению: стрелочные функции не могут быть использованы как конструкторы. Они не могут быть вызваны с `new`. ``` ```smart header="Стрелочные функции VS bind" -Существует тонкая разница между стрелочной функцией `=>` and обычной функцией вызванной с `.bind(this)`: +Существует тонкая разница между стрелочной функцией `=>` и обычной функцией, вызванной с `.bind(this)`: -- `.bind(this)` создает "связанную версию" функции. -- Стрелка `=>` ничего не привязывает. У функции просто нет `this`. При получении значения `this` - оно, как обычная переменная, берется из внешнего лексического окружения. +- `.bind(this)` создаёт "связанную версию" функции. +- Стрелка `=>` ничего не привязывает. У функции просто нет `this`. При получении значения `this` - оно, как обычная переменная, берётся из внешнего лексического окружения. ``` ## Стрелочные функции не имеют "arguments" -У стрелочных функции также нет переменной `arguments`. +У стрелочных функций также нет переменной `arguments`. Это отлично подходит для декораторов, когда нам нужно пробросить вызов с текущими `this` и `arguments`. -Например, `defer(f, ms)` принимает функцию и возвращает обертку над ней, которая откладывает вызов на `ms` миллисекунд: +Например, `defer(f, ms)` принимает функцию и возвращает обёртку над ней, которая откладывает вызов на `ms` миллисекунд: ```js run function defer(f, ms) { @@ -97,10 +97,10 @@ function sayHi(who) { } let sayHiDeferred = defer(sayHi, 2000); -sayHiDeferred("John"); // Hello, John after 2 seconds +sayHiDeferred("John"); // выводит "Hello, John" через 2 секунды ``` -То же самое без стрелочной функции выглядело бы: +То же самое без стрелочной функции выглядело бы так: ```js function defer(f, ms) { @@ -121,7 +121,7 @@ function defer(f, ms) { - Не имеют `this`. - Не имеют `arguments`. -- Не могут быть вызваны `new`. +- Не могут быть вызваны с `new`. - (У них также нет `super`, но мы про это не говорили. Про это будет в главе ). -Все это потому что они предназначены для небольшого кода, который не имеет своего "контекста", а работает в текущем. И они отлично справляются с этой задачей. +Всё это потому, что они предназначены для небольшого кода, который не имеет своего "контекста", выполняясь в текущем. И они отлично справляются с этой задачей! diff --git a/1-js/06-advanced-functions/index.md b/1-js/06-advanced-functions/index.md index 7800dcc424..0bd0a40669 100644 --- a/1-js/06-advanced-functions/index.md +++ b/1-js/06-advanced-functions/index.md @@ -1 +1 @@ -# Advanced working with functions +# Продвинутая работа с функциями 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 7768b35577..40dbc8f601 100644 --- a/1-js/07-object-properties/01-property-descriptors/article.md +++ b/1-js/07-object-properties/01-property-descriptors/article.md @@ -1,40 +1,40 @@ -# Property flags and descriptors +# Флаги и дескрипторы свойств -As we know, objects can store properties. +Как мы знаем, объекты могут содержать свойства. -Till now, a property was a simple "key-value" pair to us. But an object property is actually a more flexible and powerful thing. +До этого момента мы рассматривали свойство только как пару "ключ-значение". Но на самом деле свойство объекта гораздо мощнее и гибче. -In this chapter we'll study additional configuration options, and in the next we'll see how to invisibly turn them into getter/setter functions. +В этой главе мы изучим дополнительные флаги конфигурации для свойств, а в следующей -- увидим, как можно незаметно превратить их в специальные функции - геттеры и сеттеры. -## Property flags +## Флаги свойств -Object properties, besides a **`value`**, have three special attributes (so-called "flags"): +Помимо значения **`value`**, свойства объекта имеют три специальных атрибута (так называемые "флаги"). -- **`writable`** -- if `true`, can be changed, otherwise it's read-only. -- **`enumerable`** -- if `true`, then listed in loops, otherwise not listed. -- **`configurable`** -- if `true`, the property can be deleted and these attributes can be modified, otherwise not. +- **`writable`** -- если `true`, свойство можно изменить, иначе оно только для чтения. +- **`enumerable`** -- если `true`, свойство перечисляется в циклах, в противном случае циклы его игнорируют. +- **`configurable`** -- если `true`, свойство можно удалить, а эти атрибуты можно изменять, иначе этого делать нельзя. -We didn't see them yet, because generally they do not show up. When we create a property "the usual way", all of them are `true`. But we also can change them anytime. +Мы ещё не встречали эти атрибуты, потому что обычно они скрыты. Когда мы создаём свойство "обычным способом", все они имеют значение `true`. Но мы можем изменить их в любое время. -First, let's see how to get those flags. +Сначала посмотрим, как получить их текущие значения. -The method [Object.getOwnPropertyDescriptor](mdn:js/Object/getOwnPropertyDescriptor) allows to query the *full* information about a property. +Метод [Object.getOwnPropertyDescriptor](mdn:js/Object/getOwnPropertyDescriptor) позволяет получить *полную* информацию о свойстве. -The syntax is: +Его синтаксис: ```js let descriptor = Object.getOwnPropertyDescriptor(obj, propertyName); ``` `obj` -: The object to get information from. +: Объект, из которого мы получаем информацию. `propertyName` -: The name of the property. +: Имя свойства. -The returned value is a so-called "property descriptor" object: it contains the value and all the flags. +Возвращаемое значение - это объект, так называемый "дескриптор свойства": он содержит значение свойства и все его флаги. -For instance: +Например: ```js run let user = { @@ -44,7 +44,7 @@ let user = { let descriptor = Object.getOwnPropertyDescriptor(user, 'name'); alert( JSON.stringify(descriptor, null, 2 ) ); -/* property descriptor: +/* дескриптор свойства: { "value": "John", "writable": true, @@ -54,23 +54,23 @@ alert( JSON.stringify(descriptor, null, 2 ) ); */ ``` -To change the flags, we can use [Object.defineProperty](mdn:js/Object/defineProperty). +Чтобы изменить флаги, мы можем использовать метод [Object.defineProperty](mdn:js/Object/defineProperty). -The syntax is: +Его синтаксис: ```js Object.defineProperty(obj, propertyName, descriptor) ``` `obj`, `propertyName` -: The object and property to work on. +: Объект и его свойство, для которого нужно применить дескриптор. `descriptor` -: Property descriptor to apply. +: Применяемый дескриптор. -If the property exists, `defineProperty` updates its flags. Otherwise, it creates the property with the given value and flags; in that case, if a flag is not supplied, it is assumed `false`. +Если свойство существует, `defineProperty` обновит его флаги. В противном случае метод создаёт новое свойство с указанным значением и флагами; если какой-либо флаг не указан явно, ему присваивается значение `false`. -For instance, here a property `name` is created with all falsy flags: +Например, здесь создаётся свойство `name`, все флаги которого имеют значение `false`: ```js run let user = {}; @@ -96,13 +96,13 @@ alert( JSON.stringify(descriptor, null, 2 ) ); */ ``` -Compare it with "normally created" `user.name` above: now all flags are falsy. If that's not what we want then we'd better set them to `true` in `descriptor`. +Сравните это с предыдущим примером, в котором мы создали свойство `user.name` "обычным способом": в этот раз все флаги имеют значение `false`. Если это не то, что нам нужно, надо присвоить им значения `true` в параметре `descriptor`. -Now let's see effects of the flags by example. +Теперь давайте рассмотрим на примерах, что нам даёт использование флагов. -## Read-only +## Только для чтения -Let's make `user.name` read-only by changing `writable` flag: +Сделаем свойство `user.name` доступным только для чтения. Для этого изменим флаг `writable`: ```js run let user = { @@ -116,36 +116,39 @@ Object.defineProperty(user, "name", { }); *!* -user.name = "Pete"; // Error: Cannot assign to read only property 'name'... +user.name = "Pete"; // Ошибка: Невозможно изменить доступное только для чтения свойство 'name' */!* ``` -Now no one can change the name of our user, unless they apply their own `defineProperty` to override ours. +Теперь никто не сможет изменить имя пользователя, если только не обновит соответствующий флаг новым вызовом `defineProperty`. -Here's the same operation, but for the case when a property doesn't exist: +```smart header="Ошибки появляются только в строгом режиме" +В нестрогом режиме, без `use strict`, мы не увидим никаких ошибок при записи в свойства "только для чтения" и т.п. Но эти операции всё равно не будут выполнены успешно. Действия, нарушающие ограничения флагов, в нестрогом режиме просто молча игнорируются. +``` + +Вот тот же пример, но свойство создано "с нуля": ```js run let user = { }; Object.defineProperty(user, "name", { *!* - value: "Pete", - // for new properties need to explicitly list what's true + value: "John", + // для нового свойства необходимо явно указывать все флаги, для которых значение true enumerable: true, configurable: true */!* }); -alert(user.name); // Pete -user.name = "Alice"; // Error +alert(user.name); // John +user.name = "Pete"; // Ошибка ``` +## Неперечислимое свойство -## Non-enumerable - -Now let's add a custom `toString` to `user`. +Теперь добавим собственный метод `toString` к объекту `user`. -Normally, a built-in `toString` for objects is non-enumerable, it does not show up in `for..in`. But if we add `toString` of our own, then by default it shows up in `for..in`, like this: +Встроенный метод `toString` в объектах - неперечислимый, его не видно в цикле `for..in`. Но если мы напишем свой собственный метод `toString`, цикл `for..in` будет выводить его по умолчанию: ```js run let user = { @@ -155,11 +158,11 @@ let user = { } }; -// By default, both our properties are listed: +// По умолчанию оба свойства выведутся: for (let key in user) alert(key); // name, toString ``` -If we don't like it, then we can set `enumerable:false`. Then it won't appear in `for..in` loop, just like the built-in one: +Если мы этого не хотим, можно установить для свойства `enumerable:false`. Тогда оно перестанет появляться в цикле `for..in` аналогично встроенному `toString`: ```js run let user = { @@ -176,24 +179,24 @@ Object.defineProperty(user, "toString", { }); *!* -// Now our toString disappears: +// Теперь наше свойство toString пропало из цикла: */!* for (let key in user) alert(key); // name ``` -Non-enumerable properties are also excluded from `Object.keys`: +Неперечислимые свойства также не возвращаются `Object.keys`: ```js alert(Object.keys(user)); // name ``` -## Non-configurable +## Неконфигурируемое свойство -The non-configurable flag (`configurable:false`) is sometimes preset for built-in objects and properties. +Флаг неконфигурируемого свойства (`configurable:false`) иногда предустановлен для некоторых встроенных объектов и свойств. -A non-configurable property can not be deleted or altered with `defineProperty`. +Неконфигурируемое свойство не может быть удалено, его атрибуты не могут быть изменены. -For instance, `Math.PI` is read-only, non-enumerable and non-configurable: +Например, свойство `Math.PI` - только для чтения, неперечислимое и неконфигурируемое: ```js run let descriptor = Object.getOwnPropertyDescriptor(Math, 'PI'); @@ -208,46 +211,68 @@ alert( JSON.stringify(descriptor, null, 2 ) ); } */ ``` -So, a programmer is unable to change the value of `Math.PI` or overwrite it. +То есть программист не сможет изменить значение `Math.PI` или перезаписать его. ```js run -Math.PI = 3; // Error +Math.PI = 3; // Ошибка, потому что writable: false -// delete Math.PI won't work either +// delete Math.PI тоже не сработает +``` +Мы также не можем изменить `writable`: + +```js run +// Ошибка, из-за configurable: false +Object.defineProperty(Math, "PI", { writable: true }); ``` +Мы абсолютно ничего не можем сделать с `Math.PI`. -Making a property non-configurable is a one-way road. We cannot change it back, because `defineProperty` doesn't work on non-configurable properties. +Определение свойства как неконфигурируемого - это дорога в один конец. Мы не можем изменить его обратно с помощью `defineProperty`. -Here we are making `user.name` a "forever sealed" constant: +**Обратите внимание: `configurable: false` не даст изменить флаги свойства, а также не даст его удалить. При этом можно изменить значение свойства.** + +В коде ниже свойство `user.name` является неконфигурируемым, но мы все ещё можем изменить его значение (т.к. `writable: true`). ```js run -let user = { }; +let user = { + name: "John" +}; Object.defineProperty(user, "name", { - value: "John", - writable: false, configurable: false }); -*!* -// won't be able to change user.name or its flags -// all this won't work: -// user.name = "Pete" -// delete user.name -// defineProperty(user, "name", ...) -Object.defineProperty(user, "name", {writable: true}); // Error -*/!* +user.name = "Pete"; // работает +delete user.name; // Ошибка ``` +А здесь мы делаем `user.name` "навечно запечатанной" константой, как и встроенный `Math.PI`: +```js run +let user = { + name: "John" +}; + +Object.defineProperty(user, "name", { + writable: false, + configurable: false +}); -```smart header="Errors appear only in use strict" -In the non-strict mode, no errors occur when writing to read-only properties and such. But the operation still won't succeed. Flag-violating actions are just silently ignored in non-strict. +// теперь невозможно изменить user.name или его флаги +// всё это не будет работать: +user.name = "Pete"; +delete user.name; +Object.defineProperty(user, "name", { value: "Pete" }); +``` + +```smart header="Единственное возможное изменение атрибута: writable true -> false" +Есть небольшое исключение, касающееся изменения флагов. + +Мы можем изменить `writable: true` на `false` для неконфигурируемого свойства, тем самым предотвратив изменение его значения (чтобы добавить ещё один уровень защиты). Однако не наоборот. ``` -## Object.defineProperties +## Метод Object.defineProperties -There's a method [Object.defineProperties(obj, descriptors)](mdn:js/Object/defineProperties) that allows to define many properties at once. +Существует метод [Object.defineProperties(obj, descriptors)](mdn:js/Object/defineProperties), который позволяет определять множество свойств сразу. -The syntax is: +Его синтаксис: ```js Object.defineProperties(obj, { @@ -257,7 +282,7 @@ Object.defineProperties(obj, { }); ``` -For instance: +Например: ```js Object.defineProperties(user, { @@ -267,19 +292,19 @@ Object.defineProperties(user, { }); ``` -So, we can set many properties at once. +Таким образом, мы можем определить множество свойств одной операцией. ## Object.getOwnPropertyDescriptors -To get all property descriptors at once, we can use the method [Object.getOwnPropertyDescriptors(obj)](mdn:js/Object/getOwnPropertyDescriptors). +Чтобы получить все дескрипторы свойств сразу, можно воспользоваться методом [Object.getOwnPropertyDescriptors(obj)](mdn:js/Object/getOwnPropertyDescriptors). -Together with `Object.defineProperties` it can be used as a "flags-aware" way of cloning an object: +Вместе с `Object.defineProperties` этот метод можно использовать для клонирования объекта вместе с его флагами: ```js let clone = Object.defineProperties({}, Object.getOwnPropertyDescriptors(obj)); ``` -Normally when we clone an object, we use an assignment to copy properties, like this: +Обычно при клонировании объекта мы используем присваивание, чтобы скопировать его свойства: ```js for (let key in user) { @@ -287,33 +312,34 @@ for (let key in user) { } ``` -...But that does not copy flags. So if we want a "better" clone then `Object.defineProperties` is preferred. +...Но это не копирует флаги. Так что если нам нужен клон "получше", предпочтительнее использовать `Object.defineProperties`. -Another difference is that `for..in` ignores symbolic properties, but `Object.getOwnPropertyDescriptors` returns *all* property descriptors including symbolic ones. +Другое отличие в том, что `for..in` игнорирует символьные и неперечислимые свойства, а `Object.getOwnPropertyDescriptors` возвращает дескрипторы *всех* свойств. -## Sealing an object globally +## Глобальное запечатывание объекта -Property descriptors work at the level of individual properties. +Дескрипторы свойств работают на уровне конкретных свойств. -There are also methods that limit access to the *whole* object: +Но ещё есть методы, которые ограничивают доступ ко *всему* объекту: [Object.preventExtensions(obj)](mdn:js/Object/preventExtensions) -: Forbids the addition of new properties to the object. +: Запрещает добавлять новые свойства в объект. [Object.seal(obj)](mdn:js/Object/seal) -: Forbids adding/removing of properties. Sets `configurable: false` for all existing properties. +: Запрещает добавлять/удалять свойства. Устанавливает `configurable: false` для всех существующих свойств. [Object.freeze(obj)](mdn:js/Object/freeze) -: Forbids adding/removing/changing of properties. Sets `configurable: false, writable: false` for all existing properties. -And also there are tests for them: +: Запрещает добавлять/удалять/изменять свойства. Устанавливает `configurable: false, writable: false` для всех существующих свойств. + +А также есть методы для их проверки: [Object.isExtensible(obj)](mdn:js/Object/isExtensible) -: Returns `false` if adding properties is forbidden, otherwise `true`. +: Возвращает `false`, если добавление свойств запрещено, иначе `true`. [Object.isSealed(obj)](mdn:js/Object/isSealed) -: Returns `true` if adding/removing properties is forbidden, and all existing properties have `configurable: false`. +: Возвращает `true`, если добавление/удаление свойств запрещено и для всех существующих свойств установлено `configurable: false`. [Object.isFrozen(obj)](mdn:js/Object/isFrozen) -: Returns `true` if adding/removing/changing properties is forbidden, and all current properties are `configurable: false, writable: false`. +: Возвращает `true`, если добавление/удаление/изменение свойств запрещено, и для всех текущих свойств установлено `configurable: false, writable: false`. -These methods are rarely used in practice. +На практике эти методы используются редко. 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 41207ab579..5be8d15a1b 100644 --- a/1-js/07-object-properties/02-property-accessors/article.md +++ b/1-js/07-object-properties/02-property-accessors/article.md @@ -1,40 +1,40 @@ -# Property getters and setters +# Свойства - геттеры и сеттеры -There are two kinds of properties. +Есть два типа свойств объекта. -The first kind is *data properties*. We already know how to work with them. Actually, all properties that we've been using till now were data properties. +Первый тип это *свойства-данные (data properties)*. Мы уже знаем, как работать с ними. Все свойства, которые мы использовали до текущего момента, были свойствами-данными. -The second type of properties is something new. It's *accessor properties*. They are essentially functions that work on getting and setting a value, but look like regular properties to an external code. +Второй тип свойств мы ещё не рассматривали. Это *свойства-аксессоры (accessor properties)*. По своей сути это функции, которые используются для присвоения и получения значения, но во внешнем коде они выглядят как обычные свойства объекта. -## Getters and setters +## Геттеры и сеттеры -Accessor properties are represented by "getter" and "setter" methods. In an object literal they are denoted by `get` and `set`: +Свойства-аксессоры представлены методами: "геттер" - для чтения и "сеттер" - для записи. При литеральном объявлении объекта они обозначаются `get` и `set`: ```js let obj = { *!*get propName()*/!* { - // getter, the code executed on getting obj.propName + // геттер, срабатывает при чтении obj.propName }, *!*set propName(value)*/!* { - // setter, the code executed on setting obj.propName = value + // сеттер, срабатывает при записи obj.propName = value } }; ``` -The getter works when `obj.propName` is read, the setter -- when it is assigned. +Геттер срабатывает, когда `obj.propName` читается, сеттер -- когда значение присваивается. -For instance, we have a `user` object with `name` and `surname`: +Например, у нас есть объект `user` со свойствами `name` и `surname`: -```js run +```js let user = { name: "John", surname: "Smith" }; ``` -Now we want to add a "fullName" property, that should be "John Smith". Of course, we don't want to copy-paste existing information, so we can implement it as an accessor: +А теперь добавим свойство объекта `fullName` для полного имени, которое в нашем случае `"John Smith"`. Само собой, мы не хотим дублировать уже имеющуюся информацию, так что реализуем его при помощи аксессора: ```js run let user = { @@ -53,11 +53,23 @@ alert(user.fullName); // John Smith */!* ``` -From outside, an accessor property looks like a regular one. That's the idea of accessor properties. We don't *call* `user.fullName` as a function, we *read* it normally: the getter runs behind the scenes. +Снаружи свойство-аксессор выглядит как обычное свойство. В этом и заключается смысл свойств-аксессоров. Мы не *вызываем* `user.fullName` как функцию, а *читаем* как обычное свойство: геттер выполнит всю работу за кулисами. -As of now, `fullName` has only a getter. If we attempt to assign `user.fullName=`, there will be an error. +На данный момент у `fullName` есть только геттер. Если мы попытаемся назначить `user.fullName=`, произойдёт ошибка: -Let's fix it by adding a setter for `user.fullName`: +```js run +let user = { + get fullName() { + return `...`; + } +}; + +*!* +user.fullName = "Тест"; // Ошибка (у свойства есть только геттер) +*/!* +``` + +Давайте исправим это, добавив сеттер для `user.fullName`: ```js run let user = { @@ -75,39 +87,29 @@ let user = { */!* }; -// set fullName is executed with the given value. +// set fullName запустится с данным значением user.fullName = "Alice Cooper"; alert(user.name); // Alice alert(user.surname); // Cooper ``` -Now we have a "virtual" property. It is readable and writable, but in fact does not exist. - -```smart header="Accessor properties are only accessible with get/set" -Once a property is defined with `get prop()` or `set prop()`, it's an accessor property, not a data properety any more. +В итоге мы получили "виртуальное" свойство `fullName`. Его можно прочитать и изменить. -- If there's a getter -- we can read `object.prop`, otherwise we can't. -- If there's a setter -- we can set `object.prop=...`, otherwise we can't. - -And in either case we can't `delete` an accessor property. -``` +## Дескрипторы свойств доступа +Дескрипторы свойств-аксессоров отличаются от "обычных" свойств-данных. -## Accessor descriptors +Свойства-аксессоры не имеют `value` и `writable`, но взамен предлагают функции `get` и `set`. -Descriptors for accessor properties are different -- as compared with data properties. +То есть, дескриптор аксессора может иметь: -For accessor properties, there is no `value` and `writable`, but instead there are `get` and `set` functions. +- **`get`** -- функция без аргументов, которая сработает при чтении свойства, +- **`set`** -- функция, принимающая один аргумент, вызываемая при присвоении свойства, +- **`enumerable`** -- то же самое, что и для свойств-данных, +- **`configurable`** -- то же самое, что и для свойств-данных. -So an accessor descriptor may have: - -- **`get`** -- a function without arguments, that works when a property is read, -- **`set`** -- a function with one argument, that is called when the property is set, -- **`enumerable`** -- same as for data properties, -- **`configurable`** -- same as for data properties. - -For instance, to create an accessor `fullName` with `defineProperty`, we can pass a descriptor with `get` and `set`: +Например, для создания аксессора `fullName` при помощи `defineProperty` мы можем передать дескриптор с использованием `get` и `set`: ```js run let user = { @@ -132,9 +134,9 @@ alert(user.fullName); // John Smith for(let key in user) alert(key); // name, surname ``` -Please note once again that a property can be either an accessor or a data property, not both. +Ещё раз заметим, что свойство объекта может быть либо свойством-аксессором (с методами `get/set`), либо свойством-данным (со значением `value`). -If we try to supply both `get` and `value` in the same descriptor, there will be an error: +При попытке указать и `get`, и `value` в одном дескрипторе будет ошибка: ```js run *!* @@ -149,11 +151,11 @@ Object.defineProperty({}, 'prop', { }); ``` -## Smarter getters/setters +## Умные геттеры/сеттеры -Getters/setters can be used as wrappers over "real" property values to gain more control over them. +Геттеры/сеттеры можно использовать как обёртки над "реальными" значениями свойств, чтобы получить больше контроля над операциями с ними. -For instance, if we want to forbid too short names for `user`, we can store `name` in a special property `_name`. And filter assignments in the setter: +Например, если мы хотим запретить устанавливать короткое имя для `user`, мы можем использовать сеттер `name` для проверки, а само значение хранить в отдельном свойстве `_name`: ```js run let user = { @@ -163,7 +165,7 @@ let user = { set name(value) { if (value.length < 4) { - alert("Name is too short, need at least 4 characters"); + alert("Имя слишком короткое, должно быть более 4 символов"); return; } this._name = value; @@ -173,17 +175,19 @@ let user = { user.name = "Pete"; alert(user.name); // Pete -user.name = ""; // Name is too short... +user.name = ""; // Имя слишком короткое... ``` -Technically, the external code may still access the name directly by using `user._name`. But there is a widely known agreement that properties starting with an underscore `"_"` are internal and should not be touched from outside the object. +Таким образом, само имя хранится в `_name`, доступ к которому производится через геттер и сеттер. +Технически, внешний код всё ещё может получить доступ к имени напрямую с помощью `user._name`, но существует широко известное соглашение о том, что свойства, которые начинаются с символа `"_"`, являются внутренними, и к ним не следует обращаться из-за пределов объекта. -## Using for compatibility -One of the great ideas behind getters and setters -- they allow to take control over a "normal" data property and tweak it at any moment. +## Использование для совместимости -For instance, we started implementing user objects using data properties `name` and `age`: +У аксессоров есть интересная область применения - они позволяют в любой момент взять "обычное" свойство и изменить его поведение, поменяв на геттер и сеттер. + +Например, представим, что мы начали реализовывать объект `user`, используя свойства-данные имя `name` и возраст `age`: ```js function User(name, age) { @@ -196,7 +200,7 @@ let john = new User("John", 25); alert( john.age ); // 25 ``` -...But sooner or later, things may change. Instead of `age` we may decide to store `birthday`, because it's more precise and convenient: +...Но рано или поздно всё может измениться. Взамен возраста `age` мы можем решить хранить дату рождения `birthday`, потому что так более точно и удобно: ```js function User(name, birthday) { @@ -207,11 +211,13 @@ function User(name, birthday) { let john = new User("John", new Date(1992, 6, 1)); ``` -Now what to do with the old code that still uses `age` property? +Что нам делать со старым кодом, который использует свойство `age`? + +Мы можем попытаться найти все такие места и изменить их, но это отнимает время и может быть невыполнимо, если код используется другими людьми. И кроме того, `age` -- это отличное свойство для `user`, верно? -We can try to find all such places and fix them, but that takes time and can be hard to do if that code is written by other people. And besides, `age` is a nice thing to have in `user`, right? In some places it's just what we want. +Давайте его сохраним. -Adding a getter for `age` mitigates the problem: +Добавление геттера для `age` решит проблему: ```js run no-beautify function User(name, birthday) { @@ -219,7 +225,7 @@ function User(name, birthday) { this.birthday = birthday; *!* - // age is calculated from the current date and birthday + // возраст рассчитывается из текущей даты и дня рождения Object.defineProperty(this, "age", { get() { let todayYear = new Date().getFullYear(); @@ -231,8 +237,8 @@ function User(name, birthday) { let john = new User("John", new Date(1992, 6, 1)); -alert( john.birthday ); // birthday is available -alert( john.age ); // ...as well as the age +alert( john.birthday ); // доступен как день рождения +alert( john.age ); // ...так и возраст ``` -Now the old code works too and we've got a nice additional property. +Теперь старый код тоже работает, и у нас есть отличное дополнительное свойство! diff --git a/1-js/07-object-properties/index.md b/1-js/07-object-properties/index.md index 67fcccaffc..ee6d5a71c2 100644 --- a/1-js/07-object-properties/index.md +++ b/1-js/07-object-properties/index.md @@ -1,3 +1,3 @@ -# Object properties configuration +# Свойства объекта, их конфигурация -In this section we return to objects and study their properties even more in-depth. +В этой секции мы вернёмся к объектам и изучим их свойства более глубоко. diff --git a/1-js/08-prototypes/01-prototype-inheritance/1-property-after-delete/solution.md b/1-js/08-prototypes/01-prototype-inheritance/1-property-after-delete/solution.md index 6d25a462ae..87fcad4cc1 100644 --- a/1-js/08-prototypes/01-prototype-inheritance/1-property-after-delete/solution.md +++ b/1-js/08-prototypes/01-prototype-inheritance/1-property-after-delete/solution.md @@ -1,4 +1,4 @@ -1. `true`, taken from `rabbit`. -2. `null`, taken from `animal`. -3. `undefined`, there's no such property any more. +1. `true`, берётся из `rabbit`. +2. `null`, берётся из `animal`. +3. `undefined`, такого свойства больше нет. 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 f38fb6f97a..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 @@ -2,16 +2,17 @@ importance: 5 --- -# Working with prototype +# Работа с прототипами -Here's the code that creates a pair of objects, then modifies them. +В приведённом ниже коде создаются и изменяются два объекта. -Which values are shown in the process? +Какие значения показываются в процессе выполнения кода? ```js let animal = { jumps: null }; + let rabbit = { __proto__: animal, jumps: true @@ -28,4 +29,4 @@ delete animal.jumps; alert( rabbit.jumps ); // ? (3) ``` -There should be 3 answers. +Должно быть 3 ответа. diff --git a/1-js/08-prototypes/01-prototype-inheritance/2-search-algorithm/solution.md b/1-js/08-prototypes/01-prototype-inheritance/2-search-algorithm/solution.md index a16796f9cc..ac5c8cbd1b 100644 --- a/1-js/08-prototypes/01-prototype-inheritance/2-search-algorithm/solution.md +++ b/1-js/08-prototypes/01-prototype-inheritance/2-search-algorithm/solution.md @@ -1,5 +1,5 @@ -1. Let's add `__proto__`: +1. Добавим свойство `__proto__`: ```js run let head = { @@ -27,6 +27,6 @@ alert( table.money ); // undefined ``` -2. In modern engines, performance-wise, there's no difference whether we take a property from an object or its prototype. They remember where the property was found and reuse it in the next request. +2. С точки зрения производительности, для современных движков неважно, откуда берётся свойство -- из объекта или из прототипа. Они запоминают, где было найдено свойство, и повторно используют его в следующем запросе. - For instance, for `pockets.glasses` they remember where they found `glasses` (in `head`), and next time will search right there. They are also smart enough to update internal caches if something changes, so that optimization is safe. + Например, при обращении к `pockets.glasses` они запомнят, что нашли `glasses` в `head`, и в следующий раз будут искать там же. Они достаточно умны, чтобы при изменениях обновлять внутренний кеш, поэтому такая оптимизация безопасна. diff --git a/1-js/08-prototypes/01-prototype-inheritance/2-search-algorithm/task.md b/1-js/08-prototypes/01-prototype-inheritance/2-search-algorithm/task.md index 002b24b8a2..136a2c94b8 100644 --- a/1-js/08-prototypes/01-prototype-inheritance/2-search-algorithm/task.md +++ b/1-js/08-prototypes/01-prototype-inheritance/2-search-algorithm/task.md @@ -2,11 +2,11 @@ importance: 5 --- -# Searching algorithm +# Алгоритм поиска -The task has two parts. +Задача состоит из двух частей. -We have an object: +У нас есть объекты: ```js let head = { @@ -27,5 +27,5 @@ let pockets = { }; ``` -1. Use `__proto__` to assign prototypes in a way that any property lookup will follow the path: `pockets` -> `bed` -> `table` -> `head`. For instance, `pockets.pen` should be `3` (found in `table`), and `bed.glasses` should be `1` (found in `head`). -2. Answer the question: is it faster to get `glasses` as `pockets.glasses` or `head.glasses`? Benchmark if needed. +1. С помощью свойства `__proto__` задайте прототипы так, чтобы поиск любого свойства выполнялся по следующему пути: `pockets` -> `bed` -> `table` -> `head`. Например, `pockets.pen` должно возвращать значение `3` (найденное в `table`), а `bed.glasses` -- значение `1` (найденное в `head`). +2. Ответьте на вопрос: как быстрее получить значение `glasses` -- через `pockets.glasses` или через `head.glasses`? При необходимости составьте цепочки поиска и сравните их. diff --git a/1-js/08-prototypes/01-prototype-inheritance/3-proto-and-this/solution.md b/1-js/08-prototypes/01-prototype-inheritance/3-proto-and-this/solution.md index c7d147b9c7..b1eb8c13b9 100644 --- a/1-js/08-prototypes/01-prototype-inheritance/3-proto-and-this/solution.md +++ b/1-js/08-prototypes/01-prototype-inheritance/3-proto-and-this/solution.md @@ -1,6 +1,6 @@ -**The answer: `rabbit`.** +**Ответ: `rabbit`.** -That's because `this` is an object before the dot, so `rabbit.eat()` modifies `rabbit`. +Поскольку `this` -- это объект, который стоит перед точкой, `rabbit.eat()` изменяет объект `rabbit`. -Property lookup and execution are two different things. -The method `rabbit.eat` is first found in the prototype, then executed with `this=rabbit` +Поиск свойства и исполнение кода -- два разных процесса. +Сначала осуществляется поиск метода `rabbit.eat` в прототипе, а затем этот метод выполняется с `this=rabbit`. diff --git a/1-js/08-prototypes/01-prototype-inheritance/3-proto-and-this/task.md b/1-js/08-prototypes/01-prototype-inheritance/3-proto-and-this/task.md index b37499bad5..bf6f151b1e 100644 --- a/1-js/08-prototypes/01-prototype-inheritance/3-proto-and-this/task.md +++ b/1-js/08-prototypes/01-prototype-inheritance/3-proto-and-this/task.md @@ -2,11 +2,11 @@ importance: 5 --- -# Where it writes? +# Куда будет произведена запись? -We have `rabbit` inheriting from `animal`. +Объект `rabbit` наследует от объекта `animal`. -If we call `rabbit.eat()`, which object receives the `full` property: `animal` or `rabbit`? +Какой объект получит свойство `full` при вызове `rabbit.eat()`: `animal` или `rabbit`? ```js let animal = { diff --git a/1-js/08-prototypes/01-prototype-inheritance/4-hamster-proto/solution.md b/1-js/08-prototypes/01-prototype-inheritance/4-hamster-proto/solution.md index fad4b88607..3643c08574 100644 --- a/1-js/08-prototypes/01-prototype-inheritance/4-hamster-proto/solution.md +++ b/1-js/08-prototypes/01-prototype-inheritance/4-hamster-proto/solution.md @@ -1,18 +1,18 @@ -Let's look carefully at what's going on in the call `speedy.eat("apple")`. +Давайте внимательно посмотрим, что происходит при вызове `speedy.eat("apple")`. -1. The method `speedy.eat` is found in the prototype (`=hamster`), then executed with `this=speedy` (the object before the dot). +1. Сначала в прототипе (`=hamster`) находится метод `speedy.eat`, а затем он выполняется с `this=speedy` (объект перед точкой). -2. Then `this.stomach.push()` needs to find `stomach` property and call `push` on it. It looks for `stomach` in `this` (`=speedy`), but nothing found. +2. Затем в `this.stomach.push()` нужно найти свойство `stomach` и вызвать для него `push`. Движок ищет `stomach` в `this` (`=speedy`), но ничего не находит. -3. Then it follows the prototype chain and finds `stomach` in `hamster`. +3. Он идёт по цепочке прототипов и находит `stomach` в `hamster`. -4. Then it calls `push` on it, adding the food into *the stomach of the prototype*. +4. И вызывает для него `push`, добавляя еду в *живот прототипа*. -So all hamsters share a single stomach! +Получается, что у хомяков один живот на двоих! -Every time the `stomach` is taken from the prototype, then `stomach.push` modifies it "at place". +И при `lazy.stomach.push(...)` и при `speedy.stomach.push()`, свойство `stomach` берётся из прототипа (так как его нет в самом объекте), затем в него добавляются данные. -Please note that such thing doesn't happen in case of a simple assignment `this.stomach=`: +Обратите внимание, что этого не происходит при простом присваивании `this.stomach=`: ```js run let hamster = { @@ -20,7 +20,7 @@ let hamster = { eat(food) { *!* - // assign to this.stomach instead of this.stomach.push + // присвоение значения this.stomach вместо вызова this.stomach.push this.stomach = [food]; */!* } @@ -34,17 +34,17 @@ let lazy = { __proto__: hamster }; -// Speedy one found the food +// Шустрый хомяк нашёл еду speedy.eat("apple"); alert( speedy.stomach ); // apple -// Lazy one's stomach is empty -alert( lazy.stomach ); // +// Живот ленивого хомяка пуст +alert( lazy.stomach ); // <ничего> ``` -Now all works fine, because `this.stomach=` does not perform a lookup of `stomach`. The value is written directly into `this` object. +Теперь всё работает правильно, потому что `this.stomach=` не ищет свойство `stomach`. Значение записывается непосредственно в объект `this`. -Also we can totally evade the problem by making sure that each hamster has their own stomach: +Также мы можем полностью избежать проблемы, если у каждого хомяка будет собственный живот: ```js run let hamster = { @@ -69,12 +69,12 @@ let lazy = { */!* }; -// Speedy one found the food +// Шустрый хомяк нашёл еду speedy.eat("apple"); alert( speedy.stomach ); // apple -// Lazy one's stomach is empty -alert( lazy.stomach ); // +// Живот ленивого хомяка пуст +alert( lazy.stomach ); // <ничего> ``` -As a common solution, all properties that describe the state of a particular object, like `stomach` above, are usually written into that object. That prevents such problems. +Все свойства, описывающие состояние объекта (как свойство `stomach` в примере выше), рекомендуется записывать в сам этот объект. Это позволяет избежать подобных проблем. diff --git a/1-js/08-prototypes/01-prototype-inheritance/4-hamster-proto/task.md b/1-js/08-prototypes/01-prototype-inheritance/4-hamster-proto/task.md index 6f9fb279ec..2c8d5ceae1 100644 --- a/1-js/08-prototypes/01-prototype-inheritance/4-hamster-proto/task.md +++ b/1-js/08-prototypes/01-prototype-inheritance/4-hamster-proto/task.md @@ -2,11 +2,11 @@ importance: 5 --- -# Why two hamsters are full? +# Почему наедаются оба хомяка? -We have two hamsters: `speedy` and `lazy` inheriting from the general `hamster` object. +У нас есть два хомяка: шустрый (`speedy`) и ленивый (`lazy`); оба наследуют от общего объекта `hamster`. -When we feed one of them, the other one is also full. Why? How to fix it? +Когда мы кормим одного хомяка, второй тоже наедается. Почему? Как это исправить? ```js run let hamster = { @@ -25,11 +25,11 @@ let lazy = { __proto__: hamster }; -// This one found the food +// Этот хомяк нашёл еду speedy.eat("apple"); alert( speedy.stomach ); // apple -// This one also has it, why? fix please. +// У этого хомяка тоже есть еда. Почему? Исправьте alert( lazy.stomach ); // apple ``` diff --git a/1-js/08-prototypes/01-prototype-inheritance/article.md b/1-js/08-prototypes/01-prototype-inheritance/article.md index 8a2c3bf117..b02f250839 100644 --- a/1-js/08-prototypes/01-prototype-inheritance/article.md +++ b/1-js/08-prototypes/01-prototype-inheritance/article.md @@ -2,7 +2,7 @@ В программировании мы часто хотим взять что-то и расширить. -Например, у нас есть объект `user` со своими свойствами и методами, и мы хотим создать объекты `admin` и `guest` как его слегка измененные варианты. Мы хотели бы повторно использовать то, что есть у объекта `user`, не копировать/переопределять его методы, а просто создать новый объект на его основе. +Например, у нас есть объект `user` со своими свойствами и методами, и мы хотим создать объекты `admin` и `guest` как его слегка изменённые варианты. Мы хотели бы повторно использовать то, что есть у объекта `user`, не копировать/переопределять его методы, а просто создать новый объект на его основе. *Прототипное наследование* — это возможность языка, которая помогает в этом. @@ -10,18 +10,19 @@ В JavaScript объекты имеют специальное скрытое свойство `[[Prototype]]` (так оно названо в спецификации), которое либо равно `null`, либо ссылается на другой объект. Этот объект называется "прототип": -![prototype](object-prototype-empty.png) +![prototype](object-prototype-empty.svg) -Этот `[[Prototype]]` дает нам немного "магии". Когда мы хотим прочитать свойство из `объекта`, а оно отсутствует, JavaScript автоматически берет его из прототипа. В программировании такой механизм называется "прототипным наследованием". Многие интересные возможности языка и техники программирования основываются на нем. +Прототип даёт нам немного "магии". Когда мы хотим прочитать свойство из `object`, а оно отсутствует, JavaScript автоматически берёт его из прототипа. В программировании такой механизм называется "прототипным наследованием". Многие интересные возможности языка и техники программирования основываются на нём. -Свойство `[[Prototype]]` является внутренним и скрытым, но есть много способов задать его самостоятельно. +Свойство `[[Prototype]]` является внутренним и скрытым, но есть много способов задать его. Одним из них является использование `__proto__`, например так: -```js run +```js let animal = { eats: true }; + let rabbit = { jumps: true }; @@ -31,15 +32,7 @@ rabbit.__proto__ = animal; */!* ``` -```smart header="Свойство `__proto__` — исторически обусловленный геттер/сеттер для `[[Prototype]]`" -Обратите внимание, что `__proto__` — *не то же самое*, что `[[Prototype]]`. Это геттер/сеттер для него. - -Он существует по историческим причинам, в современном языке его заменяют функции `Object.getPrototypeOf/Object.setPrototypeOf`, которые также получают/устанавливают прототип. Мы рассмотрим причины этого и эти функции позже. - -По спецификации `__proto__` должен поддерживаться только браузерами, но на самом деле все среды, включая серверную, поддерживают его. На данный момент, поскольку нотация `__proto__` немного более понятна, мы будем использовать ее в примерах. -``` - -Если мы ищем свойство в `rabbit`, а оно отсутствует, JavaScript автоматически берет его из `animal`. +Если мы ищем свойство в `rabbit`, а оно отсутствует, JavaScript автоматически берёт его из `animal`. Например: @@ -47,6 +40,7 @@ rabbit.__proto__ = animal; let animal = { eats: true }; + let rabbit = { jumps: true }; @@ -64,11 +58,11 @@ alert( rabbit.jumps ); // true Здесь строка `(*)` устанавливает `animal` как прототип для `rabbit`. -Затем, когда `alert` пытается прочитать свойство `rabbit.eats` `(**)`, его нет в `rabbit`, поэтому JavaScript следует по ссылке `[[Prototype]]` и находит ее в `animal` (смотрите снизу вверх): +Затем, когда `alert` пытается прочитать свойство `rabbit.eats` `(**)`, его нет в `rabbit`, поэтому JavaScript следует по ссылке `[[Prototype]]` и находит его в `animal` (смотрите снизу вверх): -![](proto-animal-rabbit.png) +![](proto-animal-rabbit.svg) -Здесь мы можем сказать, что "`animal` является прототипом `rabbit`" или "`rabbit` прототипно наследуется от `animal`". +Здесь мы можем сказать, что "`animal` является прототипом `rabbit`" или "`rabbit` прототипно наследует от `animal`". Так что если у `animal` много полезных свойств и методов, то они автоматически становятся доступными у `rabbit`. Такие свойства называются "унаследованными". @@ -95,13 +89,12 @@ rabbit.walk(); // Animal walk */!* ``` -Метод автоматически берется из прототипа: +Метод автоматически берётся из прототипа: -![](proto-animal-rabbit-walk.png) +![](proto-animal-rabbit-walk.svg) Цепочка прототипов может быть длиннее: - ```js run let animal = { eats: true, @@ -126,25 +119,39 @@ let longEar = { // walk взят из цепочки прототипов longEar.walk(); // Animal walk -alert(longEar.jumps); // true (для rabbit) +alert(longEar.jumps); // true (из rabbit) ``` -![](proto-animal-rabbit-chain.png) +![](proto-animal-rabbit-chain.svg) + +Теперь, если мы прочтём что-нибудь из `longEar`, и оно будет отсутствовать, JavaScript будет искать его в `rabbit`, а затем в `animal`. -На самом деле есть только два ограничения: +Есть только два ограничения: 1. Ссылки не могут идти по кругу. JavaScript выдаст ошибку, если мы попытаемся назначить `__proto__` по кругу. -2. Значение `__proto__` может быть как объектом, так и `null`, другие типы (например, примитивы) игнорируются. +2. Значение `__proto__` может быть объектом или `null`. Другие типы игнорируются. + +Это вполне очевидно, но всё же: может быть только один `[[Prototype]]`. Объект не может наследоваться от двух других объектов. + +```smart header="Свойство `__proto__` — исторически обусловленный геттер/сеттер для `[[Prototype]]`" +Это распространённая ошибка начинающих разработчиков - не знать разницы между этими двумя понятиями. + +Обратите внимание, что `__proto__` — *не то же самое*, что внутреннее свойство `[[Prototype]]`. Это геттер/сеттер для `[[Prototype]]`. Позже мы увидим ситуации, когда это имеет значение, а пока давайте просто будем иметь это в виду, поскольку мы строим наше понимание языка JavaScript. -Это вполне очевидно, но все же: может быть только один `[[Prototype]]`. Объект не может наследоваться от двух других. +Свойство `__proto__` немного устарело, оно существует по историческим причинам. Современный JavaScript предполагает, что мы должны использовать функции `Object.getPrototypeOf/Object.setPrototypeOf` вместо того, чтобы получать/устанавливать прототип. Мы также рассмотрим эти функции позже. -## Запись не использует прототип +По спецификации `__proto__` должен поддерживаться только браузерами, но по факту все среды, включая серверную, поддерживают его. Так что мы вполне безопасно его используем. + +Далее мы будем в примерах использовать `__proto__`, так как это самый короткий и интуитивно понятный способ установки и чтения прототипа. +``` + +## Операция записи не использует прототип Прототип используется только для чтения свойств. Операции записи/удаления работают напрямую с объектом. -В приведенном ниже примере мы присваиваем `rabbit` собственный метод `walk`: +В приведённом ниже примере мы присваиваем `rabbit` собственный метод `walk`: ```js run let animal = { @@ -169,11 +176,11 @@ rabbit.walk(); // Rabbit! Bounce-bounce! Теперь вызов `rabbit.walk()` находит метод непосредственно в объекте и выполняет его, не используя прототип: -![](proto-animal-rabbit-walk-2.png) +![](proto-animal-rabbit-walk-2.svg) -Это справедливо только для свойств данных, но не для аксессоров. Если свойство является геттером/сеттером, то оно ведет себя как функция: геттеры/сеттеры ищутся в прототипе. +Свойства-аксессоры - исключение, так как запись в него обрабатывается функцией-сеттером. То есть это фактически вызов функции. -По этой причине `admin.fullName` работает корректно в приведенном ниже коде: +По этой причине `admin.fullName` работает корректно в приведённом ниже коде: ```js run let user = { @@ -198,28 +205,30 @@ alert(admin.fullName); // John Smith (*) // срабатывает сеттер! admin.fullName = "Alice Cooper"; // (**) +alert(admin.name); // Alice +alert(admin.surname); // Cooper ``` Здесь в строке `(*)` свойство `admin.fullName` имеет геттер в прототипе `user`, поэтому вызывается он. В строке `(**)` свойство также имеет сеттер в прототипе, который и будет вызван. ## Значение "this" -В приведенном выше примере может возникнуть интересный вопрос: каково значение `this` внутри `set fullName(value)`? Куда записаны свойства `this.name` и `this.surname`: в `user` или `admin`? +В приведённом выше примере может возникнуть интересный вопрос: каково значение `this` внутри `set fullName(value)`? Куда записаны свойства `this.name` и `this.surname`: в `user` или в `admin`? Ответ прост: прототипы никак не влияют на `this`. -**Неважно, где находится метод: в объекте или его прототипе. В вызове метода, `this` — всегда объект перед точкой.** +**Неважно, где находится метод: в объекте или его прототипе. При вызове метода `this` — всегда объект перед точкой.** Таким образом, вызов сеттера `admin.fullName=` в качестве `this` использует `admin`, а не `user`. -Это на самом деле очень важная деталь, потому что у нас может быть большой объект со множеством методов, от которого можно унаследоваться. Затем наследуемые объекты могут выполнять его методы, и они будут изменять состояние этих объектов, а не большого. +Это на самом деле очень важная деталь, потому что у нас может быть большой объект со множеством методов, от которого можно наследовать. Затем наследующие объекты могут вызывать его методы, но они будут изменять своё состояние, а не состояние объекта-родителя. -Например, здесь `animal` представляет собой "хранилище метода", и `rabbit` использует его. +Например, здесь `animal` представляет собой "хранилище методов", и `rabbit` использует его. Вызов `rabbit.sleep()` устанавливает `this.isSleeping` для объекта `rabbit`: ```js run -// animal has methods +// методы animal let animal = { walk() { if (!this.isSleeping) { @@ -245,16 +254,86 @@ alert(animal.isSleeping); // undefined (нет такого свойства в Картинка с результатом: -![](proto-animal-rabbit-walk-3.png) +![](proto-animal-rabbit-walk-3.svg) -Если бы у нас были другие объекты, такие как `bird`, `snake` и т.д., унаследованные от `animal`, они также получили бы доступ к методам `animal`. Но `this` в каждом методе будет соответствовать объекту, на котором происходит вызов (до точки), а не `animal`. Поэтому, когда мы записываем данные в `this`, они сохраняются в этих объектах. +Если бы у нас были другие объекты, такие как `bird`, `snake` и т.д., унаследованные от `animal`, они также получили бы доступ к методам `animal`. Но `this` при вызове каждого метода будет соответствовать объекту (перед точкой), на котором происходит вызов, а не `animal`. Поэтому, когда мы записываем данные в `this`, они сохраняются в этих объектах. В результате методы являются общими, а состояние объекта — нет. +## Цикл for..in + +Цикл `for..in` проходит не только по собственным, но и по унаследованным свойствам объекта. + +Например: + +```js run +let animal = { + eats: true +}; + +let rabbit = { + jumps: true, + __proto__: animal +}; + +*!* +// Object.keys возвращает только собственные ключи +alert(Object.keys(rabbit)); // jumps +*/!* + +*!* +// for..in проходит и по своим, и по унаследованным ключам +for(let prop in rabbit) alert(prop); // jumps, затем eats +*/!* +``` + +Если унаследованные свойства нам не нужны, то мы можем отфильтровать их при помощи встроенного метода [obj.hasOwnProperty(key)](mdn:js/Object/hasOwnProperty): он возвращает `true`, если у `obj` есть собственное, не унаследованное, свойство с именем `key`. + +Пример такой фильтрации: + +```js run +let animal = { + eats: true +}; + +let rabbit = { + jumps: true, + __proto__: animal +}; + +for(let prop in rabbit) { + let isOwn = rabbit.hasOwnProperty(prop); + + if (isOwn) { + alert(`Our: ${prop}`); // Our: jumps + } else { + alert(`Inherited: ${prop}`); // Inherited: eats + } +} +``` + +В этом примере цепочка наследования выглядит так: `rabbit` наследует от `animal`, который наследует от `Object.prototype` (так как `animal` - литеральный объект `{...}`, то это по умолчанию), а затем `null` на самом верху: + +![](rabbit-animal-object.svg) + +Заметим ещё одну деталь. Откуда взялся метод `rabbit.hasOwnProperty`? Мы его явно не определяли. Если посмотреть на цепочку прототипов, то видно, что он берётся из `Object.prototype.hasOwnProperty`. То есть он унаследован. + +...Но почему `hasOwnProperty` не появляется в цикле `for..in` в отличие от `eats` и `jumps`? Он ведь перечисляет все унаследованные свойства. + +Ответ простой: оно не перечислимо. То есть у него внутренний флаг `enumerable` стоит `false`, как и у других свойств `Object.prototype`. Поэтому оно и не появляется в цикле. + +```smart header="Почти все остальные методы получения ключей/значений игнорируют унаследованные свойства" +Почти все остальные методы, получающие ключи/значения, такие как `Object.keys`, `Object.values` и другие - игнорируют унаследованные свойства. + +Они учитывают только свойства самого объекта, не его прототипа. +``` + ## Итого - В JavaScript все объекты имеют скрытое свойство `[[Prototype]]`, которое является либо другим объектом, либо `null`. -- Мы можем использовать `obj.__ proto__` для доступа к нему (исторически обусловленный геттер/сеттер, есть другие способы, которые скоро будут рассмотрены). +- Мы можем использовать `obj.__proto__` для доступа к нему (исторически обусловленный геттер/сеттер, есть другие способы, которые скоро будут рассмотрены). - Объект, на который ссылается `[[Prototype]]`, называется "прототипом". -- Если мы хотим прочитать свойство `obj` или вызвать метод, которого не существует, тогда JavaScript попытается найти его в прототипе. Операции записи/удаления работают непосредственно с объектом, они не используют прототип (если свойство фактически не является сеттером). -- Если мы вызываем `obj.method()`, а метод взят из прототипа, то `this` все равно ссылается на `obj`. Таким образом, методы всегда работают с текущим объектом, даже если они наследуются. +- Если мы хотим прочитать свойство `obj` или вызвать метод, которого не существует у `obj`, тогда JavaScript попытается найти его в прототипе. +- Операции записи/удаления работают непосредственно с объектом, они не используют прототип (если это обычное свойство, а не сеттер). +- Если мы вызываем `obj.method()`, а метод при этом взят из прототипа, то `this` всё равно ссылается на `obj`. Таким образом, методы всегда работают с текущим объектом, даже если они наследуются. +- Цикл `for..in` перебирает как свои, так и унаследованные свойства. Остальные методы получения ключей/значений работают только с собственными свойствами объекта. diff --git a/1-js/08-prototypes/01-prototype-inheritance/object-prototype-empty.png b/1-js/08-prototypes/01-prototype-inheritance/object-prototype-empty.png deleted file mode 100644 index 3ecff3fddf..0000000000 Binary files a/1-js/08-prototypes/01-prototype-inheritance/object-prototype-empty.png and /dev/null differ 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 new file mode 100644 index 0000000000..982f2e4c17 --- /dev/null +++ b/1-js/08-prototypes/01-prototype-inheritance/object-prototype-empty.svg @@ -0,0 +1 @@ +прототип objectobject[[Prototype]] \ No newline at end of file diff --git a/1-js/08-prototypes/01-prototype-inheritance/object-prototype-empty@2x.png b/1-js/08-prototypes/01-prototype-inheritance/object-prototype-empty@2x.png deleted file mode 100644 index f80e61e680..0000000000 Binary files a/1-js/08-prototypes/01-prototype-inheritance/object-prototype-empty@2x.png and /dev/null differ diff --git a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-chain.png b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-chain.png deleted file mode 100644 index 48e02ad18b..0000000000 Binary files a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-chain.png and /dev/null differ 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 new file mode 100644 index 0000000000..4bf580ae77 --- /dev/null +++ b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-chain.svg @@ -0,0 +1 @@ +eats: true walk: functionanimaljumps: truerabbit[[Prototype]]earLength: 10longEar[[Prototype]] \ No newline at end of file diff --git a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-chain@2x.png b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-chain@2x.png deleted file mode 100644 index 1a04db885e..0000000000 Binary files a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-chain@2x.png and /dev/null differ diff --git a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk-2.png b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk-2.png deleted file mode 100644 index 22b867fc88..0000000000 Binary files a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk-2.png and /dev/null differ 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 new file mode 100644 index 0000000000..838c78395b --- /dev/null +++ b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk-2.svg @@ -0,0 +1 @@ +eats: true walk: functionanimalwalk: functionrabbit[[Prototype]] \ No newline at end of file diff --git a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk-2@2x.png b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk-2@2x.png deleted file mode 100644 index 8d9f9f1ae9..0000000000 Binary files a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk-2@2x.png and /dev/null differ diff --git a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk-3.png b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk-3.png deleted file mode 100644 index bc76d64baa..0000000000 Binary files a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk-3.png and /dev/null differ 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 new file mode 100644 index 0000000000..e994331dab --- /dev/null +++ b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk-3.svg @@ -0,0 +1 @@ +walk: function sleep: functionanimalrabbit[[Prototype]]name: "Белый кролик" isSleeping: true \ No newline at end of file diff --git a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk-3@2x.png b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk-3@2x.png deleted file mode 100644 index 725a4c150b..0000000000 Binary files a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk-3@2x.png and /dev/null differ diff --git a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk.png b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk.png deleted file mode 100644 index 29fc6d5039..0000000000 Binary files a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk.png and /dev/null differ 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 new file mode 100644 index 0000000000..b324710286 --- /dev/null +++ b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk.svg @@ -0,0 +1 @@ +eats: true walk: functionanimaljumps: truerabbit[[Prototype]] \ No newline at end of file diff --git a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk@2x.png b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk@2x.png deleted file mode 100644 index 724d6111b6..0000000000 Binary files a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk@2x.png and /dev/null differ diff --git a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit.png b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit.png deleted file mode 100644 index d50327f0e7..0000000000 Binary files a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit.png and /dev/null differ 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 new file mode 100644 index 0000000000..4f3c1bc0ec --- /dev/null +++ b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit.svg @@ -0,0 +1 @@ +eats: trueanimaljumps: truerabbit[[Prototype]] \ No newline at end of file diff --git a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit@2x.png b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit@2x.png deleted file mode 100644 index 0506b92b69..0000000000 Binary files a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit@2x.png and /dev/null differ diff --git a/1-js/08-prototypes/01-prototype-inheritance/proto-user-admin.png b/1-js/08-prototypes/01-prototype-inheritance/proto-user-admin.png deleted file mode 100644 index c37d007804..0000000000 Binary files a/1-js/08-prototypes/01-prototype-inheritance/proto-user-admin.png and /dev/null differ 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 new file mode 100644 index 0000000000..bf0baf013a --- /dev/null +++ b/1-js/08-prototypes/01-prototype-inheritance/proto-user-admin.svg @@ -0,0 +1 @@ +name: "John" surname: "Smith" set fullName: functionisAdmin: true name: "Alice" surname: "Cooper"useradmin[[Prototype]] \ No newline at end of file diff --git a/1-js/08-prototypes/01-prototype-inheritance/proto-user-admin@2x.png b/1-js/08-prototypes/01-prototype-inheritance/proto-user-admin@2x.png deleted file mode 100644 index 8c3f546ebc..0000000000 Binary files a/1-js/08-prototypes/01-prototype-inheritance/proto-user-admin@2x.png and /dev/null differ 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 new file mode 100644 index 0000000000..32a9858f83 --- /dev/null +++ b/1-js/08-prototypes/01-prototype-inheritance/rabbit-animal-object.svg @@ -0,0 +1 @@ +toString: function hasOwnProperty: function ...Object.prototypeanimal[[Prototype]][[Prototype]][[Prototype]]nulleats: truerabbitjumps: true \ No newline at end of file diff --git a/1-js/08-prototypes/02-function-prototype/1-changing-prototype/solution.md b/1-js/08-prototypes/02-function-prototype/1-changing-prototype/solution.md index 771e3061c9..6401a40c53 100644 --- a/1-js/08-prototypes/02-function-prototype/1-changing-prototype/solution.md +++ b/1-js/08-prototypes/02-function-prototype/1-changing-prototype/solution.md @@ -1,20 +1,20 @@ -Answers: +Ответы: -1. `true`. +1. `true`. - The assignment to `Rabbit.prototype` sets up `[[Prototype]]` for new objects, but it does not affect the existing ones. + Присвоение нового значения свойству `Rabbit.prototype` влияет на `[[Prototype]]` вновь создаваемых объектов, но не на прототип уже существующих. -2. `false`. +2. `false`. - Objects are assigned by reference. The object from `Rabbit.prototype` is not duplicated, it's still a single object is referenced both by `Rabbit.prototype` and by the `[[Prototype]]` of `rabbit`. + Объекты присваиваются по ссылке. Не создаётся копия `Rabbit.prototype`, это всегда один объект, на который ссылается и `Rabbit.prototype`, и `[[Prototype]]` объекта `rabbit`. - So when we change its content through one reference, it is visible through the other one. + Таким образом, когда мы изменяем этот объект по одной ссылке, изменения видны и по другой. 3. `true`. - All `delete` operations are applied directly to the object. Here `delete rabbit.eats` tries to remove `eats` property from `rabbit`, but it doesn't have it. So the operation won't have any effect. + Операция `delete` применяется к свойствам конкретного объекта, на котором она вызвана. Здесь `delete rabbit.eats` пытается удалить свойство `eats` из объекта `rabbit`, но его там нет. Таким образом, просто ничего не произойдёт. 4. `undefined`. - The property `eats` is deleted from the prototype, it doesn't exist any more. + Свойство `eats` удалено из прототипа, оно больше не существует. diff --git a/1-js/08-prototypes/02-function-prototype/1-changing-prototype/task.md b/1-js/08-prototypes/02-function-prototype/1-changing-prototype/task.md index 4b8522d3dd..613f6c1b81 100644 --- a/1-js/08-prototypes/02-function-prototype/1-changing-prototype/task.md +++ b/1-js/08-prototypes/02-function-prototype/1-changing-prototype/task.md @@ -2,11 +2,11 @@ importance: 5 --- -# Changing "prototype" +# Изменяем "prototype" -In the code below we create `new Rabbit`, and then try to modify its prototype. +В коде ниже мы создаём нового кролика `new Rabbit`, а потом пытаемся изменить его прототип. -In the start, we have this code: +Сначала у нас есть такой код: ```js run function Rabbit() {} @@ -20,7 +20,7 @@ alert( rabbit.eats ); // true ``` -1. We added one more string (emphasized), what `alert` shows now? +1. Добавим одну строчку (выделенную в коде ниже). Что вызов `alert` покажет нам сейчас? ```js function Rabbit() {} @@ -37,7 +37,7 @@ alert( rabbit.eats ); // true alert( rabbit.eats ); // ? ``` -2. ...And if the code is like this (replaced one line)? +2. ...А если код такой (заменили одну строчку)? ```js function Rabbit() {} @@ -54,7 +54,7 @@ alert( rabbit.eats ); // true alert( rabbit.eats ); // ? ``` -3. Like this (replaced one line)? +3. Или такой (заменили одну строчку)? ```js function Rabbit() {} @@ -71,7 +71,7 @@ alert( rabbit.eats ); // true alert( rabbit.eats ); // ? ``` -4. The last variant: +4. Или, наконец, такой: ```js function Rabbit() {} 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 43190e163b..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 @@ -1,6 +1,6 @@ -We can use such approach if we are sure that `"constructor"` property has the correct value. +Мы можем использовать такой способ, если мы уверены в том, что свойство `"constructor"` существующего объекта имеет корректное значение. -For instance, if we don't touch the default `"prototype"`, then this code works for sure: +Например, если мы не меняли `"prototype"`, используемый по умолчанию, то код ниже, без сомнений, сработает: ```js run function User(name) { @@ -10,14 +10,14 @@ function User(name) { let user = new User('John'); let user2 = new user.constructor('Pete'); -alert( user2.name ); // Pete (worked!) +alert( user2.name ); // Pete (сработало!) ``` -It worked, because `User.prototype.constructor == User`. +Всё получилось, потому что `User.prototype.constructor == User`. -..But if someone, so to say, overwrites `User.prototype` and forgets to recreate `"constructor"`, then it would fail. +...Но если кто-то перезапишет `User.prototype` и забудет заново назначить свойство `"constructor"`, чтобы оно указывало на `User`, то ничего не выйдет. -For instance: +Например: ```js run function User(name) { @@ -33,12 +33,17 @@ let user2 = new user.constructor('Pete'); alert( user2.name ); // undefined ``` -Why `user2.name` is `undefined`? +Почему `user2.name` приняло значение `undefined`? -Here's how `new user.constructor('Pete')` works: +Рассмотрим, как отработал вызов `new user.constructor('Pete')`: -1. First, it looks for `constructor` in `user`. Nothing. -2. Then it follows the prototype chain. The prototype of `user` is `User.prototype`, and it also has nothing. -3. The value of `User.prototype` is a plain object `{}`, its prototype is `Object.prototype`. And there is `Object.prototype.constructor == Object`. So it is used. +1. Сначала ищется свойство `constructor` в объекте `user`. Не нашлось. +2. Потом задействуется поиск по цепочке прототипов. Прототип объекта `user` -- это `User.prototype`, и там тоже нет искомого свойства. +3. Идя дальше по цепочке, значение `User.prototype` -- это пустой объект `{}`, чей прототип -- встроенный `Object.prototype`. +4. Наконец, для встроенного `Object.prototype` предусмотрен встроенный `Object.prototype.constructor == Object`. Таким образом, свойство `constructor` всё-таки найдено. -At the end, we have `let user2 = new Object('Pete')`. The built-in `Object` constructor ignores arguments, it always creates an empty object -- that's what we have in `user2` after all. +В итоге срабатывает `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/4-new-object-same-constructor/task.md b/1-js/08-prototypes/02-function-prototype/4-new-object-same-constructor/task.md index 934f3470b9..4a00088524 100644 --- a/1-js/08-prototypes/02-function-prototype/4-new-object-same-constructor/task.md +++ b/1-js/08-prototypes/02-function-prototype/4-new-object-same-constructor/task.md @@ -2,14 +2,14 @@ importance: 5 --- -# Create an object with the same constructor +# Создайте новый объект с помощью уже существующего -Imagine, we have an arbitrary object `obj`, created by a constructor function -- we don't know which one, but we'd like to create a new object using it. +Представьте, что у нас имеется некий объект `obj`, созданный функцией-конструктором -- мы не знаем какой именно, но хотелось бы создать ещё один объект такого же типа. -Can we do it like that? +Можем ли мы сделать так? ```js let obj2 = new obj.constructor(); ``` -Give an example of a constructor function for `obj` which lets such code work right. And an example that makes it work wrong. +Приведите пример функции-конструктора для объекта `obj`, с которой такой вызов корректно сработает. И пример функции-конструктора, с которой такой код поведёт себя неправильно. diff --git a/1-js/08-prototypes/02-function-prototype/article.md b/1-js/08-prototypes/02-function-prototype/article.md index c08667c7b7..cba991f886 100644 --- a/1-js/08-prototypes/02-function-prototype/article.md +++ b/1-js/08-prototypes/02-function-prototype/article.md @@ -1,18 +1,18 @@ # F.prototype -Remember, new objects can be created with a constructor function, like `new F()`. +Как мы помним, новые объекты могут быть созданы с помощью функции-конструктора `new F()`. -If `F.prototype` is an object, then `new` operator uses it to set `[[Prototype]]` for the new object. +Если в `F.prototype` содержится объект, оператор `new` устанавливает его в качестве `[[Prototype]]` для нового объекта. ```smart -JavaScript had prototypal inheritance from the beginning. It was one of the core features of the language. +JavaScript использовал прототипное наследование с момента своего появления. Это одна из основных особенностей языка. -But in the old times, there was no direct access to it. The only thing that worked reliably was a `"prototype"` property of the constructor function, described in this chapter. So there are many scripts that still use it. +Но раньше, в старые времена, прямого доступа к прототипу объекта не было. Надёжно работало только свойство `"prototype"` функции-конструктора, описанное в этой главе. Поэтому оно используется во многих скриптах. ``` -Please note that `F.prototype` here means a regular property named `"prototype"` on `F`. It sounds something similar to the term "prototype", but here we really mean a regular property with this name. +Обратите внимание, что `F.prototype` означает обычное свойство с именем `"prototype"` для `F`. Это ещё не "прототип объекта", а обычное свойство `F` с таким именем. -Here's the example: +Приведём пример: ```js run let animal = { @@ -32,65 +32,65 @@ let rabbit = new Rabbit("White Rabbit"); // rabbit.__proto__ == animal alert( rabbit.eats ); // true ``` -Setting `Rabbit.prototype = animal` literally states the following: "When a `new Rabbit` is created, assign its `[[Prototype]]` to `animal`". +Установка `Rabbit.prototype = animal` буквально говорит интерпретатору следующее: "При создании объекта через `new Rabbit()` запиши ему `animal` в `[[Prototype]]`". -That's the resulting picture: +Результат будет выглядеть так: -![](proto-constructor-animal-rabbit.png) +![](proto-constructor-animal-rabbit.svg) -On the picture, `"prototype"` is a horizontal arrow, meaning a regular property, and `[[Prototype]]` is vertical, meaning the inheritance of `rabbit` from `animal`. +На изображении: `"prototype"` - горизонтальная стрелка, обозначающая обычное свойство для `"F"`, а `[[Prototype]]` -- вертикальная, обозначающая наследование `rabbit` от `animal`. -```smart header="`F.prototype` only used at `new F` time" -`F.prototype` property is only used when `new F` is called, it assigns `[[Prototype]]` of the new object. After that, there's no connection between `F.prototype` and the new object. Think of it as a "one-time gift". +```smart header="`F.prototype` используется только в момент вызова `new F`" +`F.prototype` используется только при вызове `new F` и присваивается в качестве свойства `[[Prototype]]` нового объекта. -If, after the creation, `F.prototype` property changes (`F.prototype = `), then new objects created by `new F` will have another object as `[[Prototype]]`, but already existing objects keep the old one. +Если после создания свойство `F.prototype` изменится (`F.prototype = <другой объект>`), то новые объекты, созданные с помощью `new F`, будут иметь в качестве `[[Prototype]]` другой объект, а уже существующие объекты сохранят старый. ``` -## Default F.prototype, constructor property +## F.prototype по умолчанию, свойство constructor -Every function has the `"prototype"` property even if we don't supply it. +У каждой функции (за исключением стрелочных) по умолчанию уже есть свойство `"prototype"`. -The default `"prototype"` is an object with the only property `constructor` that points back to the function itself. +По умолчанию `"prototype"` - объект с единственным свойством `constructor`, которое ссылается на функцию-конструктор. -Like this: +Вот такой: ```js function Rabbit() {} -/* default prototype +/* прототип по умолчанию Rabbit.prototype = { constructor: Rabbit }; */ ``` -![](function-prototype-constructor.png) +![](function-prototype-constructor.svg) -We can check it: +Проверим это: ```js run function Rabbit() {} -// by default: +// по умолчанию: // Rabbit.prototype = { constructor: Rabbit } alert( Rabbit.prototype.constructor == Rabbit ); // true ``` -Naturally, if we do nothing, the `constructor` property is available to all rabbits through `[[Prototype]]`: +Соответственно, если мы ничего не меняем, то свойство `constructor` будет доступно всем кроликам через `[[Prototype]]`: ```js run function Rabbit() {} -// by default: +// по умолчанию: // Rabbit.prototype = { constructor: Rabbit } -let rabbit = new Rabbit(); // inherits from {constructor: Rabbit} +let rabbit = new Rabbit(); // наследует от {constructor: Rabbit} -alert(rabbit.constructor == Rabbit); // true (from prototype) +alert(rabbit.constructor == Rabbit); // true (свойство получено из прототипа) ``` -![](rabbit-prototype-constructor.png) +![](rabbit-prototype-constructor.svg) -We can use `constructor` property to create a new object using the same constructor as the existing one. +Мы можем использовать свойство `constructor` существующего объекта для создания нового. -Like here: +Пример: ```js run function Rabbit(name) { @@ -105,17 +105,17 @@ let rabbit2 = new rabbit.constructor("Black Rabbit"); */!* ``` -That's handy when we have an object, don't know which constructor was used for it (e.g. it comes from a 3rd party library), and we need to create another one of the same kind. +Это удобно, когда у нас есть объект, но мы не знаем, какой конструктор использовался для его создания (например, он мог быть взят из сторонней библиотеки), а нам необходимо создать ещё один такой объект. -But probably the most important thing about `"constructor"` is that... +Но, пожалуй, самое важное о свойстве `"constructor"` это то, что... -**...JavaScript itself does not ensure the right `"constructor"` value.** +**...JavaScript сам по себе не гарантирует правильное значение свойства `"constructor"`.** -Yes, it exists in the default `"prototype"` for functions, but that's all. What happens with it later -- is totally on us. +Да, оно является свойством по умолчанию в `"prototype"` у функций, но что случится с ним позже -- зависит только от нас. -In particular, if we replace the default prototype as a whole, then there will be no `"constructor"` in it. +В частности, если мы заменим прототип по умолчанию на другой объект, то свойства `"constructor"` в нём не будет. -For instance: +Например: ```js run function Rabbit() {} @@ -129,18 +129,18 @@ alert(rabbit.constructor === Rabbit); // false */!* ``` -So, to keep the right `"constructor"` we can choose to add/remove properties to the default `"prototype"` instead of overwriting it as a whole: +Таким образом, чтобы сохранить верное свойство `"constructor"`, мы должны добавлять/удалять/изменять свойства у прототипа по умолчанию вместо того, чтобы перезаписывать его целиком: ```js function Rabbit() {} -// Not overwrite Rabbit.prototype totally -// just add to it +// Не перезаписываем Rabbit.prototype полностью, +// а добавляем к нему свойство Rabbit.prototype.jumps = true -// the default Rabbit.prototype.constructor is preserved +// Прототип по умолчанию сохраняется, и мы всё ещё имеем доступ к Rabbit.prototype.constructor ``` -Or, alternatively, recreate the `constructor` property manually: +Или мы можем заново создать свойство `constructor`: ```js Rabbit.prototype = { @@ -150,26 +150,26 @@ Rabbit.prototype = { */!* }; -// now constructor is also correct, because we added it +// теперь свойство constructor снова корректное, так как мы добавили его ``` -## Summary +## Итого -In this chapter we briefly described the way of setting a `[[Prototype]]` for objects created via a constructor function. Later we'll see more advanced programming patterns that rely on it. +В этой главе мы кратко описали способ задания `[[Prototype]]` для объектов, создаваемых с помощью функции-конструктора. Позже мы рассмотрим, как можно использовать эту возможность. -Everything is quite simple, just few notes to make things clear: +Всё достаточно просто. Выделим основные моменты: -- The `F.prototype` property is not the same as `[[Prototype]]`. The only thing `F.prototype` does: it sets `[[Prototype]]` of new objects when `new F()` is called. -- The value of `F.prototype` should be either an object or null: other values won't work. -- The `"prototype"` property only has such a special effect when is set to a constructor function, and invoked with `new`. +- Свойство `F.prototype` (не путать с `[[Prototype]]`) устанавливает `[[Prototype]]` для новых объектов при вызове `new F()`. +- Значение `F.prototype` должно быть либо объектом, либо `null`. Другие значения не будут работать. +- Свойство `"prototype"` является особым, только когда оно назначено функции-конструктору, которая вызывается оператором `new`. -On regular objects the `prototype` is nothing special: +В обычных объектах `prototype` не является чем-то особенным: ```js let user = { name: "John", - prototype: "Bla-bla" // no magic at all + prototype: "Bla-bla" // никакой магии нет - обычное свойство }; ``` -By default all functions have `F.prototype = { constructor: F }`, so we can get the constructor of an object by accessing its `"constructor"` property. +По умолчанию все функции имеют `F.prototype = { constructor: F }`, поэтому мы можем получить конструктор объекта через свойство `"constructor"`. diff --git a/1-js/08-prototypes/02-function-prototype/function-prototype-constructor.png b/1-js/08-prototypes/02-function-prototype/function-prototype-constructor.png deleted file mode 100644 index 6ab9c6934a..0000000000 Binary files a/1-js/08-prototypes/02-function-prototype/function-prototype-constructor.png and /dev/null differ 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 new file mode 100644 index 0000000000..1fced82b39 --- /dev/null +++ b/1-js/08-prototypes/02-function-prototype/function-prototype-constructor.svg @@ -0,0 +1 @@ +Rabbitprototypeconstructor"prototype" по умолчанию \ No newline at end of file diff --git a/1-js/08-prototypes/02-function-prototype/function-prototype-constructor@2x.png b/1-js/08-prototypes/02-function-prototype/function-prototype-constructor@2x.png deleted file mode 100644 index 3beb0a5733..0000000000 Binary files a/1-js/08-prototypes/02-function-prototype/function-prototype-constructor@2x.png and /dev/null differ diff --git a/1-js/08-prototypes/02-function-prototype/native-prototypes-array-tostring.png b/1-js/08-prototypes/02-function-prototype/native-prototypes-array-tostring.png deleted file mode 100644 index c47938674b..0000000000 Binary files a/1-js/08-prototypes/02-function-prototype/native-prototypes-array-tostring.png and /dev/null differ diff --git a/1-js/08-prototypes/02-function-prototype/native-prototypes-array-tostring@2x.png b/1-js/08-prototypes/02-function-prototype/native-prototypes-array-tostring@2x.png deleted file mode 100644 index 293c88bf93..0000000000 Binary files a/1-js/08-prototypes/02-function-prototype/native-prototypes-array-tostring@2x.png and /dev/null differ diff --git a/1-js/08-prototypes/02-function-prototype/native-prototypes-classes.png b/1-js/08-prototypes/02-function-prototype/native-prototypes-classes.png deleted file mode 100644 index c4658a9291..0000000000 Binary files a/1-js/08-prototypes/02-function-prototype/native-prototypes-classes.png and /dev/null differ diff --git a/1-js/08-prototypes/02-function-prototype/native-prototypes-classes@2x.png b/1-js/08-prototypes/02-function-prototype/native-prototypes-classes@2x.png deleted file mode 100644 index 79445fac90..0000000000 Binary files a/1-js/08-prototypes/02-function-prototype/native-prototypes-classes@2x.png and /dev/null differ diff --git a/1-js/08-prototypes/02-function-prototype/object-prototype-1.png b/1-js/08-prototypes/02-function-prototype/object-prototype-1.png deleted file mode 100644 index a69cad4a1b..0000000000 Binary files a/1-js/08-prototypes/02-function-prototype/object-prototype-1.png and /dev/null differ diff --git a/1-js/08-prototypes/02-function-prototype/object-prototype-1@2x.png b/1-js/08-prototypes/02-function-prototype/object-prototype-1@2x.png deleted file mode 100644 index 9d661ac17e..0000000000 Binary files a/1-js/08-prototypes/02-function-prototype/object-prototype-1@2x.png and /dev/null differ diff --git a/1-js/08-prototypes/02-function-prototype/object-prototype.png b/1-js/08-prototypes/02-function-prototype/object-prototype.png deleted file mode 100644 index 820ffcc736..0000000000 Binary files a/1-js/08-prototypes/02-function-prototype/object-prototype.png and /dev/null differ diff --git a/1-js/08-prototypes/02-function-prototype/object-prototype@2x.png b/1-js/08-prototypes/02-function-prototype/object-prototype@2x.png deleted file mode 100644 index 1eb7e8749d..0000000000 Binary files a/1-js/08-prototypes/02-function-prototype/object-prototype@2x.png and /dev/null differ diff --git a/1-js/08-prototypes/02-function-prototype/proto-constructor-animal-rabbit.png b/1-js/08-prototypes/02-function-prototype/proto-constructor-animal-rabbit.png deleted file mode 100644 index 4b381460a5..0000000000 Binary files a/1-js/08-prototypes/02-function-prototype/proto-constructor-animal-rabbit.png and /dev/null differ 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 new file mode 100644 index 0000000000..ede4e1227e --- /dev/null +++ b/1-js/08-prototypes/02-function-prototype/proto-constructor-animal-rabbit.svg @@ -0,0 +1 @@ +eats: truename: "White Rabbit"animalRabbitrabbit[[Prototype]]prototype \ No newline at end of file diff --git a/1-js/08-prototypes/02-function-prototype/proto-constructor-animal-rabbit@2x.png b/1-js/08-prototypes/02-function-prototype/proto-constructor-animal-rabbit@2x.png deleted file mode 100644 index c6e6ab5f60..0000000000 Binary files a/1-js/08-prototypes/02-function-prototype/proto-constructor-animal-rabbit@2x.png and /dev/null differ diff --git a/1-js/08-prototypes/02-function-prototype/rabbit-animal-object.png b/1-js/08-prototypes/02-function-prototype/rabbit-animal-object.png deleted file mode 100644 index 3924233ddd..0000000000 Binary files a/1-js/08-prototypes/02-function-prototype/rabbit-animal-object.png and /dev/null differ diff --git a/1-js/08-prototypes/02-function-prototype/rabbit-animal-object@2x.png b/1-js/08-prototypes/02-function-prototype/rabbit-animal-object@2x.png deleted file mode 100644 index 5350a19356..0000000000 Binary files a/1-js/08-prototypes/02-function-prototype/rabbit-animal-object@2x.png and /dev/null differ diff --git a/1-js/08-prototypes/02-function-prototype/rabbit-prototype-constructor.png b/1-js/08-prototypes/02-function-prototype/rabbit-prototype-constructor.png deleted file mode 100644 index 8f3519a747..0000000000 Binary files a/1-js/08-prototypes/02-function-prototype/rabbit-prototype-constructor.png and /dev/null differ 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 new file mode 100644 index 0000000000..2424ea648d --- /dev/null +++ b/1-js/08-prototypes/02-function-prototype/rabbit-prototype-constructor.svg @@ -0,0 +1 @@ +"prototype" по умолчаниюRabbitrabbit[[Prototype]]prototypeconstructor \ No newline at end of file diff --git a/1-js/08-prototypes/02-function-prototype/rabbit-prototype-constructor@2x.png b/1-js/08-prototypes/02-function-prototype/rabbit-prototype-constructor@2x.png deleted file mode 100644 index 07acb8f4a9..0000000000 Binary files a/1-js/08-prototypes/02-function-prototype/rabbit-prototype-constructor@2x.png and /dev/null differ diff --git a/1-js/08-prototypes/03-native-prototypes/article.md b/1-js/08-prototypes/03-native-prototypes/article.md index c8b728b906..7e365b3062 100644 --- a/1-js/08-prototypes/03-native-prototypes/article.md +++ b/1-js/08-prototypes/03-native-prototypes/article.md @@ -1,8 +1,8 @@ # Встроенные прототипы -Свойство `"prototype"` широко используется в самом ядре JavaScript. Все встроенные функции-конструкторы используют его. +Свойство `"prototype"` широко используется внутри самого языка JavaScript. Все встроенные функции-конструкторы используют его. -Сначала мы посмотрим как это работает для простых объектов, а затем и для более сложных. +Сначала мы рассмотрим детали, а затем используем `"prototype"` для добавления встроенным объектам новой функциональности. ## Object.prototype @@ -15,17 +15,17 @@ alert( obj ); // "[object Object]" ? Где код, который генерирует строку `"[object Object]"`? Это встроенный метод `toString`, но где он? `obj` ведь пуст! -...Но краткая нотация `obj = {}` это то же самое, что и `obj = new Object()`, где `Object` - встроенная функция-конструктор для объектов с собственным свойством `prototype`, который ссылается на огромный объект с методом `toString` и другими. +...Но краткая нотация `obj = {}` -- это то же самое, что и `obj = new Object()`, где `Object` - встроенная функция-конструктор для объектов с собственным свойством `prototype`, которое ссылается на огромный объект с методом `toString` и другими. Вот что происходит: -![](object-prototype.png) +![](object-prototype.svg) -Когда вызывается `new Object()` (или создаётся объект с помощью литерала `{...}`), свойство `[[Prototype]]` этого объекта устанавливается как `Object.prototype` по правилам, который мы обсуждали в предыдущей статье: +Когда вызывается `new Object()` (или создаётся объект с помощью литерала `{...}`), свойство `[[Prototype]]` этого объекта устанавливается на `Object.prototype` по правилам, которые мы обсуждали в предыдущей главе: -![](object-prototype-1.png) +![](object-prototype-1.svg) -Таким образом, когда вызывается `obj.toString()` метод берётся из `Object.prototype`. +Таким образом, когда вызывается `obj.toString()`, метод берётся из `Object.prototype`. Мы можем проверить это так: @@ -33,10 +33,10 @@ 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]]`: +Обратите внимание, что по цепочке прототипов выше `Object.prototype` больше нет свойства `[[Prototype]]`: ```js run alert(Object.prototype.__proto__); // null @@ -46,47 +46,47 @@ alert(Object.prototype.__proto__); // null Другие встроенные объекты, такие как `Array`, `Date`, `Function` и другие, также хранят свои методы в прототипах. -Например, когда создаётся массив `[1, 2, 3]`, скрыто используется конструктор по-умолчанию `new Array()`. Так данные массива записываются в новый объект, и прототипом этого объекта становится `Array.prototype`, предоставляя ему свои методы. Это позволяет эффективно использовать память. +Например, при создании массива `[1, 2, 3]` внутренне используется конструктор массива `Array`. Поэтому прототипом массива становится `Array.prototype`, предоставляя ему свои методы. Это позволяет эффективно использовать память. -Согласно спецификации, наверху иерархии встроенных прототипов находится `Object.prototype`. Иногда говорят, что "все наследуется от объектов". +Согласно спецификации, наверху иерархии встроенных прототипов находится `Object.prototype`. Поэтому иногда говорят, что "всё наследует от объектов". -Вот более полная картина (для 3 встроенных объектов): +Вот более полная картина (для трёх встроенных объектов): -![](native-prototypes-classes.png) +![](native-prototypes-classes.svg) Давайте проверим прототипы: ```js run let arr = [1, 2, 3]; -// наследует от Array.prototype? +// наследует ли от Array.prototype? alert( arr.__proto__ === Array.prototype ); // true -// затем от Object.prototype? +// затем наследует ли от Object.prototype? alert( arr.__proto__.__proto__ === Object.prototype ); // true // и null на вершине иерархии alert( arr.__proto__.__proto__.__proto__ ); // null ``` -Некоторые методы в прототипах могу пересекаться, например, у `Array.prototype` есть свой метод `toString`, который выводит элементы массива через запятую: +Некоторые методы в прототипах могут пересекаться, например, у `Array.prototype` есть свой метод `toString`, который выводит элементы массива через запятую: ```js run let arr = [1, 2, 3] alert(arr); // 1,2,3 <-- результат Array.prototype.toString ``` -Как мы видели раньше, у `Object.prototype` есть свой метод `toString`, но так как `Array.prototype` ближе в цепочке прототипов, то берётся именно вариант для массивов: +Как мы видели ранее, у `Object.prototype` есть свой метод `toString`, но так как `Array.prototype` ближе в цепочке прототипов, то берётся именно вариант для массивов: -![](native-prototypes-array-tostring.png) +![](native-prototypes-array-tostring.svg) -В браузерных инструментах, таких как консоль разработчика в Chrome, можно посмотреть цепочку наследования (возможно, потребуется использовать `console.dir` для встроенных объектов): +В браузерных инструментах, таких как консоль разработчика, можно посмотреть цепочку наследования (возможно, потребуется использовать `console.dir` для встроенных объектов): ![](console_dir_array.png) -Другие встроенные объекты работают таким же способом. Даже функции -- все они объекты встроенного конструктора `Function`, и все их методы (`call`/`apply` и другие) берутся из `Function.prototype`. Также у функций есть свой метод `toString`. +Другие встроенные объекты устроены аналогично. Даже функции -- они объекты встроенного конструктора `Function`, и все их методы (`call`/`apply` и другие) берутся из `Function.prototype`. Также у функций есть свой метод `toString`. ```js run function f() {} @@ -97,9 +97,9 @@ alert(f.__proto__.__proto__ == Object.prototype); // true, наследует о ## Примитивы -Самое сложное происходит со строкам, числами и булевым типом. +Самое сложное происходит со строками, числами и булевыми значениями. -Как мы помним, они не объекты. Но если мы попытаемся получить доступ к их свойствам, тогда будет создан временный объект-обёртка с использованием встроенных конструкторов `String`, `Number`, `Boolean`, который предоставит методы и после чего исчезнет. +Как мы помним, они не объекты. Но если мы попытаемся получить доступ к их свойствам, то тогда будет создан временный объект-обёртка с использованием встроенных конструкторов `String`, `Number` и `Boolean`, который предоставит методы и после этого исчезнет. Эти объекты создаются невидимо для нас, и большая часть движков оптимизирует этот процесс, но спецификация описывает это именно таким образом. Методы этих объектов также находятся в прототипах, доступных как `String.prototype`, `Number.prototype` и `Boolean.prototype`. @@ -119,19 +119,19 @@ String.prototype.show = function() { "BOOM!".show(); // BOOM! ``` -В течение процесса разработки у нас могут возникнуть идеи о новых встроенных методах, которые нам хотелось бы иметь, и мы можем испытать искушение добавить их во встроенные прототипы. Это плохая идея. +В течение процесса разработки у нас могут возникнуть идеи о новых встроенных методах, которые нам хотелось бы иметь, и искушение добавить их во встроенные прототипы. Это плохая идея. ```warn -Прототипы глобальны, поэтому очень легко могут возникнуть конфликты. Если две библиотеки добавляют методы `String.prototype.show`, то одна из них перепишет другой. +Прототипы глобальны, поэтому очень легко могут возникнуть конфликты. Если две библиотеки добавляют метод `String.prototype.show`, то одна из них перепишет метод другой. -Так что, в общем, изменение встроенных прототипов считается плохой идей. +Так что, в общем, изменение встроенных прототипов считается плохой идеей. ``` **В современном программировании есть только один случай, в котором одобряется изменение встроенных прототипов. Это создание полифилов.** -Полифил - это термин, который означает замену метода, который существует в спецификации JavaScript, но он ещё не поддерживается текущим движком JavaScript. +Полифил - это термин, который означает эмуляцию метода, который существует в спецификации JavaScript, но ещё не поддерживается текущим движком JavaScript. -Тогда мы можем реализовать его сами и добавить его во встроенный прототип. +Тогда мы можем реализовать его сами и добавить во встроенный прототип. Например: @@ -144,7 +144,7 @@ if (!String.prototype.repeat) { // Если такого метода нет // на самом деле код должен быть немного более сложным // (полный алгоритм можно найти в спецификации) - // но даже неполный полифил часто считается достаточно хорошим + // но даже неполный полифил зачастую достаточно хорош для использования return new Array(n + 1).join(this); }; } @@ -161,7 +161,7 @@ alert( "La".repeat(3) ); // LaLaLa Некоторые методы встроенных прототипов часто одалживают. -Например, если мы создаём объект, похожий на массив (псевдомассив), мы можем скопировать некоторые методы из Array в этот объект. +Например, если мы создаём объект, похожий на массив (псевдомассив), мы можем скопировать некоторые методы из `Array` в этот объект. Пример: @@ -179,11 +179,11 @@ obj.join = Array.prototype.join; alert( obj.join(',') ); // Hello,world! ``` -Это работает, потому что для внутреннего алгоритма встроенного метода `join` важна только корректность индексов и свойства `length`, он не проверяет является ли объект на самом деле массивом. И многие встроенные методы работают так же. +Это работает, потому что для внутреннего алгоритма встроенного метода `join` важны только корректность индексов и свойство `length`, он не проверяет, является ли объект на самом деле массивом. И многие встроенные методы работают так же. Альтернативная возможность - мы можем унаследовать от массива, установив `obj.__proto__` как `Array.prototype`, таким образом все методы `Array` станут автоматически доступны в `obj`. -Но это будет невозможно, если `obj` уже наследуется от другого объекта. Помните, мы можем наследоваться только от одного объекта одновременно. +Но это будет невозможно, если `obj` уже наследует от другого объекта. Помните, мы можем наследовать только от одного объекта одновременно. Заимствование методов - гибкий способ, позволяющий смешивать функциональность разных объектов по необходимости. @@ -192,5 +192,5 @@ alert( obj.join(',') ); // Hello,world! - Все встроенные объекты следуют одному шаблону: - Методы хранятся в прототипах (`Array.prototype`, `Object.prototype`, `Date.prototype` и т.д.). - Сами объекты хранят только данные (элементы массивов, свойства объектов, даты). -- Примитивы также хранят свои методы в прототипах объектах-обёртках: `Number.prototype`, `String.prototype`, `Boolean.prototype`. Только у значений `undefined` и `null` нет объектов-обёрток. -- Встроенные прототипы могут быть изменены или дополнены новыми методами. Но не рекомендуется менять их. Единственная допустимая причина, это когда добавляется новый метод из стандарта, но он ещё не поддерживается движком JavaScript. +- Примитивы также хранят свои методы в прототипах объектов-обёрток: `Number.prototype`, `String.prototype`, `Boolean.prototype`. Только у значений `undefined` и `null` нет объектов-обёрток. +- Встроенные прототипы могут быть изменены или дополнены новыми методами. Но не рекомендуется менять их. Единственная допустимая причина -- это добавление нового метода из стандарта, который ещё не поддерживается движком JavaScript. diff --git a/1-js/08-prototypes/03-native-prototypes/function-prototype-constructor.png b/1-js/08-prototypes/03-native-prototypes/function-prototype-constructor.png deleted file mode 100644 index 6ab9c6934a..0000000000 Binary files a/1-js/08-prototypes/03-native-prototypes/function-prototype-constructor.png and /dev/null differ 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 new file mode 100644 index 0000000000..1fced82b39 --- /dev/null +++ b/1-js/08-prototypes/03-native-prototypes/function-prototype-constructor.svg @@ -0,0 +1 @@ +Rabbitprototypeconstructor"prototype" по умолчанию \ No newline at end of file diff --git a/1-js/08-prototypes/03-native-prototypes/function-prototype-constructor@2x.png b/1-js/08-prototypes/03-native-prototypes/function-prototype-constructor@2x.png deleted file mode 100644 index 3beb0a5733..0000000000 Binary files a/1-js/08-prototypes/03-native-prototypes/function-prototype-constructor@2x.png and /dev/null differ diff --git a/1-js/08-prototypes/03-native-prototypes/native-prototypes-array-tostring.png b/1-js/08-prototypes/03-native-prototypes/native-prototypes-array-tostring.png deleted file mode 100644 index c47938674b..0000000000 Binary files a/1-js/08-prototypes/03-native-prototypes/native-prototypes-array-tostring.png and /dev/null differ 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 new file mode 100644 index 0000000000..ebb4f32051 --- /dev/null +++ b/1-js/08-prototypes/03-native-prototypes/native-prototypes-array-tostring.svg @@ -0,0 +1 @@ +toString: function ...Array.prototypetoString: function ...Object.prototype[[Prototype]][[Prototype]][1, 2, 3] \ No newline at end of file diff --git a/1-js/08-prototypes/03-native-prototypes/native-prototypes-array-tostring@2x.png b/1-js/08-prototypes/03-native-prototypes/native-prototypes-array-tostring@2x.png deleted file mode 100644 index 293c88bf93..0000000000 Binary files a/1-js/08-prototypes/03-native-prototypes/native-prototypes-array-tostring@2x.png and /dev/null differ diff --git a/1-js/08-prototypes/03-native-prototypes/native-prototypes-classes.png b/1-js/08-prototypes/03-native-prototypes/native-prototypes-classes.png deleted file mode 100644 index c4658a9291..0000000000 Binary files a/1-js/08-prototypes/03-native-prototypes/native-prototypes-classes.png and /dev/null differ 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 new file mode 100644 index 0000000000..4d6129e0a0 --- /dev/null +++ b/1-js/08-prototypes/03-native-prototypes/native-prototypes-classes.svg @@ -0,0 +1 @@ +toString: function other object methodsObject.prototypenullslice: function other array methods[[Prototype]][[Prototype]][[Prototype]][[Prototype]][[Prototype]][[Prototype]][[Prototype]]Array.prototypecall: function other function methodsFunction.prototypetoFixed: function other number methodsNumber.prototype[1, 2, 3]function f(args) { ... }5 \ No newline at end of file diff --git a/1-js/08-prototypes/03-native-prototypes/native-prototypes-classes@2x.png b/1-js/08-prototypes/03-native-prototypes/native-prototypes-classes@2x.png deleted file mode 100644 index 79445fac90..0000000000 Binary files a/1-js/08-prototypes/03-native-prototypes/native-prototypes-classes@2x.png and /dev/null differ diff --git a/1-js/08-prototypes/03-native-prototypes/object-prototype-1.png b/1-js/08-prototypes/03-native-prototypes/object-prototype-1.png deleted file mode 100644 index a69cad4a1b..0000000000 Binary files a/1-js/08-prototypes/03-native-prototypes/object-prototype-1.png and /dev/null differ 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 new file mode 100644 index 0000000000..9630e68e27 --- /dev/null +++ b/1-js/08-prototypes/03-native-prototypes/object-prototype-1.svg @@ -0,0 +1 @@ +constructor: Object toString: function ...Object.prototypeObjectobj = new Object()[[Prototype]]prototype \ No newline at end of file diff --git a/1-js/08-prototypes/03-native-prototypes/object-prototype-1@2x.png b/1-js/08-prototypes/03-native-prototypes/object-prototype-1@2x.png deleted file mode 100644 index 9d661ac17e..0000000000 Binary files a/1-js/08-prototypes/03-native-prototypes/object-prototype-1@2x.png and /dev/null differ diff --git a/1-js/08-prototypes/03-native-prototypes/object-prototype-null.png b/1-js/08-prototypes/03-native-prototypes/object-prototype-null.png deleted file mode 100644 index 792c4f4237..0000000000 Binary files a/1-js/08-prototypes/03-native-prototypes/object-prototype-null.png and /dev/null differ 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 new file mode 100644 index 0000000000..9ccb342299 --- /dev/null +++ b/1-js/08-prototypes/03-native-prototypes/object-prototype-null.svg @@ -0,0 +1 @@ +obj[[Prototype]]null \ No newline at end of file diff --git a/1-js/08-prototypes/03-native-prototypes/object-prototype-null@2x.png b/1-js/08-prototypes/03-native-prototypes/object-prototype-null@2x.png deleted file mode 100644 index d97efa8849..0000000000 Binary files a/1-js/08-prototypes/03-native-prototypes/object-prototype-null@2x.png and /dev/null differ diff --git a/1-js/08-prototypes/03-native-prototypes/object-prototype.png b/1-js/08-prototypes/03-native-prototypes/object-prototype.png deleted file mode 100644 index 820ffcc736..0000000000 Binary files a/1-js/08-prototypes/03-native-prototypes/object-prototype.png and /dev/null differ diff --git a/1-js/08-prototypes/03-native-prototypes/object-prototype.svg b/1-js/08-prototypes/03-native-prototypes/object-prototype.svg new file mode 100644 index 0000000000..024dd30213 --- /dev/null +++ b/1-js/08-prototypes/03-native-prototypes/object-prototype.svg @@ -0,0 +1 @@ +constructor: Object toString: function ...Object.prototypeObjectprototype \ No newline at end of file diff --git a/1-js/08-prototypes/03-native-prototypes/object-prototype@2x.png b/1-js/08-prototypes/03-native-prototypes/object-prototype@2x.png deleted file mode 100644 index 1eb7e8749d..0000000000 Binary files a/1-js/08-prototypes/03-native-prototypes/object-prototype@2x.png and /dev/null differ diff --git a/1-js/08-prototypes/03-native-prototypes/proto-constructor-animal-rabbit.png b/1-js/08-prototypes/03-native-prototypes/proto-constructor-animal-rabbit.png deleted file mode 100644 index 4b381460a5..0000000000 Binary files a/1-js/08-prototypes/03-native-prototypes/proto-constructor-animal-rabbit.png and /dev/null differ 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 new file mode 100644 index 0000000000..ede4e1227e --- /dev/null +++ b/1-js/08-prototypes/03-native-prototypes/proto-constructor-animal-rabbit.svg @@ -0,0 +1 @@ +eats: truename: "White Rabbit"animalRabbitrabbit[[Prototype]]prototype \ No newline at end of file diff --git a/1-js/08-prototypes/03-native-prototypes/proto-constructor-animal-rabbit@2x.png b/1-js/08-prototypes/03-native-prototypes/proto-constructor-animal-rabbit@2x.png deleted file mode 100644 index c6e6ab5f60..0000000000 Binary files a/1-js/08-prototypes/03-native-prototypes/proto-constructor-animal-rabbit@2x.png and /dev/null differ diff --git a/1-js/08-prototypes/03-native-prototypes/rabbit-prototype-constructor.png b/1-js/08-prototypes/03-native-prototypes/rabbit-prototype-constructor.png deleted file mode 100644 index 8f3519a747..0000000000 Binary files a/1-js/08-prototypes/03-native-prototypes/rabbit-prototype-constructor.png and /dev/null differ 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 new file mode 100644 index 0000000000..2424ea648d --- /dev/null +++ b/1-js/08-prototypes/03-native-prototypes/rabbit-prototype-constructor.svg @@ -0,0 +1 @@ +"prototype" по умолчаниюRabbitrabbit[[Prototype]]prototypeconstructor \ No newline at end of file diff --git a/1-js/08-prototypes/03-native-prototypes/rabbit-prototype-constructor@2x.png b/1-js/08-prototypes/03-native-prototypes/rabbit-prototype-constructor@2x.png deleted file mode 100644 index 07acb8f4a9..0000000000 Binary files a/1-js/08-prototypes/03-native-prototypes/rabbit-prototype-constructor@2x.png and /dev/null differ diff --git a/1-js/08-prototypes/04-prototype-methods/2-dictionary-tostring/solution.md b/1-js/08-prototypes/04-prototype-methods/2-dictionary-tostring/solution.md index c8a3e0b9c9..23adbfd084 100644 --- a/1-js/08-prototypes/04-prototype-methods/2-dictionary-tostring/solution.md +++ b/1-js/08-prototypes/04-prototype-methods/2-dictionary-tostring/solution.md @@ -1,7 +1,7 @@ В методе можно получить все перечисляемые ключи с помощью `Object.keys` и вывести их список. -Чтобы сделать toString не перечисляемым, давайте определим его, используя дескриптор свойства. Синтаксис `Object.create` позволяет нам добавить в объект дескрипторы свойств как второй аргумент. +Чтобы сделать toString неперечисляемым, давайте определим его, используя дескриптор свойства. Синтаксис `Object.create` позволяет нам добавить в объект дескрипторы свойств как второй аргумент. ```js run *!* @@ -26,6 +26,6 @@ for(let key in dictionary) { alert(dictionary); // "apple,__proto__" ``` -Когда мы создаём свойство с помощью дескриптора, все флаги по умолчанию имеют значение `false`. Таким образом, в коде выше `dictionary.toString` не перечисляемое свойство. +Когда мы создаём свойство с помощью дескриптора, все флаги по умолчанию имеют значение `false`. Таким образом, в коде выше `dictionary.toString` -- неперечисляемое свойство. Смотрите главу [](info:property-descriptors) для ознакомления. 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 a1cd8ebb7b..5403e47e5d 100644 --- a/1-js/08-prototypes/04-prototype-methods/article.md +++ b/1-js/08-prototypes/04-prototype-methods/article.md @@ -3,11 +3,11 @@ В первой главе этого раздела мы упоминали, что существуют современные методы работы с прототипами. -Свойство `__proto__` считается устаревшим, и по стандарту должно поддерживаться только браузерами. +Свойство `__proto__` считается устаревшим, и по стандарту оно должно поддерживаться только браузерами. -Современные методы это: +Современные же методы это: -- [Object.create(proto[, descriptors])](mdn:js/Object/create) -- создаёт пустой объект со свойством `[[Prototype]]`, указанным как `proto`, и необязательными дескрипторами свойств. +- [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`. @@ -26,12 +26,13 @@ let rabbit = Object.create(animal); */!* alert(rabbit.eats); // true + *!* alert(Object.getPrototypeOf(rabbit) === animal); // получаем прототип объекта rabbit */!* *!* -Object.setPrototypeOf(rabbit, {}); // заменяем прототип объект rabbit на {} +Object.setPrototypeOf(rabbit, {}); // заменяем прототип объекта rabbit на {} */!* ``` @@ -60,35 +61,35 @@ alert(rabbit.jumps); // true let clone = Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj)); ``` -Такой вызов создаёт точную копию объекта `obj`, включая все свойства: перечисляемые и не перечисляемые, свойства, геттеры/сеттеры для свойств -- и все это с правильным свойством `[[Prototype]]`. +Такой вызов создаёт точную копию объекта `obj`, включая все свойства: перечисляемые и неперечисляемые, геттеры/сеттеры для свойств -- и всё это с правильным свойством `[[Prototype]]`. ## Краткая история -Если пересчитать все способы управления прототипом, то их будет много! И многие из них делают одно и тоже! +Если пересчитать все способы управления прототипом, то их будет много! И многие из них делают одно и то же! Почему так? В силу исторических причин. - Свойство `"prototype"` функции-конструктора существует с совсем давних времён. -- Позднее, в 2012 году, в стандарте появился метод `Object.create`. Это давало возможность создавать объекты с указанным прототипом, но не позволяло устанавливать/получать его. Тогда браузеры реализовали нестандартный аксессор `__proto__`, который позволил устанавливать/получать прототип в любой время. -- Позднее в 2015 году: в стандарт были добавлены `Object.setPrototypeOf` и `Object.getPrototypeOf,` заменяющие собой аксессор `__proto__`, который упоминается в Приложении Б стандарта, которое не обязательно к поддержке в не-браузерных окруженях. При этом де-факто `__proto__` всё ещё поддерживается везде. +- Позднее, в 2012 году, в стандарте появился метод `Object.create`. Это давало возможность создавать объекты с указанным прототипом, но не позволяло устанавливать/получать его. Тогда браузеры реализовали нестандартный аксессор `__proto__`, который позволил устанавливать/получать прототип в любое время. +- Позднее, в 2015 году, в стандарт были добавлены `Object.setPrototypeOf` и `Object.getPrototypeOf,` заменяющие собой аксессор `__proto__`, который упоминается в Приложении Б стандарта, которое не обязательно к поддержке в небраузерных окружениях. При этом де-факто `__proto__` всё ещё поддерживается везде. В итоге сейчас у нас есть все эти способы для работы с прототипом. Почему же `__proto__` был заменён на функции `getPrototypeOf/setPrototypeOf`? Читайте далее, чтобы узнать ответ. -```warn header="Не переопределяйте `[[Prototype]]`, кроме случаев, когда скорость не важна" -Технически, мы можем установить/получить `[[Prototype]]` в любое время. Но обычно мы устанавливаем прототип только раз во время создания объекта, а после не меняем: `rabbit` наследует от `animal`, и это не изменится. +```warn header="Не меняйте `[[Prototype]]` существующих объектов, если важна скорость" +Технически мы можем установить/получить `[[Prototype]]` в любое время. Но обычно мы устанавливаем прототип только раз во время создания объекта, а после не меняем: `rabbit` наследует от `animal`, и это не изменится. -И JavaScript движки хорошо оптимизированы для этого. Изменение прототипа "на лету" с помощью `Object.setPrototypeOf` или `obj.__proto__=` очень медленная операция, которая ломает внутренние оптимизации для операций доступа к свойствам объекта. Так что лучше избегайте этого, кроме тех случаев, когда вы знаете, что делаете, либо скорость JavaScript для вас не имеет никакого значения. +И JavaScript движки хорошо оптимизированы для этого. Изменение прототипа "на лету" с помощью `Object.setPrototypeOf` или `obj.__proto__=` - очень медленная операция, которая ломает внутренние оптимизации для операций доступа к свойствам объекта. Так что лучше избегайте этого кроме тех случаев, когда вы знаете, что делаете, или же когда скорость JavaScript для вас не имеет никакого значения. ``` -## "Простейший" объект +## "Простейший" объект [#very-plain] Как мы знаем, объекты можно использовать как ассоциативные массивы для хранения пар ключ/значение. -...Но если попробуем хранить *созданные пользователями* ключи (например, словари с пользовательским вводом), мы можем заметить интересный сбой: все ключи работают как ожидается, за исключением `"__proto__"`. +...Но если мы попробуем хранить *созданные пользователями* ключи (например, словари с пользовательским вводом), мы можем заметить интересный сбой: все ключи работают как ожидается, за исключением `"__proto__"`. Посмотрите на пример: @@ -107,21 +108,21 @@ alert(obj[key]); // [object Object], не "some value"! Но мы не *намеревались* реализовывать такое поведение, не так ли? Мы хотим хранить пары ключ/значение, и ключ с именем `"__proto__"` не был сохранён надлежащим образом. Так что это ошибка! -Конкретно в этом примере последствия не так ужасны, но если мы присваиваем объектные значения, то прототип и в самом деле может быть изменен. В результате дальнейшее выполнение пойдет совершенно непредсказуемым образом. +Конкретно в этом примере последствия не так ужасны, но если мы присваиваем объектные значения, то прототип и в самом деле может быть изменён. В результате дальнейшее выполнение пойдёт совершенно непредсказуемым образом. Что хуже всего -- разработчики не задумываются о такой возможности совсем. Это делает такие ошибки сложным для отлавливания или даже превращает их в уязвимости, особенно когда JavaScript используется на сервере. -Неожиданные вещи могут случаться также при получении доступа к свойству `toString`, которое по умолчанию функция, и к другим встроенным свойствам. +Неожиданные вещи могут случаться также при присвоении свойства `toString`, которое по умолчанию функция, и других свойств, которые тоже на самом деле являются встроенными методами. Как же избежать проблемы? -Во-первых, мы можем переключиться на использование коллекции `Map`, и тогда все будет в порядке. +Во-первых, мы можем переключиться на использование коллекции `Map`, и тогда всё будет в порядке. Но и `Object` может также хорошо подойти, потому что создатели языка уже давно продумали решение проблемы. Свойство `__proto__` -- не обычное, а аксессор, заданный в `Object.prototype`: -![](object-prototype-2.png) +![](object-prototype-2.svg) Так что при чтении или установке `obj.__proto__` вызывается соответствующий геттер/сеттер из прототипа `obj`, и именно он устанавливает/получает свойство `[[Prototype]]`. @@ -142,11 +143,11 @@ alert(obj[key]); // "some value" `Object.create(null)` создаёт пустой объект без прототипа (`[[Prototype]]` будет `null`): -![](object-prototype-null.png) +![](object-prototype-null.svg) -Таким образом не будет унаследованного геттера/сеттера для `__proto__`. Теперь это свойство обрабатывается как обычное свойство, и приведенный выше пример работает правильно. +Таким образом не будет унаследованного геттера/сеттера для `__proto__`. Теперь это свойство обрабатывается как обычное свойство, и приведённый выше пример работает правильно. -Мы можем назвать такой объект "простейшим" или "чистым словарным объектом", потому что они ещё проще чем обычные объекты `{...}`. +Мы можем назвать такой объект "простейшим" или "чистым словарным объектом", потому что он ещё проще, чем обычные объекты `{...}`. Недостаток в том, что у таких объектов не будет встроенных методов объекта, таких как `toString`: @@ -155,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 @@ -177,11 +178,11 @@ alert(Object.keys(chineseDictionary)); // hello,bye - [Object.create(proto[, descriptors])](mdn:js/Object/create) -- создаёт пустой объект со свойством `[[Prototype]]`, указанным как `proto` (может быть `null`), и необязательными дескрипторами свойств. - [Object.getPrototypeOf(obj)](mdn:js/Object.getPrototypeOf) -- возвращает свойство `[[Prototype]]` объекта `obj` (то же самое, что и геттер `__proto__`). -- [Object.setPrototypeOf(obj, proto)](mdn:js/Object.setPrototypeOf) -- устанавливает свойство `[[Prototype]]` объекта `obj` как `proto` (то же самое что и сеттер `__proto__`). +- [Object.setPrototypeOf(obj, proto)](mdn:js/Object.setPrototypeOf) -- устанавливает свойство `[[Prototype]]` объекта `obj` как `proto` (то же самое, что и сеттер `__proto__`). -Встроенный геттер/сеттер `__proto__` не безопасен, если мы хотим использовать *созданные пользователями* ключи в объекте. Как минимум потому, что пользователь может ввести `"__proto__"` как ключ, от чего может возникнуть ошибка. Если повезёт - последствия будут лёгкими, но обычно они непредсказуемы. +Встроенный геттер/сеттер `__proto__` не безопасен, если мы хотим использовать *созданные пользователями* ключи в объекте. Как минимум потому, что пользователь может ввести `"__proto__"` как ключ, от чего может возникнуть ошибка. Если повезёт - последствия будут лёгкими, но, вообще говоря, они непредсказуемы. -Тогда мы можем использовать либо `Object.create(null)` для создания "простейшего" объекта, либо использовать коллекцию `Map`. +Так что мы можем использовать либо `Object.create(null)` для создания "простейшего" объекта, либо использовать коллекцию `Map`. Кроме этого, `Object.create` даёт нам лёгкий способ создать поверхностную копию объекта со всеми дескрипторами: @@ -189,15 +190,16 @@ alert(Object.keys(chineseDictionary)); // hello,bye let clone = Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj)); ``` +Мы также ясно увидели, что `__proto__` -- это геттер/сеттер для свойства `[[Prototype]]`, и находится он в `Object.prototype`, как и другие методы. -- [Object.keys(obj)](mdn:js/Object/keys) / [Object.values(obj)](mdn:js/Object/values) / [Object.entries(obj)](mdn:js/Object/entries) -- возвращают массив всех перечисляемых собственных строковых имён/значений/пар ключ-значение. -- [Object.getOwnPropertySymbols(obj)](mdn:js/Object/getOwnPropertySymbols) -- возвращает массив всех собственных символьных имён свойств. -- [Object.getOwnPropertyNames(obj)](mdn:js/Object/getOwnPropertyNames) -- возвращает массив всех собственных строковых имён свойств. -- [Reflect.ownKeys(obj)](mdn:js/Reflect/ownKeys) -- возвращает массив всех собственных имён свойств. -- [obj.hasOwnProperty(key)](mdn:js/Object/hasOwnProperty): возвращает `true`, если у `obj` есть собственное (не унаследованное) свойство с именем `key`. +Мы можем создавать объекты без прототипов с помощью `Object.create(null)`. Такие объекты можно использовать как "чистые словари", у них нет проблем с использованием строки `"__proto__"` в качестве ключа. -Мы также ясно увидели, что `__proto__` -- это геттер/сеттер для свойства `[[Prototype]]` и находится он в `Object.prototype`, как и другие методы. +Ещё методы: -Мы можем создавать объекты без прототипов с помощью `Object.create(null)`. Такие объекты можно использовать как "чистые словари", у них нет проблем с использованием строки `"__proto__"` в качестве ключа. +- [Object.keys(obj)](mdn:js/Object/keys) / [Object.values(obj)](mdn:js/Object/values) / [Object.entries(obj)](mdn:js/Object/entries) -- возвращают массив всех перечисляемых собственных строковых ключей/значений/пар ключ-значение. +- [Object.getOwnPropertySymbols(obj)](mdn:js/Object/getOwnPropertySymbols) -- возвращает массив всех собственных символьных ключей. +- [Object.getOwnPropertyNames(obj)](mdn:js/Object/getOwnPropertyNames) -- возвращает массив всех собственных строковых ключей. +- [Reflect.ownKeys(obj)](mdn:js/Reflect/ownKeys) -- возвращает массив всех собственных ключей. +- [obj.hasOwnProperty(key)](mdn:js/Object/hasOwnProperty): возвращает `true`, если у `obj` есть собственное (не унаследованное) свойство с именем `key`. -Все методы, которые возвращают свойства объектов (такие как `Object.keys` и другие), возвращают "собственные" свойства. Если мы хотим получить унаследованные, тогда можем воспользоваться циклом `for..in`. +Все методы, которые возвращают свойства объектов (такие как `Object.keys` и другие), возвращают "собственные" свойства. Если мы хотим получить и унаследованные, можно воспользоваться циклом `for..in`. diff --git a/1-js/08-prototypes/04-prototype-methods/object-prototype-2.png b/1-js/08-prototypes/04-prototype-methods/object-prototype-2.png deleted file mode 100644 index 343435af6c..0000000000 Binary files a/1-js/08-prototypes/04-prototype-methods/object-prototype-2.png and /dev/null differ 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 new file mode 100644 index 0000000000..cf4d3023f8 --- /dev/null +++ b/1-js/08-prototypes/04-prototype-methods/object-prototype-2.svg @@ -0,0 +1 @@ +... get __proto__: function set __proto__: functionObject.prototypeObjectobj[[Prototype]]prototype \ No newline at end of file diff --git a/1-js/08-prototypes/04-prototype-methods/object-prototype-2@2x.png b/1-js/08-prototypes/04-prototype-methods/object-prototype-2@2x.png deleted file mode 100644 index 86b8a678e1..0000000000 Binary files a/1-js/08-prototypes/04-prototype-methods/object-prototype-2@2x.png and /dev/null differ diff --git a/1-js/08-prototypes/04-prototype-methods/object-prototype-null.png b/1-js/08-prototypes/04-prototype-methods/object-prototype-null.png deleted file mode 100644 index 792c4f4237..0000000000 Binary files a/1-js/08-prototypes/04-prototype-methods/object-prototype-null.png and /dev/null differ 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 new file mode 100644 index 0000000000..9ccb342299 --- /dev/null +++ b/1-js/08-prototypes/04-prototype-methods/object-prototype-null.svg @@ -0,0 +1 @@ +obj[[Prototype]]null \ No newline at end of file diff --git a/1-js/08-prototypes/04-prototype-methods/object-prototype-null@2x.png b/1-js/08-prototypes/04-prototype-methods/object-prototype-null@2x.png deleted file mode 100644 index d97efa8849..0000000000 Binary files a/1-js/08-prototypes/04-prototype-methods/object-prototype-null@2x.png and /dev/null differ diff --git a/1-js/08-prototypes/05-getting-all-properties/article.md b/1-js/08-prototypes/05-getting-all-properties/article.md deleted file mode 100644 index 3451817860..0000000000 --- a/1-js/08-prototypes/05-getting-all-properties/article.md +++ /dev/null @@ -1,83 +0,0 @@ - -# Getting all properties - -There are many ways to get keys/values from an object. - -Most of them operate on the object itself, excluding the prototype, let's recall them: - -- [Object.keys(obj)](mdn:js/Object/keys) / [Object.values(obj)](mdn:js/Object/values) / [Object.entries(obj)](mdn:js/Object/entries) -- returns an array of enumerable own string property names/values/key-value pairs. These methods only list *enumerable* properties, and those that have *strings as keys*. - -If we want symbolic properties: - -- [Object.getOwnPropertySymbols(obj)](mdn:js/Object/getOwnPropertySymbols) -- returns an array of all own symbolic property names. - -If we want non-enumerable properties: - -- [Object.getOwnPropertyNames(obj)](mdn:js/Object/getOwnPropertyNames) -- returns an array of all own string property names. - -If we want *all* properties: - -- [Reflect.ownKeys(obj)](mdn:js/Reflect/ownKeys) -- returns an array of all own property names. - -These methods are a bit different about which properties they return, but all of them operate on the object itself. Properties from the prototype are not listed. - -## for..in loop - -The `for..in` loop is different: it loops over inherited properties too. - -For instance: - -```js run -let animal = { - eats: true -}; - -let rabbit = { - jumps: true, - __proto__: animal -}; - -*!* -// only own keys -alert(Object.keys(rabbit)); // jumps -*/!* - -*!* -// inherited keys too -for(let prop in rabbit) alert(prop); // jumps, then eats -*/!* -``` - -If that's not what we want, and we'd like to exclude inherited properties, there's a built-in method [obj.hasOwnProperty(key)](mdn:js/Object/hasOwnProperty): it returns `true` if `obj` has its own (not inherited) property named `key`. - -So we can filter out inherited properties (or do something else with them): - -```js run -let animal = { - eats: true -}; - -let rabbit = { - jumps: true, - __proto__: animal -}; - -for(let prop in rabbit) { - let isOwn = rabbit.hasOwnProperty(prop); - alert(`${prop}: ${isOwn}`); // jumps: true, then eats: false -} -``` - -Here we have the following inheritance chain: `rabbit`, then `animal`, then `Object.prototype` (because `animal` is a literal object `{...}`, so it's by default), and then `null` above it: - -![](rabbit-animal-object.png) - -Note, there's one funny thing. Where is the method `rabbit.hasOwnProperty` coming from? Looking at the chain we can see that the method is provided by `Object.prototype.hasOwnProperty`. In other words, it's inherited. - -...But why `hasOwnProperty` does not appear in `for..in` loop, if it lists all inherited properties? The answer is simple: it's not enumerable. Just like all other properties of `Object.prototype`. That's why they are not listed. - -## Summary - -Most methods ignore inherited properties, with a notable exception of `for..in`. - -For the latter we can use [obj.hasOwnProperty(key)](mdn:js/Object/hasOwnProperty): it returns `true` if `obj` has its own (not inherited) property named `key`. diff --git a/1-js/08-prototypes/05-getting-all-properties/rabbit-animal-object.png b/1-js/08-prototypes/05-getting-all-properties/rabbit-animal-object.png deleted file mode 100644 index 3924233ddd..0000000000 Binary files a/1-js/08-prototypes/05-getting-all-properties/rabbit-animal-object.png and /dev/null differ diff --git a/1-js/08-prototypes/05-getting-all-properties/rabbit-animal-object@2x.png b/1-js/08-prototypes/05-getting-all-properties/rabbit-animal-object@2x.png deleted file mode 100644 index 5350a19356..0000000000 Binary files a/1-js/08-prototypes/05-getting-all-properties/rabbit-animal-object@2x.png and /dev/null differ diff --git a/1-js/08-prototypes/index.md b/1-js/08-prototypes/index.md index 8554a0e30b..b3c0a3c667 100644 --- a/1-js/08-prototypes/index.md +++ b/1-js/08-prototypes/index.md @@ -1 +1 @@ -# Prototypes, inheritance +# Прототипы, наследование diff --git a/1-js/09-classes/01-class/1-rewrite-to-class/task.md b/1-js/09-classes/01-class/1-rewrite-to-class/task.md index 05365e4104..5bf84cb79f 100644 --- a/1-js/09-classes/01-class/1-rewrite-to-class/task.md +++ b/1-js/09-classes/01-class/1-rewrite-to-class/task.md @@ -2,8 +2,8 @@ importance: 5 --- -# Rewrite to class +# Перепишите класс -The `Clock` class is written in functional style. Rewrite it the "class" syntax. +Класс `Clock` написан в функциональном стиле. Перепишите его, используя современный синтаксис классов. -P.S. The clock ticks in the console, open it to see. +P.S. Часики тикают в консоли. Откройте её, чтобы посмотреть. diff --git a/1-js/09-classes/01-class/article.md b/1-js/09-classes/01-class/article.md index 217873dcc1..a41d43a601 100644 --- a/1-js/09-classes/01-class/article.md +++ b/1-js/09-classes/01-class/article.md @@ -1,22 +1,22 @@ -# Class basic syntax +# Класс: базовый синтаксис -```quote author="Wikipedia" -In object-oriented programming, a *class* is an extensible program-code-template for creating objects, providing initial values for state (member variables) and implementations of behavior (member functions or methods). +```quote author="Википедия" +В объектно-ориентированном программировании *класс* - это расширяемый шаблон кода для создания объектов, который устанавливает в них начальные значения (свойства) и реализацию поведения (методы). ``` -In practice, we often need to create many objects of the same kind, like users, or goods or whatever. +На практике нам часто надо создавать много объектов одного вида, например пользователей, товары или что-то ещё. -As we already know from the chapter , `new function` can help with that. +Как мы уже знаем из главы , с этим может помочь `new function`. -But in the modern JavaScript, there's a more advanced "class" construct, that introduces great new features which are useful for object-oriented programming. +Но в современном JavaScript есть и более продвинутая конструкция "class", которая предоставляет новые возможности, полезные для объектно-ориентированного программирования. -## The "class" syntax +## Синтаксис "class" -The basic syntax is: +Базовый синтаксис выглядит так: ```js class MyClass { - // class methods + // методы класса constructor() { ... } method1() { ... } method2() { ... } @@ -25,11 +25,11 @@ class MyClass { } ``` -Then `new MyClass()` creates a new object with all the listed methods. +Затем используйте вызов `new MyClass()` для создания нового объекта со всеми перечисленными методами. -The `constructor()` method is called automatically by `new`, so we can initialize the object there. +При этом автоматически вызывается метод `constructor()`, в нём мы можем инициализировать объект. -For example: +Например: ```js run class User { @@ -44,33 +44,32 @@ class User { } -// Usage: -let user = new User("John"); +// Использование: +let user = new User("Иван"); user.sayHi(); ``` +Когда вызывается `new User("Иван")`: +1. Создаётся новый объект. +2. `constructor` запускается с заданным аргументом и сохраняет его в `this.name`. -When `new User("John")` is called: -1. A new object is created. -2. The `constructor` runs with the given argument and assigns `this.name` to it. +...Затем можно вызывать на объекте методы, такие как `user.sayHi()`. -...Then we can call methods, such as `user.sayHi`. +```warn header="Методы в классе не разделяются запятой" +Частая ошибка начинающих разработчиков - ставить запятую между методами класса, что приводит к синтаксической ошибке. -```warn header="No comma between class methods" -A common pitfall for novice developers is to put a comma between class methods, which would result in a syntax error. - -The notation here is not to be confused with object literals. Within the class, no commas are required. +Синтаксис классов отличается от литералов объектов, не путайте их. Внутри классов запятые не требуются. ``` -## What is a class? +## Что такое класс? -So, what exactly is a `class`? That's not an entirely new language-level entity, as one might think. +Итак, что же такое `class`? Это не полностью новая языковая сущность, как может показаться на первый взгляд. -Let's unveil any magic and see what a class really is. That'll help in understanding many complex aspects. +Давайте развеем всю магию и посмотрим, что такое класс на самом деле. Это поможет в понимании многих сложных аспектов. -In JavaScript, a class is a kind of a function. +В JavaScript класс - это разновидность функции. -Here, take a look: +Взгляните: ```js run class User { @@ -78,25 +77,24 @@ class User { sayHi() { alert(this.name); } } -// proof: User is a function +// доказательство: User - это функция *!* alert(typeof User); // function */!* ``` -What `class User {...}` construct really does is: -1. Creates a function named `User`, that becomes the result of the class declaration. - - The function code is taken from the `constructor` method (assumed empty if we don't write such method). -3. Stores all methods, such as `sayHi`, in `User.prototype`. +Вот что на самом деле делает конструкция `class User {...}`: -Afterwards, for new objects, when we call a method, it's taken from the prototype, just as described in the chapter . So `new User` object has access to class methods. +1. Создаёт функцию с именем `User`, которая становится результатом объявления класса. Код функции берётся из метода `constructor` (она будет пустой, если такого метода нет). +2. Сохраняет все методы, такие как `sayHi`, в `User.prototype`. -We can illustrate the result of `class User` as: +При вызове метода объекта `new User` он будет взят из прототипа, как описано в главе . Таким образом, объекты `new User` имеют доступ к методам класса. -![](class-user.png) +На картинке показан результат объявления `class User`: -Here's the code to introspect it: +![](class-user.svg) +Можно проверить вышесказанное и при помощи кода: ```js run class User { @@ -104,50 +102,49 @@ class User { sayHi() { alert(this.name); } } -// class is a function +// класс - это функция alert(typeof User); // function -// ...or, more precisely, the constructor method +// ...или, если точнее, это метод constructor alert(User === User.prototype.constructor); // true -// The methods are in User.prototype, e.g: -alert(User.prototype.sayHi); // alert(this.name); +// Методы находятся в User.prototype, например: +alert(User.prototype.sayHi); // sayHi() { alert(this.name); } -// there are exactly two methods in the prototype +// в прототипе ровно 2 метода alert(Object.getOwnPropertyNames(User.prototype)); // constructor, sayHi ``` -## Not just a syntax sugar +## Не просто синтаксический сахар -Sometimes people say that `class` is a "syntax sugar" in JavaScript, because we could actually declare the same without `class` keyword at all: +Иногда говорят, что `class` - это просто "синтаксический сахар" в JavaScript (синтаксис для улучшения читаемости кода, но не делающий ничего принципиально нового), потому что мы можем сделать всё то же самое без конструкции `class`: ```js run -// rewriting class User in pure functions +// перепишем класс User на чистых функциях -// 1. Create constructor function +// 1. Создаём функцию constructor function User(name) { this.name = name; } -// any function prototype has constructor property by default, -// so we don't need to create it +// каждый прототип функции имеет свойство constructor по умолчанию, +// поэтому нам нет необходимости его создавать -// 2. Add the method to prototype +// 2. Добавляем метод в прототип User.prototype.sayHi = function() { alert(this.name); }; -// Usage: -let user = new User("John"); +// Использование: +let user = new User("Иван"); user.sayHi(); ``` +Результат этого кода очень похож. Поэтому, действительно, есть причины, по которым `class` можно считать синтаксическим сахаром для определения конструктора вместе с методами прототипа. -The result of this definition is about the same. So, there are indeed reasons why `class` can be considered a syntax sugar to define a constructor together with its prototype methods. - -Although, there are important differences. +Однако есть важные отличия: -1. First, a function created by `class` is labelled by a special internal property `[[FunctionKind]]:"classConstructor"`. So it's not entirely the same as creating it manually. +1. Во-первых, функция, созданная с помощью `class`, помечена специальным внутренним свойством `[[IsClassConstructor]]: true`. Поэтому это не совсем то же самое, что создавать её вручную. - Unlike a regular function, a class constructor can't be called without `new`: + В отличие от обычных функций, конструктор класса не может быть вызван без `new`: ```js run class User { @@ -157,8 +154,7 @@ Although, there are important differences. alert(typeof User); // function User(); // Error: Class constructor User cannot be invoked without 'new' ``` - - Also, a string representation of a class constructor in most JavaScript engines starts with the "class..." + Кроме того, строковое представление конструктора класса в большинстве движков JavaScript начинается с "class ..." ```js run class User { @@ -168,54 +164,53 @@ Although, there are important differences. alert(User); // class User { ... } ``` -2. Class methods are non-enumerable. - A class definition sets `enumerable` flag to `false` for all methods in the `"prototype"`. - - That's good, because if we `for..in` over an object, we usually don't want its class methods. +2. Методы класса являются неперечислимыми. + Определение класса устанавливает флаг `enumerable` в `false` для всех методов в `"prototype"`. -3. Classes always `use strict`. - All code inside the class construct is automatically in strict mode. + И это хорошо, так как если мы проходимся циклом `for..in` по объекту, то обычно мы не хотим при этом получать методы класса. +3. Классы всегда используют `use strict`. + Весь код внутри класса автоматически находится в строгом режиме. -Also, in addition to its basic operation, the `class` syntax brings many other features with it which we'll explore later. +Также в дополнение к основной, описанной выше, функциональности, синтаксис `class` даёт ряд других интересных возможностей, с которыми мы познакомимся чуть позже. ## Class Expression -Just like functions, classes can be defined inside another expression, passed around, returned, assigned etc. +Как и функции, классы можно определять внутри другого выражения, передавать, возвращать, присваивать и т.д. -Here's an example of a class expression: +Пример Class Expression (по аналогии с Function Expression): ```js let User = class { sayHi() { - alert("Hello"); + alert("Привет"); } }; ``` -Similar to Named Function Expressions, class expressions may or may not have a name. +Аналогично Named Function Expression, Class Expression может иметь имя. -If a class expression has a name, it's visible inside the class only: +Если у Class Expression есть имя, то оно видно только внутри класса: ```js run -// "Named Class Expression" (alas, no such term, but that's what's going on) +// "Named Class Expression" +// (в спецификации нет такого термина, но происходящее похоже на Named Function Expression) let User = class *!*MyClass*/!* { sayHi() { - alert(MyClass); // MyClass is visible only inside the class + alert(MyClass); // имя MyClass видно только внутри класса } }; -new User().sayHi(); // works, shows MyClass definition +new User().sayHi(); // работает, выводит определение MyClass -alert(MyClass); // error, MyClass not visible outside of the class +alert(MyClass); // ошибка, имя MyClass не видно за пределами класса ``` - -We can even make classes dynamically "on-demand", like this: +Мы даже можем динамически создавать классы "по запросу": ```js run function makeClass(phrase) { - // declare a class and return it + // объявляем класс и возвращаем его return class { sayHi() { alert(phrase); @@ -223,25 +218,25 @@ function makeClass(phrase) { }; } -// Create a new class -let User = makeClass("Hello"); +// Создаём новый класс +let User = makeClass("Привет"); -new User().sayHi(); // Hello +new User().sayHi(); // Привет ``` -## Getters/setters, other shorthands +## Геттеры/сеттеры, другие сокращения -Classes also include getters/setters, generators, computed properties etc. +Как и в литеральных объектах, в классах можно объявлять вычисляемые свойства, геттеры/сеттеры и т.д. -Here's an example for `user.name` implemented using `get/set`: +Вот пример `user.name`, реализованного с использованием `get/set`: ```js run class User { constructor(name) { - // invokes the setter - this._name = name; + // вызывает сеттер + this.name = name; } *!* @@ -254,7 +249,7 @@ class User { set name(value) { */!* if (value.length < 4) { - alert("Name is too short."); + alert("Имя слишком короткое."); return; } this._name = value; @@ -262,13 +257,13 @@ class User { } -let user = new User("John"); -alert(user.name); // John +let user = new User("Иван"); +alert(user.name); // Иван -user = new User(""); // Name too short. +user = new User(""); // Имя слишком короткое. ``` -Internally, getters and setters are created on `User.prototype`, like this: +При объявлении класса геттеры/сеттеры создаются на `User.prototype`, вот так: ```js Object.defineProperties(User.prototype, { @@ -283,14 +278,13 @@ Object.defineProperties(User.prototype, { }); ``` -Here's an example with computed properties: +Пример с вычисляемым свойством в скобках `[...]`: ```js run -function f() { return "sayHi"; } - class User { - [f()]() { - alert("Hello"); + + ['say' + 'Hi']() { + alert("Привет"); } } @@ -298,71 +292,47 @@ class User { new User().sayHi(); ``` -For a generator method, similarly, prepend it with `*`. - -## Class properties +## Свойства классов -```warn header="Old browsers may need a polyfill" -Class-level properties are a recent addition to the language. +```warn header="Старым браузерам может понадобиться полифил" +Свойства классов добавлены в язык недавно. ``` - -In the example above, `User` only had methods. Let's add a property: +В приведённом выше примере у класса `User` были только методы. Давайте добавим свойство: ```js run class User { - name = "Anonymous"; +*!* + name = "Аноним"; +*/!* sayHi() { - alert(`Hello, ${this.name}!`); + alert(`Привет, ${this.name}!`); } } new User().sayHi(); ``` -The property is not placed into `User.prototype`. Instead, it is created by `new`, separately for every object. So, the property will never be shared between different objects of the same class. - - -## Summary +Свойство `name` не устанавливается в `User.prototype`. Вместо этого оно создаётся оператором `new` перед запуском конструктора, это именно свойство объекта. -JavaScript provides many ways to create a class. +## Итого -First, as per the general object-oriented terminology, a class is something that provides "object templates", allows to create same-structured objects. - -When we say "a class", that doesn't necessary means the `class` keyword. - -This is a class: - -```js -function User(name) { - this.sayHi = function() { - alert(name); - } -} -``` - -...But in most cases `class` keyword is used, as it provides great syntax and many additional features. - -The basic class syntax looks like this: +Базовый синтаксис для классов выглядит так: ```js class MyClass { - prop = value; // field - - constructor(...) { // constructor + prop = value; // свойство + constructor(...) { // конструктор // ... } - - method(...) {} // method - - get something(...) {} // getter method - set something(...) {} // setter method - - [Symbol.iterator]() {} // method with computed name/symbol name + method(...) {} // метод + get something(...) {} // геттер + set something(...) {} // сеттер + [Symbol.iterator]() {} // метод с вычисляемым именем (здесь - символом) // ... } ``` -`MyClass` is technically a function, while methods are written to `MyClass.prototype`. +`MyClass` технически является функцией (той, которую мы определяем как `constructor`), в то время как методы, геттеры и сеттеры записываются в `MyClass.prototype`. -In the next chapters we'll learn more about classes, including inheritance and other features. +В следующих главах мы узнаем больше о классах, включая наследование и другие возможности. diff --git a/1-js/09-classes/01-class/class-user.png b/1-js/09-classes/01-class/class-user.png deleted file mode 100644 index dc8b756794..0000000000 Binary files a/1-js/09-classes/01-class/class-user.png and /dev/null differ diff --git a/1-js/09-classes/01-class/class-user.svg b/1-js/09-classes/01-class/class-user.svg new file mode 100644 index 0000000000..418d71d187 --- /dev/null +++ b/1-js/09-classes/01-class/class-user.svg @@ -0,0 +1 @@ +sayHi: functionUserUser.prototypeprototypeconstructor: User \ No newline at end of file diff --git a/1-js/09-classes/01-class/class-user@2x.png b/1-js/09-classes/01-class/class-user@2x.png deleted file mode 100644 index aaa53708fa..0000000000 Binary files a/1-js/09-classes/01-class/class-user@2x.png and /dev/null differ diff --git a/1-js/09-classes/02-class-inheritance/1-class-constructor-error/solution.md b/1-js/09-classes/02-class-inheritance/1-class-constructor-error/solution.md index 27c10cf006..05272a1699 100644 --- a/1-js/09-classes/02-class-inheritance/1-class-constructor-error/solution.md +++ b/1-js/09-classes/02-class-inheritance/1-class-constructor-error/solution.md @@ -21,7 +21,7 @@ class Rabbit extends Animal { } *!* -let rabbit = new Rabbit("White Rabbit"); // ошибки нет +let rabbit = new Rabbit("Белый кролик"); // ошибки нет */!* alert(rabbit.name); // White Rabbit ``` diff --git a/1-js/09-classes/02-class-inheritance/1-class-constructor-error/task.md b/1-js/09-classes/02-class-inheritance/1-class-constructor-error/task.md index 421619327c..1dcdc4ea0b 100644 --- a/1-js/09-classes/02-class-inheritance/1-class-constructor-error/task.md +++ b/1-js/09-classes/02-class-inheritance/1-class-constructor-error/task.md @@ -6,7 +6,7 @@ importance: 5 В коде ниже класс `Rabbit` наследует `Animal`. -К сожалению, объект класса `Rabbit` не создается. Что не так? Исправьте ошибку. +К сожалению, объект класса `Rabbit` не создаётся. Что не так? Исправьте ошибку. ```js run class Animal { @@ -25,7 +25,7 @@ class Rabbit extends Animal { } *!* -let rabbit = new Rabbit("White Rabbit"); // Error: this is not defined +let rabbit = new Rabbit("Белый кролик"); // Error: this is not defined */!* alert(rabbit.name); ``` 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/3-class-extend-object/rabbit-extends-object.png b/1-js/09-classes/02-class-inheritance/3-class-extend-object/rabbit-extends-object.png deleted file mode 100644 index c610e28c3e..0000000000 Binary files a/1-js/09-classes/02-class-inheritance/3-class-extend-object/rabbit-extends-object.png and /dev/null differ diff --git a/1-js/09-classes/02-class-inheritance/3-class-extend-object/rabbit-extends-object@2x.png b/1-js/09-classes/02-class-inheritance/3-class-extend-object/rabbit-extends-object@2x.png deleted file mode 100644 index 4819c7f798..0000000000 Binary files a/1-js/09-classes/02-class-inheritance/3-class-extend-object/rabbit-extends-object@2x.png and /dev/null differ diff --git a/1-js/09-classes/02-class-inheritance/3-class-extend-object/solution.md b/1-js/09-classes/02-class-inheritance/3-class-extend-object/solution.md deleted file mode 100644 index 51f1bc302c..0000000000 --- a/1-js/09-classes/02-class-inheritance/3-class-extend-object/solution.md +++ /dev/null @@ -1,82 +0,0 @@ -Сперва давайте разберемся, почему код не работает. - -Причина становится очевидна, если мы попытаемся запустить его. Унаследованный конструктор класса должен вызывать `super()`. В противном случае `"this"` будет не определен. - -Решение: - -```js run -class Rabbit extends Object { - constructor(name) { -*!* - super(); // надо вызвать конструктор родителя, когда наследуемся -*/!* - this.name = name; - } -} - -let rabbit = new Rabbit("Rab"); - -alert( rabbit.hasOwnProperty('name') ); // true -``` - -Но это еще не все. - -Даже после исправления есть важное различие между `"class Rabbit extends Object"` и `class Rabbit`. - -Как мы знаем, синтаксис "extends" устанавливает 2 прототипа: - -1. Между `"prototype"` функций-конструкторов (для методов) -2. Между самими функциями-конструкторами (для статических методов). - -В нашем случае, для `class Rabbit extends Object` это значит: - -```js run -class Rabbit extends Object {} - -alert( Rabbit.prototype.__proto__ === Object.prototype ); // (1) true -alert( Rabbit.__proto__ === Object ); // (2) true -``` - -Таким образом, `Rabbit` предоставляет доступ к статическим методам `Object` через `Rabbit`, например: - -```js run -class Rabbit extends Object {} - -*!* -// обычно мы вызываем Object.getOwnPropertyNames -alert ( Rabbit.getOwnPropertyNames({a: 1, b: 2})); // a,b -*/!* -``` - -Но если явно не наследуем от объекта, то для `Rabbit.__ proto__` не установлено значение `Object`. - -Пример: - -```js run -class Rabbit {} - -alert( Rabbit.prototype.__proto__ === Object.prototype ); // (1) true -alert( Rabbit.__proto__ === Object ); // (2) false (!) -alert( Rabbit.__proto__ === Function.prototype ); // как у каждой функции по умолчанию - -*!* -// ошибка - нет такой функции у Rabbit -alert ( Rabbit.getOwnPropertyNames({a: 1, b: 2})); // Error -*/!* -``` - -Таким образом, в этом случае у `Rabbit` нет доступа к статическим методам `Object`. - -Кстати, у `Function.prototype` есть "общие" методы, такие как `call`, `bind` и т. д. Они в конечном итоге доступны в обоих случаях, потому что для встроенного конструктора `Object` `Object.__proto__ === Function.prototype`. - - -Пример на картинке: - -![](rabbit-extends-object.png) - -Короче говоря, есть два отличия: - -| class Rabbit | class Rabbit extends Object | -|--------------|------------------------------| -| -- | необходимо вызвать `super()` в конструкторе | -| `Rabbit.__proto__ === Function.prototype` | `Rabbit.__proto__ === Object` | diff --git a/1-js/09-classes/02-class-inheritance/3-class-extend-object/task.md b/1-js/09-classes/02-class-inheritance/3-class-extend-object/task.md deleted file mode 100644 index c17205348a..0000000000 --- a/1-js/09-classes/02-class-inheritance/3-class-extend-object/task.md +++ /dev/null @@ -1,43 +0,0 @@ -importance: 5 - ---- - -# Класс расширяет объект? - -Как мы уже знаем, все объекты наследуются от `Object.prototype` и имеют доступ к "общим" методам объекта, например `hasOwnProperty`. - -Пример: - -```js run -class Rabbit { - constructor(name) { - this.name = name; - } -} - -let rabbit = new Rabbit("Rab"); - -*!* -// метод hasOwnProperty от Object.prototype -// rabbit.__proto__ === Object.prototype -alert( rabbit.hasOwnProperty('name') ); // true -*/!* -``` - -Но что если мы явно напишем явно `"class Rabbit extends Object"` - тогда результат будет отличаться от обычного `"class Rabbit"`? - -В чем разница? - -Ниже пример кода с таким наследованием (почему он не работает?исправьте его): - -```js -class Rabbit extends Object { - constructor(name) { - this.name = name; - } -} - -let rabbit = new Rabbit("Rab"); - -alert( rabbit.hasOwnProperty('name') ); // true -``` diff --git a/1-js/09-classes/02-class-inheritance/animal-rabbit-extends.png b/1-js/09-classes/02-class-inheritance/animal-rabbit-extends.png deleted file mode 100644 index 0d887dbc0e..0000000000 Binary files a/1-js/09-classes/02-class-inheritance/animal-rabbit-extends.png and /dev/null differ 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 new file mode 100644 index 0000000000..203c59447a --- /dev/null +++ b/1-js/09-classes/02-class-inheritance/animal-rabbit-extends.svg @@ -0,0 +1 @@ +constructor: Animal run: function stop: functionAnimal.prototypeconstructor: Rabbit hide: functionRabbit.prototypeAnimalRabbitnew Rabbit[[Prototype]][[Prototype]]prototypeprototypename: "Белый кролик"constructorconstructorextends \ No newline at end of file diff --git a/1-js/09-classes/02-class-inheritance/animal-rabbit-extends@2x.png b/1-js/09-classes/02-class-inheritance/animal-rabbit-extends@2x.png deleted file mode 100644 index af09271a91..0000000000 Binary files a/1-js/09-classes/02-class-inheritance/animal-rabbit-extends@2x.png and /dev/null differ diff --git a/1-js/09-classes/02-class-inheritance/article.md b/1-js/09-classes/02-class-inheritance/article.md index 8e15d7b8ed..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,97 +16,68 @@ class Animal { this.name = name; } run(speed) { - this.speed += speed; - alert(`${this.name} runs with speed ${this.speed}.`); + this.speed = speed; + alert(`${this.name} бежит со скоростью ${this.speed}.`); } stop() { this.speed = 0; - alert(`${this.name} stopped.`); + alert(`${this.name} стоит неподвижно.`); } } -let animal = new Animal("My animal"); +let animal = new Animal("Мой питомец"); ``` -![](rabbit-animal-independent-animal.png) +Вот как мы можем представить объект `animal` и класс `Animal` графически: +![](rabbit-animal-independent-animal.svg) -...И `Rabbit`: +...И мы хотели бы создать ещё один `class Rabbit`. -```js -class Rabbit { - constructor(name) { - this.name = name; - } - hide() { - alert(`${this.name} hides!`); - } -} - -let rabbit = new Rabbit("My rabbit"); -``` - -![](rabbit-animal-independent-rabbit.png) +Поскольку кролики - это животные, класс `Rabbit` должен быть основан на `Animal`, и иметь доступ к методам животных, так чтобы кролики могли делать то, что могут делать "общие" животные. -Сейчас они полностью независимы. +Синтаксис для расширения другого класса следующий: `class Child extends Parent`. -Но мы хотим, чтобы `Rabbit` расширял `Animal`. Другими словами, кролики должны происходить от животных, т.е. иметь доступ к методам `Animal` и расширять функциональность `Animal` своими методами. - -Для того, чтобы наследовать класс от другого, мы должны использовать ключевое слово `"extends"` и указать название родительского класса перед `{..}`. - -Ниже `Rabbit` наследует от `Animal`: - -```js run -class Animal { - constructor(name) { - this.speed = 0; - this.name = name; - } - run(speed) { - this.speed += speed; - alert(`${this.name} runs with speed ${this.speed}.`); - } - stop() { - this.speed = 0; - alert(`${this.name} stopped.`); - } -} +Давайте создадим `class Rabbit`, который наследуется от `Animal`: -// Наследуем от Animal указывая "extends Animal" +```js *!* class Rabbit extends Animal { */!* hide() { - alert(`${this.name} hides!`); + alert(`${this.name} прячется!`); } } -let rabbit = new Rabbit("White Rabbit"); +let rabbit = new Rabbit("Белый кролик"); -rabbit.run(5); // White Rabbit runs with speed 5. -rabbit.hide(); // White Rabbit hides! +rabbit.run(5); // Белый кролик бежит со скоростью 5. +rabbit.hide(); // Белый кролик прячется! ``` -Теперь код `Rabbit` стал короче, так как используется конструктор класса `Animal` по умолчанию и кролик может использовать метод `run` как и все животные. -На самом деле ключевое слово `extends` добавляет ссылку на `[[Prototype]]` из `Rabbit.prototype` в `Animal.prototype`: +Объект класса `Rabbit` имеет доступ как к методам `Rabbit`, таким как `rabbit.hide()`, так и к методам `Animal`, таким как `rabbit.run()`. -![](animal-rabbit-extends.png) +Внутри ключевое слово `extends` работает по старой доброй механике прототипов. Оно устанавливает `Rabbit.prototype.[[Prototype]]` в `Animal.prototype`. Таким образом, если метода не оказалось в `Rabbit.prototype`, JavaScript берет его из `Animal.prototype`. -Если метод не найден в `Rabbit.prototype`, JavaScript возьмет его из `Animal.prototype`. +![](animal-rabbit-extends.svg) -Как мы помним из главы , в JavaScript используется одно и то же наследование на прототипах для встроенных объектов. Например `Date.prototype.[[Prototype]]` это `Object.prototype`, поэтому у дат есть универсальные методы объекта. +Например, чтобы найти метод `rabbit.run`, движок проверяет (снизу вверх на картинке): +1. Объект `rabbit` (не имеет `run`). +2. Его прототип, то есть `Rabbit.prototype` (имеет `hide`, но не имеет `run`). +3. Его прототип, то есть (вследствие `extends`) `Animal.prototype`, в котором, наконец, есть метод `run`. -````smart header="После `extends` разрешены любые выражения" -Синтаксис создания класса допускает указывать после `extends` не только класс, но любое выражение. +Как мы помним из главы , сам JavaScript использует наследование на прототипах для встроенных объектов. Например, `Date.prototype.[[Prototype]]` является `Object.prototype`, поэтому у дат есть универсальные методы объекта. +````smart header="После `extends` разрешены любые выражения" +Синтаксис создания класса допускает указывать после `extends` не только класс, но и любое выражение. Пример вызова функции, которая генерирует родительский класс: ```js run function f(phrase) { return class { - sayHi() { alert(phrase) } - } + sayHi() { alert(phrase); } + }; } *!* @@ -112,28 +88,30 @@ new User().sayHi(); // Привет ``` Здесь `class User` наследует от результата вызова `f("Привет")`. -Это может быть полезно для продвинутых приемов проектирования, где мы можем использовать функции для генерации классов в зависимости от многих условий и затем наследовать их. +Это может быть полезно для продвинутых приёмов проектирования, где мы можем использовать функции для генерации классов в зависимости от многих условий и затем наследовать их. ```` ## Переопределение методов -Давайте пойдём дальше и переопределим метод. Сейчас `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(...)` для вызова родительского конструктора (работает только внутри нашего конструктора). Пусть наш кролик автоматически прячется при остановке: @@ -146,20 +124,20 @@ class Animal { } run(speed) { - this.speed += speed; - alert(`${this.name} runs with speed ${this.speed}.`); + this.speed = speed; + alert(`${this.name} бежит со скоростью ${this.speed}.`); } stop() { this.speed = 0; - alert(`${this.name} stopped.`); + alert(`${this.name} стоит.`); } } class Rabbit extends Animal { hide() { - alert(`${this.name} hides!`); + alert(`${this.name} прячется!`); } *!* @@ -170,10 +148,10 @@ class Rabbit extends Animal { */!* } -let rabbit = new Rabbit("White Rabbit"); +let rabbit = new Rabbit("Белый кролик"); -rabbit.run(5); // White Rabbit runs with speed 5. -rabbit.stop(); // White Rabbit stopped. White rabbit hides! +rabbit.run(5); // Белый кролик бежит со скоростью 5. +rabbit.stop(); // Белый кролик стоит. Белый кролик прячется! ``` Теперь у класса `Rabbit` есть метод `stop`, который вызывает родительский `super.stop()` в процессе выполнения. @@ -181,7 +159,8 @@ rabbit.stop(); // White Rabbit stopped. White rabbit hides! ````smart header="У стрелочных функций нет `super`" Как упоминалось в главе , стрелочные функции не имеют `super`. -При обращении к `super` стрелочной функции он берется из внешней функции: +При обращении к `super` стрелочной функции он берётся из внешней функции: + ```js class Rabbit extends Animal { stop() { @@ -204,7 +183,7 @@ setTimeout(function() { super.stop() }, 1000); До сих пор у `Rabbit` не было своего конструктора. -Согласно [спецификации](https://tc39.github.io/ecma262/#sec-runtime-semantics-classdefinitionevaluation), если класс расширяет другой класс и не имеет конструктора, то автоматически создается такой "пустой" конструктор: +Согласно [спецификации](https://tc39.github.io/ecma262/#sec-runtime-semantics-classdefinitionevaluation), если класс расширяет другой класс и не имеет конструктора, то автоматически создаётся такой "пустой" конструктор: ```js class Rabbit extends Animal { @@ -245,28 +224,30 @@ class Rabbit extends Animal { *!* // Не работает! -let rabbit = new Rabbit("White Rabbit", 10); // Error: this is not defined. +let rabbit = new Rabbit("Белый кролик", 10); // Error: this is not defined. */!* ``` Упс! При создании кролика - ошибка! Что не так? -Если коротко, то в классах-потомках конструктор обязан вызывать `super(...)`, и (!) делать это перед использованием `this`. +Если коротко, то: + +- **Конструкторы в наследуемых классах должны обязательно вызывать `super(...)`, и (!) делать это перед использованием `this`.** ...Но почему? Что происходит? Это требование кажется довольно странным. -Конечно, всему есть объяснение. Давайте углубимся в детали, чтобы вы действительно поняли, что происходит. +Конечно, всему есть своё объяснение. Давайте углубимся в детали, чтобы вы действительно поняли, что происходит. В JavaScript существует различие между "функцией-конструктором наследующего класса" и всеми остальными. В наследующем классе соответствующая функция-конструктор помечена специальным внутренним свойством `[[ConstructorKind]]:"derived"`. Разница в следующем: -- Когда выполняется обычный конструктор, он создает пустой объект и продолжает работать с ним как с `this` . -- Когда запускается конструктор унаследованного класса, он этого не делает. Вместо этого он ждет, что это сделает конструктор родительского класса. +- Когда выполняется обычный конструктор, он создаёт пустой объект и присваивает его `this` . +- Когда запускается конструктор унаследованного класса, он этого не делает. Вместо этого он ждёт, что это сделает конструктор родительского класса. -Поэтому, если мы создаем собственный конструктор, мы должны вызвать `super`, в противном случае объект со ссылкой на `this` не будет создан, и мы получим ошибку. +Поэтому, если мы создаём собственный конструктор, мы должны вызвать `super`, в противном случае объект для `this` не будет создан, и мы получим ошибку. -Для `Rabbit` мы должны вызвать `super()` до того, как используем `this`, чтобы не было ошибки: +Чтобы конструктор `Rabbit` работал, он должен вызвать `super()` до того, как использовать `this`, чтобы не было ошибки: ```js run class Animal { @@ -293,36 +274,139 @@ class Rabbit extends Animal { *!* // теперь работает -let rabbit = new Rabbit("White Rabbit", 10); -alert(rabbit.name); // White Rabbit +let rabbit = new Rabbit("Белый кролик", 10); +alert(rabbit.name); // Белый кролик 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="Продвинутая информация" +Если вы читаете учебник первый раз - эту секцию можно пропустить. + +Она рассказывает о внутреннем устройстве наследования и вызовe `super`. +``` + Давайте заглянем "под капот" `super`. Здесь есть некоторые интересные моменты. -Вообще, исходя из наших знаний до этого момента, `super` вообще "не может" работать. +Вообще, исходя из наших знаний до этого момента, `super` вообще не может работать! -Давайте попробуем прикинуть, как технически это может работать. Когда метод объекта выполняется, он получает текущий объект как `this`. Если мы вызываем `super.method()`, то ему необходимо плучить `method` из прототипа текущего объекта. Как движок JavaScript должен получить прототип `this`? +Ну правда, давайте спросим себя - как он должен работать, чисто технически? Когда метод объекта выполняется, он получает текущий объект как `this`. Если мы вызываем `super.method()`, то движку необходимо получить `method` из прототипа текущего объекта. И как ему это сделать? -Задача может показаться простой, но это не так. Движок может попытаться получить метод из `[[Prototype]]` `this`, используя `this.__proto__.method`. К сожалению, это не работает. +Задача может показаться простой, но это не так. Движок знает текущий `this` и мог бы попытаться получить родительский метод как `this.__proto__.method`. Однако, увы, такой "наивный" путь не работает. Продемонстрируем проблему. Без классов, используя простые объекты для наглядности. +Вы можете пропустить эту часть и перейти ниже к подсекции `[[HomeObject]]`, если не хотите знать детали. Вреда не будет. Или читайте далее, если хотите разобраться. + В примере ниже `rabbit.__proto__ = animal`. Попробуем в `rabbit.eat()` вызвать `animal.eat()`, используя `this.__proto__`: + ```js run let animal = { name: "Animal", eat() { - alert(`${this.name} eats.`); + alert(`${this.name} ест.`); } }; let rabbit = { __proto__: animal, - name: "Rabbit", + name: "Кролик", eat() { *!* // вот как предположительно может работать super.eat() @@ -331,20 +415,20 @@ let rabbit = { } }; -rabbit.eat(); // Rabbit eats. +rabbit.eat(); // Кролик ест. ``` -В строке `(*)` мы берем `eat` из прототипа (`animal`) и вызываем его в контексте текущего объекта. Обратите внимание, что `.call(this)` здесь неспроста: простой вызов `this.__proto__.eat()` будет выполнять родительский `eat` в контексте прототипа, а не текущего объекта. +В строке `(*)` мы берём `eat` из прототипа (`animal`) и вызываем его в контексте текущего объекта. Обратите внимание, что `.call(this)` здесь неспроста: простой вызов `this.__proto__.eat()` будет выполнять родительский `eat` в контексте прототипа, а не текущего объекта. -Приведенный выше код работает так, как задумано: выполняется нужный `alert`. +Приведённый выше код работает так, как задумано: выполняется нужный `alert`. -Теперь давайте добавим еще один объект в цепочку наследования и увидим, как все сломается: +Теперь давайте добавим ещё один объект в цепочку наследования и увидим, как все сломается: ```js run let animal = { - name: "Animal", + name: "Животное", eat() { - alert(`${this.name} eats.`); + alert(`${this.name} ест.`); } }; @@ -359,7 +443,7 @@ let rabbit = { let longEar = { __proto__: rabbit, eat() { - // ...делаем что-то, связанное с длинными ушами и вызываем родительский (rabbit) метод + // ...делаем что-то, связанное с длинными ушами, и вызываем родительский (rabbit) метод this.__proto__.eat.call(this); // (**) } }; @@ -371,13 +455,13 @@ longEar.eat(); // Error: Maximum call stack size exceeded Теперь код не работает! Ошибка возникает при попытке вызова `longEar.eat()`. -На первый взгляд все не так очевидно, но если мы проследим вызов `longEar.eat()`, то сможем понять причину ошибки. В обеих строках `(*)` и `(**)` значение `this` - это текущий объект (`longEar`). Это важно: для всех методов объекта `this` указывает на текущий объект, а не на прототип или что-то еще. +На первый взгляд все не так очевидно, но если мы проследим вызов `longEar.eat()`, то сможем понять причину ошибки. В обеих строках `(*)` и `(**)` значение `this` - это текущий объект (`longEar`). Это важно: для всех методов объекта `this` указывает на текущий объект, а не на прототип или что-то ещё. Итак, в обеих линиях `(*)` и `(**)` значение `this.__proto__` одно и то же: `rabbit`. В обоих случаях метод `rabbit.eat` вызывается в бесконечном цикле не поднимаясь по цепочке вызовов. Картина того, что происходит: -![](this-super-loop.png) +![](this-super-loop.svg) 1. Внутри `longEar.eat()` строка `(**)` вызывает `rabbit.eat` со значением `this=longEar`. ```js @@ -388,7 +472,7 @@ longEar.eat(); // Error: Maximum call stack size exceeded // то же что и rabbit.eat.call(this); ``` -2. В строке `(*)` в `rabbit.eat` мы хотим передать вызов выше по цепочке, но `this=longEar`, поэтому `this.__proto__.eat` снова равен `rabbit.eat`! +2. В строке `(*)` в `rabbit.eat` мы хотим передать вызов выше по цепочке, но `this=longEar`, поэтому `this.__proto__.eat` снова равен `rabbit.eat`! ```js // внутри rabbit.eat() у нас также this = longEar @@ -399,7 +483,7 @@ longEar.eat(); // Error: Maximum call stack size exceeded rabbit.eat.call(this); ``` -3. ... `rabbit.eat` вызывает себя в бесконечном цикле, потому что не может подняться дальше по цепочке. +3. ...`rabbit.eat` вызывает себя в бесконечном цикле, потому что не может подняться дальше по цепочке. Проблема не может быть решена с помощью одного только `this`. @@ -407,52 +491,111 @@ longEar.eat(); // Error: Maximum call stack size exceeded Для решения этой проблемы в JavaScript было добавлено специальное внутреннее свойство для функций: `[[HomeObject]]`. -**Когда функция объявлена как метод внутри класса или объекта, ее свойство `[[HomeObject]]` становится равно этому объекту.** +Когда функция объявлена как метод внутри класса или объекта, её свойство `[[HomeObject]]` становится равно этому объекту. -Это фактически нарушает идею «свободных» функций, потому что методы помнят свои объекты. `[[HomeObject]]` нельзя изменить, поэтому эта связь навсегда. Это очень важное изменение в языке. +Затем `super` использует его, чтобы получить прототип родителя и его методы. -Но это изменение в языке безопасно. `[[HomeObject]]` используется только для вызова родительских методов в `super` чтобы найти прототип. Так что оно не сломает существующий код. Обычные вызовы ничего про `[[HomeObject]]` не знают, он только для super. - -Давайте посмотрим, как это работает для `super` - опять же, используя простые объекты: +Давайте посмотрим, как это работает - опять же, используя простые объекты: ```js run let animal = { - name: "Animal", - eat() { // [[HomeObject]] == animal - alert(`${this.name} eats.`); + name: "Животное", + eat() { // animal.eat.[[HomeObject]] == animal + alert(`${this.name} ест.`); } }; let rabbit = { __proto__: animal, - name: "Rabbit", - eat() { // [[HomeObject]] == rabbit + name: "Кролик", + eat() { // rabbit.eat.[[HomeObject]] == rabbit super.eat(); } }; let longEar = { __proto__: rabbit, - name: "Long Ear", - eat() { // [[HomeObject]] == longEar + name: "Длинноух", + eat() { // longEar.eat.[[HomeObject]] == longEar super.eat(); } }; *!* -longEar.eat(); // Long Ear eats. +// работает верно +longEar.eat(); // Длинноух ест. +*/!* +``` + +Это работает как задумано благодаря `[[HomeObject]]`. Метод, такой как `longEar.eat`, знает свой `[[HomeObject]]` и получает метод родителя из его прототипа. Вообще без использования `this`. + +### Методы не "свободны" + +До этого мы неоднократно видели, что функции в JavaScript "свободны", не привязаны к объектам. Их можно копировать между объектами и вызывать с любым `this`. + +Но само существование `[[HomeObject]]` нарушает этот принцип, так как методы запоминают свои объекты. `[[HomeObject]]` нельзя изменить, эта связь - навсегда. + +Единственное место в языке, где используется `[[HomeObject]]` - это `super`. Поэтому если метод не использует `super`, то мы все ещё можем считать его свободным и копировать между объектами. А вот если `super` в коде есть, то возможны побочные эффекты. + +Вот пример неверного результата `super` после копирования: + +```js run +let animal = { + sayHi() { + alert("Я животное"); + } +}; + +// rabbit наследует от animal +let rabbit = { + __proto__: animal, + sayHi() { + super.sayHi(); + } +}; + +let plant = { + sayHi() { + alert("Я растение"); + } +}; + +// tree наследует от plant +let tree = { + __proto__: plant, +*!* + sayHi: rabbit.sayHi // (*) +*/!* +}; + +*!* +tree.sayHi(); // Я животное (?!?) */!* ``` -Каждый метод запоминает свой объект во внутреннем свойстве `[[HomeObject]]`. Затем `super` использует его для получения родительского прототипа. +Вызов `tree.sayHi()` показывает "Я животное". Определённо неверно. + +Причина проста: +- В строке `(*)`, метод `tree.sayHi` скопирован из `rabbit`. Возможно, мы хотели избежать дублирования кода? +- Его `[[HomeObject]]` - это `rabbit`, ведь он был создан в `rabbit`. Свойство `[[HomeObject]]` никогда не меняется. +- В коде `tree.sayHi()` есть вызов `super.sayHi()`. Он идёт вверх от `rabbit` и берёт метод из `animal`. + +Вот диаграмма происходящего: -`[[HomeObject]]` определен для методов и в классах и в литеральных объектах. Но для объектов методы должны быть определены именно как `method()`, а не как `"method: function()"`. +![](super-homeobject-wrong.svg) -В приведенном ниже примере метод определен через двоеточие и ключевое слово `function`. Свойство `[[HomeObject]]` не установлено и поэтому наследование не работает: + +### Методы, а не свойства-функции + +Свойство `[[HomeObject]]` определено для методов как классов, так и обычных объектов. Но для объектов методы должны быть объявлены именно как `method()`, а не `"method: function()"`. + +Для нас различий нет, но они есть для JavaScript. + +В приведённом ниже примере используется синтаксис не метода, свойства-функции. Поэтому у него нет `[[HomeObject]]`, и наследование не работает: ```js run let animal = { - eat: function() { // должен быть короткий синтаксис: eat() {...} + eat: function() { // намеренно пишем так, а не eat() { ... // ... } }; @@ -468,3 +611,19 @@ let rabbit = { rabbit.eat(); // Ошибка вызова super (потому что нет [[HomeObject]]) */!* ``` + + +## Итого + +1. Чтобы унаследовать от класса: `class Child extends Parent`: + - При этом `Child.prototype.__proto__` будет равен `Parent.prototype`, так что методы будут унаследованы. +2. При переопределении конструктора: + - Обязателен вызов конструктора родителя `super()` в конструкторе `Child` до обращения к `this`. +3. При переопределении другого метода: + - Мы можем вызвать `super.method()` в методе `Child` для обращения к методу родителя `Parent`. +4. Внутренние детали: + - Методы запоминают свой объект во внутреннем свойстве `[[HomeObject]]`. Благодаря этому работает `super`, он в его прототипе ищет родительские методы. + - Поэтому копировать метод, использующий `super`, между разными объектами небезопасно. + +Также: +- У стрелочных функций нет своего `this` и `super`, поэтому они "прозрачно" встраиваются во внешний контекст. diff --git a/1-js/09-classes/02-class-inheritance/class-inheritance-array-object.png b/1-js/09-classes/02-class-inheritance/class-inheritance-array-object.png deleted file mode 100644 index 8d30622ceb..0000000000 Binary files a/1-js/09-classes/02-class-inheritance/class-inheritance-array-object.png and /dev/null differ 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 new file mode 100644 index 0000000000..5ea9bf29ea --- /dev/null +++ b/1-js/09-classes/02-class-inheritance/class-inheritance-array-object.svg @@ -0,0 +1 @@ +slice: function ...Array.prototypearrhasOwnProperty: function ...Object.prototype[1, 2, 3][[Prototype]][[Prototype]] \ No newline at end of file diff --git a/1-js/09-classes/02-class-inheritance/class-inheritance-array-object@2x.png b/1-js/09-classes/02-class-inheritance/class-inheritance-array-object@2x.png deleted file mode 100644 index 00f6bd80d0..0000000000 Binary files a/1-js/09-classes/02-class-inheritance/class-inheritance-array-object@2x.png and /dev/null differ diff --git a/1-js/09-classes/02-class-inheritance/class-inheritance-rabbit-animal-2.png b/1-js/09-classes/02-class-inheritance/class-inheritance-rabbit-animal-2.png deleted file mode 100644 index f8afbbcd64..0000000000 Binary files a/1-js/09-classes/02-class-inheritance/class-inheritance-rabbit-animal-2.png and /dev/null differ 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 new file mode 100644 index 0000000000..2172088e64 --- /dev/null +++ b/1-js/09-classes/02-class-inheritance/class-inheritance-rabbit-animal-2.svg @@ -0,0 +1 @@ +jump: functionRabbit.prototyperabbiteat: functionAnimal.prototypename: "Белый кролик"[[Prototype]][[Prototype]]Rabbit.prototype.__proto__ = Animal.prototype sets thistoString: function hasOwnProperty: function ...Object.prototype[[Prototype]][[Prototype]]null \ No newline at end of file diff --git a/1-js/09-classes/02-class-inheritance/class-inheritance-rabbit-animal-2@2x.png b/1-js/09-classes/02-class-inheritance/class-inheritance-rabbit-animal-2@2x.png deleted file mode 100644 index cf5aa6554f..0000000000 Binary files a/1-js/09-classes/02-class-inheritance/class-inheritance-rabbit-animal-2@2x.png and /dev/null differ diff --git a/1-js/09-classes/02-class-inheritance/class-inheritance-rabbit-animal.png b/1-js/09-classes/02-class-inheritance/class-inheritance-rabbit-animal.png deleted file mode 100644 index a6f8964e6c..0000000000 Binary files a/1-js/09-classes/02-class-inheritance/class-inheritance-rabbit-animal.png and /dev/null differ 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 new file mode 100644 index 0000000000..1b21dd81b5 --- /dev/null +++ b/1-js/09-classes/02-class-inheritance/class-inheritance-rabbit-animal.svg @@ -0,0 +1 @@ +методы RabbitRabbit.prototyperabbitметоды AnimalAnimal.prototype[[Prototype]][[Prototype]]свойства rabbit \ No newline at end of file diff --git a/1-js/09-classes/02-class-inheritance/class-inheritance-rabbit-animal@2x.png b/1-js/09-classes/02-class-inheritance/class-inheritance-rabbit-animal@2x.png deleted file mode 100644 index 2e3f4d7ffa..0000000000 Binary files a/1-js/09-classes/02-class-inheritance/class-inheritance-rabbit-animal@2x.png and /dev/null differ diff --git a/1-js/09-classes/02-class-inheritance/class-inheritance-rabbit-run-animal.png b/1-js/09-classes/02-class-inheritance/class-inheritance-rabbit-run-animal.png deleted file mode 100644 index 387975a9c7..0000000000 Binary files a/1-js/09-classes/02-class-inheritance/class-inheritance-rabbit-run-animal.png and /dev/null differ diff --git a/1-js/09-classes/02-class-inheritance/class-inheritance-rabbit-run-animal@2x.png b/1-js/09-classes/02-class-inheritance/class-inheritance-rabbit-run-animal@2x.png deleted file mode 100644 index ca73135933..0000000000 Binary files a/1-js/09-classes/02-class-inheritance/class-inheritance-rabbit-run-animal@2x.png and /dev/null differ diff --git a/1-js/09-classes/02-class-inheritance/rabbit-animal-independent-animal.png b/1-js/09-classes/02-class-inheritance/rabbit-animal-independent-animal.png deleted file mode 100644 index 79351a7540..0000000000 Binary files a/1-js/09-classes/02-class-inheritance/rabbit-animal-independent-animal.png and /dev/null differ 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 new file mode 100644 index 0000000000..62b876d3ac --- /dev/null +++ b/1-js/09-classes/02-class-inheritance/rabbit-animal-independent-animal.svg @@ -0,0 +1 @@ + constructor: Animal run: function stop: functionAnimal.prototypeAnimalnew Animal[[Prototype]]prototypename: "Мой питомец" \ No newline at end of file diff --git a/1-js/09-classes/02-class-inheritance/rabbit-animal-independent-animal@2x.png b/1-js/09-classes/02-class-inheritance/rabbit-animal-independent-animal@2x.png deleted file mode 100644 index 346574e0c6..0000000000 Binary files a/1-js/09-classes/02-class-inheritance/rabbit-animal-independent-animal@2x.png and /dev/null differ diff --git a/1-js/09-classes/02-class-inheritance/rabbit-animal-independent-rabbit.png b/1-js/09-classes/02-class-inheritance/rabbit-animal-independent-rabbit.png deleted file mode 100644 index 3d3b78cca5..0000000000 Binary files a/1-js/09-classes/02-class-inheritance/rabbit-animal-independent-rabbit.png and /dev/null differ 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 new file mode 100644 index 0000000000..ff669d3d84 --- /dev/null +++ b/1-js/09-classes/02-class-inheritance/rabbit-animal-independent-rabbit.svg @@ -0,0 +1 @@ + constructor: Rabbit hide: functionRabbit.prototypeRabbitnew Rabbit[[Prototype]]prototypename: "Мой кролик" \ No newline at end of file diff --git a/1-js/09-classes/02-class-inheritance/rabbit-animal-independent-rabbit@2x.png b/1-js/09-classes/02-class-inheritance/rabbit-animal-independent-rabbit@2x.png deleted file mode 100644 index a923d10e55..0000000000 Binary files a/1-js/09-classes/02-class-inheritance/rabbit-animal-independent-rabbit@2x.png and /dev/null differ 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 new file mode 100644 index 0000000000..f6450ddc49 --- /dev/null +++ b/1-js/09-classes/02-class-inheritance/super-homeobject-wrong.svg @@ -0,0 +1 @@ +sayHiplantsayHitreesayHianimalrabbit[[HomeObject]]sayHi \ No newline at end of file diff --git a/1-js/09-classes/02-class-inheritance/this-super-loop.png b/1-js/09-classes/02-class-inheritance/this-super-loop.png deleted file mode 100644 index e68ed70e7e..0000000000 Binary files a/1-js/09-classes/02-class-inheritance/this-super-loop.png and /dev/null differ 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 new file mode 100644 index 0000000000..4f5f45034a --- /dev/null +++ b/1-js/09-classes/02-class-inheritance/this-super-loop.svg @@ -0,0 +1 @@ +rabbitlongEarrabbitlongEar \ No newline at end of file diff --git a/1-js/09-classes/02-class-inheritance/this-super-loop@2x.png b/1-js/09-classes/02-class-inheritance/this-super-loop@2x.png deleted file mode 100644 index 037d075874..0000000000 Binary files a/1-js/09-classes/02-class-inheritance/this-super-loop@2x.png and /dev/null differ diff --git a/1-js/09-classes/03-static-properties-methods/1-class-extend-object/rabbit-extends-object.svg b/1-js/09-classes/03-static-properties-methods/1-class-extend-object/rabbit-extends-object.svg new file mode 100644 index 0000000000..34d783b4de --- /dev/null +++ b/1-js/09-classes/03-static-properties-methods/1-class-extend-object/rabbit-extends-object.svg @@ -0,0 +1 @@ +call: function bind: function ...Function.prototypeconstructorObjectRabbit[[Prototype]][[Prototype]]constructorcall: function bind: function ...Function.prototypeRabbit[[Prototype]]constructorclass Rabbitclass Rabbit extends Object \ No newline at end of file diff --git a/1-js/09-classes/03-static-properties-methods/1-class-extend-object/solution.md b/1-js/09-classes/03-static-properties-methods/1-class-extend-object/solution.md new file mode 100644 index 0000000000..3c1a42f600 --- /dev/null +++ b/1-js/09-classes/03-static-properties-methods/1-class-extend-object/solution.md @@ -0,0 +1,82 @@ +Сперва давайте разберёмся, почему код не работает. + +Причина становится очевидна, если мы попытаемся запустить его. Унаследованный конструктор класса должен вызывать `super()`. В противном случае `"this"` будет не определён. + +Решение: + +```js run +class Rabbit extends Object { + constructor(name) { +*!* + super(); // надо вызвать конструктор родителя, когда наследуемся +*/!* + this.name = name; + } +} + +let rabbit = new Rabbit("Кроль"); + +alert( rabbit.hasOwnProperty('name') ); // true +``` + +Но это ещё не все. + +Даже после исправления есть важное различие между `"class Rabbit extends Object"` и `class Rabbit`. + +Как мы знаем, синтаксис "extends" устанавливает 2 прототипа: + +1. Между `"prototype"` функций-конструкторов (для методов) +2. Между самими функциями-конструкторами (для статических методов). + +В случае с `class Rabbit extends Object` это значит: + +```js run +class Rabbit extends Object {} + +alert( Rabbit.prototype.__proto__ === Object.prototype ); // (1) true +alert( Rabbit.__proto__ === Object ); // (2) true +``` + +Таким образом, `Rabbit` предоставляет доступ к статическим методам `Object` через `Rabbit`, например: + +```js run +class Rabbit extends Object {} + +*!* +// обычно мы вызываем Object.getOwnPropertyNames +alert( Rabbit.getOwnPropertyNames({a: 1, b: 2}) ); // a,b +*/!* +``` + +Но если явно не наследуем от объекта, то для `Rabbit.__proto__` не установлено значение `Object`. + +Пример: + +```js run +class Rabbit {} + +alert( Rabbit.prototype.__proto__ === Object.prototype ); // (1) true +alert( Rabbit.__proto__ === Object ); // (2) false (!) +alert( Rabbit.__proto__ === Function.prototype ); // как у каждой функции по умолчанию + +*!* +// ошибка - нет такой функции у Rabbit +alert( Rabbit.getOwnPropertyNames({a: 1, b: 2}) ); // Ошибка +*/!* +``` + +Таким образом, в этом случае у `Rabbit` нет доступа к статическим методам `Object`. + +Кстати, у `Function.prototype` также есть "общие" методы, такие как `call`, `bind` и т. д. Они в конечном итоге доступны в обоих случаях, потому что для встроенного конструктора `Object` `Object.__proto__ === Function.prototype`. + + +Пример на картинке: + +![](rabbit-extends-object.svg) + +Короче говоря, есть два отличия: + +| class Rabbit | class Rabbit extends Object | +|--------------|------------------------------| +| -- | необходимо вызвать `super()` в конструкторе | +| `Rabbit.__proto__ === Function.prototype` | `Rabbit.__proto__ === Object` | diff --git a/1-js/09-classes/03-static-properties-methods/1-class-extend-object/task.md b/1-js/09-classes/03-static-properties-methods/1-class-extend-object/task.md new file mode 100644 index 0000000000..55586e055a --- /dev/null +++ b/1-js/09-classes/03-static-properties-methods/1-class-extend-object/task.md @@ -0,0 +1,42 @@ +importance: 3 + +--- + +# Класс расширяет объект? + +Как мы уже знаем, все объекты наследуют от `Object.prototype` и имеют доступ к "общим" методам объекта, например `hasOwnProperty`. + +Пример: + +```js run +class Rabbit { + constructor(name) { + this.name = name; + } +} + +let rabbit = new Rabbit("Rab"); + +*!* +// метод hasOwnProperty от Object.prototype +alert( rabbit.hasOwnProperty('name') ); // true +*/!* +``` + +Но что если мы явно напишем `"class Rabbit extends Object"` - тогда результат будет отличаться от обычного `"class Rabbit"`? + +В чем разница? + +Ниже пример кода с таким наследованием (почему он не работает? исправьте его): + +```js +class Rabbit extends Object { + constructor(name) { + this.name = name; + } +} + +let rabbit = new Rabbit("Кроль"); + +alert( rabbit.hasOwnProperty('name') ); // Ошибка +``` diff --git a/1-js/09-classes/03-static-properties-methods/animal-rabbit-static.png b/1-js/09-classes/03-static-properties-methods/animal-rabbit-static.png deleted file mode 100644 index c5e7e3e490..0000000000 Binary files a/1-js/09-classes/03-static-properties-methods/animal-rabbit-static.png and /dev/null differ 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 new file mode 100644 index 0000000000..ee1fc54748 --- /dev/null +++ b/1-js/09-classes/03-static-properties-methods/animal-rabbit-static.svg @@ -0,0 +1 @@ +constructor: Animal run: functionAnimal.prototypeconstructor: Rabbit hide: functionRabbit.prototypeAnimalRabbitrabbit[[Prototype]][[Prototype]][[Prototype]]prototypeprototypecomparename: "Белый кролик" \ No newline at end of file diff --git a/1-js/09-classes/03-static-properties-methods/animal-rabbit-static@2x.png b/1-js/09-classes/03-static-properties-methods/animal-rabbit-static@2x.png deleted file mode 100644 index de434af2cf..0000000000 Binary files a/1-js/09-classes/03-static-properties-methods/animal-rabbit-static@2x.png and /dev/null differ 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 1e8a578b48..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`, например: ```js run class User { @@ -17,21 +17,25 @@ class User { User.staticMethod(); // true ``` -Это фактически то же самое, что присвоить метод как свойство функции: +Это фактически то же самое, что присвоить метод напрямую как свойство функции: ```js -function User() { } +class User { } User.staticMethod = function() { alert(this === User); }; ``` -Значением `this` внутри `User.staticMethod()` является сам конструктор класса `User` (правило "объект до точки"). +Значением `this` при вызове `User.staticMethod()` является сам конструктор класса `User` (правило "объект до точки"). -Обычно статические методы используются для реализации функций, принадлежащих классу, но не к каким-то конкретным его объектам. +Обычно статические методы используются для реализации функций, которые будут принадлежать классу в целом, но не какому-либо его конкретному объекту. -Например, есть объекты статей `Article` и нужна функция для их сравнения. Естественным выбором будет такой метод `Article.compare`: +Звучит не очень понятно? Сейчас все встанет на свои места. + +Например, есть объекты статей `Article`, и нужна функция для их сравнения. + +Естественное решение – сделать для этого статический метод `Article.compare`: ```js run class Article { @@ -61,13 +65,15 @@ articles.sort(Article.compare); alert( articles[0].title ); // CSS ``` -Здесь метод `Article.compare` стоит "над" статьями, как способ их сравнения. Это метод не отдельной статьи, а всего класса. +Здесь метод `Article.compare` стоит "над" статьями, как средство для их сравнения. Это метод не отдельной статьи, а всего класса. -Другим примером может быть так называемый "фабричный" метод. Представим, что нам нужно создавать статьи различными способами: +Другим примером может быть так называемый "фабричный" метод. + +Скажем, нам нужно несколько способов создания статьи: 1. Создание через заданные параметры (`title`, `date` и т. д.). 2. Создание пустой статьи с сегодняшней датой. -3. ... +3. ...или как-то ещё. Первый способ может быть реализован через конструктор. А для второго можно использовать статический метод класса. @@ -93,21 +99,30 @@ let article = Article.createTodays(); alert( article.title ); // Сегодняшний дайджест ``` -Теперь каждый раз, когда нам нужно создать сегодняшний дайджест, нужно вызывать `Article.createTodays()`. Еще раз, это не метод одной статьи, а метод всего класса. +Теперь каждый раз, когда нам нужно создать сегодняшний дайджест, нужно вызывать `Article.createTodays()`. Ещё раз, это не метод одной статьи, а метод всего класса. Статические методы также используются в классах, относящихся к базам данных, для поиска/сохранения/удаления вхождений в базу данных, например: ```js // предположим, что Article - это специальный класс для управления статьями -// статический метод для удаления статьи: +// статический метод для удаления статьи по id: Article.remove({id: 12345}); ``` -## Статические свойства +````warn header="Статические методы недоступны для отдельных объектов" +Статические методы могут вызываться для классов, но не для отдельных объектов. + +Например. такой код не будет работать: + +```js +// ... +article.createTodays(); /// Error: article.createTodays is not a function +``` +```` -[recent browser=Chrome] +## Статические свойства -Статические свойства также возможны, как и обычные свойства класса: +Статические свойства также возможны, они выглядят как свойства класса, но с `static` в начале: ```js run class Article { @@ -123,9 +138,9 @@ alert( Article.publisher ); // Илья Кантор Article.publisher = "Илья Кантор"; ``` -## Статика и наследование +## Наследование статических свойств и методов [#statics-and-inheritance] -Статика наследуется, мы можем получить доступ к `Parent.method`, используя `Child.method`. +Статические свойства и методы наследуются. Например, метод `Animal.compare` в коде ниже наследуется и доступен как `Rabbit.compare`: @@ -150,7 +165,7 @@ class Animal { } -// Наследуется от Animal +// Наследует от Animal class Rabbit extends Animal { hide() { alert(`${this.name} прячется!`); @@ -159,46 +174,49 @@ class Rabbit extends Animal { let rabbits = [ new Rabbit("Белый кролик", 10), - new Rabbit("Черный кролик", 5) + new Rabbit("Чёрный кролик", 5) ]; *!* rabbits.sort(Rabbit.compare); */!* -rabbits[0].run(); // Черный кролик бежит со скоростью 5. +rabbits[0].run(); // Чёрный кролик бежит со скоростью 5. ``` -Мы можем вызвать `Rabbit.compare`, имея ввиду, что будет вызван унаследованный `Animal.compare`. +Мы можем вызвать `Rabbit.compare`, при этом будет вызван унаследованный `Animal.compare`. -Как это работает? Снова с использованием прототипов. Как вы уже могли предположить, `extends` даёт Rabbit ссылку `[[Prototype]]` на Animal. +Как это работает? Снова с использованием прототипов. Как вы уже могли предположить, `extends` даёт `Rabbit` ссылку `[[Prototype]]` на `Animal`. +![](animal-rabbit-static.svg) -![](animal-rabbit-static.png) +Так что `Rabbit extends Animal` создаёт две ссылки на прототип: -Итак, функция `Rabbit` теперь наследуется от функции `Animal`. А функция `Animal` имеет `[[Prototype]]`, равный `Function.prototype`, потому что не расширяет (`extend`) что-либо. +1. Функция `Rabbit` прототипно наследует от функции `Animal`. +2. `Rabbit.prototype` прототипно наследует от `Animal.prototype`. -Давайте это проверим: +В результате наследование работает как для обычных, так и для статических методов. + +Давайте это проверим кодом: ```js run class Animal {} class Rabbit extends Animal {} -// для статических свойств и методов +// для статики alert(Rabbit.__proto__ === Animal); // true -// а еще шаг наверх ведет к Function.prototype -alert(Animal.__proto__ === Function.prototype); // true - -// обычная цепочка прототипов для методов объектов -alert(Rabbit.prototype.__proto__ === Animal.prototype); +// для обычных методов +alert(Rabbit.prototype.__proto__ === Animal.prototype); // true ``` -Таким образом `Rabbit` имеет доступ ко всем статическим методам `Animal`. - ## Итого -Статические методы используются для функциональности, которая относится не к конкретному объекту класса, не требует существования этого объекта, а скорее принадлежит полностью классу, как например `Article.compare` -- универсальный метод для сравнения двух статей. +Статические методы используются для функциональности, принадлежат классу "в целом", а не относятся к конкретному объекту класса. + +Например, метод для сравнения двух статей `Article.compare(article1, article2)` или фабричный метод `Article.createTodays()`. + +В объявлении класса они помечаются ключевым словом `static`. Статические свойства используются в тех случаях, когда мы хотели бы сохранить данные на уровне класса, а не какого-то одного объекта. @@ -214,13 +232,13 @@ class MyClass { } ``` -Технически это то же самое, что и присвоение классу: +Технически, статическое объявление - это то же самое, что и присвоение классу: ```js MyClass.property = ... MyClass.method = ... ``` -Статические свойства наследуются. +Статические свойства и методы наследуются. -Технически, для `class B extends A` прототип класса `B` указывает на `A`: `B.[[Prototype]] = A`. Таким образом, если поле не найдено в `B`, поиск продолжается в `A`. +Для `class B extends A` прототип класса `B` указывает на `A`: `B.[[Prototype]] = A`. Таким образом, если поле не найдено в `B`, поиск продолжается в `A`. 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 ea67534190..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 @@ -1,102 +1,102 @@ -# Private and protected properties and methods +# Приватные и защищённые методы и свойства -One of the most important principles of object oriented programming -- delimiting internal interface from the external one. +Один из важнейших принципов объектно-ориентированного программирования -- разделение внутреннего и внешнего интерфейсов. -That is "a must" practice in developing anything more complex than a "hello world" app. +Это обязательная практика в разработке чего-либо сложнее, чем "hello world". -To understand this, let's break away from development and turn our eyes into the real world. +Чтобы понять этот принцип, давайте на секунду забудем о программировании и обратим взгляд на реальный мир. -Usually, devices that we're using are quite complex. But delimiting the internal interface from the external one allows to use them without problems. +Устройства, которыми мы пользуемся, обычно довольно сложно устроены. Но разделение внутреннего и внешнего интерфейсов позволяет нам пользоваться ими без каких-либо проблем. -## A real-life example +## Пример из реальной жизни -For instance, a coffee machine. Simple from outside: a button, a display, a few holes...And, surely, the result -- great coffee! :) +Например, кофеварка. Простая снаружи: кнопка, экран, несколько отверстий... И, конечно, результат -- прекрасный кофе! :) ![](coffee.jpg) -But inside... (a picture from the repair manual) +Но внутри... (картинка из инструкции по ремонту) ![](coffee-inside.jpg) -A lot of details. But we can use it without knowing anything. +Множество деталей. Но мы можем пользоваться ею, ничего об этом не зная. -Coffee machines are quite reliable, aren't they? We can use one for years, and only if something goes wrong -- bring it for repairs. +Кофеварки довольно надёжны, не так ли? Мы можем пользоваться ими годами, и если что-то пойдёт не так - отнесём в ремонт. -The secret of reliability and simplicity of a coffee machine -- all details are well-tuned and *hidden* inside. +Секрет надёжности и простоты кофеварки -- все детали хорошо отлажены и *спрятаны* внутри. -If we remove the protective cover from the coffee machine, then using it will be much more complex (where to press?), and dangerous (it can electrocute). +Если мы снимем защитный кожух с кофеварки, то пользоваться ею будет гораздо сложнее (куда нажимать?) и опаснее (может привести к поражению электрическим током). -As we'll see, in programming objects are like coffee machines. +Как мы увидим, в программировании объекты похожи на кофеварки. -But in order to hide inner details, we'll use not a protective cover, but rather special syntax of the language and conventions. +Но, чтобы скрыть внутренние детали, мы будем использовать не защитный кожух, а специальный синтаксис языка и соглашения. -## Internal and external interface +## Внутренний и внешний интерфейсы -In object-oriented programming, properties and methods are split into two groups: +В объектно-ориентированном программировании свойства и методы разделены на 2 группы: -- *Internal interface* -- methods and properties, accessible from other methods of the class, but not from the outside. -- *External interface* -- methods and properties, accessible also from outside the class. +- *Внутренний интерфейс* -- методы и свойства, доступные из других методов класса, но не снаружи класса. +- *Внешний интерфейс* -- методы и свойства, доступные снаружи класса. -If we continue the analogy with the coffee machine -- what's hidden inside: a boiler tube, heating element, and so on -- is its internal interface. +Если мы продолжаем аналогию с кофеваркой -- то, что скрыто внутри: трубка кипятильника, нагревательный элемент и т.д. -- это внутренний интерфейс. -An internal interface is used for the object to work, its details use each other. For instance, a boiler tube is attached to the heating element. +Внутренний интерфейс используется для работы объекта, его детали используют друг друга. Например, трубка кипятильника прикреплена к нагревательному элементу. -But from the outside a coffee machine is closed by the protective cover, so that no one can reach those. Details are hidden and inaccessible. We can use its features via the external interface. +Но снаружи кофеварка закрыта защитным кожухом, так что никто не может добраться до сложных частей. Детали скрыты и недоступны. Мы можем использовать их функции через внешний интерфейс. -So, all we need to use an object is to know its external interface. We may be completely unaware how it works inside, and that's great. +Итак, всё, что нам нужно для использования объекта, это знать его внешний интерфейс. Мы можем совершенно не знать, как это работает внутри, и это здорово. -That was a general introduction. +Это было общее введение. -In JavaScript, there are three types of properties and members: +В JavaScript есть два типа полей (свойств и методов) объекта: -- Public: accessible from anywhere. They comprise the external interface. Till now we were only using public properties and methods. -- Private: accessible only from inside the class. These are for the internal interface. +- Публичные: доступны отовсюду. Они составляют внешний интерфейс. До этого момента мы использовали только публичные свойства и методы. +- Приватные: доступны только внутри класса. Они для внутреннего интерфейса. -In many other languages there also exist "protected" fields: accessible only from inside the class and those extending it. They are also useful for the internal interface. They are in a sense more widespread than private ones, because we usually want inheriting classes to gain access to properly do the extension. +Во многих других языках также существуют "защищённые" поля, доступные только внутри класса или для дочерних классов (то есть, как приватные, но разрешён доступ для наследующих классов) и также полезны для внутреннего интерфейса. В некотором смысле они более распространены, чем приватные, потому что мы обычно хотим, чтобы наследующие классы получали доступ к внутренним полям. -Protected fields are not implemented in JavaScript on the language level, but in practice they are very convenient, so they are emulated. +Защищённые поля не реализованы в JavaScript на уровне языка, но на практике они очень удобны, поэтому их эмулируют. -In the next step we'll make a coffee machine in JavaScript with all these types of properties. A coffee machine has a lot of details, we won't model them to stay simple (though we could). +А теперь давайте сделаем кофеварку на JavaScript со всеми этими типами свойств. Кофеварка имеет множество деталей, мы не будем их моделировать для простоты примера (хотя могли бы). -## Protecting "waterAmount" +## Защищённое свойство "waterAmount" -Let's make a simple coffee machine class first: +Давайте для начала создадим простой класс для описания кофеварки: ```js run class CoffeeMachine { - waterAmount = 0; // the amount of water inside + waterAmount = 0; // количество воды внутри constructor(power) { this.power = power; - alert( `Created a coffee-machine, power: ${power}` ); + alert( `Создана кофеварка, мощность: ${power}` ); } } -// create the coffee machine +// создаём кофеварку let coffeeMachine = new CoffeeMachine(100); -// add water +// добавляем воды coffeeMachine.waterAmount = 200; ``` -Right now the properties `waterAmount` and `power` are public. We can easily get/set them from the outside to any value. +Прямо сейчас свойства `waterAmount` и `power` публичные. Мы можем легко получать и устанавливать им любое значение извне. -Let's change `waterAmount` property to protected to have more control over it. For instance, we don't want anyone to set it below zero. +Давайте изменим свойство `waterAmount` на защищённое, чтобы иметь больше контроля над ним. Например, мы не хотим, чтобы кто-либо устанавливал его ниже нуля. -**Protected properties are usually prefixed with an underscore `_`.** +**Защищённые свойства обычно начинаются с префикса `_`.** -That is not enforced on the language level, but there's a convention that such properties and methods should not be accessed from the outside. Most programmers follow it. +Это не синтаксис языка: есть хорошо известное соглашение между программистами, что такие свойства и методы не должны быть доступны извне. Большинство программистов следуют этому соглашению. -So our property will be called `_waterAmount`: +Так что наше свойство будет называться `_waterAmount`: ```js run class CoffeeMachine { _waterAmount = 0; set waterAmount(value) { - if (value < 0) throw new Error("Negative water"); + if (value < 0) throw new Error("Отрицательное количество воды"); this._waterAmount = value; } @@ -110,22 +110,22 @@ class CoffeeMachine { } -// create the coffee machine +// создаём новую кофеварку let coffeeMachine = new CoffeeMachine(100); -// add water -coffeeMachine.waterAmount = -10; // Error: Negative water +// устанавливаем количество воды +coffeeMachine.waterAmount = -10; // Error: Отрицательное количество воды ``` -Now the access is under control, so setting the water below zero fails. +Теперь доступ под контролем, поэтому указать воду ниже нуля не удалось. -## Read-only "power" +## Свойство только для чтения "power" -For `power` property, let's make it read-only. It sometimes happens that a property must be set at creation time only, and then never modified. +Давайте сделаем свойство `power` доступным только для чтения. Иногда нужно, чтобы свойство устанавливалось только при создании объекта и после этого никогда не изменялось. -That's exactly the case for a coffee machine: power never changes. +Это как раз требуется для кофеварки: мощность никогда не меняется. -To do so, we only need to make getter, but not the setter: +Для этого нам нужно создать только геттер, но не сеттер: ```js run class CoffeeMachine { @@ -141,58 +141,57 @@ class CoffeeMachine { } -// create the coffee machine +// создаём кофеварку let coffeeMachine = new CoffeeMachine(100); -alert(`Power is: ${coffeeMachine.power}W`); // Power is: 100W +alert(`Мощность: ${coffeeMachine.power}W`); // Мощность: 100W coffeeMachine.power = 25; // Error (no setter) ``` -````smart header="Getter/setter functions" -Here we used getter/setter syntax. +````smart header="Геттеры/сеттеры" +Здесь мы использовали синтаксис геттеров/сеттеров. -But most of the time `get.../set...` functions are preferred, like this: +Но в большинстве случаев использование функций `get.../set...` предпочтительнее: ```js class CoffeeMachine { _waterAmount = 0; *!*setWaterAmount(value)*/!* { - if (value < 0) throw new Error("Negative water"); + if (value < 0) throw new Error("Отрицательное количество воды"); this._waterAmount = value; } *!*getWaterAmount()*/!* { - return this.waterAmount; + return this._waterAmount; } } new CoffeeMachine().setWaterAmount(100); ``` +Это выглядит немного длиннее, но функции более гибкие. Они могут принимать несколько аргументов (даже если они нам сейчас не нужны). Итак, на будущее, если нам надо что-то отрефакторить, функции -- более безопасный выбор. -That looks a bit longer, but functions are more flexible. They can accept multiple arguments (even if we don't need them right now). So, for the future, just in case we need to refactor something, functions are a safer choice. - -Surely, there's a tradeoff. On the other hand, get/set syntax is shorter, so ultimately there's no strict rule, it's up to you to decide. +С другой стороны, синтаксис get/set короче, решать вам. ```` -```smart header="Protected fields are inherited" -If we inherit `class MegaMachine extends CoffeeMachine`, then nothing prevents us from accessing `this._waterAmount` or `this._power` from the methods of the new class. +```smart header="Защищённые поля наследуются" +Если мы унаследуем `class MegaMachine extends CoffeeMachine`, ничто не помешает нам обращаться к `this._waterAmount` или `this._power` из методов нового класса. -So protected fields are naturally inheritable. Unlike private ones that we'll see below. +Таким образом, защищённые поля, конечно же, наследуются. В отличие от приватных полей, в чём мы убедимся ниже. ``` -## Private "#waterLimit" +## Приватное свойство "#waterLimit" [recent browser=none] -There's a finished JavaScript proposal, almost in the standard, that provides language-level support for private properties and methods. +Есть новшество в языке JavaScript, которое почти добавлено в стандарт: оно добавляет поддержку приватных свойств и методов. -Privates should start with `#`. They are only accessible from inside the class. +Приватные свойства и методы должны начинаться с `#`. Они доступны только внутри класса. -For instance, here we add a private `#waterLimit` property and extract the water-checking logic into a separate method: +Например, в классе ниже есть приватное свойство `#waterLimit` и приватный метод `#checkWater` для проверки количества воды: -```js +```js run class CoffeeMachine { *!* #waterLimit = 200; @@ -200,41 +199,26 @@ class CoffeeMachine { *!* #checkWater(value) { - if (value < 0) throw new Error("Negative water"); - if (value > this.#waterLimit) throw new Error("Too much water"); + if (value < 0) throw new Error("Отрицательный уровень воды"); + if (value > this.#waterLimit) throw new Error("Слишком много воды"); } */!* - - _waterAmount = 0; - - set waterAmount(value) { -*!* - this.#checkWater(value); -*/!* - this._waterAmount = value; - } - - get waterAmount() { - return this.waterAmount; - } - } let coffeeMachine = new CoffeeMachine(); *!* +// снаружи нет доступа к приватным методам класса coffeeMachine.#checkWater(); // Error coffeeMachine.#waterLimit = 1000; // Error */!* - -coffeeMachine.waterAmount = 100; // Works ``` -On the language level, `#` is a special sign that the field is private. We can't access it from outside or from inheriting classes. +На уровне языка `#` является специальным символом, который означает, что поле приватное. Мы не можем получить к нему доступ извне или из наследуемых классов. -Private fields do not conflict with public ones. We can have both private `#waterAmount` and public `waterAmount` fields at the same time. +Приватные поля не конфликтуют с публичными. У нас может быть два поля одновременно -- приватное `#waterAmount` и публичное `waterAmount`. -For instance, let's make `waterAmount` an accessor for `#waterAmount`: +Например, давайте сделаем аксессор `waterAmount` для `#waterAmount`: ```js run class CoffeeMachine { @@ -246,7 +230,7 @@ class CoffeeMachine { } set waterAmount(value) { - if (value < 0) throw new Error("Negative water"); + if (value < 0) throw new Error("Отрицательный уровень воды"); this.#waterAmount = value; } } @@ -257,12 +241,12 @@ machine.waterAmount = 100; alert(machine.#waterAmount); // Error ``` -Unlike protected ones, private fields are enforced by the language itself. That's a good thing. +В отличие от защищённых, функциональность приватных полей обеспечивается самим языком. Это хорошо. -But if we inherit from `CoffeeMachine`, then we'll have no direct access to `#waterAmount`. We'll need to rely on `waterAmount` getter/setter: +Но если мы унаследуем от `CoffeeMachine`, то мы не получим прямого доступа к `#waterAmount`. Мы будем вынуждены полагаться на геттер/сеттер `waterAmount`: ```js -class CoffeeMachine extends CoffeeMachine() { +class MegaCoffeeMachine extends CoffeeMachine { method() { *!* alert( this.#waterAmount ); // Error: can only access from CoffeeMachine @@ -271,60 +255,59 @@ class CoffeeMachine extends CoffeeMachine() { } ``` -In many scenarios such limitation is too severe. If we extend a `CoffeeMachine`, we may have legitimate reason to access its internals. That's why protected fields are used most of the time, even though they are not supported by the language syntax. +Во многих случаях такое ограничение слишком жёсткое. Раз уж мы расширяем `CoffeeMachine`, у нас может быть вполне законная причина для доступа к внутренним методам и свойствам. Поэтому защищённые свойства используются чаще, хоть они и не поддерживаются синтаксисом языка. ````warn -Private fields are special. +Приватные поля особенные. -Remember, usually we can access fields by this[name]: +Как мы помним, обычно мы можем получить доступ к полям объекта с помощью `this[name]`: ```js class User { ... sayHi() { let fieldName = "name"; - alert(`Hello, ${this[fieldName]}`); + alert(`Hello, ${*!*this[fieldName]*/!*}`); } } ``` - -With private fields that's impossible: `this['#name']` doesn't work. That's a syntax limitation to ensure privacy. +С приватными свойствами такое невозможно: `this['#name']` не работает. Это ограничение синтаксиса сделано для обеспечения приватности. ```` -## Summary +## Итого -In terms of OOP, delimiting of the internal interface from the external one is called [encapsulation]("https://en.wikipedia.org/wiki/Encapsulation_(computer_programming)"). +В терминах ООП отделение внутреннего интерфейса от внешнего называется [инкапсуляция](https://ru.wikipedia.org/wiki/%D0%98%D0%BD%D0%BA%D0%B0%D0%BF%D1%81%D1%83%D0%BB%D1%8F%D1%86%D0%B8%D1%8F_(%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)). -It gives the following benefits: +Это даёт следующие выгоды: -Protection for users, so that they don't shoot themselves in the feet -: Imagine, there's a team of developers using a coffee machine. It was made by the "Best CoffeeMachine" company, and works fine, but a protective cover was removed. So the internal interface is exposed. +Защита для пользователей, чтобы они не выстрелили себе в ногу +: Представьте себе, что есть команда разработчиков, использующая кофеварку. Она была изготовлена компанией "Лучшие Кофеварки" и работает нормально, но защитный кожух был снят. Внутренний интерфейс стал доступен извне. - All developers are civilized -- they use the coffee machine as intended. But one of them, John, decided that he's the smartest one, and made some tweaks in the coffee machine internals. So the coffee machine failed two days later. + Все разработчики культурны -- они используют кофеварку по назначению. Но один из них, Джон, решил, что он самый умный, и сделал некоторые изменения во внутренностях кофеварки. После чего кофеварка вышла из строя через два дня. - That's surely not John's fault, but rather the person who removed the protective cover and let John do his manipulations. + Это, конечно, не вина Джона, а скорее человека, который снял защитный кожух и позволил Джону делать свои манипуляции. - The same in programming. If a user of a class will change things not intended to be changed from the outside -- the consequences are unpredictable. + То же самое в программировании. Если пользователь класса изменит вещи, не предназначенные для изменения извне -- последствия непредсказуемы. -Supportable -: The situation in programming is more complex than with a real-life coffee machine, because we don't just buy it once. The code constantly undergoes development and improvement. +Поддерживаемость +: Ситуация в программировании сложнее, чем с реальной кофеваркой, потому что мы не просто покупаем её один раз. Код постоянно подвергается разработке и улучшению. - **If we strictly delimit the internal interface, then the developer of the class can freely change its internal properties and methods, even without informing the users..** + **Если мы чётко отделим внутренний интерфейс, то разработчик класса сможет свободно менять его внутренние свойства и методы, даже не информируя пользователей..** - It's much easier to develop, if you know that certain methods can be renamed, their parameters can be changed, and even removed, because no external code depends on them. + Если вы разработчик такого класса, то приятно знать, что приватные методы можно безопасно переименовывать, их параметры можно изменять и даже удалять, потому что от них не зависит никакой внешний код. - For users, when a new version comes out, it may be a total overhaul, but still simple to upgrade if the external interface is the same. + В новой версии вы можете полностью всё переписать, но пользователю будет легко обновиться, если внешний интерфейс остался такой же. -Hiding complexity -: People adore to use things that are simple. At least from outside. What's inside is a different thing. +Сокрытие сложности +: Люди обожают использовать простые вещи. По крайней мере, снаружи. Что внутри -- это другое дело. - Programmers are not an exception. + Программисты не являются исключением. - **It's always convenient when implementation details are hidden, and a simple, well-documented external interface is available.** + **Всегда удобно, когда детали реализации скрыты, и доступен простой, хорошо документированный внешний интерфейс.** -To hide internal interface we use either protected or public properties: +Для сокрытия внутреннего интерфейса мы используем защищённые или приватные свойства: -- Protected fields start with `_`. That's a well-known convention, not enforced at the language level. Programmers should only access a field starting with `_` from its class and classes inheriting from it. -- Private fields start with `#`. JavaScript makes sure we only can access those from inside the class. +- Защищённые поля имеют префикс `_`. Это хорошо известное соглашение, не поддерживаемое на уровне языка. Программисты должны обращаться к полю, начинающемуся с `_`, только из его класса и классов, унаследованных от него. +- Приватные поля имеют префикс `#`. JavaScript гарантирует, что мы можем получить доступ к таким полям только внутри класса. -Right now, private fields are not well-supported among browsers, but can be polyfilled. +В настоящее время приватные поля не очень хорошо поддерживаются в браузерах, но можно использовать полифил. diff --git a/1-js/09-classes/04-private-protected-properties-methods/coffee.jpg b/1-js/09-classes/04-private-protected-properties-methods/coffee.jpg index ee26e1c064..c4b5224c08 100644 Binary files a/1-js/09-classes/04-private-protected-properties-methods/coffee.jpg and b/1-js/09-classes/04-private-protected-properties-methods/coffee.jpg differ diff --git a/1-js/09-classes/05-extend-natives/article.md b/1-js/09-classes/05-extend-natives/article.md index 63ea96cb5b..a8840522f4 100644 --- a/1-js/09-classes/05-extend-natives/article.md +++ b/1-js/09-classes/05-extend-natives/article.md @@ -1,12 +1,12 @@ -# Extending built-in classes +# Расширение встроенных классов -Built-in classes like Array, Map and others are extendable also. +От встроенных классов, таких как `Array`, `Map` и других, тоже можно наследовать. -For instance, here `PowerArray` inherits from the native `Array`: +Например, в этом примере `PowerArray` наследуется от встроенного `Array`: ```js run -// add one more method to it (can do more) +// добавим один метод (можно более одного) class PowerArray extends Array { isEmpty() { return this.length === 0; @@ -21,21 +21,20 @@ alert(filteredArr); // 10, 50 alert(filteredArr.isEmpty()); // false ``` -Please note a very interesting thing. Built-in methods like `filter`, `map` and others -- return new objects of exactly the inherited type. They rely on the `constructor` property to do so. +Обратите внимание на интересный момент: встроенные методы, такие как `filter`, `map` и другие возвращают новые объекты унаследованного класса `PowerArray`. Их внутренняя реализация такова, что для этого они используют свойство объекта `constructor`. -In the example above, +В примере выше, ```js arr.constructor === PowerArray ``` -So when `arr.filter()` is called, it internally creates the new array of results exactly as `new PowerArray`. -That's actually very cool, because we can keep using `PowerArray` methods further on the result. +Поэтому при вызове метода `arr.filter()` он внутри создаёт массив результатов, именно используя `arr.constructor`, а не обычный массив. Это замечательно, поскольку можно продолжать использовать методы `PowerArray` далее на результатах. -Even more, we can customize that behavior. +Более того, мы можем настроить это поведение. -There's a special static getter `Symbol.species`, if exists, it returns the constructor to use in such cases. +При помощи специального статического геттера `Symbol.species` можно вернуть конструктор, который JavaScript будет использовать в `filter`, `map` и других методах для создания новых объектов. -If we'd like built-in methods like `map`, `filter` will return regular arrays, we can return `Array` in `Symbol.species`, like here: +Если бы мы хотели, чтобы методы `map`, `filter` и т. д. возвращали обычные массивы, мы могли бы вернуть `Array` в `Symbol.species`, вот так: ```js run class PowerArray extends Array { @@ -44,7 +43,7 @@ class PowerArray extends Array { } *!* - // built-in methods will use this as the constructor + // встроенные методы массива будут использовать этот метод как конструктор static get [Symbol.species]() { return Array; } @@ -54,29 +53,37 @@ class PowerArray extends Array { let arr = new PowerArray(1, 2, 5, 10, 50); alert(arr.isEmpty()); // false -// filter creates new array using arr.constructor[Symbol.species] as constructor +// filter создаст новый массив, используя arr.constructor[Symbol.species] как конструктор let filteredArr = arr.filter(item => item >= 10); *!* -// filteredArr is not PowerArray, but Array +// filteredArr не является PowerArray, это Array */!* alert(filteredArr.isEmpty()); // Error: filteredArr.isEmpty is not a function ``` -As you can see, now `.filter` returns `Array`. So the extended functionality is not passed any further. +Как вы видите, теперь `.filter` возвращает `Array`. Расширенная функциональность не будет передаваться далее. -## No static inheritance in built-ins +```smart header="Аналогично работают другие коллекции" +Другие коллекции, такие как `Map`, `Set`, работают аналогично. Они также используют `Symbol.species`. +``` + +## Отсутствие статического наследования встроенных классов + +У встроенных объектов есть собственные статические методы, например `Object.keys`, `Array.isArray` и т. д. + +Как мы уже знаем, встроенные классы расширяют друг друга. -Built-in objects have their own static methods, for instance `Object.keys`, `Array.isArray` etc. +Обычно, когда один класс наследует другой, то наследуются и статические методы. Это было подробно разъяснено в главе [](info:static-properties-methods#statics-and-inheritance). -And we've already been talking about native classes extending each other: `Array.[[Prototype]] = Object`. +Но встроенные классы - исключение. Они не наследуют статические методы друг друга. -But statics are an exception. Built-in classes don't inherit static properties from each other. +Например, и `Array`, и `Date` наследуют от `Object`, так что в их экземплярах доступны методы из `Object.prototype`. Но `Array.[[Prototype]]` не ссылается на `Object`, поэтому нет методов `Array.keys()` или `Date.keys()`. -In other words, the prototype of built-in constructor `Array` does not point to `Object`. This way `Array` and `Date` do not have `Array.keys` or `Date.keys`. And that feels natural. +Ниже вы видите структуру `Date` и `Object`: -Here's the picture structure for `Date` and `Object`: +![](object-date-inheritance.svg) -![](object-date-inheritance.png) +Как видите, нет связи между `Date` и `Object`. Они независимы, только `Date.prototype` наследует от `Object.prototype`. -Note, there's no link between `Date` and `Object`. Both `Object` and `Date` exist independently. `Date.prototype` inherits from `Object.prototype`, but that's all. +В этом важное отличие наследования встроенных объектов от того, что мы получаем с использованием `extends`. diff --git a/1-js/09-classes/05-extend-natives/object-date-inheritance.png b/1-js/09-classes/05-extend-natives/object-date-inheritance.png deleted file mode 100644 index 73020a49e8..0000000000 Binary files a/1-js/09-classes/05-extend-natives/object-date-inheritance.png and /dev/null differ 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 new file mode 100644 index 0000000000..be47d7fd96 --- /dev/null +++ b/1-js/09-classes/05-extend-natives/object-date-inheritance.svg @@ -0,0 +1 @@ +constructor: Object toString: function hasOwnProperty: function ...Object.prototypeconstructor: Date toString: function getDate: function ...Date.prototypeObjectDatenew Date()[[Prototype]][[Prototype]]prototypeprototypedefineProperty keys ...now parse ...1 Jan 2019 \ No newline at end of file diff --git a/1-js/09-classes/05-extend-natives/object-date-inheritance@2x.png b/1-js/09-classes/05-extend-natives/object-date-inheritance@2x.png deleted file mode 100644 index 6520f9e265..0000000000 Binary files a/1-js/09-classes/05-extend-natives/object-date-inheritance@2x.png and /dev/null differ diff --git a/1-js/09-classes/06-instanceof/article.md b/1-js/09-classes/06-instanceof/article.md index b961cd268b..a9dc6317a4 100644 --- a/1-js/09-classes/06-instanceof/article.md +++ b/1-js/09-classes/06-instanceof/article.md @@ -1,8 +1,8 @@ # Проверка класса: "instanceof" -Оператор `instanceof` позволяет проверить, какому классу принадлежит объект, с учётом наследования. +Оператор `instanceof` позволяет проверить, принадлежит ли объект указанному классу, с учётом наследования. -Такая проверка может потребоваться во многих случаях. Здесь мы будем использовать её для создания *полиморфной* функции, которая использует аргументы по-разному в зависимости от их типа. +Такая проверка может потребоваться во многих случаях. Здесь мы используем её для создания *полиморфной* функции, которая интерпретирует аргументы по-разному в зависимости от их типа. ## Оператор instanceof [#ref-instanceof] @@ -11,7 +11,7 @@ obj instanceof Class ``` -Оператор вернёт `true`, если `obj` принадлежит классу `Class` (или класс наследуется от него). +Оператор вернёт `true`, если `obj` принадлежит классу `Class` или наследующему от него. Например: @@ -44,16 +44,19 @@ alert( arr instanceof Array ); // true alert( arr instanceof Object ); // true ``` -Пожалуйста, обратите внимание, что `arr` также принадлежит классу `Object`, потому что `Array` наследуется от `Object`. +Пожалуйста, обратите внимание, что `arr` также принадлежит классу `Object`, потому что `Array` наследует от `Object`. -Оператор `instanceof` просматривает цепочку прототипов для проверки, и может быть настроен при помощи статического метода `Symbol.hasInstance`. +Обычно оператор `instanceof` просматривает для проверки цепочку прототипов. Но это поведение может быть изменено при помощи статического метода `Symbol.hasInstance`. Алгоритм работы `obj instanceof Class` работает примерно так: -1. Если имеется статический метод `Symbol.hasInstance`, тогда вызвать его: `Class[Symbol.hasInstance](obj)`. Он должен вернут либо `true`, либо `false`. Конец. Пример: +1. Если имеется статический метод `Symbol.hasInstance`, тогда вызвать его: `Class[Symbol.hasInstance](obj)`. Он должен вернуть либо `true`, либо `false`, и это конец. Это как раз и есть возможность ручной настройки `instanceof`. + + Пример: ```js run - // предполагаем, все, что может есть, - животное + // проверка instanceof будет полагать, + // что всё со свойством canEat - животное Animal class Animal { static [Symbol.hasInstance](obj) { if (obj.canEat) return true; @@ -64,19 +67,21 @@ alert( arr instanceof Object ); // true alert(obj instanceof Animal); // true: вызван Animal[Symbol.hasInstance](obj) ``` -2. Большая часть классов не имеет метода `Symbol.hasInstance`. В этом случае проверяется, равен ли `Class.prototype` одному из прототипов в прототипной цепочке `obj`. +2. Большая часть классов не имеет метода `Symbol.hasInstance`. В этом случае используется стандартная логика: проверяется, равен ли `Class.prototype` одному из прототипов в прототипной цепочке `obj`. Другими словами, сравнивается: ```js - obj.__proto__ === Class.prototype - obj.__proto__.__proto__ === Class.prototype - obj.__proto__.__proto__.__proto__ === Class.prototype + obj.__proto__ === Class.prototype? + obj.__proto__.__proto__ === Class.prototype? + obj.__proto__.__proto__.__proto__ === Class.prototype? ... + // если какой-то из ответов true - возвратить true + // если дошли до конца цепочки - false ``` - В примере выше `Rabbit.prototype === rabbit.__proto__`, так что результат будет получен немедленно. + В примере выше `rabbit.__proto__ === Rabbit.prototype`, так что результат будет получен немедленно. - В случае с наследованием, `rabbit` также является экземпляром своего родительского класса: + В случае с наследованием, совпадение будет на втором шаге: ```js run class Animal {} @@ -86,19 +91,22 @@ alert( arr instanceof Object ); // true *!* alert(rabbit instanceof Animal); // true */!* - // rabbit.__proto__ === Rabbit.prototype + + // rabbit.__proto__ === Animal.prototype (нет совпадения) + *!* // rabbit.__proto__.__proto__ === Animal.prototype (совпадение!) + */!* ``` Вот иллюстрация того как `rabbit instanceof Animal` сравнивается с `Animal.prototype`: -![](instanceof.png) +![](instanceof.svg) -Кстати, есть метод [objA.isPrototypeOf(objB)], которые возвращает `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`. -Это может приводить к интересным последствиям при изменении `prototype`. +Это может приводить к интересным последствиям при изменении свойства `prototype` после создания объекта. Как, например, тут: @@ -115,11 +123,9 @@ alert( rabbit instanceof Rabbit ); // false */!* ``` -Это одна из причин избегать изменения `prototype`. Просто для безопасности. - ## Бонус: Object.prototype.toString возвращает тип -Мы уже знаем, что обычные объекты преобразуется к строке как `[object Object]`: +Мы уже знаем, что обычные объекты преобразуются к строке как `[object Object]`: ```js run let obj = {}; @@ -132,7 +138,7 @@ alert(obj.toString()); // то же самое Звучит странно? Так и есть. Давайте развеем мистику. -Согласно [спецификации](https://tc39.github.io/ecma262/#sec-object.prototype.tostring) встроенный метод `toString` может бы позаимствован у объекта и вызван в контексте любого другого значения. И результат зависит от типа этого значения. +Согласно [спецификации](https://tc39.github.io/ecma262/#sec-object.prototype.tostring) встроенный метод `toString` может быть позаимствован у объекта и вызван в контексте любого другого значения. И результат зависит от типа этого значения. - Для числа это будет `[object Number]` - Для булева типа это будет `[object Boolean]` @@ -147,10 +153,10 @@ alert(obj.toString()); // то же самое // скопируем метод toString в переменную для удобства let objectToString = Object.prototype.toString; -// Какой это тип? +// какой это тип? let arr = []; -alert( objectToString.call(arr) ); // [object Array] +alert( objectToString.call(arr) ); // [object *!*Array*/!*] ``` В примере мы использовали [call](mdn:js/function/call), как описано в главе [](info:call-apply-decorators), чтобы выполнить функцию `objectToString` в контексте `this=arr`. @@ -194,18 +200,18 @@ alert( {}.toString.call(new XMLHttpRequest()) ); // [object XMLHttpRequest] В итоге мы получили "typeof на стероидах", который не только работает с примитивными типами данных, но также и со встроенными объектами, и даже может быть настроен. -Он может использоваться вместо `instanceof` для встроенных объектов, когда мы хотим получить тип в виде строки, а не просто сделать проверку. +Можно использовать `{}.toString.call` вместо `instanceof` для встроенных объектов, когда мы хотим получить тип в виде строки, а не просто сделать проверку. ## Итого -Давайте вспомним, какие методы для проверки типа мы знаем: +Давайте обобщим, какие методы для проверки типа мы знаем: | | работает для | возвращает | |---------------|----------------|------------------| -| `typeof` | примитивы | строка | -| `{}.toString` | примитивы, встроенные объекты, объекты с `Symbol.toStringTag` | строка | -| `instanceof` | объекты | true/false | +| `typeof` | примитивов | строка | +| `{}.toString` | примитивов, встроенных объектов, объектов с `Symbol.toStringTag` | строка | +| `instanceof` | объектов | true/false | Как мы можем видеть, технически `{}.toString` "более продвинут", чем `typeof`. -А оператор instanceof - отличный выбор, когда мы работаем с иерархией классов и хотим делать проверки с учётом наследования. +А оператор `instanceof` - отличный выбор, когда мы работаем с иерархией классов и хотим делать проверки с учётом наследования. diff --git a/1-js/09-classes/06-instanceof/instanceof.png b/1-js/09-classes/06-instanceof/instanceof.png deleted file mode 100644 index ad0fc77ab3..0000000000 Binary files a/1-js/09-classes/06-instanceof/instanceof.png and /dev/null differ diff --git a/1-js/09-classes/06-instanceof/instanceof.svg b/1-js/09-classes/06-instanceof/instanceof.svg new file mode 100644 index 0000000000..d63b03a8a2 --- /dev/null +++ b/1-js/09-classes/06-instanceof/instanceof.svg @@ -0,0 +1 @@ +Animal.prototypeObject.prototypeRabbit.prototype[[Prototype]]rabbit[[Prototype]][[Prototype]]null[[Prototype]]= Animal.prototype? \ No newline at end of file diff --git a/1-js/09-classes/06-instanceof/instanceof@2x.png b/1-js/09-classes/06-instanceof/instanceof@2x.png deleted file mode 100644 index c25b166b3f..0000000000 Binary files a/1-js/09-classes/06-instanceof/instanceof@2x.png and /dev/null differ diff --git a/1-js/09-classes/07-mixins/article.md b/1-js/09-classes/07-mixins/article.md index bb51395e69..e92a1aae8a 100644 --- a/1-js/09-classes/07-mixins/article.md +++ b/1-js/09-classes/07-mixins/article.md @@ -1,38 +1,38 @@ -# Mixins +# Примеси -In JavaScript we can only inherit from a single object. There can be only one `[[Prototype]]` for an object. And a class may extend only one other class. +В JavaScript можно наследовать только от одного объекта. Объект имеет единственный `[[Prototype]]`. И класс может расширить только один другой класс. -But sometimes that feels limiting. For instance, I have a class `StreetSweeper` and a class `Bicycle`, and want to make a `StreetSweepingBicycle`. +Иногда это может ограничивать нас. Например, у нас есть класс `StreetSweeper` и класс `Bicycle`, а мы хотим создать их смесь: `StreetSweepingBicycle`. -Or, talking about programming, we have a class `Renderer` that implements templating and a class `EventEmitter` that implements event handling, and want to merge these functionalities together with a class `Page`, to make a page that can use templates and emit events. +Или у нас есть класс `User`, который реализует пользователей, и класс `EventEmitter`, реализующий события. Мы хотели бы добавить функциональность класса `EventEmitter` к `User`, чтобы пользователи могли легко генерировать события. -There's a concept that can help here, called "mixins". +Для таких случаев существуют "примеси". -As defined in Wikipedia, a [mixin](https://en.wikipedia.org/wiki/Mixin) is a class that contains methods for use by other classes without having to be the parent class of those other classes. +По определению из Википедии, [примесь](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)) - это класс, методы которого предназначены для использования в других классах, причём без наследования от примеси. -In other words, a *mixin* provides methods that implement a certain behavior, but we do not use it alone, we use it to add the behavior to other classes. +Другими словами, *примесь* определяет методы, которые реализуют определённое поведение. Мы не используем примесь саму по себе, а используем её, чтобы добавить функциональность другим классам. -## A mixin example +## Пример примеси -The simplest way to make a mixin in JavaScript is to make an object with useful methods, so that we can easily merge them into a prototype of any class. +Простейший способ реализовать примесь в JavaScript - это создать объект с полезными методами, которые затем могут быть легко добавлены в прототип любого класса. -For instance here the mixin `sayHiMixin` is used to add some "speech" for `User`: +В примере ниже примесь `sayHiMixin` имеет методы, которые придают объектам класса `User` возможность вести разговор: ```js run *!* -// mixin +// примесь */!* let sayHiMixin = { sayHi() { - alert(`Hello ${this.name}`); + alert(`Привет, ${this.name}`); }, sayBye() { - alert(`Bye ${this.name}`); + alert(`Пока, ${this.name}`); } }; *!* -// usage: +// использование: */!* class User { constructor(name) { @@ -40,26 +40,26 @@ class User { } } -// copy the methods +// копируем методы Object.assign(User.prototype, sayHiMixin); -// now User can say hi -new User("Dude").sayHi(); // Hello Dude! +// теперь User может сказать Привет +new User("Вася").sayHi(); // Привет, Вася! ``` -There's no inheritance, but a simple method copying. So `User` may extend some other class and also include the mixin to "mix-in" the additional methods, like this: +Это не наследование, а просто копирование методов. Таким образом, класс `User` может наследовать от другого класса, но при этом также включать в себя примеси, "подмешивающие" другие методы, например: ```js class User extends Person { - // ... +// ... } Object.assign(User.prototype, sayHiMixin); ``` -Mixins can make use of inheritance inside themselves. +Примеси могут наследовать друг друга. -For instance, here `sayHiMixin` inherits from `sayMixin`: +В примере ниже `sayHiMixin` наследует от `sayMixin`: ```js run let sayMixin = { @@ -69,16 +69,16 @@ let sayMixin = { }; let sayHiMixin = { - __proto__: sayMixin, // (or we could use Object.create to set the prototype here) + __proto__: sayMixin, // (или мы можем использовать Object.setPrototypeOf для задания прототипа) sayHi() { *!* - // call parent method + // вызываем метод родителя */!* - super.say(`Hello ${this.name}`); + super.say(`Привет, ${this.name}`); // (*) }, sayBye() { - super.say(`Bye ${this.name}`); + super.say(`Пока, ${this.name}`); // (*) } }; @@ -88,41 +88,43 @@ class User { } } -// copy the methods +// копируем методы Object.assign(User.prototype, sayHiMixin); -// now User can say hi -new User("Dude").sayHi(); // Hello Dude! +// теперь User может сказать Привет +new User("Вася").sayHi(); // Привет, Вася! ``` -Please note that the call to the parent method `super.say()` from `sayHiMixin` looks for the method in the prototype of that mixin, not the class. +Обратим внимание, что при вызове родительского метода `super.say()` из `sayHiMixin` (строки, помеченные `(*)`) этот метод ищется в прототипе самой примеси, а не класса. -![](mixin-inheritance.png) +Вот диаграмма (см. правую часть): -That's because methods from `sayHiMixin` have `[[HomeObject]]` set to it. So `super` actually means `sayHiMixin.__proto__`, not `User.__proto__`. +![](mixin-inheritance.svg) -## EventMixin +Это связано с тем, что методы `sayHi` и `sayBye` были изначально созданы в объекте `sayHiMixin`. Несмотря на то, что они скопированы, их внутреннее свойство `[[HomeObject]]` ссылается на `sayHiMixin`, как показано на картинке выше. -Now let's make a mixin for real life. +Так как `super` ищет родительские методы в `[[HomeObject]].[[Prototype]]`, это означает, что он ищет `sayHiMixin.[[Prototype]]`. -The important feature of many objects is working with events. +## EventMixin -That is: an object should have a method to "generate an event" when something important happens to it, and other objects should be able to "listen" to such events. +Многие объекты в браузерной разработке (и не только) обладают важной способностью - они могут генерировать события. События - отличный способ передачи информации всем, кто в ней заинтересован. Давайте создадим примесь, которая позволит легко добавлять функциональность по работе с событиями любым классам/объектам. -An event must have a name and, optionally, bundle some additional data. +- Примесь добавит метод `.trigger(name, [...data])` для генерации события. Аргумент `name` - это имя события, за которым могут следовать дополнительные аргументы с данными для события. +- Также будет добавлен метод `.on(name, handler)`, который назначает обработчик для события с заданным именем. Обработчик будет вызван, когда произойдёт событие с указанным именем `name`, и получит данные из `.trigger`. +- ...и метод `.off(name, handler)`, который удаляет обработчик указанного события. -For instance, an object `user` can generate an event `"login"` when the visitor logs in. And another object `calendar` may want to receive such events to load the calendar for the logged-in person. +После того, как все методы примеси будут добавлены, объект `user` сможет сгенерировать событие `"login"` после входа пользователя в личный кабинет. А другой объект, к примеру, `calendar` сможет использовать это событие, чтобы показывать зашедшему пользователю актуальный для него календарь. -Or, a `menu` can generate the event `"select"` when a menu item is selected, and other objects may want to get that information and react on that event. +Или `menu` может генерировать событие `"select"`, когда элемент меню выбран, а другие объекты могут назначать обработчики, чтобы реагировать на это событие, и т.п. -Events is a way to "share information" with anyone who wants it. They can be useful in any class, so let's make a mixin for them: +Вот код примеси: ```js run let eventMixin = { /** - * Subscribe to event, usage: - * menu.on('select', function(item) { ... } - */ + * Подписаться на событие, использование: + * menu.on('select', function(item) { ... } + */ on(eventName, handler) { if (!this._eventHandlers) this._eventHandlers = {}; if (!this._eventHandlers[eventName]) { @@ -132,11 +134,11 @@ let eventMixin = { }, /** - * Cancel the subscription, usage: - * menu.off('select', handler) + * Отменить подписку, использование: + * 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) { @@ -146,60 +148,63 @@ let eventMixin = { }, /** - * Generate the event and attach the data to it - * this.trigger('select', data1, data2); + * Сгенерировать событие с указанным именем и данными + * this.trigger('select', data1, data2); */ trigger(eventName, ...args) { - if (!this._eventHandlers || !this._eventHandlers[eventName]) { - return; // no handlers for that event name + if (!this._eventHandlers?.[eventName]) { + return; // обработчиков для этого события нет } - // call the handlers + // вызовем обработчики this._eventHandlers[eventName].forEach(handler => handler.apply(this, args)); } }; ``` -There are 3 methods here: +Итак, у нас есть 3 метода: + +1. `.on(eventName, handler)` -- назначает функцию `handler`, чтобы обработать событие с заданным именем. + + Технически существует свойство `_eventHandlers`, в котором хранится массив обработчиков для каждого имени события, и оно просто добавляет это событие в список. -1. `.on(eventName, handler)` -- assigns function `handler` to run when the event with that name happens. The handlers are stored in the `_eventHandlers` property. -2. `.off(eventName, handler)` -- removes the function from the handlers list. -3. `.trigger(eventName, ...args)` -- generates the event: all assigned handlers are called and `args` are passed as arguments to them. +3. `.off(eventName, handler)` -- убирает функцию из списка обработчиков. +4. `.trigger(eventName, ...args)` -- генерирует событие: все назначенные обработчики из `_eventHandlers[eventName]` вызываются, и `...args` передаются им в качестве аргументов. -Usage: +Использование: ```js run -// Make a class +// Создадим класс class Menu { choose(value) { this.trigger("select", value); } } -// Add the mixin +// Добавим примесь с методами для событий Object.assign(Menu.prototype, eventMixin); let menu = new Menu(); -// call the handler on selection: +// Добавим обработчик, который будет вызван при событии "select": *!* -menu.on("select", value => alert(`Value selected: ${value}`)); +menu.on("select", value => alert(`Выбранное значение: ${value}`)); */!* -// triggers the event => shows Value selected: 123 -menu.choose("123"); // value selected +// Генерирует событие => обработчик выше запускается и выводит: +menu.choose("123"); // Выбранное значение: 123 ``` -Now if we have the code interested to react on user selection, we can bind it with `menu.on(...)`. +Теперь если у нас есть код, заинтересованный в событии `"select"`, то он может слушать его с помощью `menu.on(...)`. -And the `eventMixin` can add such behavior to as many classes as we'd like, without interfering with the inheritance chain. +А `eventMixin` позволяет легко добавить такое поведение в любой класс без вмешательства в цепочку наследования. -## Summary +## Итого -*Mixin* -- is a generic object-oriented programming term: a class that contains methods for other classes. +*Примесь* -- общий термин в объектно-ориентированном программировании: класс, который содержит в себе методы для других классов. -Some other languages like e.g. python allow to create mixins using multiple inheritance. JavaScript does not support multiple inheritance, but mixins can be implemented by copying them into the prototype. +Некоторые другие языки допускают множественное наследование. JavaScript не поддерживает множественное наследование, но с помощью примесей мы можем реализовать нечто похожее, скопировав методы в прототип. -We can use mixins as a way to augment a class by multiple behaviors, like event-handling as we have seen above. +Мы можем использовать примеси для расширения функциональности классов, например, для обработки событий, как мы сделали это выше. -Mixins may become a point of conflict if they occasionally overwrite native class methods. So generally one should think well about the naming for a mixin, to minimize such possibility. +С примесями могут возникнуть конфликты, если они перезаписывают существующие методы класса. Стоит помнить об этом и быть внимательнее при выборе имён для методов примеси, чтобы их избежать. diff --git a/1-js/09-classes/07-mixins/head.html b/1-js/09-classes/07-mixins/head.html index 77ea38b204..dfe7eda551 100644 --- a/1-js/09-classes/07-mixins/head.html +++ b/1-js/09-classes/07-mixins/head.html @@ -2,8 +2,8 @@ let eventMixin = { /** - * Subscribe to event, usage: - * menu.on('select', function(item) { ... } + * Подписаться на событие, использование: + * menu.on('select', function(item) { ... } */ on(eventName, handler) { if (!this._eventHandlers) this._eventHandlers = {}; @@ -14,8 +14,8 @@ }, /** - * Cancel the subscription, usage: - * menu.off('select', handler) + * Отменить подписку, использование: + * menu.off('select', handler) */ off(eventName, handler) { let handlers = this._eventHandlers && this._eventHandlers[eventName]; @@ -28,15 +28,15 @@ }, /** - * Generate the event and attach the data to it - * this.trigger('select', data1, data2); + * Сгенерировать событие с указанным именем и данными + * this.trigger('select', data1, data2); */ trigger(eventName, ...args) { if (!this._eventHandlers || !this._eventHandlers[eventName]) { - return; // no handlers for that event name + return; // обработчиков для этого события нет } - // call the handlers + // вызовем обработчики this._eventHandlers[eventName].forEach(handler => handler.apply(this, args)); } }; diff --git a/1-js/09-classes/07-mixins/mixin-inheritance.png b/1-js/09-classes/07-mixins/mixin-inheritance.png deleted file mode 100644 index 6142ce7fcc..0000000000 Binary files a/1-js/09-classes/07-mixins/mixin-inheritance.png and /dev/null differ diff --git a/1-js/09-classes/07-mixins/mixin-inheritance.svg b/1-js/09-classes/07-mixins/mixin-inheritance.svg new file mode 100644 index 0000000000..1fdc223936 --- /dev/null +++ b/1-js/09-classes/07-mixins/mixin-inheritance.svg @@ -0,0 +1 @@ +sayHi: function sayBye: functionsayHiMixinsay: functionsayMixin[[Prototype]]constructor: User sayHi: function sayBye: functionUser.prototype[[Prototype]]name: ...user[[HomeObject] \ No newline at end of file diff --git a/1-js/09-classes/07-mixins/mixin-inheritance@2x.png b/1-js/09-classes/07-mixins/mixin-inheritance@2x.png deleted file mode 100644 index ccbd743002..0000000000 Binary files a/1-js/09-classes/07-mixins/mixin-inheritance@2x.png and /dev/null differ diff --git a/1-js/09-classes/index.md b/1-js/09-classes/index.md index 87846ef6ba..90f0aaccf5 100644 --- a/1-js/09-classes/index.md +++ b/1-js/09-classes/index.md @@ -1 +1 @@ -# Classes +# Классы diff --git a/1-js/10-error-handling/1-try-catch/1-finally-or-code-after/solution.md b/1-js/10-error-handling/1-try-catch/1-finally-or-code-after/solution.md index 05ba72e008..5a53960344 100644 --- a/1-js/10-error-handling/1-try-catch/1-finally-or-code-after/solution.md +++ b/1-js/10-error-handling/1-try-catch/1-finally-or-code-after/solution.md @@ -1,47 +1,47 @@ -The difference becomes obvious when we look at the code inside a function. +Разница в поведении станет очевидной, если рассмотреть код внутри функции. -The behavior is different if there's a "jump out" of `try..catch`. +Поведение будет различным, если управление каким-то образом выпрыгнет из `try..catch`. -For instance, when there's a `return` inside `try..catch`. The `finally` clause works in case of *any* exit from `try..catch`, even via the `return` statement: right after `try..catch` is done, but before the calling code gets the control. +Например, когда есть `return` внутри `try..catch`. Секция `finally` работает в любом случае при *любом* выходе из `try..catch`, даже через `return`: сразу после того как `try..catch` выполнится, но до того, как вызывающий код получит контроль. ```js run function f() { try { - alert('start'); + alert('начало'); *!* return "result"; */!* } catch (e) { /// ... } finally { - alert('cleanup!'); + alert('очистка!'); } } -f(); // cleanup! +f(); // очистка! ``` -...Or when there's a `throw`, like here: +...Или когда есть `throw`, например, как здесь: ```js run function f() { try { - alert('start'); - throw new Error("an error"); + alert('начало'); + throw new Error("ошибка"); } catch (e) { // ... - if("can't handle the error") { + if("не могу обработать ошибку") { *!* throw e; */!* } } finally { - alert('cleanup!') + alert('очистка!') } } -f(); // cleanup! +f(); // очистка! ``` -It's `finally` that guarantees the cleanup here. If we just put the code at the end of `f`, it wouldn't run. +Именно `finally` гарантирует очистку. Если мы просто поместим код в конце `f`, то он не выполнится в описанных ситуациях. 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 e846873438..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 @@ -2,37 +2,39 @@ importance: 5 --- -# Finally or just the code? +# Finally или просто код? -Compare the two code fragments. +Сравните два фрагмента кода. -1. The first one uses `finally` to execute the code after `try..catch`: +1. Первый использует `finally` для выполнения кода после `try..catch`: ```js try { - work work + начать работу + работать } catch (e) { - handle errors + обработать ошибку } finally { *!* - cleanup the working space + очистить рабочее пространство */!* } ``` -2. The second fragment puts the cleaning right after `try..catch`: +2. Второй фрагмент просто ставит очистку после `try..catch`: ```js try { - work work + начать работу + работать } catch (e) { - handle errors + обработать ошибку } *!* - cleanup the working space + очистить рабочее пространство */!* ``` -We definitely need the cleanup after the work has started, doesn't matter if there was an error or not. +Нам определённо нужна очистка после работы, неважно возникли ошибки или нет. -Is there an advantage here in using `finally` or both code fragments are equal? If there is such an advantage, then give an example when it matters. +Есть ли здесь преимущество в использовании `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 01e8c1e0f0..5a9e089544 100644 --- a/1-js/10-error-handling/1-try-catch/article.md +++ b/1-js/10-error-handling/1-try-catch/article.md @@ -1,400 +1,396 @@ -# Error handling, "try..catch" +# Обработка ошибок, "try..catch" -No matter how great we are at programming, sometimes our scripts have errors. They may occur because of our mistakes, an unexpected user input, an erroneous server response and for a thousand of other reasons. +Неважно, насколько мы хороши в программировании, иногда наши скрипты содержат ошибки. Они могут возникать из-за наших промахов, неожиданного ввода пользователя, неправильного ответа сервера и по тысяче других причин. -Usually, a script "dies" (immediately stops) in case of an error, printing it to console. +Обычно скрипт в случае ошибки "падает" (сразу же останавливается), с выводом ошибки в консоль. -But there's a syntax construct `try..catch` that allows to "catch" errors and, instead of dying, do something more reasonable. +Но есть синтаксическая конструкция `try..catch`, которая позволяет "ловить" ошибки и вместо падения делать что-то более осмысленное. -## The "try..catch" syntax +## Синтаксис "try..catch" -The `try..catch` construct has two main blocks: `try`, and then `catch`: +Конструкция `try..catch` состоит из двух основных блоков: `try`, и затем `catch`: ```js try { - // code... + // код... } catch (err) { - // error handling + // обработка ошибки } ``` -It works like this: +Работает она так: -1. First, the code in `try {...}` is executed. -2. If there were no errors, then `catch(err)` is ignored: the execution reaches the end of `try` and then jumps over `catch`. -3. If an error occurs, then `try` execution is stopped, and the control flows to the beginning of `catch(err)`. The `err` variable (can use any name for it) contains an error object with details about what's happened. +1. Сначала выполняется код внутри блока `try {...}`. +2. Если в нём нет ошибок, то блок `catch(err)` игнорируется: выполнение доходит до конца `try` и потом далее, полностью пропуская `catch`. +3. Если же в нём возникает ошибка, то выполнение `try` прерывается, и поток управления переходит в начало `catch(err)`. Переменная `err` (можно использовать любое имя) содержит объект ошибки с подробной информацией о произошедшем. -![](try-catch-flow.png) +![](try-catch-flow.svg) -So, an error inside the `try {…}` block does not kill the script: we have a chance to handle it in `catch`. +Таким образом, при ошибке в блоке `try {…}` скрипт не "падает", и мы получаем возможность обработать ошибку внутри `catch`. -Let's see more examples. +Давайте рассмотрим примеры. -- An errorless example: shows `alert` `(1)` and `(2)`: +- Пример без ошибок: выведет `alert` `(1)` и `(2)`: ```js run try { - alert('Start of try runs'); // *!*(1) <--*/!* + alert('Начало блока try'); // *!*(1) <--*/!* - // ...no errors here + // ...код без ошибок - alert('End of try runs'); // *!*(2) <--*/!* + alert('Конец блока try'); // *!*(2) <--*/!* } catch(err) { - alert('Catch is ignored, because there are no errors'); // (3) + alert('Catch игнорируется, так как нет ошибок'); // (3) } - - alert("...Then the execution continues"); ``` -- An example with an error: shows `(1)` and `(3)`: +- Пример с ошибками: выведет `(1)` и `(3)`: ```js run try { - alert('Start of try runs'); // *!*(1) <--*/!* + alert('Начало блока try'); // *!*(1) <--*/!* *!* - lalala; // error, variable is not defined! + lalala; // ошибка, переменная не определена! */!* - alert('End of try (never reached)'); // (2) + alert('Конец блока try (никогда не выполнится)'); // (2) } catch(err) { - alert(`Error has occurred!`); // *!*(3) <--*/!* + alert(`Возникла ошибка!`); // *!*(3) <--*/!* } - - alert("...Then the execution continues"); ``` -````warn header="`try..catch` only works for runtime errors" -For `try..catch` to work, the code must be runnable. In other words, it should be valid JavaScript. +````warn header="`try..catch` работает только для ошибок, возникающих во время выполнения кода" +Чтобы `try..catch` работал, код должен быть выполнимым. Другими словами, это должен быть корректный JavaScript-код. -It won't work if the code is syntactically wrong, for instance it has unmatched curly braces: +Он не сработает, если код синтаксически неверен, например, содержит несовпадающее количество фигурных скобок: ```js run try { {{{{{{{{{{{{ } catch(e) { - alert("The engine can't understand this code, it's invalid"); + alert("Движок не может понять этот код, он некорректен"); } ``` -The JavaScript engine first reads the code, and then runs it. The errors that occur on the reading phrase are called "parse-time" errors and are unrecoverable (from inside that code). That's because the engine can't understand the code. +JavaScript-движок сначала читает код, а затем исполняет его. Ошибки, которые возникают во время фазы чтения, называются ошибками парсинга. Их нельзя обработать (изнутри этого кода), потому что движок не понимает код. -So, `try..catch` can only handle errors that occur in the valid code. Such errors are called "runtime errors" or, sometimes, "exceptions". +Таким образом, `try..catch` может обрабатывать только ошибки, которые возникают в корректном коде. Такие ошибки называют "ошибками во время выполнения", а иногда "исключениями". ```` -````warn header="`try..catch` works synchronously" -If an exception happens in "scheduled" code, like in `setTimeout`, then `try..catch` won't catch it: +````warn header="`try..catch` работает синхронно" +Исключение, которое произойдёт в коде, запланированном "на будущее", например в `setTimeout`, `try..catch` не поймает: ```js run try { setTimeout(function() { - noSuchVariable; // script will die here + noSuchVariable; // скрипт упадёт тут }, 1000); } catch (e) { - alert( "won't work" ); + alert( "не сработает" ); } ``` -That's because `try..catch` actually wraps the `setTimeout` call that schedules the function. But the function itself is executed later, when the engine has already left the `try..catch` construct. +Это потому, что функция выполняется позже, когда движок уже покинул конструкцию `try..catch`. -To catch an exception inside a scheduled function, `try..catch` must be inside that function: +Чтобы поймать исключение внутри запланированной функции, `try..catch` должен находиться внутри самой этой функции: ```js run setTimeout(function() { try { - noSuchVariable; // try..catch handles the error! + noSuchVariable; // try..catch обрабатывает ошибку! } catch { - alert( "error is caught here!" ); + alert( "ошибка поймана!" ); } }, 1000); ``` ```` -## Error object +## Объект ошибки -When an error occurs, JavaScript generates an object containing the details about it. The object is then passed as an argument to `catch`: +Когда возникает ошибка, JavaScript генерирует объект, содержащий её детали. Затем этот объект передаётся как аргумент в блок `catch`: ```js try { // ... -} catch(err) { // <-- the "error object", could use another word instead of err +} catch(err) { // <-- объект ошибки, можно использовать другое название вместо err // ... } ``` -For all built-in errors, the error object inside `catch` block has two main properties: +Для всех встроенных ошибок этот объект имеет два основных свойства: `name` -: Error name. For an undefined variable that's `"ReferenceError"`. +: Имя ошибки. Например, для неопределённой переменной это `"ReferenceError"`. `message` -: Textual message about error details. +: Текстовое сообщение о деталях ошибки. -There are other non-standard properties available in most environments. One of most widely used and supported is: +В большинстве окружений доступны и другие, нестандартные свойства. Одно из самых широко используемых и поддерживаемых - это: `stack` -: Current call stack: a string with information about the sequence of nested calls that led to the error. Used for debugging purposes. +: Текущий стек вызова: строка, содержащая информацию о последовательности вложенных вызовов, которые привели к ошибке. Используется в целях отладки. -For instance: +Например: ```js run untrusted try { *!* - lalala; // error, variable is not defined! + lalala; // ошибка, переменная не определена! */!* } catch(err) { alert(err.name); // ReferenceError alert(err.message); // lalala is not defined - alert(err.stack); // ReferenceError: lalala is not defined at ... + alert(err.stack); // ReferenceError: lalala is not defined at (...стек вызовов) - // Can also show an error as a whole - // The error is converted to string as "name: message" + // Можем также просто вывести ошибку целиком + // Ошибка приводится к строке вида "name: message" alert(err); // ReferenceError: lalala is not defined } ``` -## Optional "catch" binding +## Блок "catch" без переменной [recent browser=new] -If we don't need error details, `catch` may omit it: +Если нам не нужны детали ошибки, в `catch` можно её пропустить: ```js try { // ... -} catch { - // error object omitted +} catch { // <-- без (err) + // ... } ``` -## Using "try..catch" +## Использование "try..catch" -Let's explore a real-life use case of `try..catch`. +Давайте рассмотрим реальные случаи использования `try..catch`. -As we already know, JavaScript supports the [JSON.parse(str)](mdn:js/JSON/parse) method to read JSON-encoded values. +Как мы уже знаем, JavaScript поддерживает метод [JSON.parse(str)](mdn:js/JSON/parse) для чтения JSON. -Usually it's used to decode data received over the network, from the server or another source. +Обычно он используется для декодирования данных, полученных по сети, от сервера или из другого источника. -We receive it and call `JSON.parse`, like this: +Мы получаем их и вызываем `JSON.parse` вот так: ```js run -let json = '{"name":"John", "age": 30}'; // data from the server +let json = '{"name":"John", "age": 30}'; // данные с сервера *!* -let user = JSON.parse(json); // convert the text representation to JS object +let user = JSON.parse(json); // преобразовали текстовое представление в JS-объект */!* -// now user is an object with properties from the string +// теперь user - объект со свойствами из строки alert( user.name ); // John alert( user.age ); // 30 ``` -You can find more detailed information about JSON in the chapter. +Вы можете найти более детальную информацию о JSON в главе . -**If `json` is malformed, `JSON.parse` generates an error, so the script "dies".** +**Если `json` некорректен, `JSON.parse` генерирует ошибку, то есть скрипт "падает".** -Should we be satisfied with that? Of course, not! +Устроит ли нас такое поведение? Конечно нет! -This way, if something's wrong with the data, the visitor will never know that (unless they open the developer console). And people really don't like when something "just dies" without any error message. +Получается, что если вдруг что-то не так с данными, то посетитель никогда (если, конечно, не откроет консоль) об этом не узнает. А люди очень не любят, когда что-то "просто падает" без всякого сообщения об ошибке. -Let's use `try..catch` to handle the error: +Давайте используем `try..catch` для обработки ошибки: ```js run -let json = "{ bad json }"; +let json = "{ некорректный JSON }"; try { *!* - let user = JSON.parse(json); // <-- when an error occurs... + let user = JSON.parse(json); // <-- тут возникает ошибка... */!* - alert( user.name ); // doesn't work + alert( user.name ); // не сработает } catch (e) { *!* - // ...the execution jumps here - alert( "Our apologies, the data has errors, we'll try to request it one more time." ); + // ...выполнение прыгает сюда + alert( "Извините, в данных ошибка, мы попробуем получить их ещё раз." ); alert( e.name ); alert( e.message ); */!* } ``` -Here we use the `catch` block only to show the message, but we can do much more: send a new network request, suggest an alternative to the visitor, send information about the error to a logging facility, ... . All much better than just dying. +Здесь мы используем блок `catch` только для вывода сообщения, но мы также можем сделать гораздо больше: отправить новый сетевой запрос, предложить посетителю альтернативный способ, отослать информацию об ошибке на сервер для логирования, ... Всё лучше, чем просто "падение". -## Throwing our own errors +## Генерация собственных ошибок -What if `json` is syntactically correct, but doesn't have a required `name` property? +Что если `json` синтаксически корректен, но не содержит необходимого свойства `name`? -Like this: +Например, так: ```js run -let json = '{ "age": 30 }'; // incomplete data +let json = '{ "age": 30 }'; // данные неполны try { - let user = JSON.parse(json); // <-- no errors + let user = JSON.parse(json); // <-- выполнится без ошибок *!* - alert( user.name ); // no name! + alert( user.name ); // нет свойства name! */!* } catch (e) { - alert( "doesn't execute" ); + alert( "не выполнится" ); } ``` -Here `JSON.parse` runs normally, but the absence of `name` is actually an error for us. +Здесь `JSON.parse` выполнится без ошибок, но на самом деле отсутствие свойства `name` для нас ошибка. -To unify error handling, we'll use the `throw` operator. +Для того, чтобы унифицировать обработку ошибок, мы воспользуемся оператором `throw`. -### "Throw" operator +### Оператор "throw" -The `throw` operator generates an error. +Оператор `throw` генерирует ошибку. -The syntax is: +Синтаксис: ```js -throw +throw <объект ошибки> ``` -Technically, we can use anything as an error object. That may be even a primitive, like a number or a string, but it's better to use objects, preferably with `name` and `message` properties (to stay somewhat compatible with built-in errors). +Технически в качестве объекта ошибки можно передать что угодно. Это может быть даже примитив, число или строка, но всё же лучше, чтобы это был объект, желательно со свойствами `name` и `message` (для совместимости со встроенными ошибками). -JavaScript has many built-in constructors for standard errors: `Error`, `SyntaxError`, `ReferenceError`, `TypeError` and others. We can use them to create error objects as well. +В JavaScript есть множество встроенных конструкторов для стандартных ошибок: `Error`, `SyntaxError`, `ReferenceError`, `TypeError` и другие. Можно использовать и их для создания объектов ошибки. -Their syntax is: +Их синтаксис: ```js let error = new Error(message); -// or +// или let error = new SyntaxError(message); let error = new ReferenceError(message); // ... ``` -For built-in errors (not for any objects, just for errors), the `name` property is exactly the name of the constructor. And `message` is taken from the argument. +Для встроенных ошибок (не для любых объектов, только для ошибок), свойство `name` -- это в точности имя конструктора. А свойство `message` берётся из аргумента. -For instance: +Например: ```js run -let error = new Error("Things happen o_O"); +let error = new Error("Ого, ошибка! o_O"); alert(error.name); // Error -alert(error.message); // Things happen o_O +alert(error.message); // Ого, ошибка! o_O ``` -Let's see what kind of error `JSON.parse` generates: +Давайте посмотрим, какую ошибку генерирует `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 0 + alert(e.message); // Expected property name or '}' in JSON at position 2 (line 1 column 3) } ``` -As we can see, that's a `SyntaxError`. +Как мы видим, это `SyntaxError`. -And in our case, the absence of `name` could be treated as a syntax error also, assuming that users must have a `name`. +В нашем случае отсутствие свойства `name` - это ошибка, ведь пользователи должны иметь имена. -So let's throw it: +Сгенерируем её: ```js run -let json = '{ "age": 30 }'; // incomplete data +let json = '{ "age": 30 }'; // данные неполны try { - let user = JSON.parse(json); // <-- no errors + let user = JSON.parse(json); // <-- выполнится без ошибок if (!user.name) { *!* - throw new SyntaxError("Incomplete data: no name"); // (*) + throw new SyntaxError("Данные неполны: нет имени"); // (*) */!* } alert( user.name ); } catch(e) { - alert( "JSON Error: " + e.message ); // JSON Error: Incomplete data: no name + alert( "JSON Error: " + e.message ); // JSON Error: Данные неполны: нет имени } ``` -In the line `(*)`, the `throw` operator generates a `SyntaxError` with the given `message`, the same way as JavaScript would generate it itself. The execution of `try` immediately stops and the control flow jumps into `catch`. +В строке `(*)` оператор `throw` генерирует ошибку `SyntaxError` с сообщением `message`. Точно такого же вида, как генерирует сам JavaScript. Выполнение блока `try` немедленно останавливается, и поток управления прыгает в `catch`. -Now `catch` became a single place for all error handling: both for `JSON.parse` and other cases. +Теперь блок `catch` становится единственным местом для обработки всех ошибок: и для `JSON.parse` и для других случаев. -## Rethrowing +## Проброс исключения -In the example above we use `try..catch` to handle incorrect data. But is it possible that *another unexpected error* occurs within the `try {...}` block? Like a variable is undefined or something else, not just that "incorrect data" thing. +В примере выше мы использовали `try..catch` для обработки некорректных данных. А что, если в блоке `try {...}` возникнет *другая неожиданная ошибка*? Например, программная (неопределённая переменная) или какая-то ещё, а не ошибка, связанная с некорректными данными. -Like this: +Пример: ```js run -let json = '{ "age": 30 }'; // incomplete data +let json = '{ "age": 30 }'; // данные неполны try { - user = JSON.parse(json); // <-- forgot to put "let" before user + user = JSON.parse(json); // <-- забыл добавить "let" перед user // ... } catch(err) { alert("JSON Error: " + err); // JSON Error: ReferenceError: user is not defined - // (no JSON Error actually) + // (не JSON ошибка на самом деле) } ``` -Of course, everything's possible! Programmers do make mistakes. Even in open-source utilities used by millions for decades -- suddenly a crazy bug may be discovered that leads to terrible hacks (like it happened with the `ssh` tool). +Конечно, возможно все! Программисты совершают ошибки. Даже в утилитах с открытым исходным кодом, используемых миллионами людей на протяжении десятилетий -- вдруг может быть обнаружена ошибка, которая приводит к ужасным взломам. -In our case, `try..catch` is meant to catch "incorrect data" errors. But by its nature, `catch` gets *all* errors from `try`. Here it gets an unexpected error, but still shows the same `"JSON Error"` message. That's wrong and also makes the code more difficult to debug. +В нашем случае `try..catch` предназначен для выявления ошибок, связанных с некорректными данными. Но по своей природе `catch` получает *все* свои ошибки из `try`. Здесь он получает неожиданную ошибку, но всё также показывает то же самое сообщение `"JSON Error"`. Это неправильно и затрудняет отладку кода. -Fortunately, we can find out which error we get, for instance from its `name`: +К счастью, мы можем выяснить, какую ошибку мы получили, например, по её свойству `name`: ```js run try { user = { /*...*/ }; } catch(e) { *!* - alert(e.name); // "ReferenceError" for accessing an undefined variable + alert(e.name); // "ReferenceError" из-за неопределённой переменной */!* } ``` -The rule is simple: +Есть простое правило: -**Catch should only process errors that it knows and "rethrow" all others.** +**Блок `catch` должен обрабатывать только те ошибки, которые ему известны, и "пробрасывать" все остальные.** -The "rethrowing" technique can be explained in more detail as: +Техника "проброс исключения" выглядит так: -1. Catch gets all errors. -2. In `catch(err) {...}` block we analyze the error object `err`. -2. If we don't know how to handle it, then we do `throw err`. +1. Блок `catch` получает все ошибки. +2. В блоке `catch(err) {...}` мы анализируем объект ошибки `err`. +3. Если мы не знаем как её обработать, тогда делаем `throw err`. -In the code below, we use rethrowing so that `catch` only handles `SyntaxError`: +В коде ниже мы используем проброс исключения, `catch` обрабатывает только `SyntaxError`: ```js run -let json = '{ "age": 30 }'; // incomplete data +let json = '{ "age": 30 }'; // данные неполны try { let user = JSON.parse(json); if (!user.name) { - throw new SyntaxError("Incomplete data: no name"); + throw new SyntaxError("Данные неполны: нет имени"); } *!* - blabla(); // unexpected error + blabla(); // неожиданная ошибка */!* alert( user.name ); @@ -405,18 +401,18 @@ try { if (e.name == "SyntaxError") { alert( "JSON Error: " + e.message ); } else { - throw e; // rethrow (*) + throw e; // проброс (*) } */!* } ``` -The error throwing on line `(*)` from inside `catch` block "falls out" of `try..catch` and can be either caught by an outer `try..catch` construct (if it exists), or it kills the script. +Ошибка в строке `(*)` из блока `catch` "выпадает наружу" и может быть поймана другой внешней конструкцией `try..catch` (если есть), или "убьёт" скрипт. -So the `catch` block actually handles only errors that it knows how to deal with and "skips" all others. +Таким образом, блок `catch` фактически обрабатывает только те ошибки, с которыми он знает, как справляться, и пропускает остальные. -The example below demonstrates how such errors can be caught by one more level of `try..catch`: +Пример ниже демонстрирует, как такие ошибки могут быть пойманы с помощью ещё одного уровня `try..catch`: ```js run function readData() { @@ -425,13 +421,13 @@ function readData() { try { // ... *!* - blabla(); // error! + blabla(); // ошибка! */!* } catch (e) { // ... if (e.name != 'SyntaxError') { *!* - throw e; // rethrow (don't know how to deal with it) + throw e; // проброс исключения (не знаю как это обработать) */!* } } @@ -441,42 +437,42 @@ try { readData(); } catch (e) { *!* - alert( "External catch got: " + e ); // caught it! + alert( "Внешний catch поймал: " + e ); // поймал! */!* } ``` -Here `readData` only knows how to handle `SyntaxError`, while the outer `try..catch` knows how to handle everything. +Здесь `readData` знает только, как обработать `SyntaxError`, тогда как внешний блок `try..catch` знает, как обработать всё. ## try..catch..finally -Wait, that's not all. +Подождите, это ещё не всё. -The `try..catch` construct may have one more code clause: `finally`. +Конструкция `try..catch` может содержать ещё одну секцию: `finally`. -If it exists, it runs in all cases: +Если секция есть, то она выполняется в любом случае: -- after `try`, if there were no errors, -- after `catch`, if there were errors. +- после `try`, если не было ошибок, +- после `catch`, если ошибки были. -The extended syntax looks like this: +Расширенный синтаксис выглядит следующим образом: ```js *!*try*/!* { - ... try to execute the code ... + ... пробуем выполнить код... } *!*catch*/!*(e) { - ... handle errors ... + ... обрабатываем ошибки ... } *!*finally*/!* { - ... execute always ... + ... выполняем всегда ... } ``` -Try running this code: +Попробуйте запустить такой код: ```js run try { alert( 'try' ); - if (confirm('Make an error?')) BAD_CODE(); + if (confirm('Сгенерировать ошибку?')) BAD_CODE(); } catch (e) { alert( 'catch' ); } finally { @@ -484,27 +480,27 @@ try { } ``` -The code has two ways of execution: +У кода есть два пути выполнения: -1. If you answer "Yes" to "Make an error?", then `try -> catch -> finally`. -2. If you say "No", then `try -> finally`. +1. Если вы ответите на вопрос "Сгенерировать ошибку?" утвердительно, то `try -> catch -> finally`. +2. Если ответите отрицательно, то `try -> finally`. -The `finally` clause is often used when we start doing something before `try..catch` and want to finalize it in any case of outcome. +Секцию `finally` часто используют, когда мы начали что-то делать и хотим завершить это вне зависимости от того, будет ошибка или нет. -For instance, we want to measure the time that a Fibonacci numbers function `fib(n)` takes. Naturally, we can start measuring before it runs and finish afterwards. But what if there's an error during the function call? In particular, the implementation of `fib(n)` in the code below returns an error for negative or non-integer numbers. +Например, мы хотим измерить время, которое занимает функция чисел Фибоначчи `fib(n)`. Естественно, мы можем начать измерения до того, как функция начнёт выполняться и закончить после. Но что делать, если при вызове функции возникла ошибка? В частности, реализация `fib(n)` в коде ниже возвращает ошибку для отрицательных и для нецелых чисел. -The `finally` clause is a great place to finish the measurements no matter what. +Секция `finally` отлично подходит для завершения измерений несмотря ни на что. -Here `finally` guarantees that the time will be measured correctly in both situations -- in case of a successful execution of `fib` and in case of an error in it: +Здесь `finally` гарантирует, что время будет измерено корректно в обеих ситуациях - и в случае успешного завершения `fib` и в случае ошибки: ```js run -let num = +prompt("Enter a positive integer number?", 35) +let num = +prompt("Введите положительное целое число?", 35) let diff, result; function fib(n) { if (n < 0 || Math.trunc(n) != n) { - throw new Error("Must not be negative, and also an integer."); + throw new Error("Должно быть целое неотрицательное число"); } return n <= 1 ? n : fib(n - 1) + fib(n - 2); } @@ -521,26 +517,26 @@ try { } */!* -alert(result || "error occured"); +alert(result || "возникла ошибка"); -alert( `execution took ${diff}ms` ); +alert( `Выполнение заняло ${diff}ms` ); ``` -You can check by running the code with entering `35` into `prompt` -- it executes normally, `finally` after `try`. And then enter `-1` -- there will be an immediate error, an the execution will take `0ms`. Both measurements are done correctly. +Вы можете это проверить, запустив этот код и введя `35` в `prompt` -- код завершится нормально, `finally` выполнится после `try`. А затем введите `-1` -- незамедлительно произойдёт ошибка, выполнение займёт `0ms`. Оба измерения выполняются корректно. -In other words, there may be two ways to exit a function: either a `return` or `throw`. The `finally` clause handles them both. +Другими словами, неважно как завершилась функция: через `return` или `throw`. Секция `finally` срабатывает в обоих случаях. -```smart header="Variables are local inside `try..catch..finally`" -Please note that `result` and `diff` variables in the code above are declared *before* `try..catch`. +```smart header="Переменные внутри `try..catch..finally` локальны" +Обратите внимание, что переменные `result` и `diff` в коде выше объявлены *до* `try..catch`. -Otherwise, if `let` were made inside the `{...}` block, it would only be visible inside of it. +Если переменную объявить в блоке, например, в `try`, то она не будет доступна после него. ``` -````smart header="`finally` and `return`" -The `finally` clause works for *any* exit from `try..catch`. That includes an explicit `return`. +````smart header="`finally` и `return`" +Блок `finally` срабатывает при *любом* выходе из `try..catch`, в том числе и `return`. -In the example below, there's a `return` in `try`. In this case, `finally` is executed just before the control returns to the outer code. +В примере ниже из `try` происходит `return`, но `finally` получает управление до того, как контроль возвращается во внешний код. ```js run function func() { @@ -559,40 +555,40 @@ function func() { } } -alert( func() ); // first works alert from finally, and then this one +alert( func() ); // сначала срабатывает alert из finally, а затем этот код ``` ```` ````smart header="`try..finally`" -The `try..finally` construct, without `catch` clause, is also useful. We apply it when we don't want to handle errors right here, but want to be sure that processes that we started are finalized. +Конструкция `try..finally` без секции `catch` также полезна. Мы применяем её, когда не хотим здесь обрабатывать ошибки (пусть выпадут), но хотим быть уверены, что начатые процессы завершились. ```js function func() { - // start doing something that needs completion (like measurements) + // начать делать что-то, что требует завершения (например, измерения) try { // ... } finally { - // complete that thing even if all dies + // завершить это, даже если все упадёт } } ``` -In the code above, an error inside `try` always falls out, because there's no `catch`. But `finally` works before the execution flow jumps outside. +В приведённом выше коде ошибка всегда выпадает наружу, потому что тут нет блока `catch`. Но `finally` отрабатывает до того, как поток управления выйдет из функции. ```` -## Global catch +## Глобальный catch -```warn header="Environment-specific" -The information from this section is not a part of the core JavaScript. +```warn header="Зависит от окружения" +Информация из данной секции не является частью языка JavaScript. ``` -Let's imagine we've got a fatal error outside of `try..catch`, and the script died. Like a programming error or something else terrible. +Давайте представим, что произошла фатальная ошибка (программная или что-то ещё ужасное) снаружи `try..catch`, и скрипт упал. -Is there a way to react on such occurrences? We may want to log the error, show something to the user (normally they don't see error messages) etc. +Существует ли способ отреагировать на такие ситуации? Мы можем захотеть залогировать ошибку, показать что-то пользователю (обычно они не видят сообщение об ошибке) и т.д. -There is none in the specification, but environments usually provide it, because it's really useful. For instance, Node.js has [process.on('uncaughtException')](https://nodejs.org/api/process.html#process_event_uncaughtexception) for that. And in the browser we can assign a function to special [window.onerror](mdn:api/GlobalEventHandlers/onerror) property. It will run in case of an uncaught error. +Такого способа нет в спецификации, но обычно окружения предоставляют его, потому что это весьма полезно. Например, в Node.js для этого есть [`process.on("uncaughtException")`](https://nodejs.org/api/process.html#process_event_uncaughtexception). А в браузере мы можем присвоить функцию специальному свойству [window.onerror](mdn:api/GlobalEventHandlers/onerror), которая будет вызвана в случае необработанной ошибки. -The syntax: +Синтаксис: ```js window.onerror = function(message, url, line, col, error) { @@ -601,75 +597,75 @@ window.onerror = function(message, url, line, col, error) { ``` `message` -: Error message. +: Сообщение об ошибке. `url` -: URL of the script where error happened. +: URL скрипта, в котором произошла ошибка. `line`, `col` -: Line and column numbers where error happened. +: Номера строки и столбца, в которых произошла ошибка. `error` -: Error object. +: Объект ошибки. -For instance: +Пример: ```html run untrusted refresh height=1 ``` -The role of the global handler `window.onerror` is usually not to recover the script execution -- that's probably impossible in case of programming errors, but to send the error message to developers. +Роль глобального обработчика `window.onerror` обычно заключается не в восстановлении выполнения скрипта -- это скорее всего невозможно в случае программной ошибки, а в отправке сообщения об ошибке разработчикам. -There are also web-services that provide error-logging for such cases, like or . +Существуют также веб-сервисы, которые предоставляют логирование ошибок для таких случаев, такие как . -They work like this: +Они работают так: -1. We register at the service and get a piece of JS (or a script URL) from them to insert on pages. -2. That JS script has a custom `window.onerror` function. -3. When an error occurs, it sends a network request about it to the service. -4. We can log in to the service web interface and see errors. +1. Мы регистрируемся в сервисе и получаем небольшой JS-скрипт (или URL скрипта) от них для вставки на страницы. +2. Этот JS-скрипт ставит свою функцию `window.onerror`. +3. Когда возникает ошибка, она выполняется и отправляет сетевой запрос с информацией о ней в сервис. +4. Мы можем войти в веб-интерфейс сервиса и увидеть ошибки. -## Summary +## Итого -The `try..catch` construct allows to handle runtime errors. It literally allows to try running the code and catch errors that may occur in it. +Конструкция `try..catch` позволяет обрабатывать ошибки во время исполнения кода. Она позволяет запустить код и перехватить ошибки, которые могут в нём возникнуть. -The syntax is: +Синтаксис: ```js try { - // run this code + // исполняем код } catch(err) { - // if an error happened, then jump here - // err is the error object + // если случилась ошибка, прыгаем сюда + // err - это объект ошибки } finally { - // do in any case after try/catch + // выполняется всегда после try/catch } ``` -There may be no `catch` section or no `finally`, so `try..catch` and `try..finally` are also valid. +Секций `catch` или `finally` может не быть, то есть более короткие конструкции `try..catch` и `try..finally` также корректны. -Error objects have following properties: +Объекты ошибок содержат следующие свойства: -- `message` -- the human-readable error message. -- `name` -- the string with error name (error constructor name). -- `stack` (non-standard) -- the stack at the moment of error creation. +- `message` -- понятное человеку сообщение. +- `name` -- строка с именем ошибки (имя конструктора ошибки). +- `stack` (нестандартное, но хорошо поддерживается) -- стек на момент ошибки. -If error is not needed, we can omit it by using `catch {` instead of `catch(err) {`. +Если объект ошибки не нужен, мы можем пропустить его, используя `catch {` вместо `catch(err) {`. -We can also generate our own errors using the `throw` operator. Technically, the argument of `throw` can be anything, but usually it's an error object inheriting from the built-in `Error` class. More on extending errors in the next chapter. +Мы можем также генерировать собственные ошибки, используя оператор `throw`. Аргументом `throw` может быть что угодно, но обычно это объект ошибки, наследуемый от встроенного класса `Error`. Подробнее о расширении ошибок см. в следующей главе. -Rethrowing is a basic pattern of error handling: a `catch` block usually expects and knows how to handle the particular error type, so it should rethrow errors it doesn't know. +*Проброс исключения* -- это очень важный приём обработки ошибок: блок `catch` обычно ожидает и знает, как обработать определённый тип ошибок, поэтому он должен пробрасывать дальше ошибки, о которых он не знает. -Even if we don't have `try..catch`, most environments allow to setup a "global" error handler to catch errors that "fall out". In-browser that's `window.onerror`. +Даже если у нас нет `try..catch`, большинство сред позволяют настроить "глобальный" обработчик ошибок, чтобы ловить ошибки, которые "выпадают наружу". В браузере это `window.onerror`. diff --git a/1-js/10-error-handling/1-try-catch/try-catch-flow.png b/1-js/10-error-handling/1-try-catch/try-catch-flow.png deleted file mode 100644 index 8b37753814..0000000000 Binary files a/1-js/10-error-handling/1-try-catch/try-catch-flow.png and /dev/null differ 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 new file mode 100644 index 0000000000..8e6ca4d79c --- /dev/null +++ b/1-js/10-error-handling/1-try-catch/try-catch-flow.svg @@ -0,0 +1 @@ +НачалоНет ошибокВ коде произошла ошибкаИгнорировать блок catchИгнорировать остаток tryВыполнить блок catchtry { }// код... \ No newline at end of file diff --git a/1-js/10-error-handling/1-try-catch/try-catch-flow@2x.png b/1-js/10-error-handling/1-try-catch/try-catch-flow@2x.png deleted file mode 100644 index f85eb63218..0000000000 Binary files a/1-js/10-error-handling/1-try-catch/try-catch-flow@2x.png and /dev/null differ diff --git a/1-js/10-error-handling/2-custom-errors/1-format-error/solution.md b/1-js/10-error-handling/2-custom-errors/1-format-error/solution.md index bb6b74cfaf..e42c4e1c80 100644 --- a/1-js/10-error-handling/2-custom-errors/1-format-error/solution.md +++ b/1-js/10-error-handling/2-custom-errors/1-format-error/solution.md @@ -6,9 +6,9 @@ class FormatError extends SyntaxError { } } -let err = new FormatError("formatting error"); +let err = new FormatError("ошибка форматирования"); -alert( err.message ); // formatting error +alert( err.message ); // ошибка форматирования alert( err.name ); // FormatError alert( err.stack ); // stack diff --git a/1-js/10-error-handling/2-custom-errors/1-format-error/task.md b/1-js/10-error-handling/2-custom-errors/1-format-error/task.md index 2c8e910fc0..ec7b2c9c5d 100644 --- a/1-js/10-error-handling/2-custom-errors/1-format-error/task.md +++ b/1-js/10-error-handling/2-custom-errors/1-format-error/task.md @@ -2,21 +2,21 @@ importance: 5 --- -# Inherit from SyntaxError +# Наследование от SyntaxError -Create a class `FormatError` that inherits from the built-in `SyntaxError` class. +Создайте класс `FormatError`, который наследует от встроенного класса `SyntaxError`. -It should support `message`, `name` and `stack` properties. +Класс должен поддерживать свойства `message`, `name` и `stack`. -Usage example: +Пример использования: ```js -let err = new FormatError("formatting error"); +let err = new FormatError("ошибка форматирования"); -alert( err.message ); // formatting error +alert( err.message ); // ошибка форматирования alert( err.name ); // FormatError alert( err.stack ); // stack alert( err instanceof FormatError ); // true -alert( err instanceof SyntaxError ); // true (because inherits from SyntaxError) +alert( err instanceof SyntaxError ); // true (потому что наследует от SyntaxError) ``` 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 5079c746d3..fbc3b9dc35 100644 --- a/1-js/10-error-handling/2-custom-errors/article.md +++ b/1-js/10-error-handling/2-custom-errors/article.md @@ -1,48 +1,46 @@ -# Custom errors, extending Error +# Пользовательские ошибки, расширение Error -When we develop something, we often need our own error classes to reflect specific things that may go wrong in our tasks. For errors in network operations we may need `HttpError`, for database operations `DbError`, for searching operations `NotFoundError` and so on. +Когда что-то разрабатываем, то нам часто необходимы собственные классы ошибок для разных вещей, которые могут пойти не так в наших задачах. Для ошибок при работе с сетью может понадобиться `HttpError`, для операций с базой данных `DbError`, для поиска - `NotFoundError` и т.д. -Our errors should support basic error properties like `message`, `name` and, preferably, `stack`. But they also may have other properties of their own, e.g. `HttpError` objects may have `statusCode` property with a value like `404` or `403` or `500`. +Наши ошибки должны поддерживать базовые свойства, такие как `message`, `name` и, желательно, `stack`. Но также они могут иметь свои собственные свойства. Например, объекты `HttpError` могут иметь свойство `statusCode` со значениями `404`, `403` или `500`. -JavaScript allows to use `throw` with any argument, so technically our custom error classes don't need to inherit from `Error`. But if we inherit, then it becomes possible to use `obj instanceof Error` to identify error objects. So it's better to inherit from it. +JavaScript позволяет вызывать `throw` с любыми аргументами, то есть технически наши классы ошибок не нуждаются в наследовании от `Error`. Но если использовать наследование, то появляется возможность идентификации объектов ошибок посредством `obj instanceof Error`. Так что лучше применять наследование. -As we build our application, our own errors naturally form a hierarchy, for instance `HttpTimeoutError` may inherit from `HttpError`, and so on. +По мере роста приложения, наши собственные ошибки образуют иерархию, например, `HttpTimeoutError` может наследовать от ` HttpError` и так далее. -## Extending Error +## Расширение Error -As an example, let's consider a function `readUser(json)` that should read JSON with user data. +В качестве примера рассмотрим функцию `readUser(json)`, которая должна читать данные пользователя в формате JSON. -Here's an example of how a valid `json` may look: +Пример того, как может выглядеть корректный `json`: ```js let json = `{ "name": "John", "age": 30 }`; ``` -Internally, we'll use `JSON.parse`. If it receives malformed `json`, then it throws `SyntaxError`. +Внутри будем использовать `JSON.parse`. При получении некорректного `json` он будет генерировать ошибку `SyntaxError`. Но даже если `json` синтаксически верен, то это не значит, что это будет корректный пользователь, верно? Могут быть пропущены необходимые данные. Например, могут отсутствовать свойства `name`и `age`, которые являются необходимыми для наших пользователей. -But even if `json` is syntactically correct, that doesn't mean that it's a valid user, right? It may miss the necessary data. For instance, it may not have `name` and `age` properties that are essential for our users. +Наша функция `readUser(json)` будет не только читать JSON-данные, но и проверять их ("валидировать"). Если необходимые поля отсутствуют или данные в неверном формате, то это будет ошибкой. Но не синтаксической ошибкой `SyntaxError`, потому что данные синтаксически корректны. Это будет другая ошибка. -Our function `readUser(json)` will not only read JSON, but check ("validate") the data. If there are no required fields, or the format is wrong, then that's an error. And that's not a `SyntaxError`, because the data is syntactically correct, but another kind of error. We'll call it `ValidationError` and create a class for it. An error of that kind should also carry the information about the offending field. +Назовём её ошибкой валидации `ValidationError` и создадим для неё класс. Ошибка этого вида должна содержать информацию о поле, которое является источником ошибки. -Our `ValidationError` class should inherit from the built-in `Error` class. +Наш класс `ValidationError` должен наследовать от встроенного класса `Error`. -That class is built-in, but we should have its approximate code before our eyes, to understand what we're extending. - -So here you are: +Класс `Error` встроенный, вот его примерный код, просто чтобы мы понимали, что расширяем: ```js -// The "pseudocode" for the built-in Error class defined by JavaScript itself +// "Псевдокод" встроенного класса Error, определённого самим JavaScript class Error { constructor(message) { this.message = message; - this.name = "Error"; // (different names for different built-in error classes) - this.stack = ; // non-standard, but most environments support it + this.name = "Error"; // (разные имена для разных встроенных классов ошибок) + this.stack = <стек вызовов>; // нестандартное свойство, но обычно поддерживается } } ``` -Now let's go on and inherit `ValidationError` from it: +Теперь давайте унаследуем от него `ValidationError` и попробуем новый класс в действии: -```js run untrusted +```js run *!* class ValidationError extends Error { */!* @@ -53,24 +51,23 @@ class ValidationError extends Error { } function test() { - throw new ValidationError("Whoops!"); + throw new ValidationError("Упс!"); } try { test(); } catch(err) { - alert(err.message); // Whoops! + alert(err.message); // Упс! alert(err.name); // ValidationError - alert(err.stack); // a list of nested calls with line numbers for each + alert(err.stack); // список вложенных вызовов с номерами строк для каждого } ``` -Please take a look at the constructor: +Обратите внимание: в строке `(1)` вызываем родительский конструктор. JavaScript требует от нас вызова `super` в дочернем конструкторе, так что это обязательно. Родительский конструктор устанавливает свойство `message`. -1. In the line `(1)` we call the parent constructor. JavaScript requires us to call `super` in the child constructor, so that's obligatory. The parent constructor sets the `message` property. -2. The parent constructor also sets the `name` property to `"Error"`, so in the line `(2)` we reset it to the right value. +Родительский конструктор также устанавливает свойство `name` для `"Error"`, поэтому в строке `(2)` мы сбрасываем его на правильное значение. -Let's try to use it in `readUser(json)`: +Попробуем использовать его в `readUser(json)`: ```js run class ValidationError extends Error { @@ -80,57 +77,57 @@ class ValidationError extends Error { } } -// Usage +// Использование function readUser(json) { let user = JSON.parse(json); if (!user.age) { - throw new ValidationError("No field: age"); + throw new ValidationError("Нет поля: age"); } if (!user.name) { - throw new ValidationError("No field: name"); + throw new ValidationError("Нет поля: name"); } return user; } -// Working example with try..catch +// Рабочий пример с try..catch try { let user = readUser('{ "age": 25 }'); } catch (err) { if (err instanceof ValidationError) { *!* - alert("Invalid data: " + err.message); // Invalid data: No field: name + alert("Некорректные данные: " + err.message); // Некорректные данные: Нет поля: name */!* } else if (err instanceof SyntaxError) { // (*) - alert("JSON Syntax Error: " + err.message); + alert("JSON Ошибка Синтаксиса: " + err.message); } else { - throw err; // unknown error, rethrow it (**) + throw err; // неизвестная ошибка, пробросить исключение (**) } } ``` -The `try..catch` block in the code above handles both our `ValidationError` and the built-in `SyntaxError` from `JSON.parse`. +Блок `try..catch` в коде выше обрабатывает и нашу `ValidationError`, и встроенную `SyntaxError` из `JSON.parse`. -Please take a look at how we use `instanceof` to check for the specific error type in the line `(*)`. +Обратите внимание, как мы используем `instanceof` для проверки конкретного типа ошибки в строке `(*)`. -We could also look at `err.name`, like this: +Мы можем также проверить тип, используя `err.name`: ```js // ... -// instead of (err instanceof SyntaxError) +// вместо (err instanceof SyntaxError) } else if (err.name == "SyntaxError") { // (*) // ... -``` +``` -The `instanceof` version is much better, because in the future we are going to extend `ValidationError`, make subtypes of it, like `PropertyRequiredError`. And `instanceof` check will continue to work for new inheriting classes. So that's future-proof. +Версия с `instanceof` гораздо лучше, потому что в будущем мы собираемся расширить `ValidationError`, сделав его подтипы, такие как `PropertyRequiredError`. И проверка `instanceof` продолжит работать для новых наследованных классов. Так что это на будущее. -Also it's important that if `catch` meets an unknown error, then it rethrows it in the line `(**)`. The `catch` only knows how to handle validation and syntax errors, other kinds (due to a typo in the code or such) should fall through. +Также важно, что если `catch` встречает неизвестную ошибку, то он пробрасывает её в строке `(**)`. Блок`catch` знает, только как обрабатывать ошибки валидации и синтаксические ошибки, а другие виды ошибок (из-за опечаток в коде и другие непонятные) он должен выпустить наружу. -## Further inheritance +## Дальнейшее наследование -The `ValidationError` class is very generic. Many things may go wrong. The property may be absent or it may be in a wrong format (like a string value for `age`). Let's make a more concrete class `PropertyRequiredError`, exactly for absent properties. It will carry additional information about the property that's missing. +Класс `ValidationError` является слишком общим. Много что может пойти не так. Свойство может отсутствовать или иметь неверный формат (например, строка как значение возраста `age`). Поэтому для отсутствующих свойств сделаем более конкретный класс `PropertyRequiredError`. Он будет нести дополнительную информацию о свойстве, которое отсутствует. ```js run class ValidationError extends Error { @@ -143,14 +140,14 @@ class ValidationError extends Error { *!* class PropertyRequiredError extends ValidationError { constructor(property) { - super("No property: " + property); + super("Нет свойства: " + property); this.name = "PropertyRequiredError"; this.property = property; } } */!* -// Usage +// Применение function readUser(json) { let user = JSON.parse(json); @@ -164,32 +161,32 @@ function readUser(json) { return user; } -// Working example with try..catch +// Рабочий пример с try..catch try { let user = readUser('{ "age": 25 }'); } catch (err) { if (err instanceof ValidationError) { *!* - alert("Invalid data: " + err.message); // Invalid data: No property: name + alert("Неверные данные: " + err.message); // Неверные данные: Нет свойства: name alert(err.name); // PropertyRequiredError alert(err.property); // name */!* } else if (err instanceof SyntaxError) { - alert("JSON Syntax Error: " + err.message); + alert("Ошибка синтаксиса JSON: " + err.message); } else { - throw err; // unknown error, rethrow it + throw err; // неизвестная ошибка, повторно выбросит исключение } } ``` -The new class `PropertyRequiredError` is easy to use: we only need to pass the property name: `new PropertyRequiredError(property)`. The human-readable `message` is generated by the constructor. +Новый класс `PropertyRequiredError` очень просто использовать: необходимо указать только имя свойства `new PropertyRequiredError(property)`. Сообщение для пользователя `message` генерируется конструктором. -Please note that `this.name` in `PropertyRequiredError` constructor is again assigned manually. That may become a bit tedious -- to assign `this.name = ` when creating each custom error. But there's a way out. We can make our own "basic error" class that removes this burden from our shoulders by using `this.constructor.name` for `this.name` in the constructor. And then inherit from it. +Обратите внимание, что свойство `this.name` в конструкторе `PropertyRequiredError` снова присвоено вручную. Правда, немного утомительно -- присваивать `this.name = ` в каждом классе пользовательской ошибки. Можно этого избежать, если сделать наш собственный "базовый" класс ошибки, который будет ставить `this.name = this.constructor.name`. И затем наследовать все ошибки уже от него. -Let's call it `MyError`. +Давайте назовём его `MyError`. -Here's the code with `MyError` and other custom error classes, simplified: +Вот упрощённый код с `MyError` и другими пользовательскими классами ошибок: ```js run class MyError extends Error { @@ -205,28 +202,31 @@ class ValidationError extends MyError { } class PropertyRequiredError extends ValidationError { constructor(property) { - super("No property: " + property); + super("Нет свойства: " + property); this.property = property; } } -// name is correct +// name корректное alert( new PropertyRequiredError("field").name ); // PropertyRequiredError ``` -Now custom errors are much shorter, especially `ValidationError`, as we got rid of the `"this.name = ..."` line in the constructor. +Теперь пользовательские ошибки стали намного короче, особенно `ValidationError`, +так как мы избавились от строки `"this.name = ..."` в конструкторе. + +## Обёртывание исключений -## Wrapping exceptions +Назначение функции `readUser` в приведённом выше коде - это "чтение данных пользователя". В процессе могут возникнуть различные виды ошибок. Сейчас у нас есть `SyntaxError` и `ValidationError`, но в будущем функция `readUser` может расшириться и, возможно, генерировать другие виды ошибок. -The purpose of the function `readUser` in the code above is "to read the user data", right? There may occur different kinds of errors in the process. Right now we have `SyntaxError` and `ValidationError`, but in the future `readUser` function may grow: the new code will probably generate other kinds of errors. +Код, который вызывает `readUser`, должен обрабатывать эти ошибки. -The code which calls `readUser` should handle these errors. Right now it uses multiple `if` in the `catch` block to check for different error types and rethrow the unknown ones. But if `readUser` function generates several kinds of errors -- then we should ask ourselves: do we really want to check for all error types one-by-one in every code that calls `readUser`? +Сейчас в нём используются проверки `if` в блоке `catch`, которые проверяют класс и обрабатывают известные ошибки и пробрасывают дальше неизвестные. Но если функция `readUser` генерирует несколько видов ошибок, то мы должны спросить себя: действительно ли мы хотим проверять все типы ошибок поодиночке во всех местах в коде, где вызывается `readUser`? -Often the answer is "No": the outer code wants to be "one level above all that". It wants to have some kind of "data reading error". Why exactly it happened -- is often irrelevant (the error message describes it). Or, even better if there is a way to get error details, but only if we need to. +Часто ответ "Нет": внешний код хочет быть на один уровень выше всего этого. Он хочет иметь какую-то обобщённую ошибку чтения данных. Почему именно это произошло -- часто не имеет значения (об этом говорится в сообщении об ошибке). Или даже лучше, если есть способ получить подробности об ошибке, но только если нам это нужно. -So let's make a new class `ReadError` to represent such errors. If an error occurs inside `readUser`, we'll catch it there and generate `ReadError`. We'll also keep the reference to the original error in its `cause` property. Then the outer code will only have to check for `ReadError`. +Итак, давайте создадим новый класс `ReadError` для представления таких ошибок. Если ошибка возникает внутри `readUser`, мы её перехватим и сгенерируем `ReadError`. Мы также сохраним ссылку на исходную ошибку в свойстве `cause`. Тогда внешний код должен будет только проверить наличие `ReadError`. -Here's the code that defines `ReadError` and demonstrates its use in `readUser` and `try..catch`: +Этот код определяет ошибку `ReadError` и демонстрирует её использование в `readUser`и `try..catch`: ```js run class ReadError extends Error { @@ -258,7 +258,7 @@ function readUser(json) { } catch (err) { *!* if (err instanceof SyntaxError) { - throw new ReadError("Syntax Error", err); + throw new ReadError("Синтаксическая ошибка", err); } else { throw err; } @@ -270,7 +270,7 @@ function readUser(json) { } catch (err) { *!* if (err instanceof ValidationError) { - throw new ReadError("Validation Error", err); + throw new ReadError("Ошибка валидации", err); } else { throw err; } @@ -285,8 +285,8 @@ try { if (e instanceof ReadError) { *!* alert(e); - // Original error: SyntaxError: Unexpected token b in JSON at position 1 - alert("Original error: " + e.cause); + // Исходная ошибка: SyntaxError:Unexpected token b in JSON at position 1 + alert("Исходная ошибка: " + e.cause); */!* } else { throw e; @@ -294,14 +294,14 @@ try { } ``` -In the code above, `readUser` works exactly as described -- catches syntax and validation errors and throws `ReadError` errors instead (unknown errors are rethrown as usual). +В приведённом выше коде `readUser` работает так, как описано - функция распознаёт синтаксические ошибки и ошибки валидации и выдаёт вместо них ошибки `ReadError` (неизвестные ошибки, как обычно, пробрасываются). -So the outer code checks `instanceof ReadError` and that's it. No need to list possible all error types. +Внешний код проверяет только `instanceof ReadError`. Не нужно перечислять все возможные типы ошибок -The approach is called "wrapping exceptions", because we take "low level exceptions" and "wrap" them into `ReadError` that is more abstract and more convenient to use for the calling code. It is widely used in object-oriented programming. +Этот подход называется "обёртывание исключений", потому что мы берём "исключения низкого уровня" и "оборачиваем" их в `ReadError`, который является более абстрактным и более удобным для использования в вызывающем коде. Такой подход широко используется в объектно-ориентированном программировании. -## Summary +## Итого -- We can inherit from `Error` and other built-in error classes normally, just need to take care of `name` property and don't forget to call `super`. -- Most of the time, we should use `instanceof` to check for particular errors. It also works with inheritance. But sometimes we have an error object coming from the 3rd-party library and there's no easy way to get the class. Then `name` property can be used for such checks. -- Wrapping exceptions is a widespread technique when a function handles low-level exceptions and makes a higher-level object to report about the errors. Low-level exceptions sometimes become properties of that object like `err.cause` in the examples above, but that's not strictly required. +- Мы можем наследовать свои классы ошибок от `Error` и других встроенных классов ошибок, но нужно позаботиться о свойстве `name` и не забыть вызвать `super`. +- Мы можем использовать `instanceof` для проверки типа ошибок. Это также работает с наследованием. Но иногда у нас объект ошибки, возникшей в сторонней библиотеке, и нет простого способа получить класс. Тогда для проверки типа ошибки можно использовать свойство `name`. +- Обёртывание исключений является распространённой техникой: функция ловит низкоуровневые исключения и создаёт одно "высокоуровневое" исключение вместо разных низкоуровневых. Иногда низкоуровневые исключения становятся свойствами этого объекта, как `err.cause` в примерах выше, но это не обязательно. diff --git a/1-js/10-error-handling/index.md b/1-js/10-error-handling/index.md index face61c6e1..815f5a1539 100644 --- a/1-js/10-error-handling/index.md +++ b/1-js/10-error-handling/index.md @@ -1 +1 @@ -# Error handling +# Обработка ошибок diff --git a/1-js/11-async/01-callbacks/01-animate-circle-callback/solution.view/index.html b/1-js/11-async/01-callbacks/01-animate-circle-callback/solution.view/index.html deleted file mode 100644 index b2192681c9..0000000000 --- a/1-js/11-async/01-callbacks/01-animate-circle-callback/solution.view/index.html +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - - - - - - - - - - - - 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 4a20ca6047..0000000000 --- a/1-js/11-async/01-callbacks/01-animate-circle-callback/task.md +++ /dev/null @@ -1,25 +0,0 @@ - -# Animated circle with callback - -In the task an animated growing circle is shown. - -Now let's say we need not just a circle, but to show a message inside it. The message should appear *after* the animation is complete (the circle is fully grown), otherwise it would look ugly. - -In the solution of the task, the function `showCircle(cx, cy, radius)` draws the circle, but gives no way to track when it's ready. - -Add a callback argument: `showCircle(cx, cy, radius, callback)` to be called when the animation is complete. The `callback` should receive the circle `

` as an argument. - -Here's the example: - -```js -showCircle(150, 150, 100, div => { - div.classList.add('message-ball'); - div.append("Hello, world!"); -}); -``` - -Demo: - -[iframe src="solution" height=260] - -Take the solution of the task as the base. diff --git a/1-js/11-async/01-callbacks/article.md b/1-js/11-async/01-callbacks/article.md index ee0bb3caaf..87f7e85465 100644 --- a/1-js/11-async/01-callbacks/article.md +++ b/1-js/11-async/01-callbacks/article.md @@ -1,10 +1,17 @@ +# Введение: колбэки -# Introduction: callbacks +```warn header="В примерах мы будем использовать браузерные методы" +Для демонстрации использования колбэков, промисов и других абстрактных понятий мы будем использовать некоторые браузерные методы: в частности, загрузку скриптов и выполнение простых манипуляций с документом. -Many actions in JavaScript are *asynchronous*. +Если вы не знакомы с этими методами, и их использование в примерах вызывает у вас недоумение, возможно, вам стоит прочитать несколько глав из [следующей части](/document) учебника. -For instance, take a look at the function `loadScript(src)`: +Тем не менее, мы все равно попытаемся максимально доходчиво всё разъяснить. Ничего особо сложного в плане браузера не будет. +``` + +Многие действия в JavaScript *асинхронные*. + +Например, рассмотрим функцию `loadScript(src)`: ```js function loadScript(src) { @@ -14,67 +21,68 @@ function loadScript(src) { } ``` -The purpose of the function is to load a new script. When it adds the ` возвратом значениявозвратом промисаошибкойstate: "fulfilled" result: valuestate: "rejected" result: error...с результатом нового промиса...state: "pending" result: undefinedвызов .then(handler) всегда возвращает промис:если handler заканчивается…этот промис завершается с: \ No newline at end of file diff --git a/1-js/11-async/03-promise-chaining/promise-handler-variants@2x.png b/1-js/11-async/03-promise-chaining/promise-handler-variants@2x.png deleted file mode 100644 index 313e241cc9..0000000000 Binary files a/1-js/11-async/03-promise-chaining/promise-handler-variants@2x.png and /dev/null differ diff --git a/1-js/11-async/03-promise-chaining/promise-then-chain.png b/1-js/11-async/03-promise-chaining/promise-then-chain.png deleted file mode 100644 index fab8a92ad4..0000000000 Binary files a/1-js/11-async/03-promise-chaining/promise-then-chain.png and /dev/null differ diff --git a/1-js/11-async/03-promise-chaining/promise-then-chain.svg b/1-js/11-async/03-promise-chaining/promise-then-chain.svg new file mode 100644 index 0000000000..fb60142fbd --- /dev/null +++ b/1-js/11-async/03-promise-chaining/promise-then-chain.svg @@ -0,0 +1 @@ +.thennew Promiseresolve(1)return 2.thenreturn 4.then \ No newline at end of file diff --git a/1-js/11-async/03-promise-chaining/promise-then-chain@2x.png b/1-js/11-async/03-promise-chaining/promise-then-chain@2x.png deleted file mode 100644 index 421b593599..0000000000 Binary files a/1-js/11-async/03-promise-chaining/promise-then-chain@2x.png and /dev/null differ diff --git a/1-js/11-async/03-promise-chaining/promise-then-many.png b/1-js/11-async/03-promise-chaining/promise-then-many.png deleted file mode 100644 index b13d20a9ef..0000000000 Binary files a/1-js/11-async/03-promise-chaining/promise-then-many.png and /dev/null differ diff --git a/1-js/11-async/03-promise-chaining/promise-then-many.svg b/1-js/11-async/03-promise-chaining/promise-then-many.svg new file mode 100644 index 0000000000..8fea7beaaf --- /dev/null +++ b/1-js/11-async/03-promise-chaining/promise-then-many.svg @@ -0,0 +1 @@ +.thennew Promiseresolve(1).then.then \ No newline at end of file diff --git a/1-js/11-async/03-promise-chaining/promise-then-many@2x.png b/1-js/11-async/03-promise-chaining/promise-then-many@2x.png deleted file mode 100644 index f0609c9467..0000000000 Binary files a/1-js/11-async/03-promise-chaining/promise-then-many@2x.png and /dev/null differ diff --git a/1-js/11-async/04-promise-error-handling/01-error-async/solution.md b/1-js/11-async/04-promise-error-handling/01-error-async/solution.md index 0d43f55e06..4356079f2d 100644 --- a/1-js/11-async/04-promise-error-handling/01-error-async/solution.md +++ b/1-js/11-async/04-promise-error-handling/01-error-async/solution.md @@ -1,4 +1,4 @@ -The answer is: **no, it won't**: +Ответ: **нет, не выполнится**: ```js run new Promise(function(resolve, reject) { @@ -8,6 +8,6 @@ new Promise(function(resolve, reject) { }).catch(alert); ``` -As said in the chapter, there's an "implicit `try..catch`" around the function code. So all synchronous errors are handled. +Как было сказано в главе, здесь присутствует "скрытый `try..catch`" вокруг кода функции. Поэтому обрабатываются все синхронные ошибки. -But here the error is generated not while the executor is running, but later. So the promise can't handle it. +В данном примере ошибка генерируется не по ходу выполнения кода, а позже. Поэтому промис не может обработать её. diff --git a/1-js/11-async/04-promise-error-handling/01-error-async/task.md b/1-js/11-async/04-promise-error-handling/01-error-async/task.md index bafc47ce94..1fbaa9c1e1 100644 --- a/1-js/11-async/04-promise-error-handling/01-error-async/task.md +++ b/1-js/11-async/04-promise-error-handling/01-error-async/task.md @@ -1,6 +1,6 @@ -# Error in setTimeout +# Ошибка в setTimeout -What do you think? Will the `.catch` trigger? Explain your answer. +Что вы думаете? Выполнится ли `.catch`? Поясните свой ответ. ```js new Promise(function(resolve, reject) { diff --git a/1-js/11-async/04-promise-error-handling/article.md b/1-js/11-async/04-promise-error-handling/article.md index 479de3deb5..fe0488bdde 100644 --- a/1-js/11-async/04-promise-error-handling/article.md +++ b/1-js/11-async/04-promise-error-handling/article.md @@ -1,31 +1,21 @@ -# Error handling with promises +# Промисы: обработка ошибок -Asynchronous actions may sometimes fail: in case of an error the corresponding promise becomes rejected. For instance, `fetch` fails if the remote server is not available. We can use `.catch` to handle errors (rejections). +Цепочки промисов отлично подходят для перехвата ошибок. Если промис завершается с ошибкой, то управление переходит в ближайший обработчик ошибок. На практике это очень удобно. -Promise chaining is great at that aspect. When a promise rejects, the control jumps to the closest rejection handler down the chain. That's very convenient in practice. - -For instance, in the code below the URL is wrong (no such server) and `.catch` handles the error: +Например, в представленном ниже примере для `fetch` указана неправильная ссылка (сайт не существует), и `.catch` перехватывает ошибку: ```js run *!* -fetch('https://no-such-server.blabla') // rejects +fetch('https://no-such-server.blabla') // ошибка */!* .then(response => response.json()) - .catch(err => alert(err)) // TypeError: failed to fetch (the text may vary) + .catch(err => alert(err)) // TypeError: failed to fetch (текст может отличаться) ``` -Or, maybe, everything is all right with the server, but the response is not valid JSON: +Как видно, `.catch` не обязательно должен быть сразу после ошибки, он может быть далее, после одного или даже нескольких `.then` -```js run -fetch('/') // fetch works fine now, the server responds successfully -*!* - .then(response => response.json()) // rejects: the page is HTML, not a valid json -*/!* - .catch(err => alert(err)) // SyntaxError: Unexpected token < in JSON at position 0 -``` - -The easiest way to catch all errors is to append `.catch` to the end of chain: +Или, может быть, с сервером всё в порядке, но в ответе мы получим некорректный JSON. Самый лёгкий путь перехватить все ошибки - это добавить `.catch` в конец цепочки: ```js run fetch('/article/promise-chaining/user.json') @@ -48,296 +38,168 @@ fetch('/article/promise-chaining/user.json') */!* ``` -Normally, `.catch` doesn't trigger at all, because there are no errors. But if any of the promises above rejects (a network problem or invalid json or whatever), then it would catch it. +Если все в порядке, то такой `.catch` вообще не выполнится. Но если любой из промисов будет отклонён (проблемы с сетью или некорректная json-строка, или что угодно другое), то ошибка будет перехвачена. -## Implicit try..catch +## Неявный try..catch -The code of a promise executor and promise handlers has an "invisible `try..catch`" around it. If an error happens, it gets caught and treated as a rejection. +Вокруг функции промиса и обработчиков находится "невидимый `try..catch`". Если происходит исключение, то оно перехватывается, и промис считается отклонённым с этой ошибкой. -For instance, this code: +Например, этот код: ```js run new Promise((resolve, reject) => { *!* - throw new Error("Whoops!"); + throw new Error("Ошибка!"); */!* -}).catch(alert); // Error: Whoops! +}).catch(alert); // Error: Ошибка! ``` -...Works exactly the same as this: +...Работает так же, как и этот: ```js run new Promise((resolve, reject) => { *!* - reject(new Error("Whoops!")); + reject(new Error("Ошибка!")); */!* -}).catch(alert); // Error: Whoops! +}).catch(alert); // Error: Ошибка! ``` -The "invisible `try..catch`" around the executor automatically catches the error and treats it as a rejection. +"Невидимый `try..catch`" вокруг промиса автоматически перехватывает ошибку и превращает её в отклонённый промис. -This happens not only in the executor, but in its handlers as well. If we `throw` inside a `.then` handler, that means a rejected promise, so the control jumps to the nearest error handler. +Это работает не только в функции промиса, но и в обработчиках. Если мы бросим ошибку (`throw`) из обработчика (`.then`), то промис будет считаться отклонённым, и управление перейдёт к ближайшему обработчику ошибок. -Here's an example: +Пример: ```js run new Promise((resolve, reject) => { - resolve("ok"); + resolve("ок"); }).then((result) => { *!* - throw new Error("Whoops!"); // rejects the promise + throw new Error("Ошибка!"); // генерируем ошибку */!* -}).catch(alert); // Error: Whoops! +}).catch(alert); // Error: Ошибка! ``` -This happens for all errors, not just those caused by the `throw` statement. For example, a programming error: +Это происходит для всех ошибок, не только для тех, которые вызваны оператором `throw`. Например, программная ошибка: ```js run new Promise((resolve, reject) => { - resolve("ok"); + resolve("ок"); }).then((result) => { *!* - blabla(); // no such function + blabla(); // нет такой функции */!* }).catch(alert); // ReferenceError: blabla is not defined ``` -The final `.catch` not only catches explicit rejections, but also occasional errors in the handlers above. +Финальный `.catch` перехватывает как промисы, в которых вызван `reject`, так и случайные ошибки в обработчиках. -## Rethrowing +## Пробрасывание ошибок -As we already noticed, `.catch` behaves like `try..catch`. We may have as many `.then` handlers as we want, and then use a single `.catch` at the end to handle errors in all of them. +Как мы уже заметили, `.catch` ведёт себя как `try..catch`. Мы можем иметь столько обработчиков `.then`, сколько мы хотим, и затем использовать один `.catch` в конце, чтобы перехватить ошибки из всех обработчиков. -In a regular `try..catch` we can analyze the error and maybe rethrow it if can't handle. The same thing is possible for promises. +В обычном `try..catch` мы можем проанализировать ошибку и повторно пробросить дальше, если не можем её обработать. То же самое возможно для промисов. -If we `throw` inside `.catch`, then the control goes to the next closest error handler. And if we handle the error and finish normally, then it continues to the closest successful `.then` handler. +Если мы пробросим (`throw`) ошибку внутри блока `.catch`, то управление перейдёт к следующему ближайшему обработчику ошибок. А если мы обработаем ошибку и завершим работу обработчика нормально, то продолжит работу ближайший успешный обработчик `.then`. -In the example below the `.catch` successfully handles the error: +В примере ниже `.catch` успешно обрабатывает ошибку: ```js run // the execution: catch -> then new Promise((resolve, reject) => { - throw new Error("Whoops!"); + throw new Error("Ошибка!"); -}).catch(function(error) { +}).catch(function(error) { - alert("The error is handled, continue normally"); + alert("Ошибка обработана, продолжить работу"); -}).then(() => alert("Next successful handler runs")); +}).then(() => alert("Управление перейдёт в следующий then")); ``` -Here the `.catch` block finishes normally. So the next successful `.then` handler is called. +Здесь блок `.catch` завершается нормально. Поэтому вызывается следующий успешный обработчик `.then`. -In the example below we see the other situation with `.catch`. The handler `(*)` catches the error and just can't handle it (e.g. it only knows how to handle `URIError`), so it throws it again: +В примере ниже мы видим другую ситуацию с блоком `.catch`. Обработчик `(*)` перехватывает ошибку и не может обработать её (например, он знает как обработать только `URIError`), поэтому ошибка пробрасывается далее: ```js run // the execution: catch -> catch -> then new Promise((resolve, reject) => { - throw new Error("Whoops!"); + throw new Error("Ошибка!"); }).catch(function(error) { // (*) if (error instanceof URIError) { - // handle it + // обрабатываем ошибку } else { - alert("Can't handle such error"); + alert("Не могу обработать ошибку"); *!* - throw error; // throwing this or another error jumps to the next catch + throw error; // пробрасывает эту или другую ошибку в следующий catch */!* } }).then(function() { - /* never runs here */ + /* не выполнится */ }).catch(error => { // (**) - alert(`The unknown error has occurred: ${error}`); - // don't return anything => execution goes the normal way + alert(`Неизвестная ошибка: ${error}`); + // ничего не возвращаем => выполнение продолжается в нормальном режиме }); ``` -Then the execution jumps from the first `.catch` `(*)` to the next one `(**)` down the chain. - -In the section below we'll see a practical example of rethrowing. - -## Fetch error handling example - -Let's improve error handling for the user-loading example. - -The promise returned by [fetch](mdn:api/WindowOrWorkerGlobalScope/fetch) rejects when it's impossible to make a request. For instance, a remote server is not available, or the URL is malformed. But if the remote server responds with error 404, or even error 500, then it's considered a valid response. - -What if the server returns a non-JSON page with error 500 in the line `(*)`? What if there's no such user, and GitHub returns a page with error 404 at `(**)`? - -```js run -fetch('no-such-user.json') // (*) - .then(response => response.json()) - .then(user => fetch(`https://api.github.com/users/${user.name}`)) // (**) - .then(response => response.json()) - .catch(alert); // SyntaxError: Unexpected token < in JSON at position 0 - // ... -``` - - -As of now, the code tries to load the response as JSON no matter what and dies with a syntax error. You can see that by running the example above, as the file `no-such-user.json` doesn't exist. - -That's not good, because the error just falls through the chain, without details: what failed and where. - -So let's add one more step: we should check the `response.status` property that has HTTP status, and if it's not 200, then throw an error. - -```js run -class HttpError extends Error { // (1) - constructor(response) { - super(`${response.status} for ${response.url}`); - this.name = 'HttpError'; - this.response = response; - } -} - -function loadJson(url) { // (2) - return fetch(url) - .then(response => { - if (response.status == 200) { - return response.json(); - } else { - throw new HttpError(response); - } - }) -} - -loadJson('no-such-user.json') // (3) - .catch(alert); // HttpError: 404 for .../no-such-user.json -``` - -1. We make a custom class for HTTP Errors to distinguish them from other types of errors. Besides, the new class has a constructor that accepts `response` object and saves it in the error. So error-handling code will be able to access it. -2. Then we put together the requesting and error-handling code into a function that fetches the `url` *and* treats any non-200 status as an error. That's convenient, because we often need such logic. -3. Now `alert` shows a more helpful descriptive message. - -The great thing about having our own class for errors is that we can easily check for it in error-handling code. +Управление переходит от первого блока `.catch` `(*)` к следующему `(**)`, вниз по цепочке. -For instance, we can make a request, and then if we get 404 -- ask the user to modify the information. +## Необработанные ошибки -The code below loads a user with the given name from GitHub. If there's no such user, then it asks for the correct name: - -```js run -function demoGithubUser() { - let name = prompt("Enter a name?", "iliakan"); - - return loadJson(`https://api.github.com/users/${name}`) - .then(user => { - alert(`Full name: ${user.name}.`); - return user; - }) - .catch(err => { -*!* - if (err instanceof HttpError && err.response.status == 404) { -*/!* - alert("No such user, please reenter."); - return demoGithubUser(); - } else { - throw err; // (*) - } - }); -} - -demoGithubUser(); -``` - -Please note: `.catch` here catches all errors, but it "knows how to handle" only `HttpError 404`. In that particular case it means that there's no such user, and `.catch` just retries in that case. - -For other errors, it has no idea what could go wrong. Maybe a programming error or something. So it just rethrows it in the line `(*)`. - -## Unhandled rejections - -What happens when an error is not handled? For instance, after the rethrow `(*)` in the example above. - -Or we could just forget to append an error handler to the end of the chain, like here: +Что произойдёт, если ошибка не будет обработана? Например, мы просто забыли добавить `.catch` в конец цепочки, как здесь: ```js untrusted run refresh new Promise(function() { - noSuchFunction(); // Error here (no such function) + noSuchFunction(); // Ошибка (нет такой функции) }) .then(() => { - // zero or many promise handlers - }); // without .catch at the end! + // обработчики .then, один или более + }); // без .catch в самом конце! ``` -In case of an error, the promise state becomes "rejected", and the execution should jump to the closest rejection handler. But there is no such handler in the examples above. So the error gets "stuck". +В случае ошибки выполнение должно перейти к ближайшему обработчику ошибок. Но в примере выше нет никакого обработчика. Поэтому ошибка как бы "застревает", её некому обработать. -In practice, just like with a regular unhandled errors, it means that something has terribly gone wrong, the script probably died. +На практике, как и при обычных необработанных ошибках в коде, это означает, что что-то пошло сильно не так. -Most JavaScript engines track such situations and generate a global error in that case. We can see it in the console. +Что происходит, когда обычная ошибка не перехвачена `try..catch`? Скрипт умирает с сообщением в консоли. Похожее происходит и в случае необработанной ошибки промиса. -In the browser we can catch such errors using the event `unhandledrejection`: +JavaScript-движок отслеживает такие ситуации и генерирует в этом случае глобальную ошибку. Вы можете увидеть её в консоли, если запустите пример выше. + +В браузере мы можем поймать такие ошибки, используя событие `unhandledrejection`: ```js run *!* window.addEventListener('unhandledrejection', function(event) { - // the event object has two special properties: - alert(event.promise); // [object Promise] - the promise that generated the error - alert(event.reason); // Error: Whoops! - the unhandled error object + // объект события имеет два специальных свойства: + alert(event.promise); // [object Promise] - промис, который сгенерировал ошибку + alert(event.reason); // Error: Ошибка! - объект ошибки, которая не была обработана }); */!* new Promise(function() { - throw new Error("Whoops!"); -}); // no catch to handle the error + throw new Error("Ошибка!"); +}); // нет обработчика ошибок ``` -The event is the part of the [HTML standard](https://html.spec.whatwg.org/multipage/webappapis.html#unhandled-promise-rejections). - -If an error occurs, and there's no `.catch`, the `unhandledrejection` handler triggers, and gets the `event` object with the information about the error, so we can do something. - -Usually such errors are unrecoverable, so our best way out is to inform the user about the problem and probably report the incident to the server. +Это событие является частью [стандарта HTML](https://html.spec.whatwg.org/multipage/webappapis.html#unhandled-promise-rejections). -In non-browser environments like Node.js there are other similar ways to track unhandled errors. +Если происходит ошибка, и отсутствует её обработчик, то генерируется событие `unhandledrejection`, и соответствующий объект `event` содержит информацию об ошибке. +Обычно такие ошибки неустранимы, поэтому лучше всего - информировать пользователя о проблеме и, возможно, отправить информацию об ошибке на сервер. -## Summary - -- `.catch` handles promise rejections of all kinds: be it a `reject()` call, or an error thrown in a handler. -- We should place `.catch` exactly in places where we want to handle errors and know how to handle them. The handler should analyze errors (custom error classes help) and rethrow unknown ones. -- It's normal not to use `.catch` if we don't know how to handle errors (all errors are unrecoverable). -- In any case we should have the `unhandledrejection` event handler (for browsers, and analogs for other environments), to track unhandled errors and inform the user (and probably our server) about the them, so that our app never "just dies". - -And finally, if we have load-indication, then `.finally` is a great handler to stop it when the fetch is complete: - -```js run -function demoGithubUser() { - let name = prompt("Enter a name?", "iliakan"); - -*!* - document.body.style.opacity = 0.3; // (1) start the indication -*/!* - - return loadJson(`https://api.github.com/users/${name}`) -*!* - .finally(() => { // (2) stop the indication - document.body.style.opacity = ''; - return new Promise(resolve => setTimeout(resolve, 0)); // (*) - }) -*/!* - .then(user => { - alert(`Full name: ${user.name}.`); - return user; - }) - .catch(err => { - if (err instanceof HttpError && err.response.status == 404) { - alert("No such user, please reenter."); - return demoGithubUser(); - } else { - throw err; - } - }); -} - -demoGithubUser(); -``` - -Here on the line `(1)` we indicate loading by dimming the document. The method doesn't matter, could use any type of indication instead. +В не-браузерных средах, таких как Node.js, есть другие способы отслеживания необработанных ошибок. -When the promise is settled, be it a successful fetch or an error, `finally` triggers at the line `(2)` and stops the indication. +## Итого -There's a little browser trick `(*)` with returning a zero-timeout promise from `finally`. That's because some browsers (like Chrome) need "a bit time" outside promise handlers to paint document changes. So it ensures that the indication is visually stopped before going further on the chain. +- `.catch` перехватывает все виды ошибок в промисах: будь то вызов `reject()` или ошибка, брошенная в обработчике при помощи `throw`. +- `.then` также перехватывает ошибки таким же образом, если задан второй аргумент (который является обработчиком ошибок). +- Необходимо размещать `.catch` там, где мы хотим обработать ошибки и знаем, как это сделать. Обработчик может проанализировать ошибку (могут быть полезны пользовательские классы ошибок) и пробросить её, если ничего не знает о ней (возможно, это программная ошибка). +- Можно и совсем не использовать `.catch`, если нет нормального способа восстановиться после ошибки. +- В любом случае нам следует использовать обработчик события `unhandledrejection` (для браузеров и аналог для других окружений), чтобы отслеживать необработанные ошибки и информировать о них пользователя (и, возможно, наш сервер), благодаря чему наше приложение никогда не будет "просто умирать". diff --git a/1-js/11-async/04-promise-error-handling/head.html b/1-js/11-async/04-promise-error-handling/head.html index 31c6b42713..a0b7419623 100644 --- a/1-js/11-async/04-promise-error-handling/head.html +++ b/1-js/11-async/04-promise-error-handling/head.html @@ -1,16 +1,4 @@ - - - diff --git a/1-js/11-async/05-promise-api/01-promise-errors-as-results/source.view/index.html b/1-js/11-async/05-promise-api/01-promise-errors-as-results/source.view/index.html deleted file mode 100644 index c760ad8cd5..0000000000 --- a/1-js/11-async/05-promise-api/01-promise-errors-as-results/source.view/index.html +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - diff --git a/1-js/11-async/05-promise-api/01-promise-errors-as-results/task.md b/1-js/11-async/05-promise-api/01-promise-errors-as-results/task.md deleted file mode 100644 index 9adbf0808b..0000000000 --- a/1-js/11-async/05-promise-api/01-promise-errors-as-results/task.md +++ /dev/null @@ -1,48 +0,0 @@ -# Отказоустойчивый Promise.all - -Мы бы хотели загрузить несколько ссылок параллельно. - -Как можно это сделать: - -```js run -let urls = [ - 'https://api.github.com/users/iliakan', - 'https://api.github.com/users/remy', - 'https://api.github.com/users/jeresig' -]; - -Promise.all(urls.map(url => fetch(url))) - // for each response show its status - .then(responses => { // (*) - for(let response of responses) { - alert(`${response.url}: ${response.status}`); - } - }); -``` - -Проблема в том, что если какой-нибудь промис завершится ошибкой, то `Promise.all` вернёт эту ошибку и мы потеряем уже загруженные ссылки. - -Это не хорошо. - -Исправьте код так, чтобы массив `responses` в строке `(*)` включал в себя объекты с успешно выполненными запросами и объекты с ошибками. - -Например, если одна из указанных ссылок некорректная, то результат должен быть примерно таким: - -```js -let urls = [ - 'https://api.github.com/users/iliakan', - 'https://api.github.com/users/remy', - 'http://no-such-url' -]; - -Promise.all(...) // код для получения данных с указанных ссылок - // ...и передаём полученные ошибки в качестве элементов массива... - .then(responses => { - // 3 ссылки => 3 элемента массива - alert(responses[0].status); // 200 - alert(responses[1].status); // 200 - alert(responses[2]); // TypeError: failed to fetch (сообщение ошибки зависит от браузера) - }); -``` - -P.S. В этой задаче вам не надо загружать всю страницу, используя `response.text()` или `response.json()`. Просто обработайте ошибки правильно. diff --git a/1-js/11-async/05-promise-api/02-promise-errors-as-results-2/solution.view/index.html b/1-js/11-async/05-promise-api/02-promise-errors-as-results-2/solution.view/index.html deleted file mode 100644 index 744efd2b76..0000000000 --- a/1-js/11-async/05-promise-api/02-promise-errors-as-results-2/solution.view/index.html +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - diff --git a/1-js/11-async/05-promise-api/02-promise-errors-as-results-2/source.view/index.html b/1-js/11-async/05-promise-api/02-promise-errors-as-results-2/source.view/index.html deleted file mode 100644 index adb86d41fe..0000000000 --- a/1-js/11-async/05-promise-api/02-promise-errors-as-results-2/source.view/index.html +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - diff --git a/1-js/11-async/05-promise-api/02-promise-errors-as-results-2/task.md b/1-js/11-async/05-promise-api/02-promise-errors-as-results-2/task.md deleted file mode 100644 index c1d461645e..0000000000 --- a/1-js/11-async/05-promise-api/02-promise-errors-as-results-2/task.md +++ /dev/null @@ -1,34 +0,0 @@ -# Отказоустойчивая загрузка с получением результата в JSON - -Необходимо улучшить решение из предыдущей задачи . Сейчас нам надо не просто вызвать `fetch`, но и загрузить JSON-объекты из указанных ссылок. - -Как можно это сделать: - -```js run -let urls = [ - 'https://api.github.com/users/iliakan', - 'https://api.github.com/users/remy', - 'https://api.github.com/users/jeresig' -]; - -// выполнить запросы -Promise.all(urls.map(url => fetch(url))) - // обработать каждый ответ, используя response.json() - .then(responses => Promise.all( - responses.map(r => r.json()) - )) - // показать имя каждого пользователя - .then(users => { // (*) - for(let user of users) { - alert(user.name); - } - }); -``` - -Проблема - если любой из запросов выполнится с ошибкой, то `Promise.all` вернёт эту ошибку и мы потеряем результаты со всех других запросов. Поэтому код, приведенных выше, не является отказоустойчивым, как и в предыдущей задаче. - -Измените код так, чтобы массив в строке `(*)` включал бы в себя обработанный JSON для успешно выполненных запросов и ошибки для ошибочных. - -Обратите внимание, что ошибки могут быть как при загрузке ссылки в `fetch` (если запрос не выполнился), так и в `response.json()` (если ответ содержит некорректный JSON). В обоих случаях ошибки должны быть элементами в объекте с результатами. - -В песочнице есть оба этих случая. diff --git a/1-js/11-async/05-promise-api/article.md b/1-js/11-async/05-promise-api/article.md index ccfcfaa299..d50fc9ed44 100644 --- a/1-js/11-async/05-promise-api/article.md +++ b/1-js/11-async/05-promise-api/article.md @@ -1,99 +1,41 @@ # Promise API -В классе `Promise` есть 4 статических метода. Давайте познакомимся с ними. - -## Promise.resolve - -Синтаксис: - -```js -let promise = Promise.resolve(value); -``` - -Возвращает выполненный промис со значением `value`. - -То же самое, что и: - -```js -let promise = new Promise(resolve => resolve(value)); -``` - -`Promise.resolve` используют, когда хотят построить асинхронную цепочку, и начальный результат уже есть. - -Например, нижеприведенная функция `loadCached` получает данные из указанного `url` и сохраняет результат. Поэтому в будущем, когда мы захотим повторно получить данные по этой же ссылке, мы сможем сразу вернуть результат: - -```js -function loadCached(url) { - let cache = loadCached.cache || (loadCached.cache = new Map()); - - if (cache.has(url)) { -*!* - return Promise.resolve(cache.get(url)); // (*) -*/!* - } - - return fetch(url) - .then(response => response.text()) - .then(text => { - cache.set(url,text); - return text; - }); -} -``` - -Мы можем использовать метод `then` для `loadCached`, вот так: `loadCached(url).then(…)`, потому что функция `loadCached` возвращает промис. Это и есть основная цель использования `Promise.resolve` в строке `(*)`: функция гарантированно возвращает унифицированный результат. Мы всегда можем использовать `.then` после `loadCached`. - -## Promise.reject - -Синтаксис: - -```js -let promise = Promise.reject(error); -``` - -Создаёт неудачно выполненный промис с указанной ошибкой (`error`). - -Подробная запись: - -```js -let promise = new Promise((resolve, reject) => reject(error)); -``` - -Мы здесь рассмотрели его для полноты изложения, в реальном коде используется редко. +В классе `Promise` есть 6 статических методов. Давайте познакомимся с ними. ## Promise.all Допустим, нам нужно запустить множество промисов параллельно и дождаться, пока все они выполнятся. -Например, параллельно загрузить несколько ссылок и обработать результат, когда он готов. +Например, параллельно загрузить несколько файлов и обработать результат, когда он готов. Для этого как раз и пригодится `Promise.all`. Синтаксис: ```js -let promise = Promise.all([...promises...]); +let promise = Promise.all(iterable); ``` -Метод `.all` принимает массив промисов (может принимать любой итерируемый объект, но обычно используется массив) и возвращает новый промис. +Метод `Promise.all` принимает массив промисов (может принимать любой перебираемый объект, но обычно используется массив) и возвращает новый промис. -Новый промис выполнится, когда весь переданный список промисов получит результат. +Новый промис завершится, когда завершится весь переданный список промисов, и его результатом будет массив их результатов. -Например, `Promise.all`, представленный ниже, выполнится спустя 3 секунды, результатом работы метода будет массив `[1, 2, 3]`: +Например, `Promise.all`, представленный ниже, выполнится спустя 3 секунды, его результатом будет массив `[1, 2, 3]`: ```js run Promise.all([ new Promise(resolve => setTimeout(() => resolve(1), 3000)), // 1 new Promise(resolve => setTimeout(() => resolve(2), 2000)), // 2 new Promise(resolve => setTimeout(() => resolve(3), 1000)) // 3 -]).then(alert); // когда все промисы выполнятся, результат будет 1,2,3. Каждый промис добавляет результат в массив +]).then(alert); // когда все промисы выполнятся, результат будет 1,2,3 +// каждый промис даёт элемент массива ``` -Обратите внимание, что порядок результатов в точности соответствует порядку промисов. Даже если первый промис будет выполняться дольше всех, он все равно запишет результат в первый элемент массива. +Обратите внимание, что порядок элементов массива в точности соответствует порядку исходных промисов. Даже если первый промис будет выполняться дольше всех, его результат всё равно будет первым в массиве. -Часто применяемый трюк - пропустить массив данных через map-функцию, которая для каждого элемента создаст задачу-промис, и затем обернёт получившийся массив в `Promise.all`. +Часто применяемый трюк - пропустить массив данных через map-функцию, которая для каждого элемента создаст задачу-промис, и затем обернуть получившийся массив в `Promise.all`. -Например, если у нас есть массив ссылок, то мы можем получить результат по ним вот так: +Например, если у нас есть массив ссылок, то мы можем загрузить их вот так: ```js run let urls = [ @@ -102,7 +44,7 @@ let urls = [ 'https://api.github.com/users/jeresig' ]; -// Проходим по каждому элементу массива, запускаем промис (fetch(url)) и добавляем его в массив (requests) +// Преобразуем каждый URL в промис, возвращённый fetch let requests = urls.map(url => fetch(url)); // Promise.all будет ожидать выполнения всех промисов @@ -112,7 +54,7 @@ Promise.all(requests) )); ``` -Более реальный пример - это получить информацию о пользователях GitHub по их логинам из массива (или мы можем получить массив товаров по их идентификаторам, логика та же): +А вот пример побольше, с получением информации о пользователях GitHub по их логинам из массива (мы могли бы получать массив товаров по их идентификаторам, логика та же): ```js run let names = ['iliakan', 'remy', 'jeresig']; @@ -121,20 +63,21 @@ let requests = names.map(name => fetch(`https://api.github.com/users/${name}`)); Promise.all(requests) .then(responses => { - // все промисы выполнены, можно показать код HTTP-состояния + // все промисы успешно завершены for(let response of responses) { alert(`${response.url}: ${response.status}`); // покажет 200 для каждой ссылки } return responses; }) - // формируем массив ответов из response.json(), чтобы прочитать его содержимое + // преобразовать массив ответов response в response.json(), + // чтобы прочитать содержимое каждого .then(responses => Promise.all(responses.map(r => r.json()))) - // после выполнения всех промисов: "users" это массив с результатами + // все JSON-ответы обработаны, users - массив с результатами .then(users => users.forEach(user => alert(user.name))); ``` -Если любой из промисов будет отклонён, то `Promise.all` немедленно вернет эту ошибку. +**Если любой из промисов завершится с ошибкой, то промис, возвращённый `Promise.all`, немедленно завершается с этой ошибкой.** Например: @@ -142,20 +85,24 @@ Promise.all(requests) Promise.all([ new Promise((resolve, reject) => setTimeout(() => resolve(1), 1000)), *!* - new Promise((resolve, reject) => setTimeout(() => reject(new Error("Whoops!")), 2000)), + new Promise((resolve, reject) => setTimeout(() => reject(new Error("Ошибка!")), 2000)), */!* new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000)) -]).catch(alert); // Error: Whoops! +]).catch(alert); // Error: Ошибка! ``` -Тут второй промис будет отклонен через 2 секунды. Это приведет к немедленному отклонению всего `Promise.all`, поэтому выполнится `.catch`: ошибка отклоненного промиса становится результатом всего `Promise.all`. +Здесь второй промис завершится с ошибкой через 2 секунды. Это приведёт к немедленной ошибке в `Promise.all`, так что выполнится `.catch`: ошибка этого промиса становится ошибкой всего `Promise.all`. -Важный момент - промисы не предоставляют способа "отменить" или "прервать" их выполнение. Таким образом, промисы продолжат выполняться, но все результаты будут игнорироваться. +```warn header="В случае ошибки, остальные результаты игнорируются" +Если один промис завершается с ошибкой, то весь `Promise.all` завершается с ней, полностью забывая про остальные промисы в списке. Их результаты игнорируются. -Есть варианты обойти данное ограничение: можно написать дополнительный код для `clearTimeout` (или каким-то другим образом отменить промис) в случае ошибки, либо можно сделать так, чтобы ошибки становились элементами в результирующем массиве (смотрите задачу ниже). +Например, если сделано несколько вызовов `fetch`, как в примере выше, и один не прошёл, то остальные будут всё ещё выполняться, но `Promise.all` за ними уже не смотрит. Скорее всего, они так или иначе завершатся, но их результаты будут проигнорированы. -````smart header="`Promise.all(...)` разрешает передавать НЕ промисы в `iterable` (итерируемом) объекте" -Обычно, `Promise.all(...)` принимает итерируемый (чаще всего массив) объект промисов. Но если любой из этих объектов не является промисом, он оборачивается в `Promise.resolve`. +`Promise.all` ничего не делает для их отмены, так как в промисах вообще нет концепции "отмены". В главе мы рассмотрим `AbortController`, который помогает с этим, но он не является частью Promise API. +``` + +````smart header="`Promise.all(iterable)` разрешает передавать не-промисы в `iterable` (перебираемом объекте)" +Обычно, `Promise.all(...)` принимает перебираемый объект промисов (чаще всего массив). Но если любой из этих объектов не является промисом, он передаётся в итоговый массив "как есть". Например, здесь результат: `[1, 2, 3]` @@ -164,18 +111,101 @@ Promise.all([ new Promise((resolve, reject) => { setTimeout(() => resolve(1), 1000) }), - 2, // обрабатывается как Promise.resolve(2) - 3 // обрабатывается как Promise.resolve(3) + 2, + 3 ]).then(alert); // 1, 2, 3 ``` -Таким образом, мы можем передавать в том числе значения, которые не являются промисами, в `Promise.all`, это бывает удобно. - +Таким образом, мы можем передавать уже готовые значения, которые не являются промисами, в `Promise.all`, иногда это бывает удобно. ```` +## Promise.allSettled + +[recent browser="new"] + +Синтаксис: + +```js +let promise = Promise.allSettled(iterable); +``` + +`Promise.all` завершается с ошибкой, если она возникает в любом из переданных промисов. Это подходит для ситуаций "всё или ничего", когда нам нужны *все* результаты для продолжения: + +```js +Promise.all([ + fetch('/template.html'), + fetch('/style.css'), + fetch('/data.json') +]).then(render); // методу render нужны результаты всех fetch +``` + +Метод `Promise.allSettled` всегда ждёт завершения всех промисов. В массиве результатов будет + +- `{status:"fulfilled", value:результат}` для успешных завершений, +- `{status:"rejected", reason:ошибка}` для ошибок. + +Например, мы хотели бы загрузить информацию о множестве пользователей. Даже если в каком-то запросе ошибка, нас всё равно интересуют остальные. + +Используем для этого `Promise.allSettled`: + +```js run +let urls = [ + 'https://api.github.com/users/iliakan', + 'https://api.github.com/users/remy', + 'https://no-such-url' +]; + +Promise.allSettled(urls.map(url => fetch(url))) + .then(results => { // (*) + results.forEach((result, num) => { + if (result.status == "fulfilled") { + alert(`${urls[num]}: ${result.value.status}`); + } + if (result.status == "rejected") { + alert(`${urls[num]}: ${result.reason}`); + } + }); + }); +``` + +Массив `results` в строке `(*)` будет таким: +```js +[ + {status: 'fulfilled', value: ...объект ответа...}, + {status: 'fulfilled', value: ...объект ответа...}, + {status: 'rejected', reason: ...объект ошибки...} +] +``` + +То есть, для каждого промиса у нас есть его статус и значение/ошибка. + +### Полифил + +Если браузер не поддерживает `Promise.allSettled`, для него легко сделать полифил: + +```js +if(!Promise.allSettled) { + Promise.allSettled = function(promises) { + return Promise.all(promises.map(p => Promise.resolve(p).then(value => ({ + status: 'fulfilled', + value: value + }), error => ({ + status: 'rejected', + reason: error + })))); + }; +} +``` + +В этом коде `promises.map` берёт аргументы, превращает их в промисы (на всякий случай) и добавляет каждому обработчик `.then`. + +Этот обработчик превращает успешный результат `value` в `{state:'fulfilled', value: value}`, а ошибку `error` в `{state:'rejected', reason: error}`. Это как раз и есть формат результатов `Promise.allSettled`. + +Затем мы можем использовать `Promise.allSettled`, чтобы получить результаты *всех* промисов, даже если при выполнении какого-то возникнет ошибка. + ## Promise.race -Метод очень похож на `Promise.all`, он принимает итерируемый объект (массив) промисов, но вместо того, чтобы ждать выполнения всех промисов, он ожидает первый завершенный промис (даже в случае ошибки) и сразу возвращает результат. +Метод очень похож на `Promise.all`, но ждёт только первый *выполненный* промис, из которого берёт результат (или ошибку). Синтаксис: @@ -188,20 +218,116 @@ let promise = Promise.race(iterable); ```js run Promise.race([ new Promise((resolve, reject) => setTimeout(() => resolve(1), 1000)), - new Promise((resolve, reject) => setTimeout(() => reject(new Error("Whoops!")), 2000)), + new Promise((resolve, reject) => setTimeout(() => reject(new Error("Ошибка!")), 2000)), new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000)) ]).then(alert); // 1 ``` -То есть, в отличие от `Promise.all`, результатом будет первый успешно выполнившийся промис из списка или первая появившаяся ошибка. Остальные промисы игнорируются. +Быстрее всех выполнился первый промис, он и дал результат. После этого остальные промисы игнорируются. + +## Promise.any + +Метод очень похож на `Promise.race`, но ждёт только первый *успешно выполненный* промис, из которого берёт результат. + +Если ни один из переданных промисов не завершится успешно, тогда возвращённый объект Promise будет отклонён с помощью `AggregateError` – специального объекта ошибок, который хранит все ошибки промисов в своём свойстве `errors`. + +Синтаксис: + +```js +let promise = Promise.any(iterable); +``` + +Например, здесь, результатом будет `1`: + +```js run +Promise.any([ + new Promise((resolve, reject) => setTimeout(() => reject(new Error("Ошибка!")), 1000)), + new Promise((resolve, reject) => setTimeout(() => resolve(1), 2000)), + new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000)) +]).then(alert); // 1 +``` + +Первый промис в этом примере был самым быстрым, но он был отклонён, поэтому результатом стал второй. После того, как первый успешно выполненный промис "выиграет гонку", все дальнейшие результаты будут проигнорированы. + +Вот пример, в котором все промисы отклоняются: + +```js run +Promise.any([ + new Promise((resolve, reject) => setTimeout(() => reject(new Error("Ошибка!")), 1000)), + new Promise((resolve, reject) => setTimeout(() => reject(new Error("Ещё одна ошибка!")), 2000)) +]).catch(error => { + console.log(error.constructor.name); // AggregateError + console.log(error.errors[0]); // Error: Ошибка! + console.log(error.errors[1]); // Error: Ещё одна ошибка! +}); +``` + +Как вы можете видеть, объекты ошибок для отклонённых промисов доступны в свойстве `errors` объекта `AggregateError`. + +## Promise.resolve/reject + +Методы `Promise.resolve` и `Promise.reject` редко используются в современном коде, так как синтаксис `async/await` (мы рассмотрим его [чуть позже](info:async-await)) делает их, в общем-то, не нужными. + +Мы рассмотрим их здесь для полноты картины, а также для тех, кто по каким-то причинам не может использовать `async/await`. + +### Promise.resolve + +- `Promise.resolve(value)` создаёт успешно выполненный промис с результатом `value`. + +То же самое, что: + +```js +let promise = new Promise(resolve => resolve(value)); +``` + +Этот метод используют для совместимости: когда ожидается, что функция возвратит именно промис. + +Например, функция `loadCached` ниже загружает URL и запоминает (кеширует) его содержимое. При будущих вызовах с тем же URL он тут же читает предыдущее содержимое из кеша, но использует `Promise.resolve`, чтобы сделать из него промис, для того, чтобы возвращаемое значение всегда было промисом: + +```js +let cache = new Map(); + +function loadCached(url) { + if (cache.has(url)) { +*!* + return Promise.resolve(cache.get(url)); // (*) +*/!* + } + + return fetch(url) + .then(response => response.text()) + .then(text => { + cache.set(url,text); + return text; + }); +} +``` + +Мы можем писать `loadCached(url).then(…)`, потому что функция `loadCached` всегда возвращает промис. Мы всегда можем использовать `.then` после `loadCached`. Это и есть цель использования `Promise.resolve` в строке `(*)`. + +### Promise.reject + +- `Promise.reject(error)` создаёт промис, завершённый с ошибкой `error`. + +То же самое, что: + +```js +let promise = new Promise((resolve, reject) => reject(error)); +``` + +На практике этот метод почти никогда не используется. ## Итого -Мы ознакомились с 4 статическими методами класса `Promise`: +Мы ознакомились с шестью статическими методами класса `Promise`: -1. `Promise.resolve(value)` -- возвращает успешно выполнившийся промис, с указанным значением. -2. `Promise.reject(error)` -- возвращает промис с указанной ошибкой. -3. `Promise.all(promises)` -- ожидает выполнения всех промисов и возвращает массив с результатами. Если любой из указанных промисов вернёт ошибку, то результатом работы `Promise.all` будет эта ошибка, результаты остальных промисов будут игнорироваться. -4. `Promise.race(promises)` -- ожидает первый выполненный промис, результатом будет выполнившийся промис или первая ошибка. +1. `Promise.all(promises)` -- ожидает выполнения всех промисов и возвращает массив с результатами. Если любой из указанных промисов вернёт ошибку, то результатом работы `Promise.all` будет эта ошибка, результаты остальных промисов будут игнорироваться. +2. `Promise.allSettled(promises)` (добавлен недавно) -- ждёт, пока все промисы завершатся и возвращает их результаты в виде массива с объектами, у каждого объекта два свойства: + - `status`: `"fulfilled"`, если выполнен успешно или `"rejected"`, если ошибка, + - `value` - результат, если успешно или `reason` - ошибка, если нет. +3. `Promise.race(promises)` -- ожидает первый *выполненный* промис, который становится его результатом, остальные игнорируются. +4. `Promise.any(promises)` (добавлен недавно) -- ожидает первый *успешно выполненный* промис, который становится его результатом, остальные игнорируются. Если все переданные промисы отклонены, [`AggregateError`](mdn:js/AggregateError) становится ошибкой `Promise.any`. +5. `Promise.resolve(value)` -- возвращает успешно выполнившийся промис с результатом `value`. +6. `Promise.reject(error)` -- возвращает промис с ошибкой `error`. -Из всех перечисленных методов, самый часто используемый - это `Promise.all`. +Из всех перечисленных методов, самый часто используемый - это, пожалуй, `Promise.all`. diff --git a/1-js/11-async/06-promisify/article.md b/1-js/11-async/06-promisify/article.md index e72329ce11..a5481112a7 100644 --- a/1-js/11-async/06-promisify/article.md +++ b/1-js/11-async/06-promisify/article.md @@ -1,29 +1,27 @@ -# Promisification +# Промисификация -Promisification -- is a long word for a simple transform. It's conversion of a function that accepts a callback into a function returning a promise. +Промисификация -- это длинное слово для простого преобразования. Мы берём функцию, которая принимает колбэк и меняем её, чтобы она вместо этого возвращала промис. -In other words, we create a wrapper-function that does the same, internally calling the original one, but returns a promise. +Такие преобразования часто необходимы в реальной жизни, так как многие функции и библиотеки основаны на колбэках, а использование промисов более удобно, поэтому есть смысл "промисифицировать" их. -Such transforms are often needed in real-life, as many functions and libraries are callback-based. But promises are more convenient. So it makes sense to promisify those. +Например, у нас есть `loadScript(src, callback)` из главы . -For instance, we have `loadScript(src, callback)` from the chapter . - -```js run +```js function loadScript(src, callback) { let script = document.createElement('script'); script.src = src; script.onload = () => callback(null, script); - script.onerror = () => callback(new Error(`Script load error for ${src}`)); + script.onerror = () => callback(new Error(`Ошибка загрузки скрипта ${src}`)); document.head.append(script); } -// usage: +// использование: // loadScript('path/script.js', (err, script) => {...}) ``` -Let's promisify it. The new `loadScriptPromise(src)` function will do the same, but accept only `src` (no callback) and return a promise. +Давайте промисифицируем её. Новая функция `loadScriptPromise(src)` будет делать то же самое, но будет принимать только `src` (не `callback`) и возвращать промис. ```js let loadScriptPromise = function(src) { @@ -35,60 +33,60 @@ let loadScriptPromise = function(src) { }) } -// usage: +// использование: // loadScriptPromise('path/script.js').then(...) ``` -Now `loadScriptPromise` fits well in our promise-based code. +Теперь `loadScriptPromise` хорошо вписывается в код, основанный на промисах. -As we can see, it delegates all the work to the original `loadScript`, providing its own callback that translates to promise `resolve/reject`. +Как видно, она передаёт всю работу исходной функции `loadScript`, предоставляя ей колбэк, по вызову которого происходит `resolve/reject` промиса. -As we may need to promisify many functions, it makes sense to use a helper. +На практике нам, скорее всего, понадобится промисифицировать не одну функцию, поэтому имеет смысл сделать для этого специальную "функцию-помощник". -That's actually very simple -- `promisify(f)` below takes a to-promisify function `f` and returns a wrapper function. +Мы назовём её `promisify(f)` - она принимает функцию для промисификации `f` и возвращает функцию-обёртку. -That wrapper does the same as in the code above: returns a promise and passes the call to the original `f`, tracking the result in a custom callback: +Эта функция-обёртка делает то же самое, что и код выше: возвращает промис и передаёт вызов оригинальной `f`, отслеживая результат в своём колбэке: ```js function promisify(f) { - return function (...args) { // return a wrapper-function + return function (...args) { // возвращает функцию-обёртку return new Promise((resolve, reject) => { - function callback(err, result) { // our custom callback for f + function callback(err, result) { // наш специальный колбэк для f if (err) { - return reject(err); + reject(err); } else { resolve(result); } } - args.push(callback); // append our custom callback to the end of arguments + args.push(callback); // добавляем колбэк в конец аргументов f - f.call(this, ...args); // call the original function + f.call(this, ...args); // вызываем оригинальную функцию }); }; }; -// usage: +// использование: let loadScriptPromise = promisify(loadScript); loadScriptPromise(...).then(...); ``` -Here we assume that the original function expects a callback with two arguments `(err, result)`. That's what we encounter most often. Then our custom callback is in exactly the right format, and `promisify` works great for such a case. +Здесь мы предполагаем, что исходная функция ожидает колбэк с двумя аргументами `(err, result)`. Это то, с чем мы чаще всего сталкиваемся. Тогда наш колбэк - в правильном формате, и `promisify` отлично работает для такого случая. -But what if the original `f` expects a callback with more arguments `callback(err, res1, res2)`? +Но что, если исходная `f` ожидает колбэк с большим количеством аргументов `callback(err, res1, res2, ...)`? -Here's a modification of `promisify` that returns an array of multiple callback results: +Ниже описана улучшенная функция `promisify`: при вызове `promisify(f, true)` результатом промиса будет массив результатов `[res1, res2, ...]`: ```js -// promisify(f, true) to get array of results +// promisify(f, true), чтобы получить массив результатов function promisify(f, manyArgs = false) { return function (...args) { return new Promise((resolve, reject) => { - function *!*callback(err, ...results*/!*) { // our custom callback for f + function *!*callback(err, ...results*/!*) { // наш специальный колбэк для f if (err) { - return reject(err); + reject(err); } else { - // resolve with all callback results if manyArgs is specified + // делаем resolve для всех results колбэка, если задано manyArgs *!*resolve(manyArgs ? results : results[0]);*/!* } } @@ -100,19 +98,19 @@ function promisify(f, manyArgs = false) { }; }; -// usage: +// использование: f = promisify(f, true); f(...).then(arrayOfResults => ..., err => ...) ``` -In some cases, `err` may be absent at all: `callback(result)`, or there's something exotic in the callback format, then we can promisify such functions manually. +Для более экзотических форматов колбэка, например без `err`: `callback(result)`, мы можем промисифицировать функции без помощника, "вручную". -There are also modules with a bit more flexible promisification functions, e.g. [es6-promisify](https://github.com/digitaldesignlabs/es6-promisify). In Node.js, there's a built-in `util.promisify` function for that. +Также существуют модули с более гибкой промисификацией, например, [es6-promisify](https://github.com/digitaldesignlabs/es6-promisify) или встроенная функция `util.promisify` в Node.js. ```smart -Promisification is a great approach, especially when you use `async/await` (see the next chapter), but not a total replacement for callbacks. +Промисификация - это отличный подход, особенно, если вы будете использовать `async/await` (см. следующую главу об ) но она не является тотальной заменой любых колбэков. -Remember, a promise may have only one result, but a callback may technically be called many times. +Помните, промис может иметь только один результат, но колбэк технически может вызываться сколько угодно раз. -So promisification is only meant for functions that call the callback once. Further calls will be ignored. +Поэтому промисификация используется для функций, которые вызывают колбэк только один раз. Последующие вызовы колбэка будут проигнорированы. ``` diff --git a/1-js/11-async/07-microtask-queue/article.md b/1-js/11-async/07-microtask-queue/article.md index 29179c19f1..3a255f8905 100644 --- a/1-js/11-async/07-microtask-queue/article.md +++ b/1-js/11-async/07-microtask-queue/article.md @@ -1,190 +1,116 @@ -# Microtasks and event loop +# Микрозадачи -Promise handlers `.then`/`.catch`/`.finally` are always asynchronous. +Обработчики промисов `.then`/`.catch`/`.finally` всегда асинхронны. -Even when a Promise is immediately resolved, the code on the lines *below* your `.then`/`.catch`/`.finally` will still execute first. +Даже когда промис сразу же выполнен, код в строках *ниже* `.then`/`.catch`/`.finally` будет запущен до этих обработчиков. -Here's the code that demonstrates it: +Вот демо: ```js run let promise = Promise.resolve(); -promise.then(() => alert("promise done")); +promise.then(() => alert("промис выполнен")); -alert("code finished"); // this alert shows first +alert("код выполнен"); // этот alert показывается первым ``` -If you run it, you see `code finished` first, and then `promise done`. +Если вы запустите его, сначала вы увидите `код выполнен`, а потом `промис выполнен`. -That's strange, because the promise is definitely done from the beginning. +Это странно, потому что промис определённо был выполнен с самого начала. -Why did the `.then` trigger afterwards? What's going on? +Почему `.then` срабатывает позже? Что происходит? -# Microtasks +## Очередь микрозадач -Asynchronous tasks need proper management. For that, the standard specifies an internal queue `PromiseJobs`, more often referred to as "microtask queue" (v8 term). +Асинхронные задачи требуют правильного управления. Для этого стандарт предусматривает внутреннюю очередь `PromiseJobs`, более известную как "очередь микрозадач (microtask queue)" (термин V8). -As said in the [specification](https://tc39.github.io/ecma262/#sec-jobs-and-job-queues): +Как сказано в [спецификации](https://tc39.github.io/ecma262/#sec-jobs-and-job-queues): -- The queue is first-in-first-out: tasks enqueued first are run first. -- Execution of a task is initiated only when nothing else is running. +- Очередь определяется как первым-пришёл-первым-ушёл (FIFO): задачи, попавшие в очередь первыми, выполняются тоже первыми. +- Выполнение задачи происходит только в том случае, если ничего больше не запущено. -Or, to say that simply, when a promise is ready, its `.then/catch/finally` handlers are put into the queue. They are not executed yet. JavaScript engine takes a task from the queue and executes it, when it becomes free from the current code. +Или, проще говоря, когда промис выполнен, его обработчики `.then/catch/finally` попадают в очередь. Они пока не выполняются. Движок JavaScript берёт задачу из очереди и выполняет её, когда он освободится от выполнения текущего кода. -That's why "code finished" in the example above shows first. +Вот почему сообщение "код выполнен" в примере выше будет показано первым. -![](promiseQueue.png) +![](promiseQueue.svg) -Promise handlers always go through that internal queue. +Обработчики промисов всегда проходят через эту внутреннюю очередь. -If there's a chain with multiple `.then/catch/finally`, then every one of them is executed asynchronously. That is, it first gets queued, and executed when the current code is complete and previously queued handlers are finished. +Если есть цепочка с несколькими `.then/catch/finally`, то каждый из них выполняется асинхронно. То есть сначала ставится в очередь, а потом выполняется, когда выполнение текущего кода завершено и добавленные ранее в очередь обработчики выполнены. -**What if the order matters for us? How can we make `code finished` work after `promise done`?** +**Но что если порядок имеет значение для нас? Как мы можем вывести `код выполнен` после `промис выполнен`?** -Easy, just put it into the queue with `.then`: +Легко, используя `.then`: ```js run Promise.resolve() - .then(() => alert("promise done!")) - .then(() => alert("code finished")); + .then(() => alert("промис выполнен!")) + .then(() => alert("код выполнен")); ``` -Now the order is as intended. +Теперь порядок стал таким, как было задумано. -## Event loop +## Необработанные ошибки -In-browser JavaScript, as well as Node.js, is based on an *event loop*. +Помните "необработанные ошибки" из главы ? -"Event loop" is a process when the engine sleeps and waits for events, then reacts on those and sleeps again. +Теперь мы можем описать, как именно JavaScript понимает, что ошибка не обработана. -Examples of events: -- `mousemove`, a user moved their mouse. -- `setTimeout` handler is to be called. -- an external ` ``` -Here we can see it in the browser, but the same is true for any module. - -### Module-level scope +### Своя область видимости переменных -Each module has its own top-level scope. In other words, top-level variables and functions from a module are not seen in other scripts. +Каждый модуль имеет свою собственную область видимости. Другими словами, переменные и функции, объявленные в модуле, не видны в других скриптах. -In the example below, two scripts are imported, and `hello.js` tries to use `user` variable declared in `user.js`, and fails: +В следующем примере импортированы 2 скрипта, и `hello.js` пытается использовать переменную `user`, объявленную в `user.js`. В итоге ошибка: [codetabs src="scopes" height="140" current="index.html"] -Modules are expected to `export` what they want to be accessible from outside and `import` what they need. +Модули должны экспортировать функциональность, предназначенную для использования извне. А другие модули могут её импортировать. -So we should import `user.js` directly into `hello.js` instead of `index.html`. +Так что нам надо импортировать `user.js` в `hello.js` и взять из него нужную функциональность, вместо того чтобы полагаться на глобальные переменные. -That's the correct variant: +Правильный вариант: [codetabs src="scopes-working" height="140" current="hello.js"] -In the browser, independant top-level scope also exists for each ` @@ -101,36 +108,36 @@ In the browser, independant top-level scope also exists for each ` ``` -### Top-level "this" is undefined +### В модуле "this" не определён + +Это незначительная особенность, но для полноты картины нам нужно упомянуть об этом. -That's kind of a minor feature, but for completeness we should mention it. +В модуле на верхнем уровне `this` не определён (undefined). -In a module, top-level `this` is undefined, as opposed to a global object in non-module scripts: +Сравним с не-модульными скриптами, там `this` - глобальный объект: ```html run height=0 ``` -## Browser-specific features +## Особенности в браузерах -There are also several browser-specific differences of scripts with `type="module"` compared to regular ones. +Есть и несколько других, именно браузерных особенностей скриптов с `type="module"` по сравнению с обычными скриптами. -You may want skip those for now if you're reading for the first time, or if you don't use JavaScript in a browser. +Если вы читаете материал в первый раз или, если не собираетесь использовать модули в браузерах, то сейчас можете пропустить эту секцию. -### Module scripts are deferred +### Модули являются отложенными (deferred) -Module scripts are *always* deferred, same effect as `defer` attribute (described in the chapter [](info:script-async-defer)), for both external and inline scripts. +Модули *всегда* выполняются в отложенном (deferred) режиме, точно так же, как скрипты с атрибутом `defer` (описан в главе [](info:script-async-defer)). Это верно и для внешних и встроенных скриптов-модулей. -In other words: -- external module scripts ` +Сравните с обычным скриптом ниже: + - + ``` -Please note: the second script actually works before the first! So we'll see `undefined` first, and then `object`. +Пожалуйста, обратите внимание: второй скрипт выполнится раньше, чем первый! Поэтому мы увидим сначала `undefined`, а потом `object`. + +Это потому, что модули начинают выполняться после полной загрузки страницы. Обычные скрипты запускаются сразу же, поэтому сообщение из обычного скрипта мы видим первым. -That's because modules are deferred, so way wait for the document to be processed. The regular scripts runs immediately, so we saw its output first. +При использовании модулей нам стоит иметь в виду, что HTML-страница будет показана браузером до того, как выполнятся модули и JavaScript-приложение будет готово к работе. Некоторые функции могут ещё не работать. Нам следует разместить "индикатор загрузки" или что-то ещё, чтобы не смутить этим посетителя. -When using modules, we should be aware that HTML-document can show up before the JavaScript application is ready. Some functionality may not work yet. We should put transparent overlays or "loading indicators", or otherwise ensure that the visitor won't be confused because of it. +### Атрибут async работает во встроенных скриптах -### Async works on inline scripts +Для не-модульных скриптов атрибут `async` работает только на внешних скриптах. Скрипты с ним запускаются сразу по готовности, они не ждут другие скрипты или HTML-документ. -Async attribute ` ``` -### External scripts +### Внешние скрипты -There are two notable differences of external module scripts: +Внешние скрипты с атрибутом `type="module"` имеют два отличия: -1. External scripts with same `src` run only once: +1. Внешние скрипты с одинаковым атрибутом `src` запускаются только один раз: ```html - + ``` -2. External scripts that are fetched from another domain require [CORS](mdn:Web/HTTP/CORS) headers. In other words, if a module script is fetched from another domain, the remote server must supply a header `Access-Control-Allow-Origin: *` (may use fetching domain instead of `*`) to indicate that the fetch is allowed. +2. Внешний скрипт, который загружается с другого домена, требует указания заголовков [CORS](mdn:Web/HTTP/CORS). Другими словами, если модульный скрипт загружается с другого домена, то удалённый сервер должен установить заголовок `Access-Control-Allow-Origin` означающий, что загрузка скрипта разрешена. ```html - - + + ``` - That ensures better security by default. + Это обеспечивает лучшую безопасность по умолчанию. -### No bare modules allowed +### Не допускаются "голые" модули -In the browser, in scripts (not in HTML), `import` must get either a relative or absolute URL. So-called "bare" modules, without a path, are not allowed. +В браузере `import` должен содержать относительный или абсолютный путь к модулю. Модули без пути называются "голыми" (bare). Они не разрешены в `import`. -For instance, this `import` is invalid: +Например, этот `import` неправильный: ```js -import {sayHi} from 'sayHi'; // Error, "bare" module -// must be './sayHi.js' or wherever the module is +import {sayHi} from 'sayHi'; // Ошибка, "голый" модуль +// путь должен быть, например './sayHi.js' или абсолютный ``` -Certain environments, like Node.js or bundle tools allow bare modules, as they have own ways for finding modules and hooks to fine-tune them. But browsers do not support bare modules yet. +Другие окружения, например Node.js, допускают использование "голых" модулей, без путей, так как в них есть свои правила, как работать с такими модулями и где их искать. Но браузеры пока не поддерживают "голые" модули. -### Compatibility, "nomodule" +### Совместимость, "nomodule" -Old browsers do not understand `type="module"`. Scripts of the unknown type are just ignored. For them, it's possible to provide a fallback using `nomodule` attribute: +Старые браузеры не понимают атрибут `type="module"`. Скрипты с неизвестным атрибутом `type` просто игнорируются. Мы можем сделать для них "резервный" скрипт при помощи атрибута `nomodule`: ```html run ``` -If we use bundle tools, then as modules are bundled together, their `import/export` statements are replaced by special bundler calls, so the resulting build does not require `type="module"`, and we can put it into a regular script: +## Инструменты сборки -```html - - -``` +В реальной жизни модули в браузерах редко используются в "сыром" виде. Обычно, мы объединяем модули вместе, используя специальный инструмент, например [Webpack](https://webpack.js.org/) и после выкладываем код на рабочий сервер. -## Build tools +Одно из преимуществ использования сборщика -- он предоставляет больший контроль над тем, как модули ищутся, позволяет использовать "голые" модули и многое другое "своё", например CSS/HTML-модули. -In real-life, browser modules are rarely used in their "raw" form. Usually, we bundle them together with a special tool such as [Webpack](https://webpack.js.org/) and deploy to the production server. +Сборщик делает следующее: -One of the benefits of using bundlers -- they give more control over how modules are resolved, allowing bare modules and much more, like CSS/HTML modules. +1. Берёт "основной" модуль, который мы собираемся поместить в ` +``` -That said, native modules are also usable. So we won't be using Webpack here: you can configure it later. +Хотя и "как есть" модули тоже можно использовать, а сборщик настроить позже при необходимости. -## Summary +## Итого -To summarize, the core concepts are: +Подводя итог, основные понятия: -1. A module is a file. To make `import/export` work, browsers need ` \ No newline at end of file diff --git a/1-js/99-js-misc/06-bigint/article.md b/1-js/99-js-misc/06-bigint/article.md new file mode 100644 index 0000000000..5f92b46238 --- /dev/null +++ b/1-js/99-js-misc/06-bigint/article.md @@ -0,0 +1,129 @@ +# BigInt + +[recent caniuse="bigint"] + +`BigInt` - это специальный числовой тип, который предоставляет возможность работать с целыми числами произвольной длины. + +Чтобы создать значение типа `BigInt`, необходимо добавить `n` в конец числового литерала или вызвать функцию `BigInt`, которая создаст число типа `BigInt` из переданного аргумента. Аргументом может быть число, строка и др. + +```js +const bigint = 1234567890123456789012345678901234567890n; + +const sameBigint = BigInt("1234567890123456789012345678901234567890"); + +const bigintFromNumber = BigInt(10); // то же самое, что и 10n +``` + +## Математические операторы + +`BigInt` можно использовать как обычные числа, к примеру: + +```js run +alert(1n + 2n); // 3 + +alert(5n / 2n); // 2 +``` + +Обратите внимание: операция деления `5/2` возвращает округлённый результат, без дробной части. Все операции с числами типа `bigint` возвращают `bigint`. + +В математических операциях мы не можем смешивать `bigint` и обычные числа: + +```js run +alert(1n + 2); // Error: Cannot mix BigInt and other types +``` + +Мы должны явно их конвертировать: используя либо `BigInt()`, либо `Number()`, например: + +```js run +let bigint = 1n; +let number = 2; + +// конвертируем number в bigint +alert(bigint + BigInt(number)); // 3 + +// конвертируем `bigint` в number +alert(Number(bigint) + number); // 3 +``` + +Конвертирование `bigint` в число всегда происходит неявно и без генерации ошибок, но если значение `bigint` слишком велико и не подходит под тип `number`, то дополнительные биты будут отброшены, так что следует быть осторожными с такими преобразованиями. + +````smart header="К `BigInt` числам нельзя применить унарный оператор `+`" +Унарный оператор `+value` является хорошо известным способом конвертировать произвольное значение `value` в число. + +Данный оператор не поддерживается при работе с `BigInt` числами: +```js run +let bigint = 1n; + +alert( +bigint ); // Ошибка! +``` + +Мы должны использовать `Number()` для преобразования `bigint` к `number`. +```` + +## Операции сравнения + +Операции сравнения, такие как `<`, `>`, работают с `bigint` и обычными числами как обычно: + +```js run +alert( 2n > 1n ); // true + +alert( 2n > 1 ); // true +``` + +Пожалуйста, обратите внимание, что обычные и `bigint` числа принадлежат к разным типам, они могут быть равны только при нестрогом сравнении `==`: + +```js run +alert( 1 == 1n ); // true + +alert( 1 === 1n ); // false +``` + +## Логические операции + +В `if` или любом другом логическом операторе `bigint` число ведёт себя как обычное число. + +К примеру, в `if` `bigint` `0n` преобразуется в `false`, другие значения преобразуются в `true`: + +```js run +if (0n) { + // никогда не выполнится +} +``` + +Логические операторы `||`, `&&` и другие также работают с `bigint` числами как с обычными числами: + +```js run +alert( 1n || 2 ); // 1 + +alert( 0n || 2 ); // 2 +``` + +## Полифилы + +Создание полифила для `BigInt` - достаточно непростая задача. Причина в том, что многие операторы в JavaScript, такие как `+`, `-` и др., ведут себя по-разному с `bigint` по сравнению с обычными числами. + +К примеру, деление `bigint` числа всегда возвращает `bigint` (округлённое при необходимости). + +Чтобы эмулировать такое поведение, полифил должен будет проанализировать код и заменить все такие операторы на свои вызовы. Такая реализация будет тяжеловесной, не очень хорошей с точки зрения производительности. + +Вот почему на данный момент нет хорошо реализованного полифила. + +Существует обратное решение, предложеное разработчиками библиотеки [JSBI](https://github.com/GoogleChromeLabs/jsbi). + +Эта библиотека реализует большие числа, используя собственные методы. Мы можем использовать их вместо встроенных `BigInt`: + +| Операция | Встроенный `BigInt` | JSBI | +|-----------|-----------------|------| +| Создание из `number` | `a = BigInt(789)` | `a = JSBI.BigInt(789)` | +| Сложение | `c = a + b` | `c = JSBI.add(a, b)` | +| Вычитание | `c = a - b` | `c = JSBI.subtract(a, b)` | +| ... | ... | ... | + +...А затем использовать полифил (плагин Babel) для замены вызовов JSBI на встроенные `Bigint` для браузеров, которые их поддерживают. + +Другими словами, данный подход предлагает использовать JSBI вместо встроенных `BigInt`. JSBI внутри себя работает с числами как с `BigInt`, эмулирует их с соблюдением всех требований спецификации. Таким образом, мы можем выполнять JSBI-код в интерпретаторах, которые не поддерживают `Bigint`, а для тех, которые поддерживают - полифил преобразует вызовы в обычные `Bigint`. + +## Ссылки + +- MDN: [BigInt](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/BigInt). +- Спецификация: [BigInt](https://tc39.es/ecma262/#sec-bigint-objects). diff --git a/1-js/99-js-misc/07-unicode/article.md b/1-js/99-js-misc/07-unicode/article.md new file mode 100644 index 0000000000..3ae322dc42 --- /dev/null +++ b/1-js/99-js-misc/07-unicode/article.md @@ -0,0 +1,173 @@ + +# Юникод, внутреннее устройство строк + +```warn header="Глубокое погружение в тему" +Этот раздел более подробно описывает, как устроены строки. Такие знания пригодятся, если вы намерены работать с эмодзи, редкими математическими символами, иероглифами, и т.д. +``` + +Как мы уже знаем, строки в JavaScript основаны на [Юникоде](https://ru.wikipedia.org/wiki/Юникод): каждый символ представляет из себя последовательность байтов из 1-4 байтов. + +JavaScript позволяет нам вставить символ в строку, указав его шестнадцатеричный Юникод с помощью одной из этих трех нотаций: + +- `\xXX` + + Вместо `XX` должны быть указаны две шестнадцатеричные цифры со значением от `00` до `FF`. В этом случае `\xXX` -- это символ, Юникод которого равен `XX`. + + Поскольку нотация `\xXX` поддерживает только две шестнадцатеричные цифры, ее можно использовать только для первых 256 символов Юникода. + + Эти 256 символов включают в себя латинский алфавит, большинство основных синтаксических символов и некоторые другие. Например, `"\x7A"` - это то же самое, что `"z"` (Юникод `U+007A`). + + ```js run + alert( "\x7A" ); // z + alert( "\xA9" ); // ©, символ авторского права + ``` + +- `\uXXXX` + + Вместо `XXXX` должны быть указаны ровно 4 шестнадцатеричные цифры со значением от `0000` до `FFFF`. В этом случае `\uXXXX` - это символ, Юникод которого равен `XXXX`. + + Символы со значениями Юникода, превышающими `U+FFFF`, также могут быть представлены с помощью этой нотации, но в таком случае нам придется использовать так называемую суррогатную пару (о ней мы поговорим позже в этой главе). + + ```js run + alert( "\u00A9" ); // ©, то же самое, что \xA9, используя 4-значную шестнадцатеричную нотацию + alert( "\u044F" ); // я, буква кириллического алфавита + alert( "\u2191" ); // ↑, символ стрелки вверх + ``` + +- `\u{X…XXXXXX}` + + Вместо `X…XXXXXX` должно быть шестнадцатеричное значение от 1 до 6 байт от `0` до `10FFFF` (максимальная точка кода, определенная стандартом Юникод). Эта нотация позволяет нам легко представлять все существующие символы Юникода. + + ```js run + alert( "\u{20331}" ); // 佫, редкий китайский иероглиф (длинный Юникод) + alert( "\u{1F60D}" ); // 😍, символ улыбающегося лица (ещё один длинный Юникод) + ``` + +## Суррогатные пары + +Все часто используемые символы имеют 2-байтовые коды (4 шестнадцатеричные цифры). В большинстве европейских языков буквы, цифры и основные унифицированные идеографические наборы CJK (CJK -- от китайской, японской и корейской систем письма) имеют 2-байтовое представление. + +Изначально JavaScript был основан на кодировке UTF-16, которая предусматривала только 2 байта на один символ. Однако 2 байта допускают только 65536 комбинаций, и этого недостаточно для всех возможных символов Юникода. + +Поэтому редкие символы, требующие более 2 байт, кодируются парой 2-байтовых символов, которые называются "суррогатной парой". + +Побочным эффектом является то, что длина таких символов равна `2`: + +```js run +alert( '𝒳'.length ); // 2, MATHEMATICAL SCRIPT CAPITAL X +alert( '😂'.length ); // 2, FACE WITH TEARS OF JOY +alert( '𩷶'.length ); // 2, редкий китайский иероглиф +``` + +Это происходит потому, что суррогатные пары не существовали в то время, когда был создан JavaScript, и поэтому они не обрабатываются языком корректно. + +На самом деле в каждой из приведенных строк у нас по одному символу, но свойство `length` показывает длину `2`. + +Получить такой символ также может быть непросто, поскольку большинство языковых функций рассматривают суррогатные пары как два символа. + +Например, здесь мы видим два странных символа в выводе: + +```js run +alert( '𝒳'[0] ); // показывает странные символы... +alert( '𝒳'[1] ); // ...части суррогатной пары +``` + +Части суррогатной пары не имеют никакого значения друг без друга. + +Технически, суррогатные пары также можно определить по их кодам: если символ имеет код в интервале `0xd800...0xdbff`, то он является первой частью суррогатной пары. Следующий символ (вторая часть) должен иметь код в интервале `0xdc00...0xdfff`. Эти интервалы зарезервированы стандартом исключительно для суррогатных пар. + +Поэтому для работы с суррогатными парами в JavaScript были добавлены методы `String.fromCodePoint` и `str.codePointAt`. + +По сути, они аналогичны [String.fromCharCode](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/String/fromCharCode) и [str.charCodeAt](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/String/charCodeAt), но они правильно обрабатывают суррогатные пары. + +Здесь можно увидеть разницу: + +```js run +// charCodeAt не учитывает суррогатные пары, поэтому он выдает коды для 1-й части 𝒳: + +alert( '𝒳'.charCodeAt(0).toString(16) ); // d835 + +// codePointAt учитывает суррогатные пары +alert( '𝒳'.codePointAt(0).toString(16) ); // 1d4b3, считывает обе части суррогатной пары +``` + +При этом, если брать с позиции 1 (а это здесь скорее неверно), то они оба возвращают только 2-ю часть пары: + +```js run +alert( '𝒳'.charCodeAt(1).toString(16) ); // dcb3 +alert( '𝒳'.codePointAt(1).toString(16) ); // dcb3 +// бессмысленная 2-я половина пары +``` + +Другие способы работы с суррогатными парами вы найдете в главе . Возможно, для этого тоже существуют специальные библиотеки, но они не настолько известные, чтобы предлагать их в учебнике. + +````warn header="Разделение строки в случайном месте может быть опасным!" +Разделив строку в случайном месте, например, с помощью `str.slice(0, 4)`, мы не можем гарантировать валидность полученного значения. Например: + +```js run +alert( 'hi 😂'.slice(0, 4) ); // hi [?] +``` + +Здесь мы видим мусорный символ (первая половина суррогатной пары 😂) в выводе. + +Просто имейте это в виду, если вы намерены надежно работать с суррогатными парами. Может быть, это не очень большая проблема, но, по крайней мере, вы должны понимать, что происходит. +```` + +## Диакритические знаки и нормализация + +Во многих языках есть символы, состоящие из основного символа и знака над/под ним. + +Например, буква `a` может быть основой для этих символов: `àáâäãåā`. + +Большинство распространенных "составных" символов имеют свой собственный код в таблице Юникода. Но не все, потому что существует слишком большое количество возможных комбинаций. + +Для поддержки любых комбинаций стандарт Юникод позволяет нам использовать несколько Юникодных символов: основной символ, за которым следует один или много символов-"меток", которые "украшают" его. + +Например, если за `S` следует специальный символ "точка сверху" (код `\u0307`), то он отобразится как Ṡ. + +```js run +alert( 'S\u0307' ); // Ṡ +``` + +Если нам нужен дополнительный знак над буквой (или под ней) -- нет проблем, просто добавляем соответствующий символ. + +Например, если мы добавим символ "точка снизу" (код `\u0323`), то получим "S с точками сверху и снизу": `Ṩ`. + +Вот, как это будет выглядеть: + +```js run +alert( 'S\u0307\u0323' ); // Ṩ +``` + +Это обеспечивает большую гибкость, но при этом возникает определенная проблема: два символа могут визуально выглядеть одинаково, но при этом они будут представлены разными комбинациями Юникода. + +Например: + +```js run +let s1 = 'S\u0307\u0323'; // Ṩ, S + точка сверху + точка снизу +let s2 = 'S\u0323\u0307'; // Ṩ, S + точка снизу + точка сверху + +alert( `s1: ${s1}, s2: ${s2}` ); + +alert( s1 == s2 ); // false, хотя символы выглядят одинаково (?!) +``` + +Для решения этой проблемы предусмотрен алгоритм "Юникодной нормализации", приводящий каждую строку к единому "нормальному" виду. + +Его реализует метод [str.normalize()](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/String/normalize). + +```js run +alert( "S\u0307\u0323".normalize() == "S\u0323\u0307".normalize() ); // true +``` + +Забавно, но в нашем случае `normalize()` "схлопывает" последовательность из трёх символов в один: `\u1e68` — S с двумя точками. + +```js run +alert( "S\u0307\u0323".normalize().length ); // 1 + +alert( "S\u0307\u0323".normalize() == "\u1e68" ); // true +``` + +В действительности это не всегда так. Причина в том, что символ `Ṩ` является "достаточно распространенным", поэтому создатели стандарта Юникод включили его в основную таблицу и присвоили ему код. + +Если вы хотите узнать больше о правилах и вариантах нормализации -- они описаны в дополнении к стандарту Юникод: [Unicode Normalization Forms](https://www.unicode.org/reports/tr15/), но для большинства практических целей достаточно информации из этого раздела. diff --git a/1-js/99-js-misc/08-intl/1-collate-array-sort/solution.md b/1-js/99-js-misc/08-intl/1-collate-array-sort/solution.md new file mode 100755 index 0000000000..0594dd8c4a --- /dev/null +++ b/1-js/99-js-misc/08-intl/1-collate-array-sort/solution.md @@ -0,0 +1,23 @@ +Здесь подойдут стандартные параметры сравнения: + +```js run +let animals = ["тигр", "ёж", "енот", "ехидна", "АИСТ", "ЯК"]; + +*!* +let collator = new Intl.Collator(); +animals.sort((a, b) => collator.compare(a, b)); +*/!* + +alert( animals ); // АИСТ,ёж,енот,ехидна,тигр,ЯК +``` + +А вот, что было бы при обычном вызове `sort()`: + +```js run +let animals = ["тигр", "ёж", "енот", "ехидна", "АИСТ", "ЯК"]; + +*!* +alert( animals.sort() ); // АИСТ,ЯК,енот,ехидна,тигр,ёж +*/!* +``` + diff --git a/1-js/99-js-misc/08-intl/1-collate-array-sort/task.md b/1-js/99-js-misc/08-intl/1-collate-array-sort/task.md new file mode 100755 index 0000000000..1259405529 --- /dev/null +++ b/1-js/99-js-misc/08-intl/1-collate-array-sort/task.md @@ -0,0 +1,19 @@ +importance: 5 + +--- + +# Отсортируйте массив с буквой ё + +Используя `Intl.Collator`, отсортируйте массив: + +```js +let animals = ["тигр", "ёж", "енот", "ехидна", "АИСТ", "ЯК"]; + +// ... ваш код ... + +alert( animals ); // АИСТ,ёж,енот,ехидна,тигр,ЯК +``` + +В этом примере порядок сортировки не должен зависеть от регистра. + +Что касается буквы `"ё"`, то мы следуем [обычным правилам сортировки буквы ё](https://ru.wikipedia.org/wiki/%D0%81#.D0.A1.D0.BE.D1.80.D1.82.D0.B8.D1.80.D0.BE.D0.B2.D0.BA.D0.B0), по которым "е" и "ё" считаются одной и той же буквой, за исключением случая, когда два слова отличаются только в позиции буквы "е" / "ё" -- тогда слово с "е" ставится первым. diff --git a/1-js/99-js-misc/08-intl/article.md b/1-js/99-js-misc/08-intl/article.md new file mode 100644 index 0000000000..b4bb313487 --- /dev/null +++ b/1-js/99-js-misc/08-intl/article.md @@ -0,0 +1,458 @@ +# Intl: интернационализация в JavaScript + +Общая проблема строк, дат, чисел в JavaScript -- они "не в курсе" языка и особенностей стран, где находится посетитель. + +В частности: + +Строки +: При сравнении сравниваются коды символов, а это неправильно, к примеру, в русском языке оказывается, что `"ё" > "я"` и `"а" > "Я"`, хотя всем известно, что `я` -- последняя буква алфавита и это она должна быть больше любой другой. + +Даты +: В разных странах принята разная запись дат. Где-то пишут 31.12.2014 (Россия), а где-то 12/31/2014 (США), где-то иначе. + +Числа +: В одних странах выводятся цифрами, в других -- иероглифами, длинные числа разделяются где-то пробелом, где-то запятой. + +Все современные браузеры, кроме IE10 (но есть библиотеки и для него) поддерживают стандарт [ECMA 402](https://www.ecma-international.org/ecma-402/1.0/ECMA-402.pdf), предназначенный решить эти проблемы навсегда. + +## Основные объекты + +`Intl.Collator` +: Умеет правильно сравнивать и сортировать строки. + +`Intl.DateTimeFormat` +: Умеет форматировать дату и время в соответствии с нужным языком. + +`Intl.NumberFormat` +: Умеет форматировать числа в соответствии с нужным языком. + +## Локаль + +*Локаль* -- первый и самый важный аргумент всех методов, связанных с интернационализацией. + +Локаль описывается строкой из трёх компонентов, которые разделяются дефисом: + +1. Код языка. +2. Код способа записи. +3. Код страны. + +На практике не всегда указаны три, обычно меньше: + +1. `ru` -- русский язык, без уточнений. +2. `en-GB` -- английский язык, используемый в Англии (`GB`). +3. `en-US` -- английский язык, используемый в США (`US`). +4. `zh-Hans-CN` -- китайский язык (`zh`), записываемый упрощённой иероглифической письменностью (`Hans`), используемый в Китае. + +Также через суффикс `-u-*` можно указать расширения локалей, например `"th-TH-u-nu-thai"` -- тайский язык (`th`), используемый в Таиланде (`TH`), с записью чисел тайскими буквами (๐, ๑, ๒, ๓, ๔, ๕, ๖, ๗, ๘, ๙) . + +Стандарт, который описывает локали -- [RFC 5646](https://datatracker.ietf.org/doc/html/rfc5646), языки описаны в [IANA language registry](https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry). + +Все методы принимают локаль в виде строки или массива, содержащего несколько локалей в порядке предпочтения. + +Если локаль не указана или `undefined` -- берётся локаль по умолчанию, установленная в окружении (браузере). + +### Подбор локали localeMatcher + +`localeMatcher` -- вспомогательная настройка, которую тоже можно везде указать, она определяет способ подбора локали, если желаемая недоступна. + +У него два значения: + +- `"lookup"` -- означает простейший порядок поиска путём обрезания суффикса, например `zh-Hans-CN` -> `zh-Hans` -> `zh` -> локаль по умолчанию. +- `"best fit"` -- использует встроенные алгоритмы и предпочтения браузера (или другого окружения) для выбора подходящей локали. + +**По умолчанию стоит `"best fit"`.** + +Если локалей несколько, например `["zh-Hans-CN", "ru-RU"]` то `localeMatcher` пытается подобрать наиболее подходящую локаль для первой из списка (китайская), если не получается -- переходит ко второй (русской) и так далее. Если ни одной не нашёл, например на компьютере не совсем поддерживается ни китайский ни русский, то используется локаль по умолчанию. + +Как правило, `"best fit"` является здесь наилучшим выбором. + +## Строки, Intl.Collator [#intl-collator] + +Синтаксис: + +```js +// создание +let collator = new Intl.Collator([locales, [options]]) +``` + +Параметры: + +`locales` +: Локаль, одна или массив в порядке предпочтения. + +`options` +: Объект с дополнительными настройками: + + - `localeMatcher` -- алгоритм выбора подходящей локали. + - `usage` -- цель сравнения: сортировка `"sort"` или поиск `"search"`, по умолчанию `"sort"`. + - `sensitivity` -- чувствительность: какие различия в символах учитывать, а какие -- нет, варианты: + - `base` -- учитывать только разные символы, без диакритических знаков и регистра, например: `а ≠ б`, `е = ё`, `а = А`. + - `accent` -- учитывать символы и диакритические знаки, например: `а ≠ б`, `е ≠ ё`, `а = А`. + - `case` -- учитывать символы и регистр, например: `а ≠ б`, `е = ё`, `а ≠ А`. + - `variant` -- учитывать всё: символ, диакритические знаки, регистр, например: `а ≠ б`, `е ≠ ё`, `а ≠ А`, используется по умолчанию. + + - `ignorePunctuation` -- игнорировать знаки пунктуации: `true/false`, по умолчанию `false`. + - `numeric` -- использовать ли численное сравнение: `true/false`, если `true`, то будет `12 > 2`, иначе `12 < 2`. + - `caseFirst` -- в сортировке должны идти первыми прописные или строчные буквы, варианты: `"upper"` (прописные), `"lower"` (строчные) или `"false"` (стандартное для локали, также является значением по умолчанию). Не поддерживается IE11. + +В подавляющем большинстве случаев подходят стандартные параметры, то есть `options` указывать не нужно. + +Использование: + +```js +let result = collator.compare(str1, str2); +``` + +Результат `compare` имеет значение `1` (больше), `0` (равно) или `-1` (меньше). + +Например: + +```js run +let collator = new Intl.Collator(); + +alert( "ёжик" > "яблоко" ); // true (ёжик больше, что неверно) +alert( collator.compare("ёжик", "яблоко") ); // -1 (ёжик меньше, верно) +``` + +Выше были использованы полностью стандартные настройки. Они различают регистр символа, но это различие можно убрать, если настроить чувствительность `sensitivity`: + +```js run +let collator1 = new Intl.Collator(); +alert( collator1.compare("ЁжиК", "ёжик") ); // 1, разные + +let collator2 = new Intl.Collator(undefined, { + sensitivity: "accent" +}); +alert( collator2.compare("ЁжиК", "ёжик") ); // 0, одинаковые +``` + +## Даты, Intl.DateTimeFormat [#intl-datetimeformat] + +Синтаксис: + +```js +// создание +let formatter = new Intl.DateTimeFormat([locales, [options]]) +``` + +Первый аргумент -- такой же, как и в `Collator`, а в объекте `options` мы можем определить, какие именно части даты показывать (часы, месяц, год...) и в каком формате. + +Полный список свойств `options`: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
СвойствоОписаниеВозможные значенияПо умолчанию
localeMatcher Алгоритм подбора локали + lookup, best fit + + best fit +
formatMatcher + Алгоритм подбора формата + basic, best fit best fit
hour12Включать ли время в 12-часовом форматеtrue -- 12-часовой формат, false -- 24-часовой
timeZoneВременная зонаВременная зона, например Europe/MoscowUTC
weekdayДень неделиnarrow, short, long
eraЭраnarrow, short, long
yearГод2-digit, numericundefined или numeric
monthМесяц2-digit, numeric, narrow, short, long undefined или numeric
dayДень2-digit, numericundefined или numeric
hourЧас 2-digit, numeric
minuteМинуты 2-digit, numeric
secondСекунды2-digit, numeric
timeZoneNameНазвание таймзоны (нет в IE11)short, long
+ +**Все локали обязаны поддерживать следующие наборы настроек:** + +- weekday, year, month, day, hour, minute, second +- weekday, year, month, day +- year, month, day +- year, month +- month, day +- hour, minute, second + +Если указанный формат не поддерживается, то настройка `formatMatcher` задаёт алгоритм подбора наиболее близкого формата: `basic` -- по [стандартным правилам](https://www.ecma-international.org/ecma-402/1.0/#BasicFormatMatcher) и `best fit` -- по умолчанию, на усмотрение окружения (браузера). + +Использование: + +```js +let dateString = formatter.format(date); +``` + +Например: + +```js run +let date = new Date(2014, 11, 31, 12, 30, 0); + +let formatter1 = new Intl.DateTimeFormat("ru"); +alert( formatter1.format(date) ); // 31.12.2014 + +let formatter2 = new Intl.DateTimeFormat("en-US"); +alert( formatter2.format(date) ); // 12/31/2014 +``` + +Длинная дата, с настройками: + +```js run +let date = new Date(2014, 11, 31, 12, 30, 0); + +let formatter = new Intl.DateTimeFormat("ru", { + weekday: "long", + year: "numeric", + month: "long", + day: "numeric" +}); + +alert( formatter.format(date) ); // среда, 31 декабря 2014 г. +``` + +Только время: + +```js run +let date = new Date(2014, 11, 31, 12, 30, 0); + +let formatter = new Intl.DateTimeFormat("ru", { + hour: "numeric", + minute: "numeric", + second: "numeric" +}); + +alert( formatter.format(date) ); // 12:30:00 +``` + +## Числа, Intl.NumberFormat + +Форматтер `Intl.NumberFormat` умеет красиво форматировать не только числа, но и валюту, а также проценты. + +Синтаксис: + +```js +let formatter = new Intl.NumberFormat([locales[, options]]); + +formatter.format(number); // форматирование +``` + +Параметры, как и раньше -- локаль и опции. + +Список опций: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Свойство Описание Возможные значения По умолчанию
localeMatcher Алгоритм подбора локали lookup, best fit best fit
style Стиль форматирования decimal, percent, currency decimal
currency Алфавитный код валюты См. Список кодов валюты, например USD
currencyDisplay Показывать валюту в виде кода, локализованного символа или локализованного названия + code, symbol, name symbol
useGrouping Разделять ли цифры на группы true, false true
minimumIntegerDigitsМинимальное количество цифр целой частиот 1 до 21 + 21
minimumFractionDigits Минимальное количество десятичных цифр + от 0 до 20 для чисел и процентов 0, для валюты зависит от кода.
maximumFractionDigitsМаксимальное количество десятичных цифр от minimumFractionDigits до 20. для чисел max(minimumFractionDigits, 3), для процентов 0, для валюты зависит от кода.
minimumSignificantDigitsМинимальное количество значимых цифрот 1 до 211
maximumSignificantDigitsМаксимальное количество значимых цифрот minimumSignificantDigits до 2121
+ +Пример без опций: + +```js run +let formatter = new Intl.NumberFormat("ru"); +alert( formatter.format(1234567890.123) ); // 1 234 567 890,123 +``` + +С ограничением значимых цифр (важны только первые 3): + +```js run +let formatter = new Intl.NumberFormat("ru", { + maximumSignificantDigits: 3 +}); +alert( formatter.format(1234567890.123) ); // 1 230 000 000 +``` + +С опциями для валюты: + +```js run +let formatter = new Intl.NumberFormat("ru", { + style: "currency", + currency: "GBP" +}); + +alert( formatter.format(1234.5) ); // 1 234,5 £ +``` + +С двумя цифрами после запятой: + +```js run +let formatter = new Intl.NumberFormat("ru", { + style: "currency", + currency: "GBP", + minimumFractionDigits: 2 +}); + +alert( formatter.format(1234.5) ); // 1 234,50 £ +``` + +## Методы в Date, String, Number + +Методы форматирования также поддерживаются в обычных строках, датах, числах: + +`String.prototype.localeCompare(that [, locales [, options]])` +: Сравнивает строку с другой, с учётом локали, например: + + ```js run + let str = "ёжик"; + + alert( str.localeCompare("яблоко", "ru") ); // -1 + ``` + +`Date.prototype.toLocaleString([locales [, options]])` +: Форматирует дату в соответствии с локалью, например: + + ```js run no-beautify + let date = new Date(2014, 11, 31, 12, 0); + + alert( date.toLocaleString("ru", { year: 'numeric', month: 'long' }) ); // Декабрь 2014 + ``` + +`Date.prototype.toLocaleDateString([locales [, options]])` +: То же, что и выше, но опции по умолчанию включают в себя год, месяц, день + +`Date.prototype.toLocaleTimeString([locales [, options]])` +: То же, что и выше, но опции по умолчанию включают в себя часы, минуты, секунды + +`Number.prototype.toLocaleString([locales [, options]])` +: Форматирует число, используя опции `Intl.NumberFormat`. + +Все эти методы при запуске создают соответствующий объект `Intl.*` и передают ему опции, можно рассматривать их как укороченные варианты вызова. + +## Старые IE + +В 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`: + +![](weakref-finalizationregistry-01.svg) + +Затем, в какой-то момент, мы перестаём использовать переменную `user` - она перезаписывается, выходит из области видимости и т.д., при этом сохраняя экземпляр `WeakRef` в переменной `admin`: + +```js +// перезапишем значение переменной user +user = null; +``` + +Слабой ссылки на объект недостаточно, чтобы сохранить его "в живых". Когда единственными оставшимися ссылками на объект-референт являются слабые ссылки, сборщик мусора вправе уничтожить этот объект и использовать его память для чего-то другого. + +Однако до тех пор, пока объект фактически не уничтожен, слабая ссылка может вернуть его, даже если на данный объект больше нет сильных ссылок. То есть наш объект становится своеобразным "[котом Шрёдингера](https://ru.wikipedia.org/wiki/Кот_Шрёдингера)" - мы не можем знать точно, "жив" он или "мёртв": + +![](weakref-finalizationregistry-02.svg) + +На этом этапе, чтобы получить объект из экземпляра `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`, содержащие сами изображения. + +Эта техника помогает избежать выделения большого объёма памяти на ресурсоёмкие объекты, которые больше никто не использует. Также она экономит память и время в случае повторного использования кешированных объектов. + +Вот визуальное представление того, как выглядит этот код: + +![](weakref-finalizationregistry-03.svg) + +Но, у данной реализации есть свои недостатки: со временем `Map` будет заполняться строками в качестве ключей, которые указывают на `WeakRef`, чей объект-референт уже был удалён сборщиком мусора: + +![](weakref-finalizationregistry-04.svg) + +Один из способов справиться с этой проблемой - это периодически проверять кеш и удалять "мёртвые" записи. Другой способ - использовать финализаторы, с которыми мы ознакомимся далее. + +## Пример №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)": + + ![](google-chrome-developer-tools.png) + + Данный функционал поддерживается в большинстве современных браузеров. После проделанных действий 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`. + +Вот визуальное представление обновлённого кода: + +![](weakref-finalizationregistry-05.svg) + +Ключевым аспектом в обновлённой реализации является то, что финализаторы позволяют создавать параллельные процессы между "основной" программой и колбэками очистки. В контексте JavaScript, "основная" программа - это наш JavaScript-код, который запускается и выполняется в нашем приложении или на веб-странице. + +Следовательно, с момента, когда объект помечается для удаления сборщиком мусора, до фактического выполнения колбэка очистки, может возникнуть определённый промежуток времени. Важно понимать, что в этом временном интервале основная программа может внести любые изменения в объект или даже вернуть его обратно в память. + +Поэтому, в колбэке очистки мы должны проверить, не была ли запись добавлена обратно в кеш основной программой, чтобы избежать удаления "живых" записей. Аналогично, при поиске ключа в кеше существует вероятность того, что значение было удалено сборщиком мусора, но колбэк очистки ещё не был выполнен. + +Такие ситуации требуют особого внимания, если вы работаете с `FinalizationRegistry`. + +## Использование WeakRef и FinalizationRegistry на практике + +Переходя от теории к практике, представьте себе реальный сценарий, когда пользователь синхронизирует свои фотографии на мобильном устройстве +с каким-либо облачным сервисом (таким как [iCloud](https://ru.wikipedia.org/wiki/ICloud) или [Google Photos](https://ru.wikipedia.org/wiki/Google_Фото)), и хочет просматривать их с других устройств. Подобные сервисы помимо основного функционала просмотра фотографий, предлагают массу дополнительных возможностей, например: + +- Редактирование фотографий и видео эффекты. +- Создание "воспоминаний" и альбомов. +- Монтаж видео из серии фотографий. +- ...и многое другое. + +В качестве примера здесь мы будем использовать достаточно примитивную реализацию подобного сервиса. Основная суть — показать возможный сценарий совместного использования `WeakRef` и `FinalizationRegistry` в реальной жизни. + +Вот как это выглядит: + +![](weakref_-finalizationregistry-demo-01.png) + +В левой части находится облачная библиотека фотографий (они отображаются в виде миниатюр). Мы можем выбрать нужные нам изображения и создать коллаж, нажав на кнопку "Create collage" в правой части страницы. Затем, получившийся результат можно будет скачать в виде изображения. + +Для увеличения скорости загрузки страницы разумно будет загружать и показывать миниатюры фотографий именно в *сжатом* качестве. Но, для создания коллажа из выбранных фотографий, загружать и использовать их в *полноразмерном* качестве. + +Ниже мы видим, что внутренний размер миниатюр составляет 240×240 пикселей. Размер был выбран специально для увеличения скорости загрузки. Кроме того, нам не нужны полноразмерные фотографии в режиме предпросмотра. + +![](weakref-finalizationregistry-demo-02.png) + +Предположим, что нам нужно создать коллаж из 4 фотографий: мы выбираем их, после чего нажимаем кнопку "Create collage". +На этом этапе уже известная нам функция weakRefCache проверяет, есть ли нужное изображение в кеше. Если нет, то скачивает его из облака и помещает в кеш для возможности дальнейшего использования. И так происходит для каждого выбранного изображения: + +![](weakref-finalizationregistry-demo-03.gif) + +Обратив внимание на вывод в консоли можно увидеть, какие из фотографий были загружены из облака - на это указывает FETCHED_IMAGE. Так как это первая попытка создания коллажа, это означает, что на данном этапе "слабый кеш" ещё был пуст, а все фотографии были скачаны из облака и помещены в него. + +Но, наряду с процессом загрузки изображений, происходит ещё и процесс очистки памяти сборщиком мусора. Это означает, что хранящийся в кеше объект, на который мы ссылаемся используя слабую ссылку, удаляется сборщиком мусора. И наш финализатор выполняется успешно, тем самым удаляя ключ, по которому изображение хранилось в кеше. Об этом нас уведомляет CLEANED_IMAGE: + +![](weakref-finalizationregistry-demo-04.jpg) + +Далее мы понимаем, что нам не нравится получившийся коллаж, и решаем изменить одно из изображений и создать новый. Для этого достаточно снять выделение с ненужного изображения, выбрать другое, и ещё раз нажать на кнопку "Create collage": + +![](weakref-finalizationregistry-demo-05.gif) + +Но, на этот раз не все изображения были скачаны из сети, и одно из них было взято из слабого кеша: об этом нам говорит сообщение CACHED_IMAGE. Это означает, что на момент создания коллажа сборщик мусора ещё не удалил наше изображение, и мы смело взяли его из кеша, +тем самым сократив количество сетевых запросов и ускорив общее время процесса создания коллажа: + +![](weakref-finalizationregistry-demo-06.jpg) + +Давайте ещё немного "поиграем", заменив одно из изображений ещё раз и создав новый коллаж: + +![](weakref-finalizationregistry-demo-07.gif) + +На этот раз результат ещё более внушительный. Из 4 выбранных изображений, 3 из них были взяты из слабого кеша, и только одно пришлось скачать из сети. +Снижение нагрузки на сеть составило около 75%. Впечатляет, не правда ли? + +![](weakref-finalizationregistry-demo-08.jpg) + +Конечно, не следует забывать, что такое поведение не является гарантированным, и зависит от конкретной реализации и работы сборщика мусора. + +Исходя из этого, сразу же возникает вполне логичный вопрос: почему бы нам не использовать обычный кеш, где мы можем сами управлять его сущностями, а не полагаться на сборщик мусора? Всё верно, в большинстве случаев нет необходимости использовать `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 @@ + + + + + + + + + + +
+ +
+
+

Сообщения:

+ +
+
+ Нет сообщений. +
+
+
+ + + + + diff --git a/1-js/99-js-misc/09-weakref-finalizationregistry/weakref-dom.view/index.js b/1-js/99-js-misc/09-weakref-finalizationregistry/weakref-dom.view/index.js new file mode 100644 index 0000000000..0b8e88f034 --- /dev/null +++ b/1-js/99-js-misc/09-weakref-finalizationregistry/weakref-dom.view/index.js @@ -0,0 +1,23 @@ +const startMessagesBtn = document.querySelector('.start-messages'); // (1) +const closeWindowBtn = document.querySelector('.window__button'); // (2) +const windowElementRef = new WeakRef(document.querySelector(".window__body")); // (3) + +startMessagesBtn.addEventListener('click', () => { // (4) + startMessages(windowElementRef); + startMessagesBtn.disabled = true; +}); + +closeWindowBtn.addEventListener('click', () => document.querySelector(".window__body").remove()); // (5) + +const startMessages = (element) => { + const timerId = setInterval(() => { // (6) + if (element.deref()) { // (7) + const payload = document.createElement("p"); + payload.textContent = `Сообщение: Статус системы OK: ${new Date().toLocaleTimeString()}`; + element.deref().append(payload); + } else { // (8) + alert("Элемент был удалён."); // (9) + clearInterval(timerId); + } + }, 1000); +}; \ No newline at end of file diff --git a/1-js/99-js-misc/09-weakref-finalizationregistry/weakref-finalizationregistry-01.svg b/1-js/99-js-misc/09-weakref-finalizationregistry/weakref-finalizationregistry-01.svg new file mode 100644 index 0000000000..2a507dbcdb --- /dev/null +++ b/1-js/99-js-misc/09-weakref-finalizationregistry/weakref-finalizationregistry-01.svg @@ -0,0 +1,32 @@ + + + + + + + + user + + name: "John" + Object + + <global> + + + + + + + + + + + + + + + + admin + + + \ No newline at end of file diff --git a/1-js/99-js-misc/09-weakref-finalizationregistry/weakref-finalizationregistry-02.svg b/1-js/99-js-misc/09-weakref-finalizationregistry/weakref-finalizationregistry-02.svg new file mode 100644 index 0000000000..6cc199a128 --- /dev/null +++ b/1-js/99-js-misc/09-weakref-finalizationregistry/weakref-finalizationregistry-02.svg @@ -0,0 +1,33 @@ + + + + + + + + + + <global> + + + name: "John" + Object + + + + + + + + + + + + admin + + + + + + + \ No newline at end of file diff --git a/1-js/99-js-misc/09-weakref-finalizationregistry/weakref-finalizationregistry-03.svg b/1-js/99-js-misc/09-weakref-finalizationregistry/weakref-finalizationregistry-03.svg new file mode 100644 index 0000000000..67425a58fd --- /dev/null +++ b/1-js/99-js-misc/09-weakref-finalizationregistry/weakref-finalizationregistry-03.svg @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + ключ + значение + image-01.jpg + image-02.jpg + image-03.jpg + + + + + + + + + + + + + + Объект WeakRef + + + + + + + + + + + + + + + + Объект WeakRef + + + + + + + + + + + + + + + + + + + Объект WeakRef + + + + + + + \ No newline at end of file diff --git a/1-js/99-js-misc/09-weakref-finalizationregistry/weakref-finalizationregistry-04.svg b/1-js/99-js-misc/09-weakref-finalizationregistry/weakref-finalizationregistry-04.svg new file mode 100644 index 0000000000..64770a0780 --- /dev/null +++ b/1-js/99-js-misc/09-weakref-finalizationregistry/weakref-finalizationregistry-04.svg @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + ключ + значение + image-01.jpg + image-02.jpg + image-03.jpg + + + + + + + + + + + + + + Объект WeakRef + + + + + + + + + + + + + + + + Объект WeakRef + + + + + undefined + undefined + + + + + + + + + + + + + + + Объект WeakRef + + + \ No newline at end of file diff --git a/1-js/99-js-misc/09-weakref-finalizationregistry/weakref-finalizationregistry-05.svg b/1-js/99-js-misc/09-weakref-finalizationregistry/weakref-finalizationregistry-05.svg new file mode 100644 index 0000000000..423dfba958 --- /dev/null +++ b/1-js/99-js-misc/09-weakref-finalizationregistry/weakref-finalizationregistry-05.svg @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + image-02.jpg + image-03.jpg + + ключ + значение + image-01.jpg + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Объект WeakRef + + + + + + + + + + + + + + + + Объект WeakRef + + + + + undefined + undefined + Удалено колбэком очистки FinalizationRegistry + + + + + + + + + + + + + + + Объект WeakRef + + + + \ No newline at end of file diff --git a/1-js/99-js-misc/09-weakref-finalizationregistry/weakref-finalizationregistry-demo-02.png b/1-js/99-js-misc/09-weakref-finalizationregistry/weakref-finalizationregistry-demo-02.png new file mode 100644 index 0000000000..7d8bb01e88 Binary files /dev/null and b/1-js/99-js-misc/09-weakref-finalizationregistry/weakref-finalizationregistry-demo-02.png differ diff --git a/1-js/99-js-misc/09-weakref-finalizationregistry/weakref-finalizationregistry-demo-03.gif b/1-js/99-js-misc/09-weakref-finalizationregistry/weakref-finalizationregistry-demo-03.gif new file mode 100644 index 0000000000..b81966dda4 Binary files /dev/null and b/1-js/99-js-misc/09-weakref-finalizationregistry/weakref-finalizationregistry-demo-03.gif differ diff --git a/1-js/99-js-misc/09-weakref-finalizationregistry/weakref-finalizationregistry-demo-04.jpg b/1-js/99-js-misc/09-weakref-finalizationregistry/weakref-finalizationregistry-demo-04.jpg new file mode 100644 index 0000000000..ba60f1e860 Binary files /dev/null and b/1-js/99-js-misc/09-weakref-finalizationregistry/weakref-finalizationregistry-demo-04.jpg differ diff --git a/1-js/99-js-misc/09-weakref-finalizationregistry/weakref-finalizationregistry-demo-05.gif b/1-js/99-js-misc/09-weakref-finalizationregistry/weakref-finalizationregistry-demo-05.gif new file mode 100644 index 0000000000..d34bda4d73 Binary files /dev/null and b/1-js/99-js-misc/09-weakref-finalizationregistry/weakref-finalizationregistry-demo-05.gif differ diff --git a/1-js/99-js-misc/09-weakref-finalizationregistry/weakref-finalizationregistry-demo-06.jpg b/1-js/99-js-misc/09-weakref-finalizationregistry/weakref-finalizationregistry-demo-06.jpg new file mode 100644 index 0000000000..b2655540f9 Binary files /dev/null and b/1-js/99-js-misc/09-weakref-finalizationregistry/weakref-finalizationregistry-demo-06.jpg differ diff --git a/1-js/99-js-misc/09-weakref-finalizationregistry/weakref-finalizationregistry-demo-07.gif b/1-js/99-js-misc/09-weakref-finalizationregistry/weakref-finalizationregistry-demo-07.gif new file mode 100644 index 0000000000..51f8745188 Binary files /dev/null and b/1-js/99-js-misc/09-weakref-finalizationregistry/weakref-finalizationregistry-demo-07.gif differ diff --git a/1-js/99-js-misc/09-weakref-finalizationregistry/weakref-finalizationregistry-demo-08.jpg b/1-js/99-js-misc/09-weakref-finalizationregistry/weakref-finalizationregistry-demo-08.jpg new file mode 100644 index 0000000000..5f98aec14d Binary files /dev/null and b/1-js/99-js-misc/09-weakref-finalizationregistry/weakref-finalizationregistry-demo-08.jpg differ diff --git a/1-js/99-js-misc/09-weakref-finalizationregistry/weakref-finalizationregistry.view/index.css b/1-js/99-js-misc/09-weakref-finalizationregistry/weakref-finalizationregistry.view/index.css new file mode 100644 index 0000000000..e6c9e39606 --- /dev/null +++ b/1-js/99-js-misc/09-weakref-finalizationregistry/weakref-finalizationregistry.view/index.css @@ -0,0 +1,285 @@ +:root { + --mineralGreen: 60, 98, 85; + --viridianGreen: 97, 135, 110; + --swampGreen: 166, 187, 141; + --fallGreen: 234, 231, 177; + --brinkPink: #FA7070; + --silverChalice: 178, 178, 178; + --white: 255, 255, 255; + --black: 0, 0, 0; + + --topBarHeight: 64px; + --itemPadding: 32px; + --containerGap: 8px; +} + +@keyframes zoom-in { + 0% { + transform: scale(1, 1); + } + + 100% { + transform: scale(1.30, 1.30); + } +} + +body, html { + margin: 0; + padding: 0; +} + +.app { + min-height: 100vh; + background-color: rgba(var(--viridianGreen), 0.5); +} + +.header { + height: var(--topBarHeight); + padding: 0 24px; + display: flex; + justify-content: space-between; + align-items: center; + background-color: rgba(var(--mineralGreen), 1); +} + +.header-text { + color: white; +} + +.container { + display: flex; + gap: 24px; + padding: var(--itemPadding); +} + +.item { + width: 50%; +} + +.item--scrollable { + overflow-y: scroll; + height: calc(100vh - var(--topBarHeight) - (var(--itemPadding) * 2)); +} + +.thumbnails-container { + display: flex; + flex-wrap: wrap; + gap: 8px; + justify-content: center; + align-items: center; +} + +.thumbnail-item { + width: calc(25% - var(--containerGap)); + cursor: pointer; + position: relative; +} + +.thumbnail-item:hover { + z-index: 1; + animation: zoom-in 0.1s forwards; +} + +.thumbnail-item--selected { + outline: 3px solid rgba(var(--fallGreen), 1); + outline-offset: -3px; +} + +.badge { + width: 16px; + height: 16px; + display: flex; + justify-content: center; + align-items: center; + padding: 4px; + position: absolute; + right: 8px; + bottom: 8px; + border-radius: 50%; + border: 2px solid rgba(var(--fallGreen), 1); + background-color: rgba(var(--swampGreen), 1); +} + +.check { + display: inline-block; + transform: rotate(45deg); + border-bottom: 2px solid white; + border-right: 2px solid white; + width: 6px; + height: 12px; +} + +.img { + width: 100%; + height: 100%; + object-fit: cover; +} + +.actions { + display: flex; + flex-wrap: wrap; + justify-content: center; + align-content: center; + padding: 0 0 16px 0; + gap: 8px; +} + +.select { + padding: 16px; + cursor: pointer; + font-weight: 700; + color: rgba(var(--black), 1); + border: 2px solid rgba(var(--swampGreen), 0.5); + background-color: rgba(var(--swampGreen), 1); +} + +.select:disabled { + cursor: not-allowed; + background-color: rgba(var(--silverChalice), 1); + color: rgba(var(--black), 0.5); + border: 2px solid rgba(var(--black), 0.25); +} + +.btn { + outline: none; + padding: 16px; + cursor: pointer; + font-weight: 700; + color: rgba(var(--black), 1); + border: 2px solid rgba(var(--black), 0.5); +} + +.btn--primary { + background-color: rgba(var(--mineralGreen), 1); +} + +.btn--primary:hover:not([disabled]) { + background-color: rgba(var(--mineralGreen), 0.85); +} + +.btn--secondary { + background-color: rgba(var(--viridianGreen), 0.5); +} + +.btn--secondary:hover:not([disabled]) { + background-color: rgba(var(--swampGreen), 0.25); +} + +.btn--success { + background-color: rgba(var(--fallGreen), 1); +} + +.btn--success:hover:not([disabled]) { + background-color: rgba(var(--fallGreen), 0.85); +} + +.btn:disabled { + cursor: not-allowed; + background-color: rgba(var(--silverChalice), 1); + color: rgba(var(--black), 0.5); + border: 2px solid rgba(var(--black), 0.25); +} + +.previewContainer { + margin-bottom: 16px; + display: flex; + width: 100%; + height: 40vh; + overflow: scroll; + border: 3px solid rgba(var(--black), 1); +} + +.previewContainer--disabled { + background-color: rgba(var(--black), 0.1); + cursor: not-allowed; +} + +.canvas { + margin: auto; + display: none; +} + +.canvas--ready { + display: block; +} + +.spinnerContainer { + display: flex; + gap: 8px; + flex-direction: column; + align-content: center; + align-items: center; + margin: auto; +} + +.spinnerContainer--hidden { + display: none; +} + +.spinnerText { + margin: 0; + color: rgba(var(--mineralGreen), 1); +} + +.spinner { + display: inline-block; + width: 50px; + height: 50px; + margin: auto; + border: 3px solid rgba(var(--mineralGreen), 0.3); + border-radius: 50%; + border-top-color: rgba(var(--mineralGreen), 0.9); + animation: spin 1s ease-in-out infinite; +} + +@keyframes spin { + to { + transform: rotate(360deg); + } +} + +.loggerContainer { + display: flex; + flex-direction: column; + gap: 8px; + padding: 0 8px 8px 8px; + width: 100%; + min-height: 30vh; + max-height: 30vh; + overflow: scroll; + border-left: 3px solid rgba(var(--black), 0.25); +} + +.logger-title { + display: flex; + align-items: center; + padding: 8px; + position: sticky; + height: 40px; + min-height: 40px; + top: 0; + left: 0; + background-color: rgba(var(--viridianGreen), 1); + font-size: 24px; + font-weight: 700; + margin: 0; +} + +.logger-item { + font-size: 14px; + padding: 8px; + border: 2px solid #5a5a5a; + color: white; +} + +.logger--primary { + background-color: #13315a; +} + +.logger--success { + background-color: #385a4e; +} + +.logger--error { + background-color: #5a1a24; +} \ No newline at end of file diff --git a/1-js/99-js-misc/09-weakref-finalizationregistry/weakref-finalizationregistry.view/index.html b/1-js/99-js-misc/09-weakref-finalizationregistry/weakref-finalizationregistry.view/index.html new file mode 100644 index 0000000000..e3637c46ec --- /dev/null +++ b/1-js/99-js-misc/09-weakref-finalizationregistry/weakref-finalizationregistry.view/index.html @@ -0,0 +1,48 @@ + + + + + + + + + + +
+
+

+ Photo Library Collage +

+
+
+
+ +
+
+
+
+
+ + + + +
+
+
+
+

+
+ +
+
+

Logger:

+
+
+
+
+
+ + + + + diff --git a/1-js/99-js-misc/09-weakref-finalizationregistry/weakref-finalizationregistry.view/index.js b/1-js/99-js-misc/09-weakref-finalizationregistry/weakref-finalizationregistry.view/index.js new file mode 100644 index 0000000000..983b34d9ab --- /dev/null +++ b/1-js/99-js-misc/09-weakref-finalizationregistry/weakref-finalizationregistry.view/index.js @@ -0,0 +1,228 @@ +import { + createImageFile, + loadImage, + weakRefCache, + LAYOUTS, + images, + THUMBNAIL_PARAMS, + stateObj, +} from "./utils.js"; + +export const state = new Proxy(stateObj, { + set(target, property, value) { + const previousValue = target[property]; + + target[property] = value; + + if (previousValue !== value) { + handleStateChange(target); + } + + return true; + }, +}); + +// Elements. +const thumbnailsContainerEl = document.querySelector(".thumbnails-container"); +const selectEl = document.querySelector(".select"); +const previewContainerEl = document.querySelector(".previewContainer"); +const canvasEl = document.querySelector(".canvas"); +const createCollageBtn = document.querySelector(".btn-create-collage"); +const startOverBtn = document.querySelector(".btn-start-over"); +const downloadBtn = document.querySelector(".btn-download"); +const spinnerContainerEl = document.querySelector(".spinnerContainer"); +const spinnerTextEl = document.querySelector(".spinnerText"); +const loggerContainerEl = document.querySelector(".loggerContainer"); + +// Renders. +// Render thumbnails previews. +images.forEach((img) => { + const thumbnail = document.createElement("div"); + thumbnail.classList.add("thumbnail-item"); + + thumbnail.innerHTML = ` + + `; + + thumbnail.addEventListener("click", (e) => handleSelection(e, img)); + + thumbnailsContainerEl.appendChild(thumbnail); +}); +// Render layouts select. +LAYOUTS.forEach((layout) => { + const option = document.createElement("option"); + option.value = JSON.stringify(layout); + option.innerHTML = layout.name; + selectEl.appendChild(option); +}); + +const handleStateChange = (state) => { + if (state.loading) { + selectEl.disabled = true; + createCollageBtn.disabled = true; + startOverBtn.disabled = true; + downloadBtn.disabled = true; + previewContainerEl.classList.add("previewContainer--disabled"); + spinnerContainerEl.classList.remove("spinnerContainer--hidden"); + spinnerTextEl.innerText = "Loading..."; + canvasEl.classList.remove("canvas--ready"); + } else if (!state.loading) { + selectEl.disabled = false; + createCollageBtn.disabled = false; + startOverBtn.disabled = false; + downloadBtn.disabled = false; + previewContainerEl.classList.remove("previewContainer--disabled"); + spinnerContainerEl.classList.add("spinnerContainer--hidden"); + canvasEl.classList.add("canvas--ready"); + } + + if (!state.selectedImages.size) { + createCollageBtn.disabled = true; + document.querySelectorAll(".badge").forEach((item) => item.remove()); + } else if (state.selectedImages.size && !state.loading) { + createCollageBtn.disabled = false; + } + + if (!state.collageRendered) { + downloadBtn.disabled = true; + } else if (state.collageRendered) { + downloadBtn.disabled = false; + } +}; +handleStateChange(state); + +const handleSelection = (e, imgName) => { + const imgEl = e.currentTarget; + + imgEl.classList.toggle("thumbnail-item--selected"); + + if (state.selectedImages.has(imgName)) { + state.selectedImages.delete(imgName); + state.selectedImages = new Set(state.selectedImages); + imgEl.querySelector(".badge")?.remove(); + } else { + state.selectedImages = new Set(state.selectedImages.add(imgName)); + + const badge = document.createElement("div"); + badge.classList.add("badge"); + badge.innerHTML = ` +
+ `; + imgEl.prepend(badge); + } +}; + +// Make a wrapper function. +let getCachedImage; +(async () => { + getCachedImage = await weakRefCache(loadImage); +})(); + +const calculateGridRows = (blobsLength) => + Math.ceil(blobsLength / state.currentLayout.columns); + +const drawCollage = (images) => { + state.drawing = true; + + let context = canvasEl.getContext("2d"); + + /** + * Calculate canvas dimensions based on the current layout. + * */ + context.canvas.width = + state.currentLayout.itemWidth * state.currentLayout.columns; + context.canvas.height = + calculateGridRows(images.length) * state.currentLayout.itemHeight; + + let currentRow = 0; + let currentCanvasDx = 0; + let currentCanvasDy = 0; + + for (let i = 0; i < images.length; i++) { + /** + * Get current row of the collage. + * */ + if (i % state.currentLayout.columns === 0) { + currentRow += 1; + currentCanvasDx = 0; + + if (currentRow > 1) { + currentCanvasDy += state.currentLayout.itemHeight; + } + } + + context.drawImage( + images[i], + 0, + 0, + images[i].width, + images[i].height, + currentCanvasDx, + currentCanvasDy, + state.currentLayout.itemWidth, + state.currentLayout.itemHeight, + ); + + currentCanvasDx += state.currentLayout.itemWidth; + } + + state.drawing = false; + state.collageRendered = true; +}; + +const createCollage = async () => { + state.loading = true; + + const images = []; + + for (const image of state.selectedImages.values()) { + const blobImage = await getCachedImage(image.img); + + const url = URL.createObjectURL(blobImage); + const img = await createImageFile(url); + + images.push(img); + URL.revokeObjectURL(url); + } + + state.loading = false; + + drawCollage(images); +}; + +/** + * Clear all settled data to start over. + * */ +const startOver = () => { + state.selectedImages = new Set(); + state.collageRendered = false; + const context = canvasEl.getContext("2d"); + context.clearRect(0, 0, canvasEl.width, canvasEl.height); + + document + .querySelectorAll(".thumbnail-item--selected") + .forEach((item) => item.classList.remove("thumbnail-item--selected")); + + loggerContainerEl.innerHTML = '

Logger:

'; +}; + +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/99-js-misc/index.md b/1-js/99-js-misc/index.md new file mode 100644 index 0000000000..098860a9be --- /dev/null +++ b/1-js/99-js-misc/index.md @@ -0,0 +1,2 @@ + +# Разное diff --git a/1-js/index.md b/1-js/index.md index 5855dd903c..db327c8555 100644 --- a/1-js/index.md +++ b/1-js/index.md @@ -1,6 +1,5 @@ -# Язык программирования JavaScript +# Язык JavaScript Здесь вы можете изучить JavaScript, начиная с нуля и заканчивая продвинутыми концепциями вроде ООП. -Мы сосредоточимся на самом языке, с минимальными замечаниями насчет того окружения, где этот язык используется. - +Мы сосредоточимся на самом языке, изредка добавляя заметки о средах его исполнения. diff --git a/10-misc/12-mutation-observer/article.md b/10-misc/12-mutation-observer/article.md deleted file mode 100644 index 1045e87a80..0000000000 --- a/10-misc/12-mutation-observer/article.md +++ /dev/null @@ -1,249 +0,0 @@ - -# Mutation observer - -`MutationObserver` is a built-in object that observes a DOM element and fires a callback in case of changes. - -We'll first see syntax, and then explore a real-world use case. - -## Syntax - -`MutationObserver` is easy to use. - -First, we create an observer with a callback-function: - -```js -let observer = new MutationObserver(callback); -``` - -And then attach it to a DOM node: - -```js -observer.observe(node, config); -``` - -`config` is an object with boolean options "what kind of changes to react on": -- `childList` -- changes in the direct children of `node`, -- `subtree` -- in all descendants of `node`, -- `attributes` -- attributes of `node`, -- `attributeOldValue` -- record the old value of attribute (infers `attributes`), -- `characterData` -- whether to observe `node.data` (text content), -- `characterDataOldValue` -- record the old value of `node.data` (infers `characterData`), -- `attributeFilter` -- an array of attribute names, to observe only selected ones. - -Then after any changes, the `callback` is executed, with a list of [MutationRecord](https://dom.spec.whatwg.org/#mutationrecord) objects as the first argument, and the observer itself as the second argument. - -[MutationRecord](https://dom.spec.whatwg.org/#mutationrecord) objects have properties: - -- `type` -- mutation type, one of - - `"attributes"` (attribute modified) - - `"characterData"` (data modified) - - `"childList"` (elements added/removed), -- `target` -- where the change occured: an element for "attributes", or text node for "characterData", or an element for a "childList" mutation, -- `addedNodes/removedNodes` -- nodes that were added/removed, -- `previousSibling/nextSibling` -- the previous and next sibling to added/removed nodes, -- `attributeName/attributeNamespace` -- the name/namespace (for XML) of the changed attribute, -- `oldValue` -- the previous value, only for attribute or text changes. - - -For example, here's a `
` with `contentEditable` attribute. That attribute allows us to focus on it and edit. - -```html run -
Edit me, please
- - -``` - -If we change the text inside `me`, we'll get a single mutation: - -```js -mutationRecords = [{ - type: "characterData", - oldValue: "me", - target: , - // other properties empty -}]; -``` - -If we select and remove the `me` altogether, we'll get multiple mutations: - -```js -mutationRecords = [{ - type: "childList", - target: , - removedNodes: [], - nextSibling: , - previousSibling: - // other properties empty -}, { - type: "characterData" - target: - // ...details depend on how the browser handles the change - // it may coalesce two adjacent text nodes "Edit " and ", please" into one node - // or it can just delete the extra space after "Edit". - // may be one mutation or a few -}]; -``` - -## Observer use case - -When `MutationObserver` is needed? Is there a scenario when such thing can be useful? - -Sure, we can track something like `contentEditable` and create "undo/redo" stack, but here's an example where `MutationObserver` is good from architectural standpoint. - -Let's say we're making a website about programming, like this one. Naturally, articles and other materials may contain source code snippets. - -An HTML code snippet looks like this: -```html -... -

-  // here's the code
-  let hello = "world";
-
-... -``` - -There's also a JavaScript highlighting library, e.g. [Prism.js](https://prismjs.com/). A call to `Prism.highlightElem(pre)` examines the contents of such `pre` elements and adds colored syntax highlighting, similar to what you in examples here, this page. - -Generally, when a page loads, e.g. at the bottom of the page, we can search for elements `pre[class*="language"]` and call `Prism.highlightElem` on them: - -```js -// highlight all code snippets on the page -document.querySelectorAll('pre[class*="language"]').forEach(Prism.highlightElem); -``` - -Now the `
` snippet looks like this (without line numbers by default):
-
-```js
-// here's the code
-let hello = "world";
-```
-
-Everything's simple so far, right? There are `
` code snippets in HTML, we highlight them.
-
-Now let's go on. Let's say we're going to dynamically fetch materials from a server. We'll study methods for that [later in the tutorial](info:fetch-basics). For now it only matters that we fetch an HTML article from a webserver and display it on demand:
-
-```js
-let article = /* fetch new content from server */
-articleElem.innerHTML = article;
-```
-
-The new `article` HTML may contain code snippets. We need to call `Prism.highlightElem` on them, otherwise they won't get highlighted.
-
-**Who's responsibility is to call `Prism.highlightElem` for a dynamically loaded article?**
-
-We could append that call to the code that loads an article, like this:
-
-```js
-let article = /* fetch new content from server */
-articleElem.innerHTML = article;
-
-*!*
-let snippets = articleElem.querySelectorAll('pre[class*="language-"]');
-snippets.forEach(Prism.highlightElem);
-*/!*
-```
-
-...But imagine, we have many places where we load contents with code: articles, quizzes, forum posts. Do we need to put the highlighting call everywhere? Then we need to be careful, not to forget about it.
-
-And what if we load the content into a third-party engine? E.g. we have a forum written by someone else, that loads contents dynamically, and we'd like to add syntax highlighting to it. No one likes to patch third-party scripts.
-
-Luckily, there's another option.
-
-We can use `MutationObserver` to automatically detect code snippets inserted in the page and highlight them.
-
-So we'll handle the highlighting functionality in one place, relieving us from the need to integrate it.
-
-## Dynamic highlight demo
-
-Here's the working example.
-
-If you run this code, it starts observing the element below and highlighting any code snippets that appear there:
-
-```js run
-let observer = new MutationObserver(mutations => {
-
-  for(let mutation of mutations) {
-    // examine new nodes
-
-    for(let node of mutation.addedNodes) {
-      // skip newly added text nodes
-      if (!(node instanceof HTMLElement)) continue;
-
-      // check the inserted element for being a code snippet
-      if (node.matches('pre[class*="language-"]')) {
-        Prism.highlightElement(node);
-      }
-
-      // search its subtree for code snippets
-      for(let elem of node.querySelectorAll('pre[class*="language-"]')) {
-        Prism.highlightElement(elem);
-      }
-    }
-  }
-
-});
-
-let demoElem = document.getElementById('highlight-demo');
-
-observer.observe(demoElem, {childList: true, subtree: true});
-```
-
-

Demo element with id="highlight-demo", obverved by the example above.

- -The code below populates `innerHTML`. If you've run the code above, snippets will get highlighted: - -```js run -let demoElem = document.getElementById('highlight-demo'); - -// dynamically insert content with code snippets -demoElem.innerHTML = `A code snippet is below: -
 let hello = "world!"; 
-
Another one:
-
-
.class { margin: 5px; } 
-
-`; -``` - -Now we have `MutationObserver` that can track all highlighting in observed elements or the whole `document`. We can add/remove code snippets in HTML without thinking about it. - - -## Garbage collection - -Observers use weak references to nodes internally. That is: if a node is removed from DOM, and becomes unreachable, then it becomes garbage collected, an observer doesn't prevent that. - -Still, we can release observers any time: - -- `observer.disconnect()` -- stops the observation. - -Additionally: - -- `mutationRecords = observer.takeRecords()` -- gets a list of unprocessed mutation records, those that happened, but the callback did not handle them. - -```js -// we're going to disconnect the observer -// it might have not yet handled some mutations -let mutationRecords = observer.takeRecords(); -// process mutationRecords - -// now all handled, disconnect -observer.disconnect(); -``` - -## Summary - -`MutationObserver` can react on changes in DOM: attributes, added/removed elements, text content. - -We can use it to track changes introduced by other parts of our own or 3rd-party code. - -For example, to post-process dynamically inserted content, as demo `innerHTML`, like highlighting in the example above. diff --git a/10-misc/index.md b/10-misc/index.md deleted file mode 100644 index 65ab3188a0..0000000000 --- a/10-misc/index.md +++ /dev/null @@ -1,4 +0,0 @@ - -# Miscellaneous - -Not yet categorized articles. diff --git a/2-ui/1-document/01-browser-environment/article.md b/2-ui/1-document/01-browser-environment/article.md index 6ff17f9b82..a461966b8b 100644 --- a/2-ui/1-document/01-browser-environment/article.md +++ b/2-ui/1-document/01-browser-environment/article.md @@ -1,121 +1,115 @@ -# Browser environment, specs +# Браузерное окружение, спецификации -The JavaScript language was initially created for web browsers. Since then, it has evolved and become a language with many uses and platforms. +Язык JavaScript изначально был создан для веб-браузеров. Но с тех пор он значительно эволюционировал и превратился в кроссплатформенный язык программирования для решения широкого круга задач. -A platform may be a browser, or a web-server, or a washing machine, or another *host*. Each of them provides platform-specific functionality. The JavaScript specification calls that a *host environment*. +Сегодня JavaScript может использоваться в браузере, на веб-сервере или в какой-то другой среде, даже в кофеварке. Каждая среда предоставляет свою функциональность, которую спецификация JavaScript называет *окружением*. -A host environment provides platform-specific objects and functions additional to the language core. Web browsers give a means to control web pages. Node.js provides server-side features, and so on. +Окружение предоставляет свои объекты и дополнительные функции, в дополнение базовым языковым. Браузеры, например, дают средства для управления веб-страницами. Node.js делает доступными какие-то серверные возможности и так далее. -Here's a bird's-eye view of what we have when JavaScript runs in a web-browser: +На картинке ниже в общих чертах показано, что доступно для JavaScript в браузерном окружении: -![](windowObjects.png) +![](windowObjects.svg) -There's a "root" object called `window`. It has two roles: +Как мы видим, имеется корневой объект `window`, который выступает в 2 ролях: -1. First, it is a global object for JavaScript code, as described in the chapter . -2. Second, it represents the "browser window" and provides methods to control it. +1. Во-первых, это глобальный объект для JavaScript-кода, об этом более подробно говорится в главе . +2. Во-вторых, он также представляет собой окно браузера и располагает методами для управления им. -For instance, here we use it as a global object: +Например, здесь мы используем `window` как глобальный объект: -```js run +```js run global function sayHi() { alert("Hello"); } -// global functions are accessible as properties of window +// глобальные функции доступны как методы глобального объекта: window.sayHi(); ``` -And here we use it as a browser window, to see the window height: +А здесь мы используем `window` как объект окна браузера, чтобы узнать его высоту: ```js run -alert(window.innerHeight); // inner window height +alert(window.innerHeight); // внутренняя высота окна браузера ``` -There are more window-specific methods and properties, we'll cover them later. +Существует гораздо больше свойств и методов для управления окном браузера. Мы рассмотрим их позднее. + +## DOM (Document Object Model) -## Document Object Model (DOM) +Document Object Model, сокращённо DOM - объектная модель документа, которая представляет все содержимое страницы в виде объектов, которые можно менять. -The `document` object gives access to the page content. We can change or create anything on the page using it. +Объект `document` - основная "входная точка". С его помощью мы можем что-то создавать или менять на странице. -For instance: +Например: ```js run -// change the background color to red +// заменим цвет фона на красный, document.body.style.background = "red"; -// change it back after 1 second +// а через секунду вернём как было setTimeout(() => document.body.style.background = "", 1000); ``` -Here we used `document.body.style`, but there's much, much more. Properties and methods are described in the specification. There happen to be two working groups who develop it: - -1. [W3C](https://en.wikipedia.org/wiki/World_Wide_Web_Consortium) -- the documentation is at . -2. [WhatWG](https://en.wikipedia.org/wiki/WHATWG), publishing at . - -As it happens, the two groups don't always agree, so it's like we have two sets of standards. But they are very similar and eventually things merge. The documentation that you can find on the given resources is very similar, with about a 99% match. There are very minor differences that you probably won't notice. - -Personally, I find more pleasant to use. +Мы использовали в примере только `document.body.style`, но на самом деле возможности по управлению страницей намного шире. Различные свойства и методы описаны в спецификации: -In the ancient past, there was no standard at all -- each browser implemented however it wanted. Different browsers had different sets, methods, and properties for the same thing, and developers had to write different code for each of them. Dark, messy times. +- **DOM Living Standard** на -Even now we can sometimes meet old code that uses browser-specific properties and works around incompatibilities. But, in this tutorial we'll use modern stuff: there's no need to learn old things until you really need to (chances are high that you won't). +```smart header="DOM - не только для браузеров" +Спецификация DOM описывает структуру документа и предоставляет объекты для манипуляций со страницей. Существуют и другие, отличные от браузеров, инструменты, использующие DOM. -Then the DOM standard appeared, in an attempt to bring everyone to an agreement. The first version was "DOM Level 1", then it was extended by DOM Level 2, then DOM Level 3, and now it's reached DOM Level 4. People from WhatWG group got tired of version numbers and are calling it just "DOM", without a number. So we'll do the same. - -```smart header="DOM is not only for browsers" -The DOM specification explains the structure of a document and provides objects to manipulate it. There are non-browser instruments that use it too. - -For instance, server-side tools that download HTML pages and process them use the DOM. They may support only a part of the specification though. +Например, серверные скрипты, которые загружают и обрабатывают HTML-страницы, также могут использовать DOM. При этом они могут поддерживать спецификацию не полностью. ``` -```smart header="CSSOM for styling" -CSS rules and stylesheets are not structured like HTML. There's a separate specification [CSSOM](https://www.w3.org/TR/cssom-1/) that explains how they are represented as objects, and how to read and write them. +```smart header="CSSOM для стилей" +Правила стилей CSS структурированы иначе чем HTML. Для них есть отдельная спецификация [CSSOM](https://www.w3.org/TR/cssom-1/), которая объясняет, как стили должны представляться в виде объектов, как их читать и писать. -CSSOM is used together with DOM when we modify style rules for the document. In practice though, CSSOM is rarely required, because usually CSS rules are static. We rarely need to add/remove CSS rules from JavaScript, so we won't cover it right now. +CSSOM используется вместе с DOM при изменении стилей документа. В реальности CSSOM требуется редко, обычно правила CSS статичны. Мы редко добавляем/удаляем стили из JavaScript, но и это возможно. ``` -## BOM (part of HTML spec) +## BOM (Browser Object Model) -Browser Object Model (BOM) are additional objects provided by the browser (host environment) to work with everything except the document. +Объектная модель браузера (Browser Object Model, BOM) - это дополнительные объекты, предоставляемые браузером (окружением), чтобы работать со всем, кроме документа. -For instance: +Например: -- The [navigator](mdn:api/Window/navigator) object provides background information about the browser and the operating system. There are many properties, but the two most widely known are: `navigator.userAgent` -- about the current browser, and `navigator.platform` -- about the platform (can help to differ between Windows/Linux/Mac etc). -- The [location](mdn:api/Window/location) object allows us to read the current URL and can redirect the browser to a new one. +- Объект [navigator](mdn:api/Window/navigator) даёт информацию о самом браузере и операционной системе. Среди множества его свойств самыми известными являются: `navigator.userAgent` -- информация о текущем браузере, и `navigator.platform` -- информация о платформе (может помочь в понимании того, в какой ОС открыт браузер -- Windows/Linux/Mac и так далее). +- Объект [location](mdn:api/Window/location) позволяет получить текущий URL и перенаправить браузер по новому адресу. -Here's how we can use the `location` object: +Вот как мы можем использовать объект `location`: ```js run -alert(location.href); // shows current URL -if (confirm("Go to wikipedia?")) { - location.href = "https://wikipedia.org"; // redirect the browser to another URL +alert(location.href); // показывает текущий URL +if (confirm("Перейти на Wikipedia?")) { + location.href = "https://wikipedia.org"; // перенаправляет браузер на другой URL } ``` -Functions `alert/confirm/prompt` are also a part of BOM: they are directly not related to the document, but represent pure browser methods of communicating with the user. - +Функции `alert/confirm/prompt` тоже являются частью BOM: они не относятся непосредственно к странице, но представляют собой методы объекта окна браузера для коммуникации с пользователем. -```smart header="HTML specification" -BOM is the part of the general [HTML specification](https://html.spec.whatwg.org). +```smart header="Спецификации" +BOM является частью общей [спецификации HTML](https://html.spec.whatwg.org). -Yes, you heard that right. The HTML spec at is not only about the "HTML language" (tags, attributes), but also covers a bunch of objects, methods and browser-specific DOM extensions. That's "HTML in broad terms". +Да, вы всё верно услышали. Спецификация HTML по адресу не только про "язык HTML" (теги, атрибуты), она также покрывает целое множество объектов, методов и специфичных для каждого браузера расширений DOM. Это всё "HTML в широком смысле". Для некоторых вещей есть отдельные спецификации, перечисленные на . ``` -## Summary +## Итого + +Говоря о стандартах, у нас есть: + +Спецификация DOM +: Описывает структуру документа, манипуляции с контентом и события, подробнее на . -Talking about standards, we have: +Спецификация CSSOM +: Описывает файлы стилей, правила написания стилей и манипуляций с ними, а также то, как это всё связано со страницей, подробнее на . -DOM specification -: Describes the document structure, manipulations and events, see . +Спецификация HTML +: Описывает язык HTML (например, теги) и BOM (объектную модель браузера) -- разные функции браузера: `setTimeout`, `alert`, `location` и так далее, подробнее на . Тут берётся за основу спецификация DOM и расширяется дополнительными свойствами и методами. -CSSOM specification -: Describes stylesheets and style rules, manipulations with them and their binding to documents, see . +Кроме того, некоторые классы описаны отдельно на . -HTML specification -: Describes the HTML language (e.g. tags) and also the BOM (browser object model) -- various browser functions: `setTimeout`, `alert`, `location` and so on, see . It takes the DOM specification and extends it with many additional properties and methods. +Пожалуйста, заметьте для себя эти ссылки, так как по ним содержится очень много информации, которую невозможно изучить полностью и держать в уме. -Now we'll get down to learning DOM, because the document plays the central role in the UI. +Когда вам нужно будет прочитать о каком-то свойстве или методе, справочник на сайте Mozilla тоже очень хороший ресурс, хотя ничто не сравнится с чтением спецификации: она сложная и объёмная, но сделает ваши знания максимально полными. -Please note the links above, as there's so much stuff to learn it's impossible to cover and remember everything. +Для поиска чего-либо обычно удобно использовать интернет-поиск со словами "WHATWG [термин]" или "MDN [термин]", например , . -When you'd like to read about a property or a method, the Mozilla manual at is a nice resource, but reading the corresponding spec may be better: it's more complex and longer to read, but will make your fundamental knowledge sound and complete. +А теперь давайте перейдём к изучению DOM, так как страница - это основа всего. diff --git a/2-ui/1-document/01-browser-environment/windowObjects.png b/2-ui/1-document/01-browser-environment/windowObjects.png deleted file mode 100644 index 5408ecb158..0000000000 Binary files a/2-ui/1-document/01-browser-environment/windowObjects.png and /dev/null differ diff --git a/2-ui/1-document/01-browser-environment/windowObjects.svg b/2-ui/1-document/01-browser-environment/windowObjects.svg new file mode 100644 index 0000000000..b7e18bb347 --- /dev/null +++ b/2-ui/1-document/01-browser-environment/windowObjects.svg @@ -0,0 +1 @@ +windowdocumentObjectnavigatorscreenlocationframeshistoryArrayFunctionXMLHttpRequestBOMJavaScriptDOM…… \ No newline at end of file diff --git a/2-ui/1-document/01-browser-environment/windowObjects@2x.png b/2-ui/1-document/01-browser-environment/windowObjects@2x.png deleted file mode 100644 index d84b380d57..0000000000 Binary files a/2-ui/1-document/01-browser-environment/windowObjects@2x.png and /dev/null differ diff --git a/2-ui/1-document/02-dom-nodes/article.md b/2-ui/1-document/02-dom-nodes/article.md index 7c75ea3bab..0856d16e75 100644 --- a/2-ui/1-document/02-dom-nodes/article.md +++ b/2-ui/1-document/02-dom-nodes/article.md @@ -4,19 +4,29 @@ libs: --- -# DOM дерево +# DOM-дерево Основой HTML-документа являются теги. -В соответствии с объектной моделью документа ("Document Object Mode", коротко DOM), каждый HTML-тег является объектом. Вложенные теги называют "дочерними" узлами родительского элемента. +В соответствии с объектной моделью документа ("Document Object Model", коротко DOM), каждый HTML-тег является объектом. Вложенные теги являются "детьми" родительского элемента. Текст, который находится внутри тега, также является объектом. -Текст, который находится внутри тега, также является объектом. +Все эти объекты доступны при помощи JavaScript, мы можем использовать их для изменения страницы. -Все эти объекты доступны для изменения при помощи JavaScript. +Например, `document.body` - объект для тега ``. + +Если запустить этот код, то `` станет красным на 3 секунды: + +```js run +document.body.style.background = 'red'; // сделать фон красным + +setTimeout(() => document.body.style.background = '', 3000); // вернуть назад +``` + +Это был лишь небольшой пример того, что может DOM. Скоро мы изучим много способов работать с DOM, но сначала нужно познакомиться с его структурой. ## Пример DOM -Для начала, исследуем дерево DOM в этом документе: +Начнём с такого, простого, документа: ```html run no-beautify @@ -30,21 +40,23 @@ libs: ``` -DOM -- это представление HTML-документа в виде дерева тегов. Вот как это выглядит: +DOM -- это представление HTML-документа в виде дерева тегов. Вот как оно выглядит:
```online -На рисунке выше, узлы-элементы можно кликать, и их дети будут скрываться и раскрываться. +На рисунке выше узлы-элементы можно кликать, и их дети будут скрываться и раскрываться. ``` -Теги будем называть *узлы-элементы* (или просто элементы). Вложенные теги становятся дочерними узлами родительского элемента. И в результате мы получаем дерево элементов: `` -- это корневой узел, а `` и `` его дочерние узлы, и т.д. +Каждый узел этого дерева - это объект. + +Теги являются *узлами-элементами* (или просто элементами). Они образуют структуру дерева: `` -- это корневой узел, `` и `` его дочерние узлы и т.д. Текст внутри элементов образует *текстовые узлы*, обозначенные как `#text`. Текстовый узел содержит в себе только строку текста. У него не может быть потомков, т.е. он находится всегда на самом нижнем уровне. @@ -55,13 +67,13 @@ drawHtmlTree(node1, 'div.domtree', 690, 320); - перевод строки: `↵` (в JavaScript он обозначается как `\n`) - пробел: `␣` -Пробелы и переводы строки -- это полноправные символы, которые образуют текстовые узлы и становятся частью дерева DOM. Так, в примере выше в теге `` есть несколько пробелов перед ``, которые образуют текстовый узел `#text` (он содержит в себе только перенос строки и несколько пробелов). +Пробелы и переводы строки -- это полноправные символы, как буквы и цифры. Они образуют текстовые узлы и становятся частью дерева DOM. Так, в примере выше в теге `<head>` есть несколько пробелов перед `<title>`, которые образуют текстовый узел `#text` (он содержит в себе только перенос строки и несколько пробелов). Существует всего два исключения из этого правила: 1. По историческим причинам пробелы и перевод строки перед тегом `<head>` игнорируются -2. Если мы записываем что-либо после закрывающего тега `</body>`, браузер автоматически перемещает эту запись в конец `body`, поскольку спецификация HTML требует чтобы все содержимое было внутри `<body>`. Поэтому после закрывающего тега `</body>` не может быть никаких пробелов. +2. Если мы записываем что-либо после закрывающего тега `</body>`, браузер автоматически перемещает эту запись в конец `body`, поскольку спецификация HTML требует, чтобы всё содержимое было внутри `<body>`. Поэтому после закрывающего тега `</body>` не может быть никаких пробелов. -В остальных случаях все просто -- если в документе есть пробелы (или любые другие символы), они становятся текстовыми узлами дерева DOM, и если мы их удалим, то в DOM их тоже не будет. +В остальных случаях всё просто -- если в документе есть пробелы (или любые другие символы), они становятся текстовыми узлами дерева DOM, и если мы их удалим, то в DOM их тоже не будет. Здесь пробельных текстовых узлов нет: @@ -73,27 +85,26 @@ drawHtmlTree(node1, 'div.domtree', 690, 320); <div class="domtree"></div> <script> -let node2 = {"name":"HTML","nodeType":1,"children":[{"name":"HEAD","nodeType":1,"children":[{"name":"TITLE","nodeType":1,"children":[{"name":"#text","nodeType":3,"content":"About elks"}]}]},{"name":"BODY","nodeType":1,"children":[{"name":"#text","nodeType":3,"content":"The truth about elks."}]}]} +let node2 = {"name":"HTML","nodeType":1,"children":[{"name":"HEAD","nodeType":1,"children":[{"name":"TITLE","nodeType":1,"children":[{"name":"#text","nodeType":3,"content":"О лосях"}]}]},{"name":"BODY","nodeType":1,"children":[{"name":"#text","nodeType":3,"content":"Правда о лосях."}]}]} drawHtmlTree(node2, 'div.domtree', 690, 210); </script> -```smart header="Edge spaces and in-between empty text are usually hidden in tools" +```smart header="Пробелы по краям строк и пробельные текстовые узлы скрыты в инструментах разработки" Когда мы работаем с деревом DOM, используя инструменты разработчика в браузере (которые мы рассмотрим позже), пробелы в начале/конце текста и пустые текстовые узлы (переносы строк) между тегами обычно не отображаются. -Потому что в основном они используются для лучшей читабельности HTML, и в большинстве случаев не влияют на отображение элементов на странице. +Таким образом инструменты разработки экономят место на экране. -В дальнейших иллюстрациях DOM мы будем для краткости пропускать пробельные текстовые узлы там, где они не имеют значения. +В дальнейших иллюстрациях DOM мы также будем для краткости пропускать пробельные текстовые узлы там, где они не имеют значения. Обычно они не влияют на то, как отображается документ. ``` - ## Автоисправление Если браузер сталкивается с некорректно написанным HTML-кодом, он автоматически корректирует его при построении DOM. Например, в начале документа всегда должен быть тег `<html>`. Даже если его нет в документе -- он будет в дереве DOM, браузер его создаст. То же самое касается и тега `<body>`. -Например, если HTML-файл состоит из единственного слова `"Привет"`, браузер обернет его в теги `<html>` и `<body>`, добавит необходимый тег `<head>`, и DOM будет выглядеть так: +Например, если HTML-файл состоит из единственного слова `"Привет"`, браузер обернёт его в теги `<html>` и `<body>`, добавит необходимый тег `<head>`, и DOM будет выглядеть так: <div class="domtree"></div> @@ -106,7 +117,7 @@ drawHtmlTree(node3, 'div.domtree', 690, 150); При генерации DOM браузер самостоятельно обрабатывает ошибки в документе, закрывает теги и так далее. -Такой документ с незакрытыми тегами: +Есть такой документ с незакрытыми тегами: ```html no-beautify <p>Привет @@ -126,7 +137,7 @@ drawHtmlTree(node4, 'div.domtree', 690, 360); </script> ````warn header="Таблицы всегда содержат `<tbody>`" -Важный "особый случай" -- работа с таблицами. По стандарту DOM у них должен быть `<tbody>`, но в HTML их можно написать (официально) без него . В этом случае браузер добавляет `<tbody>` в DOM самостоятельно. +Важный "особый случай" -- работа с таблицами. По стандарту DOM у них должен быть `<tbody>`, но в HTML их можно написать (официально) без него. В этом случае браузер добавляет `<tbody>` в DOM самостоятельно. Для такого HTML: @@ -143,12 +154,14 @@ let node5 = {"name":"TABLE","nodeType":1,"children":[{"name":"TBODY","nodeType": drawHtmlTree(node5, 'div.domtree', 600, 200); </script> -Видите? Из пустоты появился `<tbody>`, как будто документ был таким. Важно знать об этом, иначе при работе с таблицами возможны сюрпризы. +Видите? Из пустоты появился `<tbody>`, как будто документ и был таким. Важно знать об этом, иначе при работе с таблицами возможны сюрпризы. ```` ## Другие типы узлов -Добавим на страницу новые теги и комментарий: +Есть и некоторые другие типы узлов, кроме элементов и текстовых узлов. + +Например, узел-комментарий: ```html <!DOCTYPE HTML> @@ -158,7 +171,7 @@ drawHtmlTree(node5, 'div.domtree', 600, 200); <ol> <li>Лось -- животное хитрое</li> *!* - <!-- comment --> + <!-- комментарий --> */!* <li>...и коварное!</li> </ol> @@ -169,12 +182,12 @@ drawHtmlTree(node5, 'div.domtree', 600, 200); <div class="domtree"></div> <script> -let node6 = {"name":"HTML","nodeType":1,"children":[{"name":"HEAD","nodeType":1,"children":[]},{"name":"BODY","nodeType":1,"children":[{"name":"#text","nodeType":3,"content":"\n Правда о лосях.\n "},{"name":"OL","nodeType":1,"children":[{"name":"#text","nodeType":3,"content":"\n "},{"name":"LI","nodeType":1,"children":[{"name":"#text","nodeType":3,"content":"Лось -- животное хитрое"}]},{"name":"#text","nodeType":3,"content":"\n "},{"name":"#comment","nodeType":8,"content":"comment"},{"name":"#text","nodeType":3,"content":"\n "},{"name":"LI","nodeType":1,"children":[{"name":"#text","nodeType":3,"content":"...и коварное!"}]},{"name":"#text","nodeType":3,"content":"\n "}]},{"name":"#text","nodeType":3,"content":"\n \n"}]}]}; +let node6 = {"name":"HTML","nodeType":1,"children":[{"name":"HEAD","nodeType":1,"children":[]},{"name":"BODY","nodeType":1,"children":[{"name":"#text","nodeType":3,"content":"\n Правда о лосях.\n "},{"name":"OL","nodeType":1,"children":[{"name":"#text","nodeType":3,"content":"\n "},{"name":"LI","nodeType":1,"children":[{"name":"#text","nodeType":3,"content":"Лось -- животное хитрое"}]},{"name":"#text","nodeType":3,"content":"\n "},{"name":"#comment","nodeType":8,"content":"комментарий"},{"name":"#text","nodeType":3,"content":"\n "},{"name":"LI","nodeType":1,"children":[{"name":"#text","nodeType":3,"content":"...и коварное!"}]},{"name":"#text","nodeType":3,"content":"\n "}]},{"name":"#text","nodeType":3,"content":"\n \n"}]}]}; drawHtmlTree(node6, 'div.domtree', 690, 500); </script> -Здесь мы видим новый узел нового типа -- *комментарий*, обозначенный как `#comment`. +Здесь мы видим узел нового типа -- *комментарий*, обозначенный как `#comment`, между двумя текстовыми узлами. Казалось бы -- зачем комментарий в DOM? Он никак не влияет на визуальное отображение. Но есть важное правило: если что-то есть в HTML, то оно должно быть в DOM-дереве. @@ -182,7 +195,7 @@ drawHtmlTree(node6, 'div.domtree', 690, 500); Даже директива `<!DOCTYPE...>`, которую мы ставим в начале HTML, тоже является DOM-узлом. Она находится в дереве DOM прямо перед `<html>`. Мы не будем рассматривать этот узел, мы даже не рисуем его на наших диаграммах, но он существует. -Даже объект `document`, представляющий весь документ, формально, является DOM узлом. +Даже объект `document`, представляющий весь документ, формально является DOM-узлом. Существует [12 типов узлов](https://dom.spec.whatwg.org/#node). Но на практике мы в основном работаем с 4 из них: @@ -193,30 +206,28 @@ 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 - это использовать инструменты разработчика браузера. Это то, что мы каждый день делаем при разработке. Для этого откройте страницу [elks.html](elks.html), включите инструменты разработчика и перейдите на вкладку Elements. -Вот как это должно выглядеть: +Выглядит примерно так: ![](elks.png) Вы можете увидеть DOM, понажимать на элементы, детально рассмотреть их и так далее. -Обратите внимание, что структура DOM в инструментах разработчика отображается в упрощенном виде. Текстовые узлы показаны как простой текст. И кроме пробелов нет никаких "пустых" текстовых узлов. Ну и отлично, потому что большую часть времени нас будут интересовать узлы-элементы. +Обратите внимание, что структура DOM в инструментах разработчика отображается в упрощённом виде. Текстовые узлы показаны как простой текст. И кроме пробелов нет никаких "пустых" текстовых узлов. Ну и отлично, потому что большую часть времени нас будут интересовать узлы-элементы. -Клик по этой <span class="devtools" style="background-position:-328px -124px"></span> кнопке в левом верхнем углу инспектора позволяет при помощи мыши (или другого устройства ввода) выбрать элемент на веб-странице и "проинспектировать" его (браузер сам найдет и отметит его во вкладке Elements). Этот способ отлично подходит, когда у нас огромная HTML-страница (и соответствующий ей огромный DOM), и мы хотим увидеть, где находится интересующий нас элемент. +Клик по этой <span class="devtools" style="background-position:-328px -124px"></span> кнопке в левом верхнем углу инспектора позволяет при помощи мыши (или другого устройства ввода) выбрать элемент на веб-странице и "проинспектировать" его (браузер сам найдёт и отметит его во вкладке Elements). Этот способ отлично подходит, когда у нас огромная HTML-страница (и соответствующий ей огромный DOM), и мы хотим увидеть, где находится интересующий нас элемент. -Есть и другой способ сделать это, можно кликнуть на странице по элементу правой кнопкой мыши и в контекстном меню выбрать "Inspect". +Есть и другой способ сделать это: можно кликнуть на странице по элементу правой кнопкой мыши и в контекстном меню выбрать "Inspect". ![](inspect.png) В правой части инструментов разработчика находятся следующие подразделы: -- **Styles** -- здесь мы видим CSS, примененный к текущему элементу: правило за правилом, включая встроенные стили (выделены серым). Почти все можно отредактировать на месте, включая размеры/внешние и внутренние отступы. +- **Styles** -- здесь мы видим CSS, применённый к текущему элементу: правило за правилом, включая встроенные стили (выделены серым). Почти всё можно отредактировать на месте, включая размеры, внешние и внутренние отступы. - **Computed** -- здесь мы видим итоговые CSS-свойства элемента, которые он приобрёл в результате применения всего каскада стилей (в том числе унаследованные свойства и т.д.). - **Event Listeners** -- в этом разделе мы видим обработчики событий, привязанные к DOM-элементам (мы поговорим о них в следующей части учебника). - ... и т.д. @@ -225,26 +236,30 @@ drawHtmlTree(node6, 'div.domtree', 690, 500); ## Взаимодействие с консолью -Изучая DOM, мы также хотим применять к нему JavaScript. Например: получить узел и запустить какой-нибудь код для его изменения, чтобы посмотреть результат. Ниже приведено несколько способов перемещения между вкладками Elements и Console. +При работе с DOM нам часто требуется применить к нему JavaScript. Например: получить узел и запустить какой-нибудь код для его изменения, чтобы посмотреть результат. Вот несколько подсказок, как перемещаться между вкладками Elements и Console. -- На вкладке Elements выберите первый элемент `<li>`. -- Нажмите `key:Esc` -- прямо под вкладкой Elements откроется Console. +Для начала: -Последний элемент, выбранный во вкладке Elements, доступен в консоли как `$0`, предыдущий, выбранный до него, как `$1` и т.д. +1. На вкладке Elements выберите первый элемент `<li>`. +2. Нажмите `key:Esc` -- прямо под вкладкой Elements откроется Console. + +Последний элемент, выбранный во вкладке Elements, доступен в консоли как `$0`; предыдущий, выбранный до него, как `$1` и т.д. Теперь мы можем запускать на них команды. Например `$0.style.background = 'red'` сделает выбранный элемент красным, как здесь: ![](domconsole0.png) -С другой стороны, если мы находимся в консоли и есть переменная `node`, ссылающаяся на DOM-узел, можно использовать команду `inspect(node)`, чтобы увидеть этот элемент во вкладке Elements. +Это мы посмотрели как получить узел из Elements в Console. + +Есть и обратный путь: если есть переменная `node`, ссылающаяся на DOM-узел, можно использовать в консоли команду `inspect(node)`, чтобы увидеть этот элемент во вкладке Elements. -Или мы можем просто вывести его в консоль и исследовать "на месте", как `document.body` ниже: +Или мы можем просто вывести DOM-узел в консоль и исследовать "на месте", как `document.body` ниже: ![](domconsole1.png) Это может быть полезно для отладки. В следующей главе мы рассмотрим доступ и изменение DOM при помощи JavaScript. -Инструменты разработчика браузера отлично помогают в разработке: мы можем исследовать DOM, пробовать с ним что-то делать и смотреть, что идет не так. +Инструменты разработчика браузера отлично помогают в разработке: мы можем исследовать DOM, пробовать с ним что-то делать и смотреть, что идёт не так. ## Итого @@ -252,10 +267,10 @@ HTML/XML документы представлены в браузере в ви - Теги становятся узлами-элементами и формируют структуру документа. - Текст становится текстовыми узлами. -- ...и т.д., все что записано в HTML есть и в DOM-дереве, даже комментарии. +- ... и т.д. Всё, что записано в HTML, есть и в DOM-дереве, даже комментарии. Для изменения элементов или проверки DOM-дерева мы можем использовать инструменты разработчика в браузере. -Здесь мы рассмотрели основы, наиболее часто используемые и важные действия для начала разработки. Подробную документацию по инструментам разработки Chrome Developer Tools можно найти на странице <https://developers.google.com/web/tools/chrome-devtools>. Лучший способ изучить инструменты походить по разным вкладкам, почитать меню: большинство действий очевидны для пользователя. Позже, когда вы немного их изучите, прочитайте документацию и узнаете то, что осталось. +Здесь мы рассмотрели основы, наиболее часто используемые и важные действия для начала разработки. Подробную документацию по инструментам разработки Chrome Developer Tools можно найти на странице <https://developers.google.com/web/tools/chrome-devtools>. Лучший способ изучить инструменты -- походить по разным вкладкам, почитать меню: большинство действий очевидны для пользователя. Позже, когда вы немного их изучите, прочитайте документацию и узнайте то, что осталось. -У DOM-узлов есть свойства и методы, которые позволяют выбирать любой из элементов, изменять, перемещать их на странице и многое другое. Мы вернемся к ним в в последующих разделах. +У DOM-узлов есть свойства и методы, которые позволяют выбирать любой из элементов, изменять, перемещать их на странице и многое другое. Мы вернёмся к ним в последующих разделах. diff --git a/2-ui/1-document/02-dom-nodes/domconsole0.png b/2-ui/1-document/02-dom-nodes/domconsole0.png index 121c11d75a..17e5dc45c8 100644 Binary files a/2-ui/1-document/02-dom-nodes/domconsole0.png and b/2-ui/1-document/02-dom-nodes/domconsole0.png differ diff --git a/2-ui/1-document/02-dom-nodes/domconsole0@2x.png b/2-ui/1-document/02-dom-nodes/domconsole0@2x.png index a8953395c5..5f50843e39 100644 Binary files a/2-ui/1-document/02-dom-nodes/domconsole0@2x.png and b/2-ui/1-document/02-dom-nodes/domconsole0@2x.png differ diff --git a/2-ui/1-document/02-dom-nodes/domconsole1.png b/2-ui/1-document/02-dom-nodes/domconsole1.png index c04f015cf5..17369236ef 100644 Binary files a/2-ui/1-document/02-dom-nodes/domconsole1.png and b/2-ui/1-document/02-dom-nodes/domconsole1.png differ diff --git a/2-ui/1-document/02-dom-nodes/domconsole1@2x.png b/2-ui/1-document/02-dom-nodes/domconsole1@2x.png index ce0fa0fffa..38f4a81c1c 100644 Binary files a/2-ui/1-document/02-dom-nodes/domconsole1@2x.png and b/2-ui/1-document/02-dom-nodes/domconsole1@2x.png differ diff --git a/2-ui/1-document/02-dom-nodes/elks.png b/2-ui/1-document/02-dom-nodes/elks.png index 03177c40e8..1c724d9910 100644 Binary files a/2-ui/1-document/02-dom-nodes/elks.png and b/2-ui/1-document/02-dom-nodes/elks.png differ diff --git a/2-ui/1-document/02-dom-nodes/elks@2x.png b/2-ui/1-document/02-dom-nodes/elks@2x.png index e8a15bd5b9..6c6a2c46fc 100644 Binary files a/2-ui/1-document/02-dom-nodes/elks@2x.png and b/2-ui/1-document/02-dom-nodes/elks@2x.png differ diff --git a/2-ui/1-document/02-dom-nodes/inspect.png b/2-ui/1-document/02-dom-nodes/inspect.png index 075cf93080..3567c19f74 100644 Binary files a/2-ui/1-document/02-dom-nodes/inspect.png and b/2-ui/1-document/02-dom-nodes/inspect.png differ diff --git a/2-ui/1-document/02-dom-nodes/inspect@2x.png b/2-ui/1-document/02-dom-nodes/inspect@2x.png index 8743dd297d..60bfe5f899 100644 Binary files a/2-ui/1-document/02-dom-nodes/inspect@2x.png and b/2-ui/1-document/02-dom-nodes/inspect@2x.png differ diff --git a/2-ui/1-document/02-dom-nodes/statusbarButtonGlyphs.svg b/2-ui/1-document/02-dom-nodes/statusbarButtonGlyphs.svg deleted file mode 100644 index fa37b2cc3d..0000000000 --- a/2-ui/1-document/02-dom-nodes/statusbarButtonGlyphs.svg +++ /dev/null @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="120" width="320"><defs><linearGradient id="p"><stop offset="0" stop-color="#e59290"/><stop offset="1" stop-color="#e99890"/></linearGradient><linearGradient id="o"><stop offset="0" stop-color="#c0544f"/><stop offset="1" stop-color="#d08481"/></linearGradient><linearGradient id="n"><stop offset="0" stop-color="#ffa801" stop-opacity="0"/><stop offset="1" stop-color="#f0fb3d"/></linearGradient><linearGradient id="m"><stop offset="0" stop-color="#ffbd00" stop-opacity=".65"/><stop offset="1" stop-color="#fff" stop-opacity=".906"/></linearGradient><linearGradient><stop offset="0" stop-color="#a16a00"/><stop offset="1" stop-color="#c68200"/></linearGradient><linearGradient id="j"><stop offset="0" stop-color="#00d600" stop-opacity="0"/><stop offset="1" stop-color="#d8fc7b" stop-opacity=".812"/></linearGradient><linearGradient id="i"><stop offset="0" stop-color="#00ba00"/><stop offset="1" stop-color="#fff" stop-opacity=".906"/></linearGradient><linearGradient id="h"><stop offset="0" stop-color="#00a104"/><stop offset="1" stop-color="#00c605"/></linearGradient><linearGradient id="l"><stop offset="0" stop-color="#f00" stop-opacity="0"/><stop offset="1" stop-color="#f0cb68" stop-opacity=".709"/></linearGradient><linearGradient id="k"><stop offset="0" stop-color="#e60000" stop-opacity=".65"/><stop offset="1" stop-color="#fff" stop-opacity=".906"/></linearGradient><linearGradient><stop offset="0" stop-color="#f00"/><stop offset="1" stop-color="#9f0000"/></linearGradient><linearGradient id="g"><stop offset="0" stop-color="#a10000"/><stop offset="1" stop-color="#c60000"/></linearGradient><linearGradient><stop offset="0"/><stop offset="1" stop-opacity="0"/></linearGradient><linearGradient id="f"><stop offset="0"/><stop offset="1" stop-color="#5c5c5c"/></linearGradient><linearGradient id="e"><stop offset="0"/><stop offset="1" stop-color="#929292"/></linearGradient><linearGradient id="d"><stop offset="0" stop-color="#ac1f10"/><stop offset="1" stop-color="#f48f84"/></linearGradient><linearGradient id="c"><stop offset="0" stop-color="#d7687d"/><stop offset="1" stop-color="#b21402"/></linearGradient><linearGradient id="b"><stop offset="0" stop-color="#d76f7d"/><stop offset="1" stop-color="#b21402"/></linearGradient><clipPath><path d="M0 560h960V0H0v560z"/></clipPath><clipPath id="q"><path d="M160 296h16.125v-14H160v14z"/></clipPath><clipPath id="s"><path d="M358 303h22v-4h-22v4z"/></clipPath><clipPath id="r"><path d="M484.867 399.56h22.705v-19.12h-22.705v19.12z"/></clipPath><clipPath><path d="M0 560h960V0H0v560z"/></clipPath><clipPath id="t"><path d="M662 341c0-9.94 8.06-18 18-18 9.942 0 18 8.06 18 18s-8.058 18-18 18c-9.94 0-18-8.06-18-18"/></clipPath><radialGradient cx="0" cy="0" fx="0" fy="0" gradientTransform="matrix(18 0 0 -18 680 341)" gradientUnits="userSpaceOnUse" id="a" r="1" spreadMethod="pad"><stop offset="0"/><stop offset="1" stop-opacity="0"/></radialGradient><clipPath><path d="M0 560h960V0H0v560z"/></clipPath><radialGradient xlink:href="#a" gradientUnits="userSpaceOnUse" gradientTransform="matrix(18 0 0 -18 680 341)" spreadMethod="pad" cx="0" cy="0" fx="0" fy="0" r="1"/><radialGradient xlink:href="#a" gradientUnits="userSpaceOnUse" gradientTransform="matrix(18 0 0 -18 680 341)" spreadMethod="pad" cx="0" cy="0" fx="0" fy="0" r="1"/><radialGradient xlink:href="#a" id="u" gradientUnits="userSpaceOnUse" gradientTransform="matrix(18 0 0 -18 680 341)" spreadMethod="pad" cx="0" cy="0" fx="0" fy="0" r="1"/><linearGradient xlink:href="#b" x2="24" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-1 0 0 -1 24 0)"/><linearGradient xlink:href="#b" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-1 0 0 -1 24 0)" x2="24"/><linearGradient xlink:href="#b" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-1 0 0 -1 24 0)" x2="24"/><linearGradient xlink:href="#b" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-1 0 0 -1 24 0)" x2="24"/><linearGradient xlink:href="#b" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-1 0 0 -1 24 0)" x2="24"/><linearGradient xlink:href="#c" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-1 0 0 -1 24 0)" x2="24"/><linearGradient xlink:href="#b" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-1 0 0 -1 24 0)" x2="24"/><linearGradient xlink:href="#c" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-1 0 0 -1 24 0)" x2="24"/><linearGradient xlink:href="#c" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-1 0 0 -1 24 0)" x2="24"/><linearGradient xlink:href="#b" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-1 0 0 -1 24 0)" x2="24"/><linearGradient xlink:href="#d" x2="24" gradientUnits="userSpaceOnUse"/><linearGradient xlink:href="#c" gradientUnits="userSpaceOnUse" gradientTransform="matrix(0 -.583 -.583 0 120 111)" x2="24"/><linearGradient xlink:href="#e" x1="113" y1="104" x2="127" y2="104" gradientUnits="userSpaceOnUse"/><linearGradient xlink:href="#f" x1="113" y1="104" x2="127" y2="104" gradientUnits="userSpaceOnUse"/><linearGradient xlink:href="#c" id="v" gradientUnits="userSpaceOnUse" gradientTransform="matrix(0 -.583 -.583 0 120 111)" x2="24"/><linearGradient xlink:href="#c" id="y" gradientUnits="userSpaceOnUse" gradientTransform="matrix(0 -.583 -.583 0 120 111)" x2="24"/><linearGradient xlink:href="#g" id="z" gradientUnits="userSpaceOnUse" x1="227.875" y1="103.156" x2="235.125" y2="103.156"/><linearGradient xlink:href="#h" id="C" gradientUnits="userSpaceOnUse" x1="227.875" y1="103.156" x2="235.125" y2="103.156"/><linearGradient xlink:href="#i" id="D" gradientUnits="userSpaceOnUse" x1="227.875" y1="103.156" x2="235.125" y2="103.156"/><linearGradient xlink:href="#j" id="E" gradientUnits="userSpaceOnUse" x1="227.875" y1="103.156" x2="235.125" y2="103.156"/><linearGradient xlink:href="#k" id="A" gradientUnits="userSpaceOnUse" x1="227.875" y1="103.156" x2="235.125" y2="103.156"/><linearGradient xlink:href="#l" id="B" gradientUnits="userSpaceOnUse" x1="227.875" y1="103.156" x2="235.125" y2="103.156"/><linearGradient xlink:href="#m" id="F" gradientUnits="userSpaceOnUse" x1="227.875" y1="103.156" x2="235.125" y2="103.156"/><linearGradient xlink:href="#n" id="G" gradientUnits="userSpaceOnUse" x1="227.875" y1="103.156" x2="235.125" y2="103.156"/><linearGradient xlink:href="#o" x1="113" y1="104" x2="127" y2="104" gradientUnits="userSpaceOnUse" gradientTransform="matrix(0 -1 1 0 16 224)"/><linearGradient xlink:href="#o" gradientUnits="userSpaceOnUse" x1="113" y1="104" x2="127" y2="104" gradientTransform="matrix(0 -1 1 0 16 224)"/><linearGradient xlink:href="#p" x1="96.5" y1="103" x2="109.5" y2="103" gradientUnits="userSpaceOnUse" gradientTransform="matrix(0 1 -1 0 206 0)"/><linearGradient xlink:href="#o" id="w" gradientUnits="userSpaceOnUse" gradientTransform="matrix(0 -1 1 0 16 224)" x1="113" y1="104" x2="127" y2="104"/><linearGradient xlink:href="#o" gradientUnits="userSpaceOnUse" gradientTransform="matrix(0 -1 1 0 16 224)" x1="113" y1="104" x2="127" y2="104"/><linearGradient xlink:href="#p" gradientUnits="userSpaceOnUse" gradientTransform="matrix(0 1 -1 0 206 0)" x1="96.5" y1="103" x2="109.5" y2="103"/><linearGradient xlink:href="#p" id="x" gradientUnits="userSpaceOnUse" gradientTransform="matrix(0 1 -1 0 206 0)" x1="96.5" y1="103" x2="109.5" y2="103"/></defs><g clip-path="url(#q)" opacity=".5" transform="matrix(.5 0 0 -.51 -36.014 209.912)"><path d="M160 296l4-5v-9h8v9l4.125 5H160z" fill="#424242"/></g><g clip-path="url(#r)" opacity=".4" transform="matrix(.507 0 0 -.523 -41.086 214.007)"><path d="M491.55 386.362l-3.62 4.31-3.063-2.57 6.428-7.66 2.834 2.844 13.44 13.443-2.83 2.82-13.2-13.2z"/></g><path d="M57 12l-3.412-4H39v8h14.588M25 36l-3.638-4H19.57L14 40h7.362L25 36zM7 32v8h2.053L14 32H7zm2.668 12.547l-1.594-1.32L18.83 27.454l1.596 1.32L9.668 44.546zM16 15.1c-1.748 0-3.165-1.415-3.165-3.163 0-1.748 1.417-3.165 3.165-3.165V7c-2.762 0-5 2.238-5 5s2.238 5 5 5 5-2.238 5-5h-1.838c-.035 1.718-1.435 3.1-3.162 3.1M16 5l4.5 3-4.5 3m37 26H43v-5h10v5zm-12 5h14V30H41v12zM10 57H8v9h11v-2h-9v-7zm3-1h7v5h-7v-5zm7-2h-9v9h11v-9h-2zm66-21h-8v-2h8v2zm-14.5-1.563l4.2 4.313-4.2 4.313 1.4 1.437 4.2-4.312 1.4-1.438-1.4-1.438L72.9 30l-1.4 1.437zM86 37h-6v-2h6v2zm0 4h-8v-2h8v2zm-5.562-24.062c-2.486 0-4.5-2.015-4.5-4.5 0-.88.256-1.7.693-2.393l6.2 6.2c-.69.436-1.51.692-2.39.692m4.5-4.5c0 .88-.25 1.7-.69 2.393l-6.2-6.2c.7-.436 1.52-.693 2.4-.693 2.483 0 4.5 2.015 4.5 4.5M80.5 6C76.91 6 74 8.91 74 12.5s2.91 6.5 6.5 6.5 6.5-2.91 6.5-6.5S84.09 6 80.5 6M117 65h-10V55h10v10zm-1-28v4h-8v-3h-1v4h10v-5h-1zm-4.116-1.097l-1.832-2.255-1.552 1.345L111.756 39l1.435-1.488L120 30.48 118.568 29l-6.684 6.903zM149 8.333L147.667 7 144 10.667 140.333 7 139 8.333 142.667 12 139 15.667 140.333 17 144 13.333 147.667 17 149 15.667 145.333 12 149 8.333zM46.5 65.235c.32.134.844.242 1.466.242.592 0 1.144-.097 1.534-.263v-3.93l4-4.57v-.19h-11v.217l4 4.57v3.93zM47.966 67c-1.155 0-2.168-.287-2.71-.767L45 66.005v-4.117l-4-4.568V55h14v2.346l-4 4.568v4.133l-.31.228c-.624.46-1.617.725-2.724.725M86 60l-10-5v10"/><g clip-path="url(#s)" opacity=".2" transform="matrix(.5 0 0 -.5 -37 206.5)"><path d="M380 299h-22v4h22v-4z"/></g><path d="M135 54v-1h-2v14h2v-1h-1V54h1zm7 3h-6v-2h6v2zm3 3h-6v-2h6v2zm5 3h-6v-2h6v2zm2 3h-6v-2h6v2zm18 1h-2v-9h2v9zm3 0h-2V56h2v11zm3 0h-2V54h2v13zm3 0h-2v-4h2v4zm3 0h-2v-9h2v9zm3 0h-2V56h2v11zm23-10h-10v-2h10v2zm9 0h-5v-2h5v2zm-17 4h-2v-2h2v2zm13 0h-10v-2h10v2zm4 0h-2v-2h2v2zm-12 4h-7v-2h7v2zm12 0h-8v-2h8v2zm57-1h-7v-8h7v8zm-9 2h14V54h-14v12zm33-54c0 3.313 2.686 6 6 6s6-2.687 6-6-2.686-6-6-6-6 2.687-6 6"/><g clip-path="url(#t)" transform="matrix(.5 0 0 -.5 -36 206.5)"><path d="M662 341c0-9.94 8.06-18 18-18 9.942 0 18 8.06 18 18s-8.058 18-18 18c-9.94 0-18-8.06-18-18" fill="url(#u)"/></g><path d="M298 36c0 3.313 2.686 6 6 6s6-2.687 6-6-2.686-6-6-6-6 2.687-6 6"/><path d="M239.063 38.104c-1.68 0-3.042-1.36-3.042-3.04s1.37-3.043 3.05-3.043 3.04 1.37 3.04 3.05-1.36 3.04-3.04 3.04m7.938 3.46l-4.077-4.073c.443-.705.703-1.535.703-2.427 0-2.52-2.042-4.564-4.562-4.564-2.52 0-4.562 2.042-4.562 4.562s2.044 4.562 4.564 4.562c.89 0 1.72-.26 2.426-.704l4.076 4.08L247 41.57z"/><path d="M0 0l10.75 6.5L21.625 2l10.125 8.125" fill="none" stroke="#000" stroke-width="3" stroke-miterlimit="10" transform="matrix(.489 0 0 -.497 233.733 63.763)"/><path d="M231 67V54h-1v14h21.012v-1h-20.01zm6-56h-4V7h4v4zm10-2h-9V7h9v2zm0 2h-9v-1h9v1zm0 4h-9v-2h9v2zm-10 2h-4v-4h4v4zm10 0h-9v-1h9v1zm-71 21.133c-1.178 0-2.133-.955-2.133-2.133s.955-2.133 2.133-2.133 2.133.955 2.133 2.133-.955 2.133-2.133 2.133m8-3.733l-2.933-.533 1.72-2.392-2.262-2.263L178.067 31l-.467-3h-3.2l-.4 3.133-2.525-1.92-2.263 2.262 1.988 2.592-3.2.333v3.2l3.333.333-2.12 2.592 2.262 2.263 2.525-2.12.4 3.332h3.2l.4-3.2 2.525 1.988 2.263-2.263L181 38.067l3-.467v-3.2zm-9.03-15.775c0-.9.426-1.348 1.28-1.348.417 0 .737.117.957.353.22.234.33.566.33.995 0 .423-.11.758-.334 1.005-.223.246-.542.37-.954.37-.42 0-.73-.12-.95-.362-.22-.24-.33-.58-.33-1.013m.35-2.848v-.57c0-.656.11-1.2.35-1.635.23-.435.64-.884 1.22-1.35.69-.558 1.13-.993 1.33-1.302.2-.31.3-.68.3-1.108 0-.5-.162-.883-.487-1.15-.326-.27-.793-.403-1.403-.403-.55 0-1.06.08-1.53.24-.47.16-.93.35-1.377.58L173 7.51c1.18-.674 2.442-1.01 3.79-1.01 1.137 0 2.04.286 2.708.857.668.572 1.002 1.36 1.002 2.366 0 .447-.064.844-.19 1.192-.13.348-.323.68-.58.996-.26.32-.705.73-1.338 1.24-.54.44-.902.8-1.085 1.08-.183.29-.274.67-.274 1.15v.4h-1.707zm35.01 22.55c0 .793.08 1.387.24 1.78.16.394.42.59.78.59.71 0 1.062-.79 1.062-2.37 0-1.56-.356-2.34-1.067-2.34-.358 0-.617.193-.777.575-.16.384-.24.972-.24 1.765m3.663 0c0 1.224-.226 2.142-.676 2.755-.45.612-1.108.918-1.97.918-.826 0-1.466-.315-1.92-.946-.458-.63-.686-1.54-.686-2.727 0-2.432.867-3.65 2.604-3.65.836 0 1.486.315 1.95.944.464.63.696 1.53.696 2.71m-2.42-8.16l-6.6 11.68h-1.58l6.6-11.67h1.58zm-8.995 3.5c0 .8.08 1.39.233 1.77.16.39.416.59.775.59.716 0 1.074-.784 1.074-2.354s-.356-2.356-1.072-2.356c-.36 0-.616.192-.774.58-.157.388-.236.98-.236 1.773m3.664-.016c0 1.224-.224 2.14-.67 2.758-.45.616-1.11.923-1.983.923-.826 0-1.466-.32-1.92-.956-.454-.635-.68-1.544-.68-2.725 0-2.433.866-3.65 2.597-3.65.85 0 1.505.316 1.964.947.46.63.69 1.533.69 2.704M268 35.994c.42.23.71.494.87.8.16.306.24.83.24 1.57v1.02c0 .47.046.76.138.883.092.12.265.203.522.245.06.013.15.027.273.04.63.077.945.33.945.757 0 .21-.092.37-.275.5-.184.13-.428.19-.733.19-.39 0-.765-.05-1.128-.16-.364-.1-.665-.25-.904-.43-.26-.2-.445-.457-.553-.77-.11-.31-.163-.85-.163-1.61v-.82c0-.834-.46-1.313-1.382-1.44-.04-.01-.066-.01-.083-.01-.27-.037-.466-.117-.59-.238-.13-.123-.195-.298-.195-.527 0-.2.053-.36.162-.477.107-.118.28-.2.52-.253.105-.026.257-.056.457-.09.74-.113 1.11-.57 1.11-1.37v-.822c0-.706.04-1.202.13-1.486.08-.285.222-.533.422-.745.234-.237.546-.42.937-.553.39-.13.82-.2 1.292-.2.29 0 .53.064.706.19.178.128.267.296.267.504 0 .435-.337.697-1.01.785-.064.002-.107.007-.136.012-.28.04-.47.12-.578.254-.107.13-.16.43-.16.895v1.02c0 .693-.083 1.203-.25 1.527-.164.324-.45.593-.86.806m8.977-.008c-.404-.215-.687-.48-.854-.807-.168-.324-.25-.832-.25-1.526v-1.45c0-.214-.068-.377-.2-.49-.135-.112-.36-.187-.67-.223-.407-.048-.677-.127-.81-.24-.135-.11-.2-.28-.2-.51 0-.235.083-.417.252-.55.17-.13.41-.194.72-.194.41 0 .792.05 1.146.153.358.1.655.243.89.43.276.206.466.464.577.77.11.307.165.85.165 1.626v.82c0 .8.38 1.26 1.14 1.385.186.02.326.045.426.068.235.053.406.14.518.26.11.12.168.28.168.477 0 .206-.06.372-.177.5-.116.122-.29.21-.524.256l-.376.056c-.78.126-1.176.596-1.176 1.41v.806c0 .73-.04 1.24-.124 1.522-.08.286-.222.528-.422.727-.218.217-.53.394-.937.53-.404.137-.83.205-1.274.205-.31 0-.555-.066-.73-.2-.175-.13-.26-.315-.26-.552 0-.217.064-.38.195-.493.13-.11.403-.195.816-.25.34-.04.57-.12.69-.23.12-.11.18-.29.18-.534v-1.39c0-.74.08-1.262.24-1.564.16-.303.447-.568.86-.797m-1.743-21.297h-2.153v-5.38h2.152v5.384zm-4.307 0h-2.154v-5.38h2.15v5.384zm4-9.692h-5.846L265 9.04v5.844L269.077 19h5.846L279 14.882V9.038L274.923 5zM112.03 15.5c-1.932 0-3.5-1.567-3.5-3.5s1.568-3.5 3.5-3.5c1.934 0 3.5 1.567 3.5 3.5s-1.566 3.5-3.5 3.5M112 7c-2.437 0-4.625.834-9 5 4.312 4.167 6.563 5 9 5 2.437 0 4.625-.834 9-5-4.312-4.167-6.563-5-9-5m-1.5 5c0 .83.67 1.5 1.5 1.5s1.5-.67 1.5-1.5-.67-1.5-1.5-1.5-1.5.67-1.5 1.5m29 21h9L147 43h-6m6.5-13h-2v-1h-3v1h-2c-.552 0-1 .482-1 1v1h9v-1c0-.518-.448-1-1-1M317 69v-5l-5 5h5zM212 13v4h-8v-3h-1v4h10v-5h-1zM23 84l-8-5.002v10.004M13 89h-3V79h3v10zm34-1h-3v-8h3v8zm6 0h-3v-8h3v8zm25.5 1c0 1.107.897 2.004 2.004 2.004s2.004-.897 2.004-2.004-.897-2.004-2.004-2.004S78.5 87.893 78.5 89m2-3l4.498-4.01h-8.996M82 82h-3v-4h3v4zm28.5 7c0 1.107.897 2.004 2.004 2.004s2.004-.897 2.004-2.004-.897-2.004-2.004-2.004-2.004.897-2.004 2.004m2-11l4.498 4.01h-8.996M112.5 78l4.498 4.01h-8.996M114 82h-3v4h3v-4zm28.184 4.433c0 1.107.897 2.004 2.004 2.004s2.004-.897 2.004-2.004-.897-2.004-2.004-2.004-2.004.89-2.004 2"/><path d="M137.25 87.032c2.553-8.432 11.403-8.73 13.938 0" fill="none" stroke="#000" stroke-width="2.5"/><path d="M151.676 89l-4.538-2.77 6.682-2.097M151.676 89l-4.538-2.77 6.682-2.097M12 102l-7-4v8m19 0l4-7h-8M8 111l4 7H4"/><path d="M54 105v7H44v-7zm1 8v-11H43v11z"/><rect width="12" height="3" x="73" y="110" ry="0" opacity=".819"/><path fill="none" stroke="#000" stroke-width="1.937" d="M336.903 158.803h28.195v22.195h-28.195z" transform="matrix(.497 0 0 .537 1.211 -6.719)"/><path d="M179.8 84.516l-4.992-3.25v6.5"/><path d="M171.918 78.032h1v13.004h-1z"/><path fill="none" stroke="#000" stroke-width="1.937" d="M336.903 158.803h28.195v22.195h-28.195z" transform="matrix(.497 0 0 .537 33.211 -6.719)"/><path d="M206.808 84.515l4.993-3.25v6.5"/><path d="M203.918 77.953h1v12.992h-1z"/><path fill="none" stroke="#000" stroke-width="1.937" d="M336.903 158.803h28.195v22.195h-28.195z" transform="matrix(.497 0 0 .537 65.211 -6.719)"/><path d="M235.918 77.953h1v12.992h-1z"/><path fill="none" stroke="#000" stroke-width="1.937" d="M336.903 158.803h28.195v22.195h-28.195z" transform="matrix(-.497 0 0 .537 446.789 -6.719)"/><path d="M273.192 84.516l-4.993-3.25v6.5"/><path d="M276.082 78.032h-1v13.004h1z"/><path fill="none" stroke="#000" stroke-width="1.937" d="M336.903 158.803h28.195v22.195h-28.195z" transform="matrix(-.497 0 0 .537 477.789 -6.719)"/><path d="M299.2 84.516l4.992-3.25v6.5"/><path d="M307.082 78.032h-1v13.004h1z"/><path d="M120 97c-3.866 0-7 3.134-7 7s3.134 7 7 7 7-3.134 7-7-3.134-7-7-7" fill="url(#v)" transform="translate(-2 -1)"/><path d="M111.5 103c0 3.59 2.91 6.5 6.5 6.5s6.5-2.91 6.5-6.5-2.91-6.5-6.5-6.5-6.5 2.91-6.5 6.5" fill="#f27d82"/><path d="M121.5 100.933l-.933-.933-2.567 2.567-2.567-2.567-.933.933 2.566 2.567-2.566 2.567.933.933 2.567-2.567 2.567 2.567.933-.933-2.567-2.567 2.567-2.567z" fill-opacity=".364"/><path d="M121.5 100.433l-.933-.933-2.567 2.567-2.567-2.567-.933.933 2.566 2.567-2.566 2.567.933.933 2.567-2.567 2.567 2.567.933-.933-2.567-2.567 2.567-2.567z" fill="#fff"/><path d="M138.5 100.933l-.933-.933-2.567 2.567-2.567-2.567-.933.933 2.566 2.567-2.566 2.567.933.933 2.567-2.567 2.567 2.567.933-.933-2.567-2.567 2.567-2.567z" fill-opacity=".244"/><path d="M138.5 100.433l-.933-.933-2.567 2.567-2.567-2.567-.933.933 2.566 2.567-2.566 2.567.933.933 2.567-2.567 2.567 2.567.933-.933-2.567-2.567 2.567-2.567z" fill="#676767"/><path d="M120 97c-3.866 0-7 3.134-7 7s3.134 7 7 7 7-3.134 7-7-3.134-7-7-7" fill="url(#w)" transform="matrix(-1 0 0 -1 223 207)"/><path d="M103 96.5c-3.59 0-6.5 2.91-6.5 6.5s2.91 6.5 6.5 6.5 6.5-2.91 6.5-6.5-2.91-6.5-6.5-6.5" fill="url(#x)"/><path d="M106.5 100.933l-.933-.933-2.567 2.567-2.567-2.567-.933.933 2.567 2.567-2.567 2.567.933.933 2.567-2.567 2.567 2.567.933-.933-2.567-2.567 2.567-2.567z" fill="#993c35"/><path d="M106.5 100.433l-.933-.933-2.567 2.567-2.567-2.567-.933.933 2.567 2.567-2.567 2.567.933.933 2.567-2.567 2.567 2.567.933-.933-2.567-2.567 2.567-2.567z" fill="#fff"/><path d="M143 102.5c0 3.59 2.91 6.5 6.5 6.5s6.5-2.91 6.5-6.5-2.91-6.5-6.5-6.5-6.5 2.91-6.5 6.5" fill="#bebebe"/><path d="M153 100.433l-.933-.933-2.567 2.567-2.567-2.567-.933.933 2.566 2.567-2.566 2.567.933.933 2.567-2.567 2.567 2.567.933-.933-2.567-2.567 2.567-2.567z" fill-opacity=".373"/><path d="M153 99.933l-.933-.933-2.567 2.567L146.933 99l-.933.933 2.566 2.567-2.566 2.567.933.933 2.567-2.567 2.567 2.567.933-.933-2.567-2.567L153 99.933z" fill="#fff"/><path d="M160 102.5c0 3.59 2.91 6.5 6.5 6.5s6.5-2.91 6.5-6.5-2.91-6.5-6.5-6.5-6.5 2.91-6.5 6.5" fill="#9f9f9f"/><path d="M170 100.433l-.933-.933-2.567 2.567-2.567-2.567-.933.933 2.566 2.567-2.566 2.567.933.933 2.567-2.567 2.567 2.567.933-.933-2.567-2.567 2.567-2.567z" fill-opacity=".364"/><path d="M170 99.933l-.933-.933-2.567 2.567L163.933 99l-.933.933 2.566 2.567-2.566 2.567.933.933 2.567-2.567 2.567 2.567.933-.933-2.567-2.567L170 99.933z" fill="#fff"/><path d="M184.5 100.433l-.933-.933-2.567 2.567-2.567-2.567-.933.933 2.566 2.567-2.566 2.567.933.933 2.567-2.567 2.567 2.567.933-.933-2.567-2.567 2.567-2.567z" fill-opacity=".244"/><path d="M184.5 99.933l-.933-.933-2.567 2.567L178.433 99l-.933.933 2.566 2.567-2.566 2.567.933.933 2.567-2.567 2.567 2.567.933-.933-2.567-2.567 2.567-2.567z" fill="#676767"/><path d="M131.65 116.212l-1.436-2.03-1.214 1.212 2.548 3.606 1.124-1.34 5.328-6.328-1.12-1.332-5.23 6.212z"/><path d="M195.75 97.75l3.5 3.25-3.5 3.25" fill="none" stroke="#367cf1" stroke-width="1.5"/><path d="M195.75 108.75l3.5 3.25-3.5 3.25" fill="none" stroke="#939393" stroke-width="1.5"/><path d="M209 101c0 .55.446.996.996.996s.996-.446.996-.996-.446-.996-.996-.996-.996.446-.996.996" fill="#bababa"/><path d="M208.25 97.75l-3.5 3.25 3.5 3.25" fill="none" stroke="#bababa" stroke-width="1.5"/><path d="M120 97c-3.866 0-7 3.134-7 7s3.134 7 7 7 7-3.134 7-7-3.134-7-7-7" fill="url(#y)" transform="matrix(.714 0 0 .714 132.286 26.714)"/><path d="M213.357 101c0 2.564 2.08 4.643 4.643 4.643 2.564 0 4.643-2.08 4.643-4.643 0-2.564-2.08-4.643-4.643-4.643-2.564 0-4.643 2.08-4.643 4.643" fill="#eb3941"/><path d="M216 99l4 4m0-4l-4 4m0-4l4 4m0-4l-4 4" fill="none" stroke="#fff"/><path d="M203 116l4-7.778 4 7.778z" fill="none" stroke="#c19600" stroke-width="2" stroke-linecap="square" stroke-linejoin="round"/><path d="M203.017 116l4-7.778 4 7.778z" fill="#f4bd00" stroke="#f5be00" stroke-width="1.5" stroke-linecap="square" stroke-linejoin="round"/><text y="146.82" x="161.332" style="line-height:125%;-inkscape-font-specification:Times Bold" transform="scale(1.263 .792)" font-size="11.261" font-weight="bold" letter-spacing="0" word-spacing="0" fill="#ad8601" font-family="Sans"><tspan y="146.82" x="161.332">!</tspan></text><path style="line-height:125%;-inkscape-font-specification:Times Bold" d="M206 110h2v2.303l-.283 1.68h-1.434l-.283-1.68V110m0 4.57h2V116h-2v-1" font-size="9.677" font-weight="bold" letter-spacing="0" word-spacing="0" fill="#fff" font-family="Sans"/><path d="M235.125 103.156a3.625 3.844 0 1 1-7.25 0 3.625 3.844 0 1 1 7.25 0z" transform="matrix(0 1.379 -1.301 0 363.085 -218.353)" fill="url(#z)"/><path d="M233.5 101c0 3.463-3.748 5.63-6.75 3.898-1.39-.804-2.25-2.29-2.25-3.897 0-3.46 3.748-5.63 6.75-3.89 1.39.807 2.25 2.29 2.25 3.9z" fill="#d00"/><path d="M235.125 103.156a3.625 3.844 0 1 1-7.25 0 3.625 3.844 0 1 1 7.25 0z" transform="matrix(0 -.629 .927 0 133.469 244.528)" fill="url(#A)"/><path transform="matrix(0 .44 .78 0 148.487 2.121)" d="M235.125 103.156a3.625 3.844 0 1 1-7.25 0 3.625 3.844 0 1 1 7.25 0z" fill="url(#B)"/><path d="M235.125 103.156a3.625 3.844 0 1 1-7.25 0 3.625 3.844 0 1 1 7.25 0z" transform="matrix(-.015 1.379 -1.301 -.014 377.665 -216.821)" fill="url(#C)"/><path d="M244.5 101.05c-.038 3.463-3.812 5.587-6.792 3.822-1.384-.82-2.225-2.314-2.208-3.92.038-3.465 3.812-5.59 6.792-3.824 1.384.82 2.225 2.314 2.208 3.92z" fill="#00be00"/><path d="M235.125 103.156a3.625 3.844 0 1 1-7.25 0 3.625 3.844 0 1 1 7.25 0z" transform="matrix(.007 -.629 .927 .01 142.865 243.443)" fill="url(#D)"/><path transform="matrix(-.005 .44 .78 .009 160.603 1.16)" d="M235.125 103.156a3.625 3.844 0 1 1-7.25 0 3.625 3.844 0 1 1 7.25 0z" fill="url(#E)"/><path d="M251.002 106c-3.85 0-6.256-4.165-4.333-7.5.89-1.546 2.54-2.5 4.32-2.5 3.85 0 6.253 4.165 4.33 7.5-.893 1.546-2.543 2.5-4.33 2.5z" fill="#e5a600"/><path d="M255.5 101c0 3.463-3.748 5.63-6.75 3.898-1.39-.804-2.25-2.29-2.25-3.897 0-3.46 3.748-5.63 6.75-3.89 1.39.807 2.25 2.29 2.25 3.9z" fill="#ffbd00"/><path d="M235.125 103.156a3.625 3.844 0 1 1-7.25 0 3.625 3.844 0 1 1 7.25 0z" transform="matrix(0 -.629 .927 0 155.469 244.528)" fill="url(#F)"/><path transform="matrix(0 .44 .78 0 170.443 2.076)" d="M235.125 103.156a3.625 3.844 0 1 1-7.25 0 3.625 3.844 0 1 1 7.25 0z" fill="url(#G)"/></svg> \ No newline at end of file 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 decfa62c7d..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,27 +1,26 @@ -There are many ways, for instance: +Есть несколько способов для получения элементов, например: - -The `<div>` DOM node: +DOM-узел элемента `<div>`: ```js document.body.firstElementChild -// or +// или document.body.children[0] -// or (the first node is space, so we take 2nd) +// или (первый узел пробел, поэтому выбираем второй) document.body.childNodes[1] ``` -The `<ul>` DOM node: +DOM-узел элемента `<ul>`: ```js document.body.lastElementChild -// or +// или document.body.children[1] ``` -The second `<li>` (with Pete): +Второй `<li>` (с именем Пит): ```js -// get <ul>, and then get its last element child +// получаем <ul>, и его последнего ребёнка document.body.lastElementChild.lastElementChild ``` 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 4a9e741a9c..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 @@ -2,23 +2,23 @@ importance: 5 --- -# DOM children +# Дочерние элементы в DOM -For the page: +Для страницы: ```html <html> <body> - <div>Users:</div> + <div>Пользователи:</div> <ul> - <li>John</li> - <li>Pete</li> + <li>Джон</li> + <li>Пит</li> </ul> </body> </html> ``` -How to access: -- The `<div>` DOM node? -- The `<ul>` DOM node? -- The second `<li>` (with Pete)? +Напишите код, как получить... +- элемент `<div>`? +- `<ul>`? +- второй `<li>` (с именем Пит)? diff --git a/2-ui/1-document/03-dom-navigation/3-navigation-links-which-null/solution.md b/2-ui/1-document/03-dom-navigation/3-navigation-links-which-null/solution.md index e962eb6622..7597295e4d 100644 --- a/2-ui/1-document/03-dom-navigation/3-navigation-links-which-null/solution.md +++ b/2-ui/1-document/03-dom-navigation/3-navigation-links-which-null/solution.md @@ -1,4 +1,4 @@ -1. Yes, true. The element `elem.lastChild` is always the last one, it has no `nextSibling`, so if there are children, then yes. -2. No, wrong, because `elem.children[0]` is the first child among elements. But there may be non-element nodes before it. So `previousSibling` may be a text node. +1. Да. Верно. Элемент `elem.lastChild` всегда последний, у него нет ссылки `nextSibling`. +2. Нет. Неверно. Потому что `elem.children[0]` -- потомок-элемент. Но перед ним могут быть другие узлы. Например, `previousSibling` может быть текстовым узлом. -Please note that for both cases if there are no children, then there will be an error. For instance, if `elem.lastChild` is `null`, we can't access `elem.lastChild.nextSibling`. +Обратите внимание, что в обоих случаях, если детей нет, то будет ошибка. При этом `elem.lastChild` равен `null`, а значит -- ошибка при попытке доступа к `elem.lastChild.nextSibling`. diff --git a/2-ui/1-document/03-dom-navigation/3-navigation-links-which-null/task.md b/2-ui/1-document/03-dom-navigation/3-navigation-links-which-null/task.md index 235e83a0ca..0f4385b003 100644 --- a/2-ui/1-document/03-dom-navigation/3-navigation-links-which-null/task.md +++ b/2-ui/1-document/03-dom-navigation/3-navigation-links-which-null/task.md @@ -2,9 +2,9 @@ importance: 5 --- -# The sibling question +# Вопрос о соседях -If `elem` -- is an arbitrary DOM element node... +Если `elem` -- произвольный узел DOM-элемента... -- Is it true that `elem.lastChild.nextSibling` is always `null`? -- Is it true that `elem.children[0].previousSibling` is always `null` ? +- Правда, что `elem.lastChild.nextSibling` всегда равен `null`? +- Правда, что `elem.children[0].previousSibling` всегда равен `null` ? diff --git a/2-ui/1-document/03-dom-navigation/4-select-diagonal-cells/solution.md b/2-ui/1-document/03-dom-navigation/4-select-diagonal-cells/solution.md index f2aa86302a..61865ac68e 100644 --- a/2-ui/1-document/03-dom-navigation/4-select-diagonal-cells/solution.md +++ b/2-ui/1-document/03-dom-navigation/4-select-diagonal-cells/solution.md @@ -1 +1 @@ -We'll be using `rows` and `cells` properties to access diagonal table cells. +Для получения доступа к диагональным ячейкам таблицы используем свойства `rows` и `cells`. diff --git a/2-ui/1-document/03-dom-navigation/4-select-diagonal-cells/source.view/index.html b/2-ui/1-document/03-dom-navigation/4-select-diagonal-cells/source.view/index.html index ad835f9f6b..4edd94d798 100644 --- a/2-ui/1-document/03-dom-navigation/4-select-diagonal-cells/source.view/index.html +++ b/2-ui/1-document/03-dom-navigation/4-select-diagonal-cells/source.view/index.html @@ -54,7 +54,7 @@ <script> let table = document.body.firstElementChild; - // your code + // ваш код </script> </body> </html> diff --git a/2-ui/1-document/03-dom-navigation/4-select-diagonal-cells/task.md b/2-ui/1-document/03-dom-navigation/4-select-diagonal-cells/task.md index 23be59fc19..d90b8cefc9 100644 --- a/2-ui/1-document/03-dom-navigation/4-select-diagonal-cells/task.md +++ b/2-ui/1-document/03-dom-navigation/4-select-diagonal-cells/task.md @@ -2,17 +2,17 @@ importance: 5 --- -# Select all diagonal cells +# Выделите ячейки по диагонали -Write the code to paint all diagonal table cells in red. +Напишите код, который выделит красным цветом все ячейки в таблице по диагонали. -You'll need to get all diagonal `<td>` from the `<table>` and paint them using the code: +Вам нужно получить из таблицы `<table>` все диагональные `<td>` и выделить их, используя код: ```js -// td should be the reference to the table cell +// в переменной td находится DOM-элемент для тега <td> td.style.backgroundColor = 'red'; ``` -The result should be: +Должно получиться так: [iframe src="solution" height=180] diff --git a/2-ui/1-document/03-dom-navigation/article.md b/2-ui/1-document/03-dom-navigation/article.md index 1e1e4058cf..d8ef379328 100644 --- a/2-ui/1-document/03-dom-navigation/article.md +++ b/2-ui/1-document/03-dom-navigation/article.md @@ -5,37 +5,37 @@ libs: --- -# Walking the DOM +# Навигация по DOM-элементам -The DOM allows us to do anything with elements and their contents, but first we need to reach the corresponding DOM object, get it into a variable, and then we are able to modify it. +DOM позволяет нам делать что угодно с элементами и их содержимым, но для начала нужно получить соответствующий DOM-объект. -All operations on the DOM start with the `document` object. From it we can access any node. +Все операции с DOM начинаются с объекта `document`. Это главная "точка входа" в DOM. Из него мы можем получить доступ к любому узлу. -Here's a picture of links that allow for travel between DOM nodes: +Так выглядят основные ссылки, по которым можно переходить между узлами DOM: -![](dom-links.png) +![](dom-links.svg) -Let's discuss them in more detail. +Поговорим об этом подробнее. -## On top: documentElement and body +## Сверху: documentElement и body -The topmost tree nodes are available directly as `document` properties: +Самые верхние элементы дерева доступны как свойства объекта `document`: `<html>` = `document.documentElement` -: The topmost document node is `document.documentElement`. That's DOM node of `<html>` tag. +: Самый верхний узел документа: `document.documentElement`. В DOM он соответствует тегу `<html>`. `<body>` = `document.body` -: Another widely used DOM node is the `<body>` element -- `document.body`. +: Другой часто используемый DOM-узел -- узел тега `<body>`: `document.body`. `<head>` = `document.head` -: The `<head>` tag is available as `document.head`. +: Тег `<head>` доступен как `document.head`. -````warn header="There's a catch: `document.body` can be `null`" -A script cannot access an element that doesn't exist at the moment of running. +````warn header="Есть одна тонкость: `document.body` может быть равен `null`" +Нельзя получить доступ к элементу, которого ещё не существует в момент выполнения скрипта. -In particular, if a script is inside `<head>`, then `document.body` is unavailable, because the browser did not read it yet. +В частности, если скрипт находится в `<head>`, `document.body` в нём недоступен, потому что браузер его ещё не прочитал. -So, in the example below the first `alert` shows `null`: +Поэтому, в примере ниже первый `alert` выведет `null`: ```html run <html> @@ -43,7 +43,7 @@ So, in the example below the first `alert` shows `null`: <head> <script> *!* - alert( "From HEAD: " + document.body ); // null, there's no <body> yet + alert( "Из HEAD: " + document.body ); // null, <body> ещё нет */!* </script> </head> @@ -51,7 +51,7 @@ So, in the example below the first `alert` shows `null`: <body> <script> - alert( "From BODY: " + document.body ); // HTMLBodyElement, now it exists + alert( "Из BODY: " + document.body ); // HTMLBodyElement, теперь он есть </script> </body> @@ -59,49 +59,49 @@ So, in the example below the first `alert` shows `null`: ``` ```` -```smart header="In the DOM world `null` means \"doesn't exist\"" -In the DOM, the `null` value means "doesn't exist" or "no such node". +```smart header="В мире DOM `null` означает \"не существует\"" +В DOM значение `null` значит "не существует" или "нет такого узла". ``` -## Children: childNodes, firstChild, lastChild +## Дети: childNodes, firstChild, lastChild -There are two terms that we'll use from now on: +Здесь и далее мы будем использовать два принципиально разных термина: -- **Child nodes (or children)** -- elements that are direct children. In other words, they are nested exactly in the given one. For instance, `<head>` and `<body>` are children of `<html>` element. -- **Descendants** -- all elements that are nested in the given one, including children, their children and so on. +- **Дочерние узлы (или дети)** -- элементы, которые являются непосредственными детьми узла. Другими словами, элементы, которые лежат непосредственно внутри данного. Например, `<head>` и `<body>` являются детьми элемента `<html>`. +- **Потомки** -- все элементы, которые лежат внутри данного, включая детей, их детей и т.д. -For instance, here `<body>` has children `<div>` and `<ul>` (and few blank text nodes): +В примере ниже детьми тега `<body>` являются теги `<div>` и `<ul>` (и несколько пустых текстовых узлов): ```html run <html> <body> - <div>Begin</div> + <div>Начало</div> <ul> <li> - <b>Information</b> + <b>Информация</b> </li> </ul> </body> </html> ``` -...And if we ask for all descendants of `<body>`, then we get direct children `<div>`, `<ul>` and also more nested elements like `<li>` (being a child of `<ul>`) and `<b>` (being a child of `<li>`) -- the entire subtree. +...А потомки `<body>`-- это и прямые дети `<div>`, `<ul>` и вложенные в них: `<li>` (ребёнок `<ul>`) и `<b>` (ребёнок `<li>`) - в общем, все элементы поддерева. -**The `childNodes` collection provides access to all child nodes, including text nodes.** +**Коллекция `childNodes` содержит список всех детей, включая текстовые узлы.** -The example below shows children of `document.body`: +Пример ниже последовательно выведет детей `document.body`: ```html run <html> <body> - <div>Begin</div> + <div>Начало</div> <ul> - <li>Information</li> + <li>Информация</li> </ul> - <div>End</div> + <div>Конец</div> <script> *!* @@ -110,153 +110,157 @@ The example below shows children of `document.body`: } */!* </script> - ...more stuff... + ...какой-то HTML-код... </body> </html> ``` -Please note an interesting detail here. If we run the example above, the last element shown is `<script>`. In fact, the document has more stuff below, but at the moment of the script execution the browser did not read it yet, so the script doesn't see it. +Обратим внимание на маленькую деталь. Если запустить пример выше, то последним будет выведен элемент `<script>`. На самом деле, в документе есть ещё "какой-то HTML-код", но на момент выполнения скрипта браузер ещё до него не дошёл, поэтому скрипт не видит его. -**Properties `firstChild` and `lastChild` give fast access to the first and last children.** +**Свойства `firstChild` и `lastChild` обеспечивают быстрый доступ к первому и последнему дочернему элементу.** -They are just shorthands. If there exist child nodes, then the following is always true: +Они, по сути, являются всего лишь сокращениями. Если у тега есть дочерние узлы, условие ниже всегда верно: ```js elem.childNodes[0] === elem.firstChild elem.childNodes[elem.childNodes.length - 1] === elem.lastChild ``` -There's also a special function `elem.hasChildNodes()` to check whether there are any child nodes. +Для проверки наличия дочерних узлов существует также специальная функция `elem.hasChildNodes()`. -### DOM collections +### DOM-коллекции -As we can see, `childNodes` looks like an array. But actually it's not an array, but rather a *collection* -- a special array-like iterable object. +Как мы уже видели, `childNodes` похож на массив. На самом деле это не массив, а *коллекция* -- особый перебираемый объект-псевдомассив. -There are two important consequences: +И есть два важных следствия из этого: -1. We can use `for..of` to iterate over it: +1. Для перебора *коллекции* мы можем использовать `for..of`: ```js for (let node of document.body.childNodes) { - alert(node); // shows all nodes from the collection + alert(node); // покажет все узлы из коллекции } ``` - That's because it's iterable (provides the `Symbol.iterator` property, as required). + Это работает, потому что коллекция является перебираемым объектом (есть требуемый для этого метод `Symbol.iterator`). -2. Array methods won't work, because it's not an array: +2. Методы массивов не будут работать, потому что коллекция -- это не массив: ```js run - alert(document.body.childNodes.filter); // undefined (there's no filter method!) + alert(document.body.childNodes.filter); // undefined (у коллекции нет метода filter!) ``` -The first thing is nice. The second is tolerable, because we can use `Array.from` to create a "real" array from the collection, if we want array methods: +Первый пункт - это хорошо для нас. Второй - бывает неудобен, но можно пережить. Если нам хочется использовать именно методы массива, то мы можем создать настоящий массив из коллекции, используя `Array.from`: ```js run - alert( Array.from(document.body.childNodes).filter ); // now it's there + alert( Array.from(document.body.childNodes).filter ); // сделали массив ``` -```warn header="DOM collections are read-only" -DOM collections, and even more -- *all* navigation properties listed in this chapter are read-only. +```warn header="DOM-коллекции -- только для чтения" +DOM-коллекции, и даже более -- *все* навигационные свойства, перечисленные в этой главе, доступны только для чтения. -We can't replace a child by something else by assigning `childNodes[i] = ...`. +Мы не можем заменить один дочерний узел на другой, просто написав `childNodes[i] = ...`. -Changing DOM needs other methods. We will see them in the next chapter. +Для изменения DOM требуются другие методы. Мы увидим их в следующей главе. ``` -```warn header="DOM collections are live" -Almost all DOM collections with minor exceptions are *live*. In other words, they reflect the current state of DOM. +```warn header="DOM-коллекции живые" +Почти все DOM-коллекции, за небольшим исключением, *живые*. Другими словами, они отражают текущее состояние DOM. -If we keep a reference to `elem.childNodes`, and add/remove nodes into DOM, then they appear in the collection automatically. +Если мы сохраним ссылку на `elem.childNodes` и добавим/удалим узлы в DOM, то они появятся в сохранённой коллекции автоматически. ``` -````warn header="Don't use `for..in` to loop over collections" -Collections are iterable using `for..of`. Sometimes people try to use `for..in` for that. +````warn header="Не используйте цикл `for..in` для перебора коллекций" +Коллекции перебираются циклом `for..of`. Некоторые начинающие разработчики пытаются использовать для этого цикл `for..in`. -Please, don't. The `for..in` loop iterates over all enumerable properties. And collections have some "extra" rarely used properties that we usually do not want to get: +Не делайте так. Цикл `for..in` перебирает все перечисляемые свойства. А у коллекций есть некоторые "лишние", редко используемые свойства, которые обычно нам не нужны: ```html run <body> <script> - // shows 0, 1, length, item, values and more. + // выводит 0, 1, length, item, values и другие свойства. for (let prop in document.body.childNodes) alert(prop); </script> </body> ```` -## Siblings and the parent +## Соседи и родитель -*Siblings* are nodes that are children of the same parent. For instance, `<head>` and `<body>` are siblings: +*Соседи* -- это узлы, у которых один и тот же родитель. -- `<body>` is said to be the "next" or "right" sibling of `<head>`, -- `<head>` is said to be the "previous" or "left" sibling of `<body>`. +Например, здесь `<head>` и `<body>` соседи: -The parent is available as `parentNode`. +```html +<html> + <head>...</head><body>...</body> +</html> +``` -The next node in the same parent (next sibling) is `nextSibling`, and the previous one is `previousSibling`. +- говорят, что `<body>` -- "следующий" или "правый" сосед `<head>` +- также можно сказать, что `<head>` "предыдущий" или "левый" сосед `<body>`. -For instance: +Следующий узел того же родителя (следующий сосед) - в свойстве `nextSibling`, а предыдущий - в `previousSibling`. -```html run -<html><head></head><body><script> - // HTML is "dense" to evade extra "blank" text nodes. +Родитель доступен через `parentNode`. + +Например: - // parent of <body> is <html> - alert( document.body.parentNode === document.documentElement ); // true +```js run +// родителем <body> является <html> +alert( document.body.parentNode === document.documentElement ); // выведет true - // after <head> goes <body> - alert( document.head.nextSibling ); // HTMLBodyElement +// после <head> идёт <body> +alert( document.head.nextSibling ); // HTMLBodyElement - // before <body> goes <head> - alert( document.body.previousSibling ); // HTMLHeadElement -</script></body></html> +// перед <body> находится <head> +alert( document.body.previousSibling ); // HTMLHeadElement ``` -## Element-only navigation +## Навигация только по элементам -Navigation properties listed above refer to *all* nodes. For instance, in `childNodes` we can see both text nodes, element nodes, and even comment nodes if there exist. +Навигационные свойства, описанные выше, относятся ко *всем* узлам в документе. В частности, в `childNodes` находятся и текстовые узлы и узлы-элементы и узлы-комментарии, если они есть. -But for many tasks we don't want text or comment nodes. We want to manipulate element nodes that represent tags and form the structure of the page. +Но для большинства задач текстовые узлы и узлы-комментарии нам не нужны. Мы хотим манипулировать узлами-элементами, которые представляют собой теги и формируют структуру страницы. -So let's see more navigation links that only take *element nodes* into account: +Поэтому давайте рассмотрим дополнительный набор ссылок, которые учитывают только *узлы-элементы*: -![](dom-links-elements.png) +![](dom-links-elements.svg) -The links are similar to those given above, just with `Element` word inside: +Эти ссылки похожи на те, что раньше, только в ряде мест стоит слово `Element`: -- `children` -- only those children that are element nodes. -- `firstElementChild`, `lastElementChild` -- first and last element children. -- `previousElementSibling`, `nextElementSibling` -- neighbour elements. -- `parentElement` -- parent element. +- `children` -- коллекция детей, которые являются элементами. +- `firstElementChild`, `lastElementChild` -- первый и последний дочерний элемент. +- `previousElementSibling`, `nextElementSibling` -- соседи-элементы. +- `parentElement` -- родитель-элемент. -````smart header="Why `parentElement`? Can the parent be *not* an element?" -The `parentElement` property returns the "element" parent, while `parentNode` returns "any node" parent. These properties are usually the same: they both get the parent. +````smart header="Зачем нужен `parentElement`? Разве может родитель быть *не* элементом?" +Свойство `parentElement` возвращает родитель-элемент, а `parentNode` возвращает "любого родителя". Обычно эти свойства одинаковы: они оба получают родителя. -With the one exception of `document.documentElement`: +За исключением `document.documentElement`: ```js run -alert( document.documentElement.parentNode ); // document -alert( document.documentElement.parentElement ); // null +alert( document.documentElement.parentNode ); // выведет document +alert( document.documentElement.parentElement ); // выведет null ``` -In other words, the `documentElement` (`<html>`) is the root node. Formally, it has `document` as its parent. But `document` is not an element node, so `parentNode` returns it and `parentElement` does not. +Причина в том, что родителем корневого узла `document.documentElement` (`<html>`) является `document`. Но `document` -- это не узел-элемент, так что `parentNode` вернёт его, а `parentElement` нет. -This loop travels up from an arbitrary element `elem` to `<html>`, but not to the `document`: +Эта деталь может быть полезна, если мы хотим пройти вверх по цепочке родителей от произвольного элемента `elem` к `<html>`, но не до `document`: ```js -while(elem = elem.parentElement) { - alert( elem ); // parent chain till <html> +while(elem = elem.parentElement) { // идти наверх до <html> + alert( elem ); } ``` ```` -Let's modify one of the examples above: replace `childNodes` with `children`. Now it shows only elements: +Изменим один из примеров выше: заменим `childNodes` на `children`. Теперь цикл выводит только элементы: ```html run <html> <body> - <div>Begin</div> + <div>Начало</div> <ul> - <li>Information</li> + <li>Информация</li> </ul> - <div>End</div> + <div>Конец</div> <script> *!* @@ -270,59 +274,59 @@ Let's modify one of the examples above: replace `childNodes` with `children`. No </html> ``` -## More links: tables [#dom-navigation-tables] +## Ещё немного ссылок: таблицы [#dom-navigation-tables] -Till now we described the basic navigation properties. +До сих пор мы описывали основные навигационные ссылки. -Certain types of DOM elements may provide additional properties, specific to their type, for convenience. +Некоторые типы DOM-элементов предоставляют для удобства дополнительные свойства, специфичные для их типа. -Tables are a great example and important particular case of that. +Таблицы -- отличный пример таких элементов. -**The `<table>`** element supports (in addition to the given above) these properties: -- `table.rows` -- the collection of `<tr>` elements of the table. -- `table.caption/tHead/tFoot` -- references to elements `<caption>`, `<thead>`, `<tfoot>`. -- `table.tBodies` -- the collection of `<tbody>` elements (can be many according to the standard). +**Элемент `<table>`**, в дополнение к свойствам, о которых речь шла выше, поддерживает следующие: +- `table.rows` -- коллекция строк `<tr>` таблицы. +- `table.caption/tHead/tFoot` -- ссылки на элементы таблицы `<caption>`, `<thead>`, `<tfoot>`. +- `table.tBodies` -- коллекция элементов таблицы `<tbody>` (по спецификации их может быть больше одного). -**`<thead>`, `<tfoot>`, `<tbody>`** elements provide the `rows` property: -- `tbody.rows` -- the collection of `<tr>` inside. +**`<thead>`, `<tfoot>`, `<tbody>`** предоставляют свойство `rows`: +- `tbody.rows` -- коллекция строк `<tr>` секции. **`<tr>`:** -- `tr.cells` -- the collection of `<td>` and `<th>` cells inside the given `<tr>`. -- `tr.sectionRowIndex` -- the position (index) of the given `<tr>` inside the enclosing `<thead>/<tbody>/<tfoot>`. -- `tr.rowIndex` -- the number of the `<tr>` in the table as a whole (including all table rows). +- `tr.cells` -- коллекция `<td>` и `<th>` ячеек, находящихся внутри строки `<tr>`. +- `tr.sectionRowIndex` -- номер строки `<tr>` в текущей секции `<thead>/<tbody>/<tfoot>`. +- `tr.rowIndex` -- номер строки `<tr>` в таблице (включая все строки таблицы). **`<td>` and `<th>`:** -- `td.cellIndex` -- the number of the cell inside the enclosing `<tr>`. +- `td.cellIndex` -- номер ячейки в строке `<tr>`. -An example of usage: +Пример использования: ```html run height=100 <table id="table"> <tr> - <td>one</td><td>two</td> + <td>один</td><td>два</td> </tr> <tr> - <td>three</td><td>four</td> + <td>три</td><td>четыре</td> </tr> </table> <script> - // get the content of the first row, second cell - alert( table.*!*rows[0].cells[1]*/!*.innerHTML ) // "two" + // выводит содержимое первой строки, второй ячейки + alert( table.*!*rows[0].cells[1]*/!*.innerHTML ) // "два" </script> ``` -The specification: [tabular data](https://html.spec.whatwg.org/multipage/tables.html). +Спецификация: [tabular data](https://html.spec.whatwg.org/multipage/tables.html). -There are also additional navigation properties for HTML forms. We'll look at them later when we start working with forms. +Существуют также дополнительные навигационные ссылки для HTML-форм. Мы рассмотрим их позже, когда начнём работать с формами. -# Summary +## Итого -Given a DOM node, we can go to its immediate neighbours using navigation properties. +Получив DOM-узел, мы можем перейти к его ближайшим соседям используя навигационные ссылки. -There are two main sets of them: +Есть два основных набора ссылок: -- For all nodes: `parentNode`, `childNodes`, `firstChild`, `lastChild`, `previousSibling`, `nextSibling`. -- For element nodes only: `parentElement`, `children`, `firstElementChild`, `lastElementChild`, `previousElementSibling`, `nextElementSibling`. +- Для всех узлов: `parentNode`, `childNodes`, `firstChild`, `lastChild`, `previousSibling`, `nextSibling`. +- Только для узлов-элементов: `parentElement`, `children`, `firstElementChild`, `lastElementChild`, `previousElementSibling`, `nextElementSibling`. -Some types of DOM elements, e.g. tables, provide additional properties and collections to access their content. +Некоторые виды DOM-элементов, например таблицы, предоставляют дополнительные ссылки и коллекции для доступа к своему содержимому. diff --git a/2-ui/1-document/03-dom-navigation/dom-links-elements.png b/2-ui/1-document/03-dom-navigation/dom-links-elements.png deleted file mode 100644 index c0e2f69413..0000000000 Binary files a/2-ui/1-document/03-dom-navigation/dom-links-elements.png and /dev/null differ diff --git a/2-ui/1-document/03-dom-navigation/dom-links-elements.svg b/2-ui/1-document/03-dom-navigation/dom-links-elements.svg new file mode 100644 index 0000000000..fd0b2826a4 --- /dev/null +++ b/2-ui/1-document/03-dom-navigation/dom-links-elements.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="440" height="316" viewBox="0 0 440 316"><defs><style>@import url(https://fonts.googleapis.com/css?family=Open+Sans:bold,italic,bolditalic%7CPT+Mono);@font-face{font-family:'PT Mono';font-weight:700;font-style:normal;src:local('PT MonoBold'),url(/font/PTMonoBold.woff2) format('woff2'),url(/font/PTMonoBold.woff) format('woff'),url(/font/PTMonoBold.ttf) format('truetype')}</style></defs><g id="dom" fill="none" fill-rule="evenodd" stroke="none" stroke-width="1"><g id="dom-links-elements.svg"><path id="Rectangle-8" fill="#FBF2EC" stroke="#DBAF88" stroke-width="2" d="M129 10h198v28H129z"/><text id="document.documentEle" fill="#AF6E24" font-family="PTMono-Regular, PT Mono" font-size="12" font-weight="normal"><tspan x="142.6" y="29">document.documentElement </tspan></text><text id="Type-something" fill="#AF6E24" font-family="PTMono-Regular, PT Mono" font-size="12" font-weight="normal"><tspan x="336.9" y="29"><HTML></tspan></text><path id="Rectangle-7" fill="#FBF2EC" stroke="#DBAF88" stroke-width="2" d="M163 78h117v28H163z"/><text id="document.body--" fill="#AF6E24" font-family="PTMono-Regular, PT Mono" font-size="12" font-weight="normal"><tspan x="174.2" y="95">document.body </tspan></text><text id="(if-inside-body)" fill="#AF6E24" font-family="PTMono-Regular, PT Mono" font-size="12" font-weight="normal"><tspan x="300.9" y="95">(if inside body)</tspan></text><path id="Line-5" stroke="#AF6E24" stroke-linecap="square" stroke-width="2" d="M14.5 115H427"/><text id="parentElement" font-family="PTMono-Regular, PT Mono" font-size="12" font-weight="normal"><tspan x="175" y="172" fill="#AF6E24">parent</tspan> <tspan x="218.2" y="172" fill="#C06334">Element</tspan></text><path id="Rectangle-6" fill="#FBF2EC" stroke="#DBAF88" stroke-width="2" d="M180 213h80v28h-80z"/><text id="<DIV>" fill="#AF6E24" font-family="OpenSans-Regular, Open Sans" font-size="12" font-weight="normal"><tspan x="204.192" y="232"><DIV></tspan></text><path id="Line-6" fill="#C06334" fill-rule="nonzero" d="M220.5 178.71l.872 1.55 6.75 12 .49.871-1.743.98-.49-.87-4.879-8.674V208.5h-2v-23.933l-4.878 8.673-.49.872-1.744-.98.49-.872 6.75-12 .872-1.55z"/><path id="Line-7" fill="#C06334" fill-rule="nonzero" d="M415.369 218.388l.871.49 12 6.75 1.55.872-1.55.872-12 6.75-.871.49-.98-1.743.87-.49 8.673-4.879H266.5v-2h157.432l-8.672-4.878-.872-.49.98-1.744z"/><text id="nextElementSibling" font-family="PTMono-Regular, PT Mono" font-size="12" font-weight="normal"><tspan x="276" y="212" fill="#AF6E24">next</tspan> <tspan x="304.8" y="212" fill="#C06334">Element</tspan> <tspan x="355.2" y="212" fill="#AF6E24">Sibling</tspan></text><path id="Line-8" fill="#C06334" fill-rule="nonzero" d="M23.631 218.388l.98 1.743-.87.49-8.674 4.879H169v2H15.067l8.673 4.878.872.49-.98 1.744-.872-.49-12-6.75-1.55-.872 1.55-.872 12-6.75.871-.49z"/><text id="previousElementSibli" font-family="PTMono-Regular, PT Mono" font-size="12" font-weight="normal"><tspan x="6" y="212" fill="#AF6E24">previous</tspan> <tspan x="63.6" y="212" fill="#C06334">Element</tspan> <tspan x="114" y="212" fill="#AF6E24">Sibling</tspan></text><path id="Line" fill="#C06334" fill-rule="nonzero" d="M174.822 248.123l1.055 1.7-.85.527-48.476 30.089-6.917 4.292 9.941-.428 1-.043.085 1.998-.999.043-13.755.594-1.776.076.857-1.557 6.636-12.064.482-.876 1.752.964-.482.876-4.797 8.718 6.918-4.293 48.477-30.089.85-.527z"/><path id="Line-3" fill="#C06334" fill-rule="nonzero" d="M269.214 248.115l.835.55 46.157 30.354 6.78 4.46-4.565-8.841-.459-.889 1.777-.918.459.889 6.317 12.233.816 1.58-1.774-.123-13.735-.954-.997-.07.138-1.995.998.07 9.926.689-6.78-4.46-46.156-30.354-.836-.55 1.099-1.671z"/><text id="children" fill="#C06334" font-family="PTMono-Regular, PT Mono" font-size="12" font-weight="normal"><tspan x="188" y="273">children</tspan></text><text id="firstElementChild--" font-family="PTMono-Regular, PT Mono" font-size="12" font-weight="normal"><tspan x="43" y="307" fill="#AF6E24">first</tspan> <tspan x="79" y="307" fill="#C06334">Element</tspan> <tspan x="129.4" y="307" fill="#AF6E24">Child </tspan></text><text id="lastElementChild" font-family="PTMono-Regular, PT Mono" font-size="12" font-weight="normal"><tspan x="273" y="307" fill="#AF6E24">last</tspan> <tspan x="301.8" y="307" fill="#C06334">Element</tspan> <tspan x="352.2" y="307" fill="#AF6E24">Child</tspan></text><path id="Line-Copy-2" fill="#C06334" fill-rule="nonzero" d="M222.5 151.5v4h-2v-4h2zm0-6v4h-2v-4h2zm0-6v4h-2v-4h2zm-1-14.29l.872 1.55 6.75 12 .49.871-1.743.98-.49-.87-4.88-8.676.001.435h-2l-.001-.432-4.877 8.672-.49.872-1.744-.98.49-.872 6.75-12 .872-1.55zm1 8.29v4h-2v-4h2z"/><path id="Line-2-Copy" fill="#C06334" fill-rule="nonzero" d="M221.5 44.71l.872 1.55 6.75 12 .49.871-1.743.98-.49-.87-4.879-8.674v21.528h-2V50.567l-4.878 8.673-.49.872-1.744-.98.49-.872 6.75-12 .872-1.55z"/></g></g></svg> \ No newline at end of file diff --git a/2-ui/1-document/03-dom-navigation/dom-links-elements@2x.png b/2-ui/1-document/03-dom-navigation/dom-links-elements@2x.png deleted file mode 100644 index 6634711770..0000000000 Binary files a/2-ui/1-document/03-dom-navigation/dom-links-elements@2x.png and /dev/null differ diff --git a/2-ui/1-document/03-dom-navigation/dom-links.png b/2-ui/1-document/03-dom-navigation/dom-links.png deleted file mode 100644 index d6b5ddf96a..0000000000 Binary files a/2-ui/1-document/03-dom-navigation/dom-links.png and /dev/null differ diff --git a/2-ui/1-document/03-dom-navigation/dom-links.svg b/2-ui/1-document/03-dom-navigation/dom-links.svg new file mode 100644 index 0000000000..6c34bca4a4 --- /dev/null +++ b/2-ui/1-document/03-dom-navigation/dom-links.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="420" height="388" viewBox="0 0 420 388"><defs><style>@import url(https://fonts.googleapis.com/css?family=Open+Sans:bold,italic,bolditalic%7CPT+Mono);@font-face{font-family:'PT Mono';font-weight:700;font-style:normal;src:local('PT MonoBold'),url(/font/PTMonoBold.woff2) format('woff2'),url(/font/PTMonoBold.woff) format('woff'),url(/font/PTMonoBold.ttf) format('truetype')}</style></defs><g id="dom" fill="none" fill-rule="evenodd" stroke="none" stroke-width="1"><g id="dom-links.svg"><path id="Rectangle-9" fill="#FBF2EC" stroke="#DBAF88" stroke-width="2" d="M150 20h117v28H150z"/><path id="Rectangle-7" fill="#FBF2EC" stroke="#DBAF88" stroke-width="2" d="M151 154h117v28H151z"/><path id="Rectangle-8" fill="#FBF2EC" stroke="#DBAF88" stroke-width="2" d="M117 87h198v28H117z"/><text id="document" fill="#AF6E24" font-family="PTMono-Regular, PT Mono" font-size="12" font-weight="normal"><tspan x="179.7" y="38">document</tspan></text><text id="document.documentEle" fill="#AF6E24" font-family="PTMono-Regular, PT Mono" font-size="12" font-weight="normal"><tspan x="129.6" y="105">document.documentElement </tspan></text><text id="Type-something" fill="#AF6E24" font-family="PTMono-Regular, PT Mono" font-size="12" font-weight="normal"><tspan x="324.9" y="105"><HTML></tspan></text><text id="document.body--" fill="#AF6E24" font-family="PTMono-Regular, PT Mono" font-size="12" font-weight="normal"><tspan x="164.2" y="173">document.body </tspan></text><text id="(if-inside-body)" fill="#AF6E24" font-family="PTMono-Regular, PT Mono" font-size="12" font-weight="normal"><tspan x="283.9" y="173">(if inside body)</tspan></text><path id="Line" fill="#C06334" fill-rule="nonzero" d="M209.5 119.71l.872 1.55 6.75 12 .49.871-1.743.98-.49-.87-4.879-8.674v23.785h-2v-23.785l-4.878 8.673-.49.872-1.744-.98.49-.872 6.75-12 .872-1.55z"/><path id="Line-2" fill="#C06334" fill-rule="nonzero" d="M209.5 52.71l.872 1.55 6.75 12 .49.871-1.743.98-.49-.87-4.879-8.674v21.528h-2V58.567l-4.878 8.673-.49.872-1.744-.98.49-.872 6.75-12 .872-1.55z"/><path id="Line" stroke="#AF6E24" stroke-linecap="square" stroke-width="2" d="M2.5 191H415"/><text id="parentNode" fill="#AF6E24" font-family="PTMono-Regular, PT Mono" font-size="12" font-weight="normal"><tspan x="172" y="248">parentNode</tspan></text><path id="Rectangle-6" fill="#FBF2EC" stroke="#DBAF88" stroke-width="2" d="M169 289h80v28h-80z"/><text id="<DIV>" fill="#AF6E24" font-family="OpenSans-Regular, Open Sans" font-size="12" font-weight="normal"><tspan x="192.192" y="308"><DIV></tspan></text><path id="Line" fill="#C06334" fill-rule="nonzero" d="M208.5 254.21l.872 1.55 6.75 12 .49.871-1.743.98-.49-.87-4.88-8.676.001 8.185v16.25h-2v-16.25l-.001-8.182-4.877 8.672-.49.872-1.744-.98.49-.872 6.75-12 .872-1.55z"/><path id="Line-Copy" fill="#C06334" fill-rule="nonzero" d="M209.5 226.5v4h-2v-4h2zm0-6v4h-2v-4h2zm0-6v4h-2v-4h2zm-1-14.29l.872 1.55 6.75 12 .49.871-1.743.98-.49-.87-4.88-8.676.001.435h-2l-.001-.432-4.877 8.672-.49.872-1.744-.98.49-.872 6.75-12 .872-1.55zm1 8.29v4h-2v-4h2z"/><path id="Line" fill="#C06334" fill-rule="nonzero" d="M356.369 294.388l.871.49 12 6.75 1.55.872-1.55.872-12 6.75-.871.49-.98-1.743.87-.49 8.673-4.879H254.5v-2h110.432l-8.672-4.878-.872-.49.98-1.744z"/><text id="nextSibling" fill="#AF6E24" font-family="PTMono-Regular, PT Mono" font-size="12" font-weight="normal"><tspan x="264" y="288">nextSibling</tspan></text><path id="Line" fill="#C06334" fill-rule="nonzero" d="M61.631 294.388l.98 1.743-.87.49-8.674 4.879H165.5v2H53.067l8.673 4.878.872.49-.98 1.744-.872-.49-12-6.75-1.55-.872 1.55-.872 12-6.75.871-.49z"/><text id="previousSibling" fill="#AF6E24" font-family="PTMono-Regular, PT Mono" font-size="12" font-weight="normal"><tspan x="50" y="288">previousSibling</tspan></text><path id="Line" fill="#C06334" fill-rule="nonzero" d="M162.822 324.123l1.055 1.7-.85.527-48.476 30.089-6.917 4.292 9.941-.428 1-.043.085 1.998-.999.043-13.755.594-1.776.076.857-1.557 6.636-12.064.482-.876 1.752.964-.482.876-4.797 8.718 6.918-4.293 48.477-30.089.85-.527z"/><path id="Line-3" fill="#C06334" fill-rule="nonzero" d="M257.214 324.115l.835.55 46.157 30.354 6.78 4.46-4.565-8.841-.459-.889 1.777-.918.459.889 6.317 12.233.816 1.58-1.774-.123-13.735-.954-.997-.07.138-1.995.998.07 9.926.689-6.78-4.46-46.156-30.354-.836-.55 1.099-1.671z"/><text id="childNodes" fill="#AF6E24" font-family="PTMono-Regular, PT Mono" font-size="12" font-weight="normal"><tspan x="170" y="343">childNodes</tspan></text><text id="firstChild--" fill="#AF6E24" font-family="PTMono-Regular, PT Mono" font-size="12" font-weight="normal"><tspan x="64" y="379">firstChild </tspan></text><text id="Type-something" fill="#AF6E24" font-family="PTMono-Regular, PT Mono" font-size="12" font-weight="normal"><tspan x="279" y="379">lastChild</tspan></text></g></g></svg> \ No newline at end of file diff --git a/2-ui/1-document/03-dom-navigation/dom-links@2x.png b/2-ui/1-document/03-dom-navigation/dom-links@2x.png deleted file mode 100644 index 827552f656..0000000000 Binary files a/2-ui/1-document/03-dom-navigation/dom-links@2x.png and /dev/null differ diff --git a/2-ui/1-document/04-searching-elements-dom/1-find-elements/solution.md b/2-ui/1-document/04-searching-elements-dom/1-find-elements/solution.md index c7080388e2..7ac8474c41 100644 --- a/2-ui/1-document/04-searching-elements-dom/1-find-elements/solution.md +++ b/2-ui/1-document/04-searching-elements-dom/1-find-elements/solution.md @@ -1,36 +1,35 @@ -There are many ways to do it. +Есть много путей как это сделать. -Here are some of them: +Вот некоторые: ```js -// 1. The table with `id="age-table"`. +// 1. Таблица с `id="age-table"`. let table = document.getElementById('age-table') -// 2. All label elements inside that table +// 2. Все label в этой таблице table.getElementsByTagName('label') -// or +// или document.querySelectorAll('#age-table label') -// 3. The first td in that table (with the word "Age"). +// 3. Первый td в этой таблице table.rows[0].cells[0] -// or +// или table.getElementsByTagName('td')[0] -// or +// или table.querySelector('td') -// 4. The form with the name "search". -// assuming there's only one element with name="search" +// 4. Форма с name="search" +// предполагаем, что есть только один элемент с таким name в документе let form = document.getElementsByName('search')[0] -// or, form specifically +// или, именно форма: document.querySelector('form[name="search"]') -// 5. The first input in that form. +// 5. Первый input в этой форме form.getElementsByTagName('input')[0] -// or +// или form.querySelector('input') -// 6. The last input in that form. -// there's no direct query for that -let inputs = form.querySelectorAll('input') // search all -inputs[inputs.length-1] // take last +// 6. Последний input в этой форме +let inputs = form.querySelectorAll('input') // найти все input +inputs[inputs.length-1] // взять последний ``` diff --git a/2-ui/1-document/04-searching-elements-dom/1-find-elements/task.md b/2-ui/1-document/04-searching-elements-dom/1-find-elements/task.md index 4d4faeb2a6..fd79aac79c 100644 --- a/2-ui/1-document/04-searching-elements-dom/1-find-elements/task.md +++ b/2-ui/1-document/04-searching-elements-dom/1-find-elements/task.md @@ -2,17 +2,17 @@ importance: 4 --- -# Search for elements +# Поиск элементов -Here's the document with the table and form. +Вот документ с таблицей и формой. -How to find? +Как найти?... -1. The table with `id="age-table"`. -2. All `label` elements inside that table (there should be 3 of them). -3. The first `td` in that table (with the word "Age"). -4. The `form` with the name `search`. -5. The first `input` in that form. -6. The last `input` in that form. +1. Таблицу с `id="age-table"`. +2. Все элементы `label` внутри этой таблицы (их три). +3. Первый `td` в этой таблице (со словом "Age"). +4. Форму `form` с именем `name="search"`. +5. Первый `input` в этой форме. +6. Последний `input` в этой форме. -Open the page [table.html](table.html) in a separate window and make use of browser tools for that. +Откройте страницу [table.html](table.html) в отдельном окне и используйте для этого браузерные инструменты разработчика. diff --git a/2-ui/1-document/04-searching-elements-dom/article.md b/2-ui/1-document/04-searching-elements-dom/article.md index 18ce24eba5..bb4dafaf02 100644 --- a/2-ui/1-document/04-searching-elements-dom/article.md +++ b/2-ui/1-document/04-searching-elements-dom/article.md @@ -1,88 +1,94 @@ -# Searching: getElement*, querySelector* -DOM navigation properties are great when elements are close to each other. What if they are not? How to get an arbitrary element of the page? +# Поиск: getElement*, querySelector* -There are additional searching methods for that. +Свойства навигации по DOM хороши, когда элементы расположены рядом. А что, если нет? Как получить произвольный элемент страницы? -## document.getElementById or just id +Для этого в DOM есть дополнительные методы поиска. -If an element has the `id` attribute, then there's a global variable by the name from that `id`. +## document.getElementById или просто id -We can use it to immediately access the element no matter where it is: +Если у элемента есть атрибут `id`, то мы можем получить его вызовом `document.getElementById(id)`, где бы он ни находился. + +Например: ```html run -<div id="*!*elem*/!*"> - <div id="*!*elem-content*/!*">Element</div> +<div id="elem"> + <div id="elem-content">Element</div> </div> <script> - alert(elem); // DOM-element with id="elem" - alert(window.elem); // accessing global variable like this also works + // получить элемент +*!* + let elem = document.getElementById('elem'); +*/!* - // for elem-content things are a bit more complex - // that has a dash inside, so it can't be a variable name - alert(window['elem-content']); // ...but accessible using square brackets [...] + // сделать его фон красным + elem.style.background = 'red'; </script> ``` -The behavior is described [in the specification](http://www.whatwg.org/specs/web-apps/current-work/#dom-window-nameditem), but it is supported mainly for compatibility. The browser tries to help us by mixing namespaces of JS and DOM. Good for very simple scripts, but there may be name conflicts. Also, when we look in JS and don't have HTML in view, it's not obvious where the variable comes from. +Также есть глобальная переменная с именем, указанным в `id`: -If we declare a variable with the same name, it takes precedence: - -```html run untrusted height=0 -<div id="elem"></div> +```html run +<div id="*!*elem*/!*"> + <div id="*!*elem-content*/!*">Элемент</div> +</div> <script> - let elem = 5; + // elem - ссылка на элемент с id="elem" + elem.style.background = 'red'; - alert(elem); // the variable overrides the element + // внутри id="elem-content" есть дефис, так что такой id не может служить именем переменной + // ...но мы можем обратиться к нему через квадратные скобки: window['elem-content'] </script> ``` -The better alternative is to use a special method `document.getElementById(id)`. - -For instance: +...Но это только если мы не объявили в JavaScript переменную с таким же именем, иначе она будет иметь приоритет: -```html run -<div id="elem"> - <div id="elem-content">Element</div> -</div> +```html run untrusted height=0 +<div id="elem"></div> <script> -*!* - let elem = document.getElementById('elem'); -*/!* + let elem = 5; // теперь elem равен 5, а не <div id="elem"> - elem.style.background = 'red'; + alert(elem); // 5 </script> ``` -Here in the tutorial we'll often use `id` to directly reference an element, but that's only to keep things short. In real life `document.getElementById` is the preferred method. +```warn header="Пожалуйста, не используйте такие глобальные переменные для доступа к элементам" +Это поведение соответствует [стандарту](https://html.spec.whatwg.org/#dom-window-nameditem), но поддерживается в основном для совместимости, как осколок далёкого прошлого. -```smart header="There can be only one" -The `id` must be unique. There can be only one element in the document with the given `id`. +Браузер пытается помочь нам, смешивая пространства имён JS и DOM. Это удобно для простых скриптов, которые находятся прямо в HTML, но, вообще говоря, не очень хорошо. Возможны конфликты имён. Кроме того, при чтении JS-кода, не видя HTML, непонятно, откуда берётся переменная. -If there are multiple elements with the same `id`, then the behavior of corresponding methods is unpredictable. The browser may return any of them at random. So please stick to the rule and keep `id` unique. +В этом учебнике мы будем обращаться к элементам по `id` в примерах для краткости, когда очевидно, откуда берётся элемент. + +В реальной жизни лучше использовать `document.getElementById`. ``` -```warn header="Only `document.getElementById`, not `anyNode.getElementById`" -The method `getElementById` that can be called only on `document` object. It looks for the given `id` in the whole document. +```smart header="Значение `id` должно быть уникальным" +Значение `id` должно быть уникальным. В документе может быть только один элемент с данным `id`. + +Если в документе есть несколько элементов с одинаковым значением `id`, то поведение методов поиска непредсказуемо. Браузер может вернуть любой из них случайным образом. Поэтому, пожалуйста, придерживайтесь правила сохранения уникальности `id`. +``` + +```warn header="Только `document.getElementById`, а не `anyElem.getElementById`" +Метод `getElementById` можно вызвать только для объекта `document`. Он осуществляет поиск по `id` по всему документу. ``` ## querySelectorAll [#querySelectorAll] -By far, the most versatile method, `elem.querySelectorAll(css)` returns all elements inside `elem` matching the given CSS selector. +Самый универсальный метод поиска - это `elem.querySelectorAll(css)`, он возвращает все элементы внутри `elem`, удовлетворяющие данному CSS-селектору. -Here we look for all `<li>` elements that are last children: +Следующий запрос получает все элементы `<li>`, которые являются последними потомками в `<ul>`: ```html run <ul> - <li>The</li> - <li>test</li> + <li>Этот</li> + <li>тест</li> </ul> <ul> - <li>has</li> - <li>passed</li> + <li>полностью</li> + <li>пройден</li> </ul> <script> *!* @@ -90,44 +96,44 @@ Here we look for all `<li>` elements that are last children: */!* for (let elem of elements) { - alert(elem.innerHTML); // "test", "passed" + alert(elem.innerHTML); // "тест", "пройден" } </script> ``` -This method is indeed powerful, because any CSS selector can be used. +Этот метод действительно мощный, потому что можно использовать любой CSS-селектор. -```smart header="Can use pseudo-classes as well" -Pseudo-classes in the CSS selector like `:hover` and `:active` are also supported. For instance, `document.querySelectorAll(':hover')` will return the collection with elements that the pointer is over now (in nesting order: from the outermost `<html>` to the most nested one). +```smart header="Псевдоклассы тоже работают" +Псевдоклассы в CSS-селекторе, в частности `:hover` и `:active`, также поддерживаются. Например, `document.querySelectorAll(':hover')` вернёт коллекцию (в порядке вложенности: от внешнего к внутреннему) из текущих элементов под курсором мыши. ``` ## querySelector [#querySelector] -The call to `elem.querySelector(css)` returns the first element for the given CSS selector. +Метод `elem.querySelector(css)` возвращает первый элемент, соответствующий данному CSS-селектору. -In other words, the result is the same as `elem.querySelectorAll(css)[0]`, but the latter is looking for *all* elements and picking one, while `elem.querySelector` just looks for one. So it's faster and shorter to write. +Иначе говоря, результат такой же, как при вызове `elem.querySelectorAll(css)[0]`, но он сначала найдёт *все* элементы, а потом возьмёт первый, в то время как `elem.querySelector` найдёт только первый и остановится. Это быстрее, кроме того, его короче писать. ## matches -Previous methods were searching the DOM. +Предыдущие методы искали по DOM. -The [elem.matches(css)](http://dom.spec.whatwg.org/#dom-element-matches) does not look for anything, it merely checks if `elem` matches the given CSS-selector. It returns `true` or `false`. +Метод [elem.matches(css)](https://dom.spec.whatwg.org/#dom-element-matches) ничего не ищет, а проверяет, удовлетворяет ли `elem` CSS-селектору, и возвращает `true` или `false`. -The method comes handy when we are iterating over elements (like in array or something) and trying to filter those that interest us. +Этот метод удобен, когда мы перебираем элементы (например, в массиве или в чём-то подобном) и пытаемся выбрать те из них, которые нас интересуют. -For instance: +Например: ```html run <a href="http://example.com/file.zip">...</a> <a href="http://ya.ru">...</a> <script> - // can be any collection instead of document.body.children + // может быть любая коллекция вместо document.body.children for (let elem of document.body.children) { *!* if (elem.matches('a[href$="zip"]')) { */!* - alert("The archive reference: " + elem.href ); + alert("Ссылка на архив: " + elem.href ); } } </script> @@ -135,21 +141,21 @@ For instance: ## closest -*Ancestors* of an element are: parent, the parent of parent, its parent and so on. The ancestors together form the chain of parents from the element to the top. +*Предки* элемента - родитель, родитель родителя, его родитель и так далее. Вместе они образуют цепочку иерархии от элемента до вершины. -The method `elem.closest(css)` looks the nearest ancestor that matches the CSS-selector. The `elem` itself is also included in the search. +Метод `elem.closest(css)` ищет ближайшего предка, который соответствует CSS-селектору. Сам элемент также включается в поиск. -In other words, the method `closest` goes up from the element and checks each of parents. If it matches the selector, then the search stops, and the ancestor is returned. +Другими словами, метод `closest` поднимается вверх от элемента и проверяет каждого из родителей. Если он соответствует селектору, поиск прекращается. Метод возвращает либо предка, либо `null`, если такой элемент не найден. -For instance: +Например: ```html run -<h1>Contents</h1> +<h1>Содержание</h1> <div class="contents"> <ul class="book"> - <li class="chapter">Chapter 1</li> - <li class="chapter">Chapter 1</li> + <li class="chapter">Глава 1</li> + <li class="chapter">Глава 2</li> </ul> </div> @@ -159,44 +165,44 @@ For instance: alert(chapter.closest('.book')); // UL alert(chapter.closest('.contents')); // DIV - alert(chapter.closest('h1')); // null (because h1 is not an ancestor) + alert(chapter.closest('h1')); // null (потому что h1 - не предок) </script> ``` ## getElementsBy* -There are also other methods to look for nodes by a tag, class, etc. +Существуют также другие методы поиска элементов по тегу, классу и так далее. -Today, they are mostly history, as `querySelector` is more powerful and shorter to write. +На данный момент, они скорее исторические, так как `querySelector` более чем эффективен. -So here we cover them mainly for completeness, while you can still find them in the old scripts. +Здесь мы рассмотрим их для полноты картины, также вы можете встретить их в старом коде. -- `elem.getElementsByTagName(tag)` looks for elements with the given tag and returns the collection of them. The `tag` parameter can also be a star `"*"` for "any tags". -- `elem.getElementsByClassName(className)` returns elements that have the given CSS class. Elements may have other classes too. -- `document.getElementsByName(name)` returns elements with the given `name` attribute, document-wide. very rarely used. +- `elem.getElementsByTagName(tag)` ищет элементы с данным тегом и возвращает их коллекцию. Передав `"*"` вместо тега, можно получить всех потомков. +- `elem.getElementsByClassName(className)` возвращает элементы, которые имеют данный CSS-класс. +- `document.getElementsByName(name)` возвращает элементы с заданным атрибутом `name`. Очень редко используется. -For instance: +Например: ```js -// get all divs in the document +// получить все элементы div в документе let divs = document.getElementsByTagName('div'); ``` -Let's find all `input` tags inside the table: +Давайте найдём все `input` в таблице: ```html run height=50 <table id="table"> <tr> - <td>Your age:</td> + <td>Ваш возраст:</td> <td> <label> - <input type="radio" name="age" value="young" checked> less than 18 + <input type="radio" name="age" value="young" checked> младше 18 </label> <label> - <input type="radio" name="age" value="mature"> from 18 to 50 + <input type="radio" name="age" value="mature"> от 18 до 50 </label> <label> - <input type="radio" name="age" value="senior"> more than 60 + <input type="radio" name="age" value="senior"> старше 60 </label> </td> </tr> @@ -213,31 +219,31 @@ Let's find all `input` tags inside the table: </script> ``` -```warn header="Don't forget the `\"s\"` letter!" -Novice developers sometimes forget the letter `"s"`. That is, they try to call `getElementByTagName` instead of <code>getElement<b>s</b>ByTagName</code>. +```warn header="Не забываем про букву `\"s\"`!" +Одна из самых частых ошибок начинающих разработчиков (впрочем, иногда и не только) - это забыть букву `"s"`. То есть пробовать вызывать метод `getElementByTagName` вместо <code>getElement<b>s</b>ByTagName</code>. -The `"s"` letter is absent in `getElementById`, because it returns a single element. But `getElementsByTagName` returns a collection of elements, so there's `"s"` inside. +Буква `"s"` отсутствует в названии метода `getElementById`, так как в данном случае возвращает один элемент. Но `getElementsByTagName` вернёт список элементов, поэтому `"s"` обязательна. ``` -````warn header="It returns a collection, not an element!" -Another widespread novice mistake is to write: +````warn header="Возвращает коллекцию, а не элемент!" +Другая распространённая ошибка - написать: ```js -// doesn't work +// не работает document.getElementsByTagName('input').value = 5; ``` -That won't work, because it takes a *collection* of inputs and assigns the value to it rather than to elements inside it. +Попытка присвоить значение *коллекции*, а не элементам внутри неё, не сработает. -We should either iterate over the collection or get an element by its index, and then assign, like this: +Нужно перебрать коллекцию в цикле или получить элемент по номеру и уже ему присваивать значение, например, так: ```js -// should work (if there's an input) +// работает (если есть input) document.getElementsByTagName('input')[0].value = 5; ``` ```` -Looking for `.article` elements: +Ищем элементы с классом `.article`: ```html run height=50 <form name="my-form"> @@ -246,23 +252,23 @@ Looking for `.article` elements: </form> <script> - // find by name attribute + // ищем по имени атрибута let form = document.getElementsByName('my-form')[0]; - // find by class inside the form + // ищем по классу внутри form let articles = form.getElementsByClassName('article'); - alert(articles.length); // 2, found two elements with class "article" + alert(articles.length); // 2, находим два элемента с классом article </script> ``` -## Live collections +## Живые коллекции -All methods `"getElementsBy*"` return a *live* collection. Such collections always reflect the current state of the document and "auto-update" when it changes. +Все методы `"getElementsBy*"` возвращают *живую* коллекцию. Такие коллекции всегда отражают текущее состояние документа и автоматически обновляются при его изменении. -In the example below, there are two scripts. +В приведённом ниже примере есть два скрипта. -1. The first one creates a reference to the collection of `<div>`. As of now, its length is `1`. -2. The second scripts runs after the browser meets one more `<div>`, so its length is `2`. +1. Первый создаёт ссылку на коллекцию `<div>`. На этот момент её длина равна `1`. +2. Второй скрипт запускается после того, как браузер встречает ещё один `<div>`, теперь её длина - `2`. ```html run <div>First div</div> @@ -281,9 +287,9 @@ In the example below, there are two scripts. </script> ``` -In contrast, `querySelectorAll` returns a *static* collection. It's like a fixed array of elements. +Напротив, `querySelectorAll` возвращает *статическую* коллекцию. Это похоже на фиксированный массив элементов. -If we use it instead, then both scripts output `1`: +Если мы будем использовать его в примере выше, то оба скрипта вернут длину коллекции, равную `1`: ```html run @@ -303,21 +309,19 @@ If we use it instead, then both scripts output `1`: </script> ``` -Now we can easily see the difference. The static collection did not increase after the appearance of a new `div` in the document. - -Here we used separate scripts to illustrate how the element addition affects the collection, but any DOM manipulations affect them. Soon we'll see more of them. +Теперь мы легко видим разницу. Длина статической коллекции не изменилась после появления нового `div` в документе. -## Summary +## Итого -There are 6 main methods to search for nodes in DOM: +Есть 6 основных методов поиска элементов в DOM: <table> <thead> <tr> -<td>Method</td> -<td>Searches by...</td> -<td>Can call on an element?</td> -<td>Live?</td> +<td>Метод</td> +<td>Ищет по...</td> +<td>Ищет внутри элемента?</td> +<td>Возвращает живую коллекцию?</td> </tr> </thead> <tbody> @@ -360,12 +364,12 @@ There are 6 main methods to search for nodes in DOM: </tbody> </table> -By far the most used are `querySelector` and `querySelectorAll`, but `getElementBy*` can be sporadically helpful or found in the old scripts. +Безусловно, наиболее часто используемыми в настоящее время являются методы `querySelector` и `querySelectorAll`, но и методы `getElement(s)By*` могут быть полезны в отдельных случаях, а также встречаются в старом коде. -Besides that: +Кроме того: -- There is `elem.matches(css)` to check if `elem` matches the given CSS selector. -- There is `elem.closest(css)` to look for the nearest ancestor that matches the given CSS-selector. The `elem` itself is also checked. +- Есть метод `elem.matches(css)`, который проверяет, удовлетворяет ли элемент CSS-селектору. +- Метод `elem.closest(css)` ищет ближайшего по иерархии предка, соответствующему данному CSS-селектору. Сам элемент также включён в поиск. -And let's mention one more method here to check for the child-parent relationship: -- `elemA.contains(elemB)` returns true if `elemB` is inside `elemA` (a descendant of `elemA`) or when `elemA==elemB`. +И, напоследок, давайте упомянем ещё один метод, который проверяет наличие отношений между предком и потомком: +- `elemA.contains(elemB)` вернёт `true`, если `elemB` находится внутри `elemA` (`elemB` потомок `elemA`) или когда `elemA==elemB`. diff --git a/2-ui/1-document/05-basic-dom-node-properties/2-lastchild-nodetype-inline/solution.md b/2-ui/1-document/05-basic-dom-node-properties/2-lastchild-nodetype-inline/solution.md index 52c34640aa..1c84a92456 100644 --- a/2-ui/1-document/05-basic-dom-node-properties/2-lastchild-nodetype-inline/solution.md +++ b/2-ui/1-document/05-basic-dom-node-properties/2-lastchild-nodetype-inline/solution.md @@ -1,8 +1,8 @@ -There's a catch here. +Здесь есть подвох. -At the time of `<script>` execution the last DOM node is exactly `<script>`, because the browser did not process the rest of the page yet. +Во время выполнения `<script>` последним DOM-узлом является `<script>`, потому что браузер ещё не обработал остальную часть страницы. -So the result is `1` (element node). +Поэтому результатом будет `1` (узел-элемент). ```html run height=60 <html> diff --git a/2-ui/1-document/05-basic-dom-node-properties/2-lastchild-nodetype-inline/task.md b/2-ui/1-document/05-basic-dom-node-properties/2-lastchild-nodetype-inline/task.md index 0ed407cae5..3b5d51955d 100644 --- a/2-ui/1-document/05-basic-dom-node-properties/2-lastchild-nodetype-inline/task.md +++ b/2-ui/1-document/05-basic-dom-node-properties/2-lastchild-nodetype-inline/task.md @@ -2,9 +2,9 @@ importance: 5 --- -# What's in the nodeType? +# Что содержит свойство nodeType? -What does the script show? +Что выведет этот код? ```html <html> diff --git a/2-ui/1-document/05-basic-dom-node-properties/2-tree-info/solution.md b/2-ui/1-document/05-basic-dom-node-properties/2-tree-info/solution.md index 781b7a929a..b8218e1d29 100644 --- a/2-ui/1-document/05-basic-dom-node-properties/2-tree-info/solution.md +++ b/2-ui/1-document/05-basic-dom-node-properties/2-tree-info/solution.md @@ -1,4 +1,4 @@ -Let's make a loop over `<li>`: +Пройдём циклом по всем элементам `<li>`: ```js for (let li of document.querySelectorAll('li')) { @@ -6,14 +6,14 @@ for (let li of document.querySelectorAll('li')) { } ``` -In the loop we need to get the text inside every `li`. We can read it directly from the first child node, that is the text node: +В цикле нам нужно получить текст в каждом элементе `li`. Мы можем прочитать текстовое содержимое элемента списка из первого дочернего узла `li`, который будет текстовым узлом: ```js for (let li of document.querySelectorAll('li')) { let title = li.firstChild.data; - // title is the text in <li> before any other nodes + // переменная title содержит текст элемента <li> } ``` -Then we can get the number of descendants `li.getElementsByTagName('li')`. +Так мы сможем получить количество потомков как `li.getElementsByTagName('li').length`. diff --git a/2-ui/1-document/05-basic-dom-node-properties/2-tree-info/solution.view/index.html b/2-ui/1-document/05-basic-dom-node-properties/2-tree-info/solution.view/index.html index 5947ec0977..66ffadf8ef 100644 --- a/2-ui/1-document/05-basic-dom-node-properties/2-tree-info/solution.view/index.html +++ b/2-ui/1-document/05-basic-dom-node-properties/2-tree-info/solution.view/index.html @@ -3,36 +3,36 @@ <body> <ul> - <li>Animals + <li>Животные <ul> - <li>Mammals + <li>Млекопитающие <ul> - <li>Cows</li> - <li>Donkeys</li> - <li>Dogs</li> - <li>Tigers</li> + <li>Коровы</li> + <li>Ослы</li> + <li>Собаки</li> + <li>Тигры</li> </ul> </li> - <li>Other + <li>Другие <ul> - <li>Snakes</li> - <li>Birds</li> - <li>Lizards</li> + <li>Змеи</li> + <li>Птицы</li> + <li>Ящерицы</li> </ul> </li> </ul> </li> - <li>Fishes + <li>Рыбы <ul> - <li>Aquarium + <li>Аквариумные <ul> - <li>Guppy</li> - <li>Angelfish</li> + <li>Гуппи</li> + <li>Рыба-ангел</li> </ul> </li> - <li>Sea + <li>Морские <ul> - <li>Sea trout</li> + <li>Морская форель</li> </ul> </li> </ul> @@ -41,12 +41,12 @@ <script> for (let li of document.querySelectorAll('li')) { - // get the title from the text node + // получаем название из текстового узла let title = li.firstChild.data; - title = title.trim(); // remove extra spaces from ends + title = title.trim(); // удаляем лишние пробелы c конца строки - // get the descendants count + // считаем количество потомков let count = li.getElementsByTagName('li').length; alert(title + ': ' + count); diff --git a/2-ui/1-document/05-basic-dom-node-properties/2-tree-info/source.view/index.html b/2-ui/1-document/05-basic-dom-node-properties/2-tree-info/source.view/index.html index fbfacaa88f..776e6d96d6 100644 --- a/2-ui/1-document/05-basic-dom-node-properties/2-tree-info/source.view/index.html +++ b/2-ui/1-document/05-basic-dom-node-properties/2-tree-info/source.view/index.html @@ -3,36 +3,36 @@ <body> <ul> - <li>Animals + <li>Животные <ul> - <li>Mammals + <li>Млекопитающие <ul> - <li>Cows</li> - <li>Donkeys</li> - <li>Dogs</li> - <li>Tigers</li> + <li>Коровы</li> + <li>Ослы</li> + <li>Собаки</li> + <li>Тигры</li> </ul> </li> - <li>Other + <li>Другие <ul> - <li>Snakes</li> - <li>Birds</li> - <li>Lizards</li> + <li>Змеи</li> + <li>Птицы</li> + <li>Ящерицы</li> </ul> </li> </ul> </li> - <li>Fishes + <li>Рыбы <ul> - <li>Aquarium + <li>Аквариумные <ul> - <li>Guppy</li> - <li>Angelfish</li> + <li>Гуппи</li> + <li>Рыба-ангел</li> </ul> </li> - <li>Sea + <li>Морские <ul> - <li>Sea trout</li> + <li>Морская форель</li> </ul> </li> </ul> @@ -40,7 +40,7 @@ </ul> <script> - // ... your code... + // ... ваш код... </script> </body> diff --git a/2-ui/1-document/05-basic-dom-node-properties/2-tree-info/task.md b/2-ui/1-document/05-basic-dom-node-properties/2-tree-info/task.md index f2d9edc674..8d9ff22d34 100644 --- a/2-ui/1-document/05-basic-dom-node-properties/2-tree-info/task.md +++ b/2-ui/1-document/05-basic-dom-node-properties/2-tree-info/task.md @@ -2,13 +2,13 @@ importance: 5 --- -# Count descendants +# Считаем потомков -There's a tree structured as nested `ul/li`. +У нас есть дерево, структурированное как вложенные списки `ul/li`. -Write the code that for each `<li>` shows: +Напишите код, который выведет каждый элемент списка `<li>`: -1. What's the text inside it (without the subtree) -2. The number of nested `<li>` -- all descendants, including the deeply nested ones. +1. Какой в нём текст (без поддерева) ? +2. Какое число потомков -- всех вложенных `<li>` (включая глубоко вложенные) ? [demo src="solution"] diff --git a/2-ui/1-document/05-basic-dom-node-properties/3-tag-in-comment/solution.md b/2-ui/1-document/05-basic-dom-node-properties/3-tag-in-comment/solution.md index 32900a7896..2622d760a6 100644 --- a/2-ui/1-document/05-basic-dom-node-properties/3-tag-in-comment/solution.md +++ b/2-ui/1-document/05-basic-dom-node-properties/3-tag-in-comment/solution.md @@ -1,4 +1,4 @@ -The answer: **`BODY`**. +Ответ: **`BODY`**. ```html run <script> @@ -10,8 +10,8 @@ The answer: **`BODY`**. </script> ``` -What's going on step by step: +Происходящее по шагам: -1. The content of `<body>` is replaced with the comment. The comment is `<!--BODY-->`, because `body.tagName == "BODY"`. As we remember, `tagName` is always uppercase in HTML. -2. The comment is now the only child node, so we get it in `body.firstChild`. -3. The `data` property of the comment is its contents (inside `<!--...-->`): `"BODY"`. +1. Заменяем содержимое `<body>` на комментарий. Он будет иметь вид `<!--BODY-->`, т.к. `body.tagName == "BODY"`. Как мы помним, свойство `tagName` в HTML всегда находится в верхнем регистре. +2. Этот комментарий теперь является первым и единственным потомком `body.firstChild`. +3. Значение свойства `data` для элемента-комментария -- это его содержимое (внутри `<!--...-->`): `"BODY"`. diff --git a/2-ui/1-document/05-basic-dom-node-properties/3-tag-in-comment/task.md b/2-ui/1-document/05-basic-dom-node-properties/3-tag-in-comment/task.md index efe50b48f2..245a5e356e 100644 --- a/2-ui/1-document/05-basic-dom-node-properties/3-tag-in-comment/task.md +++ b/2-ui/1-document/05-basic-dom-node-properties/3-tag-in-comment/task.md @@ -2,9 +2,9 @@ importance: 3 --- -# Tag in comment +# Тег в комментарии -What does this code show? +Что выведет этот код? ```html <script> @@ -12,6 +12,6 @@ What does this code show? body.innerHTML = "<!--" + body.tagName + "-->"; - alert( body.firstChild.data ); // what's here? + alert( body.firstChild.data ); // что выведет? </script> ``` diff --git a/2-ui/1-document/05-basic-dom-node-properties/4-where-document-in-hierarchy/solution.md b/2-ui/1-document/05-basic-dom-node-properties/4-where-document-in-hierarchy/solution.md index db7ebc9d8c..ea54b96cc0 100644 --- a/2-ui/1-document/05-basic-dom-node-properties/4-where-document-in-hierarchy/solution.md +++ b/2-ui/1-document/05-basic-dom-node-properties/4-where-document-in-hierarchy/solution.md @@ -1,33 +1,33 @@ -We can see which class it belongs by outputting it, like: +Объектом какого класса является `document`, можно выяснить так: ```js run alert(document); // [object HTMLDocument] ``` -Or: +Или так: ```js run alert(document.constructor.name); // HTMLDocument ``` -So, `document` is an instance of `HTMLDocument` class. +Итак, `document` -- объект класса `HTMLDocument`. -What's its place in the hierarchy? +Какое место HTMLDocument занимает в иерархии? -Yeah, we could browse the specification, but it would be faster to figure out manually. +Можно поискать в документации. Но попробуем выяснить это самостоятельно. -Let's traverse the prototype chain via `__proto__`. +Пройдём по цепочке прототипов по ссылке`__proto__`. -As we know, methods of a class are in the `prototype` of the constructor. For instance, `HTMLDocument.prototype` has methods for documents. +Как мы знаем, методы класса находятся в `prototype` конструктора. Например, в `HTMLDocument.prototype` находятся методы для объектов типа document. -Also, there's a reference to the constructor function inside the `prototype`: +Также внутри `prototype` есть ссылка на функцию-конструктор: ```js run alert(HTMLDocument.prototype.constructor === HTMLDocument); // true ``` -For built-in classes in all prototypes there's a `constructor` reference, and we can get `constructor.name` to see the name of the class. Let's do it for all objects in the `document` prototype chain: +Чтобы получить имя класса в строковой форме, используем `constructor.name`. Сделаем это для всей цепочки прототипов `document` вверх до класса `Node`: ```js run alert(HTMLDocument.prototype.constructor.name); // HTMLDocument @@ -35,4 +35,6 @@ alert(HTMLDocument.prototype.__proto__.constructor.name); // Document alert(HTMLDocument.prototype.__proto__.__proto__.constructor.name); // Node ``` -We also could examine the object using `console.dir(document)` and see these names by opening `__proto__`. The console takes them from `constructor` internally. +Вот и иерархия. + +Мы также можем исследовать объект с помощью `console.dir(document)` и увидеть имена функций-конструкторов, открыв `__proto__`. Браузерная консоль берёт их как раз из свойства `constructor`. diff --git a/2-ui/1-document/05-basic-dom-node-properties/4-where-document-in-hierarchy/task.md b/2-ui/1-document/05-basic-dom-node-properties/4-where-document-in-hierarchy/task.md index de266c6ae9..8ce82a1500 100644 --- a/2-ui/1-document/05-basic-dom-node-properties/4-where-document-in-hierarchy/task.md +++ b/2-ui/1-document/05-basic-dom-node-properties/4-where-document-in-hierarchy/task.md @@ -2,10 +2,10 @@ importance: 4 --- -# Where's the "document" in the hierarchy? +# Где в DOM-иерархии "document"? -Which class does the `document` belong to? +Объектом какого класса является `document`? -What's its place in the DOM hierarchy? +Какое место он занимает в DOM-иерархии? -Does it inherit from `Node` or `Element`, or maybe `HTMLElement`? +Наследует ли он от `Node` или от `Element`, или может от `HTMLElement`? diff --git a/2-ui/1-document/05-basic-dom-node-properties/_dom-class-hierarchy.svg b/2-ui/1-document/05-basic-dom-node-properties/_dom-class-hierarchy.svg new file mode 100644 index 0000000000..356d0dbcb6 --- /dev/null +++ b/2-ui/1-document/05-basic-dom-node-properties/_dom-class-hierarchy.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="552" height="403" viewBox="0 0 552 403"><defs><style>@import url(https://fonts.googleapis.com/css?family=Open+Sans:bold,italic,bolditalic%7CPT+Mono);@font-face{font-family:'PT Mono';font-weight:700;font-style:normal;src:local('PT MonoBold'),url(/font/PTMonoBold.woff2) format('woff2'),url(/font/PTMonoBold.woff) format('woff'),url(/font/PTMonoBold.ttf) format('truetype')}</style></defs><g id="dom" fill="none" fill-rule="evenodd" stroke="none" stroke-width="1"><g id="dom-class-hierarchy.svg"><path id="Rectangle-9" fill="#FBF2EC" stroke="#DBAF88" stroke-width="2" d="M181 6h118v28H181z"/><path id="Rectangle-8" fill="#FBF2EC" stroke="#DBAF88" stroke-width="2" d="M181 74h118v28H181z"/><text id="EventTarget" fill="#AF6E24" font-family="PTMono-Regular, PT Mono" font-size="12" font-weight="normal"><tspan x="200.9" y="24">EventTarget</tspan></text><text id="Node" fill="#AF6E24" font-family="PTMono-Regular, PT Mono" font-size="12" font-weight="normal"><tspan x="225.6" y="91">Node </tspan></text><path id="Line-2" fill="#C06334" fill-rule="nonzero" d="M240.5 39.5l7 14h-6v17h-2v-17h-6l7-14z"/><path id="Rectangle-8-Copy" fill="#FBF2EC" stroke="#DBAF88" stroke-width="2" d="M181 144h118v28H181z"/><text id="Element" fill="#AF6E24" font-family="PTMono-Regular, PT Mono" font-size="12" font-weight="normal"><tspan x="214.8" y="161">Element </tspan></text><path id="Line-2-Copy" fill="#C06334" fill-rule="nonzero" d="M240.5 109.5l7 14h-6v17h-2v-17h-6l7-14z"/><path id="Rectangle-8-Copy-4" fill="#FBF2EC" stroke="#DBAF88" stroke-width="2" d="M181 230h118v28H181z"/><text id="HTMLElement" fill="#AF6E24" font-family="PTMono-Regular, PT Mono" font-size="12" font-weight="normal"><tspan x="200.4" y="247">HTMLElement </tspan></text><path id="Line-2-Copy-4" fill="#C06334" fill-rule="nonzero" d="M240.5 195.5l7 14h-6v17h-2v-17h-6l7-14zM72.5 158.5l7.273 13.86-5.999.117L74 183.98l.02 1-2 .04-.02-1-.226-11.503-5.998.118L72.5 158.5z"/><path id="Rectangle-8-Copy-6" fill="#FBF2EC" stroke="#DBAF88" stroke-width="2" d="M171 300h138v28H171z"/><text id="HTMLBodyElement" fill="#AF6E24" font-family="PTMono-Regular, PT Mono" font-size="12" font-weight="normal"><tspan x="186" y="317">HTMLBodyElement </tspan></text><path id="Line-2-Copy-6" fill="#C06334" fill-rule="nonzero" d="M240.5 265.5l7 14h-6v17h-2v-17h-6l7-14z"/><path id="Rectangle-8-Copy-7" fill="#FBF2EC" stroke="#DBAF88" stroke-width="2" d="M1 300h138v28H1z"/><text id="HTMLInputElement" fill="#AF6E24" font-family="PTMono-Regular, PT Mono" font-size="12" font-weight="normal"><tspan x="13.4" y="317">HTMLInputElement </tspan></text><path id="Line-2-Copy-7" fill="#C06334" fill-rule="nonzero" d="M159 261l-6.753 14.12-3.685-4.736-29.448 22.905-.79.614-1.227-1.578.79-.614 29.448-22.906-3.684-4.735L159 261z"/><path id="Rectangle-8-Copy-8" fill="#FBF2EC" stroke="#DBAF88" stroke-width="2" d="M339 300h138v28H339z"/><text id="HTMLAnchorElement" fill="#AF6E24" font-family="PTMono-Regular, PT Mono" font-size="12" font-weight="normal"><tspan x="348.3" y="317">HTMLAnchorElement </tspan></text><path id="Line-2-Copy-8" fill="#C06334" fill-rule="nonzero" d="M312 261l15.305 3.28-3.749 4.684 29.069 23.255.78.625-1.249 1.562-.78-.625-29.069-23.254-3.748 4.685L312 261z"/><path id="Rectangle-8-Copy-2" fill="#FBF2EC" stroke="#DBAF88" stroke-width="2" d="M22 126h98v28H22z"/><text id="Document" fill="#AF6E24" font-family="PTMono-Regular, PT Mono" font-size="12" font-weight="normal"><tspan x="42.2" y="144">Document </tspan></text><path id="Rectangle-8-Copy-2" fill="#FBF2EC" stroke="#DBAF88" stroke-width="2" d="M22 192h98v28H22z"/><text id="HTMLDocument" fill="#AF6E24" font-family="PTMono-Regular, PT Mono" font-size="12" font-weight="normal"><tspan x="27.8" y="210">HTMLDocument </tspan></text><path id="Line-2-Copy-2" fill="#C06334" fill-rule="nonzero" d="M168 90l-8.862 12.902-2.905-5.251-34.749 19.224-.875.484-.968-1.75.875-.484 34.749-19.224-2.904-5.25L168 90z"/><path id="Rectangle-8-Copy-3" fill="#FBF2EC" stroke="#DBAF88" stroke-width="2" d="M369 126h118v28H369z"/><text id="CharacterData" fill="#AF6E24" font-family="PTMono-Regular, PT Mono" font-size="12" font-weight="normal"><tspan x="381.2" y="144">CharacterData </tspan></text><path id="Line-2-Copy-3" fill="#C06334" fill-rule="nonzero" d="M314 91l15.648.379-2.813 5.299 36.634 19.439.883.468-.937 1.767-.884-.469-36.633-19.439-2.812 5.301L314 91zM458 159l13.93 7.138-4.836 3.552 9.712 13.218.592.806-1.612 1.184-.592-.806-9.712-13.218-4.834 3.553L458 159zM400 158l-1.376 15.592-5.11-3.146-8.662 14.078-.524.852-1.704-1.048.524-.852 8.663-14.078-5.11-3.143L400 158z"/><text id="Document-as-a-whole" fill="#C06334" font-family="PTMono-Regular, PT Mono" font-size="12" font-weight="normal"><tspan x="6.6" y="235">Весь документ в целом</tspan></text><text id="<input-type="…">" fill="#C06334" font-family="PTMono-Regular, PT Mono" font-size="12" font-weight="normal"><tspan x="13.4" y="341"><input type="…"></tspan></text><text id="<body>" fill="#C06334" font-family="PTMono-Regular, PT Mono" font-size="12" font-weight="normal"><tspan x="220.4" y="341"><body></tspan></text><text id="<a-href="…">" fill="#C06334" font-family="PTMono-Regular, PT Mono" font-size="12" font-weight="normal"><tspan x="365.8" y="341"><a href="…"></tspan></text><text id="<div>...</div>" font-family="PTMono-Regular, PT Mono" font-size="12" font-weight="normal"><tspan x="192.6" y="188" fill="#AF6E24"><</tspan> <tspan x="199.8" y="188" fill="#C06334">div</tspan> <tspan x="221.4" y="188" fill="#AF6E24">></tspan> <tspan x="228.6" y="188" fill="#DBAF88">...</tspan> <tspan x="250.2" y="188" fill="#AF6E24"></</tspan> <tspan x="264.6" y="188" fill="#C06334">div</tspan> <tspan x="286.2" y="188" fill="#AF6E24">></tspan></text><path id="Rectangle-8-Copy-3" fill="#FBF2EC" stroke="#DBAF88" stroke-width="2" d="M449 192h78v28h-78z"/><text id="Comment" fill="#AF6E24" font-family="PTMono-Regular, PT Mono" font-size="12" font-weight="normal"><tspan x="463.8" y="210">Comment </tspan></text><text id="<!--comment-->" fill="#C06334" font-family="PTMono-Regular, PT Mono" font-size="12" font-weight="normal"><tspan x="442.6" y="237"><!--comment--></tspan></text><path id="Rectangle-8-Copy-3" fill="#FBF2EC" stroke="#DBAF88" stroke-width="2" d="M342 192h78v28h-78z"/><text id="Text" fill="#AF6E24" font-family="PTMono-Regular, PT Mono" font-size="12" font-weight="normal"><tspan x="365.6" y="210">Text </tspan></text><text id=""Hello"" fill="#C06334" font-family="PTMono-Regular, PT Mono" font-size="12" font-weight="normal"><tspan x="356.8" y="237">"Hello"</tspan></text></g></g></svg> diff --git a/2-ui/1-document/05-basic-dom-node-properties/article.md b/2-ui/1-document/05-basic-dom-node-properties/article.md index eca02163a0..3d36432531 100644 --- a/2-ui/1-document/05-basic-dom-node-properties/article.md +++ b/2-ui/1-document/05-basic-dom-node-properties/article.md @@ -1,56 +1,84 @@ -# Node properties: type, tag and contents -Let's get a more in-depth look at DOM nodes. +# Свойства узлов: тип, тег и содержимое -In this chapter we'll see more into what they are and their most used properties. +Теперь давайте более внимательно взглянем на DOM-узлы. -## DOM node classes +В этой главе мы подробнее разберём, что они собой представляют и изучим их основные свойства. -DOM nodes have different properties depending on their class. For instance, an element node corresponding to tag `<a>` has link-related properties, and the one corresponding to `<input>` has input-related properties and so on. Text nodes are not the same as element nodes. But there are also common properties and methods between all of them, because all classes of DOM nodes form a single hierarchy. +## Классы DOM-узлов -Each DOM node belongs to the corresponding built-in class. +У разных DOM-узлов могут быть разные свойства. Например, у узла, соответствующего тегу `<a>`, есть свойства, связанные со ссылками, а у соответствующего тегу `<input>` -- свойства, связанные с полем ввода и т.д. Текстовые узлы отличаются от узлов-элементов. Но у них есть общие свойства и методы, потому что все классы DOM-узлов образуют единую иерархию. -The root of the hierarchy is [EventTarget](https://dom.spec.whatwg.org/#eventtarget), that is inherited by [Node](http://dom.spec.whatwg.org/#interface-node), and other DOM nodes inherit from it. +Каждый DOM-узел принадлежит соответствующему встроенному классу. -Here's the picture, explanations to follow: +Корнем иерархии является [EventTarget](https://dom.spec.whatwg.org/#eventtarget), от него наследует [Node](https://dom.spec.whatwg.org/#interface-node) и остальные DOM-узлы. -![](dom-class-hierarchy.png) +На рисунке ниже изображены основные классы: -The classes are: +![](_dom-class-hierarchy.svg) -- [EventTarget](https://dom.spec.whatwg.org/#eventtarget) -- is the root "abstract" class. Objects of that class are never created. It serves as a base, so that all DOM nodes support so-called "events", we'll study them later. -- [Node](http://dom.spec.whatwg.org/#interface-node) -- is also an "abstract" class, serving as a base for DOM nodes. It provides the core tree functionality: `parentNode`, `nextSibling`, `childNodes` and so on (they are getters). Objects of `Node` class are never created. But there are concrete node classes that inherit from it, namely: `Text` for text nodes, `Element` for element nodes and more exotic ones like `Comment` for comment nodes. -- [Element](http://dom.spec.whatwg.org/#interface-element) -- is a base class for DOM elements. It provides element-level navigation like `nextElementSibling`, `children` and searching methods like `getElementsByTagName`, `querySelector`. In the browser there may be not only HTML, but also XML and SVG documents. The `Element` class serves as a base for more specific classes: `SVGElement`, `XMLElement` and `HTMLElement`. -- [HTMLElement](https://html.spec.whatwg.org/multipage/dom.html#htmlelement) -- is finally the basic class for all HTML elements. It is inherited by various HTML elements: - - [HTMLInputElement](https://html.spec.whatwg.org/multipage/forms.html#htmlinputelement) -- the class for `<input>` elements, - - [HTMLBodyElement](https://html.spec.whatwg.org/multipage/semantics.html#htmlbodyelement) -- the class for `<body>` elements, - - [HTMLAnchorElement](https://html.spec.whatwg.org/multipage/semantics.html#htmlanchorelement) -- the class for `<a>` elements - - ...and so on, each tag has its own class that may provide specific properties and methods. +Существуют следующие классы: -So, the full set of properties and methods of a given node comes as the result of the inheritance. +- [EventTarget](https://dom.spec.whatwg.org/#eventtarget) -- это корневой "абстрактный" класс для всего. -For example, let's consider the DOM object for an `<input>` element. It belongs to [HTMLInputElement](https://html.spec.whatwg.org/multipage/forms.html#htmlinputelement) class. It gets properties and methods as a superposition of: + Объекты этого класса никогда не создаются. Он служит основой, благодаря которой все DOM-узлы поддерживают так называемые "события", о которых мы поговорим позже. -- `HTMLInputElement` -- this class provides input-specific properties, and inherits from... -- `HTMLElement` -- it provides common HTML element methods (and getters/setters) and inherits from... -- `Element` -- provides generic element methods and inherits from... -- `Node` -- provides common DOM node properties and inherits from... -- `EventTarget` -- gives the support for events (to be covered), -- ...and finally it inherits from `Object`, so "pure object" methods like `hasOwnProperty` are also available. +- [Node](https://dom.spec.whatwg.org/#interface-node) -- также является "абстрактным" классом, и служит основой для DOM-узлов. -To see the DOM node class name, we can recall that an object usually has the `constructor` property. It references to the class constructor, and `constructor.name` is its name: + Он обеспечивает базовую функциональность: `parentNode`, `nextSibling`, `childNodes` и т.д. (это геттеры). Объекты класса `Node` никогда не создаются. Но есть определённые классы узлов, которые наследуются от него (и следовательно наследуют функционал `Node`). + +- [Document](https://dom.spec.whatwg.org/#interface-document), по историческим причинам часто наследуется `HTMLDocument` (хотя последняя спецификация этого не навязывает) -- это документ в целом. + + Глобальный объект `document` принадлежит именно к этому классу. Он служит точкой входа в DOM. + +- [CharacterData](https://dom.spec.whatwg.org/#interface-characterdata) -- "абстрактный" класс. Вот, кем он наследуется: + - [Text](https://dom.spec.whatwg.org/#interface-text) -- класс, соответствующий тексту внутри элементов. Например, `Hello` в `<p>Hello</p>`. + - [Comment](https://dom.spec.whatwg.org/#interface-comment) -- класс для комментариев. Они не отображаются, но каждый комментарий становится членом DOM. + +- [Element](https://dom.spec.whatwg.org/#interface-element) -- это базовый класс для DOM-элементов. + + Он обеспечивает навигацию на уровне элементов: `nextElementSibling`, `children`. + А также и методы поиска элементов: `getElementsByTagName`, `querySelector`. + + Браузер поддерживает не только HTML, но также XML и SVG. Таким образом, класс `Element` служит основой для более специфичных классов: `SVGElement`, `XmlElement` (они нам здесь не нужны) и `HTMLElement`. + +- И наконец, [HTMLElement](https://html.spec.whatwg.org/multipage/dom.html#htmlelement) является базовым классом для всех остальных HTML-элементов. Мы будем работать с ним большую часть времени. + + От него наследуются конкретные элементы: + + - [HTMLInputElement](https://html.spec.whatwg.org/multipage/forms.html#htmlinputelement) -- класс для тега `<input>`, + - [HTMLBodyElement](https://html.spec.whatwg.org/multipage/semantics.html#htmlbodyelement) -- класс для тега `<body>`, + - [HTMLAnchorElement](https://html.spec.whatwg.org/multipage/semantics.html#htmlanchorelement) -- класс для тега `<a>`, + - ...и т.д. + +Также существует множество других тегов со своими собственными классами, которые могут иметь определенные свойства и методы, в то время как некоторые элементы, такие как `<span>`, `<section>` и `<article>`, не имеют каких-либо определенных свойств, поэтому они являются экземплярами класса `HTMLElement`. + +Таким образом, полный набор свойств и методов данного узла является результатом цепочки наследования. + +Рассмотрим DOM-объект для тега `<input>`. Он принадлежит классу [HTMLInputElement](https://html.spec.whatwg.org/multipage/forms.html#htmlinputelement). + +Он получает свойства и методы из (в порядке наследования): + +- `HTMLInputElement` -- этот класс предоставляет специфичные для элементов формы свойства, +- `HTMLElement` -- предоставляет общие для HTML-элементов методы (и геттеры/сеттеры), +- `Element` -- предоставляет типовые методы элемента, +- `Node` -- предоставляет общие свойства DOM-узлов, +- `EventTarget` -- обеспечивает поддержку событий (поговорим о них дальше), +- ...и, наконец, он наследует от `Object`, поэтому доступны также методы "обычного объекта", такие как `hasOwnProperty`. + +Для того, чтобы узнать имя класса DOM-узла, вспомним, что обычно у объекта есть свойство `constructor`. Оно ссылается на конструктор класса, и в свойстве `constructor.name` содержится его имя: ```js run alert( document.body.constructor.name ); // HTMLBodyElement ``` -...Or we can just `toString` it: +...Или мы можем просто привести его к `строке`: ```js run alert( document.body ); // [object HTMLBodyElement] ``` -We also can use `instanceof` to check the inheritance: +Проверить наследование можно также при помощи `instanceof`: ```js run alert( document.body instanceof HTMLBodyElement ); // true @@ -60,38 +88,38 @@ alert( document.body instanceof Node ); // true alert( document.body instanceof EventTarget ); // true ``` -As we can see, DOM nodes are regular JavaScript objects. They use prototype-based classes for inheritance. +Как видно, DOM-узлы -- это обычные JavaScript объекты. Для наследования они используют классы, основанные на прототипах. -That's also easy to see by outputting an element with `console.dir(elem)` in a browser. There in the console you can see `HTMLElement.prototype`, `Element.prototype` and so on. +В этом легко убедиться, если вывести в консоли браузера любой элемент через `console.dir(elem)`. Или даже напрямую обратиться к методам, которые хранятся в `HTMLElement.prototype`, `Element.prototype` и т.д. -```smart header="`console.dir(elem)` versus `console.log(elem)`" -Most browsers support two commands in their developer tools: `console.log` and `console.dir`. They output their arguments to the console. For JavaScript objects these commands usually do the same. +```smart header="`console.dir(elem)` и `console.log(elem)`" +Большинство браузеров поддерживают в инструментах разработчика две команды: `console.log` и `console.dir`. Они выводят свои аргументы в консоль. Для JavaScript-объектов эти команды обычно выводят одно и то же. -But for DOM elements they are different: +Но для DOM-элементов они работают по-разному: -- `console.log(elem)` shows the element DOM tree. -- `console.dir(elem)` shows the element as a DOM object, good to explore its properties. +- `console.log(elem)` выводит элемент в виде DOM-дерева. +- `console.dir(elem)` выводит элемент в виде DOM-объекта, что удобно для анализа его свойств. -Try it on `document.body`. +Попробуйте сами на `document.body`. Вы увидите разницу во всех современных браузерах (кроме Firefox, где `console.log(elem)` и `console.dir(elem)` выводят одно и то же -- элемент в виде DOM-объекта). ``` -````smart header="IDL in the spec" -In the specification, classes are described not using JavaScript, but a special [Interface description language](https://en.wikipedia.org/wiki/Interface_description_language) (IDL), that is usually easy to understand. +````smart header="Спецификация IDL" +В спецификации для описания классов DOM используется не JavaScript, а специальный язык [Interface description language](https://ru.wikipedia.org/wiki/%D0%AF%D0%B7%D1%8B%D0%BA_%D0%BE%D0%BF%D0%B8%D1%81%D0%B0%D0%BD%D0%B8%D1%8F_%D0%B8%D0%BD%D1%82%D0%B5%D1%80%D1%84%D0%B5%D0%B9%D1%81%D0%BE%D0%B2) (IDL), с которым достаточно легко разобраться. -In IDL all properties are prepended with their types. For instance, `DOMString`, `boolean` and so on. +В IDL все свойства представлены с указанием их типов. Например, `DOMString`, `boolean` и т.д. -Here's an excerpt from it, with comments: +Небольшой отрывок IDL с комментариями: ```js -// Define HTMLInputElement +// Объявление HTMLInputElement *!* -// The colon ":" means that HTMLInputElement inherits from HTMLElement +// Двоеточие ":" после HTMLInputElement означает, что он наследует от HTMLElement */!* interface HTMLInputElement: HTMLElement { - // here go properties and methods of <input> elements + // далее идут свойства и методы элемента <input> *!* - // "DOMString" means that the value of these properties are strings + // "DOMString" означает, что значение свойства - строка */!* attribute DOMString accept; attribute DOMString alt; @@ -99,397 +127,400 @@ interface HTMLInputElement: HTMLElement { attribute DOMString value; *!* - // boolean value property (true/false) + // boolean - значит, что autofocus хранит логический тип данных (true/false) attribute boolean autofocus; */!* ... *!* - // now the method: "void" means that the method returns no value + // "void" перед методом означает, что данный метод не возвращает значение */!* void select(); ... } ``` - -Other classes are somewhat similar. ```` -## The "nodeType" property +## Свойство "nodeType" -The `nodeType` property provides an old-fashioned way to get the "type" of a DOM node. +Свойство `nodeType` предоставляет ещё один, "старомодный" способ узнать "тип" DOM-узла. -It has a numeric value: -- `elem.nodeType == 1` for element nodes, -- `elem.nodeType == 3` for text nodes, -- `elem.nodeType == 9` for the document object, -- there are few other values in [the specification](https://dom.spec.whatwg.org/#node). +Его значением является цифра: +- `elem.nodeType == 1` для узлов-элементов, +- `elem.nodeType == 3` для текстовых узлов, +- `elem.nodeType == 9` для объектов документа, +- [В спецификации](https://dom.spec.whatwg.org/#node) можно посмотреть остальные значения. -For instance: +Например: ```html run <body> <script> let elem = document.body; - // let's examine what it is? - alert(elem.nodeType); // 1 => element + // давайте разберёмся: какой тип узла находится в elem? + alert(elem.nodeType); // 1 => элемент - // and the first child is... - alert(elem.firstChild.nodeType); // 3 => text + // и его первый потомок... + alert(elem.firstChild.nodeType); // 3 => текст - // for the document object, the type is 9 + // для объекта document значение типа -- 9 alert( document.nodeType ); // 9 </script> </body> ``` -In modern scripts, we can use `instanceof` and other class-based tests to see the node type, but sometimes `nodeType` may be simpler. We can only read `nodeType`, not change it. +В современных скриптах, чтобы узнать тип узла, мы можем использовать метод `instanceof` и другие способы проверить класс, но иногда `nodeType` проще использовать. Мы не можем изменить значение `nodeType`, только прочитать его. -## Tag: nodeName and tagName +## Тег: nodeName и tagName -Given a DOM node, we can read its tag name from `nodeName` or `tagName` properties: +Получив DOM-узел, мы можем узнать имя его тега из свойств `nodeName` и `tagName`: -For instance: +Например: ```js run alert( document.body.nodeName ); // BODY alert( document.body.tagName ); // BODY ``` -Is there any difference between tagName and nodeName? +Есть ли какая-то разница между `tagName` и `nodeName`? -Sure, the difference is reflected in their names, but is indeed a bit subtle. +Да, она отражена в названиях свойств, но не очевидна. -- The `tagName` property exists only for `Element` nodes. -- The `nodeName` is defined for any `Node`: - - for elements it means the same as `tagName`. - - for other node types (text, comment, etc.) it has a string with the node type. +- Свойство `tagName` есть только у элементов `Element`. +- Свойство `nodeName` определено для любых узлов `Node`: + - для элементов оно равно `tagName`. + - для остальных типов узлов (текст, комментарий и т.д.) оно содержит строку с типом узла. -In other words, `tagName` is only supported by element nodes (as it originates from `Element` class), while `nodeName` can say something about other node types. +Другими словами, свойство `tagName` есть только у узлов-элементов (поскольку они происходят от класса `Element`), а `nodeName` может что-то сказать о других типах узлов. -For instance, let's compare `tagName` and `nodeName` for the `document` and a comment node: +Например, сравним `tagName` и `nodeName` на примере объекта `document` и узла-комментария: ```html run -<body><!-- comment --> +<body><!-- комментарий --> <script> - // for comment - alert( document.body.firstChild.tagName ); // undefined (no element) + // для комментария + alert( document.body.firstChild.tagName ); // undefined (не элемент) alert( document.body.firstChild.nodeName ); // #comment // for document - alert( document.tagName ); // undefined (not element) + alert( document.tagName ); // undefined (не элемент) alert( document.nodeName ); // #document </script> </body> ``` -If we only deal with elements, then `tagName` is the only thing we should use. - +Если мы имеем дело только с элементами, то можно использовать `tagName` или `nodeName`, нет разницы. -```smart header="The tag name is always uppercase except XHTML" -The browser has two modes of processing documents: HTML and XML. Usually the HTML-mode is used for webpages. XML-mode is enabled when the browser receives an XML-document with the header: `Content-Type: application/xml+xhtml`. +```smart header="Имена тегов (кроме XHTML) всегда пишутся в верхнем регистре" +В браузере существуют два режима обработки документа: HTML и XML. HTML-режим обычно используется для веб-страниц. XML-режим включается, если браузер получает XML-документ с заголовком: `Content-Type: application/xml+xhtml`. -In HTML mode `tagName/nodeName` is always uppercased: it's `BODY` either for `<body>` or `<BoDy>`. +В HTML-режиме значения `tagName/nodeName` всегда записаны в верхнем регистре. Будет выведено `BODY` вне зависимости от того, как записан тег в HTML `<body>` или `<BoDy>`. -In XML mode the case is kept "as is". Nowadays XML mode is rarely used. +В XML-режиме регистр сохраняется "как есть". В настоящее время XML-режим применяется редко. ``` -## innerHTML: the contents +## innerHTML: содержимое элемента -The [innerHTML](https://w3c.github.io/DOM-Parsing/#widl-Element-innerHTML) property allows to get the HTML inside the element as a string. +Свойство [innerHTML](https://w3c.github.io/DOM-Parsing/#widl-Element-innerHTML) позволяет получить HTML-содержимое элемента в виде строки. -We can also modify it. So it's one of most powerful ways to change the page. +Мы также можем изменять его. Это один из самых мощных способов менять содержимое на странице. -The example shows the contents of `document.body` and then replaces it completely: +Пример ниже показывает содержимое `document.body`, а затем полностью заменяет его: ```html run <body> - <p>A paragraph</p> - <div>A div</div> + <p>Параграф</p> + <div>DIV</div> <script> - alert( document.body.innerHTML ); // read the current contents - document.body.innerHTML = 'The new BODY!'; // replace it + alert( document.body.innerHTML ); // читаем текущее содержимое + document.body.innerHTML = 'Новый BODY!'; // заменяем содержимое </script> </body> ``` -We can try to insert invalid HTML, the browser will fix our errors: +Мы можем попробовать вставить некорректный HTML, браузер исправит наши ошибки: ```html run <body> <script> - document.body.innerHTML = '<b>test'; // forgot to close the tag - alert( document.body.innerHTML ); // <b>test</b> (fixed) + document.body.innerHTML = '<b>тест'; // забыли закрыть тег + alert( document.body.innerHTML ); // <b>тест</b> (исправлено) </script> </body> ``` -```smart header="Scripts don't execute" -If `innerHTML` inserts a `<script>` tag into the document -- it doesn't execute. +```smart header="Скрипты не выполнятся" +Если `innerHTML` вставляет в документ тег `<script>` -- он становится частью HTML, но не запускается. -It becomes a part of HTML, just as a script that has already run. ``` -### Beware: "innerHTML+=" does a full overwrite +### Будьте внимательны: "innerHTML+=" осуществляет перезапись -We can append "more HTML" by using `elem.innerHTML+="something"`. +Мы можем добавить HTML к элементу, используя `elem.innerHTML+="ещё html"`. -Like this: +Вот так: ```js -chatDiv.innerHTML += "<div>Hello<img src='smile.gif'/> !</div>"; -chatDiv.innerHTML += "How goes?"; +chatDiv.innerHTML += "<div>Привет<img src='smile.gif'/> !</div>"; +chatDiv.innerHTML += "Как дела?"; ``` -But we should be very careful about doing it, because what's going on is *not* an addition, but a full overwrite. +На практике этим следует пользоваться с большой осторожностью, так как фактически происходит *не* добавление, а перезапись. -Technically, these two lines do the same: +Технически эти две строки делают одно и то же: ```js elem.innerHTML += "..."; -// is a shorter way to write: +// это более короткая запись для: *!* elem.innerHTML = elem.innerHTML + "..." */!* ``` -In other words, `innerHTML+=` does this: +Другими словами, `innerHTML+=` делает следующее: -1. The old contents is removed. -2. The new `innerHTML` is written instead (a concatenation of the old and the new one). +1. Старое содержимое удаляется. +2. На его место становится новое значение `innerHTML` (с добавленной строкой). -**As the content is "zeroed-out" and rewritten from the scratch, all images and other resources will be reloaded**. +**Так как содержимое "обнуляется" и переписывается заново, все изображения и другие ресурсы будут перезагружены**. -In the `chatDiv` example above the line `chatDiv.innerHTML+="How goes?"` re-creates the HTML content and reloads `smile.gif` (hope it's cached). If `chatDiv` has a lot of other text and images, then the reload becomes clearly visible. +В примере `chatDiv` выше строка `chatDiv.innerHTML+="Как дела?"` заново создаёт содержимое HTML и перезагружает `smile.gif` (надеемся, картинка закеширована). Если в `chatDiv` много текста и изображений, то эта перезагрузка будет очень заметна. -There are other side-effects as well. For instance, if the existing text was selected with the mouse, then most browsers will remove the selection upon rewriting `innerHTML`. And if there was an `<input>` with a text entered by the visitor, then the text will be removed. And so on. +Есть и другие побочные эффекты. Например, если существующий текст выделен мышкой, то при переписывании `innerHTML` большинство браузеров снимут выделение. А если это поле ввода `<input>` с текстом, введённым пользователем, то текст будет удалён. И т.д. -Luckily, there are other ways to add HTML besides `innerHTML`, and we'll study them soon. +К счастью, есть и другие способы добавить содержимое, не использующие `innerHTML`, которые мы изучим позже. -## outerHTML: full HTML of the element +## outerHTML: HTML элемента целиком -The `outerHTML` property contains the full HTML of the element. That's like `innerHTML` plus the element itself. +Свойство `outerHTML` содержит HTML элемента целиком. Это как `innerHTML` плюс сам элемент. -Here's an example: +Посмотрим на пример: ```html run -<div id="elem">Hello <b>World</b></div> +<div id="elem">Привет <b>Мир</b></div> <script> - alert(elem.outerHTML); // <div id="elem">Hello <b>World</b></div> + alert(elem.outerHTML); // <div id="elem">Привет <b>Мир</b></div> </script> ``` -**Beware: unlike `innerHTML`, writing to `outerHTML` does not change the element. Instead, it replaces it as a whole in the outer context.** +**Будьте осторожны: в отличие от `innerHTML`, запись в `outerHTML` не изменяет элемент. Вместо этого элемент заменяется целиком во внешнем контексте.** -Yeah, sounds strange, and strange it is, that's why we make a separate note about it here. Take a look. +Да, звучит странно, и это действительно необычно, поэтому здесь мы и отмечаем это особо. -Consider the example: +Рассмотрим пример: ```html run -<div>Hello, world!</div> +<div>Привет, мир!</div> <script> let div = document.querySelector('div'); *!* - // replace div.outerHTML with <p>...</p> + // заменяем div.outerHTML на <p>...</p> */!* - div.outerHTML = '<p>A new element!</p>'; // (*) + div.outerHTML = '<p>Новый элемент</p>'; // (*) *!* - // Wow! The div is still the same! + // Содержимое div осталось тем же! */!* - alert(div.outerHTML); // <div>Hello, world!</div> + alert(div.outerHTML); // <div>Привет, мир!</div> (**) </script> ``` -In the line `(*)` we take the full HTML of `<div>...</div>` and replace it by `<p>...</p>`. In the outer document we can see the new content instead of the `<div>`. But the old `div` variable is still the same. +Какая-то магия, да? -The `outerHTML` assignment does not modify the DOM element, but extracts it from the outer context and inserts a new piece of HTML instead of it. +В строке `(*)` мы заменили `div` на `<p>Новый элемент</p>`. Во внешнем документе мы видим новое содержимое вместо `<div>`. Но, как видно в строке (`**`), старая переменная `div` осталась прежней! -Novice developers sometimes make an error here: they modify `div.outerHTML` and then continue to work with `div` as if it had the new content in it. +Это потому, что использование `outerHTML` не изменяет DOM-элемент, а удаляет его из внешнего контекста и вставляет вместо него новый HTML-код. -That's possible with `innerHTML`, but not with `outerHTML`. +То есть, при `div.outerHTML=...` произошло следующее: +- `div` был удалён из документа. +- Вместо него был вставлен другой HTML `<p>Новый элемент</p>`. +- В `div` осталось старое значение. Новый HTML не сохранён ни в какой переменной. -We can write to `outerHTML`, but should keep in mind that it doesn't change the element we're writing to. It creates the new content on its place instead. We can get a reference to new elements by querying DOM. +Здесь легко сделать ошибку: заменить `div.outerHTML`, а потом продолжить работать с `div`, как будто там новое содержимое. Но это не так. Подобное верно для `innerHTML`, но не для `outerHTML`. -## nodeValue/data: text node content +Мы можем писать в `elem.outerHTML`, но надо иметь в виду, что это не меняет элемент, в который мы пишем. Вместо этого создаётся новый HTML на его месте. Мы можем получить ссылки на новые элементы, обратившись к DOM. -The `innerHTML` property is only valid for element nodes. +## nodeValue/data: содержимое текстового узла -Other node types have their counterpart: `nodeValue` and `data` properties. These two are almost the same for practical use, there are only minor specification differences. So we'll use `data`, because it's shorter. +Свойство `innerHTML` есть только у узлов-элементов. -We can read it, like this: +У других типов узлов, в частности, у текстовых, есть свои аналоги: свойства `nodeValue` и `data`. Эти свойства очень похожи при использовании, есть лишь небольшие различия в спецификации. Мы будем использовать `data`, потому что оно короче. + +Прочитаем содержимое текстового узла и комментария: ```html run height="50" <body> - Hello - <!-- Comment --> + Привет + <!-- Комментарий --> <script> let text = document.body.firstChild; *!* - alert(text.data); // Hello + alert(text.data); // Привет */!* let comment = text.nextSibling; *!* - alert(comment.data); // Comment + alert(comment.data); // Комментарий */!* </script> </body> ``` -For text nodes we can imagine a reason to read or modify them, but why comments? Usually, they are not interesting at all, but sometimes developers embed information into HTML in them, like this: +Мы можем представить, для чего нам может понадобиться читать или изменять текстовый узел, но комментарии? + +Иногда их используют для вставки информации и инструкций шаблонизатора в HTML, как в примере ниже: ```html <!-- if isAdmin --> - <div>Welcome, Admin!</div> + <div>Добро пожаловать, Admin!</div> <!-- /if --> ``` -...Then JavaScript can read it and process embedded instructions. +...Затем JavaScript может прочитать это из свойства `data` и обработать инструкции. -## textContent: pure text +## textContent: просто текст -The `textContent` provides access to the *text* inside the element: only text, minus all `<tags>`. +Свойство `textContent` предоставляет доступ к *тексту* внутри элемента за вычетом всех `<тегов>`. -For instance: +Например: ```html run <div id="news"> - <h1>Headline!</h1> - <p>Martians attack people!</p> + <h1>Срочно в номер!</h1> + <p>Марсиане атаковали человечество!</p> </div> <script> - // Headline! Martians attack people! + // Срочно в номер! Марсиане атаковали человечество! alert(news.textContent); </script> ``` -As we can see, only text is returned, as if all `<tags>` were cut out, but the text in them remained. +Как мы видим, возвращается только текст, как если бы все `<теги>` были вырезаны, но текст в них остался. -In practice, reading such text is rarely needed. +На практике редко появляется необходимость читать текст таким образом. -**Writing to `textContent` is much more useful, because it allows to write text the "safe way".** +**Намного полезнее возможность записывать текст в `textContent`, т.к. позволяет писать текст "безопасным способом".** -Let's say we have an arbitrary string, for instance entered by a user, and want to show it. +Представим, что у нас есть произвольная строка, введённая пользователем, и мы хотим показать её. -- With `innerHTML` we'll have it inserted "as HTML", with all HTML tags. -- With `textContent` we'll have it inserted "as text", all symbols are treated literally. +- С `innerHTML` вставка происходит "как HTML", со всеми HTML-тегами. +- С `textContent` вставка получается "как текст", все символы трактуются буквально. -Compare the two: +Сравним два тега div: ```html run <div id="elem1"></div> <div id="elem2"></div> <script> - let name = prompt("What's your name?", "<b>Winnie-the-pooh!</b>"); + let name = prompt("Введите ваше имя?", "<b>Винни-пух!</b>"); elem1.innerHTML = name; elem2.textContent = name; </script> ``` -1. The first `<div>` gets the name "as HTML": all tags become tags, so we see the bold name. -2. The second `<div>` gets the name "as text", so we literally see `<b>Winnie-the-pooh!</b>`. +1. В первый `<div>` имя приходит "как HTML": все теги стали именно тегами, поэтому мы видим имя, выделенное жирным шрифтом. +2. Во второй `<div>` имя приходит "как текст", поэтому мы видим `<b>Винни-пух!</b>`. -In most cases, we expect the text from a user, and want to treat it as text. We don't want unexpected HTML in our site. An assignment to `textContent` does exactly that. +В большинстве случаев мы рассчитываем получить от пользователя текст и хотим, чтобы он интерпретировался как текст. Мы не хотим, чтобы на сайте появлялся произвольный HTML-код. Присваивание через `textContent` -- один из способов от этого защититься. -## The "hidden" property +## Свойство "hidden" -The "hidden" attribute and the DOM property specifies whether the element is visible or not. +Атрибут и DOM-свойство "hidden" указывает на то, видим ли мы элемент или нет. -We can use it in HTML or assign using JavaScript, like this: +Мы можем использовать его в HTML или назначать при помощи JavaScript, как в примере ниже: ```html run height="80" -<div>Both divs below are hidden</div> +<div>Оба тега DIV внизу невидимы</div> -<div hidden>With the attribute "hidden"</div> +<div hidden>С атрибутом "hidden"</div> -<div id="elem">JavaScript assigned the property "hidden"</div> +<div id="elem">С назначенным JavaScript свойством "hidden"</div> <script> elem.hidden = true; </script> ``` -Technically, `hidden` works the same as `style="display:none"`. But it's shorter to write. +Технически, `hidden` работает так же, как `style="display:none"`. Но его применение проще. -Here's a blinking element: +Мигающий элемент: ```html run height=50 -<div id="elem">A blinking element</div> +<div id="elem">Мигающий элемент</div> <script> setInterval(() => elem.hidden = !elem.hidden, 1000); </script> ``` -## More properties +## Другие свойства -DOM elements also have additional properties, many of them provided by the class: +У DOM-элементов есть дополнительные свойства, в частности, зависящие от класса: -- `value` -- the value for `<input>`, `<select>` and `<textarea>` (`HTMLInputElement`, `HTMLSelectElement`...). -- `href` -- the "href" for `<a href="...">` (`HTMLAnchorElement`). -- `id` -- the value of "id" attribute, for all elements (`HTMLElement`). -- ...and much more... +- `value` -- значение для `<input>`, `<select>` и `<textarea>` (`HTMLInputElement`, `HTMLSelectElement`...). +- `href` -- адрес ссылки "href" для `<a href="...">` (`HTMLAnchorElement`). +- `id` -- значение атрибута "id" для всех элементов (`HTMLElement`). +- ...и многие другие... -For instance: +Например: ```html run height="80" -<input type="text" id="elem" value="value"> +<input type="text" id="elem" value="значение"> <script> alert(elem.type); // "text" alert(elem.id); // "elem" - alert(elem.value); // value + alert(elem.value); // значение </script> ``` -Most standard HTML attributes have the corresponding DOM property, and we can access it like that. +Большинство стандартных HTML-атрибутов имеют соответствующее DOM-свойство, и мы можем получить к нему доступ. -If we want to know the full list of supported properties for a given class, we can find them in the specification. For instance, HTMLInputElement is documented at <https://html.spec.whatwg.org/#htmlinputelement>. +Если мы хотим узнать полный список поддерживаемых свойств для данного класса, можно найти их в спецификации. Например, класс `HTMLInputElement` описывается здесь: <https://html.spec.whatwg.org/#htmlinputelement>. -Or if we'd like to get them fast or are interested in a concrete browser specification -- we can always output the element using `console.dir(elem)` and read the properties. Or explore "DOM properties" in the Elements tab of the browser developer tools. +Если же нам нужно быстро что-либо узнать или нас интересует специфика определённого браузера -- мы всегда можем вывести элемент в консоль, используя `console.dir(elem)`, и прочитать все свойства. Или исследовать "свойства DOM" во вкладке Elements браузерных инструментов разработчика. -## Summary +## Итого -Each DOM node belongs to a certain class. The classes form a hierarchy. The full set of properties and methods come as the result of inheritance. +Каждый DOM-узел принадлежит определённому классу. Классы формируют иерархию. Весь набор свойств и методов является результатом наследования. -Main DOM node properties are: +Главные свойства DOM-узла: `nodeType` -: We can get `nodeType` from the DOM object class, but often we need just to see if it is a text or element node. The `nodeType` property is good for that. It has numeric values, most important are: `1` -- for elements,`3` -- for text nodes. Read-only. +: Свойство `nodeType` позволяет узнать тип DOM-узла. Его значение - числовое: `1` для элементов,`3` для текстовых узлов, и т.д. Только для чтения. `nodeName/tagName` -: For elements, tag name (uppercased unless XML-mode). For non-element nodes `nodeName` describes what it is. Read-only. +: Для элементов это свойство возвращает название тега (записывается в верхнем регистре, за исключением XML-режима). Для узлов-неэлементов `nodeName` описывает, что это за узел. Только для чтения. `innerHTML` -: The HTML content of the element. Can be modified. +: Внутреннее HTML-содержимое узла-элемента. Можно изменять. `outerHTML` -: The full HTML of the element. A write operation into `elem.outerHTML` does not touch `elem` itself. Instead it gets replaced with the new HTML in the outer context. +: Полный HTML узла-элемента. Запись в `elem.outerHTML` не меняет `elem`. Вместо этого она заменяет его во внешнем контексте. `nodeValue/data` -: The content of a non-element node (text, comment). These two are almost the same, usually we use `data`. Can be modified. +: Содержимое узла-неэлемента (текст, комментарий). Эти свойства практически одинаковые, обычно мы используем `data`. Можно изменять. `textContent` -: The text inside the element, basically HTML minus all `<tags>`. Writing into it puts the text inside the element, with all special characters and tags treated exactly as text. Can safely insert user-generated text and protect from unwanted HTML insertions. +: Текст внутри элемента: HTML за вычетом всех `<тегов>`. Запись в него помещает текст в элемент, при этом все специальные символы и теги интерпретируются как текст. Можно использовать для защиты от вставки произвольного HTML кода. `hidden` -: When set to `true`, does the same as CSS `display:none`. +: Когда значение установлено в `true`, делает то же самое, что и CSS `display:none`. -DOM nodes also have other properties depending on their class. For instance, `<input>` elements (`HTMLInputElement`) support `value`, `type`, while `<a>` elements (`HTMLAnchorElement`) support `href` etc. Most standard HTML attributes have a corresponding DOM property. +В зависимости от своего класса DOM-узлы имеют и другие свойства. Например у элементов `<input>` (`HTMLInputElement`) есть свойства `value`, `type`, у элементов `<a>` (`HTMLAnchorElement`) есть `href` и т.д. Большинство стандартных HTML-атрибутов имеют соответствующие свойства DOM. -But HTML attributes and DOM properties are not always the same, as we'll see in the next chapter. +Впрочем, HTML-атрибуты и свойства DOM не всегда одинаковы, мы увидим это в следующей главе. diff --git a/2-ui/1-document/05-basic-dom-node-properties/dom-class-hierarchy.png b/2-ui/1-document/05-basic-dom-node-properties/dom-class-hierarchy.png deleted file mode 100644 index d82e903760..0000000000 Binary files a/2-ui/1-document/05-basic-dom-node-properties/dom-class-hierarchy.png and /dev/null differ diff --git a/2-ui/1-document/05-basic-dom-node-properties/dom-class-hierarchy@2x.png b/2-ui/1-document/05-basic-dom-node-properties/dom-class-hierarchy@2x.png deleted file mode 100644 index 8a5ce5c0b7..0000000000 Binary files a/2-ui/1-document/05-basic-dom-node-properties/dom-class-hierarchy@2x.png and /dev/null differ diff --git a/2-ui/1-document/06-dom-attributes-and-properties/1-get-user-attribute/solution.md b/2-ui/1-document/06-dom-attributes-and-properties/1-get-user-attribute/solution.md index 0507832f35..9c4af87d3e 100644 --- a/2-ui/1-document/06-dom-attributes-and-properties/1-get-user-attribute/solution.md +++ b/2-ui/1-document/06-dom-attributes-and-properties/1-get-user-attribute/solution.md @@ -7,12 +7,12 @@ <div data-widget-name="menu">Choose the genre</div> <script> - // getting it + // получаем элемент let elem = document.querySelector('[data-widget-name]'); - // reading the value + // читаем значение alert(elem.dataset.widgetName); - // or + // или так alert(elem.getAttribute('data-widget-name')); </script> </body> diff --git a/2-ui/1-document/06-dom-attributes-and-properties/1-get-user-attribute/task.md b/2-ui/1-document/06-dom-attributes-and-properties/1-get-user-attribute/task.md index 4cdf231b0a..a70fcd7357 100644 --- a/2-ui/1-document/06-dom-attributes-and-properties/1-get-user-attribute/task.md +++ b/2-ui/1-document/06-dom-attributes-and-properties/1-get-user-attribute/task.md @@ -2,9 +2,9 @@ importance: 5 --- -# Get the attribute +# Получите атрибут -Write the code to select the element with `data-widget-name` attribute from the document and to read its value. +Напишите код для выбора элемента с атрибутом `data-widget-name` из документа и прочитайте его значение. ```html run <!DOCTYPE html> diff --git a/2-ui/1-document/06-dom-attributes-and-properties/2-yellow-links/solution.md b/2-ui/1-document/06-dom-attributes-and-properties/2-yellow-links/solution.md index 726be4c8fa..884cb8f63e 100644 --- a/2-ui/1-document/06-dom-attributes-and-properties/2-yellow-links/solution.md +++ b/2-ui/1-document/06-dom-attributes-and-properties/2-yellow-links/solution.md @@ -1,9 +1,9 @@ -First, we need to find all external references. +Во-первых, мы должны найти все внешние ссылки. -There are two ways. +Это можно сделать двумя способами. -The first is to find all links using `document.querySelectorAll('a')` and then filter out what we need: +Первый -- это найти все ссылки, используя `document.querySelectorAll('a')`, а затем отфильтровать ненужное: ```js let links = document.querySelectorAll('a'); @@ -12,23 +12,23 @@ for (let link of links) { *!* let href = link.getAttribute('href'); */!* - if (!href) continue; // no attribute + if (!href) continue; // нет атрибута - if (!href.includes('://')) continue; // no protocol + if (!href.includes('://')) continue; // нет протокола - if (href.startsWith('http://internal.com')) continue; // internal + if (href.startsWith('http://internal.com')) continue; // внутренняя link.style.color = 'orange'; } ``` -Please note: we use `link.getAttribute('href')`. Not `link.href`, because we need the value from HTML. +Пожалуйста, обратите внимание: мы используем `link.getAttribute('href')`. Не `link.href`, потому что нам нужно значение из HTML. -...Another, simpler way would be to add the checks to CSS selector: +...Другой, более простой путь -- добавить проверку в CSS-селектор: ```js -// look for all links that have :// in href -// but href doesn't start with http://internal.com +// найти все ссылки, атрибут href у которых содержит :// +// и при этом href не начинается с http://internal.com let selector = 'a[href*="://"]:not([href^="http://internal.com"])'; let links = document.querySelectorAll(selector); diff --git a/2-ui/1-document/06-dom-attributes-and-properties/2-yellow-links/solution.view/index.html b/2-ui/1-document/06-dom-attributes-and-properties/2-yellow-links/solution.view/index.html index 4209a5f34d..f6f0dd0643 100644 --- a/2-ui/1-document/06-dom-attributes-and-properties/2-yellow-links/solution.view/index.html +++ b/2-ui/1-document/06-dom-attributes-and-properties/2-yellow-links/solution.view/index.html @@ -4,11 +4,11 @@ <a name="list">The list:</a> <ul> - <li><a href="http://google.com">http://google.com</a></li> + <li><a href="https://google.com">https://google.com</a></li> <li><a href="/tutorial">/tutorial.html</a></li> <li><a href="local/path">local/path</a></li> <li><a href="ftp://ftp.com/my.zip">ftp://ftp.com/my.zip</a></li> - <li><a href="http://nodejs.org">http://nodejs.org</a></li> + <li><a href="https://nodejs.org">https://nodejs.org</a></li> <li><a href="http://internal.com/test">http://internal.com/test</a></li> </ul> diff --git a/2-ui/1-document/06-dom-attributes-and-properties/2-yellow-links/source.view/index.html b/2-ui/1-document/06-dom-attributes-and-properties/2-yellow-links/source.view/index.html index e120483238..5bbdc31765 100644 --- a/2-ui/1-document/06-dom-attributes-and-properties/2-yellow-links/source.view/index.html +++ b/2-ui/1-document/06-dom-attributes-and-properties/2-yellow-links/source.view/index.html @@ -4,11 +4,11 @@ <a name="list">The list:</a> <ul> - <li><a href="http://google.com">http://google.com</a></li> + <li><a href="https://google.com">https://google.com</a></li> <li><a href="/tutorial">/tutorial.html</a></li> <li><a href="local/path">local/path</a></li> <li><a href="ftp://ftp.com/my.zip">ftp://ftp.com/my.zip</a></li> - <li><a href="http://nodejs.org">http://nodejs.org</a></li> + <li><a href="https://nodejs.org">https://nodejs.org</a></li> <li><a href="http://internal.com/test">http://internal.com/test</a></li> </ul> diff --git a/2-ui/1-document/06-dom-attributes-and-properties/2-yellow-links/task.md b/2-ui/1-document/06-dom-attributes-and-properties/2-yellow-links/task.md index b0a8ab7b17..b4d5f5875f 100644 --- a/2-ui/1-document/06-dom-attributes-and-properties/2-yellow-links/task.md +++ b/2-ui/1-document/06-dom-attributes-and-properties/2-yellow-links/task.md @@ -2,15 +2,15 @@ importance: 3 --- -# Make external links orange +# Сделайте внешние ссылки оранжевыми -Make all external links orange by altering their `style` property. +Сделайте все внешние ссылки оранжевыми, изменяя их свойство `style`. -A link is external if: -- Its `href` has `://` in it -- But doesn't start with `http://internal.com`. +Ссылка является внешней, если: +- Её `href` содержит `://` +- Но не начинается с `http://internal.com`. -Example: +Пример: ```html run <a name="list">the list</a> @@ -24,12 +24,12 @@ Example: </ul> <script> - // setting style for a single link + // добавление стиля для одной ссылки let link = document.querySelector('a'); link.style.color = 'orange'; </script> ``` -The result should be: +Результат должен быть таким: [iframe border=1 height=180 src="solution"] diff --git a/2-ui/1-document/06-dom-attributes-and-properties/article.md b/2-ui/1-document/06-dom-attributes-and-properties/article.md index c1bcf2ce13..98f32b23f3 100644 --- a/2-ui/1-document/06-dom-attributes-and-properties/article.md +++ b/2-ui/1-document/06-dom-attributes-and-properties/article.md @@ -1,18 +1,18 @@ -# Attributes and properties +# Атрибуты и свойства -When the browser loads the page, it "reads" (another word: "parses") the HTML and generates DOM objects from it. For element nodes, most standard HTML attributes automatically become properties of DOM objects. +Когда браузер загружает страницу, он "читает" (также говорят: "парсит") HTML и генерирует из него DOM-объекты. Для узлов-элементов большинство стандартных HTML-атрибутов автоматически становятся свойствами DOM-объектов. -For instance, if the tag is `<body id="page">`, then the DOM object has `body.id="page"`. +Например, для такого тега `<body id="page">` у DOM-объекта будет такое свойство `body.id="page"`. -But the attribute-property mapping is not one-to-one! In this chapter we'll pay attention to separate these two notions, to see how to work with them, when they are the same, and when they are different. +Но преобразование атрибута в свойство происходит не один-в-один! В этой главе мы уделим внимание различию этих двух понятий, чтобы посмотреть, как работать с ними, когда они одинаковые и когда разные. -## DOM properties +## DOM-свойства -We've already seen built-in DOM properties. There's a lot. But technically no one limits us, and if it's not enough -- we can add our own. +Ранее мы уже видели встроенные DOM-свойства. Их много. Но технически нас никто не ограничивает, и если этого мало -- мы можем добавить своё собственное свойство. -DOM nodes are regular JavaScript objects. We can alter them. +DOM-узлы -- это обычные объекты JavaScript. Мы можем их изменять. -For instance, let's create a new property in `document.body`: +Например, создадим новое свойство для `document.body`: ```js run document.body.myData = { @@ -23,17 +23,17 @@ document.body.myData = { alert(document.body.myData.title); // Imperator ``` -We can add a method as well: +Мы можем добавить и метод: ```js run document.body.sayTagName = function() { alert(this.tagName); }; -document.body.sayTagName(); // BODY (the value of "this" in the method is document.body) +document.body.sayTagName(); // BODY (значением "this" в этом методе будет document.body) ``` -We can also modify built-in prototypes like `Element.prototype` and add new methods to all elements: +Также можно изменять встроенные прототипы, такие как `Element.prototype` и добавлять новые методы ко всем элементам: ```js run Element.prototype.sayHi = function() { @@ -44,59 +44,59 @@ document.documentElement.sayHi(); // Hello, I'm HTML document.body.sayHi(); // Hello, I'm BODY ``` -So, DOM properties and methods behave just like those of regular JavaScript objects: +Итак, DOM-свойства и методы ведут себя так же, как и обычные объекты JavaScript: -- They can have any value. -- They are case-sensitive (write `elem.nodeType`, not `elem.NoDeTyPe`). +- Им можно присвоить любое значение. +- Они регистрозависимы (нужно писать `elem.nodeType`, не `elem.NoDeTyPe`). -## HTML attributes +## HTML-атрибуты -In HTML, tags may have attributes. When the browser parses the HTML to create DOM objects for tags, it recognizes *standard* attributes and creates DOM properties from them. +В HTML у тегов могут быть атрибуты. Когда браузер парсит HTML, чтобы создать DOM-объекты для тегов, он распознаёт *стандартные* атрибуты и создаёт DOM-свойства для них. -So when an element has `id` or another *standard* attribute, the corresponding property gets created. But that doesn't happen if the attribute is non-standard. +Таким образом, когда у элемента есть `id` или другой *стандартный* атрибут, создаётся соответствующее свойство. Но этого не происходит, если атрибут нестандартный. -For instance: +Например: ```html run <body id="test" something="non-standard"> <script> alert(document.body.id); // test *!* - // non-standard attribute does not yield a property + // нестандартный атрибут не преобразуется в свойство alert(document.body.something); // undefined */!* </script> </body> ``` -Please note that a standard attribute for one element can be unknown for another one. For instance, `"type"` is standard for `<input>` ([HTMLInputElement](https://html.spec.whatwg.org/#htmlinputelement)), but not for `<body>` ([HTMLBodyElement](https://html.spec.whatwg.org/#htmlbodyelement)). Standard attributes are described in the specification for the corresponding element class. +Пожалуйста, учтите, что стандартный атрибут для одного тега может быть нестандартным для другого. Например, атрибут `"type"` является стандартным для элемента `<input>` ([HTMLInputElement](https://html.spec.whatwg.org/#htmlinputelement)), но не является стандартным для `<body>` ([HTMLBodyElement](https://html.spec.whatwg.org/#htmlbodyelement)). Стандартные атрибуты описаны в спецификации для соответствующего класса элемента. -Here we can see it: +Мы можем увидеть это на примере ниже: ```html run <body id="body" type="..."> <input id="input" type="text"> <script> alert(input.type); // text *!* - alert(body.type); // undefined: DOM property not created, because it's non-standard + alert(body.type); // undefined: DOM-свойство не создалось, потому что оно нестандартное */!* </script> </body> ``` -So, if an attribute is non-standard, there won't be a DOM-property for it. Is there a way to access such attributes? +Таким образом, для нестандартных атрибутов не будет соответствующих DOM-свойств. Есть ли способ получить такие атрибуты? -Sure. All attributes are accessible by using the following methods: +Конечно. Все атрибуты доступны с помощью следующих методов: -- `elem.hasAttribute(name)` -- checks for existence. -- `elem.getAttribute(name)` -- gets the value. -- `elem.setAttribute(name, value)` -- sets the value. -- `elem.removeAttribute(name)` -- removes the attribute. +- `elem.hasAttribute(name)` -- проверяет наличие атрибута. +- `elem.getAttribute(name)` -- получает значение атрибута. +- `elem.setAttribute(name, value)` -- устанавливает значение атрибута. +- `elem.removeAttribute(name)` -- удаляет атрибут. -These methods operate exactly with what's written in HTML. +Эти методы работают именно с тем, что написано в HTML. -Also one can read all attributes using `elem.attributes`: a collection of objects that belong to a built-in [Attr](https://dom.spec.whatwg.org/#attr) class, with `name` and `value` properties. +Кроме этого, получить все атрибуты элемента можно с помощью свойства `elem.attributes`: коллекция объектов, которая принадлежит ко встроенному классу [Attr](https://dom.spec.whatwg.org/#attr) со свойствами `name` и `value`. -Here's a demo of reading a non-standard property: +Вот демонстрация чтения нестандартного свойства: ```html run <body something="non-standard"> @@ -108,43 +108,43 @@ Here's a demo of reading a non-standard property: </body> ``` -HTML attributes have the following features: +У HTML-атрибутов есть следующие особенности: -- Their name is case-insensitive (`id` is same as `ID`). -- Their values are always strings. +- Их имена регистронезависимы (`id` то же самое, что и `ID`). +- Их значения всегда являются строками. -Here's an extended demo of working with attributes: +Расширенная демонстрация работы с атрибутами: ```html run <body> <div id="elem" about="Elephant"></div> <script> - alert( elem.getAttribute('About') ); // (1) 'Elephant', reading + alert( elem.getAttribute('About') ); // (1) 'Elephant', чтение - elem.setAttribute('Test', 123); // (2), writing + elem.setAttribute('Test', 123); // (2), запись - alert( elem.outerHTML ); // (3), see it's there + alert( elem.outerHTML ); // (3), посмотрим, есть ли атрибут в HTML (да) - for (let attr of elem.attributes) { // (4) list all + for (let attr of elem.attributes) { // (4) весь список alert( `${attr.name} = ${attr.value}` ); } </script> </body> ``` -Please note: +Пожалуйста, обратите внимание: -1. `getAttribute('About')` -- the first letter is uppercase here, and in HTML it's all lowercase. But that doesn't matter: attribute names are case-insensitive. -2. We can assign anything to an attribute, but it becomes a string. So here we have `"123"` as the value. -3. All attributes including ones that we set are visible in `outerHTML`. -4. The `attributes` collection is iterable and has all the attributes of the element (standard and non-standard) as objects with `name` and `value` properties. +1. `getAttribute('About')` -- здесь первая буква заглавная, а в HTML -- строчная. Но это не важно: имена атрибутов регистронезависимы. +2. Мы можем присвоить что угодно атрибуту, но это станет строкой. Поэтому в этой строчке мы получаем значение `"123"`. +3. Все атрибуты, в том числе те, которые мы установили, видны в `outerHTML`. +4. Коллекция `attributes` является перебираемой. В ней есть все атрибуты элемента (стандартные и нестандартные) в виде объектов со свойствами `name` и `value`. -## Property-attribute synchronization +## Синхронизация между атрибутами и свойствами -When a standard attribute changes, the corresponding property is auto-updated, and (with some exceptions) vice versa. +Когда стандартный атрибут изменяется, соответствующее свойство автоматически обновляется. Это работает и в обратную сторону (за некоторыми исключениями). -In the example below `id` is modified as an attribute, and we can see the property changed too. And then the same backwards: +В примере ниже `id` модифицируется как атрибут, и можно увидеть, что свойство также изменено. То же самое работает и в обратную сторону: ```html run <input> @@ -152,17 +152,17 @@ In the example below `id` is modified as an attribute, and we can see the proper <script> let input = document.querySelector('input'); - // attribute => property + // атрибут => свойство input.setAttribute('id', 'id'); - alert(input.id); // id (updated) + alert(input.id); // id (обновлено) - // property => attribute + // свойство => атрибут input.id = 'newId'; - alert(input.getAttribute('id')); // newId (updated) + alert(input.getAttribute('id')); // newId (обновлено) </script> ``` -But there are exclusions, for instance `input.value` synchronizes only from attribute -> to property, but not back: +Но есть и исключения, например, `input.value` синхронизируется только в одну сторону -- атрибут -> значение, но не в обратную: ```html run <input> @@ -170,108 +170,107 @@ But there are exclusions, for instance `input.value` synchronizes only from attr <script> let input = document.querySelector('input'); - // attribute => property + // атрибут => значение input.setAttribute('value', 'text'); alert(input.value); // text *!* - // NOT property => attribute + // свойство => атрибут input.value = 'newValue'; - alert(input.getAttribute('value')); // text (not updated!) + alert(input.getAttribute('value')); // text (не обновилось!) */!* </script> ``` -In the example above: -- Changing the attribute `value` updates the property. -- But the property change does not affect the attribute. +В примере выше: +- Изменение атрибута `value` обновило свойство. +- Но изменение свойства не повлияло на атрибут. -That "feature" may actually come in handy, because the user may modify `value`, and then after it, if we want to recover the "original" value from HTML, it's in the attribute. +Иногда эта "особенность" может пригодиться, потому что действия пользователя могут приводить к изменениям `value`, и если после этого мы захотим восстановить "оригинальное" значение из HTML, оно будет в атрибуте. -## DOM properties are typed +## DOM-свойства типизированы -DOM properties are not always strings. For instance, the `input.checked` property (for checkboxes) is a boolean: +DOM-свойства не всегда являются строками. Например, свойство `input.checked` (для чекбоксов) имеет логический тип: ```html run <input id="input" type="checkbox" checked> checkbox <script> - alert(input.getAttribute('checked')); // the attribute value is: empty string - alert(input.checked); // the property value is: true + alert(input.getAttribute('checked')); // значение атрибута: пустая строка + alert(input.checked); // значение свойства: true </script> ``` -There are other examples. The `style` attribute is a string, but the `style` property is an object: +Есть и другие примеры. Атрибут `style` -- строка, но свойство `style` является объектом: ```html run <div id="div" style="color:red;font-size:120%">Hello</div> <script> - // string + // строка alert(div.getAttribute('style')); // color:red;font-size:120% - // object + // объект alert(div.style); // [object CSSStyleDeclaration] alert(div.style.color); // red </script> ``` -That's an important difference. But even if a DOM property type is a string, it may differ from the attribute! +Хотя большинство свойств, всё же, строки. -For instance, the `href` DOM property is always a *full* URL, even if the attribute contains a relative URL or just a `#hash`. +При этом некоторые из них, хоть и строки, могут отличаться от атрибутов. Например, DOM-свойство `href` всегда содержит *полный* URL, даже если атрибут содержит относительный URL или просто `#hash`. -Here's an example: +Ниже пример: ```html height=30 run <a id="a" href="#hello">link</a> <script> - // attribute + // атрибут alert(a.getAttribute('href')); // #hello - // property - alert(a.href ); // full URL in the form http://site.com/page#hello + // свойство + alert(a.href ); // полный URL в виде http://site.com/page#hello </script> ``` -If we need the value of `href` or any other attribute exactly as written in the HTML, we can use `getAttribute`. +Если же нужно значение `href` или любого другого атрибута в точности, как оно записано в HTML, можно воспользоваться `getAttribute`. +## Нестандартные атрибуты, dataset -## Non-standard attributes, dataset +При написании HTML мы используем много стандартных атрибутов. Но что насчёт нестандартных, пользовательских? Давайте посмотрим, полезны они или нет, и для чего они нужны. -When writing HTML, we use a lot of standard attributes. But what about non-standard, custom ones? First, let's see whether they are useful or not? What for? +Иногда нестандартные атрибуты используются для передачи пользовательских данных из HTML в JavaScript, или чтобы "помечать" HTML-элементы для JavaScript. -Sometimes non-standard attributes are used to pass custom data from HTML to JavaScript, or to "mark" HTML-elements for JavaScript. - -Like this: +Как тут: ```html run -<!-- mark the div to show "name" here --> +<!-- пометить div, чтобы показать здесь поле "name" --> <div *!*show-info="name"*/!*></div> -<!-- and age here --> +<!-- а здесь возраст "age" --> <div *!*show-info="age"*/!*></div> <script> - // the code finds an element with the mark and shows what's requested + // код находит элемент с пометкой и показывает запрошенную информацию let user = { name: "Pete", age: 25 }; for(let div of document.querySelectorAll('[show-info]')) { - // insert the corresponding info into the field + // вставить соответствующую информацию в поле let field = div.getAttribute('show-info'); - div.innerHTML = user[field]; // Pete, then age + div.innerHTML = user[field]; // сначала Pete в name, потом 25 в age } </script> ``` -Also they can be used to style an element. +Также они могут быть использованы, чтобы стилизовать элементы. -For instance, here for the order state the attribute `order-state` is used: +Например, здесь для состояния заказа используется атрибут `order-state`: ```html run <style> - /* styles rely on the custom attribute "order-state" */ + /* стили зависят от пользовательского атрибута "order-state" */ .order[order-state="new"] { color: green; } @@ -298,24 +297,24 @@ For instance, here for the order state the attribute `order-state` is used: </div> ``` -Why the attribute may be preferable to classes like `.order-state-new`, `.order-state-pending`, `order-state-canceled`? +Почему атрибут может быть предпочтительнее таких классов, как `.order-state-new`, `.order-state-pending`, `order-state-canceled`? -That's because an attribute is more convenient to manage. The state can be changed as easy as: +Это потому, что атрибутом удобнее управлять. Состояние может быть изменено достаточно просто: ```js -// a bit simpler than removing old/adding a new class +// немного проще, чем удаление старого/добавление нового класса div.setAttribute('order-state', 'canceled'); ``` -But there may be a possible problem with custom attributes. What if we use a non-standard attribute for our purposes and later the standard introduces it and makes it do something? The HTML language is alive, it grows, more attributes appear to suit the needs of developers. There may be unexpected effects in such case. +Но с пользовательскими атрибутами могут возникнуть проблемы. Что если мы используем нестандартный атрибут для наших целей, а позже он появится в стандарте и будет выполнять какую-то функцию? Язык HTML живой, он растёт, появляется больше атрибутов, чтобы удовлетворить потребности разработчиков. В этом случае могут возникнуть неожиданные эффекты. -To avoid conflicts, there exist [data-*](https://html.spec.whatwg.org/#embedding-custom-non-visible-data-with-the-data-*-attributes) attributes. +Чтобы избежать конфликтов, существуют атрибуты вида [data-*](https://html.spec.whatwg.org/#embedding-custom-non-visible-data-with-the-data-*-attributes). -**All attributes starting with "data-" are reserved for programmers' use. They are available in the `dataset` property.** +**Все атрибуты, начинающиеся с префикса "data-", зарезервированы для использования программистами. Они доступны в свойстве `dataset`.** -For instance, if an `elem` has an attribute named `"data-about"`, it's available as `elem.dataset.about`. +Например, если у `elem` есть атрибут `"data-about"`, то обратиться к нему можно как `elem.dataset.about`. -Like this: +Как тут: ```html run <body data-about="Elephants"> @@ -324,9 +323,9 @@ Like this: </script> ``` -Multiword attributes like `data-order-state` become camel-cased: `dataset.orderState`. +Атрибуты, состоящие из нескольких слов, к примеру `data-order-state`, становятся свойствами, записанными с помощью верблюжьей нотации: `dataset.orderState`. -Here's a rewritten "order state" example: +Вот переписанный пример "состояния заказа": ```html run <style> @@ -348,39 +347,39 @@ Here's a rewritten "order state" example: </div> <script> - // read + // чтение alert(order.dataset.orderState); // new - // modify + // изменение order.dataset.orderState = "pending"; // (*) </script> ``` -Using `data-*` attributes is a valid, safe way to pass custom data. +Использование `data-*` атрибутов -- валидный, безопасный способ передачи пользовательских данных. -Please note that we can not only read, but also modify data-attributes. Then CSS updates the view accordingly: in the example above the last line `(*)` changes the color to blue. +Пожалуйста, примите во внимание, что мы можем не только читать, но и изменять data-атрибуты. Тогда CSS обновит представление соответствующим образом: в примере выше последняя строка `(*)` меняет цвет на синий. -## Summary +## Итого -- Attributes -- is what's written in HTML. -- Properties -- is what's in DOM objects. +- Атрибуты -- это то, что написано в HTML. +- Свойства -- это то, что находится в DOM-объектах. -A small comparison: +Небольшое сравнение: -| | Properties | Attributes | +| | Свойства | Атрибуты | |------------|------------|------------| -|Type|Any value, standard properties have types described in the spec|A string| -|Name|Name is case-sensitive|Name is not case-sensitive| +|Тип|Любое значение, стандартные свойства имеют типы, описанные в спецификации|Строка| +|Имя|Имя регистрозависимо|Имя регистронезависимо| -Methods to work with attributes are: +Методы для работы с атрибутами: -- `elem.hasAttribute(name)` -- to check for existence. -- `elem.getAttribute(name)` -- to get the value. -- `elem.setAttribute(name, value)` -- to set the value. -- `elem.removeAttribute(name)` -- to remove the attribute. -- `elem.attributes` is a collection of all attributes. +- `elem.hasAttribute(name)` -- проверить на наличие. +- `elem.getAttribute(name)` -- получить значение. +- `elem.setAttribute(name, value)` -- установить значение. +- `elem.removeAttribute(name)` -- удалить атрибут. +- `elem.attributes` -- это коллекция всех атрибутов. -For most needs, DOM properties can serve us well. We should refer to attributes only when DOM properties do not suit us, when we need exactly attributes, for instance: +В большинстве ситуаций предпочтительнее использовать DOM-свойства. Нужно использовать атрибуты только тогда, когда DOM-свойства не подходят, когда нужны именно атрибуты, например: -- We need a non-standard attribute. But if it starts with `data-`, then we should use `dataset`. -- We want to read the value "as written" in HTML. The value of the DOM property may be different, for instance the `href` property is always a full URL, and we may want to get the "original" value. +- Нужен нестандартный атрибут. Но если он начинается с `data-`, тогда нужно использовать `dataset`. +- Мы хотим получить именно то значение, которое написано в HTML. Значение DOM-свойства может быть другим, например, свойство `href` -- всегда полный URL, а нам может понадобиться получить "оригинальное" значение. diff --git a/2-ui/1-document/07-modifying-document/1-createtextnode-vs-innerhtml/solution.md b/2-ui/1-document/07-modifying-document/1-createtextnode-vs-innerhtml/solution.md index 93ae862fd6..d865baa7f6 100644 --- a/2-ui/1-document/07-modifying-document/1-createtextnode-vs-innerhtml/solution.md +++ b/2-ui/1-document/07-modifying-document/1-createtextnode-vs-innerhtml/solution.md @@ -1,18 +1,18 @@ -Answer: **1 and 3**. +Ответ: **1 и 3**. -Both commands result in adding the `text` "as text" into the `elem`. +Результатом обеих команд будет добавление `text` "как текст" в `elem`. -Here's an example: +Пример: ```html run height=80 <div id="elem1"></div> <div id="elem2"></div> <div id="elem3"></div> <script> - let text = '<b>text</b>'; + let text = '<b>текст</b>'; elem1.append(document.createTextNode(text)); - elem2.textContent = text; - elem3.innerHTML = text; + elem2.innerHTML = text; + elem3.textContent = text; </script> ``` diff --git a/2-ui/1-document/07-modifying-document/1-createtextnode-vs-innerhtml/task.md b/2-ui/1-document/07-modifying-document/1-createtextnode-vs-innerhtml/task.md index e127bc0efa..5b79fd609b 100644 --- a/2-ui/1-document/07-modifying-document/1-createtextnode-vs-innerhtml/task.md +++ b/2-ui/1-document/07-modifying-document/1-createtextnode-vs-innerhtml/task.md @@ -4,9 +4,9 @@ importance: 5 # createTextNode vs innerHTML vs textContent -We have an empty DOM element `elem` and a string `text`. +У нас есть пустой DOM-элемент `elem` и строка `text`. -Which of these 3 commands do exactly the same? +Какие из этих 3-х команд работают одинаково? 1. `elem.append(document.createTextNode(text))` 2. `elem.innerHTML = text` diff --git a/2-ui/1-document/07-modifying-document/10-clock-setinterval/solution.md b/2-ui/1-document/07-modifying-document/10-clock-setinterval/solution.md index 15238fcf4f..953d1bf1a5 100644 --- a/2-ui/1-document/07-modifying-document/10-clock-setinterval/solution.md +++ b/2-ui/1-document/07-modifying-document/10-clock-setinterval/solution.md @@ -1,6 +1,6 @@ -First, let's make HTML/CSS. +Для начала придумаем подходящую HTML/CSS-структуру. -Each component of the time would look great in its own `<span>`: +Здесь каждый компонент времени удобно поместить в соответствующий `<span>`: ```html <div id="clock"> @@ -8,9 +8,9 @@ Each component of the time would look great in its own `<span>`: </div> ``` -Also we'll need CSS to color them. +Каждый `span` раскрашивается при помощи CSS. -The `update` function will refresh the clock, to be called by `setInterval` every second: +Функция `update` будет обновлять часы, `setInterval` вызывает её каждую секунду: ```js function update() { @@ -32,14 +32,14 @@ function update() { } ``` -In the line `(*)` we every time check the current date. The calls to `setInterval` are not reliable: they may happen with delays. +В строке `(*)` каждый раз мы получаем текущую дату. Вызовы `setInterval` не надёжны: они могут происходить с задержками. -The clock-managing functions: +Функция `clockStart` для запуска часов: ```js let timerId; -function clockStart() { // run the clock +function clockStart() { // запустить часы timerId = setInterval(update, 1000); update(); // (*) } @@ -50,4 +50,4 @@ function clockStop() { } ``` -Please note that the call to `update()` is not only scheduled in `clockStart()`, but immediately run in the line `(*)`. Otherwise the visitor would have to wait till the first execution of `setInterval`. And the clock would be empty till then. +Обратите внимание, что вызов `update()` не только запланирован, но и тут же производится в строке `(*)`. Иначе посетителю пришлось бы ждать до первого выполнения `setInterval`, то есть целую секунду. diff --git a/2-ui/1-document/07-modifying-document/10-clock-setinterval/solution.view/index.html b/2-ui/1-document/07-modifying-document/10-clock-setinterval/solution.view/index.html index 1bf642b102..8a1bd9cacf 100644 --- a/2-ui/1-document/07-modifying-document/10-clock-setinterval/solution.view/index.html +++ b/2-ui/1-document/07-modifying-document/10-clock-setinterval/solution.view/index.html @@ -1,64 +1,68 @@ -<!DOCTYPE HTML> -<html> -<head> - <style> - .hour { - color: red - } - - .min { - color: green - } - - .sec { - color: blue - } - </style> -</head> - -<body> - - <div id="clock"> - <span class="hour">hh</span>:<span class="min">mm</span>:<span class="sec">ss</span> - </div> - - <script> - let timerId; - - function update() { - let clock = document.getElementById('clock'); - let date = new Date(); - - let hours = date.getHours(); - if (hours < 10) hours = '0' + hours; - clock.children[0].innerHTML = hours; - - let minutes = date.getMinutes(); - if (minutes < 10) minutes = '0' + minutes; - clock.children[1].innerHTML = minutes; - - let seconds = date.getSeconds(); - if (seconds < 10) seconds = '0' + seconds; - clock.children[2].innerHTML = seconds; - } - - function clockStart() { - timerId = setInterval(update, 1000); - update(); // <-- start right now, don't wait 1 second till the first setInterval works - } - - function clockStop() { - clearInterval(timerId); - } - - clockStart(); - </script> - - <!-- click on this button calls clockStart() --> - <input type="button" onclick="clockStart()" value="Start"> - - <!-- click on this button calls clockStop() --> - <input type="button" onclick="clockStop()" value="Stop"> - -</body> -</html> +<!DOCTYPE HTML> +<html> +<head> + <style> + .hour { + color: red + } + + .min { + color: green + } + + .sec { + color: blue + } + </style> +</head> + +<body> + + <div id="clock"> + <span class="hour">hh</span>:<span class="min">mm</span>:<span class="sec">ss</span> + </div> + + <script> + let timerId; + + function update() { + let clock = document.getElementById('clock'); + let date = new Date(); + + let hours = date.getHours(); + if (hours < 10) hours = '0' + hours; + clock.children[0].innerHTML = hours; + + let minutes = date.getMinutes(); + if (minutes < 10) minutes = '0' + minutes; + clock.children[1].innerHTML = minutes; + + let seconds = date.getSeconds(); + if (seconds < 10) seconds = '0' + seconds; + clock.children[2].innerHTML = seconds; + } + + function clockStart() { + // устанавить новый интервал только в том случае, если часы остановлены + // в противном случае мы бы переписали ссылку timerID на интервал выполнения и никогда больше не смогли бы остановить часы + if (!timerId) { + timerId = setInterval(update, 1000); + } + update(); // <-- начать тут же, не ждать 1 секунду пока первый setInterval сработает + } + + function clockStop() { + clearInterval(timerId); + timerId = null; // <-- очистить timerID, чтобы указать, что часы были остановлены, чтобы можно было запустить их снова в clockStart() + } + + </script> + + <!-- клик по этой кнопке вызовет clockStart() --> + <input type="button" onclick="clockStart()" value="Start"> + + <!-- клик по этой кнопке вызовет clockStop() --> + <input type="button" onclick="clockStop()" value="Stop"> + +</body> +</html> diff --git a/2-ui/1-document/07-modifying-document/10-clock-setinterval/source.view/index.html b/2-ui/1-document/07-modifying-document/10-clock-setinterval/source.view/index.html index ecf5df99af..e17213b386 100644 --- a/2-ui/1-document/07-modifying-document/10-clock-setinterval/source.view/index.html +++ b/2-ui/1-document/07-modifying-document/10-clock-setinterval/source.view/index.html @@ -2,11 +2,11 @@ <html> <body> - <!-- click on this button calls clockStart() --> - <input type="button" onclick="clockStart()" value="Start"> + <!-- клик по этой кнопке вызовет clockStart() --> + <input type="button" onclick="clockStart()" value="Старт"> - <!-- click on this button calls clockStop() --> - <input type="button" onclick="clockStop()" value="Stop"> + <!-- клик по этой кнопке вызовет clockStop() --> + <input type="button" onclick="clockStop()" value="Стоп"> </body> </html> diff --git a/2-ui/1-document/07-modifying-document/10-clock-setinterval/task.md b/2-ui/1-document/07-modifying-document/10-clock-setinterval/task.md index a1b53e337d..56fde8e0d5 100644 --- a/2-ui/1-document/07-modifying-document/10-clock-setinterval/task.md +++ b/2-ui/1-document/07-modifying-document/10-clock-setinterval/task.md @@ -2,10 +2,10 @@ importance: 4 --- -# Colored clock with setInterval +# Цветные часы с использованием setInterval -Create a colored clock like here: +Создайте цветные часы как в примере ниже: [iframe src="solution" height=60] -Use HTML/CSS for the styling, JavaScript only updates time in elements. +Для стилизации используйте HTML/CSS, JavaScript должен только обновлять время в элементах. diff --git a/2-ui/1-document/07-modifying-document/11-append-to-list/solution.md b/2-ui/1-document/07-modifying-document/11-append-to-list/solution.md index 4e77fb5cbf..4143a69000 100644 --- a/2-ui/1-document/07-modifying-document/11-append-to-list/solution.md +++ b/2-ui/1-document/07-modifying-document/11-append-to-list/solution.md @@ -1,7 +1,7 @@ -When we need to insert a piece of HTML somewhere, `insertAdjacentHTML` is the best fit. +Когда нам необходимо вставить фрагмент HTML-кода, можно использовать `insertAdjacentHTML`, он лучше всего подходит для таких задач. -The solution: +Решение: ```js one.insertAdjacentHTML('afterend', '<li>2</li><li>3</li>'); diff --git a/2-ui/1-document/07-modifying-document/11-append-to-list/task.md b/2-ui/1-document/07-modifying-document/11-append-to-list/task.md index 543cd3e466..a65adad440 100644 --- a/2-ui/1-document/07-modifying-document/11-append-to-list/task.md +++ b/2-ui/1-document/07-modifying-document/11-append-to-list/task.md @@ -2,9 +2,9 @@ importance: 5 --- -# Insert the HTML in the list +# Вставьте HTML в список -Write the code to insert `<li>2</li><li>3</li>` between two `<li>` here: +Напишите код для вставки `<li>2</li><li>3</li>` между этими двумя `<li>`: ```html <ul id="ul"> diff --git a/2-ui/1-document/07-modifying-document/12-sort-table/solution.md b/2-ui/1-document/07-modifying-document/12-sort-table/solution.md index 25dd58b0cf..bfe51303d6 100644 --- a/2-ui/1-document/07-modifying-document/12-sort-table/solution.md +++ b/2-ui/1-document/07-modifying-document/12-sort-table/solution.md @@ -1,4 +1,4 @@ -The solution is short, yet may look a bit tricky, so here I provide it with extensive comments: +Решение короткое, но может показаться немного сложным, поэтому здесь я предоставлю подробные комментарии: ```js @@ -9,11 +9,11 @@ let sortedRows = Array.from(table.rows) table.tBodies[0].append(...sortedRows); ``` -1. Get all `<tr>`, like `table.querySelectorAll('tr')`, then make an array from them, cause we need array methods. -2. The first TR (`table.rows[0]`) is actually a table header, so we take the rest by `.slice(1)`. -3. Then sort them comparing by the content of the first `<td>` (the name field). -4. Now insert nodes in the right order by `.append(...sortedRows)`. +1. Получим все `<tr>`, как `table.querySelectorAll('tr')`, затем сделаем массив из них, потому что нам понадобятся методы массива. +2. Первый TR (`table.rows[0]`) -- это заголовок таблицы, поэтому мы берём `.slice(1)`. +3. Затем отсортируем их по содержимому в первом `<td>` (по имени). +4. Теперь вставим узлы в правильном порядке `.append(...sortedRows)`. - Tables always have an implicit <tbody> element, so we need to take it and insert into it: a simple `table.append(...)` would fail. + Таблицы всегда имеют неявный элемент `<tbody>`, поэтому нам нужно получить его и вставить в него: простой `table.append(...)` потерпит неудачу. - Please note: we don't have to remove them, just "re-insert", they leave the old place automatically. + Обратите внимание: нам не нужно их удалять, просто "вставляем их заново", они автоматически покинут старое место. diff --git a/2-ui/1-document/07-modifying-document/12-sort-table/solution.view/index.html b/2-ui/1-document/07-modifying-document/12-sort-table/solution.view/index.html index 81e985748f..b47cf85582 100644 --- a/2-ui/1-document/07-modifying-document/12-sort-table/solution.view/index.html +++ b/2-ui/1-document/07-modifying-document/12-sort-table/solution.view/index.html @@ -4,9 +4,9 @@ <table id="table"> <tr> - <th>Name</th> - <th>Surname</th> - <th>Age</th> + <th>Имя</th> + <th>Фамилия</th> + <th>Возраст</th> </tr> <tr> <td>John</td> diff --git a/2-ui/1-document/07-modifying-document/12-sort-table/source.view/index.html b/2-ui/1-document/07-modifying-document/12-sort-table/source.view/index.html index e41eb229fa..37f06d90a5 100644 --- a/2-ui/1-document/07-modifying-document/12-sort-table/source.view/index.html +++ b/2-ui/1-document/07-modifying-document/12-sort-table/source.view/index.html @@ -4,9 +4,9 @@ <table id="table"> <tr> - <th>Name</th> - <th>Surname</th> - <th>Age</th> + <th>Имя</th> + <th>Фамилия</th> + <th>Возраст</th> </tr> <tr> <td>John</td> @@ -26,7 +26,7 @@ </table> <script> - // ... your code ... + // ... ваш код ... </script> </body> diff --git a/2-ui/1-document/07-modifying-document/12-sort-table/task.md b/2-ui/1-document/07-modifying-document/12-sort-table/task.md index 41d6fca29c..68b7db4ebc 100644 --- a/2-ui/1-document/07-modifying-document/12-sort-table/task.md +++ b/2-ui/1-document/07-modifying-document/12-sort-table/task.md @@ -2,38 +2,34 @@ importance: 5 --- -# Sort the table +# Сортировка таблицы -There's a table: +Вот таблица: +```html run <table> -<tr> - <th>Name</th> - <th>Surname</th> - <th>Age</th> -</tr> -<tr> - <td>John</td> - <td>Smith</td> - <td>10</td> -</tr> -<tr> - <td>Pete</td> - <td>Brown</td> - <td>15</td> -</tr> -<tr> - <td>Ann</td> - <td>Lee</td> - <td>5</td> -</tr> -<tr> - <td>...</td> - <td>...</td> - <td>...</td> -</tr> +<thead> + <tr> + <th>Name</th><th>Surname</th><th>Age</th> + </tr> +</thead> +<tbody> + <tr> + <td>John</td><td>Smith</td><td>10</td> + </tr> + <tr> + <td>Pete</td><td>Brown</td><td>15</td> + </tr> + <tr> + <td>Ann</td><td>Lee</td><td>5</td> + </tr> + <tr> + <td>...</td><td>...</td><td>...</td> + </tr> +</tbody> </table> +``` -There may be more rows in it. +В ней может быть больше строк. -Write the code to sort it by the `"name"` column. +Напишите код для сортировки по столбцу `"name"`. diff --git a/2-ui/1-document/07-modifying-document/4-clear-elem/solution.md b/2-ui/1-document/07-modifying-document/4-clear-elem/solution.md index 62c3386d84..a6b8c622e7 100644 --- a/2-ui/1-document/07-modifying-document/4-clear-elem/solution.md +++ b/2-ui/1-document/07-modifying-document/4-clear-elem/solution.md @@ -1,5 +1,5 @@ -First, let's see how *not* to do it: +Сначала давайте посмотрим, как *не* надо это делать: ```js function clear(elem) { @@ -9,11 +9,11 @@ function clear(elem) { } ``` -That won't work, because the call to `remove()` shifts the collection `elem.childNodes`, so elements start from the index `0` every time. But `i` increases, and some elements will be skipped. +Это не будет работать, потому что вызов `remove()` сдвигает коллекцию `elem.childNodes`, поэтому элементы начинаются каждый раз с индекса `0`. А `i` увеличивается, и некоторые элементы будут пропущены. -The `for..of` loop also does the same. +Цикл `for..of` делает то же самое. -The right variant could be: +Правильным вариантом может быть: ```js function clear(elem) { @@ -23,7 +23,7 @@ function clear(elem) { } ``` -And also there's a simpler way to do the same: +А также есть более простой способ сделать то же самое: ```js function clear(elem) { diff --git a/2-ui/1-document/07-modifying-document/4-clear-elem/task.md b/2-ui/1-document/07-modifying-document/4-clear-elem/task.md index 938d534703..b373d2b1a1 100644 --- a/2-ui/1-document/07-modifying-document/4-clear-elem/task.md +++ b/2-ui/1-document/07-modifying-document/4-clear-elem/task.md @@ -2,19 +2,19 @@ importance: 5 --- -# Clear the element +# Очистите элемент -Create a function `clear(elem)` that removes everything from the element. +Создайте функцию `clear(elem)`, которая удаляет всё содержимое из `elem`. ```html run height=60 <ol id="elem"> - <li>Hello</li> - <li>World</li> + <li>Привет</li> + <li>Мир</li> </ol> <script> - function clear(elem) { /* your code */ } + function clear(elem) { /* ваш код */ } - clear(elem); // clears the list + clear(elem); // очищает список </script> ``` diff --git a/2-ui/1-document/07-modifying-document/5-why-aaa/solution.md b/2-ui/1-document/07-modifying-document/5-why-aaa/solution.md index 6b85168b99..18f3f3768b 100644 --- a/2-ui/1-document/07-modifying-document/5-why-aaa/solution.md +++ b/2-ui/1-document/07-modifying-document/5-why-aaa/solution.md @@ -1,9 +1,9 @@ -The HTML in the task is incorrect. That's the reason of the odd thing. +HTML в задаче некорректен. В этом всё дело. -The browser has to fix it automatically. But there may be no text inside the `<table>`: according to the spec only table-specific tags are allowed. So the browser adds `"aaa"` *before* the `<table>`. +Браузер исправил ошибку автоматически. Но внутри `<table>` не может быть текста: в соответствии со спецификацией допускаются только табличные теги. Поэтому браузер показывает `"aaa"` *до* `<table>`. -Now it's obvious that when we remove the table, it remains. +Теперь очевидно, что когда мы удаляем таблицу, текст остаётся. -The question can be easily answered by exploring the DOM using the browser tools. It shows `"aaa"` before the `<table>`. +На этот вопрос можно легко ответить, изучив DOM, используя инструменты браузера. Вы увидите `"aaa"` до элемента `<table>`. -The HTML standard specifies in detail how to process bad HTML, and such behavior of the browser is correct. +Вообще, в стандарте HTML описано, как браузеру обрабатывать некорректный HTML, так что такое действие браузера является правильным. diff --git a/2-ui/1-document/07-modifying-document/5-why-aaa/task.md b/2-ui/1-document/07-modifying-document/5-why-aaa/task.md index 03064ed2c3..494e09c802 100644 --- a/2-ui/1-document/07-modifying-document/5-why-aaa/task.md +++ b/2-ui/1-document/07-modifying-document/5-why-aaa/task.md @@ -2,22 +2,27 @@ importance: 1 --- -# Why does "aaa" remain? +# Почему остаётся "aaa"? + +В примере ниже вызов `table.remove()` удаляет таблицу из документа. + +Но если вы запустите его, вы увидите, что текст `"aaa"` все еще виден. + +Почему так происходит? -Run the example. Why does `table.remove()` not delete the text `"aaa"`? ```html height=100 run <table id="table"> aaa <tr> - <td>Test</td> + <td>Тест</td> </tr> </table> <script> - alert(table); // the table, as it should be + alert(table); // таблица, как и должно быть table.remove(); - // why there's still aaa in the document? + // почему в документе остался текст "ааа"? </script> ``` diff --git a/2-ui/1-document/07-modifying-document/6-create-list/solution.md b/2-ui/1-document/07-modifying-document/6-create-list/solution.md index 1669be18fa..840f7349e0 100644 --- a/2-ui/1-document/07-modifying-document/6-create-list/solution.md +++ b/2-ui/1-document/07-modifying-document/6-create-list/solution.md @@ -1 +1 @@ -Please note the usage of `textContent` to assign the `<li>` content. +Обратите внимание на использование `textContent` для добавления содержимого в `<li>`. diff --git a/2-ui/1-document/07-modifying-document/6-create-list/solution.view/index.html b/2-ui/1-document/07-modifying-document/6-create-list/solution.view/index.html index 071645e8d2..d4e1694ad1 100755 --- a/2-ui/1-document/07-modifying-document/6-create-list/solution.view/index.html +++ b/2-ui/1-document/07-modifying-document/6-create-list/solution.view/index.html @@ -1,14 +1,14 @@ <!DOCTYPE HTML> <html> <body> - <h1>Create a list</h1> + <h1>Создать список</h1> <script> let ul = document.createElement('ul'); document.body.append(ul); while (true) { - let data = prompt("Enter the text for the list item", ""); + let data = prompt("Введите текст для элемента списка", ""); if (!data) { break; diff --git a/2-ui/1-document/07-modifying-document/6-create-list/task.md b/2-ui/1-document/07-modifying-document/6-create-list/task.md index 43b0a34a78..4fee71695d 100644 --- a/2-ui/1-document/07-modifying-document/6-create-list/task.md +++ b/2-ui/1-document/07-modifying-document/6-create-list/task.md @@ -2,18 +2,18 @@ importance: 4 --- -# Create a list +# Создайте список -Write an interface to create a list from user input. +Напишите интерфейс для создания списка. -For every list item: +Для каждого пункта: -1. Ask a user about its content using `prompt`. -2. Create the `<li>` with it and add it to `<ul>`. -3. Continue until the user cancels the input (by pressing `key:Esc` or CANCEL in prompt). +1. Запрашивайте содержимое пункта у пользователя с помощью `prompt`. +2. Создавайте элемент `<li>` и добавляйте его к `<ul>`. +3. Продолжайте до тех пор, пока пользователь не отменит ввод (нажатием клавиши `key:Esc` или введя пустую строку). -All elements should be created dynamically. +Все элементы должны создаваться динамически. -If a user types HTML-tags, they should be treated like a text. +Если пользователь вводит HTML-теги, они должны обрабатываться как текст. [demo src="solution"] diff --git a/2-ui/1-document/07-modifying-document/7-create-object-tree/build-tree-dom.view/index.html b/2-ui/1-document/07-modifying-document/7-create-object-tree/build-tree-dom.view/index.html index aec462fe8a..f561cfaed1 100755 --- a/2-ui/1-document/07-modifying-document/7-create-object-tree/build-tree-dom.view/index.html +++ b/2-ui/1-document/07-modifying-document/7-create-object-tree/build-tree-dom.view/index.html @@ -6,19 +6,19 @@ <script> let data = { - "Fish": { - "trout": {}, - "salmon": {} + "Рыбы": { + "форель": {}, + "лосось": {} }, - "Tree": { - "Huge": { - "sequoia": {}, - "oak": {} + "Деревья": { + "Огромные": { + "секвойя": {}, + "дуб": {} }, - "Flowering": { - "redbud": {}, - "magnolia": {} + "Цветковые": { + "яблоня": {}, + "магнолия": {} } } }; @@ -28,8 +28,8 @@ } function createTreeDom(obj) { - // if there's no children, then the call returns undefined - // and the <ul> won't be created + // если нет дочерних элементов, то вызов возвращает undefined + // и элемент <ul> не будет создан if (!Object.keys(obj).length) return; let ul = document.createElement('ul'); diff --git a/2-ui/1-document/07-modifying-document/7-create-object-tree/innerhtml.view/index.html b/2-ui/1-document/07-modifying-document/7-create-object-tree/innerhtml.view/index.html index fd85e6c760..14f2ff27bf 100644 --- a/2-ui/1-document/07-modifying-document/7-create-object-tree/innerhtml.view/index.html +++ b/2-ui/1-document/07-modifying-document/7-create-object-tree/innerhtml.view/index.html @@ -6,19 +6,19 @@ <script> let data = { - "Fish": { - "trout": {}, - "salmon": {} + "Рыбы": { + "форель": {}, + "лосось": {} }, - "Tree": { - "Huge": { - "sequoia": {}, - "oak": {} + "Деревья": { + "Огромные": { + "секвойя": {}, + "дуб": {} }, - "Flowering": { - "redbud": {}, - "magnolia": {} + "Цветковые": { + "яблоня": {}, + "магнолия": {} } } }; @@ -27,7 +27,7 @@ container.innerHTML = createTreeText(obj); } - function createTreeText(obj) { // standalone recursive function + function createTreeText(obj) { // отдельная рекурсивная функция let li = ''; let ul; for (let key in obj) { diff --git a/2-ui/1-document/07-modifying-document/7-create-object-tree/solution.md b/2-ui/1-document/07-modifying-document/7-create-object-tree/solution.md index d29636ee2a..f6538e9a1f 100644 --- a/2-ui/1-document/07-modifying-document/7-create-object-tree/solution.md +++ b/2-ui/1-document/07-modifying-document/7-create-object-tree/solution.md @@ -1,4 +1,4 @@ -The easiest way to walk the object is to use recursion. +Самый лёгкий способ -- это использовать рекурсию. -1. [The solution with innerHTML](sandbox:innerhtml). -2. [The solution with DOM](sandbox:build-tree-dom). +1. [Решение с innerHTML](sandbox:innerhtml). +2. [Решение через DOM](sandbox:build-tree-dom). diff --git a/2-ui/1-document/07-modifying-document/7-create-object-tree/source.view/index.html b/2-ui/1-document/07-modifying-document/7-create-object-tree/source.view/index.html index d035aec682..cb44b0c5b2 100755 --- a/2-ui/1-document/07-modifying-document/7-create-object-tree/source.view/index.html +++ b/2-ui/1-document/07-modifying-document/7-create-object-tree/source.view/index.html @@ -9,27 +9,27 @@ <div id="tree"></div> - <!-- The result should be: + <!-- Результат должен быть: <div id="tree"> <ul> - <li>Fish + <li>Рыбы <ul> - <li>trout</li> - <li>salmon</li> + <li>форель</li> + <li>лосось</li> </ul> </li> - <li>Trees + <li>Деревья <ul> - <li>Huge + <li>Огромные <ul> - <li>sequoia</li> - <li>oak</li> + <li>секвойя</li> + <li>дуб</li> </ul> </li> - <li>Flowering + <li>Цветковые <ul> - <li>redbud</li> - <li>magnolia</li> + <li>яблоня</li> + <li>магнолия</li> </ul> </li> </ul> @@ -40,25 +40,25 @@ <script> let data = { - "Fish": { - "trout": {}, - "salmon": {} + "Рыбы": { + "форель": {}, + "лосось": {} }, - "Tree": { - "Huge": { - "sequoia": {}, - "oak": {} + "Деревья": { + "Огромные": { + "секвойя": {}, + "дуб": {} }, - "Flowering": { - "redbud": {}, - "magnolia": {} + "Цветковые": { + "яблоня": {}, + "магнолия": {} } } }; function createTree(container, data) { - /* your code */ + /* ваш код */ } createTree(document.getElementById('tree'), data); diff --git a/2-ui/1-document/07-modifying-document/7-create-object-tree/task.md b/2-ui/1-document/07-modifying-document/7-create-object-tree/task.md index 6b60c096f9..7dfd267b41 100644 --- a/2-ui/1-document/07-modifying-document/7-create-object-tree/task.md +++ b/2-ui/1-document/07-modifying-document/7-create-object-tree/task.md @@ -2,50 +2,50 @@ importance: 5 --- -# Create a tree from the object +# Создайте дерево из объекта -Write a function `createTree` that creates a nested `ul/li` list from the nested object. +Напишите функцию `createTree`, которая создаёт вложенный список `ul/li` из объекта. -For instance: +Например: ```js let data = { - "Fish": { - "trout": {}, - "salmon": {} + "Рыбы": { + "форель": {}, + "лосось": {} }, - "Tree": { - "Huge": { - "sequoia": {}, - "oak": {} + "Деревья": { + "Огромные": { + "секвойя": {}, + "дуб": {} }, - "Flowering": { - "redbud": {}, - "magnolia": {} + "Цветковые": { + "яблоня": {}, + "магнолия": {} } } }; ``` -The syntax: +Синтаксис: ```js let container = document.getElementById('container'); *!* -createTree(container, data); // creates the tree in the container +createTree(container, data); // создаёт дерево в контейнере */!* ``` -The result (tree) should look like this: +Результат (дерево): [iframe border=1 src="build-tree-dom"] -Choose one of two ways of solving this task: +Выберите один из двух способов решения этой задачи: -1. Create the HTML for the tree and then assign to `container.innerHTML`. -2. Create tree nodes and append with DOM methods. +1. Создать строку, а затем присвоить через `container.innerHTML`. +2. Создавать узлы через методы DOM. -Would be great if you could do both. +Если получится -- сделайте оба. -P.S. The tree should not have "extra" elements like empty `<ul></ul>` for the leaves. +P.S. Желательно, чтобы в дереве не было лишних элементов, в частности -– пустых `<ul></ul>` на нижнем уровне. diff --git a/2-ui/1-document/07-modifying-document/8-tree-count/solution.md b/2-ui/1-document/07-modifying-document/8-tree-count/solution.md index 43b9a362c8..865a1d95c4 100644 --- a/2-ui/1-document/07-modifying-document/8-tree-count/solution.md +++ b/2-ui/1-document/07-modifying-document/8-tree-count/solution.md @@ -1 +1 @@ -To append text to each `<li>` we can alter the text node `data`. +Чтобы добавить текст к каждому `<li>`, мы можем изменить текстовый узел `data`. diff --git a/2-ui/1-document/07-modifying-document/8-tree-count/solution.view/index.html b/2-ui/1-document/07-modifying-document/8-tree-count/solution.view/index.html index ec44bfda18..582808de74 100644 --- a/2-ui/1-document/07-modifying-document/8-tree-count/solution.view/index.html +++ b/2-ui/1-document/07-modifying-document/8-tree-count/solution.view/index.html @@ -3,36 +3,36 @@ <body> <ul> - <li>Animals + <li>Животные <ul> - <li>Mammals + <li>Млекопитающие <ul> - <li>Cows</li> - <li>Donkeys</li> - <li>Dogs</li> - <li>Tigers</li> + <li>Коровы</li> + <li>Ослы</li> + <li>Собаки</li> + <li>Тигры</li> </ul> </li> - <li>Other + <li>Другие <ul> - <li>Snakes</li> - <li>Birds</li> - <li>Lizards</li> + <li>Змеи</li> + <li>Птицы</li> + <li>Ящерицы</li> </ul> </li> </ul> </li> - <li>Fishes + <li>Рыбы <ul> - <li>Aquarium + <li>Аквариумные <ul> - <li>Guppy</li> - <li>Angelfish</li> + <li>Гуппи</li> + <li>Скалярии</li> </ul> </li> - <li>Sea + <li>Морские <ul> - <li>Sea trout</li> + <li>Морская форель</li> </ul> </li> </ul> @@ -43,11 +43,11 @@ let lis = document.getElementsByTagName('li'); for (let li of lis) { - // get the count of all <li> below this <li> + // получить количество всех <li> ниже этого <li> let descendantsCount = li.getElementsByTagName('li').length; if (!descendantsCount) continue; - // add directly to the text node (append to the text) + // добавить непосредственно к текстовому узлу (добавить к тексту) li.firstChild.data += ' [' + descendantsCount + ']'; } </script> diff --git a/2-ui/1-document/07-modifying-document/8-tree-count/source.view/index.html b/2-ui/1-document/07-modifying-document/8-tree-count/source.view/index.html index 542bd9376f..0cc3889de2 100644 --- a/2-ui/1-document/07-modifying-document/8-tree-count/source.view/index.html +++ b/2-ui/1-document/07-modifying-document/8-tree-count/source.view/index.html @@ -3,36 +3,36 @@ <body> <ul> - <li>Animals + <li>Животные <ul> - <li>Mammals + <li>Млекопитающие <ul> - <li>Cows</li> - <li>Donkeys</li> - <li>Dogs</li> - <li>Tigers</li> + <li>Коровы</li> + <li>Ослы</li> + <li>Собаки</li> + <li>Тигры</li> </ul> </li> - <li>Other + <li>Другие <ul> - <li>Snakes</li> - <li>Birds</li> - <li>Lizards</li> + <li>Змеи</li> + <li>Птицы</li> + <li>Ящерицы</li> </ul> </li> </ul> </li> - <li>Fishes + <li>Рыбы <ul> - <li>Aquarium + <li>Аквариумные <ul> - <li>Guppy</li> - <li>Angelfish</li> + <li>Гуппи</li> + <li>Скалярии</li> </ul> </li> - <li>Sea + <li>Морские <ul> - <li>Sea trout</li> + <li>Морская форель</li> </ul> </li> </ul> @@ -40,7 +40,7 @@ </ul> <script> - // ... your code ... + // ... ваш код ... </script> </body> diff --git a/2-ui/1-document/07-modifying-document/8-tree-count/task.md b/2-ui/1-document/07-modifying-document/8-tree-count/task.md index d6343bf3bd..c2e6571c4f 100644 --- a/2-ui/1-document/07-modifying-document/8-tree-count/task.md +++ b/2-ui/1-document/07-modifying-document/8-tree-count/task.md @@ -2,12 +2,12 @@ importance: 5 --- -# Show descendants in a tree +# Выведите список потомков в дереве -There's a tree organized as nested `ul/li`. +Есть дерево, организованное в виде вложенных списков `ul/li`. -Write the code that adds to each `<li>` the number of its descendants. Skip leaves (nodes without children). +Напишите код, который добавит каждому элементу списка `<li>` количество вложенных в него элементов. Узлы нижнего уровня, без детей – пропускайте. -The result: +Результат: [iframe border=1 src="solution"] diff --git a/2-ui/1-document/07-modifying-document/9-calendar-table/solution.md b/2-ui/1-document/07-modifying-document/9-calendar-table/solution.md index 67bb5e13d1..c0e15c7b2a 100644 --- a/2-ui/1-document/07-modifying-document/9-calendar-table/solution.md +++ b/2-ui/1-document/07-modifying-document/9-calendar-table/solution.md @@ -1,9 +1,9 @@ -We'll create the table as a string: `"<table>...</table>"`, and then assign it to `innerHTML`. +Для решения задачи сгенерируем таблицу в виде строки: `"<table>...</table>"`, а затем присвоим в `innerHTML`. -The algorithm: +Алгоритм: -1. Create the table header with `<th>` and weekday names. -1. Create the date object `d = new Date(year, month-1)`. That's the first day of `month` (taking into account that months in JavaScript start from `0`, not `1`). -2. First few cells till the first day of the month `d.getDay()` may be empty. Let's fill them in with `<td></td>`. -3. Increase the day in `d`: `d.setDate(d.getDate()+1)`. If `d.getMonth()` is not yet the next month, then add the new cell `<td>` to the calendar. If that's a Sunday, then add a newline <code>"</tr><tr>"</code>. -4. If the month has finished, but the table row is not yet full, add empty `<td>` into it, to make it square. +1. Создать заголовок таблицы с `<th>` и именами дней недели. +2. Создать объект даты `d = new Date(year, month-1)`. Это первый день месяца `month` (с учётом того, что месяцы в JS начинаются от 0, а не от 1). +3. Ячейки первого ряда пустые от начала и до дня недели `d.getDay()`, с которого начинается месяц. Заполним `<td></td>`. +4. Увеличить день в `d`: `d.setDate(d.getDate()+1)`. Если `d.getMonth()` ещё не в следующем месяце, то добавим новую ячейку `<td>` в календарь. Если это воскресенье, то добавим новую строку <code>"</tr><tr>"</code>. +5. Если месяц закончился, но строка таблицы ещё не заполнена, добавим в неё пустые `<td>`, чтобы сделать в календаре красивые пустые квадратики. diff --git a/2-ui/1-document/07-modifying-document/9-calendar-table/solution.view/index.html b/2-ui/1-document/07-modifying-document/9-calendar-table/solution.view/index.html index 7e211abc6c..d57c2a0f7e 100644 --- a/2-ui/1-document/07-modifying-document/9-calendar-table/solution.view/index.html +++ b/2-ui/1-document/07-modifying-document/9-calendar-table/solution.view/index.html @@ -29,30 +29,30 @@ <script> function createCalendar(elem, year, month) { - let mon = month - 1; // months in JS are 0..11, not 1..12 + let mon = month - 1; // месяцы в JS идут от 0 до 11, а не от 1 до 12 let d = new Date(year, mon); - let table = '<table><tr><th>MO</th><th>TU</th><th>WE</th><th>TH</th><th>FR</th><th>SA</th><th>SU</th></tr><tr>'; + let table = '<table><tr><th>пн</th><th>вт</th><th>ср</th><th>чт</th><th>пт</th><th>сб</th><th>вс</th></tr><tr>'; - // spaces for the first row - // from Monday till the first day of the month + // пробелы для первого ряда + // с понедельника до первого дня месяца // * * * 1 2 3 4 for (let i = 0; i < getDay(d); i++) { table += '<td></td>'; } - // <td> with actual dates + // <td> ячейки календаря с датами while (d.getMonth() == mon) { table += '<td>' + d.getDate() + '</td>'; - if (getDay(d) % 7 == 6) { // sunday, last day of week - newline + if (getDay(d) % 7 == 6) { // вс, последний день - перевод строки table += '</tr><tr>'; } d.setDate(d.getDate() + 1); } - // add spaces after last days of month for the last row + // добить таблицу пустыми ячейками, если нужно // 29 30 31 * * * * if (getDay(d) != 0) { for (let i = getDay(d); i < 7; i++) { @@ -60,15 +60,15 @@ } } - // close the table + // закрыть таблицу table += '</tr></table>'; elem.innerHTML = table; } - function getDay(date) { // get day number from 0 (monday) to 6 (sunday) + function getDay(date) { // получить номер дня недели, от 0 (пн) до 6 (вс) let day = date.getDay(); - if (day == 0) day = 7; // make Sunday (0) the last day + if (day == 0) day = 7; // сделать воскресенье (0) последним днем return day - 1; } diff --git a/2-ui/1-document/07-modifying-document/9-calendar-table/source.view/index.html b/2-ui/1-document/07-modifying-document/9-calendar-table/source.view/index.html index e1f4cd6bd4..8dcd5fce2a 100644 --- a/2-ui/1-document/07-modifying-document/9-calendar-table/source.view/index.html +++ b/2-ui/1-document/07-modifying-document/9-calendar-table/source.view/index.html @@ -28,7 +28,7 @@ <script> function createCalendar(elem, year, month) { - // ...your code that generates the calndar in elem... + // ...ваш код... } createCalendar(calendar, 2012, 9); diff --git a/2-ui/1-document/07-modifying-document/9-calendar-table/task.md b/2-ui/1-document/07-modifying-document/9-calendar-table/task.md index 37b1a60d23..fff1c3f6d5 100644 --- a/2-ui/1-document/07-modifying-document/9-calendar-table/task.md +++ b/2-ui/1-document/07-modifying-document/9-calendar-table/task.md @@ -2,16 +2,16 @@ importance: 4 --- -# Create a calendar +# Создайте календарь в виде таблицы -Write a function `createCalendar(elem, year, month)`. +Напишите функцию `createCalendar(elem, year, month)`. -The call should create a calendar for the given year/month and put it inside `elem`. +Вызов функции должен создать календарь для заданного месяца `month` в году `year` и вставить его в `elem`. -The calendar should be a table, where a week is `<tr>`, and a day is `<td>`. The table top should be `<th>` with weekday names: the first day should be Monday, and so on till Sunday. +Календарь должен быть таблицей, где неделя - это `<tr>`, а день – это `<td>`. У таблицы должен быть заголовок с названиями дней недели, каждый день – `<th>`, первым днём недели должен быть понедельник. -For instance, `createCalendar(cal, 2012, 9)` should generate in element `cal` the following calendar: +Например, `createCalendar(cal, 2012, 9)` сгенерирует в `cal` следующий календарь: [iframe height=210 src="solution"] -P.S. For this task it's enough to generate the calendar, should not yet be clickable. +P.S. В этой задаче достаточно сгенерировать календарь, кликабельным его делать не нужно. diff --git a/2-ui/1-document/07-modifying-document/article.md b/2-ui/1-document/07-modifying-document/article.md index 16aacb4568..b19c56a9f0 100644 --- a/2-ui/1-document/07-modifying-document/article.md +++ b/2-ui/1-document/07-modifying-document/article.md @@ -1,16 +1,14 @@ -# Modifying the document +# Изменение документа -DOM modifications is the key to create "live" pages. +Модификации DOM - это ключ к созданию "живых" страниц. -Here we'll see how to create new elements "on the fly" and modify the existing page content. +Здесь мы увидим, как создавать новые элементы "на лету" и изменять уже существующие. -First we'll see a simple example and then explain the methods. +## Пример: показать сообщение -## Example: show a message +Рассмотрим методы на примере - а именно, добавим на страницу сообщение, которое будет выглядеть получше, чем `alert`. -For a start, let's see how to add a message on the page that looks nicer than `alert`. - -Here's how it will look: +Вот такое: ```html autorun height="80" <style> @@ -25,51 +23,52 @@ Here's how it will look: *!* <div class="alert"> - <strong>Hi there!</strong> You've read an important message. + <strong>Всем привет!</strong> Вы прочитали важное сообщение. </div> */!* ``` -That was an HTML example. Now let's create the same `div` with JavaScript (assuming that the styles are still in the HTML or an external CSS file). - -## Creating an element +Это был пример HTML. Теперь давайте создадим такой же `div`, используя JavaScript (предполагаем, что стили в HTML или во внешнем CSS-файле). +## Создание элемента -To create DOM nodes, there are two methods: +DOM-узел можно создать двумя методами: `document.createElement(tag)` -: Creates a new element with the given tag: +: Создаёт новый *элемент* с заданным тегом: ```js let div = document.createElement('div'); ``` `document.createTextNode(text)` -: Creates a new *text node* with the given text: +: Создаёт новый *текстовый узел* с заданным текстом: ```js - let textNode = document.createTextNode('Here I am'); + let textNode = document.createTextNode('А вот и я'); ``` + +Большую часть времени нам нужно создавать узлы элементов, такие как `div` для сообщения. -### Creating the message +### Создание сообщения -In our case we want to make a `div` with given classes and the message in it: +В нашем случае сообщение - это `div` с классом `alert` и HTML в нём: ```js let div = document.createElement('div'); -div.className = "alert alert-success"; -div.innerHTML = "<strong>Hi there!</strong> You've read an important message."; +div.className = "alert"; +div.innerHTML = "<strong>Всем привет!</strong> Вы прочитали важное сообщение."; ``` -After that, we have our DOM element ready. Right now it is just in a variable and we cannot see it. That is because it's not yet inserted into the page. +Мы создали элемент, но пока он только в переменной. Мы не можем видеть его на странице, поскольку он не является частью документа. -## Insertion methods +## Методы вставки -To make the `div` show up, we need to insert it somewhere into `document`. For instance, in `document.body`. +Чтобы наш `div` появился, нам нужно вставить его где-нибудь в `document`. Например, в `document.body`. -There's a special method for that: `document.body.appendChild(div)`. +Для этого есть метод `append`, в нашем случае: `document.body.append(div)`. -Here's the full code: +Вот полный пример: ```html run height="80" <style> @@ -84,85 +83,24 @@ Here's the full code: <script> let div = document.createElement('div'); - div.className = "alert alert-success"; - div.innerHTML = "<strong>Hi there!</strong> You've read an important message."; + div.className = "alert"; + div.innerHTML = "<strong>Всем привет!</strong> Вы прочитали важное сообщение."; *!* - document.body.appendChild(div); + document.body.append(div); */!* </script> ``` -Here's a brief list of methods to insert a node into a parent element (`parentElem` for short): - -`parentElem.appendChild(node)` -: Appends `node` as the last child of `parentElem`. - - The following example adds a new `<li>` to the end of `<ol>`: - - ```html run height=100 - <ol id="list"> - <li>0</li> - <li>1</li> - <li>2</li> - </ol> - - <script> - let newLi = document.createElement('li'); - newLi.innerHTML = 'Hello, world!'; - - list.appendChild(newLi); - </script> - ``` - -`parentElem.insertBefore(node, nextSibling)` -: Inserts `node` before `nextSibling` into `parentElem`. - - The following code inserts a new list item before the second `<li>`: - - ```html run height=100 - <ol id="list"> - <li>0</li> - <li>1</li> - <li>2</li> - </ol> - <script> - let newLi = document.createElement('li'); - newLi.innerHTML = 'Hello, world!'; - - *!* - list.insertBefore(newLi, list.children[1]); - */!* - </script> - ``` - To insert `newLi` as the first element, we can do it like this: - - ```js - list.insertBefore(newLi, list.firstChild); - ``` - -`parentElem.replaceChild(node, oldChild)` -: Replaces `oldChild` with `node` among children of `parentElem`. - -All these methods return the inserted node. In other words, `parentElem.appendChild(node)` returns `node`. But usually the returned value is not used, we just run the method. - -These methods are "old school": they exist from the ancient times and we can meet them in many old scripts. Unfortunately, there are some tasks that are hard to solve with them. - -For instance, how to insert *html* if we have it as a string? Or, given a node, how to insert another node *before* it? Of course, all that is doable, but not in an elegant way. - -So there exist two other sets of insertion methods to handle all cases easily. - -### prepend/append/before/after +Вот методы для различных вариантов вставки: -This set of methods provides more flexible insertions: +- `node.append(...nodes or strings)` -- добавляет узлы или строки в конец `node`, +- `node.prepend(...nodes or strings)` -- вставляет узлы или строки в начало `node`, +- `node.before(...nodes or strings)` –- вставляет узлы или строки до `node`, +- `node.after(...nodes or strings)` –- вставляет узлы или строки после `node`, +- `node.replaceWith(...nodes or strings)` –- заменяет `node` заданными узлами или строками. -- `node.append(...nodes or strings)` -- append nodes or strings at the end of `node`, -- `node.prepend(...nodes or strings)` -- insert nodes or strings into the beginning of `node`, -- `node.before(...nodes or strings)` –- insert nodes or strings before the `node`, -- `node.after(...nodes or strings)` –- insert nodes or strings after the `node`, -- `node.replaceWith(...nodes or strings)` –- replaces `node` with the given nodes or strings. - -Here's an example of using these methods to add more items to a list and the text before/after it: +Вот пример использования этих методов, чтобы добавить новые элементы в список и текст до/после него: ```html autorun <ol id="ol"> @@ -172,24 +110,24 @@ Here's an example of using these methods to add more items to a list and the tex </ol> <script> - ol.before('before'); - ol.after('after'); + ol.before('before'); // вставить строку "before" перед <ol> + ol.after('after'); // вставить строку "after" после <ol> - let prepend = document.createElement('li'); - prepend.innerHTML = 'prepend'; - ol.prepend(prepend); + let liFirst = document.createElement('li'); + liFirst.innerHTML = 'prepend'; + ol.prepend(liFirst); // вставить liFirst в начало <ol> - let append = document.createElement('li'); - append.innerHTML = 'append'; - ol.append(append); + let liLast = document.createElement('li'); + liLast.innerHTML = 'append'; + ol.append(liLast); // вставить liLast в конец <ol> </script> ``` -Here's a small picture what methods do: +Наглядная иллюстрация того, куда эти методы вставляют: -![](before-prepend-append-after.png) +![](before-prepend-append-after.svg) -So the final list will be: +Итоговый список будет таким: ```html before @@ -203,82 +141,82 @@ before after ``` -These methods can insert multiple lists of nodes and text pieces in a single call. +Эти методы могут вставлять несколько узлов и текстовых фрагментов за один вызов. -For instance, here a string and an element are inserted: +Например, здесь вставляется строка и элемент: ```html run <div id="div"></div> <script> - div.before('<p>Hello</p>', document.createElement('hr')); + div.before('<p>Привет</p>', document.createElement('hr')); </script> ``` -All text is inserted *as text*. +Весь текст вставляется *как текст*. -So the final HTML is: +Поэтому финальный HTML будет: ```html run *!* -<p>Hello</p> +<p>Привет</p> */!* <hr> <div id="div"></div> ``` -In other words, strings are inserted in a safe way, like `elem.textContent` does it. +Другими словами, строки вставляются безопасным способом, как делает это `elem.textContent`. -So, these methods can only be used to insert DOM nodes or text pieces. +Поэтому эти методы могут использоваться только для вставки DOM-узлов или текстовых фрагментов. -But what if we want to insert HTML "as html", with all tags and stuff working, like `elem.innerHTML`? +А что, если мы хотим вставить HTML именно "как html", со всеми тегами и прочим, как делает это `elem.innerHTML`? -### insertAdjacentHTML/Text/Element +## insertAdjacentHTML/Text/Element -There's another, pretty versatile method: `elem.insertAdjacentHTML(where, html)`. +С этим может помочь другой, довольно универсальный метод: `elem.insertAdjacentHTML(where, html)`. -The first parameter is a string, specifying where to insert. Must be one of the following: +Первый параметр - это специальное слово, указывающее, куда по отношению к `elem` производить вставку. Значение должно быть одним из следующих: -- `"beforebegin"` -- insert `html` before `elem`, -- `"afterbegin"` -- insert `html` into `elem`, at the beginning, -- `"beforeend"` -- insert `html` into `elem`, at the end, -- `"afterend"` -- insert `html` after `elem`. +- `"beforebegin"` -- вставить `html` непосредственно перед `elem`, +- `"afterbegin"` -- вставить `html` в начало `elem`, +- `"beforeend"` -- вставить `html` в конец `elem`, +- `"afterend"` -- вставить `html` непосредственно после `elem`. -The second parameter is an HTML string, inserted "as is". +Второй параметр - это HTML-строка, которая будет вставлена именно "как HTML". -For instance: +Например: ```html run <div id="div"></div> <script> - div.insertAdjacentHTML('beforebegin', '<p>Hello</p>'); - div.insertAdjacentHTML('afterend', '<p>Bye</p>'); + div.insertAdjacentHTML('beforebegin', '<p>Привет</p>'); + div.insertAdjacentHTML('afterend', '<p>Пока</p>'); </script> ``` -...Would lead to: +...Приведёт к: ```html run -<p>Hello</p> +<p>Привет</p> <div id="div"></div> -<p>Bye</p> +<p>Пока</p> ``` -That's how we can append an arbitrary HTML to our page. +Так мы можем добавлять произвольный HTML на страницу. -Here's the picture of insertion variants: +Варианты вставки: -![](insert-adjacent.png) +![](insert-adjacent.svg) -We can easily notice similarities between this and the previous picture. The insertion points are actually the same, but this method inserts HTML. +Мы можем легко заметить сходство между этой и предыдущей картинкой. Точки вставки фактически одинаковые, но этот метод вставляет HTML. -The method has two brothers: +У метода есть два брата: -- `elem.insertAdjacentText(where, text)` -- the same syntax, but a string of `text` is inserted "as text" instead of HTML, -- `elem.insertAdjacentElement(where, elem)` -- the same syntax, but inserts an element. +- `elem.insertAdjacentText(where, text)` -- такой же синтаксис, но строка `text` вставляется "как текст", вместо HTML, +- `elem.insertAdjacentElement(where, elem)` -- такой же синтаксис, но вставляет элемент `elem`. -They exist mainly to make the syntax "uniform". In practice, only `insertAdjacentHTML` is used most of the time. Because for elements and text, we have methods `append/prepend/before/after` -- they are shorter to write and can insert nodes/text pieces. +Они существуют, в основном, чтобы унифицировать синтаксис. На практике часто используется только `insertAdjacentHTML`. Потому что для элементов и текста у нас есть методы `append/prepend/before/after` -- их быстрее написать, и они могут вставлять как узлы, так и текст. -So here's an alternative variant of showing a message: +Так что, вот альтернативный вариант показа сообщения: ```html run <style> @@ -292,23 +230,67 @@ So here's an alternative variant of showing a message: </style> <script> - document.body.insertAdjacentHTML("afterbegin", `<div class="alert alert-success"> - <strong>Hi there!</strong> You've read an important message. + document.body.insertAdjacentHTML("afterbegin", `<div class="alert"> + <strong>Всем привет!</strong> Вы прочитали важное сообщение. </div>`); </script> ``` -## Cloning nodes: cloneNode +## Удаление узлов -How to insert one more similar message? +Для удаления узла есть методы `node.remove()`. -We could make a function and put the code there. But the alternative way would be to *clone* the existing `div` and modify the text inside it (if needed). +Например, сделаем так, чтобы наше сообщение удалялось через секунду: -Sometimes when we have a big element, that may be faster and simpler. +```html run untrusted +<style> +.alert { + padding: 15px; + border: 1px solid #d6e9c6; + border-radius: 4px; + color: #3c763d; + background-color: #dff0d8; +} +</style> + +<script> + let div = document.createElement('div'); + div.className = "alert"; + div.innerHTML = "<strong>Всем привет!</strong> Вы прочитали важное сообщение."; + + document.body.append(div); +*!* + setTimeout(() => div.remove(), 1000); +*/!* +</script> +``` + +Если нам нужно *переместить* элемент в другое место -- нет необходимости удалять его со старого. + +**Все методы вставки автоматически удаляют узлы со старых мест.** + +Например, давайте поменяем местами элементы: + +```html run height=50 +<div id="first">Первый</div> +<div id="second">Второй</div> +<script> + // нет необходимости вызывать метод remove + second.after(first); // берёт #second и после него вставляет #first +</script> +``` -- The call `elem.cloneNode(true)` creates a "deep" clone of the element -- with all attributes and subelements. If we call `elem.cloneNode(false)`, then the clone is made without child elements. +## Клонирование узлов: cloneNode -An example of copying the message: +Как вставить ещё одно подобное сообщение? + +Мы могли бы создать функцию и поместить код туда. Альтернатива - *клонировать* существующий `div` и изменить текст внутри него (при необходимости). + +Иногда, когда у нас есть большой элемент, это может быть быстрее и проще. + +- Вызов `elem.cloneNode(true)` создаёт "глубокий" клон элемента -- со всеми атрибутами и дочерними элементами. Если мы вызовем `elem.cloneNode(false)`, тогда клон будет без дочерних элементов. + +Пример копирования сообщения: ```html run height="120" <style> @@ -322,27 +304,26 @@ An example of copying the message: </style> <div class="alert" id="div"> - <strong>Hi there!</strong> You've read an important message. + <strong>Всем привет!</strong> Вы прочитали важное сообщение. </div> <script> *!* - let div2 = div.cloneNode(true); // clone the message - div2.querySelector('strong').innerHTML = 'Bye there!'; // change the clone + let div2 = div.cloneNode(true); // клонировать сообщение + div2.querySelector('strong').innerHTML = 'Всем пока!'; // изменить клонированный элемент - div.after(div2); // show the clone after the existing div + div.after(div2); // показать клонированный элемент после существующего div */!* </script> ``` - ## DocumentFragment [#document-fragment] -`DocumentFragment` is a special DOM node that serves as a wrapper to pass around groups of nodes. +`DocumentFragment` является специальным DOM-узлом, который служит обёрткой для передачи списков узлов. -We can append other nodes to it, but when we insert it somewhere, then it "disappears", leaving its content inserted instead. +Мы можем добавить к нему другие узлы, но когда мы вставляем его куда-то, он "исчезает", вместо него вставляется его содержимое. -For example, `getListContent` below generates a fragment with `<li>` items, that are later inserted into `<ul>`: +Например, `getListContent` ниже генерирует фрагмент с элементами `<li>`, которые позже вставляются в `<ul>`: ```html run <ul id="ul"></ul> @@ -366,7 +347,7 @@ ul.append(getListContent()); // (*) </script> ``` -Please note, at the last line `(*)` we append `DocumentFragment`, but it "blends in", so the resulting structure will be: +Обратите внимание, что на последней строке с `(*)` мы добавляем `DocumentFragment`, но он "исчезает", поэтому структура будет: ```html <ul> @@ -376,7 +357,7 @@ Please note, at the last line `(*)` we append `DocumentFragment`, but it "blends </ul> ``` -`DocumentFragment` is rarely used explicitly. Why append to a special kind of node, if we can return an array of nodes instead? Rewritten example: +`DocumentFragment` редко используется. Зачем добавлять элементы в специальный вид узла, если вместо этого мы можем вернуть массив узлов? Переписанный пример: ```html run <ul id="ul"></ul> @@ -395,156 +376,173 @@ function getListContent() { } *!* -ul.append(...getListContent()); // append + "..." operator = friends! +ul.append(...getListContent()); // append + оператор "..." = друзья! */!* </script> ``` -We mention `DocumentFragment` mainly because there are some concepts on top of it, like [template](info:template-element) element, that we'll cover much later. +Мы упоминаем `DocumentFragment` в основном потому, что он используется в некоторых других областях, например, для элемента [template](info:template-element), который мы рассмотрим гораздо позже. +## Устаревшие методы вставки/удаления -## Removal methods +[old] -To remove nodes, there are the following methods: +Есть несколько других, более старых, методов вставки и удаления, которые существуют по историческим причинам. +Сейчас уже нет причин их использовать, так как современные методы `append`, `prepend`, `before`, `after`, `remove`, `replaceWith` более гибкие и удобные. -`parentElem.removeChild(node)` -: Removes `node` from `parentElem` (assuming it's a child). +Мы упоминаем о них только потому, что их можно найти во многих старых скриптах: -`node.remove()` -: Removes the `node` from its place. +`parentElem.appendChild(node)` +: Добавляет `node` в конец дочерних элементов `parentElem`. -We can easily see that the second method is much shorter. The first one exists for historical reasons. + Следующий пример добавляет новый `<li>` в конец `<ol>`: -````smart -If we want to *move* an element to another place -- there's no need to remove it from the old one. + ```html run height=100 + <ol id="list"> + <li>0</li> + <li>1</li> + <li>2</li> + </ol> -**All insertion methods automatically remove the node from the old place.** + <script> + let newLi = document.createElement('li'); + newLi.innerHTML = 'Привет, мир!'; -For instance, let's swap elements: + list.appendChild(newLi); + </script> + ``` -```html run height=50 -<div id="first">First</div> -<div id="second">Second</div> -<script> - // no need to call remove - second.after(first); // take #second and after it - insert #first -</script> -``` -```` +`parentElem.insertBefore(node, nextSibling)` +: Вставляет `node` перед `nextSibling` в `parentElem`. -Let's make our message disappear after a second: + Следующий пример вставляет новый элемент перед вторым `<li>`: -```html run untrusted -<style> -.alert { - padding: 15px; - border: 1px solid #d6e9c6; - border-radius: 4px; - color: #3c763d; - background-color: #dff0d8; -} -</style> + ```html run height=100 + <ol id="list"> + <li>0</li> + <li>1</li> + <li>2</li> + </ol> + <script> + let newLi = document.createElement('li'); + newLi.innerHTML = 'Привет, мир!'; -<script> - let div = document.createElement('div'); - div.className = "alert alert-success"; - div.innerHTML = "<strong>Hi there!</strong> You've read an important message."; + *!* + list.insertBefore(newLi, list.children[1]); + */!* + </script> + ``` + Чтобы вставить `newLi` в начало, мы можем сделать вот так: - document.body.append(div); -*!* - setTimeout(() => div.remove(), 1000); - // or setTimeout(() => document.body.removeChild(div), 1000); -*/!* -</script> -``` + ```js + list.insertBefore(newLi, list.firstChild); + ``` + +`parentElem.replaceChild(node, oldChild)` +: Заменяет `oldChild` на `node` среди дочерних элементов `parentElem`. -## A word about "document.write" +`parentElem.removeChild(node)` +: Удаляет `node` из `parentElem` (предполагается, что он родитель `node`). -There's one more, very ancient method of adding something to a web-page: `document.write`. + Этот пример удалит первый `<li>` из `<ol>`: -The syntax: + ```html run height=100 + <ol id="list"> + <li>0</li> + <li>1</li> + <li>2</li> + </ol> + + <script> + let li = list.firstElementChild; + list.removeChild(li); + </script> + ``` + +Все эти методы возвращают вставленный/удалённый узел. Другими словами, `parentElem.appendChild(node)` вернёт `node`. Но обычно возвращаемое значение не используют, просто вызывают метод. + +## Несколько слов о "document.write" + +Есть ещё один, очень древний метод добавления содержимого на веб-страницу: `document.write`. + +Синтаксис: ```html run -<p>Somewhere in the page...</p> +<p>Где-то на странице...</p> *!* <script> - document.write('<b>Hello from JS</b>'); + document.write('<b>Привет из JS</b>'); </script> */!* -<p>The end</p> +<p>Конец</p> ``` -The call to `document.write(html)` writes the `html` into page "right here and now". The `html` string can be dynamically generated, so it's kind of flexible. We can use JavaScript to create a full-fledged webpage and write it. +Вызов `document.write(html)` записывает `html` на страницу "прямо здесь и сейчас". Строка `html` может быть динамически сгенерирована, поэтому метод достаточно гибкий. Мы можем использовать JavaScript, чтобы создать полноценную веб-страницу и записать её в документ. -The method comes from times when there was no DOM, no standards... Really old times. It still lives, because there are scripts using it. +Этот метод пришёл к нам со времён, когда ещё не было ни DOM, ни стандартов... Действительно старые времена. Он всё ещё живёт, потому что есть скрипты, которые используют его. -In modern scripts we can rarely see it, because of the following important limitation: +В современных скриптах он редко встречается из-за следующего важного ограничения: -**The call to `document.write` only works while the page is loading.** +**Вызов `document.write` работает только во время загрузки страницы.** -If we call it afterwards, the existing document content is erased. +Если вызвать его позже, то существующее содержимое документа затрётся. -For instance: +Например: ```html run -<p>After one second the contents of this page will be replaced...</p> +<p>Через одну секунду содержимое этой страницы будет заменено...</p> *!* <script> - // document.write after 1 second - // that's after the page loaded, so it erases the existing content - setTimeout(() => document.write('<b>...By this.</b>'), 1000); + // document.write через 1 секунду + // вызов происходит после того, как страница загрузится, поэтому метод затирает содержимое + setTimeout(() => document.write('<b>...Этим.</b>'), 1000); </script> */!* ``` -So it's kind of unusable at "after loaded" stage, unlike other DOM methods we covered above. - -That was the downside. - -Technically, when `document.write` is called while the browser is still reading HTML, it appends something to it, and the browser consumes it just as it were initially there. - -That gives us the upside -- it works blazingly fast, because there's *no DOM modification*. It writes directly into the page text, while the DOM is not yet built, and the browser puts it into DOM at generation-time. +Так что после того, как страница загружена, он уже непригоден к использованию, в отличие от других методов DOM, которые мы рассмотрели выше. -So if we need to add a lot of text into HTML dynamically, and we're at page loading phase, and the speed matters, it may help. But in practice these requirements rarely come together. And usually we can see this method in scripts just because they are old. +Это его недостаток. -## Summary +Есть и преимущество. Технически, когда `document.write` запускается во время чтения HTML браузером, и что-то пишет в документ, то браузер воспринимает это так, как будто это изначально было частью загруженного HTML-документа. -Methods to create new nodes: +Поэтому он работает невероятно быстро, ведь при этом *нет модификации DOM*. Метод пишет прямо в текст страницы, пока DOM ещё в процессе создания. -- `document.createElement(tag)` -- creates an element with the given tag, -- `document.createTextNode(value)` -- creates a text node (rarely used), -- `elem.cloneNode(deep)` -- clones the element, if `deep==true` then with all descendants. +Так что, если нам нужно динамически добавить много текста в HTML, и мы находимся на стадии загрузки, и для нас очень важна скорость, это может помочь. Но на практике эти требования редко сочетаются. И обычно мы можем увидеть этот метод в скриптах просто потому, что они старые. -Insertion and removal of nodes: +## Итого -- From the parent: - - `parent.appendChild(node)` - - `parent.insertBefore(node, nextSibling)` - - `parent.removeChild(node)` - - `parent.replaceChild(newElem, node)` +- Методы для создания узлов: + - `document.createElement(tag)` -- создаёт элемент с заданным тегом, + - `document.createTextNode(value)` -- создаёт текстовый узел (редко используется), + - `elem.cloneNode(deep)` -- клонирует элемент, если `deep==true`, то со всеми дочерними элементами. - All these methods return `node`. +- Вставка и удаление: + - `node.append(...nodes or strings)` -- вставляет в `node` в конец, + - `node.prepend(...nodes or strings)` -- вставляет в `node` в начало, + - `node.before(...nodes or strings)` -- вставляет прямо перед `node`, + - `node.after(...nodes or strings)` -- вставляет сразу после `node`, + - `node.replaceWith(...nodes or strings)` -- заменяет `node`. + - `node.remove()` -- удаляет `node`. -- Given a list of nodes and strings: - - `node.append(...nodes or strings)` -- insert into `node`, at the end, - - `node.prepend(...nodes or strings)` -- insert into `node`, at the beginning, - - `node.before(...nodes or strings)` –- insert right before `node`, - - `node.after(...nodes or strings)` –- insert right after `node`, - - `node.replaceWith(...nodes or strings)` –- replace `node`. - - `node.remove()` –- remove the `node`. +- Устаревшие методы: + - `parent.appendChild(node)` + - `parent.insertBefore(node, nextSibling)` + - `parent.removeChild(node)` + - `parent.replaceChild(newElem, node)` - Text strings are inserted "as text". + Все эти методы возвращают `node`. -- Given a piece of HTML: `elem.insertAdjacentHTML(where, html)`, inserts depending on where: - - `"beforebegin"` -- insert `html` right before `elem`, - - `"afterbegin"` -- insert `html` into `elem`, at the beginning, - - `"beforeend"` -- insert `html` into `elem`, at the end, - - `"afterend"` -- insert `html` right after `elem`. +- Если нужно вставить фрагмент HTML, то `elem.insertAdjacentHTML(where, html)` вставляет в зависимости от `where`: + - `"beforebegin"` -- вставляет `html` прямо перед `elem`, + - `"afterbegin"` -- вставляет `html` в `elem` в начало, + - `"beforeend"` -- вставляет `html` в `elem` в конец, + - `"afterend"` -- вставляет `html` сразу после `elem`. - Also there are similar methods `elem.insertAdjacentText` and `elem.insertAdjacentElement`, they insert text strings and elements, but they are rarely used. + Также существуют похожие методы `elem.insertAdjacentText` и `elem.insertAdjacentElement`, они вставляют текстовые строки и элементы, но они редко используются. -- To append HTML to the page before it has finished loading: - - `document.write(html)` +- Чтобы добавить HTML на страницу до завершения её загрузки: + - `document.write(html)` - After the page is loaded such a call erases the document. Mostly seen in old scripts. + После загрузки страницы такой вызов затирает документ. В основном встречается в старых скриптах. diff --git a/2-ui/1-document/07-modifying-document/before-prepend-append-after.png b/2-ui/1-document/07-modifying-document/before-prepend-append-after.png deleted file mode 100644 index 858056f72e..0000000000 Binary files a/2-ui/1-document/07-modifying-document/before-prepend-append-after.png and /dev/null differ diff --git a/2-ui/1-document/07-modifying-document/before-prepend-append-after.svg b/2-ui/1-document/07-modifying-document/before-prepend-append-after.svg new file mode 100644 index 0000000000..0843713cec --- /dev/null +++ b/2-ui/1-document/07-modifying-document/before-prepend-append-after.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="409" height="203" viewBox="0 0 409 203"><defs><style>@import url(https://fonts.googleapis.com/css?family=Open+Sans:bold,italic,bolditalic%7CPT+Mono);@font-face{font-family:'PT Mono';font-weight:700;font-style:normal;src:local('PT MonoBold'),url(/font/PTMonoBold.woff2) format('woff2'),url(/font/PTMonoBold.woff) format('woff'),url(/font/PTMonoBold.ttf) format('truetype')}</style></defs><g id="dom" fill="none" fill-rule="evenodd" stroke="none" stroke-width="1"><g id="before-prepend-append-after.svg"><g id="<ol>-<li>0</li>-<li>" fill-rule="nonzero" transform="translate(18.63 60.2)"><path id="<" fill="#7E7C7B" d="M0 5.53v-.406l6.594-4.018.532.868-5.558 3.29L7.14 8.498l-.532.84z"/><path id="ol" fill="#A7333A" d="M8.568 6.3c0-1.13.294-2.023.882-2.681.588-.658 1.428-.987 2.52-.987.588 0 1.094.096 1.519.287.425.191.777.45 1.057.777.28.327.488.714.623 1.162.135.448.203.929.203 1.442 0 .56-.075 1.066-.224 1.519-.15.453-.369.838-.658 1.155-.29.317-.646.562-1.071.735a3.82 3.82 0 01-1.449.259c-.579 0-1.083-.096-1.512-.287a2.967 2.967 0 01-1.064-.777 3.168 3.168 0 01-.623-1.162A4.963 4.963 0 018.568 6.3zm1.162 0c0 .327.04.653.119.98.08.327.208.62.385.882.177.261.408.471.693.63.285.159.632.238 1.043.238.747 0 1.309-.231 1.687-.693.378-.462.567-1.141.567-2.037 0-.336-.04-.665-.119-.987a2.608 2.608 0 00-.392-.875 2.066 2.066 0 00-.7-.63c-.285-.159-.632-.238-1.043-.238-.747 0-1.307.229-1.68.686-.373.457-.56 1.139-.56 2.044zM17.836 0h2.324v7.448c0 .57.096.97.287 1.204.191.233.474.35.847.35.261 0 .511-.047.749-.14.238-.093.502-.252.791-.476l.504.77c-.15.13-.313.247-.49.35a3.638 3.638 0 01-1.106.42 2.963 2.963 0 01-1.442-.077 1.579 1.579 0 01-.679-.427 1.897 1.897 0 01-.413-.777c-.093-.322-.14-.721-.14-1.197V.938h-1.232V0z"/><path id="><" fill="#7E7C7B" d="M25.732 9.338l-.532-.84 5.572-3.234-5.558-3.29.532-.868 6.594 4.018v.406l-6.608 3.808zM16.8 25.53v-.406l6.594-4.018.532.868-5.558 3.29 5.572 3.234-.532.84L16.8 25.53z"/><path id="li" fill="#A7333A" d="M26.236 20h2.324v7.448c0 .57.096.97.287 1.204.191.233.474.35.847.35.261 0 .511-.047.749-.14.238-.093.502-.252.791-.476l.504.77c-.15.13-.313.247-.49.35a3.638 3.638 0 01-1.106.42 2.963 2.963 0 01-1.442-.077 1.579 1.579 0 01-.679-.427 1.897 1.897 0 01-.413-.777c-.093-.322-.14-.721-.14-1.197v-6.51h-1.232V20zm8.204 9.8v-.938h2.436v-5.124H34.44V22.8h3.556v6.062h2.38v.938H34.44zm2.058-8.988c0-.252.084-.469.252-.651a.838.838 0 01.644-.273c.27 0 .497.091.679.273a.889.889 0 01.273.651.806.806 0 01-.273.616.963.963 0 01-.679.252.872.872 0 01-.644-.252.838.838 0 01-.252-.616z"/><path id=">" fill="#7E7C7B" d="M42.532 29.338l-.532-.84 5.572-3.234-5.558-3.29.532-.868 6.594 4.018v.406z"/><path id="0" fill="#181717" d="M50.54 24.9c0-.793.07-1.505.21-2.135.14-.63.35-1.162.63-1.596.28-.434.635-.765 1.064-.994.43-.229.938-.343 1.526-.343.625 0 1.155.112 1.589.336.434.224.786.55 1.057.98.27.43.467.959.588 1.589.121.63.182 1.351.182 2.163a9.88 9.88 0 01-.21 2.135c-.14.63-.35 1.162-.63 1.596-.28.434-.635.765-1.064.994-.43.229-.938.343-1.526.343-.616 0-1.141-.124-1.575-.371a2.956 2.956 0 01-1.064-1.043c-.275-.448-.474-.982-.595-1.603a10.713 10.713 0 01-.182-2.051zm5.698 0c0-.495-.028-.966-.084-1.414l-4.158 3.794c.159.532.397.957.714 1.274.317.317.733.476 1.246.476.821 0 1.407-.34 1.757-1.022.35-.681.525-1.717.525-3.108zm-4.536 0c0 .233.007.457.021.672.014.215.03.425.049.63l4.172-3.78c-.159-.504-.394-.905-.707-1.204-.313-.299-.735-.448-1.267-.448-.83 0-1.416.343-1.757 1.029-.34.686-.511 1.72-.511 3.101z"/><path id="</" fill="#7E7C7B" d="M58.8 25.53v-.406l6.594-4.018.532.868-5.558 3.29 5.572 3.234-.532.84L58.8 25.53zm14.084-5.698l.882.392-5.11 11.536-.882-.392 5.11-11.536z"/><path id="li" fill="#A7333A" d="M76.636 20h2.324v7.448c0 .57.096.97.287 1.204.191.233.474.35.847.35.261 0 .511-.047.749-.14.238-.093.502-.252.791-.476l.504.77c-.15.13-.313.247-.49.35a3.638 3.638 0 01-1.106.42 2.963 2.963 0 01-1.442-.077 1.579 1.579 0 01-.679-.427 1.897 1.897 0 01-.413-.777c-.093-.322-.14-.721-.14-1.197v-6.51h-1.232V20zm8.204 9.8v-.938h2.436v-5.124H84.84V22.8h3.556v6.062h2.38v.938H84.84zm2.058-8.988c0-.252.084-.469.252-.651a.838.838 0 01.644-.273c.27 0 .497.091.679.273a.889.889 0 01.273.651.806.806 0 01-.273.616.963.963 0 01-.679.252.872.872 0 01-.644-.252.838.838 0 01-.252-.616z"/><path id="><" fill="#7E7C7B" d="M92.932 29.338l-.532-.84 5.572-3.234-5.558-3.29.532-.868 6.594 4.018v.406l-6.608 3.808zM16.8 45.53v-.406l6.594-4.018.532.868-5.558 3.29 5.572 3.234-.532.84L16.8 45.53z"/><path id="li" fill="#A7333A" d="M26.236 40h2.324v7.448c0 .57.096.97.287 1.204.191.233.474.35.847.35.261 0 .511-.047.749-.14.238-.093.502-.252.791-.476l.504.77c-.15.13-.313.247-.49.35a3.638 3.638 0 01-1.106.42 2.963 2.963 0 01-1.442-.077 1.579 1.579 0 01-.679-.427 1.897 1.897 0 01-.413-.777c-.093-.322-.14-.721-.14-1.197v-6.51h-1.232V40zm8.204 9.8v-.938h2.436v-5.124H34.44V42.8h3.556v6.062h2.38v.938H34.44zm2.058-8.988c0-.252.084-.469.252-.651a.838.838 0 01.644-.273c.27 0 .497.091.679.273a.889.889 0 01.273.651.806.806 0 01-.273.616.963.963 0 01-.679.252.872.872 0 01-.644-.252.838.838 0 01-.252-.616z"/><path id=">" fill="#7E7C7B" d="M42.532 49.338l-.532-.84 5.572-3.234-5.558-3.29.532-.868 6.594 4.018v.406z"/><path id="1" fill="#181717" d="M51.548 48.792h2.198v-7.364l-2.24 1.568-.546-.798L54.04 40h.784v8.792h2.156V49.8h-5.432z"/><path id="</" fill="#7E7C7B" d="M58.8 45.53v-.406l6.594-4.018.532.868-5.558 3.29 5.572 3.234-.532.84L58.8 45.53zm14.084-5.698l.882.392-5.11 11.536-.882-.392 5.11-11.536z"/><path id="li" fill="#A7333A" d="M76.636 40h2.324v7.448c0 .57.096.97.287 1.204.191.233.474.35.847.35.261 0 .511-.047.749-.14.238-.093.502-.252.791-.476l.504.77c-.15.13-.313.247-.49.35a3.638 3.638 0 01-1.106.42 2.963 2.963 0 01-1.442-.077 1.579 1.579 0 01-.679-.427 1.897 1.897 0 01-.413-.777c-.093-.322-.14-.721-.14-1.197v-6.51h-1.232V40zm8.204 9.8v-.938h2.436v-5.124H84.84V42.8h3.556v6.062h2.38v.938H84.84zm2.058-8.988c0-.252.084-.469.252-.651a.838.838 0 01.644-.273c.27 0 .497.091.679.273a.889.889 0 01.273.651.806.806 0 01-.273.616.963.963 0 01-.679.252.872.872 0 01-.644-.252.838.838 0 01-.252-.616z"/><path id="><" fill="#7E7C7B" d="M92.932 49.338l-.532-.84 5.572-3.234-5.558-3.29.532-.868 6.594 4.018v.406l-6.608 3.808zM16.8 65.53v-.406l6.594-4.018.532.868-5.558 3.29 5.572 3.234-.532.84L16.8 65.53z"/><path id="li" fill="#A7333A" d="M26.236 60h2.324v7.448c0 .57.096.97.287 1.204.191.233.474.35.847.35.261 0 .511-.047.749-.14.238-.093.502-.252.791-.476l.504.77c-.15.13-.313.247-.49.35a3.638 3.638 0 01-1.106.42 2.963 2.963 0 01-1.442-.077 1.579 1.579 0 01-.679-.427 1.897 1.897 0 01-.413-.777c-.093-.322-.14-.721-.14-1.197v-6.51h-1.232V60zm8.204 9.8v-.938h2.436v-5.124H34.44V62.8h3.556v6.062h2.38v.938H34.44zm2.058-8.988c0-.252.084-.469.252-.651a.838.838 0 01.644-.273c.27 0 .497.091.679.273a.889.889 0 01.273.651.806.806 0 01-.273.616.963.963 0 01-.679.252.872.872 0 01-.644-.252.838.838 0 01-.252-.616z"/><path id=">" fill="#7E7C7B" d="M42.532 69.338l-.532-.84 5.572-3.234-5.558-3.29.532-.868 6.594 4.018v.406z"/><path id="2" fill="#181717" d="M56.77 62.478c0 .859-.406 1.815-1.218 2.87-.812 1.055-1.946 2.203-3.402 3.444h4.928V69.8h-6.216v-1.008c.177-.168.42-.387.728-.658.308-.27.64-.572.994-.903a29.01 29.01 0 001.085-1.071c.369-.383.702-.775 1.001-1.176.299-.401.541-.803.728-1.204a2.75 2.75 0 00.28-1.162c0-.56-.147-1.001-.441-1.323-.294-.322-.735-.483-1.323-.483-.504 0-.929.056-1.274.168a3.101 3.101 0 00-.938.49l-.476-.77c.42-.299.866-.518 1.337-.658s.987-.21 1.547-.21c.877 0 1.54.238 1.988.714.448.476.672 1.12.672 1.932z"/><path id="</" fill="#7E7C7B" d="M58.8 65.53v-.406l6.594-4.018.532.868-5.558 3.29 5.572 3.234-.532.84L58.8 65.53zm14.084-5.698l.882.392-5.11 11.536-.882-.392 5.11-11.536z"/><path id="li" fill="#A7333A" d="M76.636 60h2.324v7.448c0 .57.096.97.287 1.204.191.233.474.35.847.35.261 0 .511-.047.749-.14.238-.093.502-.252.791-.476l.504.77c-.15.13-.313.247-.49.35a3.638 3.638 0 01-1.106.42 2.963 2.963 0 01-1.442-.077 1.579 1.579 0 01-.679-.427 1.897 1.897 0 01-.413-.777c-.093-.322-.14-.721-.14-1.197v-6.51h-1.232V60zm8.204 9.8v-.938h2.436v-5.124H84.84V62.8h3.556v6.062h2.38v.938H84.84zm2.058-8.988c0-.252.084-.469.252-.651a.838.838 0 01.644-.273c.27 0 .497.091.679.273a.889.889 0 01.273.651.806.806 0 01-.273.616.963.963 0 01-.679.252.872.872 0 01-.644-.252.838.838 0 01-.252-.616z"/><path id="></" fill="#7E7C7B" d="M92.932 69.338l-.532-.84 5.572-3.234-5.558-3.29.532-.868 6.594 4.018v.406l-6.608 3.808zM0 85.53v-.406l6.594-4.018.532.868-5.558 3.29 5.572 3.234-.532.84L0 85.53zm14.084-5.698l.882.392-5.11 11.536-.882-.392 5.11-11.536z"/><path id="ol" fill="#A7333A" d="M16.968 86.3c0-1.13.294-2.023.882-2.681.588-.658 1.428-.987 2.52-.987.588 0 1.094.096 1.519.287.425.191.777.45 1.057.777.28.327.488.714.623 1.162.135.448.203.929.203 1.442 0 .56-.075 1.066-.224 1.519-.15.453-.369.838-.658 1.155-.29.317-.646.562-1.071.735a3.82 3.82 0 01-1.449.259c-.579 0-1.083-.096-1.512-.287a2.967 2.967 0 01-1.064-.777 3.168 3.168 0 01-.623-1.162 4.963 4.963 0 01-.203-1.442zm1.162 0c0 .327.04.653.119.98.08.327.208.62.385.882.177.261.408.471.693.63.285.159.632.238 1.043.238.747 0 1.309-.231 1.687-.693.378-.462.567-1.141.567-2.037 0-.336-.04-.665-.119-.987a2.608 2.608 0 00-.392-.875 2.066 2.066 0 00-.7-.63c-.285-.159-.632-.238-1.043-.238-.747 0-1.307.229-1.68.686-.373.457-.56 1.139-.56 2.044zm8.106-6.3h2.324v7.448c0 .57.096.97.287 1.204.191.233.474.35.847.35.261 0 .511-.047.749-.14.238-.093.502-.252.791-.476l.504.77c-.15.13-.313.247-.49.35a3.638 3.638 0 01-1.106.42 2.963 2.963 0 01-1.442-.077 1.579 1.579 0 01-.679-.427 1.897 1.897 0 01-.413-.777c-.093-.322-.14-.721-.14-1.197v-6.51h-1.232V80z"/><path id=">" fill="#7E7C7B" d="M34.132 89.338l-.532-.84 5.572-3.234-5.558-3.29.532-.868 6.594 4.018v.406z"/></g><g id="Line-Copy-5-+-Line-Copy-4-+-Line-Copy-3" stroke="#C06334" stroke-linecap="square" stroke-width="3" transform="rotate(90 37 184.5)"><path id="Line-Copy-5" d="M.527.625S30.885-3.5 30.885 81.25s-15.179 94.723-15.179 94.723"/><path id="Line" d="M15 178v-17"/><path id="Line-Copy" d="M15 178l15-5"/></g><g id="Line-Copy-5-+-Line-Copy-4-+-Line-Copy-3-Copy-Copy" stroke="#C06334" stroke-linecap="square" stroke-width="3" transform="rotate(90 53.5 171.5)"><path id="Line-Copy-5" d="M.257.737s21.205 7.589 21.205 93.119v67.243"/><path id="Line" d="M21.409 162.661L14.5 150.5"/><path id="Line-Copy" d="M21.476 162.491L27.5 150.5"/></g><g id="Line-Copy-5-+-Line-Copy-4-+-Line-Copy-3-Copy-Copy-Copy" stroke="#C06334" stroke-linecap="square" stroke-width="3" transform="matrix(0 -1 -1 0 225 96)"><path id="Line-Copy-5" d="M.257.737s21.205 7.589 21.205 93.119v67.243"/><path id="Line" d="M21.409 162.661L14.5 150.5"/><path id="Line-Copy" d="M21.476 162.491L27.5 150.5"/></g><path id="Line-Copy-3" stroke="#C06334" stroke-linecap="square" stroke-width="3" d="M45.5 49.5l5-14"/><path id="Line-Copy-2" stroke="#C06334" stroke-linecap="square" stroke-width="3" d="M45.5 49.5h14"/><text id="ol.after" fill="#C06334" font-family="PTMono-Regular, PT Mono" font-size="14" font-weight="normal"><tspan x="113" y="193">ol.after</tspan></text><text id="ol.append" fill="#C06334" font-family="PTMono-Regular, PT Mono" font-size="14" font-weight="normal"><tspan x="113" y="156">ol.append</tspan></text><text id="ol.prepend" fill="#C06334" font-family="PTMono-Regular, PT Mono" font-size="14" font-weight="normal"><tspan x="113" y="66">ol.prepend</tspan></text><g id="Line-Copy-5-+-Line-Copy-4-+-Line-Copy-3-Copy" stroke="#C06334" stroke-linecap="square" stroke-width="3" transform="matrix(0 -1 -1 0 222.5 64.5)"><path id="Line-Copy-5" d="M.527.625S30.885-3.5 30.885 81.25s-15.179 94.723-15.179 94.723"/></g><text id="ol.before" fill="#C06334" font-family="PTMono-Regular, PT Mono" font-size="14" font-weight="normal"><tspan x="111" y="26">ol.before</tspan></text><text id="(…nodes-or-strings)" fill="#643B0C" font-family="PTMono-Regular, PT Mono" font-size="14" font-weight="normal"><tspan x="198" y="112">(…nodes or strings)</tspan></text></g></g></svg> \ No newline at end of file diff --git a/2-ui/1-document/07-modifying-document/before-prepend-append-after@2x.png b/2-ui/1-document/07-modifying-document/before-prepend-append-after@2x.png deleted file mode 100644 index 9d68943147..0000000000 Binary files a/2-ui/1-document/07-modifying-document/before-prepend-append-after@2x.png and /dev/null differ diff --git a/2-ui/1-document/07-modifying-document/insert-adjacent.png b/2-ui/1-document/07-modifying-document/insert-adjacent.png deleted file mode 100644 index 7cf2f5973c..0000000000 Binary files a/2-ui/1-document/07-modifying-document/insert-adjacent.png and /dev/null differ diff --git a/2-ui/1-document/07-modifying-document/insert-adjacent.svg b/2-ui/1-document/07-modifying-document/insert-adjacent.svg new file mode 100644 index 0000000000..e26fd023a1 --- /dev/null +++ b/2-ui/1-document/07-modifying-document/insert-adjacent.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="409" height="203" viewBox="0 0 409 203"><defs><style>@import url(https://fonts.googleapis.com/css?family=Open+Sans:bold,italic,bolditalic%7CPT+Mono);@font-face{font-family:'PT Mono';font-weight:700;font-style:normal;src:local('PT MonoBold'),url(/font/PTMonoBold.woff2) format('woff2'),url(/font/PTMonoBold.woff) format('woff'),url(/font/PTMonoBold.ttf) format('truetype')}</style></defs><g id="dom" fill="none" fill-rule="evenodd" stroke="none" stroke-width="1"><g id="insert-adjacent.svg"><g id="<ol>-<li>0</li>-<li>" fill-rule="nonzero" transform="translate(18.63 60.2)"><path id="<" fill="#7E7C7B" d="M0 5.53v-.406l6.594-4.018.532.868-5.558 3.29L7.14 8.498l-.532.84z"/><path id="ol" fill="#A7333A" d="M8.568 6.3c0-1.13.294-2.023.882-2.681.588-.658 1.428-.987 2.52-.987.588 0 1.094.096 1.519.287.425.191.777.45 1.057.777.28.327.488.714.623 1.162.135.448.203.929.203 1.442 0 .56-.075 1.066-.224 1.519-.15.453-.369.838-.658 1.155-.29.317-.646.562-1.071.735a3.82 3.82 0 01-1.449.259c-.579 0-1.083-.096-1.512-.287a2.967 2.967 0 01-1.064-.777 3.168 3.168 0 01-.623-1.162A4.963 4.963 0 018.568 6.3zm1.162 0c0 .327.04.653.119.98.08.327.208.62.385.882.177.261.408.471.693.63.285.159.632.238 1.043.238.747 0 1.309-.231 1.687-.693.378-.462.567-1.141.567-2.037 0-.336-.04-.665-.119-.987a2.608 2.608 0 00-.392-.875 2.066 2.066 0 00-.7-.63c-.285-.159-.632-.238-1.043-.238-.747 0-1.307.229-1.68.686-.373.457-.56 1.139-.56 2.044zM17.836 0h2.324v7.448c0 .57.096.97.287 1.204.191.233.474.35.847.35.261 0 .511-.047.749-.14.238-.093.502-.252.791-.476l.504.77c-.15.13-.313.247-.49.35a3.638 3.638 0 01-1.106.42 2.963 2.963 0 01-1.442-.077 1.579 1.579 0 01-.679-.427 1.897 1.897 0 01-.413-.777c-.093-.322-.14-.721-.14-1.197V.938h-1.232V0z"/><path id="><" fill="#7E7C7B" d="M25.732 9.338l-.532-.84 5.572-3.234-5.558-3.29.532-.868 6.594 4.018v.406l-6.608 3.808zM16.8 25.53v-.406l6.594-4.018.532.868-5.558 3.29 5.572 3.234-.532.84L16.8 25.53z"/><path id="li" fill="#A7333A" d="M26.236 20h2.324v7.448c0 .57.096.97.287 1.204.191.233.474.35.847.35.261 0 .511-.047.749-.14.238-.093.502-.252.791-.476l.504.77c-.15.13-.313.247-.49.35a3.638 3.638 0 01-1.106.42 2.963 2.963 0 01-1.442-.077 1.579 1.579 0 01-.679-.427 1.897 1.897 0 01-.413-.777c-.093-.322-.14-.721-.14-1.197v-6.51h-1.232V20zm8.204 9.8v-.938h2.436v-5.124H34.44V22.8h3.556v6.062h2.38v.938H34.44zm2.058-8.988c0-.252.084-.469.252-.651a.838.838 0 01.644-.273c.27 0 .497.091.679.273a.889.889 0 01.273.651.806.806 0 01-.273.616.963.963 0 01-.679.252.872.872 0 01-.644-.252.838.838 0 01-.252-.616z"/><path id=">" fill="#7E7C7B" d="M42.532 29.338l-.532-.84 5.572-3.234-5.558-3.29.532-.868 6.594 4.018v.406z"/><path id="0" fill="#181717" d="M50.54 24.9c0-.793.07-1.505.21-2.135.14-.63.35-1.162.63-1.596.28-.434.635-.765 1.064-.994.43-.229.938-.343 1.526-.343.625 0 1.155.112 1.589.336.434.224.786.55 1.057.98.27.43.467.959.588 1.589.121.63.182 1.351.182 2.163a9.88 9.88 0 01-.21 2.135c-.14.63-.35 1.162-.63 1.596-.28.434-.635.765-1.064.994-.43.229-.938.343-1.526.343-.616 0-1.141-.124-1.575-.371a2.956 2.956 0 01-1.064-1.043c-.275-.448-.474-.982-.595-1.603a10.713 10.713 0 01-.182-2.051zm5.698 0c0-.495-.028-.966-.084-1.414l-4.158 3.794c.159.532.397.957.714 1.274.317.317.733.476 1.246.476.821 0 1.407-.34 1.757-1.022.35-.681.525-1.717.525-3.108zm-4.536 0c0 .233.007.457.021.672.014.215.03.425.049.63l4.172-3.78c-.159-.504-.394-.905-.707-1.204-.313-.299-.735-.448-1.267-.448-.83 0-1.416.343-1.757 1.029-.34.686-.511 1.72-.511 3.101z"/><path id="</" fill="#7E7C7B" d="M58.8 25.53v-.406l6.594-4.018.532.868-5.558 3.29 5.572 3.234-.532.84L58.8 25.53zm14.084-5.698l.882.392-5.11 11.536-.882-.392 5.11-11.536z"/><path id="li" fill="#A7333A" d="M76.636 20h2.324v7.448c0 .57.096.97.287 1.204.191.233.474.35.847.35.261 0 .511-.047.749-.14.238-.093.502-.252.791-.476l.504.77c-.15.13-.313.247-.49.35a3.638 3.638 0 01-1.106.42 2.963 2.963 0 01-1.442-.077 1.579 1.579 0 01-.679-.427 1.897 1.897 0 01-.413-.777c-.093-.322-.14-.721-.14-1.197v-6.51h-1.232V20zm8.204 9.8v-.938h2.436v-5.124H84.84V22.8h3.556v6.062h2.38v.938H84.84zm2.058-8.988c0-.252.084-.469.252-.651a.838.838 0 01.644-.273c.27 0 .497.091.679.273a.889.889 0 01.273.651.806.806 0 01-.273.616.963.963 0 01-.679.252.872.872 0 01-.644-.252.838.838 0 01-.252-.616z"/><path id="><" fill="#7E7C7B" d="M92.932 29.338l-.532-.84 5.572-3.234-5.558-3.29.532-.868 6.594 4.018v.406l-6.608 3.808zM16.8 45.53v-.406l6.594-4.018.532.868-5.558 3.29 5.572 3.234-.532.84L16.8 45.53z"/><path id="li" fill="#A7333A" d="M26.236 40h2.324v7.448c0 .57.096.97.287 1.204.191.233.474.35.847.35.261 0 .511-.047.749-.14.238-.093.502-.252.791-.476l.504.77c-.15.13-.313.247-.49.35a3.638 3.638 0 01-1.106.42 2.963 2.963 0 01-1.442-.077 1.579 1.579 0 01-.679-.427 1.897 1.897 0 01-.413-.777c-.093-.322-.14-.721-.14-1.197v-6.51h-1.232V40zm8.204 9.8v-.938h2.436v-5.124H34.44V42.8h3.556v6.062h2.38v.938H34.44zm2.058-8.988c0-.252.084-.469.252-.651a.838.838 0 01.644-.273c.27 0 .497.091.679.273a.889.889 0 01.273.651.806.806 0 01-.273.616.963.963 0 01-.679.252.872.872 0 01-.644-.252.838.838 0 01-.252-.616z"/><path id=">" fill="#7E7C7B" d="M42.532 49.338l-.532-.84 5.572-3.234-5.558-3.29.532-.868 6.594 4.018v.406z"/><path id="1" fill="#181717" d="M51.548 48.792h2.198v-7.364l-2.24 1.568-.546-.798L54.04 40h.784v8.792h2.156V49.8h-5.432z"/><path id="</" fill="#7E7C7B" d="M58.8 45.53v-.406l6.594-4.018.532.868-5.558 3.29 5.572 3.234-.532.84L58.8 45.53zm14.084-5.698l.882.392-5.11 11.536-.882-.392 5.11-11.536z"/><path id="li" fill="#A7333A" d="M76.636 40h2.324v7.448c0 .57.096.97.287 1.204.191.233.474.35.847.35.261 0 .511-.047.749-.14.238-.093.502-.252.791-.476l.504.77c-.15.13-.313.247-.49.35a3.638 3.638 0 01-1.106.42 2.963 2.963 0 01-1.442-.077 1.579 1.579 0 01-.679-.427 1.897 1.897 0 01-.413-.777c-.093-.322-.14-.721-.14-1.197v-6.51h-1.232V40zm8.204 9.8v-.938h2.436v-5.124H84.84V42.8h3.556v6.062h2.38v.938H84.84zm2.058-8.988c0-.252.084-.469.252-.651a.838.838 0 01.644-.273c.27 0 .497.091.679.273a.889.889 0 01.273.651.806.806 0 01-.273.616.963.963 0 01-.679.252.872.872 0 01-.644-.252.838.838 0 01-.252-.616z"/><path id="><" fill="#7E7C7B" d="M92.932 49.338l-.532-.84 5.572-3.234-5.558-3.29.532-.868 6.594 4.018v.406l-6.608 3.808zM16.8 65.53v-.406l6.594-4.018.532.868-5.558 3.29 5.572 3.234-.532.84L16.8 65.53z"/><path id="li" fill="#A7333A" d="M26.236 60h2.324v7.448c0 .57.096.97.287 1.204.191.233.474.35.847.35.261 0 .511-.047.749-.14.238-.093.502-.252.791-.476l.504.77c-.15.13-.313.247-.49.35a3.638 3.638 0 01-1.106.42 2.963 2.963 0 01-1.442-.077 1.579 1.579 0 01-.679-.427 1.897 1.897 0 01-.413-.777c-.093-.322-.14-.721-.14-1.197v-6.51h-1.232V60zm8.204 9.8v-.938h2.436v-5.124H34.44V62.8h3.556v6.062h2.38v.938H34.44zm2.058-8.988c0-.252.084-.469.252-.651a.838.838 0 01.644-.273c.27 0 .497.091.679.273a.889.889 0 01.273.651.806.806 0 01-.273.616.963.963 0 01-.679.252.872.872 0 01-.644-.252.838.838 0 01-.252-.616z"/><path id=">" fill="#7E7C7B" d="M42.532 69.338l-.532-.84 5.572-3.234-5.558-3.29.532-.868 6.594 4.018v.406z"/><path id="2" fill="#181717" d="M56.77 62.478c0 .859-.406 1.815-1.218 2.87-.812 1.055-1.946 2.203-3.402 3.444h4.928V69.8h-6.216v-1.008c.177-.168.42-.387.728-.658.308-.27.64-.572.994-.903a29.01 29.01 0 001.085-1.071c.369-.383.702-.775 1.001-1.176.299-.401.541-.803.728-1.204a2.75 2.75 0 00.28-1.162c0-.56-.147-1.001-.441-1.323-.294-.322-.735-.483-1.323-.483-.504 0-.929.056-1.274.168a3.101 3.101 0 00-.938.49l-.476-.77c.42-.299.866-.518 1.337-.658s.987-.21 1.547-.21c.877 0 1.54.238 1.988.714.448.476.672 1.12.672 1.932z"/><path id="</" fill="#7E7C7B" d="M58.8 65.53v-.406l6.594-4.018.532.868-5.558 3.29 5.572 3.234-.532.84L58.8 65.53zm14.084-5.698l.882.392-5.11 11.536-.882-.392 5.11-11.536z"/><path id="li" fill="#A7333A" d="M76.636 60h2.324v7.448c0 .57.096.97.287 1.204.191.233.474.35.847.35.261 0 .511-.047.749-.14.238-.093.502-.252.791-.476l.504.77c-.15.13-.313.247-.49.35a3.638 3.638 0 01-1.106.42 2.963 2.963 0 01-1.442-.077 1.579 1.579 0 01-.679-.427 1.897 1.897 0 01-.413-.777c-.093-.322-.14-.721-.14-1.197v-6.51h-1.232V60zm8.204 9.8v-.938h2.436v-5.124H84.84V62.8h3.556v6.062h2.38v.938H84.84zm2.058-8.988c0-.252.084-.469.252-.651a.838.838 0 01.644-.273c.27 0 .497.091.679.273a.889.889 0 01.273.651.806.806 0 01-.273.616.963.963 0 01-.679.252.872.872 0 01-.644-.252.838.838 0 01-.252-.616z"/><path id="></" fill="#7E7C7B" d="M92.932 69.338l-.532-.84 5.572-3.234-5.558-3.29.532-.868 6.594 4.018v.406l-6.608 3.808zM0 85.53v-.406l6.594-4.018.532.868-5.558 3.29 5.572 3.234-.532.84L0 85.53zm14.084-5.698l.882.392-5.11 11.536-.882-.392 5.11-11.536z"/><path id="ol" fill="#A7333A" d="M16.968 86.3c0-1.13.294-2.023.882-2.681.588-.658 1.428-.987 2.52-.987.588 0 1.094.096 1.519.287.425.191.777.45 1.057.777.28.327.488.714.623 1.162.135.448.203.929.203 1.442 0 .56-.075 1.066-.224 1.519-.15.453-.369.838-.658 1.155-.29.317-.646.562-1.071.735a3.82 3.82 0 01-1.449.259c-.579 0-1.083-.096-1.512-.287a2.967 2.967 0 01-1.064-.777 3.168 3.168 0 01-.623-1.162 4.963 4.963 0 01-.203-1.442zm1.162 0c0 .327.04.653.119.98.08.327.208.62.385.882.177.261.408.471.693.63.285.159.632.238 1.043.238.747 0 1.309-.231 1.687-.693.378-.462.567-1.141.567-2.037 0-.336-.04-.665-.119-.987a2.608 2.608 0 00-.392-.875 2.066 2.066 0 00-.7-.63c-.285-.159-.632-.238-1.043-.238-.747 0-1.307.229-1.68.686-.373.457-.56 1.139-.56 2.044zm8.106-6.3h2.324v7.448c0 .57.096.97.287 1.204.191.233.474.35.847.35.261 0 .511-.047.749-.14.238-.093.502-.252.791-.476l.504.77c-.15.13-.313.247-.49.35a3.638 3.638 0 01-1.106.42 2.963 2.963 0 01-1.442-.077 1.579 1.579 0 01-.679-.427 1.897 1.897 0 01-.413-.777c-.093-.322-.14-.721-.14-1.197v-6.51h-1.232V80z"/><path id=">" fill="#7E7C7B" d="M34.132 89.338l-.532-.84 5.572-3.234-5.558-3.29.532-.868 6.594 4.018v.406z"/></g><text id="ol.insertAdjacentHTM" fill="#643B0C" font-family="PTMono-Regular, PT Mono" font-size="14" font-weight="normal"><tspan x="156" y="112">ol.insertAdjacentHTML(*, html)</tspan></text><g id="Line-Copy-5-+-Line-Copy-4-+-Line-Copy-3" stroke="#C06334" stroke-linecap="square" stroke-width="3" transform="rotate(90 37 184.5)"><path id="Line-Copy-5" d="M.527.625S30.885-3.5 30.885 81.25s-15.179 94.723-15.179 94.723"/><path id="Line" d="M15 178v-17"/><path id="Line-Copy" d="M15 178l15-5"/></g><g id="Line-Copy-5-+-Line-Copy-4-+-Line-Copy-3-Copy-Copy" stroke="#C06334" stroke-linecap="square" stroke-width="3" transform="rotate(90 53.5 171.5)"><path id="Line-Copy-5" d="M.257.737s21.205 7.589 21.205 93.119v67.243"/><path id="Line" d="M21.409 162.661L14.5 150.5"/><path id="Line-Copy" d="M21.476 162.491L27.5 150.5"/></g><g id="Line-Copy-5-+-Line-Copy-4-+-Line-Copy-3-Copy-Copy-Copy" stroke="#C06334" stroke-linecap="square" stroke-width="3" transform="matrix(0 -1 -1 0 225 96)"><path id="Line-Copy-5" d="M.257.737s21.205 7.589 21.205 93.119v67.243"/><path id="Line" d="M21.409 162.661L14.5 150.5"/><path id="Line-Copy" d="M21.476 162.491L27.5 150.5"/></g><path id="Line-Copy-3" stroke="#C06334" stroke-linecap="square" stroke-width="3" d="M45.5 49.5l5-14"/><path id="Line-Copy-2" stroke="#C06334" stroke-linecap="square" stroke-width="3" d="M45.5 49.5h14"/><text id="afterend" fill="#C06334" font-family="PTMono-Regular, PT Mono" font-size="14" font-weight="normal"><tspan x="113" y="193">afterend</tspan></text><text id="beforeend" fill="#C06334" font-family="PTMono-Regular, PT Mono" font-size="14" font-weight="normal"><tspan x="113" y="156">beforeend</tspan></text><text id="afterbegin" fill="#C06334" font-family="PTMono-Regular, PT Mono" font-size="14" font-weight="normal"><tspan x="113" y="66">afterbegin</tspan></text><g id="Line-Copy-5-+-Line-Copy-4-+-Line-Copy-3-Copy" stroke="#C06334" stroke-linecap="square" stroke-width="3" transform="matrix(0 -1 -1 0 222.5 64.5)"><path id="Line-Copy-5" d="M.527.625S30.885-3.5 30.885 81.25s-15.179 94.723-15.179 94.723"/></g><text id="beforebegin" fill="#C06334" font-family="PTMono-Regular, PT Mono" font-size="14" font-weight="normal"><tspan x="111" y="26">beforebegin</tspan></text></g></g></svg> \ No newline at end of file diff --git a/2-ui/1-document/07-modifying-document/insert-adjacent@2x.png b/2-ui/1-document/07-modifying-document/insert-adjacent@2x.png deleted file mode 100644 index fcb14017ae..0000000000 Binary files a/2-ui/1-document/07-modifying-document/insert-adjacent@2x.png and /dev/null differ diff --git a/2-ui/1-document/08-styles-and-classes/2-create-notification/solution.view/index.html b/2-ui/1-document/08-styles-and-classes/2-create-notification/solution.view/index.html index dc4eeec9fe..6c82c8ffad 100755 --- a/2-ui/1-document/08-styles-and-classes/2-create-notification/solution.view/index.html +++ b/2-ui/1-document/08-styles-and-classes/2-create-notification/solution.view/index.html @@ -6,7 +6,7 @@ <body> - <h2>Notification is on the right</h2> + <h2>Уведомление находится справа</h2> <p> Lorem ipsum dolor sit amet, consectetur adipisicing elit. Dolorum aspernatur quam ex eaque inventore quod voluptatem adipisci omnis nemo nulla fugit iste numquam ducimus cumque minima porro ea quidem maxime necessitatibus beatae labore soluta voluptatum diff --git a/2-ui/1-document/08-styles-and-classes/2-create-notification/source.view/index.html b/2-ui/1-document/08-styles-and-classes/2-create-notification/source.view/index.html index b5e2f83f7d..88b6885a97 100755 --- a/2-ui/1-document/08-styles-and-classes/2-create-notification/source.view/index.html +++ b/2-ui/1-document/08-styles-and-classes/2-create-notification/source.view/index.html @@ -6,7 +6,7 @@ <body> - <h2>Notification is on the right</h2> + <h2>Уведомление находится справа</h2> <p> Lorem ipsum dolor sit amet, consectetur adipisicing elit. Dolorum aspernatur quam ex eaque inventore quod voluptatem adipisci omnis nemo nulla fugit iste numquam ducimus cumque minima porro ea quidem maxime necessitatibus beatae labore soluta voluptatum diff --git a/2-ui/1-document/08-styles-and-classes/2-create-notification/task.md b/2-ui/1-document/08-styles-and-classes/2-create-notification/task.md index 60930cb6cf..3a3d9fc732 100644 --- a/2-ui/1-document/08-styles-and-classes/2-create-notification/task.md +++ b/2-ui/1-document/08-styles-and-classes/2-create-notification/task.md @@ -2,23 +2,23 @@ importance: 5 --- -# Create a notification +# Создать уведомление -Write a function `showNotification(options)` that creates a notification: `<div class="notification">` with the given content. The notification should automatically disappear after 1.5 seconds. +Напишите функцию `showNotification(options)`, которая создаёт уведомление: `<div class="notification">` с заданным содержимым. Уведомление должно автоматически исчезнуть через 1,5 секунды. -The options are: +Пример объекта `options`: ```js -// shows an element with the text "Hello" near the right-top of the window +// показывает элемент с текстом "Hello" рядом с правой верхней частью окна. showNotification({ - top: 10, // 10px from the top of the window (by default 0px) - right: 10, // 10px from the right edge of the window (by default 0px) - html: "Hello!", // the HTML of notification - className: "welcome" // an additional class for the div (optional) + top: 10, // 10px от верхней границы окна (по умолчанию 0px) + right: 10, // 10px от правого края окна (по умолчанию 0px) + html: "Hello!", // HTML-уведомление + className: "welcome" // дополнительный класс для div (необязательно) }); ``` [demo src="solution"] -Use CSS positioning to show the element at given top/right coordinates. The source document has the necessary styles. +Используйте CSS-позиционирование для отображения элемента в заданных координатах. Исходный документ имеет необходимые стили. diff --git a/2-ui/1-document/08-styles-and-classes/article.md b/2-ui/1-document/08-styles-and-classes/article.md index 5159c532d3..d81ef38a00 100644 --- a/2-ui/1-document/08-styles-and-classes/article.md +++ b/2-ui/1-document/08-styles-and-classes/article.md @@ -1,36 +1,59 @@ -# Styles and classes +# Стили и классы -Before we get into JavaScript's ways of dealing with styles and classes -- here's an important rule. Hopefully it's obvious enough, but we still have to mention it. +До того, как начнёте изучать способы работы со стилями и классами в JavaScript, есть одно важное правило. Надеемся, это достаточно очевидно, но мы всё равно должны об этом упомянуть. -There are generally two ways to style an element: +Как правило, существует два способа задания стилей для элемента: -1. Create a class in CSS and add it: `<div class="...">` -2. Write properties directly into `style`: `<div style="...">`. +1. Создать класс в CSS и использовать его: `<div class="...">` +2. Писать стили непосредственно в атрибуте `style`: `<div style="...">`. -CSS is always the preferred way -- not only for HTML, but in JavaScript as well. +JavaScript может менять и классы, и свойство `style`. -We should only manipulate the `style` property if classes "can't handle it". +Классы - всегда предпочтительный вариант по сравнению со `style`. Мы должны манипулировать свойством `style` только в том случае, если классы "не могут справиться". -For instance, `style` is acceptable if we calculate coordinates of an element dynamically and want to set them from JavaScript, like this: +Например, использование `style` является приемлемым, если мы вычисляем координаты элемента динамически и хотим установить их из JavaScript: ```js -let top = /* complex calculations */; -let left = /* complex calculations */; -elem.style.left = left; // e.g '123px' -elem.style.top = top; // e.g '456px' +let top = /* сложные расчёты */; +let left = /* сложные расчёты */; + +elem.style.left = left; // например, '123px', значение вычисляется во время работы скрипта +elem.style.top = top; // например, '456px' +``` + +Мы также можем установить сразу несколько стилей для элемента, используя свойство `cssText`. Но с ним нужно быть осторожнее, т.к оно может как добавить стили к существующим, так и полностью перезаписать их на новые: + +```js +let top = /* сложные расчёты */; +let left = /* сложные расчёты */; + +// полная перезапись стилей elem, используем = +elem.style.cssText *!*=*/!* ` + top: ${top}; + left: ${left}; +`; + +// добавление новых стилей к существующим стилям elem, используем += +elem.style.cssText *!*+=*/!* ` + top: ${top}; + left: ${left}; +`; + +// если элементу уже заданы стили, которые мы хотим добавить (+=), +// они будут перезаписаны на новые. ``` -For other cases, like making the text red, adding a background icon -- describe that in CSS and then apply the class. That's more flexible and easier to support. +В других случаях, например, чтобы сделать текст красным, добавить значок фона -- описываем это в CSS и добавляем класс (JavaScript может это сделать). Это более гибкое и лёгкое в поддержке решение. -## className and classList +## className и classList -Changing a class is one of the most often used actions in scripts. +Изменение класса является одним из наиболее часто используемых действий в скриптах. -In the ancient time, there was a limitation in JavaScript: a reserved word like `"class"` could not be an object property. That limitation does not exist now, but at that time it was impossible to have a `"class"` property, like `elem.class`. +Когда-то давно в JavaScript существовало ограничение: зарезервированное слово типа `"class"` не могло быть свойством объекта. Это ограничение сейчас отсутствует, но в то время было невозможно иметь свойство `elem.class`. -So for classes the similar-looking property `"className"` was introduced: the `elem.className` corresponds to the `"class"` attribute. +Поэтому для классов было введено схожее свойство `"className"`: `elem.className` соответствует атрибуту `"class"`. -For instance: +Например: ```html run <body class="main page"> @@ -40,19 +63,19 @@ For instance: </body> ``` -If we assign something to `elem.className`, it replaces the whole strings of classes. Sometimes that's what we need, but often we want to add/remove a single class. +Если мы присваиваем что-то `elem.className`, то это заменяет всю строку с классами. Иногда это то, что нам нужно, но часто мы хотим добавить/удалить один класс. -There's another property for that: `elem.classList`. +Для этого есть другое свойство: `elem.classList`. -The `elem.classList` is a special object with methods to `add/remove/toggle` classes. +`elem.classList` -- это специальный объект с методами для добавления/удаления одного класса. -For instance: +Например: ```html run <body class="main page"> <script> *!* - // add a class + // добавление класса document.body.classList.add('article'); */!* @@ -61,31 +84,39 @@ For instance: </body> ``` -So we can operate both on the full class string using `className` or on individual classes using `classList`. What we choose depends on our needs. +Так что мы можем работать как со строкой полного класса, используя `className`, так и с отдельными классами, используя `classList`. Выбираем тот вариант, который нам удобнее. -Methods of `classList`: +Методы `classList`: -- `elem.classList.add/remove("class")` -- adds/removes the class. -- `elem.classList.toggle("class")` -- if the class exists, then removes it, otherwise adds it. -- `elem.classList.contains("class")` -- returns `true/false`, checks for the given class. +- `elem.classList.add/remove("class")` -- добавить/удалить класс. +- `elem.classList.toggle("class")` -- добавить класс, если его нет, иначе удалить. +- `elem.classList.contains("class")` -- проверка наличия класса, возвращает `true/false`. -Besides that, `classList` is iterable, so we can list all classes like this: +Кроме того, `classList` является перебираемым, поэтому можно перечислить все классы при помощи `for..of`: ```html run <body class="main page"> <script> for (let name of document.body.classList) { - alert(name); // main, and then page + alert(name); // main, затем page } </script> </body> ``` -## Element style +## Свойство style + +Свойство `elem.style` - это объект, который соответствует тому, что написано в атрибуте `"style"`. -The property `elem.style` is an object that corresponds to what's written in the `"style"` attribute. Setting `elem.style.width="100px"` works as if we had in the attribute `style="width:100px"`. +Свойства из одного слова записываются так же, с маленькой буквы: -For multi-word property the camelCase is used: +```js no-beautify +background => elem.style.background +top => elem.style.top +opacity => elem.style.opacity +``` + +Для свойств из нескольких слов используется [camelCase](https://ru.wikipedia.org/wiki/CamelCase): ```js no-beautify background-color => elem.style.backgroundColor @@ -93,81 +124,56 @@ z-index => elem.style.zIndex border-left-width => elem.style.borderLeftWidth ``` -For instance: +Например: ```js run document.body.style.backgroundColor = prompt('background color?', 'green'); ``` -````smart header="Prefixed properties" -Browser-prefixed properties like `-moz-border-radius`, `-webkit-border-radius` also follow the same rule, for instance: +````smart header="Свойства с префиксом" +Стили с браузерным префиксом, например, `-moz-border-radius`, `-webkit-border-radius` преобразуются по тому же принципу: дефис означает заглавную букву. + +Например: ```js button.style.MozBorderRadius = '5px'; button.style.WebkitBorderRadius = '5px'; ``` - -That is: a dash `"-"` becomes an uppercase. ```` -## Resetting the style property +## Сброс стилей -Sometimes we want to assign a style property, and later remove it. +Иногда нам нужно добавить свойство стиля, а потом, позже, убрать его. -For instance, to hide an element, we can set `elem.style.display = "none"`. +Например, чтобы скрыть элемент, мы можем задать `elem.style.display = "none"`. -Then later we may want to remove the `style.display` as if it were not set. Instead of `delete elem.style.display` we should assign an empty string to it: `elem.style.display = ""`. +Затем мы можем удалить свойство `style.display`, чтобы вернуться к первоначальному состоянию. Вместо `delete elem.style.display` мы должны присвоить ему пустую строку: `elem.style.display = ""`. ```js run -// if we run this code, the <body> "blinks" -document.body.style.display = "none"; // hide +// если мы запустим этот код, <body> "мигнёт" +document.body.style.display = "none"; // скрыть -setTimeout(() => document.body.style.display = "", 1000); // back to normal +setTimeout(() => document.body.style.display = "", 1000); // возврат к нормальному состоянию ``` -If we set `display` to an empty string, then the browser applies CSS classes and its built-in styles normally, as if there were no such `display` property at all. +Если мы установим в `style.display` пустую строку, то браузер применит CSS-классы и встроенные стили, как если бы такого свойства `style.display` вообще не было. -````smart header="Full rewrite with `style.cssText`" -Normally, we use `style.*` to assign individual style properties. We can't set the full style like `div.style="color: red; width: 100px"`, because `div.style` is an object, and it's read-only. +## Следите за единицами измерения -To set the full style as a string, there's a special property `style.cssText`: +Не забудьте добавить к значениям единицы измерения. -```html run -<div id="div">Button</div> - -<script> - // we can set special style flags like "important" here - div.style.cssText=`color: red !important; - background-color: yellow; - width: 100px; - text-align: center; - `; - - alert(div.style.cssText); -</script> -``` - -We rarely use it, because such assignment removes all existing styles: it does not add, but replaces them. May occasionally delete something needed. But still can be done for new elements when we know we won't delete something important. - -The same can be accomplished by setting an attribute: `div.setAttribute('style', 'color: red...')`. -```` - -## Mind the units - -CSS units must be provided in style values. - -For instance, we should not set `elem.style.top` to `10`, but rather to `10px`. Otherwise it wouldn't work: +Например, мы должны устанавливать `10px`, а не просто `10` в свойство `elem.style.top`. Иначе это не сработает: ```html run height=100 <body> <script> *!* - // doesn't work! + // не работает! document.body.style.margin = 20; - alert(document.body.style.margin); // '' (empty string, the assignment is ignored) + alert(document.body.style.margin); // '' (пустая строка, присваивание игнорируется) */!* - // now add the CSS unit (px) - and it works + // сейчас добавим единицу измерения (px) - и заработает document.body.style.margin = '20px'; alert(document.body.style.margin); // 20px @@ -177,19 +183,19 @@ For instance, we should not set `elem.style.top` to `10`, but rather to `10px`. </body> ``` -Please note how the browser "unpacks" the property `style.margin` in the last lines and infers `style.marginLeft` and `style.marginTop` (and other partial margins) from it. +Пожалуйста, обратите внимание, браузер "распаковывает" свойство `style.margin` в последних строках и выводит `style.marginLeft` и `style.marginTop` из него. -## Computed styles: getComputedStyle +## Вычисленные стили: getComputedStyle -Modifying a style is easy. But how to *read* it? +Итак, изменить стиль очень просто. Но как его *прочитать*? -For instance, we want to know the size, margins, the color of an element. How to do it? +Например, мы хотим знать размер, отступы, цвет элемента. Как это сделать? -**The `style` property operates only on the value of the `"style"` attribute, without any CSS cascade.** +**Свойство `style` оперирует только значением атрибута `"style"`, без учёта CSS-каскада.** -So we can't read anything that comes from CSS classes using `elem.style`. +Поэтому, используя `elem.style`, мы не можем прочитать ничего, что приходит из классов CSS. -For instance, here `style` doesn't see the margin: +Например, здесь `style` не может видеть отступы: ```html run height=60 no-beautify <head> @@ -197,35 +203,35 @@ For instance, here `style` doesn't see the margin: </head> <body> - The red text + Красный текст <script> *!* - alert(document.body.style.color); // empty - alert(document.body.style.marginTop); // empty + alert(document.body.style.color); // пусто + alert(document.body.style.marginTop); // пусто */!* </script> </body> ``` -...But what if we need, say, to increase the margin by 20px? We would want the current value of it. +...Но что, если нам нужно, скажем, увеличить отступ на `20px`? Для начала нужно его текущее значение получить. -There's another method for that: `getComputedStyle`. +Для этого есть метод: `getComputedStyle`. -The syntax is: +Синтаксис: ```js -getComputedStyle(element[, pseudo]) +getComputedStyle(element, [pseudo]) ``` element -: Element to read the value for. +: Элемент, значения для которого нужно получить pseudo -: A pseudo-element if required, for instance `::before`. An empty string or no argument means the element itself. +: Указывается, если нужен стиль псевдоэлемента, например `::before`. Пустая строка или отсутствие аргумента означают сам элемент. -The result is an object with style properties, like `elem.style`, but now with respect to all CSS classes. +Результат вызова - объект со стилями, похожий на `elem.style`, но с учётом всех CSS-классов. -For instance: +Например: ```html run height=100 <head> @@ -236,7 +242,7 @@ For instance: <script> let computedStyle = getComputedStyle(document.body); - // now we can read the margin and the color from it + // сейчас мы можем прочитать отступ и цвет alert( computedStyle.marginTop ); // 5px alert( computedStyle.color ); // rgb(255, 0, 0) @@ -245,58 +251,44 @@ For instance: </body> ``` -```smart header="Computed and resolved values" -There are two concepts in [CSS](https://drafts.csswg.org/cssom/#resolved-values): +```smart header="Вычисленное (computed) и окончательное (resolved) значения" +Есть две концепции в [CSS](https://drafts.csswg.org/cssom/#resolved-values): -1. A *computed* style value is the value after all CSS rules and CSS inheritance is applied, as the result of the CSS cascade. It can look like `height:1em` or `font-size:125%`. -2. A *resolved* style value is the one finally applied to the element. Values like `1em` or `125%` are relative. The browser takes the computed value and makes all units fixed and absolute, for instance: `height:20px` or `font-size:16px`. For geometry properties resolved values may have a floating point, like `width:50.5px`. +1. *Вычисленное* (computed) значение – это то, которое получено после применения всех CSS-правил и CSS-наследования. Например, `height:1em` или `font-size:125%`. +2. *Окончательное* ([resolved](https://drafts.csswg.org/cssom/#resolved-values)) значение – непосредственно применяемое к элементу. Значения `1em` или `125%` являются относительными. Браузер берёт вычисленное значение и делает все единицы измерения фиксированными и абсолютными, например, `height:20px` или `font-size:16px`. Для геометрических свойств разрешённые значения могут иметь плавающую точку, например, `width:50.5px`. -A long time ago `getComputedStyle` was created to get computed values, but it turned out that resolved values are much more convenient, and the standard changed. +Давным-давно `getComputedStyle` был создан для получения вычисленных значений, но оказалось, что окончательные значения гораздо удобнее, и стандарт изменился. -So nowadays `getComputedStyle` actually returns the resolved value of the property. +Так что, в настоящее время `getComputedStyle` фактически возвращает окончательное значение свойства, для геометрии оно обычно в пикселях. ``` -````warn header="`getComputedStyle` requires the full property name" -We should always ask for the exact property that we want, like `paddingLeft` or `marginTop` or `borderTopWidth`. Otherwise the correct result is not guaranteed. - -For instance, if there are properties `paddingLeft/paddingTop`, then what should we get for `getComputedStyle(elem).padding`? Nothing, or maybe a "generated" value from known paddings? There's no standard rule here. - -There are other inconsistencies. As an example, some browsers (Chrome) show `10px` in the document below, and some of them (Firefox) -- do not: +````warn header="`getComputedStyle` требует полное свойство!" +Для правильного получения значения нужно указать точное свойство. Например: `paddingLeft`, `marginTop`, `borderTopWidth`. При обращении к сокращённому: `padding`, `margin`, `border` – правильный результат не гарантируется. -```html run -<style> - body { - margin: 10px; - } -</style> -<script> - let style = getComputedStyle(document.body); - alert(style.margin); // empty string in Firefox -</script> -``` +Например, если есть свойства `paddingLeft/paddingTop`, то что мы получим вызывая `getComputedStyle(elem).padding`? Ничего, или, может быть, "сгенерированное" значение из известных внутренних отступов? Стандарта для этого нет. ```` -```smart header="\"Visited\" links styles are hidden!" -Visited links may be colored using `:visited` CSS pseudoclass. +```smart header="Стили, применяемые к посещённым `:visited` ссылкам, скрываются!" +Посещённые ссылки могут быть окрашены с помощью псевдокласса `:visited`. -But `getComputedStyle` does not give access to that color, because otherwise an arbitrary page could find out whether the user visited a link by creating it on the page and checking the styles. +Но `getComputedStyle` не даёт доступ к этой информации, чтобы произвольная страница не могла определить, посещал ли пользователь ту или иную ссылку, проверив стили. -JavaScript may not see the styles applied by `:visited`. And also, there's a limitation in CSS that forbids to apply geometry-changing styles in `:visited`. That's to guarantee that there's no sideway for an evil page to test if a link was visited and hence to break the privacy. +JavaScript не видит стили, применяемые с помощью `:visited`. Кроме того, в CSS есть ограничение, которое запрещает в целях безопасности применять к `:visited` CSS-стили, изменяющие геометрию элемента. Это гарантирует, что нет обходного пути для "злой" страницы проверить, была ли ссылка посещена и, следовательно, нарушить конфиденциальность. ``` -## Summary +## Итого -To manage classes, there are two DOM properties: +Для управления классами существуют два DOM-свойства: -- `className` -- the string value, good to manage the whole set of classes. -- `classList` -- the object with methods `add/remove/toggle/contains`, good for individual classes. +- `className` -- строковое значение, удобно для управления всем набором классов. +- `classList` - объект с методами `add/remove/toggle/contains`, удобно для управления отдельными классами. -To change the styles: +Чтобы изменить стили: -- The `style` property is an object with camelCased styles. Reading and writing to it has the same meaning as modifying individual properties in the `"style"` attribute. To see how to apply `important` and other rare stuff -- there's a list of methods at [MDN](mdn:api/CSSStyleDeclaration). +- Свойство `style` является объектом со стилями в формате camelCase. Чтение и запись в него работают так же, как изменение соответствующих свойств в атрибуте `"style"`. Чтобы узнать, как добавить в него `important` и делать некоторые другие редкие вещи - смотрите [документацию](mdn:api/CSSStyleDeclaration). -- The `style.cssText` property corresponds to the whole `"style"` attribute, the full string of styles. +- Свойство `style.cssText` соответствует всему атрибуту `"style"`, полной строке стилей. -To read the resolved styles (with respect to all classes, after all CSS is applied and final values are calculated): +Для чтения окончательных стилей (с учётом всех классов, после применения CSS и вычисления окончательных значений) используется: -- The `getComputedStyle(elem[, pseudo])` returns the style-like object with them. Read-only. +- Метод `getComputedStyle(elem, [pseudo])` возвращает объект, похожий по формату на `style`. Только для чтения. diff --git a/2-ui/1-document/09-size-and-scroll/1-get-scroll-height-bottom/solution.md b/2-ui/1-document/09-size-and-scroll/1-get-scroll-height-bottom/solution.md index 01e0736552..177c1ebf56 100644 --- a/2-ui/1-document/09-size-and-scroll/1-get-scroll-height-bottom/solution.md +++ b/2-ui/1-document/09-size-and-scroll/1-get-scroll-height-bottom/solution.md @@ -1,7 +1,7 @@ -The solution is: +Решение: ```js let scrollBottom = elem.scrollHeight - elem.scrollTop - elem.clientHeight; ``` -In other words: (full height) minus (scrolled out top part) minus (visible part) -- that's exactly the scrolled out bottom part. +Другими словами: (вся высота) минус (часть, прокрученная сверху) минус (видимая часть) -- результат в точности соответствует размеру прокрутки снизу. diff --git a/2-ui/1-document/09-size-and-scroll/1-get-scroll-height-bottom/task.md b/2-ui/1-document/09-size-and-scroll/1-get-scroll-height-bottom/task.md index b0f1f22140..9f806ecf6d 100644 --- a/2-ui/1-document/09-size-and-scroll/1-get-scroll-height-bottom/task.md +++ b/2-ui/1-document/09-size-and-scroll/1-get-scroll-height-bottom/task.md @@ -2,10 +2,10 @@ importance: 5 --- -# What's the scroll from the bottom? +# Найти размер прокрутки снизу -The `elem.scrollTop` property is the size of the scrolled out part from the top. How to get "`scrollBottom`" -- the size from the bottom? +Свойство `elem.scrollTop` содержит размер прокрученной области при отсчёте сверху. А как подсчитать размер прокрутки снизу (назовём его `scrollBottom`)? -Write the code that works for an arbitrary `elem`. +Напишите соответствующее выражение для произвольного элемента `elem`. -P.S. Please check your code: if there's no scroll or the element is fully scrolled down, then it should return `0`. +P.S. Проверьте: если прокрутки нет вообще или элемент полностью прокручен – оно должно давать `0`. diff --git a/2-ui/1-document/09-size-and-scroll/2-scrollbar-width/solution.md b/2-ui/1-document/09-size-and-scroll/2-scrollbar-width/solution.md index 1ba1e5e576..e0998aed5c 100644 --- a/2-ui/1-document/09-size-and-scroll/2-scrollbar-width/solution.md +++ b/2-ui/1-document/09-size-and-scroll/2-scrollbar-width/solution.md @@ -1,16 +1,16 @@ -To get the scrollbar width, we can create an element with the scroll, but without borders and paddings. +Чтобы получить ширину полосы прокрутки, создадим элемент с прокруткой, но без рамок и внутренних отступов. -Then the difference between its full width `offsetWidth` and the inner content area width `clientWidth` will be exactly the scrollbar: +Тогда разница между его полной шириной `offsetWidth` и шириной внутреннего содержимого `clientWidth` будет равна как раз прокрутке: ```js run -// create a div with the scroll +// создадим элемент с прокруткой let div = document.createElement('div'); div.style.overflowY = 'scroll'; div.style.width = '50px'; div.style.height = '50px'; -// must put it in the document, otherwise sizes will be 0 +// мы должны вставить элемент в документ, иначе размеры будут равны 0 document.body.append(div); let scrollWidth = div.offsetWidth - div.clientWidth; diff --git a/2-ui/1-document/09-size-and-scroll/2-scrollbar-width/task.md b/2-ui/1-document/09-size-and-scroll/2-scrollbar-width/task.md index 67787894a4..4e0cc417f3 100644 --- a/2-ui/1-document/09-size-and-scroll/2-scrollbar-width/task.md +++ b/2-ui/1-document/09-size-and-scroll/2-scrollbar-width/task.md @@ -2,10 +2,10 @@ importance: 3 --- -# What is the scrollbar width? +# Узнать ширину полосы прокрутки -Write the code that returns the width of a standard scrollbar. +Напишите код, который возвращает ширину стандартной полосы прокрутки. -For Windows it usually varies between `12px` and `20px`. If the browser doesn't reserves any space for it, then it may be `0px`. +Для Windows она обычно колеблется от `12px` до `20px`. Если браузер не выделяет место под полосу прокрутки (так тоже бывает, она может быть прозрачной над текстом), тогда значение может быть `0px`. -P.S. The code should work for any HTML document, do not depend on its content. +P.S. Ваш код должен работать в любом HTML-документе, независимо от его содержимого. diff --git a/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/ball-half/index.html b/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/ball-half/index.html index ca9c4d579b..79d0b1f845 100755 --- a/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/ball-half/index.html +++ b/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/ball-half/index.html @@ -9,7 +9,7 @@ background-color: #00FF00; position: relative; } - + #ball { position: absolute; } @@ -29,8 +29,8 @@ let ball = document.getElementById('ball') let field = document.getElementById('field') - // ball.offsetWidth=0 before image loaded! - // to fix: set width + // ball.offsetWidth=0 до того, как изображение загрузилось! + // исправим это, установив ширину: ball.style.left = Math.round(field.clientWidth / 2) + 'px' ball.style.top = Math.round(field.clientHeight / 2) + 'px' </script> @@ -38,4 +38,4 @@ </body> -</html> \ No newline at end of file +</html> diff --git a/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/field.png b/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/field.png deleted file mode 100644 index 67b9019a04..0000000000 Binary files a/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/field.png and /dev/null differ diff --git a/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/field.svg b/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/field.svg new file mode 100644 index 0000000000..4ae90b1c71 --- /dev/null +++ b/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/field.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="233" height="156" viewBox="0 0 233 156"><defs><style>@import url(https://fonts.googleapis.com/css?family=Open+Sans:bold,italic,bolditalic%7CPT+Mono);@font-face{font-family:'PT Mono';font-weight:700;font-style:normal;src:local('PT MonoBold'),url(/font/PTMonoBold.woff2) format('woff2'),url(/font/PTMonoBold.woff) format('woff'),url(/font/PTMonoBold.ttf) format('truetype')}</style></defs><g id="dom" fill="none" fill-rule="evenodd" stroke="none" stroke-width="1"><g id="field.svg"><image id="Screen-Shot-2017-02-25-at-23.45.22" width="224" height="150" x="4" y="3" opacity=".7" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAcAAAAEsCAYAAABUo2OKAAAABGdBTUEAALGOfPtRkwAAAERlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAABwKADAAQAAAABAAABLAAAAACEEXZQAAAS70lEQVR4Ae3cUU7rTBIGUDxiNzywIdbCKtgFe2ERILEB3uDB08woqLuRYsdxmfL1CbKwk3SlfartLz93NMNYHjceBAgQIEDgYAL/Odj5Ol0CBAgQIPA/AQFoIRAgQIDAIQUE4CHb7qQJECBA4LYnGIahf8oxAQIECBDYvUD/P3nxX4C7b6kTIECAAIElAgJwiZoxBAgQILB7AQG4+xY6AQIECBBYIiAAl6gZQ4AAAQK7FxCAu2+hEyBAgACBJQICcImaMQQIECCwewEBuPsWOgECBAgQWCIgAJeoGUOAAAECuxcQgLtvoRMgQIAAgSUCAnCJmjEECBAgsHsBAbj7FjoBAgQIEFgiIACXqBlDgAABArsXEIC7b6ETIECAAIElAgJwiZoxBAgQILB7AQG4+xY6AQIECBBYIiAAl6gZQ4AAAQK7FxCAu2+hEyBAgACBJQICcImaMQQIECCwewEBuPsWOgECBAgQWCJwu2TQRWPGi97tzQQIECBA4P8CQyyE/wKM9VWdAAECBJIKCMCkjTEtAgQIEIgVEICxvqoTIECAQFIBAZi0MaZFgAABArECAjDWV3UCBAgQSCogAJM2xrQIECBAIFZAAMb6qk6AAAECSQUEYNLGmBYBAgQIxAoIwFhf1QkQIEAgqYAATNoY0yJAgACBWAEBGOurOgECBAgkFRCASRtjWgQIECAQKyAAY31VJ0CAAIGkAgIwaWNMiwABAgRiBQRgrK/qBAgQIJBUQAAmbYxpESBAgECsgACM9VWdAAECBJIKCMCkjTEtAgQIEIgVEICxvqoTIECAQFIBAZi0MaZFgAABArECAjDWV3UCBAgQSCogAJM2xrQIECBAIFZAAMb6qk6AAAECSQVuN53XV/m0p7K9l+2hbPdlu+RhPD/rx/Xj/nHM++clWTH3vWP3KOPGVbex1Dv9PHa1P39eOb3j/G/j297wO79e+letH+unvr+5fvor5PzxX1w/db9W2O/ibtz2T6AvXSy/dcdTh8a3Qvxaj6kj66cVsn5aj6kj66cV2nr9tJ++zlGfiKVq+y3x2uP6O8VzV7t+bc6+8W1v5pjV7+HHr76e67UxZ9/6sX62Xj/1562w3+fd8P1EHaXDMNSH1+831Uu517J9lO1uYWnj+Vk/rh/3j2U30L3dP9eOozbubrYPwGVtM4oAAQIEjiYQHIDb/hvg0ZrnfAkQIEAgrYAATNsaEyNAgACBSAEBGKmrNgECBAikFRCAaVtjYgQIECAQKSAAI3XVJkCAAIG0AgIwbWtMjAABAgQiBQRgpK7aBAgQIJBWQACmbY2JESBAgECkgACM1FWbAAECBNIKCMC0rTExAgQIEIgUEICRumoTIECAQFoBAZi2NSZGgAABApECAjBSV20CBAgQSCsgANO2xsQIECBAIFJAAEbqqk2AAAECaQUEYNrWmBgBAgQIRAoIwEhdtQkQIEAgrYAATNsaEyNAgACBSAEBGKmrNgECBAikFRCAaVtjYgQIECAQKSAAI3XVJkCAAIG0AgIwbWtMjAABAgQiBQRgpK7aBAgQIJBWQACmbY2JESBAgECkgACM1FWbAAECBNIKCMC0rTExAgQIEIgUEICRumoTIECAQFoBAZi2NSZGgAABApECAjBSV20CBAgQSCsgANO2xsQIECBAIFLgNrL4r9pf5Zmnsr2X7aFs92W75GE8P+vH9eP+ccz75yVZMfe9Y/co48ZVt7HUO/08drU/f145veP8b+Pb3vA7v176V60f66e+v7l++ivk/PFfXD91v1bY7+Ju3PZPoC9dLL91x1OHxrdC/FqPqSPrpxWyflqPqSPrpxXaev20n77OUZ+IpWr7LfHa4/o7xXNXu35tzr7xbW/mmNXv4cevvp7rtTFn3/qxfrZeP/XnrbDf593w/UQdpcMw1IfX7zfVS7nXsn2U7W5haeP5WT+uH/ePZTfQvd0/146jNu5utg/AZW0zigABAgSOJhAcgNv+G+DRmud8CRAgQCCtgABM2xoTI0CAAIFIAQEYqas2AQIECKQVEIBpW2NiBAgQIBApIAAjddUmQIAAgbQCAjBta0yMAAECBCIFBGCkrtoECBAgkFZAAKZtjYkRIECAQKSAAIzUVZsAAQIE0goIwLStMTECBAgQiBQQgJG6ahMgQIBAWgEBmLY1JkaAAAECkQICMFJXbQIECBBIKyAA07bGxAgQIEAgUkAARuqqTYAAAQJpBQRg2taYGAECBAhECgjASF21CRAgQCCtgABM2xoTI0CAAIFIAQEYqas2AQIECKQVEIBpW2NiBAgQIBApIAAjddUmQIAAgbQCAjBta0yMAAECBCIFBGCkrtoECBAgkFZAAKZtjYkRIECAQKSAAIzUVZsAAQIE0goIwLStMTECBAgQiBQQgJG6ahMgQIBAWgEBmLY1JkaAAAECkQICMFJXbQIECBBIKyAA07bGxAgQIEAgUuA2sviv2l/lmaeyvZftoWz3ZbvkYTw/68f14/5xzPvnJVkx971j9yjjxlW3sdQ7/Tx2tT9/Xjm94/xv49ve8Du/XvpXrR/rp76/uX76K+T88V9cP3W/Vtjv4m7c9k+gL10sv3XHU4fGt0L8Wo+pI+unFbJ+Wo+pI+unFdp6/bSfvs5Rn4ilavst8drj+jvFc1e7fm3OvvFtb+aY1e/hx6++nuu1MWff+rF+tl4/9eetsN/n3fD9RB2lwzDUh9fvN9VLudeyfZTtbmFp4/lZP64f949lN9C93T/XjqM27m62D8BlbTOKAAECBI4mEByA2/4b4NGa53wJECBAIK2AAEzbGhMjQIAAgUgBARipqzYBAgQIpBUQgGlbY2IECBAgECkgACN11SZAgACBtAICMG1rTIwAAQIEIgUEYKSu2gQIECCQVkAApm2NiREgQIBApIAAjNRVmwABAgTSCgjAtK0xMQIECBCIFBCAkbpqEyBAgEBaAQGYtjUmRoAAAQKRAgIwUldtAgQIEEgrIADTtsbECBAgQCBSQABG6qpNgAABAmkFBGDa1pgYAQIECEQKCMBIXbUJECBAIK2AAEzbGhMjQIAAgUgBARipqzYBAgQIpBUQgGlbY2IECBAgECkgACN11SZAgACBtAICMG1rTIwAAQIEIgUEYKSu2gQIECCQVkAApm2NiREgQIBApIAAjNRVmwABAgTSCgjAtK0xMQIECBCIFBCAkbpqEyBAgEBaAQGYtjUmRoAAAQKRAgIwUldtAgQIEEgrIADTtsbECBAgQCBS4Day+K/aX+WZp7K9l+2hbPdlu+RhPD/rx/Xj/nHM++clWTH3vWP3KOPGVbex1Dv9PHa1P39eOb3j/G/j297wO79e+letH+unvr+5fvor5PzxX1w/db9W2O/ibtz2T6AvXSy/dcdTh8a3Qvxaj6kj66cVsn5aj6kj66cV2nr9tJ++zlGfiKVq+y3x2uP6O8VzV7t+bc6+8W1v5pjV7+HHr76e67UxZ9/6sX62Xj/1562w3+fd8P1EHaXDMNSH1+831Uu517J9lO1uYWnj+Vk/rh/3j2U30L3dP9eOozbubrYPwGVtM4oAAQIEjiYQHIDb/hvg0ZrnfAkQIEAgrYAATNsaEyNAgACBSAEBGKmrNgECBAikFRCAaVtjYgQIECAQKSAAI3XVJkCAAIG0AgIwbWtMjAABAgQiBQRgpK7aBAgQIJBWQACmbY2JESBAgECkgACM1FWbAAECBNIKCMC0rTExAgQIEIgUEICRumoTIECAQFoBAZi2NSZGgAABApECAjBSV20CBAgQSCsgANO2xsQIECBAIFJAAEbqqk2AAAECaQUEYNrWmBgBAgQIRAoIwEhdtQkQIEAgrYAATNsaEyNAgACBSAEBGKmrNgECBAikFRCAaVtjYgQIECAQKSAAI3XVJkCAAIG0AgIwbWtMjAABAgQiBQRgpK7aBAgQIJBWQACmbY2JESBAgECkgACM1FWbAAECBNIKCMC0rTExAgQIEIgUEICRumoTIECAQFoBAZi2NSZGgAABApECAjBSV20CBAgQSCsgANO2xsQIECBAIFLgNrL4r9pf5Zmnsr2X7aFs92W75GE8P+vH9eP+ccz75yVZMfe9Y/co48ZVt7HUO/08drU/f145veP8b+Pb3vA7v176V60f66e+v7l++ivk/PFfXD91v1bY7+Ju3PZPoC9dLL91x1OHxrdC/FqPqSPrpxWyflqPqSPrpxXaev20n77OUZ+IpWr7LfHa4/o7xXNXu35tzr7xbW/mmNXv4cevvp7rtTFn3/qxfrZeP/XnrbDf593w/UQdpcMw1IfX7zfVS7nXsn2U7W5haeP5WT+uH/ePZTfQvd0/146jNu5utg/AZW0zigABAgSOJhAcgNv+G+DRmud8CRAgQCCtgABM2xoTI0CAAIFIAQEYqas2AQIECKQVEIBpW2NiBAgQIBApIAAjddUmQIAAgbQCAjBta0yMAAECBCIFBGCkrtoECBAgkFZAAKZtjYkRIECAQKSAAIzUVZsAAQIE0goIwLStMTECBAgQiBQQgJG6ahMgQIBAWgEBmLY1JkaAAAECkQICMFJXbQIECBBIKyAA07bGxAgQIEAgUkAARuqqTYAAAQJpBQRg2taYGAECBAhECgjASF21CRAgQCCtgABM2xoTI0CAAIFIAQEYqas2AQIECKQVEIBpW2NiBAgQIBApIAAjddUmQIAAgbQCAjBta0yMAAECBCIFBGCkrtoECBAgkFZAAKZtjYkRIECAQKSAAIzUVZsAAQIE0goIwLStMTECBAgQiBQQgJG6ahMgQIBAWgEBmLY1JkaAAAECkQICMFJXbQIECBBIKyAA07bGxAgQIEAgUuA2sviv2l/lmaeyvZftoWz3ZbvkYTw/68f14/5xzPvnJVkx971j9yjjxlW3sdQ7/Tx2tT9/Xjm94/xv49ve8Du/XvpXrR/rp76/uX76K+T88V9cP3W/Vtjv4m7c9k+gL10sv3XHU4fGt0L8Wo+pI+unFbJ+Wo+pI+unFdp6/bSfvs5Rn4ilavst8drj+jvFc1e7fm3OvvFtb+aY1e/hx6++nuu1MWff+rF+tl4/9eetsN/n3fD9RB2lwzDUh9fvN9VLudeyfZTtbmFp4/lZP64f949lN9C93T/XjqM27m62D8BlbTOKAAECBI4mEByA2/4b4NGa53wJECBAIK2AAEzbGhMjQIAAgUgBARipqzYBAgQIpBUQgGlbY2IECBAgECkgACN11SZAgACBtAICMG1rTIwAAQIEIgUEYKSu2gQIECCQVkAApm2NiREgQIBApIAAjNRVmwABAgTSCgjAtK0xMQIECBCIFBCAkbpqEyBAgEBaAQGYtjUmRoAAAQKRAgIwUldtAgQIEEgrIADTtsbECBAgQCBSQABG6qpNgAABAmkFBGDa1pgYAQIECEQKCMBIXbUJECBAIK2AAEzbGhMjQIAAgUgBARipqzYBAgQIpBUQgGlbY2IECBAgECkgACN11SZAgACBtAICMG1rTIwAAQIEIgUEYKSu2gQIECCQVkAApm2NiREgQIBApIAAjNRVmwABAgTSCgjAtK0xMQIECBCIFBCAkbpqEyBAgEBaAQGYtjUmRoAAAQKRAgIwUldtAgQIEEgrIADTtsbECBAgQCBS4Day+K/aX+WZp7K9l+2hbPdlu+RhPD/rx/Xj/nHM++clWTH3vWP3KOPGVbex1Dv9PHa1P39eOb3j/G/j297wO79e+letH+unvr+5fvor5PzxX1w/db9W2O/ibtz2T6AvXSy/dcdTh8a3Qvxaj6kj66cVsn5aj6kj66cV2nr9tJ++zlGfiKVq+y3x2uP6O8VzV7t+bc6+8W1v5pjV7+HHr76e67UxZ9/6sX62Xj/1562w3+fd8P1EHaXDMNSH1+831Uu517J9lO1uYWnj+Vk/rh/3j2U30L3dP9eOozbubrYPwGVtM4oAAQIEjiYQHIDb/hvg0ZrnfAkQIEAgrYAATNsaEyNAgACBSAEBGKmrNgECBAikFRCAaVtjYgQIECAQKSAAI3XVJkCAAIG0AgIwbWtMjAABAgQiBeL/v0BX/p+xRmKoTYAAAQLHEfBfgMfptTMlQIAAgUpAAFYYdgkQIEDgOAIC8Di9dqYECBAgUAkIwArDLgECBAgcR0AAHqfXzpQAAQIEKgEBWGHYJUCAAIHjCAjA4/TamRIgQIBAJSAAKwy7BAgQIHAcAQF4nF47UwIECBCoBARghWGXAAECBI4jIACP02tnSoAAAQKVgACsMOwSIECAwHEEBOBxeu1MCRAgQKASEIAVhl0CBAgQOI6AADxOr50pAQIECFQCArDCsEuAAAECxxEQgMfptTMlQIAAgUpAAFYYdgkQIEDgOAK3/akOw9A/5ZgAAQIECPxzAsNYHv/cWTkhAgQIECAwIeBPoBNAXiZAgACBf1NAAP6bfXVWBAgQIDAhIAAngLxMgAABAv+mwH8BIoXpK3F6REIAAAAASUVORK5CYII="/><text id="(0,0)" fill="#A7333A" font-family="PTMono-Bold, PT Mono" font-size="14" font-weight="bold"><tspan x="45" y="60">(0,0)</tspan></text><circle id="Oval" cx="15.5" cy="15.5" r="4.5" fill="#C06334"/><text id="clientWidth" fill="#A7333A" font-family="PTMono-Bold, PT Mono" font-size="14" font-weight="bold"><tspan x="73" y="114">clientWidth</tspan></text><path id="Line-9" fill="#C06334" fill-rule="nonzero" d="M24.114 22.183l20.711 4.719-5.078 6.181 13.705 11.258 1.16.952-1.905 2.318-1.16-.952-13.704-11.258-5.078 6.183-8.651-19.401z"/><path id="Line-10" fill="#C06334" fill-rule="nonzero" d="M197 118l19 9.5-19 9.5v-8H34v8l-19-9.5 19-9.5v8h163v-8z"/></g></g></svg> \ No newline at end of file diff --git a/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/field@2x.png b/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/field@2x.png deleted file mode 100644 index fe58f35427..0000000000 Binary files a/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/field@2x.png and /dev/null differ diff --git a/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/solution.md b/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/solution.md index 69dec23ce8..12cf07d69e 100644 --- a/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/solution.md +++ b/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/solution.md @@ -1,50 +1,48 @@ -The ball has `position:absolute`. It means that its `left/top` coordinates are measured from the nearest positioned element, that is `#field` (because it has `position:relative`). +Мяч имеет CSS-свойство `position:absolute`. Это означает, что координаты `left/top` измеряются относительно ближайшего спозиционированного элемента, которым является `#field` (т.к. у него есть CSS-свойство `position:relative`). -The coordinates start from the inner left-upper corner of the field: +Координаты отсчитываются от внутреннего верхнего левого угла поля: -![](field.png) +![](field.svg) -The inner field width/height is `clientWidth/clientHeight`. So the field center has coordinates `(clientWidth/2, clientHeight/2)`. +Ширина и высота внутреннего поля -- это `clientWidth/clientHeight`. Таким образом, его центр имеет координаты `(clientWidth/2, clientHeight/2)`. -...But if we set `ball.style.left/top` to such values, then not the ball as a whole, but the left-upper edge of the ball would be in the center: +...Но если мы установим мячу такие значения `ball.style.left/top`, то в центре будет не сам мяч, а его левый верхний угол: ```js ball.style.left = Math.round(field.clientWidth / 2) + 'px'; ball.style.top = Math.round(field.clientHeight / 2) + 'px'; ``` -Here's how it looks: +Вот как это выглядит: [iframe height=180 src="ball-half"] -To align the ball center with the center of the field, we should move the ball to the half of its width to the left and to the half of its height to the top: +Для того, чтобы центр мяча находился в центре поля, нам нужно сместить мяч на половину его ширины влево и на половину его высоты вверх: ```js ball.style.left = Math.round(field.clientWidth / 2 - ball.offsetWidth / 2) + 'px'; ball.style.top = Math.round(field.clientHeight / 2 - ball.offsetHeight / 2) + 'px'; ``` -**Attention: the pitfall!** +**Внимание, подводный камень!** -The code won't work reliably while `<img>` has no width/height: +Код выше стабильно работать не будет, потому что `<img>` идёт без ширины/высоты: ```html <img src="ball.png" id="ball"> ``` -When the browser does not know the width/height of an image (from tag attributes or CSS), then it assumes them to equal `0` until the image finishes loading. +Если браузеру неизвестны ширина и высота изображения (из атрибута HTML-тега или CSS-свойств), он считает их равными `0` до тех пор, пока изображение не загрузится. -In real life after the first load browser usually caches the image, and on next loads it will have the size immediately. +При первой загрузке браузер обычно кеширует изображения, так что при последующей загрузке оно будет доступно тут же, вместе с размерами. Но при первой загрузке значение ширины мяча `ball.offsetWidth` равно `0`. Это приводит к вычислению неверных координат. -But on the first load the value of `ball.offsetWidth` is `0`. That leads to wrong coordinates. - -We should fix that by adding `width/height` to `<img>`: +Мы можем исправить это, добавив атрибуты `width/height` тегу `<img>`: ```html <img src="ball.png" *!*width="40" height="40"*/!* id="ball"> ``` -...Or provide the size in CSS: +...Или задав размеры в CSS: ```css #ball { diff --git a/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/solution.view/index.html b/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/solution.view/index.html index 9ebe6001e7..c7712b25f8 100755 --- a/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/solution.view/index.html +++ b/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/solution.view/index.html @@ -26,8 +26,8 @@ <script> - // ball.offsetWidth=0 before image loaded! - // to fix: set width + // ball.offsetWidth=0 до того, как изображение загрузилось! + // исправим это, установив ширину: ball.style.left = Math.round(field.clientWidth / 2 - ball.offsetWidth / 2) + 'px' ball.style.top = Math.round(field.clientHeight / 2 - ball.offsetHeight / 2) + 'px' </script> diff --git a/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/task.md b/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/task.md index 57746d9217..685edda8a3 100644 --- a/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/task.md +++ b/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/task.md @@ -2,19 +2,19 @@ importance: 5 --- -# Place the ball in the field center +# Поместите мяч в центр поля -Here's how the source document looks: +Исходный документ выглядит так: [iframe src="source" edit link height=180] -What are coordinates of the field center? +Каковы координаты центра поля? -Calculate them and use to place the ball into the center of the field: +Вычислите их и используйте, чтобы поместить мяч в центр поля: [iframe src="solution" height=180] -- The element should be moved by JavaScript, not CSS. -- The code should work with any ball size (`10`, `20`, `30` pixels) and any field size, not be bound to the given values. +- Элемент должен позиционироваться за счёт JavaScript, а не CSS. +- Код должен работать с любым размером мяча (`10`, `20`, `30` пикселей) и любым размером поля без привязки к исходным значениям. -P.S. Sure, centering could be done with CSS, but here we want exactly JavaScript. Further we'll meet other topics and more complex situations when JavaScript must be used. Here we do a "warm-up". +P.S. Да, центрирование можно сделать при помощи чистого CSS, но задача именно на JavaScript. Далее будут другие темы и более сложные ситуации, когда JavaScript будет уже точно необходим, это – своего рода "разминка". diff --git a/2-ui/1-document/09-size-and-scroll/6-width-vs-clientwidth/solution.md b/2-ui/1-document/09-size-and-scroll/6-width-vs-clientwidth/solution.md index 0a18bd86e1..756a1f1a21 100644 --- a/2-ui/1-document/09-size-and-scroll/6-width-vs-clientwidth/solution.md +++ b/2-ui/1-document/09-size-and-scroll/6-width-vs-clientwidth/solution.md @@ -1,6 +1,6 @@ -Differences: +Отличия: -1. `clientWidth` is numeric, while `getComputedStyle(elem).width` returns a string with `px` at the end. -2. `getComputedStyle` may return non-numeric width like `"auto"` for an inline element. -3. `clientWidth` is the inner content area of the element plus paddings, while CSS width (with standard `box-sizing`) is the inner content area *without paddings*. -4. If there's a scrollbar and the browser reserves the space for it, some browser substract that space from CSS width (cause it's not available for content any more), and some do not. The `clientWidth` property is always the same: scrollbar size is substracted if reserved. +1. `clientWidth` возвращает число, а `getComputedStyle(elem).width` - строку с `px` на конце. +2. `getComputedStyle` не всегда даст ширину, он может вернуть, к примеру, `"auto"` для строчного элемента. +3. `clientWidth` соответствует внутренней области элемента, включая внутренние отступы `padding`, а CSS-ширина (при стандартном значении `box-sizing`) соответствует внутренней области *без внутренних отступов* `padding`. +4. Если есть полоса прокрутки, и для неё зарезервировано место, то некоторые браузеры вычитают его из CSS-ширины (т.к. оно больше недоступно для содержимого), а некоторые - нет. Свойство `clientWidth` всегда ведёт себя одинаково: оно всегда обозначает размер за вычетом прокрутки, т.е. реально доступный для содержимого. diff --git a/2-ui/1-document/09-size-and-scroll/6-width-vs-clientwidth/task.md b/2-ui/1-document/09-size-and-scroll/6-width-vs-clientwidth/task.md index 5378c5915b..8176aa5742 100644 --- a/2-ui/1-document/09-size-and-scroll/6-width-vs-clientwidth/task.md +++ b/2-ui/1-document/09-size-and-scroll/6-width-vs-clientwidth/task.md @@ -2,8 +2,8 @@ importance: 5 --- -# The difference: CSS width versus clientWidth +# В чём отличие CSS-свойств width и clientWidth -What's the difference between `getComputedStyle(elem).width` and `elem.clientWidth`? +В чём отличие между `getComputedStyle(elem).width` и `elem.clientWidth`? -Give at least 3 differences. The more the better. +Укажите хотя бы 3 отличия, лучше - больше. diff --git a/2-ui/1-document/09-size-and-scroll/article.md b/2-ui/1-document/09-size-and-scroll/article.md index 774857f3ff..211e54b708 100644 --- a/2-ui/1-document/09-size-and-scroll/article.md +++ b/2-ui/1-document/09-size-and-scroll/article.md @@ -1,72 +1,71 @@ -# Element size and scrolling +# Размеры и прокрутка элементов -There are many JavaScript properties that allow us to read information about element width, height and other geometry features. +Существует множество JavaScript-свойств, которые позволяют считывать информацию об элементе: ширину, высоту и другие геометрические характеристики. В этой главе мы будем называть их "метрики". -We often need them when moving or positioning elements in JavaScript, to correctly calculate coordinates. +Они часто требуются, когда нам нужно передвигать или позиционировать элементы с помощью JavaScript. +## Простой пример -## Sample element - -As a sample element to demonstrate properties we'll use the one given below: +В качестве простого примера демонстрации свойств мы будем использовать следующий элемент: ```html no-beautify <div id="example"> - ...Text... + ...Текст... </div> <style> #example { width: 300px; height: 200px; border: 25px solid #E8C48F; - padding: 20px; - overflow: auto; + padding: 20px; + overflow: auto; } </style> ``` -It has the border, padding and scrolling. The full set of features. There are no margins, as they are not the part of the element itself, and there are no special properties for them. +У элемента есть рамка (border), внутренний отступ (padding) и прокрутка. Полный набор характеристик. Обратите внимание, тут нет внешних отступов (margin), потому что они не являются частью элемента, для них нет особых JavaScript-свойств. -The element looks like this: +Результат выглядит так: -![](metric-css.png) +![](metric-css.svg) -You can [open the document in the sandbox](sandbox:metric). +Вы можете [открыть этот пример в песочнице](sandbox:metric). -```smart header="Mind the scrollbar" -The picture above demonstrates the most complex case when the element has a scrollbar. Some browsers (not all) reserve the space for it by taking it from the content. +```smart header="Внимание, полоса прокрутки" +В иллюстрации выше намеренно продемонстрирован самый сложный и полный случай, когда у элемента есть ещё и полоса прокрутки. Некоторые браузеры (не все) отбирают место для неё, забирая его у области, отведённой для содержимого (помечена как "content width" выше). -So, without scrollbar the content width would be `300px`, but if the scrollbar is `16px` wide (the width may vary between devices and browsers) then only `300 - 16 = 284px` remains, and we should take it into account. That's why examples from this chapter assume that there's a scrollbar. If there's no scrollbar, then things are just a bit simpler. +Таким образом, без учёта полосы прокрутки ширина области содержимого (content width) будет `300px`, но если предположить, что ширина полосы прокрутки равна `16px` (её точное значение зависит от устройства и браузера), тогда остаётся только `300 - 16 = 284px`, и мы должны это учитывать. Вот почему примеры в этой главе даны с полосой прокрутки. Без неё некоторые вычисления будут проще. ``` -```smart header="The `padding-bottom` may be filled with text" -Usually paddings are shown empty on illustrations, but if there's a lot of text in the element and it overflows, then browsers show the "overflowing" text at `padding-bottom`, so you can see that in examples. But the padding is still there, unless specified otherwise. +```smart header="Область `padding-bottom` (нижний внутренний отступ) может быть заполнена текстом" +Нижние внутренние отступы `padding-bottom` изображены пустыми на наших иллюстрациях, но если элемент содержит много текста, то он будет перекрывать `padding-bottom`, это нормально. ``` -## Geometry - -Element properties that provide width, height and other geometry are always numbers. They are assumed to be in pixels. +## Метрики -Here's the overall picture: +Вот общая картина с геометрическими свойствами: -![](metric-all.png) +![](metric-all.svg) -They are many properties, it's difficult to fit them all in the single picture, but their values are simple and easy to understand. +Значениями свойств являются числа, подразумевается, что они в пикселях. -Let's start exploring them from the outside of the element. +Давайте начнём исследовать, начиная снаружи элемента. ## offsetParent, offsetLeft/Top -These properties are rarely needed, but still they are the "most outer" geometry properties, so we'll start with them. +Эти свойства редко используются, но так как они являются "самыми внешними" метриками, мы начнём с них. + +В свойстве `offsetParent` находится предок элемента, который используется внутри браузера для вычисления координат при рендеринге. -The `offsetParent` is the nearest ancestor that is: +То есть, ближайший предок, который удовлетворяет следующим условиям: -1. CSS-positioned (`position` is `absolute`, `relative`, `fixed` or `sticky`), -2. or `<td>`, `<th>`, `<table>`, -2. or `<body>`. +1. Является CSS-позиционированным (CSS-свойство `position` равно `absolute`, `relative`, `fixed` или `sticky`), +2. или `<td>`, `<th>`, `<table>`, +3. или `<body>`. -In most practical cases we can use `offsetParent` to get the nearest CSS-positioned ancestor. And `offsetLeft/offsetTop` provide x/y coordinates relative to its upper-left corner. +Свойства `offsetLeft/offsetTop` содержат координаты x/y относительно верхнего левого угла `offsetParent`. -In the example below the inner `<div>` has `<main>` as `offsetParent` and `offsetLeft/offsetTop` shifts from its upper-left corner (`180`): +В примере ниже внутренний `<div>` имеет элемент `<main>` в качестве `offsetParent`, а свойства `offsetLeft/offsetTop` являются сдвигами относительно верхнего левого угла (`180`): ```html run height=10 <main style="position: relative" id="main"> @@ -76,41 +75,40 @@ In the example below the inner `<div>` has `<main>` as `offsetParent` and `offse </main> <script> alert(example.offsetParent.id); // main - alert(example.offsetLeft); // 180 (note: a number, not a string "180px") + alert(example.offsetLeft); // 180 (обратите внимание: число, а не строка "180px") alert(example.offsetTop); // 180 </script> ``` -![](metric-offset-parent.png) +![](metric-offset-parent.svg) +Существует несколько ситуаций, когда `offsetParent` равно `null`: -There are several occasions when `offsetParent` is `null`: - -1. For not shown elements (`display:none` or not in the document). -2. For `<body>` and `<html>`. -3. For elements with `position:fixed`. +1. Для скрытых элементов (с CSS-свойством `display:none` или когда его нет в документе). +2. Для элементов `<body>` и `<html>`. +3. Для элементов с `position:fixed`. ## offsetWidth/Height -Now let's move on to the element itself. +Теперь переходим к самому элементу. -These two properties are the simplest ones. They provide the "outer" width/height of the element. Or, in other words, its full size including borders. +Эти два свойства – самые простые. Они содержат "внешнюю" ширину/высоту элемента, то есть его полный размер, включая рамки. -![](metric-offset-width-height.png) +![](metric-offset-width-height.svg) -For our sample element: +Для нашего элемента: -- `offsetWidth = 390` -- the outer width, can be calculated as inner CSS-width (`300px`) plus paddings (`2 * 20px`) and borders (`2 * 25px`). -- `offsetHeight = 290` -- the outer height. +- `offsetWidth = 390` -- внешняя ширина блока, её можно получить сложением CSS-ширины (`300px`), внутренних отступов (`2 * 20px`) и рамок (`2 * 25px`). +- `offsetHeight = 290` -- внешняя высота блока. -````smart header="Geometry properties for not shown elements are zero/null" -Geometry properties are calculated only for shown elements. +````smart header="Метрики для не показываемых элементов равны нулю." +Координаты и размеры в JavaScript устанавливаются только для видимых элементов. -If an element (or any of its ancestors) has `display:none` or is not in the document, then all geometry properties are zero or `null` depending on what it is. +Если элемент (или любой его родитель) имеет `display:none` или отсутствует в документе, то все его метрики равны нулю (или `null`, если это `offsetParent`). -For example, `offsetParent` is `null`, and `offsetWidth`, `offsetHeight` are `0`. +Например, свойство `offsetParent` равно `null`, а `offsetWidth` и `offsetHeight` равны `0`, когда мы создали элемент, но ещё не вставили его в документ, или если у элемента (или у его родителя) `display:none`. -We can use this to check if an element is hidden, like this: +Мы можем использовать это, чтобы делать проверку на видимость: ```js function isHidden(elem) { @@ -118,122 +116,125 @@ function isHidden(elem) { } ``` -Please note that such `isHidden` returns `true` for elements that are on-screen, but have zero sizes (like an empty `<div>`). +Заметим, что функция `isHidden` также вернёт `true` для элементов, которые в принципе показываются, но их размеры равны нулю. ```` ## clientTop/Left -Inside the element we have the borders. +Пойдём дальше. Внутри элемента у нас рамки (border). + +Для них есть свойства-метрики `clientTop` и `clientLeft`. -To measure them, there are properties `clientTop` and `clientLeft`. +В нашем примере: -In our example: +- `clientLeft = 25` -- ширина левой рамки +- `clientTop = 25` -- ширина верхней рамки -- `clientLeft = 25` -- left border width -- `clientTop = 25` -- top border width +![](metric-client-left-top.svg) -![](metric-client-left-top.png) +...Но на самом деле эти свойства - вовсе не ширины рамок, а отступы внутренней части элемента от внешней. -...But to be precise -- they are not borders, but relative coordinates of the inner side from the outer side. +В чём же разница? -What's the difference? +Она возникает, когда документ располагается справа налево (операционная система на арабском языке или иврите). Полоса прокрутки в этом случае находится слева, и тогда свойство `clientLeft` включает в себя ещё и ширину полосы прокрутки. -It becomes visible when the document is right-to-left (the operating system is in Arabic or Hebrew languages). The scrollbar is then not on the right, but on the left, and then `clientLeft` also includes the scrollbar width. +В этом случае `clientLeft` будет равно `25`, но с прокруткой -- `25 + 16 = 41`. -In that case, `clientLeft` would be not `25`, but with the scrollbar width `25 + 16 = 41`: +Вот соответствующий пример на иврите: -![](metric-client-left-top-rtl.png) +![](metric-client-left-top-rtl.svg) ## clientWidth/Height -These properties provide the size of the area inside the element borders. +Эти свойства -- размер области внутри рамок элемента. + +Они включают в себя ширину области содержимого вместе с внутренними отступами `padding`, но без прокрутки: -They include the content width together with paddings, but without the scrollbar: +![](metric-client-width-height.svg) -![](metric-client-width-height.png) +На рисунке выше посмотрим вначале на высоту `clientHeight`. -On the picture above let's first consider `clientHeight`: it's easier to evaluate. There's no horizontal scrollbar, so it's exactly the sum of what's inside the borders: CSS-height `200px` plus top and bottom paddings (`2 * 20px`) total `240px`. +Горизонтальной прокрутки нет, так что это в точности то, что внутри рамок: CSS-высота `200px` плюс верхние и нижние внутренние отступы (`2 * 20px`), итого `240px`. -Now `clientWidth` -- here the content width is not `300px`, but `284px`, because `16px` are occupied by the scrollbar. So the sum is `284px` plus left and right paddings, total `324px`. +Теперь `clientWidth` -- ширина содержимого здесь равна не `300px`, а `284px`, т.к. `16px` отведено для полосы прокрутки. Таким образом: `284px` плюс левый и правый отступы -- всего `324px`. -**If there are no paddings, then `clientWidth/Height` is exactly the content area, inside the borders and the scrollbar (if any).** +**Если нет внутренних отступов `padding`, то `clientWidth/Height` в точности равны размеру области содержимого внутри рамок за вычетом полосы прокрутки (если она есть).** -![](metric-client-width-nopadding.png) +![](metric-client-width-nopadding.svg) -So when there's no padding we can use `clientWidth/clientHeight` to get the content area size. +Поэтому в тех случаях, когда мы точно знаем, что отступов нет, можно использовать `clientWidth/clientHeight` для получения размеров внутренней области содержимого. ## scrollWidth/Height -- Properties `clientWidth/clientHeight` only account for the visible part of the element. -- Properties `scrollWidth/scrollHeight` also include the scrolled out (hidden) parts: +Эти свойства - как `clientWidth/clientHeight`, но также включают в себя прокрученную (которую не видно) часть элемента. -![](metric-scroll-width-height.png) +![](metric-scroll-width-height.svg) -On the picture above: +На рисунке выше: -- `scrollHeight = 723` -- is the full inner height of the content area including the scrolled out parts. -- `scrollWidth = 324` -- is the full inner width, here we have no horizontal scroll, so it equals `clientWidth`. +- `scrollHeight = 723` -- полная внутренняя высота, включая прокрученную область. +- `scrollWidth = 324` -- полная внутренняя ширина, в данном случае прокрутки нет, поэтому она равна `clientWidth`. -We can use these properties to expand the element wide to its full width/height. +Эти свойства можно использовать, чтобы "распахнуть" элемент на всю ширину/высоту. -Like this: +Таким кодом: ```js -// expand the element to the full content height +// распахнуть элемент на всю высоту element.style.height = `${element.scrollHeight}px`; ``` ```online -Click the button to expand the element: +Нажмите на кнопку, чтобы распахнуть элемент: -<div id="element" style="width:300px;height:200px; padding: 0;overflow: auto; border:1px solid black;">text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text</div> +<div id="element" style="width:300px;height:200px; padding: 0;overflow: auto; border:1px solid black;">текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст</div> <button style="padding:0" onclick="element.style.height = `${element.scrollHeight}px`">element.style.height = `${element.scrollHeight}px`</button> ``` ## scrollLeft/scrollTop -Properties `scrollLeft/scrollTop` are the width/height of the hidden, scrolled out part of the element. +Свойства `scrollLeft/scrollTop` -- ширина/высота невидимой, прокрученной в данный момент, части элемента слева и сверху. -On the picture below we can see `scrollHeight` and `scrollTop` for a block with a vertical scroll. +Следующая иллюстрация показывает значения `scrollHeight` и `scrollTop` для блока с вертикальной прокруткой. -![](metric-scroll-top.png) +![](metric-scroll-top.svg) -In other words, `scrollTop` is "how much is scrolled up". +Другими словами, свойство `scrollTop` -- это "сколько уже прокручено вверх". -````smart header="`scrollLeft/scrollTop` can be modified" -Most of the geometry properties here are read-only, but `scrollLeft/scrollTop` can be changed, and the browser will scroll the element. +````smart header="`Свойства scrollLeft/scrollTop` можно изменять" +В отличие от большинства свойств, которые доступны только для чтения, значения `scrollLeft/scrollTop` можно изменять, и браузер выполнит прокрутку элемента. ```online -If you click the element below, the code `elem.scrollTop += 10` executes. That makes the element content scroll `10px` down. +При клике на следующий элемент будет выполняться код `elem.scrollTop += 10`. Поэтому он будет прокручиваться на `10px` вниз. -<div onclick="this.scrollTop+=10" style="cursor:pointer;border:1px solid black;width:100px;height:80px;overflow:auto">Click<br>Me<br>1<br>2<br>3<br>4<br>5<br>6<br>7<br>8<br>9</div> +<div onclick="this.scrollTop+=10" style="cursor:pointer;border:1px solid black;width:100px;height:80px;overflow:auto">Кликни<br>Меня<br>1<br>2<br>3<br>4<br>5<br>6<br>7<br>8<br>9</div> ``` -Setting `scrollTop` to `0` or `Infinity` will make the element scroll to the very top/bottom respectively. +Установка значения `scrollTop` на `0` или на большое значение, такое как `1e9`, прокрутит элемент в самый верх/низ соответственно. ```` -## Don't take width/height from CSS +## Не стоит брать width/height из CSS -We've just covered geometry properties of DOM elements. They are normally used to get widths, heights and calculate distances. +Мы рассмотрели метрики, которые есть у DOM-элементов, и которые можно использовать для получения различных высот, ширин и прочих расстояний. -But as we know from the chapter <info:styles-and-classes>, we can read CSS-height and width using `getComputedStyle`. +Но как мы знаем из главы <info:styles-and-classes>, CSS-высоту и ширину можно извлечь, используя `getComputedStyle`. -So why not to read the width of an element like this? +Так почему бы не получать, к примеру, ширину элемента при помощи `getComputedStyle`, вот так? ```js run let elem = document.body; -alert( getComputedStyle(elem).width ); // show CSS width for elem +alert( getComputedStyle(elem).width ); // показывает CSS-ширину elem ``` -Why should we use geometry properties instead? There are two reasons: +Почему мы должны использовать свойства-метрики вместо этого? На то есть две причины: -1. First, CSS width/height depend on another property: `box-sizing` that defines "what is" CSS width and height. A change in `box-sizing` for CSS purposes may break such JavaScript. -2. Second, CSS `width/height` may be `auto`, for instance for an inline element: +1. Во-первых, CSS-свойства `width/height` зависят от другого свойства -- `box-sizing`, которое определяет, "что такое", собственно, эти CSS-ширина и высота. Получается, что изменение `box-sizing`, к примеру, для более удобной вёрстки, сломает такой JavaScript. +2. Во-вторых, CSS свойства `width/height` могут быть равны `auto`, например, для инлайнового элемента: ```html run - <span id="elem">Hello!</span> + <span id="elem">Привет!</span> <script> *!* @@ -242,34 +243,34 @@ Why should we use geometry properties instead? There are two reasons: </script> ``` - From the CSS standpoint, `width:auto` is perfectly normal, but in JavaScript we need an exact size in `px` that we can use in calculations. So here CSS width is useless at all. + Конечно, с точки зрения CSS `width:auto` – совершенно нормально, но нам-то в JavaScript нужен конкретный размер в `px`, который мы могли бы использовать для вычислений. Получается, что в данном случае ширина из CSS вообще бесполезна. -And there's one more reason: a scrollbar. Sometimes the code that works fine without a scrollbar starts to bug with it, because a scrollbar takes the space from the content in some browsers. So the real width available for the content is *less* than CSS width. And `clientWidth/clientHeight` take that into account. +Есть и ещё одна причина: полоса прокрутки. Бывает, без полосы прокрутки код работает прекрасно, но стоит ей появиться, как начинают проявляться баги. Так происходит потому, что полоса прокрутки "отъедает" место от области внутреннего содержимого в некоторых браузерах. Таким образом, реальная ширина содержимого *меньше* CSS-ширины. Как раз это и учитывают свойства `clientWidth/clientHeight`. -...But with `getComputedStyle(elem).width` the situation is different. Some browsers (e.g. Chrome) return the real inner width, minus the scrollbar, and some of them (e.g. Firefox) -- CSS width (ignore the scrollbar). Such cross-browser differences is the reason not to use `getComputedStyle`, but rather rely on geometry properties. +...Но с `getComputedStyle(elem).width` ситуация иная. Некоторые браузеры (например, Chrome) возвращают реальную внутреннюю ширину с вычетом ширины полосы прокрутки, а некоторые (например, Firefox) -- именно CSS-свойство (игнорируя полосу прокрутки). Эти кроссбраузерные отличия – ещё один повод не использовать `getComputedStyle`, а использовать свойства-метрики. ```online -If your browser reserves the space for a scrollbar (most browsers for Windows do), then you can test it below. +Если ваш браузер показывает полосу прокрутки (например, под Windows почти все браузеры так делают), то вы можете протестировать это сами, нажав на кнопку в ифрейме ниже. [iframe src="cssWidthScroll" link border=1] -The element with text has CSS `width:300px`. +У элемента с текстом в стилях указано CSS-свойство `width:300px`. -On a Desktop Windows OS, Firefox, Chrome, Edge all reserve the space for the scrollbar. But Firefox shows `300px`, while Chrome and Edge show less. That's because Firefox returns the CSS width and other browsers return the "real" width. +На ОС Windows браузеры Firefox, Chrome и Edge резервируют место для полосы прокрутки. Но Firefox отображает `300px`, в то время как Chrome и Edge -- меньше. Это из-за того, что Firefox возвращает именно CSS-ширину, а остальные браузеры -- "реальную" ширину за вычетом прокрутки. ``` -Please note that the described difference is only about reading `getComputedStyle(...).width` from JavaScript, visually everything is correct. +Обратите внимание: описанные различия касаются только чтения свойства `getComputedStyle(...).width` из JavaScript, визуальное отображение корректно в обоих случаях. -## Summary +## Итого -Elements have the following geometry properties: +У элементов есть следующие геометрические свойства (метрики): -- `offsetParent` -- is the nearest positioned ancestor or `td`, `th`, `table`, `body`. -- `offsetLeft/offsetTop` -- coordinates relative to the upper-left edge of `offsetParent`. -- `offsetWidth/offsetHeight` -- "outer" width/height of an element including borders. -- `clientLeft/clientTop` -- the distance from the upper-left outer corner to its upper-left inner corner. For left-to-right OS they are always the widths of left/top borders. For right-to-left OS the vertical scrollbar is on the left so `clientLeft` includes its width too. -- `clientWidth/clientHeight` -- the width/height of the content including paddings, but without the scrollbar. -- `scrollWidth/scrollHeight` -- the width/height of the content including the scrolled out parts. Also includes paddings, but not the scrollbar. -- `scrollLeft/scrollTop` -- width/height of the scrolled out part of the element, starting from its upper-left corner. +- `offsetParent` -- ближайший CSS-позиционированный родитель или ближайший `td`, `th`, `table`, `body`. +- `offsetLeft/offsetTop` -- позиция в пикселях верхнего левого угла относительно `offsetParent`. +- `offsetWidth/offsetHeight` -- "внешняя" ширина/высота элемента, включая рамки. +- `clientLeft/clientTop` -- расстояние от верхнего левого внешнего угла до внутренного. Для операционных систем с ориентацией слева-направо эти свойства равны ширинам левой/верхней рамки. Если язык ОС таков, что ориентация справа налево, так что вертикальная полоса прокрутки находится не справа, а слева, то `clientLeft` включает в своё значение её ширину. +- `clientWidth/clientHeight` -- ширина/высота содержимого вместе с внутренними отступами `padding`, но без полосы прокрутки. +- `scrollWidth/scrollHeight` -- ширина/высота содержимого, аналогично `clientWidth/Height`, но учитывают прокрученную, невидимую область элемента. +- `scrollLeft/scrollTop` -- ширина/высота прокрученной сверху части элемента, считается от верхнего левого угла. -All properties are read-only except `scrollLeft/scrollTop`. They make the browser scroll the element if changed. +Все свойства доступны только для чтения, кроме `scrollLeft/scrollTop`, изменение которых заставляет браузер прокручивать элемент. diff --git a/2-ui/1-document/09-size-and-scroll/cssWidthScroll.view/index.html b/2-ui/1-document/09-size-and-scroll/cssWidthScroll.view/index.html index 2869c7ed91..00bd658cdf 100755 --- a/2-ui/1-document/09-size-and-scroll/cssWidthScroll.view/index.html +++ b/2-ui/1-document/09-size-and-scroll/cssWidthScroll.view/index.html @@ -2,12 +2,12 @@ <html> <body> <div id="elem" style="overflow-y:scroll;width:300px;height:200px;border:1px solid black"> - text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text - text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text - text text text text text text text text text text text text text text text text text text text text text text text text text text + текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст + текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст + текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст </div> - The element has <code>style="width:300px"</code> + Элемент имеет <code>style="width:300px"</code> <br> <button onclick="alert( getComputedStyle(elem).width )">alert( getComputedStyle(elem).width )</button> diff --git a/2-ui/1-document/09-size-and-scroll/metric-all.png b/2-ui/1-document/09-size-and-scroll/metric-all.png deleted file mode 100644 index 6060b9890b..0000000000 Binary files a/2-ui/1-document/09-size-and-scroll/metric-all.png and /dev/null differ diff --git a/2-ui/1-document/09-size-and-scroll/metric-all.svg b/2-ui/1-document/09-size-and-scroll/metric-all.svg new file mode 100644 index 0000000000..a5dadb47f0 --- /dev/null +++ b/2-ui/1-document/09-size-and-scroll/metric-all.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="670" height="602" viewBox="0 0 670 602"><defs><style>@import url(https://fonts.googleapis.com/css?family=Open+Sans:bold,italic,bolditalic%7CPT+Mono);@font-face{font-family:'PT Mono';font-weight:700;font-style:normal;src:local('PT MonoBold'),url(/font/PTMonoBold.woff2) format('woff2'),url(/font/PTMonoBold.woff) format('woff'),url(/font/PTMonoBold.ttf) format('truetype')}</style></defs><defs><linearGradient id="linearGradient-1" x1="50%" x2="50%" y1="0%" y2="100%"><stop offset="0%" stop-color="#FFF"/><stop offset="100%" stop-color="#D1CFCD"/></linearGradient><linearGradient id="linearGradient-2" x1="50%" x2="50%" y1="0%" y2="100%"><stop offset="0%" stop-color="#FFF"/><stop offset="100%" stop-color="#D1CFCD"/></linearGradient></defs><g id="dom" fill="none" fill-rule="evenodd" stroke="none" stroke-width="1"><g id="metric-all.svg"><text id="Introduction" fill="#643B0C" font-family="OpenSans-Bold, Open Sans" font-size="16" font-weight="bold"><tspan x="160" y="94">Introduction</tspan> <tspan x="160" y="122" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">This Ecma Standard is based on several </tspan> <tspan x="160" y="141" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">originating technologies, the most well </tspan> <tspan x="160" y="160" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">known being JavaScript (Netscape) and </tspan> <tspan x="160" y="179" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">JScript (Microsoft). The language was </tspan> <tspan x="160" y="198" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">invented by Brendan Eich at Netscape and </tspan> <tspan x="160" y="217" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">first appeared in that company’s Navigator </tspan> <tspan x="160" y="236" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">2.0 browser. It has appeared in all </tspan> <tspan x="160" y="255" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">subsequent browsers from Netscape and </tspan> <tspan x="160" y="274" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">in all browsers from Microsoft starting with </tspan> <tspan x="160" y="293" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">Internet Explorer 3.0.</tspan> <tspan x="160" y="312" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">The development of this Standard started </tspan> <tspan x="160" y="331" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">in November 1996. The first edition of this </tspan> <tspan x="160" y="350" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">Ecma Standard was adopted by the Ecma </tspan> <tspan x="160" y="369" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">General Assembly of June 1997.</tspan> <tspan x="160" y="388" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">That Ecma Standard was submitted to ISO/</tspan> <tspan x="160" y="407" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">IEC JTC 1 for adoption under the fast-track </tspan> <tspan x="160" y="426" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">procedure, and approved as international </tspan> <tspan x="160" y="445" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">standard ISO/IEC 16262, in April 1998. The </tspan> <tspan x="160" y="464" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">Ecma General Assembly of June 1998 </tspan> <tspan x="160" y="483" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">approved the second edition of ECMA-262 </tspan> <tspan x="160" y="502" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">to keep it fully aligned with ISO/IEC 16262. </tspan> <tspan x="160" y="521" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">Changes between the first and the second </tspan> <tspan x="160" y="540" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">edition are editorial in nature.</tspan></text><path id="Rectangle-1" fill="#DBAF88" d="M491 162v290H117V162h374zm-25 25H142v240h324V187z"/><path id="Rectangle-2" stroke="#DBAF88" stroke-width="2" d="M141 62h326v500H141z"/><text id="scrollHeight" fill="#C06334" font-family="PTMono-Regular, PT Mono" font-size="14" font-weight="normal" transform="rotate(-90 592 310)"><tspan x="541.6" y="314.5">scrollHeight</tspan></text><text id="offsetHeight" fill="#C06334" font-family="PTMono-Regular, PT Mono" font-size="14" font-weight="normal" transform="rotate(-90 552 310)"><tspan x="501.6" y="314.5">offsetHeight</tspan></text><text id="scrollTop" fill="#C06334" font-family="PTMono-Regular, PT Mono" font-size="14" font-weight="normal" transform="rotate(-90 618 125)"><tspan x="580.2" y="129.5">scrollTop</tspan></text><path id="Line-27" stroke="#C06334" stroke-dasharray="3,6" stroke-linecap="square" stroke-width="2" d="M466.5 62H640"/><path id="Line-28" stroke="#C06334" stroke-dasharray="3,6" stroke-linecap="square" stroke-width="2" d="M492.5 163h92.14"/><path id="Line-29" stroke="#C06334" stroke-dasharray="3,6" stroke-linecap="square" stroke-width="2" d="M492.5 451h92.14"/><path id="Line-33" stroke="#C06334" stroke-dasharray="3,6" stroke-linecap="square" stroke-width="2" d="M467.5 189H640"/><path id="Line-32" stroke="#C06334" stroke-dasharray="3,6" stroke-linecap="square" stroke-width="2" d="M467.5 427h72.14"/><path id="Line-26" stroke="#C06334" stroke-dasharray="3,6" stroke-linecap="square" stroke-width="2" d="M466.5 561h148.14"/><path id="Line-25" fill="#C06334" fill-rule="nonzero" d="M605 64.5l7 14h-6v466h6l-7 14-7-14h6v-466h-6l7-14z"/><path id="Line-30" fill="#C06334" fill-rule="nonzero" d="M565 164.5l7 14h-6v255h6l-7 14-7-14h6v-255h-6l7-14z"/><text id="clientHeight" fill="#C06334" font-family="PTMono-Regular, PT Mono" font-size="14" font-weight="normal" transform="rotate(-90 510 304)"><tspan x="459.6" y="308.5">clientHeight</tspan></text><path id="Line-34" fill="#C06334" fill-rule="nonzero" d="M523 191.5l7 14h-6v206h6l-7 14-7-14h6v-206h-6l7-14z"/><text id="offsetTop" fill="#C06334" font-family="PTMono-Regular, PT Mono" font-size="14" font-weight="normal" transform="rotate(-90 104 83)"><tspan x="66.2" y="87.5">offsetTop</tspan></text><text id="clientLeft" fill="#166388" font-family="PTMono-Regular, PT Mono" font-size="14" font-weight="normal" transform="rotate(-90 130.5 237)"><tspan x="88.5" y="241.5">clientLeft</tspan></text><path id="Line-36" fill="#C06334" fill-rule="nonzero" d="M117 4.5l7 14h-6v128h6l-7 14-7-14h6v-128h-6l7-14z"/><path id="Line-31" fill="#C06334" fill-rule="nonzero" d="M631 64.5l7 14h-6v96.499l6 .001-7 14-7-14 6-.001V78.5h-6l7-14z"/><path id="Rectangle-14" fill="#FFF" d="M154 73h312v89H154z"/><path id="Rectangle-15" fill="#FFF" d="M154 451h312v93H154z"/><path id="Line-39" fill="#C06334" fill-rule="nonzero" d="M431 479.09l14 7-14 7-.001-6h-271.36l.001 6-14-7 14-7-.001 6h271.36l.001-6z"/><path id="Line-42" stroke="#C06334" stroke-dasharray="3,6" stroke-linecap="square" stroke-width="2" d="M445.64 510v-84"/><path id="Line-43" stroke="#C06334" stroke-dasharray="3,6" stroke-linecap="square" stroke-width="2" d="M141.64 510v-84"/><text id="clientWidth" fill="#C06334" font-family="PTMono-Regular, PT Mono" font-size="14" font-weight="normal"><tspan x="261.3" y="478">clientWidth</tspan></text><path id="Line-41" fill="#C06334" fill-rule="nonzero" d="M100 156.09l14 7-14 7v-6H18.639l.001 6-14-7 14-7-.001 6H100v-6z"/><text id="clientTop" fill="#166388" font-family="PTMono-Regular, PT Mono" font-size="14" font-weight="normal"><tspan x="147.7" y="178">clientTop</tspan></text><text id="offsetLeft" fill="#C06334" font-family="PTMono-Regular, PT Mono" font-size="14" font-weight="normal"><tspan x="17.5" y="154">offsetLeft</tspan></text><path id="Line-40" fill="#C06334" fill-rule="nonzero" d="M475 522.09l14 7-14 7-.001-6h-340.36l.001 6-14-7 14-7-.001 6h340.36l.001-6z"/><path id="Line-45" stroke="#C06334" stroke-dasharray="3,6" stroke-linecap="square" stroke-width="2" d="M490.64 551V447"/><path id="Line-44" stroke="#C06334" stroke-dasharray="3,6" stroke-linecap="square" stroke-width="2" d="M116.64 551V447"/><text id="offsetWidth" fill="#C06334" font-family="PTMono-Regular, PT Mono" font-size="14" font-weight="normal"><tspan x="258.3" y="516">offsetWidth</tspan></text><path id="Rectangle-233" stroke="#AF6E24" stroke-width="2" d="M1 1h668v600H1z"/><g id="Group" transform="translate(450 187)"><rect id="Rectangle-19" width="15" height="239" x=".5" y=".5" fill="#D1CFCD" stroke="#D1CFCD" rx="3"/><g id="Rectangle-18-+-Triangle-1"><rect id="Rectangle-18" width="15" height="19" x=".5" y=".5" fill="url(#linearGradient-1)" stroke="#D1CFCD" rx="3"/><path id="Triangle-1" fill="#7E7C7B" d="M8 7l3.2 6H4.8z"/></g><g id="Rectangle-18-+-Triangle-2" transform="matrix(1 0 0 -1 0 240)"><rect id="Rectangle-18" width="15" height="19" x=".5" y=".5" fill="url(#linearGradient-1)" stroke="#D1CFCD" rx="3"/><path id="Triangle-1" fill="#7E7C7B" d="M8 7l3.2 6H4.8z"/></g><g id="Rectangle-18-+-Triangle-3-+-Group" transform="translate(0 50)"><g id="Rectangle-18-+-Triangle-3" fill="url(#linearGradient-2)" stroke="#D1CFCD" transform="matrix(1 0 0 -1 0 51)"><rect id="Rectangle-18" width="15" height="50" x=".5" y=".5" rx="3"/></g><g id="Group" fill="#D1CFCD" stroke="#7E7C7B" transform="translate(4 20)"><path id="Rectangle-22" d="M.5.5h7v1h-7z"/><path id="Rectangle-23" d="M.5 3.5h7v1h-7z"/><path id="Rectangle-24" d="M.5 6.5h7v1h-7z"/><path id="Rectangle-25" d="M.5 9.5h7v1h-7z"/></g></g></g><g id="Group" transform="translate(115.676 162.5)"><g id="Line-4-+-Line-5" stroke="#166388" stroke-linecap="square" stroke-width="2" transform="translate(22.324 18.5)"><path id="Line-4" d="M2.5.5L0 6" transform="matrix(1 0 0 -1 0 6)"/><path id="Line-5" d="M5.5.5L3 6" transform="rotate(180 4.5 3)"/></g><g id="Line-4-+-Line-6" stroke="#166388" stroke-linecap="square" stroke-width="2" transform="matrix(1 0 0 -1 22.324 6.5)"><path id="Line-4" d="M2.5.5L0 6" transform="matrix(1 0 0 -1 0 6)"/><path id="Line-5" d="M5.5.5L3 6" transform="rotate(180 4.5 3)"/></g><path id="Line-49" fill="#166388" stroke="#166388" stroke-linecap="round" stroke-linejoin="bevel" stroke-width="2" d="M25.824 25.5h-25"/><path id="Line-50" fill="#166388" stroke="#166388" stroke-linecap="round" stroke-linejoin="bevel" stroke-width="2" d="M25.324 25V0"/><path id="Line-4" fill="#166388" fill-rule="nonzero" d="M19.328 21.676l.91.414 5.5 2.5.91.413-.589 1.296.09.198-.226.1-.102.227-.198-.09-.385.176-5.5 2.5-.91.414-.828-1.82.91-.414 4.297-1.954-3.797-1.726-.91-.413.828-1.821zM7.32 21.676l.828 1.82-.91.414-3.798 1.726 4.298 1.954.91.413-.827 1.821-.91-.414-5.5-2.5-.387-.176-.196.09-.103-.225-.225-.102.089-.198L0 25.003l.91-.413 5.5-2.5.91-.414z"/></g></g></g></svg> \ No newline at end of file diff --git a/2-ui/1-document/09-size-and-scroll/metric-all@2x.png b/2-ui/1-document/09-size-and-scroll/metric-all@2x.png deleted file mode 100644 index 3c53be1dd6..0000000000 Binary files a/2-ui/1-document/09-size-and-scroll/metric-all@2x.png and /dev/null differ diff --git a/2-ui/1-document/09-size-and-scroll/metric-client-left-top-rtl.png b/2-ui/1-document/09-size-and-scroll/metric-client-left-top-rtl.png deleted file mode 100644 index efcc9440b5..0000000000 Binary files a/2-ui/1-document/09-size-and-scroll/metric-client-left-top-rtl.png and /dev/null differ diff --git a/2-ui/1-document/09-size-and-scroll/metric-client-left-top-rtl.svg b/2-ui/1-document/09-size-and-scroll/metric-client-left-top-rtl.svg new file mode 100644 index 0000000000..e8dd3d60a0 --- /dev/null +++ b/2-ui/1-document/09-size-and-scroll/metric-client-left-top-rtl.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="359" height="316" viewBox="0 0 359 316"><defs><style>@import url(https://fonts.googleapis.com/css?family=Open+Sans:bold,italic,bolditalic%7CPT+Mono);@font-face{font-family:'PT Mono';font-weight:700;font-style:normal;src:local('PT MonoBold'),url(/font/PTMonoBold.woff2) format('woff2'),url(/font/PTMonoBold.woff) format('woff'),url(/font/PTMonoBold.ttf) format('truetype')}</style></defs><defs><linearGradient id="linearGradient-1" x1="0%" x2="62.299%" y1="47.096%" y2="47.096%"><stop offset="0%" stop-color="#FFF"/><stop offset="100%" stop-color="#FFF" stop-opacity="0"/></linearGradient><linearGradient id="linearGradient-2" x1="50%" x2="50%" y1="0%" y2="100%"><stop offset="0%" stop-color="#FFF"/><stop offset="100%" stop-color="#D1CFCD"/></linearGradient><linearGradient id="linearGradient-3" x1="50%" x2="50%" y1="0%" y2="100%"><stop offset="0%" stop-color="#FFF"/><stop offset="100%" stop-color="#D1CFCD"/></linearGradient><linearGradient id="linearGradient-4" x1="0%" x2="62.299%" y1="47.096%" y2="47.096%"><stop offset="0%" stop-color="#FFF"/><stop offset="100%" stop-color="#FFF" stop-opacity="0"/></linearGradient></defs><g id="dom" fill="none" fill-rule="evenodd" stroke="none" stroke-width="1"><g id="metric-client-left-top-rtl.svg"><path fill="#FFF" d="M0 0h359v316H0z"/><path id="היא-שפת-תסריט-מפורשת" fill="#643B0C" d="M336.484 135v-10.031h4.047V135h-4.047zm13.594 0v-9.063c0-2.416-.206-3.856-.617-4.32-.412-.463-1.685-.695-3.82-.695h-12.047l1.547-4.625h11.171c3.136 0 5.23.458 6.282 1.375a3.733 3.733 0 011.234 1.984c.198.792.297 2.177.297 4.156V135h-4.047zm-20.39-6.266h-4.063c.73-2.666 1.094-4.573 1.094-5.718 0-.823-.36-1.474-1.078-1.954-.72-.479-1.964-.895-3.735-1.25l2.094-3.89c2.552.51 4.33 1.21 5.336 2.101 1.005.891 1.508 2.206 1.508 3.946 0 1.094-.25 2.76-.75 5l-.406 1.765zM302.063 135v-.516c0-2.677.28-4.971.843-6.882.563-1.912 1.51-3.8 2.844-5.665l-.703-.671c-1.136-1.063-2.162-1.948-3.078-2.657l3.687-2.687c2.448 1.937 5.037 4.51 7.766 7.719.625-1.084 1.039-2.032 1.242-2.844.203-.813.305-1.927.305-3.344v-1.156h4.328v.906c0 3.521-1.182 6.568-3.547 9.14a41.618 41.618 0 014.422 6.47l-3.485 2.578c-2.062-3.709-4.864-7.355-8.406-10.938-1.26 1.958-1.89 4.948-1.89 8.969V135h-4.329zm-31.954 0l-2.203-17.5 3.922-1.578 1.172 9.25c1.135-.281 1.932-.945 2.39-1.992.459-1.047.688-2.716.688-5.008v-1.875h4.344v1.719c0 3.093-.565 5.445-1.695 7.054-1.13 1.61-2.92 2.607-5.368 2.993l.282 2.312c2.718 0 4.89-.453 6.515-1.36 2.844-1.583 4.266-5.192 4.266-10.827v-1.891h4.328v1.562c0 5.615-1.58 9.875-4.742 12.782-3.162 2.906-7.794 4.359-13.899 4.359zm-21.937 0l1.39-4.234h10.47v-4.157c0-1.948-.193-3.205-.579-3.773-.385-.568-1.385-1.065-3-1.492l-2.984-.781-.235.671c-.135.396-.203.74-.203 1.032 0 1.01.87 1.583 2.61 1.718l-1.078 3.25c-3.5-.323-5.25-1.671-5.25-4.046 0-.917.374-2.438 1.125-4.563l.968-2.703 7.125 1.734c2.313.563 3.815 1.284 4.508 2.164.693.88 1.04 2.503 1.04 4.868v6.078L262.671 135h-14.5zm-22.969 0l1.547-4.625h4.031c-.823-2.115-1.234-4.708-1.234-7.781v-1.672h-2.328l1.547-4.625h8.578c3.135 0 5.229.458 6.281 1.375a3.733 3.733 0 011.234 1.984c.198.792.297 2.177.297 4.156V135h-4.047v-9.063c0-2.416-.203-3.856-.609-4.32-.406-.463-1.682-.695-3.828-.695h-3.063v1.375c0 3.354.48 6.047 1.438 8.078L233.516 135h-8.313zm-33.547 0l1.547-4.625h4.031c-.823-2.115-1.234-4.708-1.234-7.781v-1.672h-2.328l1.547-4.625h8.578c3.135 0 5.229.458 6.281 1.375a3.733 3.733 0 011.234 1.984c.198.792.297 2.177.297 4.156V135h-4.047v-9.063c0-2.416-.203-3.856-.609-4.32-.406-.463-1.682-.695-3.828-.695h-3.063v1.375c0 3.354.48 6.047 1.438 8.078L199.969 135h-8.313zm-18.265 0l-1.766-14.078h-2.516l1.547-4.625h11.078c2.094 0 3.578.078 4.453.234.876.157 1.573.469 2.094.938.875.791 1.313 2.047 1.313 3.765 0 2.292-.662 4.506-1.985 6.641s-3.104 3.854-5.343 5.156c-2.26 1.313-5.22 1.969-8.875 1.969zm3.453-4.625c2.333-.177 4.088-.687 5.265-1.531 1.938-1.407 2.907-3.25 2.907-5.531 0-.938-.328-1.57-.985-1.899-.656-.328-1.948-.492-3.875-.492h-4.469l1.157 9.453zM162.297 135v-9.063c0-2.416-.203-3.856-.61-4.32-.406-.463-1.682-.695-3.828-.695h-5.89l1.531-4.625h5.047c3.135 0 5.229.458 6.281 1.375a3.773 3.773 0 011.219 1.992c.198.797.297 2.18.297 4.148V135h-4.047zm-14.047-6.266h-4.063c.73-2.666 1.094-4.573 1.094-5.718 0-.823-.36-1.474-1.078-1.954-.719-.479-1.963-.895-3.734-1.25l2.094-3.89c2.552.51 4.33 1.21 5.335 2.101 1.006.891 1.508 2.206 1.508 3.946 0 1.094-.25 2.76-.75 5l-.406 1.765zM122.375 135l-2.234-17.688 3.921-1.39 1.797 14.453c2.667 0 4.795-.615 6.383-1.844 1.589-1.229 2.383-2.87 2.383-4.922 0-2.24-1.208-3.359-3.625-3.359-.646 0-1.339.104-2.078.313l1.406-4.36a9.102 9.102 0 012.234-.297c1.917 0 3.415.576 4.493 1.727 1.078 1.15 1.617 2.747 1.617 4.789 0 2.292-.698 4.437-2.094 6.437s-3.292 3.568-5.687 4.703c-2.01.959-4.85 1.438-8.516 1.438zm222.344 38l2.265-8.688c.417-1.593.625-2.692.625-3.296 0-1.24-1.057-2.907-3.171-5l3.609-2.094c1.146.958 2.125 2.265 2.937 3.922 1.667-2.615 3.667-3.922 6-3.922 1.76 0 3.167.67 4.22 2.008 1.051 1.338 1.676 3.257 1.874 5.757l.531 6.688-1.53 4.625h-9.032l1.547-4.625h4.922l-.36-4.797c-.25-3.354-1.349-5.031-3.297-5.031-1.125 0-2.07.56-2.836 1.68-.765 1.12-1.497 3.007-2.195 5.664L348.953 173h-4.234zm-19.344 0l1.39-4.234h10.47v-4.157c0-1.948-.193-3.205-.579-3.773-.385-.568-1.385-1.065-3-1.492l-2.984-.781-.235.671c-.135.396-.203.74-.203 1.032 0 1.01.87 1.583 2.61 1.718l-1.078 3.25c-3.5-.323-5.25-1.671-5.25-4.047 0-.916.375-2.437 1.125-4.562l.968-2.703 7.125 1.734c2.313.563 3.815 1.284 4.508 2.164.693.88 1.04 2.503 1.04 4.868v6.078L339.874 173h-14.5zm-7.14 0v-9.594c0-1.927-.32-3.252-.962-3.976-.64-.724-1.997-1.295-4.07-1.711l2-3.797c2.448.448 4.208 1.15 5.281 2.11.709.635 1.185 1.385 1.43 2.25.245.864.367 2.25.367 4.155V173h-4.047zm-11.844 0v-9.063c0-2.416-.203-3.856-.61-4.32-.406-.463-1.682-.695-3.828-.695h-5.89l1.53-4.625h5.048c3.135 0 5.229.458 6.28 1.375a3.773 3.773 0 011.22 1.992c.198.797.296 2.18.296 4.149V173h-4.046zm-30.516 0l-2.203-17.5 3.922-1.578 1.172 9.25c1.135-.281 1.932-.945 2.39-1.992.459-1.047.688-2.716.688-5.008v-1.875h4.344v1.719c0 3.093-.566 5.445-1.696 7.054-1.13 1.61-2.92 2.607-5.367 2.993l.281 2.312c2.719 0 4.89-.453 6.516-1.36 2.844-1.583 4.265-5.192 4.265-10.827v-1.891h4.329v1.562c0 5.615-1.581 9.875-4.743 12.782-3.161 2.906-7.794 4.359-13.898 4.359zM250 173l1.547-4.625h4.031c-.823-2.115-1.234-4.708-1.234-7.781v-1.672h-2.328l1.547-4.625h8.578c3.135 0 5.229.458 6.28 1.375a3.733 3.733 0 011.235 1.984c.198.792.297 2.177.297 4.156V173h-4.047v-9.063c0-2.416-.203-3.856-.61-4.32-.405-.463-1.681-.695-3.827-.695h-3.063v1.375c0 3.354.48 6.047 1.438 8.078L258.313 173H250zm-31.422 0l2.266-8.688c.416-1.593.625-2.692.625-3.296 0-1.24-1.058-2.907-3.172-5l3.61-2.094c1.145.958 2.124 2.265 2.937 3.922 1.666-2.615 3.666-3.922 6-3.922 1.76 0 3.166.67 4.219 2.008 1.052 1.338 1.677 3.257 1.875 5.757l.53 6.688-1.53 4.625h-9.032l1.547-4.625h4.922l-.36-4.797c-.25-3.354-1.348-5.031-3.296-5.031-1.125 0-2.07.56-2.836 1.68-.766 1.12-1.498 3.007-2.196 5.664L222.813 173h-4.234zm-19.25 0l1.547-4.625h9.64v-4.438c0-2.416-.202-3.856-.609-4.32-.406-.463-1.682-.695-3.828-.695h-5.828l1.547-4.625h4.953c3.135 0 5.23.458 6.281 1.375a3.734 3.734 0 011.235 1.992c.198.797.297 2.18.297 4.149v6.562h2.5L215.53 173h-16.203zm-7.516 0v-9.594c0-1.927-.32-3.252-.96-3.976-.641-.724-1.998-1.295-4.07-1.711l2-3.797c2.447.448 4.208 1.15 5.28 2.11.709.635 1.185 1.385 1.43 2.25.245.864.367 2.25.367 4.155V173h-4.047zm-23.015 0l-1.766-14.078h-2.515l1.547-4.625h11.078c2.093 0 3.578.078 4.453.234.875.157 1.573.469 2.094.938.875.791 1.312 2.047 1.312 3.765 0 2.292-.661 4.506-1.984 6.641s-3.105 3.854-5.344 5.156c-2.26 1.313-5.219 1.969-8.875 1.969zm3.453-4.625c2.333-.177 4.089-.687 5.266-1.531 1.937-1.407 2.906-3.25 2.906-5.531 0-.938-.328-1.57-.984-1.899-.657-.328-1.948-.492-3.875-.492h-4.47l1.157 9.453zM146.531 173l-1.765-14.078h-2.516l1.547-4.625h11.078c2.094 0 3.578.078 4.453.234.875.157 1.573.469 2.094.938.875.791 1.312 2.047 1.312 3.765 0 2.292-.661 4.506-1.984 6.641s-3.104 3.854-5.344 5.156c-2.26 1.313-5.218 1.969-8.875 1.969zm3.453-4.625c2.334-.177 4.089-.687 5.266-1.531 1.938-1.407 2.906-3.25 2.906-5.531 0-.938-.328-1.57-.984-1.899-.656-.328-1.948-.492-3.875-.492h-4.469l1.156 9.453zM119.531 173l1.547-4.625h4.031c-.823-2.115-1.234-4.708-1.234-7.781v-1.672h-2.328l1.547-4.625h8.578c3.135 0 5.229.458 6.281 1.375a3.733 3.733 0 011.234 1.984c.198.792.297 2.177.297 4.156V173h-4.047v-9.063c0-2.416-.203-3.856-.609-4.32-.406-.463-1.682-.695-3.828-.695h-3.063v1.375c0 3.354.48 6.047 1.438 8.078L127.844 173h-8.313zm288.766 38v-.516c0-2.677.281-4.971.844-6.882.562-1.912 1.51-3.8 2.843-5.665l-.703-.671c-1.135-1.063-2.161-1.948-3.078-2.657l3.688-2.687c2.448 1.937 5.036 4.51 7.765 7.719.625-1.084 1.04-2.032 1.242-2.844.204-.813.305-1.927.305-3.344v-1.156h4.328v.906c0 3.521-1.182 6.568-3.547 9.14a41.618 41.618 0 014.422 6.47l-3.484 2.578c-2.063-3.709-4.865-7.355-8.406-10.938-1.26 1.958-1.891 4.948-1.891 8.969V211h-4.328zm-8.438 0v-9.594c0-1.927-.32-3.252-.96-3.976-.641-.724-1.998-1.295-4.07-1.711l2-3.797c2.447.448 4.207 1.15 5.28 2.11.709.635 1.185 1.385 1.43 2.25.245.864.367 2.25.367 4.155V211h-4.047zm-23.718 0l1.546-4.625h9.641v-4.438c0-2.416-.203-3.856-.61-4.32-.406-.463-1.682-.695-3.827-.695h-5.829l1.547-4.625h4.954c3.135 0 5.229.458 6.28 1.375a3.734 3.734 0 011.235 1.992c.198.797.297 2.18.297 4.149v6.562h2.5L392.344 211H376.14zm-4.016-6.266h-4.063c.73-2.666 1.094-4.573 1.094-5.718 0-.823-.36-1.474-1.078-1.953-.719-.48-1.963-.896-3.734-1.25l2.094-3.891c2.552.51 4.33 1.21 5.335 2.101 1.006.891 1.508 2.206 1.508 3.946 0 1.094-.25 2.76-.75 5l-.406 1.765zm-11.594 0h-4.062c.729-2.666 1.094-4.573 1.094-5.718 0-.823-.36-1.474-1.079-1.953-.718-.48-1.963-.896-3.734-1.25l2.094-3.891c2.552.51 4.33 1.21 5.336 2.101 1.005.891 1.507 2.206 1.507 3.946 0 1.094-.25 2.76-.75 5l-.406 1.765zm-27.328 10.891v-14.61h4.047v14.61h-4.047zm-2.515-18.703l1.546-4.625h10.844c1.99 0 3.39.088 4.203.266.813.177 1.495.541 2.047 1.093.802.813 1.203 1.98 1.203 3.5 0 3.052-1.12 5.901-3.36 8.547-2.239 2.646-5.13 4.537-8.671 5.672l1.672-4.969c1.656-.469 3.036-1.383 4.14-2.742 1.105-1.36 1.657-2.82 1.657-4.383 0-.937-.328-1.565-.985-1.883-.656-.317-1.958-.476-3.906-.476h-10.39zM312.594 211l-2.235-17.688 3.922-1.39 1.797 14.453c2.667 0 4.794-.615 6.383-1.844 1.588-1.229 2.383-2.87 2.383-4.922 0-2.24-1.209-3.359-3.625-3.359-.646 0-1.339.104-2.078.313l1.406-4.36a9.102 9.102 0 012.234-.297c1.917 0 3.414.576 4.492 1.727 1.079 1.15 1.618 2.747 1.618 4.789 0 2.292-.698 4.437-2.094 6.437s-3.292 3.568-5.688 4.703c-2.01.959-4.849 1.438-8.515 1.438zm-6.516-6.266h-4.062c.729-2.666 1.093-4.573 1.093-5.718 0-.823-.359-1.474-1.078-1.953-.719-.48-1.963-.896-3.734-1.25l2.094-3.891c2.552.51 4.33 1.21 5.336 2.101 1.005.891 1.507 2.206 1.507 3.946 0 1.094-.25 2.76-.75 5l-.406 1.765zM277.766 211v-14.078h-2.485l1.547-4.625h10.485c3.135 0 5.229.458 6.28 1.375a3.734 3.734 0 011.235 1.992c.198.797.297 2.18.297 4.149v6.562L293.578 211h-15.812zm4.062-4.625h9.25v-4.438c0-2.427-.203-3.87-.61-4.328-.406-.458-1.682-.687-3.827-.687h-4.813v9.453zM243.86 211v-10.031h4.047V211h-4.047zm13.594 0v-9.063c0-2.416-.206-3.856-.617-4.32-.412-.463-1.685-.695-3.82-.695h-12.047l1.547-4.625h11.172c3.135 0 5.229.458 6.28 1.375a3.733 3.733 0 011.235 1.984c.198.792.297 2.177.297 4.156V211h-4.047zm-37.594 0l2.266-8.688c.417-1.593.625-2.692.625-3.296 0-1.24-1.057-2.907-3.172-5l3.61-2.094c1.145.958 2.124 2.265 2.937 3.922 1.667-2.615 3.667-3.922 6-3.922 1.76 0 3.167.67 4.219 2.008 1.052 1.338 1.677 3.257 1.875 5.757l.531 6.688-1.531 4.625h-9.031l1.546-4.625h4.922l-.36-4.797c-.25-3.354-1.348-5.031-3.296-5.031-1.125 0-2.07.56-2.836 1.68-.766 1.12-1.497 3.007-2.195 5.664L224.094 211h-4.235zm-7.453 0v-9.594c0-1.927-.32-3.252-.96-3.976-.641-.724-1.998-1.295-4.071-1.711l2-3.797c2.448.448 4.208 1.15 5.281 2.11.709.635 1.185 1.385 1.43 2.25.245.864.367 2.25.367 4.155V211h-4.047zm-27.75 0l1.547-4.625h4.031c-.823-2.115-1.234-4.708-1.234-7.781v-1.672h-2.328l1.547-4.625h8.578c3.135 0 5.229.458 6.281 1.375a3.733 3.733 0 011.234 1.984c.198.792.297 2.177.297 4.156V211h-4.047v-9.063c0-2.416-.203-3.856-.609-4.32-.406-.463-1.682-.695-3.828-.695h-3.063v1.375c0 3.354.48 6.047 1.438 8.078L192.969 211h-8.313zm-19.703 0v-.516c0-2.677.281-4.971.844-6.882.562-1.912 1.51-3.8 2.844-5.665l-.703-.671c-1.136-1.063-2.162-1.948-3.079-2.657l3.688-2.687c2.448 1.937 5.036 4.51 7.766 7.719.625-1.084 1.039-2.032 1.242-2.844.203-.813.304-1.927.304-3.344v-1.156h4.328v.906c0 3.521-1.182 6.568-3.546 9.14a41.618 41.618 0 014.422 6.47l-3.485 2.578c-2.062-3.709-4.864-7.355-8.406-10.938-1.26 1.958-1.89 4.948-1.89 8.969V211h-4.329zm-22.14 0l2.265-8.688c.417-1.593.625-2.692.625-3.296 0-1.24-1.057-2.907-3.172-5l3.61-2.094c1.145.958 2.125 2.265 2.937 3.922 1.667-2.615 3.667-3.922 6-3.922 1.76 0 3.167.67 4.219 2.008 1.052 1.338 1.677 3.257 1.875 5.757l.531 6.688-1.531 4.625h-9.031l1.547-4.625h4.921l-.359-4.797c-.25-3.354-1.349-5.031-3.297-5.031-1.125 0-2.07.56-2.836 1.68-.765 1.12-1.497 3.007-2.195 5.664L147.047 211h-4.234zm-23.282 0l1.547-4.625h4.031c-.823-2.115-1.234-4.708-1.234-7.781v-1.672h-2.328l1.547-4.625h8.578c3.135 0 5.229.458 6.281 1.375a3.733 3.733 0 011.234 1.984c.198.792.297 2.177.297 4.156V211h-4.047v-9.063c0-2.416-.203-3.856-.609-4.32-.406-.463-1.682-.695-3.828-.695h-3.063v1.375c0 3.354.48 6.047 1.438 8.078L127.844 211h-8.313zm318.844 38.39l1.594-4.718c2.041-.23 3.687-.839 4.937-1.828 1.948-1.542 2.922-3.406 2.922-5.594 0-.906-.32-1.52-.96-1.844-.641-.323-1.873-.484-3.696-.484h-5.563v-9.625l4.047-1.36v6.36h4.063c2.5 0 4.242.36 5.226 1.078.985.719 1.477 1.984 1.477 3.797 0 3.437-1.318 6.49-3.953 9.156-2.636 2.667-6 4.354-10.094 5.063zm-21.86-.39l-2.202-17.5 3.921-1.578 1.172 9.25c1.136-.281 1.933-.945 2.39-1.992.46-1.047.688-2.716.688-5.008v-1.875h4.344v1.719c0 3.093-.565 5.445-1.695 7.054-1.13 1.61-2.92 2.607-5.367 2.993l.28 2.312c2.72 0 4.891-.453 6.517-1.36 2.843-1.583 4.265-5.192 4.265-10.827v-1.891h4.328v1.562c0 5.615-1.58 9.875-4.742 12.782-3.161 2.906-7.794 4.359-13.898 4.359zm-6.546-6.266h-4.063c.73-2.666 1.094-4.573 1.094-5.718 0-.823-.36-1.474-1.078-1.953-.719-.48-1.964-.896-3.735-1.25l2.094-3.891c2.552.51 4.331 1.21 5.336 2.101s1.508 2.206 1.508 3.946c0 1.094-.25 2.76-.75 5l-.406 1.765zm-24.016 6.657l1.594-4.72c2.042-.228 3.687-.838 4.937-1.827 1.948-1.542 2.922-3.406 2.922-5.594 0-.906-.32-1.52-.96-1.844-.641-.323-1.873-.484-3.696-.484h-5.563v-9.625l4.047-1.36v6.36h4.063c2.5 0 4.242.36 5.226 1.078.985.719 1.477 1.984 1.477 3.797 0 3.437-1.318 6.49-3.953 9.156-2.636 2.667-6 4.354-10.094 5.063zm-8.89-.391v-9.594c0-1.927-.32-3.252-.961-3.976-.641-.724-1.998-1.295-4.07-1.711l2-3.797c2.447.448 4.208 1.15 5.28 2.11.709.635 1.185 1.385 1.43 2.25.245.864.367 2.25.367 4.155V249h-4.046zm-23.72 0l1.548-4.625h9.64v-4.438c0-2.416-.203-3.856-.61-4.32-.405-.463-1.681-.695-3.827-.695h-5.828l1.546-4.625h4.954c3.135 0 5.229.458 6.28 1.375a3.734 3.734 0 011.235 1.992c.198.797.297 2.18.297 4.149v6.562h2.5L369.547 249h-16.203zm-29.859 0l1.547-4.625h9.64v-4.438c0-2.416-.202-3.856-.608-4.32-.407-.463-1.683-.695-3.829-.695h-5.828l1.547-4.625h4.953c3.136 0 5.23.458 6.281 1.375a3.734 3.734 0 011.235 1.992c.198.797.297 2.18.297 4.149v6.562h2.5L339.688 249h-16.204zm-20.046 0v-.516c0-2.677.28-4.971.843-6.882.563-1.912 1.51-3.8 2.844-5.665l-.703-.671c-1.136-1.063-2.162-1.948-3.078-2.657l3.687-2.687c2.448 1.937 5.037 4.51 7.766 7.719.625-1.084 1.039-2.032 1.242-2.844.203-.813.305-1.927.305-3.344v-1.156h4.328v.906c0 3.521-1.182 6.568-3.547 9.14a41.618 41.618 0 014.422 6.47l-3.485 2.578c-2.062-3.709-4.864-7.355-8.406-10.938-1.26 1.958-1.89 4.948-1.89 8.969V249h-4.329zm-24.266 0l1.547-4.625h4.031c-.823-2.115-1.234-4.708-1.234-7.781v-1.672h-2.329l1.547-4.625h8.579c3.135 0 5.229.458 6.28 1.375a3.733 3.733 0 011.235 1.984c.198.792.297 2.177.297 4.156V249h-4.047v-9.063c0-2.416-.203-3.856-.61-4.32-.406-.463-1.682-.695-3.827-.695h-3.063v1.375c0 3.354.48 6.047 1.438 8.078L287.484 249h-8.312zm-7.094 0v-9.063c0-2.416-.203-3.856-.61-4.32-.406-.463-1.682-.695-3.827-.695h-5.891l1.531-4.625h5.047c3.136 0 5.23.458 6.281 1.375a3.773 3.773 0 011.22 1.992c.197.797.296 2.18.296 4.149V249h-4.047zm-14.047-6.266h-4.062c.729-2.666 1.094-4.573 1.094-5.718 0-.823-.36-1.474-1.079-1.953-.718-.48-1.963-.896-3.734-1.25l2.094-3.891c2.552.51 4.33 1.21 5.336 2.101 1.005.891 1.507 2.206 1.507 3.946 0 1.094-.25 2.76-.75 5l-.406 1.765zM219.86 249v-.516c0-2.677.282-4.971.844-6.882.563-1.912 1.51-3.8 2.844-5.665l-.703-.671c-1.136-1.063-2.162-1.948-3.078-2.657l3.687-2.687c2.448 1.937 5.037 4.51 7.766 7.719.625-1.084 1.039-2.032 1.242-2.844.203-.813.305-1.927.305-3.344v-1.156h4.328v.906c0 3.521-1.183 6.568-3.547 9.14a41.618 41.618 0 014.422 6.47l-3.485 2.578c-2.062-3.709-4.864-7.355-8.406-10.938-1.26 1.958-1.89 4.948-1.89 8.969V249h-4.329zm-4.937-6.266h-4.063c.73-2.666 1.094-4.573 1.094-5.718 0-.823-.36-1.474-1.078-1.953-.719-.48-1.964-.896-3.734-1.25l2.093-3.891c2.552.51 4.331 1.21 5.336 2.101 1.006.891 1.508 2.206 1.508 3.946 0 1.094-.25 2.76-.75 5l-.406 1.765zM192.75 249l1.563-4.625h5.515v-6.078c0-1.646-.354-2.724-1.062-3.234-.948-.678-2.224-1.245-3.828-1.704l1.843-3.437c1.73.427 3.297 1.047 4.703 1.86.907.53 1.532 1.182 1.875 1.952.344.771.516 1.922.516 3.453v7.188L202.328 249h-9.578zm-17.906 0l-2.235-17.688 3.922-1.39 1.797 14.453c2.667 0 4.794-.615 6.383-1.844 1.588-1.229 2.383-2.87 2.383-4.922 0-2.24-1.209-3.359-3.625-3.359-.646 0-1.339.104-2.078.313l1.406-4.36a9.102 9.102 0 012.234-.297c1.917 0 3.414.576 4.492 1.727 1.079 1.15 1.618 2.747 1.618 4.789 0 2.292-.698 4.437-2.094 6.437s-3.292 3.568-5.688 4.703c-2.01.959-4.849 1.438-8.515 1.438zm-9.938 0v-9.063c0-2.416-.203-3.856-.61-4.32-.405-.463-1.681-.695-3.827-.695h-5.89l1.53-4.625h5.047c3.136 0 5.23.458 6.281 1.375a3.773 3.773 0 011.22 1.992c.197.797.296 2.18.296 4.149V249h-4.047zm-24.625 0l1.563-4.625h5.515v-6.078c0-1.646-.354-2.724-1.062-3.234-.948-.678-2.224-1.245-3.828-1.704l1.844-3.437c1.729.427 3.296 1.047 4.703 1.86.906.53 1.53 1.182 1.875 1.952.343.771.515 1.922.515 3.453v7.188L149.86 249h-9.578zm-17.906 0l-2.234-17.688 3.921-1.39 1.797 14.453c2.667 0 4.795-.615 6.383-1.844 1.589-1.229 2.383-2.87 2.383-4.922 0-2.24-1.208-3.359-3.625-3.359-.646 0-1.339.104-2.078.313l1.406-4.36a9.102 9.102 0 012.234-.297c1.917 0 3.415.576 4.493 1.727 1.078 1.15 1.617 2.747 1.617 4.789 0 2.292-.698 4.437-2.094 6.437s-3.292 3.568-5.687 4.703c-2.01.959-4.85 1.438-8.516 1.438zm258.422 38v-9.594c0-1.927-.32-3.252-.961-3.976-.64-.724-1.997-1.295-4.07-1.711l2-3.797c2.448.448 4.208 1.15 5.28 2.11.71.635 1.186 1.385 1.43 2.25.245.864.368 2.25.368 4.156V287h-4.047zm-11.844 0v-9.063c0-2.416-.203-3.856-.61-4.32-.406-.463-1.682-.695-3.827-.695h-5.891l1.531-4.625h5.047c3.136 0 5.23.458 6.281 1.375a3.773 3.773 0 011.22 1.992c.197.797.296 2.18.296 4.149V287h-4.047zm-29.25 0l1.36-4.14h8.468c-3.833-3.709-6.713-8.162-8.64-13.36l4.093-1.578a28.973 28.973 0 004.938 8.875c1.469-1.781 2.203-3.943 2.203-6.485v-2.015h4.344v1.312c0 3.97-1.36 7.214-4.078 9.735 1.573 1.614 2.974 2.791 4.203 3.531l-1.39 4.125h-15.5zm-20.594 0v-10.031h4.047V287h-4.047zm13.594 0v-9.063c0-2.416-.206-3.856-.617-4.32-.412-.463-1.685-.695-3.82-.695h-12.047l1.547-4.625h11.171c3.136 0 5.23.458 6.282 1.375a3.733 3.733 0 011.234 1.984c.198.792.297 2.177.297 4.156V287h-4.047zm-48.61 2.781l1.563-4.672c1.667 0 3.459-.28 5.375-.843l-3.36-14.985 3.876-1.36 3.265 14.563c1.636-1.24 2.774-2.78 3.415-4.625.64-1.843.96-4.484.96-7.921v-1.641h4.344v1.156c0 3.99-.612 7.318-1.836 9.985-1.224 2.666-3.153 4.88-5.789 6.64-3.698 2.469-7.635 3.703-11.812 3.703zm-14.796-2.39l1.594-4.72c2.041-.228 3.687-.838 4.937-1.827 1.948-1.542 2.922-3.406 2.922-5.594 0-.906-.32-1.52-.96-1.844-.642-.323-1.873-.484-3.696-.484h-5.563v-9.625l4.047-1.36v6.36h4.063c2.5 0 4.242.36 5.226 1.078.985.719 1.477 1.984 1.477 3.797 0 3.437-1.318 6.49-3.953 9.156-2.636 2.667-6 4.354-10.094 5.063zm-15.938-6.657h-4.062c.729-2.666 1.094-4.573 1.094-5.718 0-.823-.36-1.474-1.078-1.954-.72-.479-1.964-.895-3.735-1.25l2.094-3.89c2.552.51 4.33 1.21 5.336 2.101 1.005.891 1.508 2.206 1.508 3.946 0 1.094-.25 2.76-.75 5l-.407 1.765zM236.656 287v-14.078h-9.531l1.547-4.625h16.094l-1.547 4.625h-2.516V287h-4.047zm-12.86-6.266h-4.062c.73-2.666 1.094-4.573 1.094-5.718 0-.823-.36-1.474-1.078-1.954-.719-.479-1.964-.895-3.734-1.25l2.093-3.89c2.552.51 4.331 1.21 5.336 2.101 1.006.891 1.508 2.206 1.508 3.946 0 1.094-.25 2.76-.75 5l-.406 1.765zM196.547 287v-14.078h-9.53l1.547-4.625h16.093l-1.547 4.625h-2.515V287h-4.047zm-28.25 0l1.392-4.234h10.468v-4.157c0-1.948-.192-3.205-.578-3.773-.385-.568-1.385-1.065-3-1.492l-2.984-.781-.235.671c-.135.396-.203.74-.203 1.032 0 1.01.87 1.583 2.61 1.718l-1.078 3.25c-3.5-.323-5.25-1.671-5.25-4.046 0-.917.374-2.438 1.125-4.563l.968-2.703 7.125 1.734c2.313.563 3.815 1.284 4.508 2.164.693.88 1.04 2.503 1.04 4.868v6.078L182.796 287h-14.5zm-8.75 0v-14.078h-9.53l1.547-4.625h16.093l-1.547 4.625h-2.515V287h-4.047zm-28.25 0l1.392-4.234h10.468v-4.157c0-1.948-.192-3.205-.578-3.773-.385-.568-1.385-1.065-3-1.492l-2.984-.781-.235.671c-.135.396-.203.74-.203 1.032 0 1.01.87 1.583 2.61 1.718l-1.078 3.25c-3.5-.323-5.25-1.671-5.25-4.046 0-.917.374-2.438 1.125-4.563l.968-2.703 7.125 1.734c2.313.563 3.815 1.284 4.508 2.164.693.88 1.04 2.503 1.04 4.868v6.078L145.796 287h-14.5zm-7.155 4.625v-14.281c0-2.032-.36-3.344-1.079-3.938-1.041-.875-2.328-1.552-3.859-2.031l1.86-3.453c1.875.562 3.53 1.37 4.968 2.422.834.614 1.401 1.364 1.703 2.25.302.885.454 2.255.454 4.11v14.921h-4.047zM426.797 325v-10.031h4.047V325h-4.047zm13.594 0v-9.063c0-2.416-.206-3.856-.618-4.32-.411-.463-1.684-.695-3.82-.695h-12.047l1.547-4.625h11.172c3.135 0 5.23.458 6.281 1.375a3.733 3.733 0 011.235 1.984c.198.792.296 2.177.296 4.156V325h-4.046zm-36.422 0v-.516c0-2.677.281-4.971.844-6.882.562-1.912 1.51-3.8 2.843-5.664l-.703-.672c-1.135-1.063-2.161-1.948-3.078-2.657l3.688-2.687c2.447 1.937 5.036 4.51 7.765 7.719.625-1.084 1.04-2.032 1.242-2.844.203-.813.305-1.927.305-3.344v-1.156h4.328v.906c0 3.521-1.182 6.568-3.547 9.14a41.618 41.618 0 014.422 6.47l-3.484 2.578c-2.063-3.709-4.865-7.355-8.406-10.938-1.26 1.958-1.891 4.948-1.891 8.969V325h-4.328zm-4.938-6.266h-4.062c.729-2.666 1.094-4.573 1.094-5.718 0-.823-.36-1.474-1.079-1.954-.718-.479-1.963-.895-3.734-1.25l2.094-3.89c2.552.51 4.33 1.21 5.336 2.101 1.005.891 1.507 2.206 1.507 3.946 0 1.094-.25 2.76-.75 5l-.406 1.765zM376.86 325l1.563-4.625h5.515v-6.078c0-1.646-.354-2.724-1.062-3.235-.948-.677-2.224-1.244-3.828-1.703l1.844-3.437c1.729.427 3.296 1.047 4.703 1.86.906.53 1.531 1.182 1.875 1.952.344.771.515 1.922.515 3.454v7.187L386.438 325h-9.579zm-17.906 0l-2.234-17.688 3.922-1.39 1.796 14.453c2.667 0 4.795-.615 6.383-1.844 1.589-1.229 2.383-2.87 2.383-4.922 0-2.24-1.208-3.359-3.625-3.359-.646 0-1.338.104-2.078.313l1.406-4.36a9.102 9.102 0 012.235-.297c1.916 0 3.414.576 4.492 1.727 1.078 1.15 1.617 2.747 1.617 4.789 0 2.292-.698 4.437-2.094 6.437s-3.291 3.568-5.687 4.704c-2.01.958-4.85 1.437-8.516 1.437zm-9.937 0v-9.063c0-2.416-.203-3.856-.61-4.32-.406-.463-1.682-.695-3.828-.695h-5.89l1.53-4.625h5.048c3.135 0 5.229.458 6.28 1.375a3.773 3.773 0 011.22 1.992c.198.797.296 2.18.296 4.149V325h-4.046zm-24.625 0l1.562-4.625h5.516v-6.078c0-1.646-.354-2.724-1.063-3.235-.948-.677-2.224-1.244-3.828-1.703l1.844-3.437c1.729.427 3.297 1.047 4.703 1.86.906.53 1.531 1.182 1.875 1.952.344.771.516 1.922.516 3.454v7.187L333.969 325h-9.578zm-17.907 0l-2.234-17.688 3.922-1.39 1.797 14.453c2.666 0 4.794-.615 6.383-1.844 1.588-1.229 2.382-2.87 2.382-4.922 0-2.24-1.208-3.359-3.625-3.359-.645 0-1.338.104-2.078.313l1.406-4.36a9.102 9.102 0 012.235-.297c1.917 0 3.414.576 4.492 1.727 1.078 1.15 1.617 2.747 1.617 4.789 0 2.292-.698 4.437-2.094 6.437-1.395 2-3.291 3.568-5.687 4.704-2.01.958-4.849 1.437-8.516 1.437zm-32.359 0l1.547-4.625h9.64v-4.438c0-2.416-.203-3.856-.609-4.32-.406-.463-1.682-.695-3.828-.695h-5.828l1.547-4.625h4.953c3.135 0 5.229.458 6.281 1.375a3.734 3.734 0 011.235 1.992c.197.797.296 2.18.296 4.149v6.562h2.5L290.33 325h-16.204zm-19.219 0l1.36-4.14h8.468c-3.833-3.709-6.713-8.162-8.64-13.36l4.094-1.578a28.973 28.973 0 004.937 8.875c1.469-1.781 2.203-3.943 2.203-6.485v-2.015h4.344v1.312c0 3.97-1.36 7.214-4.078 9.735 1.573 1.614 2.974 2.791 4.203 3.531l-1.39 4.125h-15.5zm-8.687 0v-14.078h-9.531l1.546-4.625h16.094l-1.547 4.625h-2.515V325h-4.047zm-40.422 0v-10.031h4.047V325h-4.047zm13.594 0v-9.063c0-2.416-.206-3.856-.618-4.32-.411-.463-1.684-.695-3.82-.695h-12.047l1.547-4.625h11.172c3.135 0 5.23.458 6.281 1.375a3.733 3.733 0 011.235 1.984c.198.792.297 2.177.297 4.156V325h-4.047zm-32.813.39l1.594-4.718c2.042-.23 3.687-.839 4.937-1.828 1.948-1.542 2.922-3.406 2.922-5.594 0-.906-.32-1.52-.96-1.844-.641-.323-1.873-.484-3.696-.484h-5.563v-9.625l4.047-1.36v6.36h4.063c2.5 0 4.242.36 5.226 1.078.985.719 1.477 1.984 1.477 3.797 0 3.437-1.318 6.49-3.953 9.156-2.636 2.667-6 4.354-10.094 5.063zm-21.125 4.235v-14.61h4.047v14.61h-4.047zm-2.516-18.703l1.547-4.625h10.844c1.99 0 3.39.088 4.203.265.813.178 1.495.542 2.047 1.094.802.813 1.203 1.98 1.203 3.5 0 3.052-1.12 5.901-3.36 8.547-2.239 2.646-5.13 4.537-8.671 5.672l1.672-4.969c1.656-.469 3.036-1.383 4.14-2.742 1.105-1.36 1.657-2.82 1.657-4.383 0-.937-.328-1.565-.985-1.883-.656-.317-1.958-.476-3.906-.476h-10.39zM155.625 325v-9.594c0-1.927-.32-3.252-.96-3.976-.642-.724-1.998-1.295-4.071-1.711l2-3.797c2.448.448 4.208 1.15 5.281 2.11.708.635 1.185 1.385 1.43 2.25.244.864.367 2.25.367 4.156V325h-4.047zm-25.516 0v-14.078h-2.53l1.562-4.625h10.89c3.136 0 5.23.458 6.281 1.375a3.734 3.734 0 011.235 1.992c.198.797.297 2.18.297 4.149V325h-4.047v-9.063c0-2.416-.203-3.856-.61-4.32-.406-.463-1.682-.695-3.828-.695h-5.203V325h-4.047zm-9.656 0v-5.016h5.016V325h-5.016zm249.906 38v-10.031h4.047V363h-4.047zm13.594 0v-9.063c0-2.416-.206-3.856-.617-4.32-.412-.463-1.685-.695-3.82-.695h-12.047l1.547-4.625h11.171c3.136 0 5.23.458 6.282 1.375a3.733 3.733 0 011.234 1.984c.198.792.297 2.177.297 4.156V363h-4.047zm-36.86 0l-2.202-17.5 3.921-1.578 1.172 9.25c1.136-.281 1.933-.945 2.391-1.992.458-1.047.688-2.716.688-5.008v-1.875h4.343v1.719c0 3.093-.565 5.445-1.695 7.054-1.13 1.61-2.92 2.607-5.367 2.993l.281 2.312c2.719 0 4.89-.453 6.516-1.36 2.843-1.583 4.265-5.192 4.265-10.827v-1.891h4.328v1.562c0 5.615-1.58 9.875-4.742 12.782-3.161 2.906-7.794 4.359-13.898 4.359zm-21.937 0l1.39-4.234h10.47v-4.157c0-1.948-.193-3.205-.579-3.773-.385-.568-1.385-1.065-3-1.492l-2.984-.781-.234.671c-.136.396-.203.74-.203 1.032 0 1.01.87 1.583 2.609 1.718l-1.078 3.25c-3.5-.323-5.25-1.671-5.25-4.046 0-.917.375-2.438 1.125-4.563l.969-2.703 7.125 1.734c2.312.563 3.815 1.284 4.507 2.164.693.88 1.04 2.503 1.04 4.868v6.078L339.656 363h-14.5zm-20.656 0v-10.031h4.047V363H304.5zm13.594 0v-9.063c0-2.416-.206-3.856-.617-4.32-.412-.463-1.685-.695-3.82-.695h-12.048l1.547-4.625h11.172c3.136 0 5.23.458 6.281 1.375a3.733 3.733 0 011.235 1.984c.198.792.297 2.177.297 4.156V363h-4.047zm-48.14 0l2.265-8.688c.416-1.593.625-2.692.625-3.296 0-1.24-1.058-2.907-3.172-5l3.61-2.094c1.145.958 2.124 2.265 2.937 3.922 1.666-2.615 3.666-3.922 6-3.922 1.76 0 3.166.67 4.219 2.008 1.052 1.338 1.677 3.257 1.875 5.757l.53 6.688-1.53 4.625h-9.032l1.547-4.625h4.922l-.36-4.797c-.25-3.354-1.348-5.031-3.296-5.031-1.125 0-2.07.56-2.836 1.68-.766 1.12-1.498 3.007-2.195 5.664L274.188 363h-4.235zm-7.376 0v-9.063c0-2.416-.203-3.856-.61-4.32-.406-.463-1.682-.695-3.827-.695h-5.891l1.531-4.625h5.047c3.136 0 5.23.458 6.281 1.375a3.773 3.773 0 011.22 1.992c.197.797.296 2.18.296 4.149V363h-4.047zm-31.14 0v-14.078h-2.532l1.563-4.625h10.89c3.136 0 5.23.458 6.282 1.375a3.734 3.734 0 011.234 1.992c.198.797.297 2.18.297 4.149V363h-4.047v-9.063c0-2.416-.203-3.856-.61-4.32-.406-.463-1.682-.695-3.827-.695h-5.204V363h-4.047zm-6.344-6.266h-4.063c.73-2.666 1.094-4.573 1.094-5.718 0-.823-.36-1.474-1.078-1.954-.719-.479-1.964-.895-3.734-1.25l2.093-3.89c2.552.51 4.331 1.21 5.336 2.101s1.508 2.206 1.508 3.946c0 1.094-.25 2.76-.75 5l-.406 1.765zM198.204 363l1.546-4.625h9.64v-4.438c0-2.416-.202-3.856-.609-4.32-.406-.463-1.682-.695-3.828-.695h-5.828l1.547-4.625h4.953c3.135 0 5.23.458 6.281 1.375a3.734 3.734 0 011.235 1.992c.198.797.297 2.18.297 4.149v6.562h2.5L214.405 363h-16.203zm-21.032 0v-10.031h4.047V363h-4.047zm13.594 0v-9.063c0-2.416-.206-3.856-.618-4.32-.411-.463-1.684-.695-3.82-.695h-12.047l1.547-4.625H187c3.135 0 5.23.458 6.281 1.375a3.733 3.733 0 011.235 1.984c.198.792.297 2.177.297 4.156V363h-4.047zm-46.97 0v-.516c0-2.677.282-4.971.845-6.882.562-1.912 1.51-3.8 2.843-5.664l-.703-.672c-1.135-1.063-2.161-1.948-3.078-2.657l3.688-2.687c2.448 1.937 5.036 4.51 7.765 7.719.625-1.084 1.04-2.032 1.242-2.844.204-.813.305-1.927.305-3.344v-1.156h4.328v.906c0 3.521-1.182 6.568-3.547 9.14a41.618 41.618 0 014.422 6.47l-3.484 2.578c-2.063-3.709-4.865-7.355-8.406-10.938-1.26 1.958-1.891 4.948-1.891 8.969V363h-4.328zm-24.265 0l1.547-4.625h4.031c-.823-2.115-1.234-4.708-1.234-7.781v-1.672h-2.328l1.547-4.625h8.578c3.135 0 5.229.458 6.281 1.375a3.733 3.733 0 011.234 1.984c.198.792.297 2.177.297 4.156V363h-4.047v-9.063c0-2.416-.203-3.856-.609-4.32-.406-.463-1.682-.695-3.828-.695h-3.063v1.375c0 3.354.48 6.047 1.438 8.078L127.844 363h-8.313zm285.016 31.734h-4.063c.73-2.666 1.094-4.573 1.094-5.718 0-.823-.36-1.474-1.078-1.954-.719-.479-1.964-.895-3.734-1.25l2.093-3.89c2.552.51 4.331 1.21 5.336 2.101 1.006.891 1.508 2.206 1.508 3.946 0 1.094-.25 2.76-.75 5l-.406 1.765zM378.812 401l1.532-4.625h8.734c.865-1.99 1.297-3.682 1.297-5.078 0-1.646-.38-2.787-1.14-3.422-.761-.635-2.13-.953-4.11-.953h-5.406l1.531-4.625h5.094c1.781 0 3.15.156 4.11.469.957.312 1.77.87 2.437 1.671 1.062 1.271 1.593 3.063 1.593 5.375 0 1.844-.49 4.032-1.468 6.563L391.5 401h-12.688zm-7.453 0v-9.594c0-1.927-.32-3.252-.96-3.976-.641-.724-1.998-1.295-4.07-1.711l2-3.797c2.447.448 4.207 1.15 5.28 2.11.709.635 1.185 1.385 1.43 2.25.245.864.367 2.25.367 4.156V401h-4.047zm-20.843.39l1.593-4.718c2.042-.23 3.688-.839 4.938-1.828 1.948-1.542 2.922-3.406 2.922-5.594 0-.906-.32-1.52-.961-1.844-.64-.323-1.873-.484-3.695-.484h-5.563v-9.625l4.047-1.36v6.36h4.062c2.5 0 4.243.36 5.227 1.078.984.719 1.476 1.984 1.476 3.797 0 3.437-1.317 6.49-3.953 9.156-2.635 2.667-6 4.354-10.093 5.063zm-8.891-.39v-9.594c0-1.927-.32-3.252-.96-3.976-.642-.724-1.998-1.295-4.071-1.711l2-3.797c2.448.448 4.208 1.15 5.281 2.11.708.635 1.185 1.385 1.43 2.25.244.864.367 2.25.367 4.156V401h-4.047zm-27.75 0l1.547-4.625h4.031c-.823-2.115-1.234-4.708-1.234-7.781v-1.672h-2.328l1.546-4.625h8.579c3.135 0 5.229.458 6.28 1.375a3.733 3.733 0 011.235 1.984c.198.792.297 2.177.297 4.156V401h-4.047v-9.063c0-2.416-.203-3.856-.61-4.32-.405-.463-1.681-.695-3.827-.695h-3.063v1.375c0 3.354.48 6.047 1.438 8.078L322.188 401h-8.313zm-30.688 0l-2.203-17.5 3.922-1.578 1.172 9.25c1.136-.281 1.932-.945 2.39-1.992.46-1.047.688-2.716.688-5.008v-1.875h4.344v1.719c0 3.093-.565 5.445-1.695 7.054-1.13 1.61-2.92 2.607-5.368 2.993l.282 2.312c2.719 0 4.89-.453 6.515-1.36 2.844-1.583 4.266-5.192 4.266-10.827v-1.891h4.328v1.562c0 5.615-1.58 9.875-4.742 12.782-3.162 2.906-7.794 4.359-13.899 4.359zm-21.937 0l1.39-4.234h10.47v-4.157c0-1.948-.193-3.205-.579-3.773-.385-.568-1.385-1.065-3-1.492l-2.984-.781-.235.671c-.135.396-.203.74-.203 1.032 0 1.01.87 1.583 2.61 1.718l-1.078 3.25c-3.5-.323-5.25-1.671-5.25-4.046 0-.917.375-2.438 1.125-4.563l.968-2.703 7.125 1.734c2.313.563 3.815 1.284 4.508 2.164.693.88 1.04 2.503 1.04 4.868v6.078L275.75 401h-14.5zm-22.969 0l1.547-4.625h4.031c-.823-2.115-1.234-4.708-1.234-7.781v-1.672h-2.328l1.547-4.625h8.578c3.135 0 5.229.458 6.281 1.375a3.733 3.733 0 011.235 1.984c.197.792.296 2.177.296 4.156V401h-4.046v-9.063c0-2.416-.204-3.856-.61-4.32-.406-.463-1.682-.695-3.828-.695h-3.063v1.375c0 3.354.48 6.047 1.438 8.078L246.594 401h-8.313zm-31.234 0v-10.031h4.047V401h-4.047zm13.594 0v-9.063c0-2.416-.206-3.856-.618-4.32-.411-.463-1.684-.695-3.82-.695h-12.047l1.547-4.625h11.172c3.135 0 5.23.458 6.281 1.375a3.733 3.733 0 011.235 1.984c.198.792.297 2.177.297 4.156V401h-4.047zm-39.72 0l1.548-4.625h4.031c-.823-2.115-1.234-4.708-1.234-7.781v-1.672h-2.328l1.546-4.625h8.578c3.136 0 5.23.458 6.282 1.375a3.733 3.733 0 011.234 1.984c.198.792.297 2.177.297 4.156V401h-4.047v-9.063c0-2.416-.203-3.856-.61-4.32-.406-.463-1.682-.695-3.827-.695h-3.063v1.375c0 3.354.48 6.047 1.438 8.078L189.234 401h-8.312zm-13.171.39l-1.594-3.859c2.334-1.135 4.724-2.724 7.172-4.765l-.39-2.485c-.23-1.427-.566-2.398-1.008-2.914-.443-.515-1.42-1.075-2.93-1.68l-.89-.359 1.75-3.406c2.562.844 4.317 1.687 5.265 2.531.625.563 1.055 1.177 1.29 1.844.233.667.517 2.094.85 4.281l.61 4c.448 2.938.89 5.078 1.328 6.422h-4.36c-.28-1.115-.577-2.594-.89-4.438-1.542 1.553-3.61 3.162-6.203 4.829zm-5.375-6.656h-4.063c.73-2.666 1.094-4.573 1.094-5.718 0-.823-.36-1.474-1.078-1.954-.719-.479-1.963-.895-3.734-1.25l2.094-3.89c2.552.51 4.33 1.21 5.335 2.101 1.006.891 1.508 2.206 1.508 3.946 0 1.094-.25 2.76-.75 5l-.406 1.765zM147.281 401v-9.594c0-1.927-.32-3.252-.96-3.976-.641-.724-1.998-1.295-4.071-1.711l2-3.797c2.448.448 4.208 1.15 5.281 2.11.709.635 1.185 1.385 1.43 2.25.245.864.367 2.25.367 4.156V401h-4.047zm-27.75 0l1.547-4.625h4.031c-.823-2.115-1.234-4.708-1.234-7.781v-1.672h-2.328l1.547-4.625h8.578c3.135 0 5.229.458 6.281 1.375a3.733 3.733 0 011.234 1.984c.198.792.297 2.177.297 4.156V401h-4.047v-9.063c0-2.416-.203-3.856-.609-4.32-.406-.463-1.682-.695-3.828-.695h-3.063v1.375c0 3.354.48 6.047 1.438 8.078L127.844 401h-8.313zm218.406 38v-10.031h4.047V439h-4.046zm13.594 0v-9.063c0-2.416-.205-3.856-.617-4.32-.411-.463-1.685-.695-3.82-.695h-12.047l1.547-4.625h11.172c3.135 0 5.229.458 6.28 1.375a3.733 3.733 0 011.235 1.984c.198.792.297 2.177.297 4.156V439h-4.047zm-35.687 0l1.547-4.625h9.64v-4.438c0-2.416-.203-3.856-.61-4.32-.405-.463-1.681-.695-3.827-.695h-5.828l1.546-4.625h4.954c3.135 0 5.229.458 6.28 1.375a3.734 3.734 0 011.235 1.992c.198.797.297 2.18.297 4.149v6.562h2.5L332.047 439h-16.203zm-18.61 0l-1.765-14.078h-2.516l1.547-4.625h11.078c2.094 0 3.578.078 4.453.234.875.157 1.573.469 2.094.938.875.791 1.313 2.047 1.313 3.765 0 2.292-.662 4.506-1.985 6.641s-3.104 3.854-5.344 5.156c-2.26 1.313-5.218 1.969-8.875 1.969zm3.454-4.625c2.333-.177 4.088-.687 5.265-1.531 1.938-1.407 2.906-3.25 2.906-5.531 0-.938-.328-1.57-.984-1.899-.656-.328-1.948-.492-3.875-.492h-4.469l1.156 9.453zm-11.125-1.64H285.5c.73-2.667 1.094-4.574 1.094-5.72 0-.822-.36-1.473-1.078-1.952-.72-.48-1.964-.896-3.735-1.25l2.094-3.891c2.552.51 4.33 1.21 5.336 2.101 1.005.891 1.508 2.206 1.508 3.946 0 1.094-.25 2.76-.75 5l-.406 1.765zM263.375 439l-1.766-14.078h-2.515l1.547-4.625h11.078c2.094 0 3.578.078 4.453.234.875.157 1.573.469 2.094.938.875.791 1.312 2.047 1.312 3.765 0 2.292-.661 4.506-1.984 6.641s-3.104 3.854-5.344 5.156c-2.26 1.313-5.219 1.969-8.875 1.969zm3.453-4.625c2.333-.177 4.089-.687 5.266-1.531 1.937-1.407 2.906-3.25 2.906-5.531 0-.938-.328-1.57-.984-1.899-.657-.328-1.948-.492-3.875-.492h-4.47l1.157 9.453zm-11.125-1.64h-4.062c.729-2.667 1.093-4.574 1.093-5.72 0-.822-.359-1.473-1.078-1.952-.719-.48-1.963-.896-3.734-1.25l2.094-3.891c2.552.51 4.33 1.21 5.336 2.101 1.005.891 1.507 2.206 1.507 3.946 0 1.094-.25 2.76-.75 5l-.406 1.765zM224.781 439l1.547-4.625h4.031c-.823-2.115-1.234-4.708-1.234-7.781v-1.672h-2.328l1.547-4.625h8.578c3.135 0 5.229.458 6.281 1.375a3.733 3.733 0 011.234 1.984c.198.792.297 2.177.297 4.156V439h-4.047v-9.063c0-2.416-.203-3.856-.609-4.32-.406-.463-1.682-.695-3.828-.695h-3.063v1.375c0 3.354.48 6.047 1.438 8.078L233.094 439h-8.313zm-102.89 0v-23.125h4.812v9.39h9.563v-9.39h4.812V439h-4.812v-10.547h-9.563V439h-4.812zm30.703 0v-19.953h-8.281v-3.172h21.39v3.172h-8.281V439h-4.828zm16.344 0v-23.125h6.375l5.562 16.266 5.734-16.266h5.594V439h-4.437v-17.453l-5.641 15.906h-3.875l-5.531-16.14V439h-3.781zm29.046 0v-23.125h4.813v19.844h10.422V439h-15.235zm204.094 38v-9.594c0-1.927-.32-3.252-.96-3.976-.641-.724-1.998-1.295-4.071-1.711l2-3.797c2.448.448 4.208 1.15 5.281 2.11.708.635 1.185 1.385 1.43 2.25.245.864.367 2.25.367 4.156V477h-4.047zm-25.625 0l2.266-8.688c.416-1.593.625-2.692.625-3.296 0-1.24-1.058-2.907-3.172-5l3.61-2.094c1.145.958 2.124 2.265 2.937 3.922 1.666-2.615 3.666-3.922 6-3.922 1.76 0 3.166.67 4.219 2.008 1.052 1.338 1.677 3.257 1.875 5.757l.53 6.688-1.53 4.625h-9.032l1.547-4.625h4.922l-.36-4.797c-.25-3.354-1.348-5.031-3.296-5.031-1.125 0-2.07.56-2.836 1.68-.766 1.12-1.498 3.007-2.195 5.664L380.688 477h-4.235zm-19.984 0v-.516c0-2.677.281-4.971.844-6.882.562-1.912 1.51-3.8 2.843-5.664l-.703-.672c-1.135-1.063-2.161-1.948-3.078-2.657l3.688-2.687c2.447 1.937 5.036 4.51 7.765 7.719.625-1.084 1.04-2.032 1.242-2.844.203-.813.305-1.927.305-3.344v-1.156h4.328v.906c0 3.521-1.182 6.568-3.547 9.14a41.618 41.618 0 014.422 6.47l-3.484 2.578c-2.063-3.709-4.865-7.355-8.406-10.938-1.26 1.958-1.891 4.948-1.891 8.969V477h-4.328zm-20.328 0l1.39-4.234H348v-4.157c0-1.948-.193-3.205-.578-3.773-.386-.568-1.386-1.065-3-1.492l-2.985-.781-.234.671c-.135.396-.203.74-.203 1.032 0 1.01.87 1.583 2.61 1.718l-1.079 3.25c-3.5-.323-5.25-1.671-5.25-4.046 0-.917.375-2.438 1.125-4.563l.969-2.703 7.125 1.734c2.313.563 3.815 1.284 4.508 2.164.693.88 1.039 2.503 1.039 4.868v6.078L350.64 477h-14.5zm-20.11 0l-2.203-17.5 3.922-1.578 1.172 9.25c1.135-.281 1.932-.945 2.39-1.992.459-1.047.688-2.716.688-5.008v-1.875h4.344v1.719c0 3.093-.565 5.445-1.696 7.054-1.13 1.61-2.919 2.607-5.367 2.993l.281 2.312c2.72 0 4.891-.453 6.516-1.36 2.844-1.583 4.266-5.192 4.266-10.827v-1.891h4.328v1.562c0 5.615-1.58 9.875-4.742 12.782-3.162 2.906-7.795 4.359-13.899 4.359zm-9.969 0v-9.063c0-2.416-.203-3.856-.609-4.32-.406-.463-1.682-.695-3.828-.695h-5.89l1.53-4.625h5.048c3.135 0 5.229.458 6.28 1.375a3.773 3.773 0 011.22 1.992c.197.797.296 2.18.296 4.149V477h-4.046zm-33.375 0l1.547-4.625h4.032c-.823-2.115-1.235-4.708-1.235-7.781v-1.672h-2.328l1.547-4.625h8.578c3.136 0 5.23.458 6.281 1.375a3.733 3.733 0 011.235 1.984c.198.792.297 2.177.297 4.156V477h-4.047v-9.063c0-2.416-.203-3.856-.61-4.32-.406-.463-1.682-.695-3.828-.695h-3.062v1.375c0 3.354.479 6.047 1.437 8.078L281 477h-8.313zm-29.515 0l1.547-4.625h9.64v-4.438c0-2.416-.203-3.856-.609-4.32-.406-.463-1.682-.695-3.828-.695h-5.828l1.547-4.625h4.953c3.135 0 5.229.458 6.281 1.375a3.734 3.734 0 011.234 1.992c.198.797.297 2.18.297 4.149v6.562h2.5L259.375 477h-16.203zm-18.156 0l1.53-4.625h8.735c.865-1.99 1.297-3.682 1.297-5.078 0-1.646-.38-2.787-1.14-3.422-.76-.635-2.13-.953-4.11-.953h-5.406l1.531-4.625h5.094c1.781 0 3.15.156 4.11.469.958.312 1.77.87 2.437 1.671 1.062 1.271 1.594 3.063 1.594 5.375 0 1.844-.49 4.032-1.47 6.563L237.704 477h-12.687zm-7.547 4.625v-18.703h-10l1.547-4.625h12.5v23.328h-4.047zm-36.453-4.234l1.593-4.72c2.042-.228 3.688-.838 4.938-1.827 1.948-1.542 2.922-3.406 2.922-5.594 0-.906-.32-1.52-.961-1.844-.64-.323-1.873-.484-3.696-.484h-5.562v-9.625l4.047-1.36v6.36h4.062c2.5 0 4.243.36 5.227 1.078.984.719 1.476 1.984 1.476 3.797 0 3.437-1.317 6.49-3.953 9.156-2.635 2.667-6 4.354-10.093 5.063zm-5.391-6.657h-4.063c.73-2.666 1.094-4.573 1.094-5.718 0-.823-.36-1.474-1.078-1.954-.719-.479-1.963-.895-3.734-1.25l2.094-3.89c2.552.51 4.33 1.21 5.335 2.101 1.006.891 1.508 2.206 1.508 3.946 0 1.094-.25 2.76-.75 5l-.406 1.765zM148.828 477l1.36-4.14h8.468c-3.833-3.709-6.713-8.162-8.64-13.36l4.093-1.578a28.973 28.973 0 004.938 8.875c1.469-1.781 2.203-3.943 2.203-6.485v-2.015h4.344v1.312c0 3.97-1.36 7.214-4.078 9.735 1.573 1.614 2.974 2.791 4.203 3.531l-1.39 4.125h-15.5zm-7.078 0v-9.594c0-1.927-.32-3.252-.96-3.976-.642-.724-1.998-1.295-4.071-1.711l2-3.797c2.448.448 4.208 1.15 5.281 2.11.708.635 1.185 1.385 1.43 2.25.244.864.367 2.25.367 4.156V477h-4.047zm-11.844 0v-9.063c0-2.416-.203-3.856-.61-4.32-.405-.463-1.681-.695-3.827-.695h-5.89l1.53-4.625h5.047c3.136 0 5.23.458 6.281 1.375a3.773 3.773 0 011.22 1.992c.197.797.296 2.18.296 4.149V477h-4.047zm207.406 31.734h-4.062c.73-2.666 1.094-4.573 1.094-5.718 0-.823-.36-1.474-1.078-1.954-.72-.479-1.964-.895-3.735-1.25l2.094-3.89c2.552.51 4.33 1.21 5.336 2.101 1.005.891 1.508 2.206 1.508 3.946 0 1.094-.25 2.76-.75 5l-.406 1.765zm-11.593 0h-4.063c.73-2.666 1.094-4.573 1.094-5.718 0-.823-.36-1.474-1.078-1.954-.719-.479-1.964-.895-3.735-1.25l2.094-3.89c2.552.51 4.331 1.21 5.336 2.101s1.508 2.206 1.508 3.946c0 1.094-.25 2.76-.75 5l-.406 1.765zM297.656 515l-2.203-17.5 3.922-1.578 1.172 9.25c1.135-.281 1.932-.945 2.39-1.992.459-1.047.688-2.716.688-5.008v-1.875h4.344v1.719c0 3.093-.565 5.445-1.696 7.054-1.13 1.61-2.919 2.607-5.367 2.993l.281 2.312c2.72 0 4.891-.453 6.516-1.36 2.844-1.583 4.266-5.192 4.266-10.827v-1.891h4.328v1.562c0 5.615-1.58 9.875-4.742 12.782-3.162 2.906-7.795 4.359-13.899 4.359zm-10.047 0v-9.594c0-1.927-.32-3.252-.96-3.976-.641-.724-1.998-1.295-4.07-1.711l2-3.797c2.447.448 4.207 1.15 5.28 2.11.709.635 1.185 1.385 1.43 2.25.245.864.367 2.25.367 4.156V515h-4.047zm-25.625 0l2.266-8.688c.417-1.593.625-2.692.625-3.296 0-1.24-1.057-2.907-3.172-5l3.61-2.094c1.145.958 2.124 2.265 2.937 3.922 1.667-2.615 3.667-3.922 6-3.922 1.76 0 3.167.67 4.219 2.008 1.052 1.338 1.677 3.257 1.875 5.757l.531 6.688-1.531 4.625h-9.031l1.546-4.625h4.922l-.36-4.797c-.25-3.354-1.348-5.031-3.296-5.031-1.125 0-2.07.56-2.836 1.68-.766 1.12-1.497 3.007-2.195 5.664L266.219 515h-4.235zm-3.953-6.266h-4.062c.729-2.666 1.094-4.573 1.094-5.718 0-.823-.36-1.474-1.079-1.954-.718-.479-1.963-.895-3.734-1.25l2.094-3.89c2.552.51 4.33 1.21 5.336 2.101 1.005.891 1.507 2.206 1.507 3.946 0 1.094-.25 2.76-.75 5l-.406 1.765zM219.86 515v-.516c0-2.677.282-4.971.844-6.882.563-1.912 1.51-3.8 2.844-5.664l-.703-.672c-1.136-1.063-2.162-1.948-3.078-2.657l3.687-2.687c2.448 1.937 5.037 4.51 7.766 7.719.625-1.084 1.039-2.032 1.242-2.844.203-.813.305-1.927.305-3.344v-1.156h4.328v.906c0 3.521-1.183 6.568-3.547 9.14a41.618 41.618 0 014.422 6.47l-3.485 2.578c-2.062-3.709-4.864-7.355-8.406-10.938-1.26 1.958-1.89 4.948-1.89 8.969V515h-4.329zm-4.937-6.266h-4.063c.73-2.666 1.094-4.573 1.094-5.718 0-.823-.36-1.474-1.078-1.954-.719-.479-1.964-.895-3.734-1.25l2.093-3.89c2.552.51 4.331 1.21 5.336 2.101 1.006.891 1.508 2.206 1.508 3.946 0 1.094-.25 2.76-.75 5l-.406 1.765zM192.75 515l1.563-4.625h5.515v-6.078c0-1.646-.354-2.724-1.062-3.235-.948-.677-2.224-1.244-3.828-1.703l1.843-3.437c1.73.427 3.297 1.047 4.703 1.86.907.53 1.532 1.182 1.875 1.952.344.771.516 1.922.516 3.454v7.187L202.328 515h-9.578zm-17.906 0l-2.235-17.688 3.922-1.39 1.797 14.453c2.667 0 4.794-.615 6.383-1.844 1.588-1.229 2.383-2.87 2.383-4.922 0-2.24-1.209-3.359-3.625-3.359-.646 0-1.339.104-2.078.313l1.406-4.36a9.102 9.102 0 012.234-.297c1.917 0 3.414.576 4.492 1.727 1.079 1.15 1.618 2.747 1.618 4.789 0 2.292-.698 4.437-2.094 6.437s-3.292 3.568-5.688 4.704c-2.01.958-4.849 1.437-8.515 1.437zm-9.938 0v-9.063c0-2.416-.203-3.856-.61-4.32-.405-.463-1.681-.695-3.827-.695h-5.89l1.53-4.625h5.047c3.136 0 5.23.458 6.281 1.375a3.773 3.773 0 011.22 1.992c.197.797.296 2.18.296 4.149V515h-4.047zm-24.625 0l1.563-4.625h5.515v-6.078c0-1.646-.354-2.724-1.062-3.235-.948-.677-2.224-1.244-3.828-1.703l1.844-3.437c1.729.427 3.296 1.047 4.703 1.86.906.53 1.53 1.182 1.875 1.952.343.771.515 1.922.515 3.454v7.187L149.86 515h-9.578zm-17.906 0l-2.234-17.688 3.921-1.39 1.797 14.453c2.667 0 4.795-.615 6.383-1.844 1.589-1.229 2.383-2.87 2.383-4.922 0-2.24-1.208-3.359-3.625-3.359-.646 0-1.339.104-2.078.313l1.406-4.36a9.102 9.102 0 012.234-.297c1.917 0 3.415.576 4.493 1.727 1.078 1.15 1.617 2.747 1.617 4.789 0 2.292-.698 4.437-2.094 6.437s-3.292 3.568-5.687 4.704c-2.01.958-4.85 1.437-8.516 1.437zm212.14 38l2.266-8.688c.417-1.593.625-2.692.625-3.296 0-1.24-1.057-2.907-3.172-5l3.61-2.094c1.146.958 2.125 2.265 2.937 3.922 1.667-2.615 3.667-3.922 6-3.922 1.76 0 3.167.67 4.219 2.008 1.052 1.338 1.677 3.257 1.875 5.758l.531 6.687-1.531 4.625h-9.031l1.547-4.625h4.921l-.359-4.797c-.25-3.354-1.349-5.031-3.297-5.031-1.125 0-2.07.56-2.836 1.68-.765 1.12-1.497 3.007-2.195 5.664L338.75 553h-4.234zm-23.28 0l1.546-4.625h4.031c-.822-2.115-1.234-4.708-1.234-7.781v-1.672h-2.328l1.547-4.625h8.578c3.135 0 5.23.458 6.281 1.375a3.733 3.733 0 011.235 1.984c.198.792.296 2.177.296 4.157V553h-4.046v-9.063c0-2.416-.203-3.856-.61-4.32-.406-.463-1.682-.695-3.828-.695h-3.062v1.375c0 3.354.479 6.047 1.437 8.078L319.547 553h-8.313zm-7.173 0v-9.594c0-1.927-.32-3.252-.96-3.976-.641-.724-1.998-1.295-4.07-1.711l2-3.797c2.447.448 4.208 1.15 5.28 2.11.709.635 1.185 1.385 1.43 2.25.245.864.367 2.25.367 4.155V553h-4.046zm-25.515 0v-14.078h-2.531l1.562-4.625h10.89c3.136 0 5.23.458 6.282 1.375a3.734 3.734 0 011.234 1.992c.198.797.297 2.18.297 4.149V553h-4.047v-9.063c0-2.416-.203-3.856-.609-4.32-.406-.463-1.682-.695-3.828-.695h-5.203V553h-4.047zm-20.485 0l1.532-4.625h8.734c.865-1.99 1.297-3.682 1.297-5.078 0-1.646-.38-2.787-1.14-3.422-.761-.635-2.13-.953-4.11-.953h-5.406l1.531-4.625h5.094c1.781 0 3.15.156 4.11.469.957.312 1.77.87 2.437 1.672 1.062 1.27 1.593 3.062 1.593 5.375 0 1.843-.49 4.03-1.468 6.562L270.75 553h-12.688zm-21.156 0l2.266-8.688c.417-1.593.625-2.692.625-3.296 0-1.24-1.057-2.907-3.172-5l3.61-2.094c1.145.958 2.124 2.265 2.937 3.922 1.667-2.615 3.667-3.922 6-3.922 1.76 0 3.167.67 4.219 2.008 1.052 1.338 1.677 3.257 1.875 5.758l.53 6.687-1.53 4.625h-9.032l1.547-4.625h4.922l-.36-4.797c-.25-3.354-1.348-5.031-3.296-5.031-1.125 0-2.07.56-2.836 1.68-.766 1.12-1.497 3.007-2.195 5.664L241.14 553h-4.235zm-3.953-6.266h-4.062c.729-2.666 1.093-4.573 1.093-5.718 0-.823-.359-1.474-1.078-1.953-.719-.48-1.963-.896-3.734-1.25l2.094-3.891c2.552.51 4.33 1.21 5.336 2.101 1.005.891 1.507 2.206 1.507 3.946 0 1.094-.25 2.76-.75 5l-.406 1.765zM204.641 553v-14.078h-2.485l1.547-4.625h10.484c3.136 0 5.23.458 6.282 1.375a3.734 3.734 0 011.234 1.992c.198.797.297 2.18.297 4.149v6.562L220.453 553h-15.812zm4.062-4.625h9.25v-4.438c0-2.427-.203-3.87-.61-4.328-.406-.458-1.682-.687-3.827-.687h-4.813v9.453zm-20.953-1.64h-4.063c.73-2.667 1.094-4.574 1.094-5.72 0-.822-.36-1.473-1.078-1.952-.719-.48-1.963-.896-3.734-1.25l2.094-3.891c2.552.51 4.33 1.21 5.335 2.101 1.006.891 1.508 2.206 1.508 3.946 0 1.094-.25 2.76-.75 5l-.406 1.765zM172.656 553v-9.594c0-1.927-.32-3.252-.96-3.976-.641-.724-1.998-1.295-4.071-1.711l2-3.797c2.448.448 4.208 1.15 5.281 2.11.709.635 1.185 1.385 1.43 2.25.245.864.367 2.25.367 4.155V553h-4.047zm-27.75 0l1.547-4.625h4.031c-.823-2.115-1.234-4.708-1.234-7.781v-1.672h-2.328l1.547-4.625h8.578c3.135 0 5.229.458 6.281 1.375a3.733 3.733 0 011.234 1.984c.198.792.297 2.177.297 4.157V553h-4.047v-9.063c0-2.416-.203-3.856-.609-4.32-.406-.463-1.682-.695-3.828-.695h-3.063v1.375c0 3.354.48 6.047 1.438 8.078L153.219 553h-8.313zm-7.094 0v-9.063c0-2.416-.203-3.856-.609-4.32-.406-.463-1.682-.695-3.828-.695h-5.89l1.53-4.625h5.048c3.135 0 5.229.458 6.28 1.375a3.773 3.773 0 011.22 1.992c.197.797.296 2.18.296 4.149V553h-4.047zm-17.359 0v-5.016h5.016V553h-5.016zm322.688 38.39l1.593-4.718c2.042-.23 3.688-.839 4.938-1.828 1.948-1.542 2.922-3.406 2.922-5.594 0-.906-.32-1.52-.961-1.844-.64-.323-1.873-.484-3.695-.484h-5.563v-9.625l4.047-1.36v6.36h4.062c2.5 0 4.243.36 5.227 1.078.984.719 1.476 1.984 1.476 3.797 0 3.437-1.317 6.49-3.953 9.156-2.635 2.667-6 4.354-10.093 5.063zm-22.594-.39l2.265-8.688c.417-1.593.625-2.692.625-3.296 0-1.24-1.057-2.907-3.171-5l3.609-2.094c1.146.958 2.125 2.265 2.938 3.922 1.666-2.615 3.666-3.922 6-3.922 1.76 0 3.166.67 4.218 2.008s1.677 3.257 1.875 5.758l.531 6.687-1.53 4.625h-9.032l1.547-4.625h4.922l-.36-4.797c-.25-3.354-1.349-5.031-3.296-5.031-1.126 0-2.07.56-2.836 1.68-.766 1.12-1.498 3.007-2.196 5.664L424.781 591h-4.234zm-21.625 2.781l1.562-4.672c1.667 0 3.459-.28 5.375-.843L402.5 573.28l3.875-1.36 3.266 14.563c1.635-1.24 2.773-2.78 3.414-4.625.64-1.843.96-4.484.96-7.922v-1.64h4.344v1.156c0 3.99-.612 7.318-1.836 9.985-1.224 2.666-3.153 4.88-5.789 6.64-3.698 2.469-7.635 3.703-11.812 3.703zM380.078 591l-2.203-17.5 3.922-1.578 1.172 9.25c1.135-.281 1.932-.945 2.39-1.992.459-1.047.688-2.716.688-5.008v-1.875h4.344v1.719c0 3.093-.565 5.445-1.696 7.054-1.13 1.61-2.919 2.607-5.367 2.992l.281 2.313c2.72 0 4.891-.453 6.516-1.36 2.844-1.583 4.266-5.192 4.266-10.827v-1.891h4.328v1.562c0 5.615-1.581 9.875-4.742 12.782-3.162 2.906-7.795 4.359-13.899 4.359zm-23.562 0v-10.031h4.046V591h-4.046zm13.593 0v-9.063c0-2.416-.205-3.856-.617-4.32-.411-.463-1.685-.695-3.82-.695h-12.047l1.547-4.625h11.172c3.135 0 5.229.458 6.281 1.375a3.733 3.733 0 011.234 1.984c.198.792.297 2.177.297 4.157V591h-4.047zm-21.78 0h-1.923v-5.016h5.016v3.891c0 4.094-1.672 6.14-5.016 6.14v-1.734c1.282 0 1.922-.927 1.922-2.781v-.5zm-31.204 0l1.531-4.625h8.735c.864-1.99 1.296-3.682 1.296-5.078 0-1.646-.38-2.787-1.14-3.422-.76-.635-2.13-.953-4.11-.953h-5.406l1.531-4.625h5.094c1.782 0 3.151.156 4.11.469.958.312 1.77.87 2.437 1.672 1.063 1.27 1.594 3.062 1.594 5.375 0 1.843-.49 4.03-1.469 6.562L329.813 591h-12.688zm-3.953-6.266h-4.063c.73-2.666 1.094-4.573 1.094-5.718 0-.823-.36-1.474-1.078-1.953-.719-.48-1.964-.896-3.734-1.25l2.093-3.891c2.552.51 4.331 1.21 5.336 2.101 1.006.891 1.508 2.206 1.508 3.946 0 1.094-.25 2.76-.75 5l-.406 1.765zM298.078 591v-9.594c0-1.927-.32-3.252-.96-3.976-.641-.724-1.998-1.295-4.071-1.711l2-3.797c2.448.448 4.208 1.15 5.281 2.11.708.635 1.185 1.385 1.43 2.25.245.864.367 2.25.367 4.155V591h-4.047zm-25.14 0v-14.078h-2.485l1.547-4.625h10.484c3.136 0 5.23.458 6.282 1.375a3.734 3.734 0 011.234 1.992c.198.797.297 2.18.297 4.149v6.562L288.75 591h-15.813zm4.062-4.625h9.25v-4.438c0-2.427-.203-3.87-.61-4.328-.406-.458-1.682-.687-3.827-.687H277v9.453zM265.203 591h-1.922v-5.016h5.016v3.891c0 4.094-1.672 6.14-5.016 6.14v-1.734c1.282 0 1.922-.927 1.922-2.781v-.5zm-20.484 0v-9.063c0-2.416-.203-3.856-.61-4.32-.406-.463-1.682-.695-3.828-.695h-5.89l1.53-4.625h5.048c3.135 0 5.229.458 6.281 1.375a3.773 3.773 0 011.219 1.992c.198.797.297 2.18.297 4.149V591h-4.047zm-17.547 0v-9.594c0-1.927-.32-3.252-.961-3.976-.64-.724-1.997-1.295-4.07-1.711l2-3.797c2.448.448 4.208 1.15 5.28 2.11.71.635 1.186 1.385 1.43 2.25.245.864.368 2.25.368 4.155V591h-4.047zm-23.719 0l1.547-4.625h9.64v-4.438c0-2.416-.202-3.856-.609-4.32-.406-.463-1.682-.695-3.828-.695h-5.828l1.547-4.625h4.953c3.135 0 5.23.458 6.281 1.375a3.734 3.734 0 011.235 1.992c.198.797.297 2.18.297 4.149v6.562h2.5L219.655 591h-16.203zm-30.594 0v-.516c0-2.677.282-4.971.844-6.882.563-1.912 1.51-3.8 2.844-5.664l-.703-.672c-1.136-1.063-2.162-1.948-3.078-2.657l3.687-2.687c2.448 1.937 5.037 4.51 7.766 7.719.625-1.084 1.039-2.032 1.242-2.844.203-.813.305-1.927.305-3.344v-1.156h4.328v.906c0 3.521-1.183 6.568-3.547 9.14a41.618 41.618 0 014.422 6.47l-3.485 2.578c-2.062-3.709-4.864-7.355-8.406-10.938-1.26 1.958-1.89 4.948-1.89 8.969V591h-4.329zm-24.265 0l1.547-4.625h4.03c-.822-2.115-1.233-4.708-1.233-7.781v-1.672h-2.329l1.547-4.625h8.578c3.136 0 5.23.458 6.282 1.375a3.733 3.733 0 011.234 1.984c.198.792.297 2.177.297 4.157V591H164.5v-9.063c0-2.416-.203-3.856-.61-4.32-.406-.463-1.682-.695-3.827-.695H157v1.375c0 3.354.48 6.047 1.438 8.078L156.905 591h-8.312zm-7.094 0v-9.063c0-2.416-.203-3.856-.61-4.32-.406-.463-1.682-.695-3.827-.695h-5.891l1.531-4.625h5.047c3.135 0 5.23.458 6.281 1.375a3.773 3.773 0 011.219 1.992c.198.797.297 2.18.297 4.149V591H141.5zm-14.047-6.266h-4.062c.729-2.666 1.093-4.573 1.093-5.718 0-.823-.359-1.474-1.078-1.953-.719-.48-1.963-.896-3.734-1.25l2.094-3.891c2.552.51 4.33 1.21 5.336 2.101 1.005.891 1.507 2.206 1.507 3.946 0 1.094-.25 2.76-.75 5l-.406 1.765zM406.016 629v-10.031h4.046V629h-4.046zm13.593 0v-9.063c0-2.416-.205-3.856-.617-4.32-.411-.463-1.685-.695-3.82-.695h-12.047l1.547-4.625h11.172c3.135 0 5.229.458 6.281 1.375a3.733 3.733 0 011.234 1.984c.198.792.297 2.177.297 4.157V629h-4.047zm-36.421 0v-.516c0-2.677.28-4.971.843-6.882.563-1.912 1.51-3.8 2.844-5.664l-.703-.672c-1.136-1.063-2.162-1.948-3.078-2.657l3.687-2.687c2.448 1.937 5.037 4.51 7.766 7.719.625-1.084 1.039-2.032 1.242-2.844.203-.813.305-1.927.305-3.344v-1.156h4.328v.906c0 3.521-1.182 6.568-3.547 9.14a41.618 41.618 0 014.422 6.47l-3.485 2.578c-2.062-3.709-4.864-7.355-8.406-10.938-1.26 1.958-1.89 4.948-1.89 8.969V629h-4.329zm-4.938-6.266h-4.063c.73-2.666 1.094-4.573 1.094-5.718 0-.823-.36-1.474-1.078-1.953-.719-.48-1.963-.896-3.734-1.25l2.094-3.891c2.552.51 4.33 1.21 5.335 2.101 1.006.891 1.508 2.206 1.508 3.946 0 1.094-.25 2.76-.75 5l-.406 1.765zM356.078 629l1.563-4.625h5.515v-6.078c0-1.646-.354-2.724-1.062-3.235-.948-.677-2.224-1.244-3.828-1.703l1.843-3.437c1.73.427 3.297 1.047 4.704 1.86.906.53 1.53 1.182 1.875 1.952.343.771.515 1.922.515 3.453v7.188L365.656 629h-9.578zm-17.906 0l-2.235-17.688 3.922-1.39 1.797 14.453c2.667 0 4.795-.615 6.383-1.844 1.589-1.229 2.383-2.87 2.383-4.922 0-2.24-1.208-3.359-3.625-3.359-.646 0-1.339.104-2.078.313l1.406-4.36a9.102 9.102 0 012.234-.297c1.917 0 3.414.576 4.493 1.727 1.078 1.15 1.617 2.747 1.617 4.789 0 2.292-.698 4.437-2.094 6.437s-3.292 3.568-5.688 4.703c-2.01.959-4.848 1.438-8.515 1.438zm-9.938 0v-9.063c0-2.416-.203-3.856-.609-4.32-.406-.463-1.682-.695-3.828-.695h-5.89l1.53-4.625h5.047c3.136 0 5.23.458 6.282 1.375a3.773 3.773 0 011.218 1.992c.198.797.297 2.18.297 4.149V629h-4.047zm-24.625 0l1.563-4.625h5.515v-6.078c0-1.646-.354-2.724-1.062-3.235-.948-.677-2.224-1.244-3.828-1.703l1.844-3.437c1.729.427 3.296 1.047 4.703 1.86.906.53 1.531 1.182 1.875 1.952.344.771.515 1.922.515 3.453v7.188L313.188 629h-9.579zm-17.906 0l-2.234-17.688 3.922-1.39 1.796 14.453c2.667 0 4.795-.615 6.383-1.844 1.589-1.229 2.383-2.87 2.383-4.922 0-2.24-1.208-3.359-3.625-3.359-.646 0-1.338.104-2.078.313l1.406-4.36a9.102 9.102 0 012.235-.297c1.916 0 3.414.576 4.492 1.727 1.078 1.15 1.617 2.747 1.617 4.789 0 2.292-.698 4.437-2.094 6.437s-3.291 3.568-5.687 4.703c-2.01.959-4.85 1.438-8.516 1.438zm-34.078 0v-10.031h4.047V629h-4.047zm13.594 0v-9.063c0-2.416-.206-3.856-.617-4.32-.412-.463-1.685-.695-3.82-.695h-12.048l1.547-4.625h11.172c3.136 0 5.23.458 6.281 1.375a3.733 3.733 0 011.235 1.984c.198.792.297 2.177.297 4.157V629h-4.047zm-37.594 0l2.266-8.688c.416-1.593.625-2.692.625-3.296 0-1.24-1.058-2.907-3.172-5l3.61-2.094c1.145.958 2.124 2.265 2.937 3.922 1.666-2.615 3.666-3.922 6-3.922 1.76 0 3.166.67 4.218 2.008s1.677 3.257 1.875 5.758l.532 6.687-1.532 4.625h-9.03l1.546-4.625h4.922l-.36-4.797c-.25-3.354-1.348-5.031-3.296-5.031-1.125 0-2.07.56-2.836 1.68-.766 1.12-1.498 3.007-2.196 5.664L231.86 629h-4.234zm-7.453 0v-9.594c0-1.927-.32-3.252-.961-3.976-.64-.724-1.997-1.295-4.07-1.711l2-3.797c2.448.448 4.208 1.15 5.28 2.11.71.635 1.186 1.385 1.43 2.25.245.864.368 2.25.368 4.155V629h-4.047zm-13.531 0v-14.078h-9.532l1.547-4.625h16.094l-1.547 4.625h-2.516V629h-4.046zm-16.282 0v-9.063c0-2.416-.203-3.856-.609-4.32-.406-.463-1.682-.695-3.828-.695h-5.89l1.53-4.625h5.047c3.136 0 5.23.458 6.282 1.375a3.773 3.773 0 011.218 1.992c.198.797.297 2.18.297 4.149V629h-4.047zm-24.625 0l1.563-4.625h5.516v-6.078c0-1.646-.355-2.724-1.063-3.235-.948-.677-2.224-1.244-3.828-1.703l1.844-3.437c1.729.427 3.296 1.047 4.703 1.86.906.53 1.531 1.182 1.875 1.952.344.771.515 1.922.515 3.453v7.188L175.312 629h-9.578zm-3.625-6.266h-4.062c.729-2.666 1.094-4.573 1.094-5.718 0-.823-.36-1.474-1.078-1.953-.72-.48-1.964-.896-3.735-1.25l2.094-3.891c2.552.51 4.33 1.21 5.336 2.101 1.005.891 1.508 2.206 1.508 3.946 0 1.094-.25 2.76-.75 5l-.407 1.765zm-11.593 0h-4.063c.73-2.666 1.094-4.573 1.094-5.718 0-.823-.36-1.474-1.078-1.953-.719-.48-1.964-.896-3.735-1.25l2.094-3.891c2.552.51 4.33 1.21 5.336 2.101 1.005.891 1.508 2.206 1.508 3.946 0 1.094-.25 2.76-.75 5l-.406 1.765zM122.203 629v-14.078h-2.484l1.547-4.625h10.484c3.135 0 5.23.458 6.281 1.375a3.734 3.734 0 011.235 1.992c.198.797.297 2.18.297 4.149v6.562L138.016 629h-15.813zm4.063-4.625h9.25v-4.438c0-2.427-.203-3.87-.61-4.328-.406-.458-1.682-.687-3.828-.687h-4.812v9.453zM401.844 667l2.265-8.688c.417-1.593.625-2.692.625-3.296 0-1.24-1.057-2.907-3.171-5l3.609-2.094c1.146.958 2.125 2.265 2.937 3.922 1.667-2.615 3.667-3.922 6-3.922 1.76 0 3.167.67 4.22 2.008 1.051 1.338 1.676 3.257 1.874 5.758l.531 6.687-1.53 4.625h-9.032l1.547-4.625h4.922l-.36-4.797c-.25-3.354-1.349-5.031-3.297-5.031-1.125 0-2.07.56-2.836 1.68-.765 1.12-1.497 3.007-2.195 5.664L406.078 667h-4.234zm-20.422 0l-2.203-17.5 3.922-1.578 1.171 9.25c1.136-.281 1.933-.945 2.391-1.992.458-1.047.688-2.716.688-5.008v-1.875h4.343v1.719c0 3.093-.565 5.445-1.695 7.054-1.13 1.61-2.92 2.607-5.367 2.992l.281 2.313c2.719 0 4.89-.453 6.516-1.36 2.844-1.583 4.265-5.192 4.265-10.827v-1.891h4.329v1.562c0 5.615-1.581 9.875-4.743 12.782-3.161 2.906-7.794 4.359-13.898 4.359zm-18.969.39l1.594-4.718c2.042-.23 3.687-.839 4.937-1.828 1.948-1.542 2.922-3.406 2.922-5.594 0-.906-.32-1.52-.96-1.844-.641-.323-1.873-.484-3.696-.484h-5.563v-9.625l4.047-1.36v6.36h4.063c2.5 0 4.242.36 5.226 1.078.985.719 1.477 1.984 1.477 3.797 0 3.437-1.318 6.49-3.953 9.156-2.636 2.667-6 4.354-10.094 5.063zm-20.687-.39l1.546-4.625h9.641v-4.438c0-2.416-.203-3.856-.61-4.32-.406-.463-1.682-.695-3.827-.695h-5.829l1.547-4.625h4.954c3.135 0 5.229.458 6.28 1.375a3.734 3.734 0 011.235 1.992c.198.797.297 2.18.297 4.149v6.562h2.5L357.969 667h-16.203zm-4.016-6.266h-4.063c.73-2.666 1.094-4.573 1.094-5.718 0-.823-.36-1.474-1.078-1.953-.719-.48-1.963-.896-3.734-1.25l2.094-3.891c2.552.51 4.33 1.21 5.335 2.101 1.006.891 1.508 2.206 1.508 3.946 0 1.094-.25 2.76-.75 5l-.406 1.765zM309.437 667v-14.078h-2.484l1.547-4.625h10.484c3.136 0 5.23.458 6.282 1.375a3.734 3.734 0 011.234 1.992c.198.797.297 2.18.297 4.149v6.562L325.25 667h-15.813zm4.063-4.625h9.25v-4.438c0-2.427-.203-3.87-.61-4.328-.406-.458-1.682-.687-3.827-.687H313.5v9.453zM276.078 667l-2.203-17.5 3.922-1.578 1.172 9.25c1.135-.281 1.932-.945 2.39-1.992.459-1.047.688-2.716.688-5.008v-1.875h4.344v1.719c0 3.093-.565 5.445-1.696 7.054-1.13 1.61-2.919 2.607-5.367 2.992l.281 2.313c2.72 0 4.891-.453 6.516-1.36 2.844-1.583 4.266-5.192 4.266-10.827v-1.891h4.328v1.562c0 5.615-1.581 9.875-4.742 12.782-3.162 2.906-7.795 4.359-13.899 4.359zm-21.937 0l1.39-4.234H266v-4.157c0-1.948-.193-3.205-.578-3.773-.386-.568-1.386-1.065-3-1.492l-2.985-.782-.234.672c-.135.396-.203.74-.203 1.032 0 1.01.87 1.583 2.61 1.718l-1.079 3.25c-3.5-.323-5.25-1.671-5.25-4.047 0-.916.375-2.437 1.125-4.562l.969-2.703 7.125 1.734c2.313.563 3.815 1.284 4.508 2.164.693.88 1.039 2.503 1.039 4.867v6.079L268.64 667h-14.5zm-20.657 0v-10.031h4.047V667h-4.047zm13.594 0v-9.063c0-2.416-.206-3.856-.617-4.32-.412-.463-1.685-.695-3.82-.695h-12.047l1.547-4.625h11.172c3.135 0 5.229.458 6.28 1.375a3.733 3.733 0 011.235 1.984c.198.792.297 2.177.297 4.157V667h-4.047zm-38.234 0v-7.016c0-2.354.682-4.343 2.047-5.968L206 651.938l1.719-4.016 11.094 4.719-1.672 4.062-3.125-1.36c-.75.938-1.125 2.256-1.125 3.954V667h-4.047zm-9.735 0v-9.594c0-1.927-.32-3.252-.96-3.976-.641-.724-1.998-1.295-4.07-1.711l2-3.797c2.447.448 4.207 1.15 5.28 2.11.709.635 1.185 1.385 1.43 2.25.245.864.367 2.25.367 4.155V667h-4.047zm-11.734 0v-5.016h5.016V667h-5.016zm-32.156 0v-10.031h4.047V667h-4.047zm13.594 0v-9.063c0-2.416-.206-3.856-.618-4.32-.411-.463-1.685-.695-3.82-.695h-12.047l1.547-4.625h11.172c3.135 0 5.229.458 6.281 1.375a3.733 3.733 0 011.234 1.984c.198.792.297 2.177.297 4.157V667h-4.047zm-20.391-6.266h-4.063c.73-2.666 1.094-4.573 1.094-5.718 0-.823-.36-1.474-1.078-1.953-.719-.48-1.964-.896-3.734-1.25l2.093-3.891c2.552.51 4.331 1.21 5.336 2.101 1.006.891 1.508 2.206 1.508 3.946 0 1.094-.25 2.76-.75 5l-.406 1.765zM120.797 667v-.516c0-2.677.281-4.971.844-6.882.562-1.912 1.51-3.8 2.843-5.664l-.703-.672c-1.135-1.063-2.161-1.948-3.078-2.657l3.688-2.687c2.448 1.937 5.036 4.51 7.765 7.719.625-1.084 1.04-2.032 1.242-2.844.204-.813.305-1.927.305-3.344v-1.156h4.328v.906c0 3.521-1.182 6.568-3.547 9.14a41.618 41.618 0 014.422 6.47l-3.484 2.578c-2.063-3.709-4.865-7.355-8.406-10.938-1.26 1.958-1.891 4.948-1.891 8.969V667h-4.328zm275.937 31.734h-4.062c.729-2.666 1.094-4.573 1.094-5.718 0-.823-.36-1.474-1.079-1.953-.718-.48-1.963-.896-3.734-1.25l2.094-3.891c2.552.51 4.33 1.21 5.336 2.101 1.005.891 1.508 2.206 1.508 3.946 0 1.094-.25 2.76-.75 5l-.407 1.765zM380.031 705v-14.078H370.5l1.547-4.625h16.094l-1.547 4.625h-2.516V705h-4.047zm-16.36 0v-9.594c0-1.927-.32-3.252-.96-3.976-.64-.724-1.997-1.295-4.07-1.711l2-3.797c2.448.448 4.208 1.15 5.28 2.11.71.635 1.186 1.385 1.43 2.25.245.864.368 2.25.368 4.155V705h-4.047zm-26.093 2.781l1.563-4.672c1.666 0 3.458-.28 5.375-.843l-3.36-14.985 3.875-1.36 3.266 14.563c1.635-1.24 2.773-2.78 3.414-4.625.64-1.843.96-4.484.96-7.922v-1.64h4.345v1.156c0 3.99-.612 7.318-1.836 9.985-1.224 2.666-3.154 4.88-5.79 6.64-3.697 2.469-7.635 3.703-11.812 3.703zM318.188 705v-10.031h4.046V705h-4.046zm13.593 0v-9.063c0-2.416-.205-3.856-.617-4.32-.411-.463-1.685-.695-3.82-.695h-12.047l1.547-4.625h11.172c3.135 0 5.229.458 6.28 1.375a3.733 3.733 0 011.235 1.984c.198.792.297 2.177.297 4.157V705h-4.047zm-46.234 0l1.547-4.625h9.64v-4.438c0-2.416-.203-3.856-.609-4.32-.406-.463-1.682-.695-3.828-.695h-5.828l1.547-4.625h4.953c3.135 0 5.229.458 6.281 1.375a3.734 3.734 0 011.234 1.992c.198.797.297 2.18.297 4.149v6.562h2.5L301.75 705h-16.203zm-21.688 2.781l1.563-4.672c1.667 0 3.458-.28 5.375-.843l-3.36-14.985 3.875-1.36 3.266 14.563c1.636-1.24 2.774-2.78 3.414-4.625.64-1.843.961-4.484.961-7.922v-1.64h4.344v1.156c0 3.99-.612 7.318-1.836 9.985-1.224 2.666-3.154 4.88-5.79 6.64-3.697 2.469-7.635 3.703-11.812 3.703zm-2.375-9.047h-4.062c.729-2.666 1.094-4.573 1.094-5.718 0-.823-.36-1.474-1.079-1.953-.718-.48-1.963-.896-3.734-1.25l2.094-3.891c2.552.51 4.33 1.21 5.336 2.101 1.005.891 1.508 2.206 1.508 3.946 0 1.094-.25 2.76-.75 5l-.407 1.765zm-27.328 10.891v-14.61h4.047v14.61h-4.047zm-2.515-18.703l1.547-4.625h10.843c1.99 0 3.39.088 4.203.265.813.178 1.495.542 2.047 1.094.802.813 1.203 1.98 1.203 3.5 0 3.052-1.12 5.901-3.359 8.547-2.24 2.646-5.13 4.537-8.672 5.672l1.672-4.969c1.656-.469 3.036-1.383 4.14-2.742 1.105-1.36 1.657-2.82 1.657-4.383 0-.937-.328-1.565-.984-1.883-.657-.317-1.959-.476-3.907-.476h-10.39zM224.406 705v-9.063c0-2.416-.203-3.856-.61-4.32-.405-.463-1.681-.695-3.827-.695h-5.89l1.53-4.625h5.047c3.136 0 5.23.458 6.281 1.375a3.773 3.773 0 011.22 1.992c.197.797.296 2.18.296 4.149V705h-4.047zm-38.734 0l1.531-4.625h8.734c.865-1.99 1.297-3.682 1.297-5.078 0-1.646-.38-2.787-1.14-3.422-.76-.635-2.13-.953-4.11-.953h-5.406l1.531-4.625h5.094c1.781 0 3.151.156 4.11.469.958.312 1.77.87 2.437 1.672 1.063 1.27 1.594 3.062 1.594 5.375 0 1.843-.49 4.03-1.469 6.562L198.359 705h-12.687zm-20.422 0l-2.203-17.5 3.922-1.578 1.172 9.25c1.135-.281 1.932-.945 2.39-1.992.459-1.047.688-2.716.688-5.008v-1.875h4.344v1.719c0 3.093-.566 5.445-1.696 7.054-1.13 1.61-2.92 2.607-5.367 2.992l.281 2.313c2.719 0 4.89-.453 6.516-1.36 2.844-1.583 4.266-5.192 4.266-10.827v-1.891h4.328v1.562c0 5.615-1.581 9.875-4.743 12.782-3.161 2.906-7.794 4.359-13.898 4.359zm-21.938 0l1.391-4.234h10.469v-4.157c0-1.948-.193-3.205-.578-3.773-.386-.568-1.386-1.065-3-1.492l-2.985-.782-.234.672c-.135.396-.203.74-.203 1.032 0 1.01.87 1.583 2.61 1.718l-1.079 3.25c-3.5-.323-5.25-1.671-5.25-4.047 0-.916.375-2.437 1.125-4.562l.969-2.703 7.125 1.734c2.312.563 3.815 1.284 4.508 2.164.692.88 1.039 2.503 1.039 4.867v6.079L157.813 705h-14.5zm-20.656 0v-10.031h4.047V705h-4.047zm13.594 0v-9.063c0-2.416-.206-3.856-.617-4.32-.412-.463-1.685-.695-3.82-.695h-12.047l1.546-4.625h11.172c3.136 0 5.23.458 6.282 1.375a3.733 3.733 0 011.234 1.984c.198.792.297 2.177.297 4.157V705h-4.047zm169.5 38v-10.031h4.047V743h-4.047zm13.594 0v-9.063c0-2.416-.206-3.856-.617-4.32-.412-.463-1.685-.695-3.82-.695h-12.048l1.547-4.625h11.172c3.136 0 5.23.458 6.281 1.375a3.733 3.733 0 011.235 1.984c.198.792.297 2.177.297 4.157V743h-4.047zm-37.594 0l2.266-8.688c.416-1.593.625-2.692.625-3.296 0-1.24-1.058-2.907-3.172-5l3.61-2.094c1.145.958 2.124 2.265 2.937 3.922 1.666-2.615 3.666-3.922 6-3.922 1.76 0 3.166.67 4.218 2.008s1.677 3.257 1.875 5.758l.532 6.687-1.532 4.625h-9.03l1.546-4.625h4.922l-.36-4.797c-.25-3.354-1.348-5.031-3.296-5.031-1.125 0-2.07.56-2.836 1.68-.766 1.12-1.498 3.007-2.196 5.664L285.984 743h-4.234zm-7.453 0v-9.594c0-1.927-.32-3.252-.961-3.976-.64-.724-1.997-1.295-4.07-1.711l2-3.797c2.448.448 4.208 1.15 5.28 2.11.71.635 1.186 1.385 1.43 2.25.245.864.368 2.25.368 4.155V743h-4.047zm-22.703 0l-2.235-17.688 3.922-1.39 1.797 14.453c2.667 0 4.794-.615 6.383-1.844 1.588-1.229 2.383-2.87 2.383-4.922 0-2.24-1.209-3.359-3.625-3.359-.646 0-1.339.104-2.078.313l1.406-4.36a9.102 9.102 0 012.234-.297c1.917 0 3.414.576 4.492 1.727 1.079 1.15 1.618 2.747 1.618 4.789 0 2.292-.698 4.437-2.094 6.437s-3.292 3.568-5.688 4.703c-2.01.959-4.849 1.438-8.515 1.438zm-21.813 0l1.547-4.625h9.64v-4.438c0-2.416-.202-3.856-.609-4.32-.406-.463-1.682-.695-3.828-.695h-5.828l1.547-4.625h4.953c3.136 0 5.23.458 6.281 1.375a3.734 3.734 0 011.235 1.992c.198.797.297 2.18.297 4.149v6.562h2.5L245.984 743h-16.203zm-21.687 2.781l1.562-4.672c1.667 0 3.459-.28 5.375-.843l-3.36-14.985 3.876-1.36 3.266 14.563c1.635-1.24 2.773-2.78 3.414-4.625.64-1.843.96-4.484.96-7.922v-1.64h4.344v1.156c0 3.99-.612 7.318-1.836 9.985-1.224 2.666-3.153 4.88-5.789 6.64-3.698 2.469-7.635 3.703-11.812 3.703zM186.39 743l1.547-4.625h4.03c-.822-2.115-1.234-4.708-1.234-7.781v-1.672h-2.328l1.547-4.625h8.578c3.136 0 5.23.458 6.281 1.375a3.733 3.733 0 011.235 1.984c.198.792.297 2.177.297 4.157V743h-4.047v-9.063c0-2.416-.203-3.856-.61-4.32-.406-.463-1.682-.695-3.828-.695h-3.062v1.375c0 3.354.479 6.047 1.437 8.078l-1.53 4.625h-8.313zm-29.516 0l1.547-4.625h9.64v-4.438c0-2.416-.203-3.856-.609-4.32-.406-.463-1.682-.695-3.828-.695h-5.828l1.547-4.625h4.953c3.135 0 5.229.458 6.281 1.375a3.734 3.734 0 011.234 1.992c.198.797.297 2.18.297 4.149v6.562h2.5L173.08 743h-16.204zm-9.125 0v-14.078h-9.531l1.547-4.625h16.093l-1.547 4.625h-2.515V743h-4.047zm-28.25 0l1.39-4.234h10.47v-4.157c0-1.948-.193-3.205-.579-3.773-.385-.568-1.385-1.065-3-1.492l-2.984-.782-.234.672c-.136.396-.204.74-.204 1.032 0 1.01.87 1.583 2.61 1.718l-1.078 3.25c-3.5-.323-5.25-1.671-5.25-4.047 0-.916.375-2.437 1.125-4.562l.968-2.703 7.125 1.734c2.313.563 3.815 1.284 4.508 2.164.693.88 1.04 2.503 1.04 4.867v6.079L134 743h-14.5z"/><path id="Rectangle-1" stroke="#C06334" stroke-width="2" d="M117 106h282v198H117z"/><g id="Rectangle-209-+-Rectangle-208" fill="#DBAF88" transform="translate(15 15)"><path id="Rectangle-209" d="M0 0h50v300H0z"/><path id="Rectangle-208" d="M165.5-130.5h50v311h-50z" transform="rotate(-90 190.5 25)"/></g><path id="Rectangle-5" fill="url(#linearGradient-1)" d="M158-7h162v447H158z" transform="rotate(-180 239 216.5)"/><path id="Rectangle-6" fill="#FFF" d="M319 8h162v399H319z"/><g id="Group-2" transform="translate(65 65)"><rect id="Rectangle-19" width="20" height="239" x=".5" y=".5" fill="#D1CFCD" stroke="#D1CFCD" rx="3"/><g id="Rectangle-18-+-Triangle-1"><rect id="Rectangle-18" width="20" height="19" x=".5" y=".5" fill="url(#linearGradient-2)" stroke="#D1CFCD" rx="3"/><path id="Triangle-1" fill="#7E7C7B" d="M10.5 7l4.2 6H6.3z"/></g><g id="Rectangle-18-+-Triangle-2" transform="matrix(1 0 0 -1 0 240)"><rect id="Rectangle-18" width="20" height="19" x=".5" y=".5" fill="url(#linearGradient-2)" stroke="#D1CFCD" rx="3"/><path id="Triangle-1" fill="#7E7C7B" d="M10.5 7l4.2 6H6.3z"/></g><g id="Rectangle-18-+-Triangle-3-+-Group" transform="translate(0 50)"><g id="Rectangle-18-+-Triangle-3" fill="url(#linearGradient-3)" stroke="#D1CFCD" transform="matrix(1 0 0 -1 0 51)"><rect id="Rectangle-18" width="20" height="50" x=".5" y=".5" rx="3"/></g><g id="Group" fill="#D1CFCD" stroke="#7E7C7B" transform="translate(5.25 20)"><path id="Rectangle-22" d="M.5.5H10v1H.5z"/><path id="Rectangle-23" d="M.5 3.5H10v1H.5z"/><path id="Rectangle-24" d="M.5 6.5H10v1H.5z"/><path id="Rectangle-25" d="M.5 9.5H10v1H.5z"/></g></g></g><path id="Line-7" fill="#C06334" fill-rule="nonzero" d="M65.41 15.68l7 14h-6.001v20h6.001l-7 14-7-14h5.999v-20H58.41l7-14z"/><path id="Line-28" fill="#C06334" fill-rule="nonzero" d="M72 57.68l14 7-14 7-.001-6h-42.59l.001 6-14-7 14-7-.001 6h42.59l.001-6z"/><text id="clientTop:25px-=-bor" font-family="PTMono-Regular, PT Mono" font-size="12" font-weight="normal"><tspan x="78.7" y="43" fill="#643B0C">clientTop:</tspan> <tspan x="150.7" y="43" fill="#166388">25px</tspan> <tspan x="179.5" y="43" fill="#1C85B5"> </tspan> <tspan x="186.7" y="43" fill="#643B0C">= border</tspan></text><text id="clientLeft:41px" font-family="PTMono-Regular, PT Mono" font-size="12" font-weight="normal" transform="rotate(-90 40.32 125)"><tspan x="-13.68" y="129" fill="#643B0C">clientLeft:</tspan> <tspan x="65.52" y="129" fill="#166388">41px</tspan></text><g id="Rectangle-8-+-Rectangle-7" transform="translate(-41.5 128.5)"><path id="Rectangle-8" fill="url(#linearGradient-4)" d="M151.5-151.5h144v447h-144z" transform="rotate(-90 223.5 72)"/><path id="Rectangle-7" fill="#FFF" d="M151.5 24.5h162v399h-162z" transform="rotate(90 232.5 224)"/></g></g></g></svg> \ No newline at end of file diff --git a/2-ui/1-document/09-size-and-scroll/metric-client-left-top-rtl@2x.png b/2-ui/1-document/09-size-and-scroll/metric-client-left-top-rtl@2x.png deleted file mode 100644 index 9b7edcf572..0000000000 Binary files a/2-ui/1-document/09-size-and-scroll/metric-client-left-top-rtl@2x.png and /dev/null differ diff --git a/2-ui/1-document/09-size-and-scroll/metric-client-left-top.png b/2-ui/1-document/09-size-and-scroll/metric-client-left-top.png deleted file mode 100644 index 787557205a..0000000000 Binary files a/2-ui/1-document/09-size-and-scroll/metric-client-left-top.png and /dev/null differ diff --git a/2-ui/1-document/09-size-and-scroll/metric-client-left-top.svg b/2-ui/1-document/09-size-and-scroll/metric-client-left-top.svg new file mode 100644 index 0000000000..8097afa78f --- /dev/null +++ b/2-ui/1-document/09-size-and-scroll/metric-client-left-top.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="353" height="316" viewBox="0 0 353 316"><defs><style>@import url(https://fonts.googleapis.com/css?family=Open+Sans:bold,italic,bolditalic%7CPT+Mono);@font-face{font-family:'PT Mono';font-weight:700;font-style:normal;src:local('PT MonoBold'),url(/font/PTMonoBold.woff2) format('woff2'),url(/font/PTMonoBold.woff) format('woff'),url(/font/PTMonoBold.ttf) format('truetype')}</style></defs><defs><linearGradient id="linearGradient-1" x1="0%" x2="62.299%" y1="47.096%" y2="47.096%"><stop offset="0%" stop-color="#FFF"/><stop offset="100%" stop-color="#FFF" stop-opacity="0"/></linearGradient></defs><g id="dom" fill="none" fill-rule="evenodd" stroke="none" stroke-width="1"><g id="metric-client-left-top.svg"><path fill="#FFF" d="M0 0h353v316H0z"/><path id="Rectangle-206" fill="#DBAF88" d="M12 15h50v300H12z"/><path id="Rectangle-207" fill="#DBAF88" d="M177.5-115.5h50v311h-50z" transform="rotate(-90 202.5 40)"/><text id="Introduction" fill="#643B0C" font-family="OpenSans-Bold, Open Sans" font-size="32" font-weight="bold"><tspan x="102" y="134">Introduction</tspan> <tspan x="102" y="183" font-family="OpenSans-Regular, Open Sans" font-size="24" font-weight="normal">This Ecma Standard is based on </tspan> <tspan x="102" y="216" font-family="OpenSans-Regular, Open Sans" font-size="24" font-weight="normal">several originating </tspan> <tspan x="102" y="249" font-family="OpenSans-Regular, Open Sans" font-size="24" font-weight="normal">technologies, the most well </tspan> <tspan x="102" y="282" font-family="OpenSans-Regular, Open Sans" font-size="24" font-weight="normal">known being JavaScript </tspan> <tspan x="102" y="315" font-family="OpenSans-Regular, Open Sans" font-size="24" font-weight="normal">(Netscape) and JScript </tspan> <tspan x="102" y="348" font-family="OpenSans-Regular, Open Sans" font-size="24" font-weight="normal">(Microsoft). The language was </tspan> <tspan x="102" y="381" font-family="OpenSans-Regular, Open Sans" font-size="24" font-weight="normal">invented by Brendan Eich at </tspan> <tspan x="102" y="414" font-family="OpenSans-Regular, Open Sans" font-size="24" font-weight="normal">Netscape and first appeared in </tspan> <tspan x="102" y="447" font-family="OpenSans-Regular, Open Sans" font-size="24" font-weight="normal">that company’s Navigator 2.0 </tspan> <tspan x="102" y="480" font-family="OpenSans-Regular, Open Sans" font-size="24" font-weight="normal">browser. It has appeared in all </tspan> <tspan x="102" y="513" font-family="OpenSans-Regular, Open Sans" font-size="24" font-weight="normal">subsequent browsers from </tspan> <tspan x="102" y="546" font-family="OpenSans-Regular, Open Sans" font-size="24" font-weight="normal">Netscape and in all browsers </tspan> <tspan x="102" y="579" font-family="OpenSans-Regular, Open Sans" font-size="24" font-weight="normal">from Microsoft starting with </tspan> <tspan x="102" y="612" font-family="OpenSans-Regular, Open Sans" font-size="24" font-weight="normal">Internet Explorer 3.0.</tspan> <tspan x="102" y="645" font-family="OpenSans-Regular, Open Sans" font-size="24" font-weight="normal">The development of this </tspan> <tspan x="102" y="678" font-family="OpenSans-Regular, Open Sans" font-size="24" font-weight="normal">Standard started in November </tspan> <tspan x="102" y="711" font-family="OpenSans-Regular, Open Sans" font-size="24" font-weight="normal">1996. The first edition of this </tspan> <tspan x="102" y="744" font-family="OpenSans-Regular, Open Sans" font-size="24" font-weight="normal">Ecma Standard was adopted by </tspan> <tspan x="102" y="777" font-family="OpenSans-Regular, Open Sans" font-size="24" font-weight="normal">the Ecma General Assembly of </tspan> <tspan x="102" y="810" font-family="OpenSans-Regular, Open Sans" font-size="24" font-weight="normal">June 1997.</tspan></text><path id="Rectangle-5" fill="url(#linearGradient-1)" d="M42-7h275v447H42z" transform="rotate(-180 179.5 216.5)"/><path id="Rectangle-6" fill="#FFF" d="M316 8h162v399H316z"/><g id="Rectangle-8-+-Rectangle-7" transform="translate(-44.5 -2.5)"><path id="Rectangle-8" fill="url(#linearGradient-1)" d="M86-86h275v447H86z" transform="rotate(-90 223.5 137.5)"/><path id="Rectangle-7" fill="#FFF" d="M151.5 155.5h162v399h-162z" transform="rotate(90 232.5 355)"/></g><path id="Line-7" fill="#C06334" fill-rule="nonzero" d="M62.41 15.68l7 14h-6.001v20h6.001l-7 14-7-14h5.999v-20H55.41l7-14z"/><path id="Line-28" fill="#C06334" fill-rule="nonzero" d="M46.41 57.68l14 7-14 7-.001-6h-20l.001 6-14-7 14-7-.001 6h20l.001-6z"/><text id="clientTop:25px-=-bor" font-family="PTMono-Regular, PT Mono" font-size="12" font-weight="normal"><tspan x="75.7" y="43" fill="#643B0C">clientTop:</tspan> <tspan x="147.7" y="43" fill="#166388">25px</tspan> <tspan x="176.5" y="43" fill="#643B0C"> = border</tspan></text><text id="clientLeft:25px" font-family="PTMono-Regular, PT Mono" font-size="12" font-weight="normal" transform="rotate(-90 37.32 136)"><tspan x="-16.68" y="140" fill="#643B0C">clientLeft:</tspan> <tspan x="62.52" y="140" fill="#166388">25px</tspan></text></g></g></svg> \ No newline at end of file diff --git a/2-ui/1-document/09-size-and-scroll/metric-client-left-top@2x.png b/2-ui/1-document/09-size-and-scroll/metric-client-left-top@2x.png deleted file mode 100644 index 8034c7aff9..0000000000 Binary files a/2-ui/1-document/09-size-and-scroll/metric-client-left-top@2x.png and /dev/null differ diff --git a/2-ui/1-document/09-size-and-scroll/metric-client-width-height.png b/2-ui/1-document/09-size-and-scroll/metric-client-width-height.png deleted file mode 100644 index d50d1186ff..0000000000 Binary files a/2-ui/1-document/09-size-and-scroll/metric-client-width-height.png and /dev/null differ diff --git a/2-ui/1-document/09-size-and-scroll/metric-client-width-height.svg b/2-ui/1-document/09-size-and-scroll/metric-client-width-height.svg new file mode 100644 index 0000000000..83864b4c57 --- /dev/null +++ b/2-ui/1-document/09-size-and-scroll/metric-client-width-height.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="500" height="493" viewBox="0 0 500 493"><defs><style>@import url(https://fonts.googleapis.com/css?family=Open+Sans:bold,italic,bolditalic%7CPT+Mono);@font-face{font-family:'PT Mono';font-weight:700;font-style:normal;src:local('PT MonoBold'),url(/font/PTMonoBold.woff2) format('woff2'),url(/font/PTMonoBold.woff) format('woff'),url(/font/PTMonoBold.ttf) format('truetype')}</style></defs><defs><linearGradient id="linearGradient-1" x1="50%" x2="50%" y1="0%" y2="100%"><stop offset="0%" stop-color="#FFF"/><stop offset="100%" stop-color="#D1CFCD"/></linearGradient><linearGradient id="linearGradient-2" x1="50%" x2="50%" y1="0%" y2="100%"><stop offset="0%" stop-color="#FFF"/><stop offset="100%" stop-color="#D1CFCD"/></linearGradient></defs><g id="dom" fill="none" fill-rule="evenodd" stroke="none" stroke-width="1"><g id="metric-client-width-height.svg"><path id="Rectangle-2" fill="#DBAF88" d="M411 130v290H21V130h390zm-25 25H46v240h340V155z"/><path id="Rectangle-1" stroke="#C06334" stroke-width="2" d="M350 177v199H67V177h283z"/><g id="Group" transform="translate(370 155)"><rect id="Rectangle-19" width="15" height="239" x=".5" y=".5" fill="#D1CFCD" stroke="#D1CFCD" rx="3"/><g id="Rectangle-18-+-Triangle-1"><rect id="Rectangle-18" width="15" height="19" x=".5" y=".5" fill="url(#linearGradient-1)" stroke="#D1CFCD" rx="3"/><path id="Triangle-1" fill="#7E7C7B" d="M8 7l3.2 6H4.8z"/></g><g id="Rectangle-18-+-Triangle-2" transform="matrix(1 0 0 -1 0 240)"><rect id="Rectangle-18" width="15" height="19" x=".5" y=".5" fill="url(#linearGradient-1)" stroke="#D1CFCD" rx="3"/><path id="Triangle-1" fill="#7E7C7B" d="M8 7l3.2 6H4.8z"/></g><g id="Rectangle-18-+-Triangle-3-+-Group" transform="translate(0 50)"><g id="Rectangle-18-+-Triangle-3" fill="url(#linearGradient-2)" stroke="#D1CFCD" transform="matrix(1 0 0 -1 0 51)"><rect id="Rectangle-18" width="15" height="50" x=".5" y=".5" rx="3"/></g><g id="Group" fill="#D1CFCD" stroke="#7E7C7B" transform="translate(4 20)"><path id="Rectangle-22" d="M.5.5h7v1h-7z"/><path id="Rectangle-23" d="M.5 3.5h7v1h-7z"/><path id="Rectangle-24" d="M.5 6.5h7v1h-7z"/><path id="Rectangle-25" d="M.5 9.5h7v1h-7z"/></g></g></g><text id="border" font-family="PTMono-Regular, PT Mono" font-size="12" font-weight="normal"><tspan x="12.9" y="75" fill="#C06334">border</tspan> <tspan x="20.1" y="89" fill="#1C85B5">25px</tspan></text><text id="padding" font-family="PTMono-Regular, PT Mono" font-size="12" font-weight="normal"><tspan x="32.3" y="45" fill="#C06334">padding</tspan> <tspan x="43.1" y="59" fill="#1C85B5">20px</tspan></text><text id="content-width:284px" font-family="PTMono-Regular, PT Mono" font-size="12" font-weight="normal"><tspan x="139.1" y="109" fill="#C06334">content width:</tspan> <tspan x="239.9" y="109" fill="#1C85B5">284px</tspan></text><path id="Line-15" fill="#C06334" fill-rule="nonzero" d="M336.5 110l14 7-14 7v-6H82.679l.001 6-14-7 14-7-.001 6H336.5v-6z"/><path id="Line-14" stroke="#C06334" stroke-dasharray="3,6" stroke-linecap="square" stroke-width="2" d="M21.48 93v43"/><path id="Line-13" stroke="#C06334" stroke-dasharray="3,6" stroke-linecap="square" stroke-width="2" d="M47.48 93v41"/><text id="border-2" font-family="PTMono-Regular, PT Mono" font-size="12" font-weight="normal"><tspan x="376.9" y="76" fill="#C06334">border</tspan> <tspan x="384.1" y="90" fill="#1C85B5">25px</tspan></text><text id="padding-2" font-family="PTMono-Regular, PT Mono" font-size="12" font-weight="normal"><tspan x="339.3" y="16" fill="#C06334">padding</tspan> <tspan x="350.1" y="30" fill="#1C85B5">20px</tspan></text><text id="scrollbar" font-family="PTMono-Regular, PT Mono" font-size="12" font-weight="normal"><tspan x="349.1" y="48" fill="#C06334">scrollbar</tspan> <tspan x="367.1" y="62" fill="#1C85B5">16px</tspan></text><path id="Line-17" stroke="#C06334" stroke-dasharray="3,6" stroke-linecap="square" stroke-width="2" d="M371.48 93v43"/><path id="Line-20" stroke="#C06334" stroke-dasharray="3,6" stroke-linecap="square" stroke-width="2" d="M385.48 93v43"/><path id="Line-18" stroke="#C06334" stroke-dasharray="3,6" stroke-linecap="square" stroke-width="2" d="M410.48 93v41"/><path id="Line-16" stroke="#C06334" stroke-dasharray="3,6" stroke-linecap="square" stroke-width="2" d="M66.48 93v41"/><path id="Line-19" stroke="#C06334" stroke-dasharray="3,6" stroke-linecap="square" stroke-width="2" d="M353.48 93v41"/><text id="clientWidth-=-20+284" font-family="PTMono-Regular, PT Mono" font-size="14" font-weight="normal"><tspan x="80.3" y="473" fill="#C06334">clientWidth = </tspan> <tspan x="197.9" y="473" fill="#1C85B5">20+284+20 </tspan> <tspan x="281.9" y="473" fill="#C06334">=</tspan> <tspan x="290.3" y="473" fill="#1C85B5"> 324px</tspan></text><path id="Line-24" stroke="#C06334" stroke-dasharray="3,6" stroke-linecap="square" stroke-width="2" d="M46.5 376v88.142"/><path id="Line-25" stroke="#C06334" stroke-dasharray="3,6" stroke-linecap="square" stroke-width="2" d="M370.5 376v88.142"/><path id="Line-22" fill="#C06334" fill-rule="nonzero" d="M354 445.071l14 7-14 7v-6.001H62v6.001l-14-7 14-7v5.999h292v-5.999z"/><text id="clientHeight:240px" font-family="PTMono-Regular, PT Mono" font-size="14" font-weight="normal" transform="rotate(-90 488.5 277.5)"><tspan x="412.9" y="282" fill="#C06334">clientHeight:</tspan> <tspan x="522.1" y="282" fill="#1C85B5">240px</tspan></text><path id="Line-27" stroke="#C06334" stroke-dasharray="3,6" stroke-linecap="square" stroke-width="2" d="M365.5 156h120"/><path id="Line-28" stroke="#C06334" stroke-dasharray="3,6" stroke-linecap="square" stroke-width="2" d="M365.5 394h120"/><path id="Line-26" fill="#C06334" fill-rule="nonzero" d="M476 160l7 14-6-.001V375h6l-7 14-7-14h6V173.999l-6 .001 7-14z"/><text id="height:200px" font-family="PTMono-Regular, PT Mono" font-size="12" font-weight="normal" transform="rotate(-90 422.5 274)"><tspan x="379.3" y="278" fill="#C06334">height:</tspan> <tspan x="429.7" y="278" fill="#1C85B5">200px</tspan></text><path id="Line-3" stroke="#C06334" stroke-dasharray="3,6" stroke-linecap="square" stroke-width="2" d="M353.5 175h88.142"/><path id="Line-2" stroke="#C06334" stroke-dasharray="3,6" stroke-linecap="square" stroke-width="2" d="M353.5 377h88.142"/><path id="Line" fill="#C06334" fill-rule="nonzero" d="M435 177.5l7 14h-6V360h6l-7 14-7-14h6V191.5h-6l7-14z"/><text id="Introduction" fill="#643B0C" font-family="OpenSans-Bold, Open Sans" font-size="16" font-weight="bold"><tspan x="69" y="193">Introduction</tspan> <tspan x="69" y="221" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">This Ecma Standard is based on several </tspan> <tspan x="69" y="240" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">originating technologies, the most well </tspan> <tspan x="69" y="259" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">known being JavaScript (Netscape) and </tspan> <tspan x="69" y="278" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">JScript (Microsoft). The language was </tspan> <tspan x="69" y="297" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">invented by Brendan Eich at Netscape and </tspan> <tspan x="69" y="316" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">first appeared in that company’s Navigator </tspan> <tspan x="69" y="335" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">2.0 browser. It has appeared in all </tspan> <tspan x="69" y="354" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">subsequent browsers from Netscape and </tspan> <tspan x="69" y="373" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">in all browsers from Microsoft starting with </tspan></text></g></g></svg> \ No newline at end of file diff --git a/2-ui/1-document/09-size-and-scroll/metric-client-width-height@2x.png b/2-ui/1-document/09-size-and-scroll/metric-client-width-height@2x.png deleted file mode 100644 index 76017b9ee6..0000000000 Binary files a/2-ui/1-document/09-size-and-scroll/metric-client-width-height@2x.png and /dev/null differ diff --git a/2-ui/1-document/09-size-and-scroll/metric-client-width-nopadding.png b/2-ui/1-document/09-size-and-scroll/metric-client-width-nopadding.png deleted file mode 100644 index 173bf1529b..0000000000 Binary files a/2-ui/1-document/09-size-and-scroll/metric-client-width-nopadding.png and /dev/null differ diff --git a/2-ui/1-document/09-size-and-scroll/metric-client-width-nopadding.svg b/2-ui/1-document/09-size-and-scroll/metric-client-width-nopadding.svg new file mode 100644 index 0000000000..330d2a7c04 --- /dev/null +++ b/2-ui/1-document/09-size-and-scroll/metric-client-width-nopadding.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="409" height="467" viewBox="0 0 409 467"><defs><style>@import url(https://fonts.googleapis.com/css?family=Open+Sans:bold,italic,bolditalic%7CPT+Mono);@font-face{font-family:'PT Mono';font-weight:700;font-style:normal;src:local('PT MonoBold'),url(/font/PTMonoBold.woff2) format('woff2'),url(/font/PTMonoBold.woff) format('woff'),url(/font/PTMonoBold.ttf) format('truetype')}</style></defs><defs><linearGradient id="linearGradient-1" x1="50%" x2="50%" y1="0%" y2="100%"><stop offset="0%" stop-color="#FFF"/><stop offset="100%" stop-color="#D1CFCD"/></linearGradient><linearGradient id="linearGradient-2" x1="50%" x2="50%" y1="0%" y2="100%"><stop offset="0%" stop-color="#FFF"/><stop offset="100%" stop-color="#D1CFCD"/></linearGradient></defs><g id="dom" fill="none" fill-rule="evenodd" stroke="none" stroke-width="1"><g id="metric-client-width-nopadding.svg"><path fill="#FFF" d="M0 0h409v467H0z"/><path id="Rectangle-1" fill="#DBAF88" d="M389 131v250H35V131h354zm-25 25H60v200h304V156z"/><text id="clientWidth:284px-=-" font-family="PTMono-Regular, PT Mono" font-size="12" font-weight="normal"><tspan x="84.7" y="399.642" fill="#C06334">clientWidth:</tspan> <tspan x="171.1" y="399.642" fill="#1C85B5">284px </tspan> <tspan x="214.3" y="399.642" fill="#C06334">= content width</tspan></text><path id="Line-44" fill="#C06334" fill-rule="nonzero" d="M329.68 402.642l14 7-14 7-.001-6.001h-252l.001 6-14-7 14-7-.001 6h252l.001-6z"/><text id="CSS-width:-300px" font-family="PTMono-Regular, PT Mono" font-size="12" font-weight="normal"><tspan x="145.9" y="429.642" fill="#C06334">CSS width:</tspan> <tspan x="217.9" y="429.642" fill="#1C85B5"> 300px</tspan></text><path id="Line-47" fill="#C06334" fill-rule="nonzero" d="M349 427.642l14 7-14 7v-6.001H76.679l.001 6-14-7 14-7-.001 6H349v-6z"/><text id="Introduction-This-Ec" fill="#643B0C" font-family="OpenSans-Bold, Open Sans" font-size="16" font-weight="bold"><tspan x="62" y="173">Introduction</tspan> <tspan x="62" y="201" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">This Ecma Standard is based on several </tspan> <tspan x="62" y="220" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">originating technologies, the most well </tspan> <tspan x="62" y="239" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">known being JavaScript (Netscape) and </tspan> <tspan x="62" y="258" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">JScript (Microsoft). The language was </tspan> <tspan x="62" y="277" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">invented by Brendan Eich at Netscape and </tspan> <tspan x="62" y="296" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">first appeared in that company’s Navigator </tspan> <tspan x="62" y="315" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">2.0 browser. It has appeared in all </tspan> <tspan x="62" y="334" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">subsequent browsers from Netscape and </tspan> <tspan x="62" y="353" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">in all browsers from Microsoft starting with </tspan></text><ellipse id="Oval-5" cx="191" cy="58.858" fill="#FBF2EC" stroke="#C06334" stroke-width="2" rx="76" ry="43.5"/><path id="Line" fill="#C06334" fill-rule="nonzero" d="M192 103v11.638h6l-7 14-7-14h6V103h2z"/><text id="padding:-0;" fill="#AF6E24" font-family="PTMono-Regular, PT Mono" font-size="16" font-weight="normal"><tspan x="136" y="52">padding: 0;</tspan> <tspan x="136" y="70">width: 300px;</tspan></text><g id="Group-2" transform="translate(348 156)"><rect id="Rectangle-19" width="15" height="199" x=".5" y=".5" fill="#D1CFCD" stroke="#D1CFCD" rx="3"/><g id="Rectangle-18-+-Triangle-1"><rect id="Rectangle-18" width="15" height="19" x=".5" y=".5" fill="url(#linearGradient-1)" stroke="#D1CFCD" rx="3"/><path id="Triangle-1" fill="#7E7C7B" d="M8 7l3.2 6H4.8z"/></g><g id="Rectangle-18-+-Triangle-2" transform="matrix(1 0 0 -1 0 200)"><rect id="Rectangle-18" width="15" height="19" x=".5" y=".5" fill="url(#linearGradient-1)" stroke="#D1CFCD" rx="3"/><path id="Triangle-1" fill="#7E7C7B" d="M8 7l3.2 6H4.8z"/></g><g id="Rectangle-18-+-Triangle-3-+-Group" transform="translate(0 50)"><g id="Rectangle-18-+-Triangle-3" fill="url(#linearGradient-2)" stroke="#D1CFCD" transform="matrix(1 0 0 -1 0 51)"><rect id="Rectangle-18" width="15" height="50" x=".5" y=".5" rx="3"/></g><g id="Group" fill="#D1CFCD" stroke="#7E7C7B" transform="translate(4 20)"><path id="Rectangle-22" d="M.5.5h7v1h-7z"/><path id="Rectangle-23" d="M.5 3.5h7v1h-7z"/><path id="Rectangle-24" d="M.5 6.5h7v1h-7z"/><path id="Rectangle-25" d="M.5 9.5h7v1h-7z"/></g></g></g><path id="Line-46" stroke="#C06334" stroke-dasharray="3,6" stroke-linecap="square" stroke-width="2" d="M60.5 354.642v96"/><path id="Line-45" stroke="#C06334" stroke-dasharray="3,6" stroke-linecap="square" stroke-width="2" d="M345.5 354.642V414"/><path id="Line-48" stroke="#C06334" stroke-dasharray="3,6" stroke-linecap="square" stroke-width="2" d="M363.5 354.642v96"/></g></g></svg> \ No newline at end of file diff --git a/2-ui/1-document/09-size-and-scroll/metric-client-width-nopadding@2x.png b/2-ui/1-document/09-size-and-scroll/metric-client-width-nopadding@2x.png deleted file mode 100644 index 2d145d77fc..0000000000 Binary files a/2-ui/1-document/09-size-and-scroll/metric-client-width-nopadding@2x.png and /dev/null differ diff --git a/2-ui/1-document/09-size-and-scroll/metric-css.png b/2-ui/1-document/09-size-and-scroll/metric-css.png deleted file mode 100644 index 5579b4abdb..0000000000 Binary files a/2-ui/1-document/09-size-and-scroll/metric-css.png and /dev/null differ diff --git a/2-ui/1-document/09-size-and-scroll/metric-css.svg b/2-ui/1-document/09-size-and-scroll/metric-css.svg new file mode 100644 index 0000000000..13aa62afd0 --- /dev/null +++ b/2-ui/1-document/09-size-and-scroll/metric-css.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="566" height="469" viewBox="0 0 566 469"><defs><style>@import url(https://fonts.googleapis.com/css?family=Open+Sans:bold,italic,bolditalic%7CPT+Mono);@font-face{font-family:'PT Mono';font-weight:700;font-style:normal;src:local('PT MonoBold'),url(/font/PTMonoBold.woff2) format('woff2'),url(/font/PTMonoBold.woff) format('woff'),url(/font/PTMonoBold.ttf) format('truetype')}</style></defs><defs><linearGradient id="linearGradient-1" x1="50%" x2="50%" y1="0%" y2="100%"><stop offset="0%" stop-color="#FFF"/><stop offset="100%" stop-color="#D1CFCD"/></linearGradient><linearGradient id="linearGradient-2" x1="50%" x2="50%" y1="0%" y2="100%"><stop offset="0%" stop-color="#FFF"/><stop offset="100%" stop-color="#D1CFCD"/></linearGradient></defs><g id="dom" fill="none" fill-rule="evenodd" stroke="none" stroke-width="1"><g id="metric-css.svg"><path id="Rectangle-1" stroke="#C06334" stroke-width="2" d="M460 184v199H177V184h283z"/><path id="Rectangle-1" fill="#DBAF88" d="M521 139v290H131V139h390zm-25 25H156v240h340V164z"/><g id="Group" transform="translate(480 163)"><rect id="Rectangle-19" width="15" height="239" x=".5" y=".5" fill="#D1CFCD" stroke="#D1CFCD" rx="3"/><g id="Rectangle-18-+-Triangle-1"><rect id="Rectangle-18" width="15" height="19" x=".5" y=".5" fill="url(#linearGradient-1)" stroke="#D1CFCD" rx="3"/><path id="Triangle-1" fill="#7E7C7B" d="M8 7l3.2 6H4.8z"/></g><g id="Rectangle-18-+-Triangle-2" transform="matrix(1 0 0 -1 0 240)"><rect id="Rectangle-18" width="15" height="19" x=".5" y=".5" fill="url(#linearGradient-1)" stroke="#D1CFCD" rx="3"/><path id="Triangle-1" fill="#7E7C7B" d="M8 7l3.2 6H4.8z"/></g><g id="Rectangle-18-+-Triangle-3-+-Group" transform="translate(0 50)"><g id="Rectangle-18-+-Triangle-3" fill="url(#linearGradient-2)" stroke="#D1CFCD" transform="matrix(1 0 0 -1 0 51)"><rect id="Rectangle-18" width="15" height="50" x=".5" y=".5" rx="3"/></g><g id="Group" fill="#D1CFCD" stroke="#7E7C7B" transform="translate(4 20)"><path id="Rectangle-22" d="M.5.5h7v1h-7z"/><path id="Rectangle-23" d="M.5 3.5h7v1h-7z"/><path id="Rectangle-24" d="M.5 6.5h7v1h-7z"/><path id="Rectangle-25" d="M.5 9.5h7v1h-7z"/></g></g></g><text id="padding:20px" font-family="PTMono-Regular, PT Mono" font-size="12" font-weight="normal"><tspan x="7" y="176" fill="#C06334">padding:</tspan> <tspan x="64.6" y="176" fill="#1C85B5">20px</tspan></text><text id="height:200px" font-family="PTMono-Regular, PT Mono" font-size="12" font-weight="normal" transform="rotate(-90 530.5 282)"><tspan x="487.3" y="286" fill="#C06334">height:</tspan> <tspan x="537.7" y="286" fill="#1C85B5">200px</tspan></text><path id="Line" stroke="#C06334" stroke-dasharray="3,6" stroke-linecap="square" stroke-width="2" d="M461.5 183h88.142"/><path id="Line-3" stroke="#C06334" stroke-dasharray="3,6" stroke-linecap="square" stroke-width="2" d="M93.5 184h88.142"/><path id="Line-4" stroke="#C06334" stroke-dasharray="3,6" stroke-linecap="square" stroke-width="2" d="M93.5 162h88.142"/><path id="Line-2" stroke="#C06334" stroke-dasharray="3,6" stroke-linecap="square" stroke-width="2" d="M461.5 385h88.142"/><path id="Line" fill="#C06334" fill-rule="nonzero" d="M543 185.5l7 14h-6V368h6l-7 14-7-14h6V199.5h-6l7-14z"/><text id="padding:20px-2" font-family="PTMono-Regular, PT Mono" font-size="12" font-weight="normal"><tspan x="7" y="396" fill="#C06334">padding:</tspan> <tspan x="64.6" y="396" fill="#1C85B5">20px</tspan></text><path id="Line-5" stroke="#C06334" stroke-dasharray="3,6" stroke-linecap="square" stroke-width="2" d="M93.5 404h88.142"/><path id="Line-6" stroke="#C06334" stroke-dasharray="3,6" stroke-linecap="square" stroke-width="2" d="M93.5 382h88.142"/><text id="border" font-family="PTMono-Regular, PT Mono" font-size="12" font-weight="normal"><tspan x="122.9" y="83" fill="#C06334">border</tspan> <tspan x="130.1" y="97" fill="#1C85B5">25px</tspan></text><text id="padding" font-family="PTMono-Regular, PT Mono" font-size="12" font-weight="normal"><tspan x="142.3" y="53" fill="#C06334">padding</tspan> <tspan x="153.1" y="67" fill="#1C85B5">20px</tspan></text><text id="content-width:284px" font-family="PTMono-Regular, PT Mono" font-size="12" font-weight="normal"><tspan x="249.1" y="117" fill="#C06334">content width:</tspan> <tspan x="349.9" y="117" fill="#1C85B5">284px</tspan></text><path id="Line-21" fill="#C06334" fill-rule="nonzero" d="M446.5 118l14 7-14 7v-6H192.679l.001 6-14-7 14-7-.001 6H446.5v-6z"/><path id="Line-14" stroke="#C06334" stroke-dasharray="3,6" stroke-linecap="square" stroke-width="2" d="M131.48 101v43"/><path id="Line-13" stroke="#C06334" stroke-dasharray="3,6" stroke-linecap="square" stroke-width="2" d="M157.48 101v41"/><text id="border-2" font-family="PTMono-Regular, PT Mono" font-size="12" font-weight="normal"><tspan x="486.9" y="84" fill="#C06334">border</tspan> <tspan x="494.1" y="98" fill="#1C85B5">25px</tspan></text><text id="padding-2" font-family="PTMono-Regular, PT Mono" font-size="12" font-weight="normal"><tspan x="449.3" y="24" fill="#C06334">padding</tspan> <tspan x="460.1" y="38" fill="#1C85B5">20px</tspan></text><text id="scrollbar" font-family="PTMono-Regular, PT Mono" font-size="12" font-weight="normal"><tspan x="459.1" y="56" fill="#C06334">scrollbar</tspan> <tspan x="477.1" y="70" fill="#1C85B5">16px</tspan></text><text id="Introduction" fill="#643B0C" font-family="OpenSans-Bold, Open Sans" font-size="16" font-weight="bold"><tspan x="178" y="200">Introduction</tspan> <tspan x="178" y="228" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">This Ecma Standard is based on several </tspan> <tspan x="178" y="247" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">originating technologies, the most well </tspan> <tspan x="178" y="266" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">known being JavaScript (Netscape) and </tspan> <tspan x="178" y="285" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">JScript (Microsoft). The language was </tspan> <tspan x="178" y="304" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">invented by Brendan Eich at Netscape and </tspan> <tspan x="178" y="323" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">first appeared in that company’s Navigator </tspan> <tspan x="178" y="342" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">2.0 browser. It has appeared in all </tspan> <tspan x="178" y="361" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">subsequent browsers from Netscape and </tspan> <tspan x="178" y="380" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">in all browsers from Microsoft starting with</tspan> <tspan x="178" y="399" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal"> </tspan></text><path id="Line-17" stroke="#C06334" stroke-dasharray="3,6" stroke-linecap="square" stroke-width="2" d="M481.48 101v43"/><path id="Line-20" stroke="#C06334" stroke-dasharray="3,6" stroke-linecap="square" stroke-width="2" d="M495.48 101v43"/><path id="Line-18" stroke="#C06334" stroke-dasharray="3,6" stroke-linecap="square" stroke-width="2" d="M520.48 101v41"/><path id="Line-16" stroke="#C06334" stroke-dasharray="3,6" stroke-linecap="square" stroke-width="2" d="M176.48 101v41"/><path id="Line-19" stroke="#C06334" stroke-dasharray="3,6" stroke-linecap="square" stroke-width="2" d="M463.48 101v41"/></g></g></svg> \ No newline at end of file diff --git a/2-ui/1-document/09-size-and-scroll/metric-css@2x.png b/2-ui/1-document/09-size-and-scroll/metric-css@2x.png deleted file mode 100644 index c4df7706b2..0000000000 Binary files a/2-ui/1-document/09-size-and-scroll/metric-css@2x.png and /dev/null differ diff --git a/2-ui/1-document/09-size-and-scroll/metric-offset-parent.png b/2-ui/1-document/09-size-and-scroll/metric-offset-parent.png deleted file mode 100644 index 059ef13efa..0000000000 Binary files a/2-ui/1-document/09-size-and-scroll/metric-offset-parent.png and /dev/null differ diff --git a/2-ui/1-document/09-size-and-scroll/metric-offset-parent.svg b/2-ui/1-document/09-size-and-scroll/metric-offset-parent.svg new file mode 100644 index 0000000000..9e247639be --- /dev/null +++ b/2-ui/1-document/09-size-and-scroll/metric-offset-parent.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="597" height="520" viewBox="0 0 597 520"><defs><style>@import url(https://fonts.googleapis.com/css?family=Open+Sans:bold,italic,bolditalic%7CPT+Mono);@font-face{font-family:'PT Mono';font-weight:700;font-style:normal;src:local('PT MonoBold'),url(/font/PTMonoBold.woff2) format('woff2'),url(/font/PTMonoBold.woff) format('woff'),url(/font/PTMonoBold.ttf) format('truetype')}</style></defs><defs><linearGradient id="linearGradient-3" x1="50%" x2="50%" y1="0%" y2="100%"><stop offset="0%" stop-color="#FFF"/><stop offset="100%" stop-color="#D1CFCD"/></linearGradient><linearGradient id="linearGradient-4" x1="50%" x2="50%" y1="0%" y2="100%"><stop offset="0%" stop-color="#FFF"/><stop offset="100%" stop-color="#D1CFCD"/></linearGradient><pattern id="pattern-1" width="30" height="30" x="-12" y="-6" patternUnits="userSpaceOnUse"><use xlink:href="#image-2"/></pattern><image id="image-2" width="30" height="30" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAYAAAA7MK6iAAAAAXNSR0IArs4c6QAAAERlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAHqADAAQAAAABAAAAHgAAAADKQTcFAAAA/UlEQVRIDe3VXQ6DIAwA4LUea7uCIeFdD+f2bDReYbvGbqLOmmEEAQ2T8jJeDEnTr+VPaJrmnWVZmef58+IYbdtex3G8nxkHMZIeKRKoyRT4DKfAF5gb12BOfANz4VaYA3fCsXHsuu5GiG0IIV4AUPR9X50dhzGSHiny/3Kd+iPwvdnaqeZ8szWYTvYXfwzDUEgp936VwXFoXiO6QoQiYlXXtfeq/RK36VgVQijhU/LS13lonBOmAkKTquLN7zof0sQMUHPqlDreW/aQuCmnfy9Dkh5pBtbtx9hLtXrmd97jFPhyuLjxBaal4MQ1mBPfwFy4FebAnXBsPNnL9QFH9tNXxu42ugAAAABJRU5ErkJggg=="/></defs><g id="dom" fill="none" fill-rule="evenodd" stroke="none" stroke-width="1"><g id="metric-offset-parent.svg"><path fill="#FFF" d="M0 0h597v520H0z"/><path id="Rectangle-10" fill="url(#pattern-1)" fill-opacity=".5" stroke="#D1CFCD" stroke-width="3" d="M19.5 25.5h558v479h-558z"/><path id="Rectangle-1" fill="#DBAF88" d="M552 185v290H178V185h374zm-25 25H203v240h324V210z"/><text id="offsetTop:180px" font-family="PTMono-Regular, PT Mono" font-size="14" font-weight="normal" transform="rotate(-90 163 104.32)"><tspan x="100" y="108.82" fill="#C06334">offsetTop:</tspan> <tspan x="184" y="108.82" fill="#1C85B5">180px</tspan></text><text id="offsetLeft:180px" font-family="PTMono-Regular, PT Mono" font-size="14" font-weight="normal"><tspan x="34.3" y="179.32" fill="#C06334">offsetLeft:</tspan> <tspan x="126.7" y="179.32" fill="#1C85B5">180px</tspan></text><text id="Introduction" fill="#643B0C" font-family="OpenSans-Bold, Open Sans" font-size="16" font-weight="bold"><tspan x="223" y="247">Introduction</tspan> <tspan x="223" y="275" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">This Ecma Standard is based on several </tspan> <tspan x="223" y="294" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">originating technologies, the most well </tspan> <tspan x="223" y="313" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">known being JavaScript (Netscape) and </tspan> <tspan x="223" y="332" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">JScript (Microsoft). The language was </tspan> <tspan x="223" y="351" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">invented by Brendan Eich at Netscape </tspan> <tspan x="223" y="370" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">and first appeared in that company’s </tspan> <tspan x="223" y="389" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">Navigator 2.0 browser. It has appeared </tspan> <tspan x="223" y="408" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">in all subsequent browsers from </tspan> <tspan x="223" y="427" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">Netscape and in all browsers from </tspan> <tspan x="223" y="446" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">Microsoft</tspan></text><path id="Line-40" fill="#C06334" fill-rule="nonzero" d="M179 26.5l7 14h-6v128.82h6l-7 14-7-14h6V40.5h-6l7-14z"/><path id="Line-41" fill="#C06334" fill-rule="nonzero" d="M163.41 179.91l14 7-14 7v-6H34.589l.001 6-14-7 14-7-.001 6H163.41v-6z"/><circle id="Oval-2" cx="203" cy="211" r="2" fill="#1C85B5"/><circle id="Oval-2" cx="179" cy="186" r="3" fill="#1C85B5"/><ellipse id="Oval-6" cx="364.25" cy="104" fill="#FBF2EC" stroke="#C06334" stroke-width="2" rx="100" ry="50"/><path id="Line" fill="#C06334" fill-rule="nonzero" d="M366 156.379v11.638h6l-7 14-7-14h6v-11.638h2z"/><text id="position:-absolute;" fill="#AF6E24" font-family="PTMono-Regular, PT Mono" font-size="14" font-weight="normal"><tspan x="296.5" y="95.793">position: absolute;</tspan> <tspan x="296.5" y="110.793">left: 180px;</tspan> <tspan x="296.5" y="125.793">top: 180px;</tspan></text><text id="offsetParent-<MAIN>-" fill="#AF6E24" font-family="PTMono-Bold, PT Mono" font-size="16" font-weight="bold"><tspan x="20.5" y="19.379">offsetParent <MAIN> </tspan></text><text id="<DIV>" fill="#AF6E24" font-family="PTMono-Bold, PT Mono" font-size="16" font-weight="bold"><tspan x="183.5" y="180.379"><DIV></tspan></text><g id="Group" transform="translate(511 210)"><rect id="Rectangle-19" width="15" height="239" x=".5" y=".5" fill="#D1CFCD" stroke="#D1CFCD" rx="3"/><g id="Rectangle-18-+-Triangle-1"><rect id="Rectangle-18" width="15" height="19" x=".5" y=".5" fill="url(#linearGradient-3)" stroke="#D1CFCD" rx="3"/><path id="Triangle-1" fill="#7E7C7B" d="M8 7l3.2 6H4.8z"/></g><g id="Rectangle-18-+-Triangle-2" transform="matrix(1 0 0 -1 0 240)"><rect id="Rectangle-18" width="15" height="19" x=".5" y=".5" fill="url(#linearGradient-3)" stroke="#D1CFCD" rx="3"/><path id="Triangle-1" fill="#7E7C7B" d="M8 7l3.2 6H4.8z"/></g><g id="Rectangle-18-+-Triangle-3-+-Group" transform="translate(0 50)"><g id="Rectangle-18-+-Triangle-3" fill="url(#linearGradient-4)" stroke="#D1CFCD" transform="matrix(1 0 0 -1 0 51)"><rect id="Rectangle-18" width="15" height="50" x=".5" y=".5" rx="3"/></g><g id="Group" fill="#D1CFCD" stroke="#7E7C7B" transform="translate(4 20)"><path id="Rectangle-22" d="M.5.5h7v1h-7z"/><path id="Rectangle-23" d="M.5 3.5h7v1h-7z"/><path id="Rectangle-24" d="M.5 6.5h7v1h-7z"/><path id="Rectangle-25" d="M.5 9.5h7v1h-7z"/></g></g></g></g></g></svg> \ No newline at end of file diff --git a/2-ui/1-document/09-size-and-scroll/metric-offset-parent@2x.png b/2-ui/1-document/09-size-and-scroll/metric-offset-parent@2x.png deleted file mode 100644 index 0f86b4f858..0000000000 Binary files a/2-ui/1-document/09-size-and-scroll/metric-offset-parent@2x.png and /dev/null differ diff --git a/2-ui/1-document/09-size-and-scroll/metric-offset-width-height.png b/2-ui/1-document/09-size-and-scroll/metric-offset-width-height.png deleted file mode 100644 index 82551b8e1c..0000000000 Binary files a/2-ui/1-document/09-size-and-scroll/metric-offset-width-height.png and /dev/null differ diff --git a/2-ui/1-document/09-size-and-scroll/metric-offset-width-height.svg b/2-ui/1-document/09-size-and-scroll/metric-offset-width-height.svg new file mode 100644 index 0000000000..49bdccda78 --- /dev/null +++ b/2-ui/1-document/09-size-and-scroll/metric-offset-width-height.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="508" height="509" viewBox="0 0 508 509"><defs><style>@import url(https://fonts.googleapis.com/css?family=Open+Sans:bold,italic,bolditalic%7CPT+Mono);@font-face{font-family:'PT Mono';font-weight:700;font-style:normal;src:local('PT MonoBold'),url(/font/PTMonoBold.woff2) format('woff2'),url(/font/PTMonoBold.woff) format('woff'),url(/font/PTMonoBold.ttf) format('truetype')}</style></defs><defs><linearGradient id="linearGradient-1" x1="50%" x2="50%" y1="0%" y2="100%"><stop offset="0%" stop-color="#FFF"/><stop offset="100%" stop-color="#D1CFCD"/></linearGradient><linearGradient id="linearGradient-2" x1="50%" x2="50%" y1="0%" y2="100%"><stop offset="0%" stop-color="#FFF"/><stop offset="100%" stop-color="#D1CFCD"/></linearGradient></defs><g id="dom" fill="none" fill-rule="evenodd" stroke="none" stroke-width="1"><g id="metric-offset-width-height.svg"><path id="Rectangle-2" fill="#DBAF88" d="M420 130v290H30V130h390zm-25 25H55v240h340V155z"/><path id="Rectangle-1" stroke="#C06334" stroke-width="2" d="M359 177v199H76V177h283z"/><g id="Group" transform="translate(379 155)"><rect id="Rectangle-19" width="15" height="239" x=".5" y=".5" fill="#D1CFCD" stroke="#D1CFCD" rx="3"/><g id="Rectangle-18-+-Triangle-1"><rect id="Rectangle-18" width="15" height="19" x=".5" y=".5" fill="url(#linearGradient-1)" stroke="#D1CFCD" rx="3"/><path id="Triangle-1" fill="#7E7C7B" d="M8 7l3.2 6H4.8z"/></g><g id="Rectangle-18-+-Triangle-2" transform="matrix(1 0 0 -1 0 240)"><rect id="Rectangle-18" width="15" height="19" x=".5" y=".5" fill="url(#linearGradient-1)" stroke="#D1CFCD" rx="3"/><path id="Triangle-1" fill="#7E7C7B" d="M8 7l3.2 6H4.8z"/></g><g id="Rectangle-18-+-Triangle-3-+-Group" transform="translate(0 50)"><g id="Rectangle-18-+-Triangle-3" fill="url(#linearGradient-2)" stroke="#D1CFCD" transform="matrix(1 0 0 -1 0 51)"><rect id="Rectangle-18" width="15" height="50" x=".5" y=".5" rx="3"/></g><g id="Group" fill="#D1CFCD" stroke="#7E7C7B" transform="translate(4 20)"><path id="Rectangle-22" d="M.5.5h7v1h-7z"/><path id="Rectangle-23" d="M.5 3.5h7v1h-7z"/><path id="Rectangle-24" d="M.5 6.5h7v1h-7z"/><path id="Rectangle-25" d="M.5 9.5h7v1h-7z"/></g></g></g><text id="border" font-family="PTMono-Regular, PT Mono" font-size="12" font-weight="normal"><tspan x="21.9" y="75" fill="#C06334">border</tspan> <tspan x="29.1" y="89" fill="#1C85B5">25px</tspan></text><text id="padding" font-family="PTMono-Regular, PT Mono" font-size="12" font-weight="normal"><tspan x="41.3" y="45" fill="#C06334">padding</tspan> <tspan x="52.1" y="59" fill="#1C85B5">20px</tspan></text><text id="content-width:284px" font-family="PTMono-Regular, PT Mono" font-size="12" font-weight="normal"><tspan x="148.1" y="109" fill="#C06334">content width:</tspan> <tspan x="248.9" y="109" fill="#1C85B5">284px</tspan></text><text id="height:200px" font-family="PTMono-Regular, PT Mono" font-size="12" font-weight="normal" transform="rotate(-90 429.5 274)"><tspan x="386.3" y="278" fill="#C06334">height:</tspan> <tspan x="436.7" y="278" fill="#1C85B5">200px</tspan></text><path id="Line" stroke="#C06334" stroke-dasharray="3,6" stroke-linecap="square" stroke-width="2" d="M360.5 175h88.142"/><path id="Line-2" stroke="#C06334" stroke-dasharray="3,6" stroke-linecap="square" stroke-width="2" d="M360.5 377h88.142"/><path id="Line" fill="#C06334" fill-rule="nonzero" d="M442 177.5l7 14h-6V360h6l-7 14-7-14h6V191.5h-6l7-14z"/><path id="Line-15" fill="#C06334" fill-rule="nonzero" d="M345.5 110l14 7-14 7v-6H91.679l.001 6-14-7 14-7-.001 6H345.5v-6z"/><path id="Line-14" stroke="#C06334" stroke-dasharray="3,6" stroke-linecap="square" stroke-width="2" d="M30.48 93v43"/><path id="Line-13" stroke="#C06334" stroke-dasharray="3,6" stroke-linecap="square" stroke-width="2" d="M56.48 93v41"/><text id="border-2" font-family="PTMono-Regular, PT Mono" font-size="12" font-weight="normal"><tspan x="385.9" y="76" fill="#C06334">border</tspan> <tspan x="393.1" y="90" fill="#1C85B5">25px</tspan></text><text id="padding-2" font-family="PTMono-Regular, PT Mono" font-size="12" font-weight="normal"><tspan x="348.3" y="16" fill="#C06334">padding</tspan> <tspan x="359.1" y="30" fill="#1C85B5">20px</tspan></text><text id="scrollbar" font-family="PTMono-Regular, PT Mono" font-size="12" font-weight="normal"><tspan x="358.1" y="48" fill="#C06334">scrollbar</tspan> <tspan x="376.1" y="62" fill="#1C85B5">16px</tspan></text><path id="Line-17" stroke="#C06334" stroke-dasharray="3,6" stroke-linecap="square" stroke-width="2" d="M380.48 93v43"/><path id="Line-20" stroke="#C06334" stroke-dasharray="3,6" stroke-linecap="square" stroke-width="2" d="M394.48 93v43"/><path id="Line-18" stroke="#C06334" stroke-dasharray="3,6" stroke-linecap="square" stroke-width="2" d="M419.48 93v41"/><path id="Line-16" stroke="#C06334" stroke-dasharray="3,6" stroke-linecap="square" stroke-width="2" d="M75.48 93v41"/><path id="Line-19" stroke="#C06334" stroke-dasharray="3,6" stroke-linecap="square" stroke-width="2" d="M362.48 93v41"/><text id="offsetWidth-=-25+20+" font-family="PTMono-Regular, PT Mono" font-size="14" font-weight="normal"><tspan x="56.5" y="484" fill="#C06334">offsetWidth = </tspan> <tspan x="174.1" y="484" fill="#1C85B5">25+20+284+20+16+25 </tspan> <tspan x="333.7" y="484" fill="#C06334">=</tspan> <tspan x="342.1" y="484" fill="#1C85B5"> 390px</tspan></text><path id="Line-24" stroke="#C06334" stroke-dasharray="3,6" stroke-linecap="square" stroke-width="2" d="M30.5 419v78"/><path id="Line-25" stroke="#C06334" stroke-dasharray="3,6" stroke-linecap="square" stroke-width="2" d="M419.5 419v80"/><path id="Line-22" fill="#C06334" fill-rule="nonzero" d="M403 456.071l14 7-14 7v-6.001H47.089l.001 6.001-14-7 14-7-.001 5.999H403v-5.999z"/><text id="offsetHeight:290px" font-family="PTMono-Regular, PT Mono" font-size="14" font-weight="normal" transform="rotate(-90 483.5 280.5)"><tspan x="407.9" y="285" fill="#C06334">offsetHeight:</tspan> <tspan x="517.1" y="285" fill="#1C85B5">290px</tspan></text><path id="Line-27" stroke="#C06334" stroke-dasharray="3,6" stroke-linecap="square" stroke-width="2" d="M417.5 131h88.142"/><path id="Line-28" stroke="#C06334" stroke-dasharray="3,6" stroke-linecap="square" stroke-width="2" d="M417.5 420h88.142"/><path id="Line-26" fill="#C06334" fill-rule="nonzero" d="M469 132.59l7 14-6-.001V405.41h6l-7 14-7-14h6V146.589l-6 .001 7-14z"/><text id="Introduction" fill="#643B0C" font-family="OpenSans-Bold, Open Sans" font-size="16" font-weight="bold"><tspan x="79" y="193">Introduction</tspan> <tspan x="79" y="221" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">This Ecma Standard is based on several </tspan> <tspan x="79" y="240" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">originating technologies, the most well </tspan> <tspan x="79" y="259" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">known being JavaScript (Netscape) and </tspan> <tspan x="79" y="278" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">JScript (Microsoft). The language was </tspan> <tspan x="79" y="297" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">invented by Brendan Eich at Netscape and </tspan> <tspan x="79" y="316" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">first appeared in that company’s Navigator </tspan> <tspan x="79" y="335" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">2.0 browser. It has appeared in all </tspan> <tspan x="79" y="354" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">subsequent browsers from Netscape and </tspan> <tspan x="79" y="373" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">in all browsers from Microsoft starting </tspan> <tspan x="79" y="392" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">with </tspan></text></g></g></svg> \ No newline at end of file diff --git a/2-ui/1-document/09-size-and-scroll/metric-offset-width-height@2x.png b/2-ui/1-document/09-size-and-scroll/metric-offset-width-height@2x.png deleted file mode 100644 index 805687cd6b..0000000000 Binary files a/2-ui/1-document/09-size-and-scroll/metric-offset-width-height@2x.png and /dev/null differ diff --git a/2-ui/1-document/09-size-and-scroll/metric-scroll-top.png b/2-ui/1-document/09-size-and-scroll/metric-scroll-top.png deleted file mode 100644 index 83f410837c..0000000000 Binary files a/2-ui/1-document/09-size-and-scroll/metric-scroll-top.png and /dev/null differ diff --git a/2-ui/1-document/09-size-and-scroll/metric-scroll-top.svg b/2-ui/1-document/09-size-and-scroll/metric-scroll-top.svg new file mode 100644 index 0000000000..c6d14d0f38 --- /dev/null +++ b/2-ui/1-document/09-size-and-scroll/metric-scroll-top.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="489" height="542" viewBox="0 0 489 542"><defs><style>@import url(https://fonts.googleapis.com/css?family=Open+Sans:bold,italic,bolditalic%7CPT+Mono);@font-face{font-family:'PT Mono';font-weight:700;font-style:normal;src:local('PT MonoBold'),url(/font/PTMonoBold.woff2) format('woff2'),url(/font/PTMonoBold.woff) format('woff'),url(/font/PTMonoBold.ttf) format('truetype')}</style></defs><defs><linearGradient id="linearGradient-1" x1="50%" x2="50%" y1="0%" y2="100%"><stop offset="0%" stop-color="#FFF"/><stop offset="100%" stop-color="#D1CFCD"/></linearGradient><linearGradient id="linearGradient-2" x1="50%" x2="50%" y1="0%" y2="100%"><stop offset="0%" stop-color="#FFF"/><stop offset="100%" stop-color="#D1CFCD"/></linearGradient></defs><g id="dom" fill="none" fill-rule="evenodd" stroke="none" stroke-width="1"><g id="metric-scroll-top.svg"><path fill="#FFF" d="M0 0h489v542H0z"/><text id="Introduction" fill="#643B0C" font-family="OpenSans-Bold, Open Sans" font-size="16" font-weight="bold"><tspan x="94" y="54">Introduction</tspan> <tspan x="94" y="82" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">This Ecma Standard is based on several </tspan> <tspan x="94" y="101" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">originating technologies, the most well </tspan> <tspan x="94" y="120" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">known being JavaScript (Netscape) and </tspan> <tspan x="94" y="139" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">JScript (Microsoft). The language was </tspan> <tspan x="94" y="158" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">invented by Brendan Eich at Netscape and </tspan> <tspan x="94" y="177" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">first appeared in that company’s Navigator </tspan> <tspan x="94" y="196" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">2.0 browser. It has appeared in all </tspan> <tspan x="94" y="215" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">subsequent browsers from Netscape and </tspan> <tspan x="94" y="234" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">in all browsers from Microsoft starting with </tspan> <tspan x="94" y="253" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">Internet Explorer 3.0.</tspan> <tspan x="94" y="272" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">The development of this Standard started </tspan> <tspan x="94" y="291" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">in November 1996. The first edition of this </tspan> <tspan x="94" y="310" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">Ecma Standard was adopted by the Ecma </tspan> <tspan x="94" y="329" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">General Assembly of June 1997.</tspan> <tspan x="94" y="348" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">That Ecma Standard was submitted to ISO/</tspan> <tspan x="94" y="367" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">IEC JTC 1 for adoption under the fast-track </tspan> <tspan x="94" y="386" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">procedure, and approved as international </tspan> <tspan x="94" y="405" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">standard ISO/IEC 16262, in April 1998. The </tspan> <tspan x="94" y="424" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">Ecma General Assembly of June 1998 </tspan> <tspan x="94" y="443" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">approved the second edition of ECMA-262 </tspan> <tspan x="94" y="462" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">to keep it fully aligned with ISO/IEC 16262. </tspan> <tspan x="94" y="481" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">Changes between the first and the second </tspan> <tspan x="94" y="500" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">edition are editorial in nature.</tspan></text><path id="Rectangle-1" fill="#DBAF88" d="M425 122v290H51V122h374zm-25 25H76v240h324V147z"/><path id="Rectangle-2" stroke="#DBAF88" stroke-width="2" d="M75 22h326v500H75z"/><text id="scrollTop" fill="#C06334" font-family="PTMono-Regular, PT Mono" font-size="12" font-weight="normal" transform="rotate(-90 16.5 84)"><tspan x="-15.9" y="88">scrollTop</tspan></text><path id="Line-43" stroke="#C06334" stroke-dasharray="3,6" stroke-linecap="square" stroke-width="2" d="M9.5 147h66.14"/><path id="Line-42" stroke="#C06334" stroke-dasharray="3,6" stroke-linecap="square" stroke-width="2" d="M10.5 21h66.14"/><path id="Line-39" fill="#C06334" fill-rule="nonzero" d="M35 20.5l7 14h-6v97.819l6 .001-7 14-7-14 6-.001V34.5h-6l7-14z"/><path id="Rectangle-14" fill="#FFF" d="M88 33h312v89H88z"/><path id="Rectangle-15" fill="#FFF" d="M88 411h312v89H88z"/><text id="scrollHeight:723px" font-family="PTMono-Regular, PT Mono" font-size="14" font-weight="normal" transform="rotate(-90 449 270)"><tspan x="373.4" y="274.5" fill="#C06334">scrollHeight:</tspan> <tspan x="482.6" y="274.5" fill="#1C85B5">723px</tspan></text><path id="Line-27" stroke="#C06334" stroke-dasharray="3,6" stroke-linecap="square" stroke-width="2" d="M393.5 22h78.14"/><path id="Line-28" stroke="#C06334" stroke-dasharray="3,6" stroke-linecap="square" stroke-width="2" d="M393.5 522h78.14"/><path id="Line-25" fill="#C06334" fill-rule="nonzero" d="M462 24.5l7 14h-6v466h6l-7 14-7-14h6v-466h-6l7-14z"/><g id="Group" transform="translate(384 147)"><rect id="Rectangle-19" width="15" height="239" x=".5" y=".5" fill="#D1CFCD" stroke="#D1CFCD" rx="3"/><g id="Rectangle-18-+-Triangle-1"><rect id="Rectangle-18" width="15" height="19" x=".5" y=".5" fill="url(#linearGradient-1)" stroke="#D1CFCD" rx="3"/><path id="Triangle-1" fill="#7E7C7B" d="M8 7l3.2 6H4.8z"/></g><g id="Rectangle-18-+-Triangle-2" transform="matrix(1 0 0 -1 0 240)"><rect id="Rectangle-18" width="15" height="19" x=".5" y=".5" fill="url(#linearGradient-1)" stroke="#D1CFCD" rx="3"/><path id="Triangle-1" fill="#7E7C7B" d="M8 7l3.2 6H4.8z"/></g><g id="Rectangle-18-+-Triangle-3-+-Group" transform="translate(0 50)"><g id="Rectangle-18-+-Triangle-3" fill="url(#linearGradient-2)" stroke="#D1CFCD" transform="matrix(1 0 0 -1 0 51)"><rect id="Rectangle-18" width="15" height="50" x=".5" y=".5" rx="3"/></g><g id="Group" fill="#D1CFCD" stroke="#7E7C7B" transform="translate(4 20)"><path id="Rectangle-22" d="M.5.5h7v1h-7z"/><path id="Rectangle-23" d="M.5 3.5h7v1h-7z"/><path id="Rectangle-24" d="M.5 6.5h7v1h-7z"/><path id="Rectangle-25" d="M.5 9.5h7v1h-7z"/></g></g></g></g></g></svg> \ No newline at end of file diff --git a/2-ui/1-document/09-size-and-scroll/metric-scroll-top@2x.png b/2-ui/1-document/09-size-and-scroll/metric-scroll-top@2x.png deleted file mode 100644 index 1ef15502e7..0000000000 Binary files a/2-ui/1-document/09-size-and-scroll/metric-scroll-top@2x.png and /dev/null differ diff --git a/2-ui/1-document/09-size-and-scroll/metric-scroll-width-height.png b/2-ui/1-document/09-size-and-scroll/metric-scroll-width-height.png deleted file mode 100644 index 54e6b96344..0000000000 Binary files a/2-ui/1-document/09-size-and-scroll/metric-scroll-width-height.png and /dev/null differ diff --git a/2-ui/1-document/09-size-and-scroll/metric-scroll-width-height.svg b/2-ui/1-document/09-size-and-scroll/metric-scroll-width-height.svg new file mode 100644 index 0000000000..0c3d299528 --- /dev/null +++ b/2-ui/1-document/09-size-and-scroll/metric-scroll-width-height.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="463" height="524" viewBox="0 0 463 524"><defs><style>@import url(https://fonts.googleapis.com/css?family=Open+Sans:bold,italic,bolditalic%7CPT+Mono);@font-face{font-family:'PT Mono';font-weight:700;font-style:normal;src:local('PT MonoBold'),url(/font/PTMonoBold.woff2) format('woff2'),url(/font/PTMonoBold.woff) format('woff'),url(/font/PTMonoBold.ttf) format('truetype')}</style></defs><defs><linearGradient id="linearGradient-1" x1="50%" x2="50%" y1="0%" y2="100%"><stop offset="0%" stop-color="#FFF"/><stop offset="100%" stop-color="#D1CFCD"/></linearGradient><linearGradient id="linearGradient-2" x1="50%" x2="50%" y1="0%" y2="100%"><stop offset="0%" stop-color="#FFF"/><stop offset="100%" stop-color="#D1CFCD"/></linearGradient></defs><g id="dom" fill="none" fill-rule="evenodd" stroke="none" stroke-width="1"><g id="metric-scroll-width-height.svg"><path fill="#FFF" d="M0 0h463v524H0z"/><text id="Introduction" fill="#643B0C" font-family="OpenSans-Bold, Open Sans" font-size="16" font-weight="bold"><tspan x="66" y="54">Introduction</tspan> <tspan x="66" y="82" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">This Ecma Standard is based on several </tspan> <tspan x="66" y="101" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">originating technologies, the most well </tspan> <tspan x="66" y="120" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">known being JavaScript (Netscape) and </tspan> <tspan x="66" y="139" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">JScript (Microsoft). The language was </tspan> <tspan x="66" y="158" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">invented by Brendan Eich at Netscape </tspan> <tspan x="66" y="177" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">and first appeared in that company’s </tspan> <tspan x="66" y="196" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">Navigator 2.0 browser. It has appeared </tspan> <tspan x="66" y="215" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">in all subsequent browsers from </tspan> <tspan x="66" y="234" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">Netscape and in all browsers from </tspan> <tspan x="66" y="253" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">Microsoft starting with Internet Explorer </tspan> <tspan x="66" y="272" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">3.0.</tspan> <tspan x="66" y="291" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">The development of this Standard </tspan> <tspan x="66" y="310" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">started in November 1996. The first </tspan> <tspan x="66" y="329" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">edition of this Ecma Standard was </tspan> <tspan x="66" y="348" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">adopted by the Ecma General Assembly </tspan> <tspan x="66" y="367" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">of June 1997.</tspan> <tspan x="66" y="386" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">That Ecma Standard was submitted to </tspan> <tspan x="66" y="405" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">ISO/IEC JTC 1 for adoption under the </tspan> <tspan x="66" y="424" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">fast-track procedure, and approved as </tspan> <tspan x="66" y="443" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">international standard ISO/IEC 16262, in </tspan> <tspan x="66" y="462" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">April 1998. The Ecma General Assembly </tspan> <tspan x="66" y="481" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">of June 1998 approved the second </tspan> <tspan x="66" y="500" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">edition of ECMA-262 to keep it fully </tspan> <tspan x="66" y="519" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">aligned with ISO/IEC 16262. Changes </tspan> <tspan x="66" y="538" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">between the first and the second </tspan> <tspan x="66" y="557" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">edition are editorial in nature.</tspan></text><path id="Rectangle-15" fill="#FFF" d="M58 410h312v111H58z"/><path id="Rectangle-14" fill="#FFF" d="M58 35h312v89H58z"/><path id="Rectangle-1" fill="#DBAF88" d="M395 123v290H21V123h374zm-25 25H46v240h324V148z"/><path id="Rectangle-2" stroke="#DBAF88" stroke-width="2" d="M45 22h326v502H45z"/><text id="scrollHeight:723px" font-family="PTMono-Regular, PT Mono" font-size="14" font-weight="normal" transform="rotate(-90 426 270.5)"><tspan x="350.4" y="275" fill="#C06334">scrollHeight:</tspan> <tspan x="459.6" y="275" fill="#1C85B5">723px</tspan></text><path id="Line-27" stroke="#C06334" stroke-dasharray="3,6" stroke-linecap="square" stroke-width="2" d="M371 22h78"/><path id="Line-26" stroke="#C06334" stroke-dasharray="3,6" stroke-linecap="square" stroke-width="2" d="M371 524h78"/><path id="Line-25" fill="#C06334" fill-rule="nonzero" d="M439 26l7 14h-6v466h6l-7 14-7-14h6V40h-6l7-14z"/><path id="Line-39" fill="#C06334" fill-rule="nonzero" d="M335.36 102l14 7-14 7-.001-6H64v6l-14-7 14-7v6h271.359l.001-6z"/><path id="Line-42" stroke="#C06334" stroke-dasharray="3,6" stroke-linecap="square" stroke-width="2" d="M352 154v-54"/><path id="Line-43" stroke="#C06334" stroke-dasharray="3,6" stroke-linecap="square" stroke-width="2" d="M47 154v-54"/><text id="scrollWidth-=-324px" font-family="PTMono-Regular, PT Mono" font-size="14" font-weight="normal"><tspan x="149.2" y="105" fill="#C06334">scrollWidth = </tspan> <tspan x="266.8" y="105" fill="#1C85B5">324px</tspan></text><g id="Group" transform="translate(354 148)"><rect id="Rectangle-19" width="15" height="239" x=".5" y=".5" fill="#D1CFCD" stroke="#D1CFCD" rx="3"/><g id="Rectangle-18-+-Triangle-1"><rect id="Rectangle-18" width="15" height="19" x=".5" y=".5" fill="url(#linearGradient-1)" stroke="#D1CFCD" rx="3"/><path id="Triangle-1" fill="#7E7C7B" d="M8 7l3.2 6H4.8z"/></g><g id="Rectangle-18-+-Triangle-2" transform="matrix(1 0 0 -1 0 240)"><rect id="Rectangle-18" width="15" height="19" x=".5" y=".5" fill="url(#linearGradient-1)" stroke="#D1CFCD" rx="3"/><path id="Triangle-1" fill="#7E7C7B" d="M8 7l3.2 6H4.8z"/></g><g id="Rectangle-18-+-Triangle-3-+-Group" transform="translate(0 50)"><g id="Rectangle-18-+-Triangle-3" fill="url(#linearGradient-2)" stroke="#D1CFCD" transform="matrix(1 0 0 -1 0 51)"><rect id="Rectangle-18" width="15" height="50" x=".5" y=".5" rx="3"/></g><g id="Group" fill="#D1CFCD" stroke="#7E7C7B" transform="translate(4 20)"><path id="Rectangle-22" d="M.5.5h7v1h-7z"/><path id="Rectangle-23" d="M.5 3.5h7v1h-7z"/><path id="Rectangle-24" d="M.5 6.5h7v1h-7z"/><path id="Rectangle-25" d="M.5 9.5h7v1h-7z"/></g></g></g></g></g></svg> \ No newline at end of file diff --git a/2-ui/1-document/09-size-and-scroll/metric-scroll-width-height@2x.png b/2-ui/1-document/09-size-and-scroll/metric-scroll-width-height@2x.png deleted file mode 100644 index d57c5c198f..0000000000 Binary files a/2-ui/1-document/09-size-and-scroll/metric-scroll-width-height@2x.png and /dev/null differ diff --git a/2-ui/1-document/09-size-and-scroll/metric.view/index.html b/2-ui/1-document/09-size-and-scroll/metric.view/index.html index 4ac208164c..b2d8c392eb 100755 --- a/2-ui/1-document/09-size-and-scroll/metric.view/index.html +++ b/2-ui/1-document/09-size-and-scroll/metric.view/index.html @@ -43,7 +43,7 @@ <h3>Introduction</h3> </div> - <div id="mouse-wrap">Mouse coordinates: <span id="mouse">...</span></div> + <div id="mouse-wrap">Координаты мыши: <span id="mouse">...</span></div> <div id="info"></div> diff --git a/2-ui/1-document/10-size-and-scroll-window/article.md b/2-ui/1-document/10-size-and-scroll-window/article.md index b8c16a3c7f..09335e0239 100644 --- a/2-ui/1-document/10-size-and-scroll-window/article.md +++ b/2-ui/1-document/10-size-and-scroll-window/article.md @@ -1,50 +1,50 @@ -# Window sizes and scrolling +# Размеры и прокрутка окна -How to find out the width and height of the browser window? How to get the full width and height of the document, including the scrolled out part? How to scroll the page using JavaScript? +Как узнать ширину и высоту окна браузера? Как получить полную ширину и высоту документа, включая прокрученную часть? Как прокрутить страницу с помощью JavaScript? -From the DOM point of view, the root document element is `document.documentElement`. That element corresponds to `<html>` and has geometry properties described in the [previous chapter](info:size-and-scroll). For some cases we can use it, but there are additional methods and peculiarities important enough to consider. +Для большинства таких запросов мы можем использовать корневой элемент документа `document.documentElement`, который соответствует тегу `<html>`. Однако есть дополнительные методы и особенности, которые необходимо учитывать. -## Width/height of the window +## Ширина/высота окна -Properties `clientWidth/clientHeight` of `document.documentElement` is exactly what we want here: +Чтобы получить ширину/высоту окна, можно взять свойства `clientWidth/clientHeight` из `document.documentElement`: -![](document-client-width-height.png) +![](document-client-width-height.svg) ```online -For instance, this button shows the height of your window: +Например, эта кнопка показывает высоту вашего окна: <button onclick="alert(document.documentElement.clientHeight)">alert(document.documentElement.clientHeight)</button> ``` -````warn header="Not `window.innerWidth/Height`" -Browsers also support properties `window.innerWidth/innerHeight`. They look like what we want. So what's the difference? +````warn header="Не `window.innerWidth/Height`" +Браузеры также поддерживают свойства `window.innerWidth/innerHeight`. Вроде бы, похоже на то, что нам нужно. Почему же не использовать их? -If there's a scrollbar occupying some space, `clientWidth/clientHeight` provide the width/height inside it. In other words, they return width/height of the visible part of the document, available for the content. +Если есть полоса прокрутки, и она занимает какое-то место, то свойства `clientWidth/clientHeight` указывают на ширину/высоту документа без неё (за её вычетом). Иными словами, они возвращают высоту/ширину видимой части документа, доступной для содержимого. -And `window.innerWidth/innerHeight` ignore the scrollbar. +А `window.innerWidth/innerHeight` включают в себя полосу прокрутки. -If there's a scrollbar, and it occupies some space, then these two lines show different values: +Если полоса прокрутки занимает некоторое место, то эти две строки выведут разные значения: ```js run -alert( window.innerWidth ); // full window width -alert( document.documentElement.clientWidth ); // window width minus the scrollbar +alert( window.innerWidth ); // полная ширина окна +alert( document.documentElement.clientWidth ); // ширина окна за вычетом полосы прокрутки ``` -In most cases we need the *available* window width: to draw or position something. That is: inside scrollbars if there are any. So we should use `documentElement.clientHeight/Width`. +В большинстве случаев нам нужна *доступная* ширина окна: для рисования или позиционирования. Полоса прокрутки "отъедает" её часть. Поэтому следует использовать `documentElement.clientHeight/Width`. ```` -```warn header="`DOCTYPE` is important" -Please note: top-level geometry properties may work a little bit differently when there's no `<!DOCTYPE HTML>` in HTML. Odd things are possible. +```warn header="`DOCTYPE` -- это важно" +Обратите внимание, что геометрические свойства верхнего уровня могут работать немного иначе, если в HTML нет `<!DOCTYPE HTML>`. Возможны странности. -In modern HTML we should always write `DOCTYPE`. Generally that's not a JavaScript question, but here it affects JavaScript as well. +В современном HTML мы всегда должны указывать `DOCTYPE`. ``` -## Width/height of the document +## Ширина/высота документа -Theoretically, as the root document element is `documentElement.clientWidth/Height`, and it encloses all the content, we could measure its full size as `documentElement.scrollWidth/scrollHeight`. +Теоретически, т.к. корневым элементом документа является `documentElement`, и он включает в себя всё содержимое, мы можем получить полный размер документа как `documentElement.scrollWidth/scrollHeight`. -These properties work well for regular elements. But for the whole page these properties do not work as intended. In Chrome/Safari/Opera if there's no scroll, then `documentElement.scrollHeight` may be even less than `documentElement.clientHeight`! For regular elements that's a nonsense. +Но именно на этом элементе, для страницы в целом, эти свойства работают не так, как предполагается. В Chrome/Safari/Opera, если нет прокрутки, то `documentElement.scrollHeight` может быть даже меньше, чем `documentElement.clientHeight`! С точки зрения элемента это невозможная ситуация. -To have a reliable result on the full document height, we should take the maximum of these properties: +Чтобы надёжно получить полную высоту документа, нам следует взять максимальное из этих свойств: ```js run let scrollHeight = Math.max( @@ -53,106 +53,160 @@ let scrollHeight = Math.max( document.body.clientHeight, document.documentElement.clientHeight ); -alert('Full document height, with scrolled out part: ' + scrollHeight); +alert('Полная высота документа с прокручиваемой частью: ' + scrollHeight); ``` -Why so? Better don't ask. These inconsistencies come from ancient times, not a "smart" logic. +Почему? +Лучше не спрашивайте. Эти несоответствия идут с древних времён. Глубокой логики здесь нет. -## Get the current scroll [#page-scroll] +## Получение текущей прокрутки [#page-scroll] -Regular elements have their current scroll state in `elem.scrollLeft/scrollTop`. +Обычные элементы хранят текущее состояние прокрутки в `elem.scrollLeft/scrollTop`. -What's with the page? Most browsers provide `documentElement.scrollLeft/Top` for the document scroll, but Chrome/Safari/Opera have bugs (like [157855](https://code.google.com/p/chromium/issues/detail?id=157855), [106133](https://bugs.webkit.org/show_bug.cgi?id=106133)) and we should use `document.body` instead of `document.documentElement` there. +Что же со страницей? +В большинстве браузеров мы можем обратиться к `documentElement.scrollLeft/Top`, за исключением основанных на старом WebKit (Safari), где есть ошибка ([5991](https://bugs.webkit.org/show_bug.cgi?id=5991)), и там нужно использовать `document.body` вместо `document.documentElement`. -Luckily, we don't have to remember these peculiarities at all, because of the special properties `window.pageXOffset/pageYOffset`: +К счастью, нам совсем не обязательно запоминать эти особенности, потому что текущую прокрутку можно прочитать из свойств `window.pageXOffset/pageYOffset`: ```js run -alert('Current scroll from the top: ' + window.pageYOffset); -alert('Current scroll from the left: ' + window.pageXOffset); +alert('Текущая прокрутка сверху: ' + window.pageYOffset); +alert('Текущая прокрутка слева: ' + window.pageXOffset); ``` -These properties are read-only. +Эти свойства доступны только для чтения. -## Scrolling: scrollTo, scrollBy, scrollIntoView [#window-scroll] +```smart header="В качестве свойств объекта `window` также доступны `scrollX` и `scrollY`" +По историческим причинам существует два аналога `window.pageXOffset` и `window.pageYOffset`: +- `window.pageXOffset` -- то же самое, что и `window.scrollX`. +- `window.pageYOffset` -- то же самое, что и `window.scrollY`. +``` + +## Прокрутка: scrollTo, scrollBy, scrollIntoView [#window-scroll] ```warn -To scroll the page from JavaScript, its DOM must be fully built. +Для прокрутки страницы из JavaScript её DOM должен быть полностью построен. -For instance, if we try to scroll the page from the script in `<head>`, it won't work. +Например, если мы попытаемся прокрутить страницу из скрипта, подключенного в `<head>`, это не сработает. ``` -Regular elements can be scrolled by changing `scrollTop/scrollLeft`. +Обычные элементы можно прокручивать, изменяя `scrollTop/scrollLeft`. -We can do the same for the page: -- For all browsers except Chrome/Safari/Opera: modify `document.documentElement.scrollTop/Left`. -- In Chrome/Safari/Opera: use `document.body.scrollTop/Left` instead. +Мы можем сделать то же самое для страницы в целом, используя `document.documentElement.scrollTop/Left` (кроме основанных на старом WebKit (Safari), где, как сказано выше, `document.body.scrollTop/Left`). -It should work, but smells like cross-browser incompatibilities. Not good. Fortunately, there's a simpler, more universal solution: special methods [window.scrollBy(x,y)](mdn:api/Window/scrollBy) and [window.scrollTo(pageX,pageY)](mdn:api/Window/scrollTo). +Есть и другие способы, в которых подобных несовместимостей нет: специальные методы [window.scrollBy(x,y)](https://developer.mozilla.org/ru/docs/Web/API/Window/scrollBy) и [window.scrollTo(pageX,pageY)](https://developer.mozilla.org/ru/docs/Web/API/Window/scrollTo). -- The method `scrollBy(x,y)` scrolls the page relative to its current position. For instance, `scrollBy(0,10)` scrolls the page `10px` down. +- Метод `scrollBy(x,y)` прокручивает страницу *относительно её текущего положения*. Например, `scrollBy(0,10)` прокручивает страницу на `10px` вниз. ```online - The button below demonstrates this: + Кнопка ниже демонстрирует это: <button onclick="window.scrollBy(0,10)">window.scrollBy(0,10)</button> ``` -- The method `scrollTo(pageX,pageY)` scrolls the page relative to the document's top-left corner. It's like setting `scrollLeft/scrollTop`. - - To scroll to the very beginning, we can use `scrollTo(0,0)`. +- Метод `scrollTo(pageX,pageY)` прокручивает страницу *на абсолютные координаты* `(pageX,pageY)`. То есть, чтобы левый-верхний угол видимой части страницы имел данные координаты относительно левого верхнего угла документа. Это всё равно, что поставить `scrollLeft/scrollTop`. + Для прокрутки в самое начало мы можем использовать `scrollTo(0,0)`. ```online <button onclick="window.scrollTo(0,0)">window.scrollTo(0,0)</button> ``` -These methods work for all browsers the same way. +В обоих методах вместо координат также может использоваться объект `options`, как аргумент: + +```js +window.scrollTo(options); +window.scrollBy(options); +``` + +`options` поддерживает три свойства: + +```js +window.scrollTo({ + top: 100, + left: 0, + behavior: "smooth" +}); +``` + +- `top` -- то же самое, что `y`/`pageY` +- `left` -- то же самое, что `x`/`pageX` +- `behavior` -- определяет, каким образом будет прокручиваться страница: + + - `"smooth"` -- плавно (не поддерживается в IE и в старых версиях Safari) + - `"instant"` -- мгновенно + - `"auto"` -- определяется браузером (зависит от CSS-свойства [scroll-behavior](https://developer.mozilla.org/ru/docs/Web/CSS/scroll-behavior)) + +```online +Демонстрация плавной прокрутки страницы: + +<button onclick="window.scrollBy({ top: 500, left: 0, behavior: 'smooth' })">window.scrollBy({ top: 500, left: 0, behavior: 'smooth' })</button> +``` ## scrollIntoView -For completeness, let's cover one more method: [elem.scrollIntoView(top)](mdn:api/Element/scrollIntoView). +Для полноты картины давайте рассмотрим ещё один метод: [elem.scrollIntoView(top)](mdn:api/Element/scrollIntoView). -The call to `elem.scrollIntoView(top)` scrolls the page to make `elem` visible. It has one argument: +Вызов `elem.scrollIntoView(top)` прокручивает страницу, чтобы `elem` оказался вверху. +У него есть один аргумент: -- if `top=true` (that's the default), then the page will be scrolled to make `elem` appear on the top of the window. The upper edge of the element is aligned with the window top. -- if `top=false`, then the page scrolls to make `elem` appear at the bottom. The bottom edge of the element is aligned with the window bottom. +- если `top=true` (по умолчанию), то страница будет прокручена, чтобы `elem` появился в верхней части окна. Верхний край элемента совмещён с верхней частью окна. +- если `top=false`, то страница будет прокручена, чтобы `elem` появился внизу. Нижний край элемента будет совмещён с нижним краем окна. ```online -The button below scrolls the page to make itself show at the window top: +Кнопка ниже прокрутит страницу так, что она сама окажется вверху: <button onclick="this.scrollIntoView()">this.scrollIntoView()</button> -And this button scrolls the page to show it at the bottom: +А следующая кнопка прокрутит страницу так, что она сама окажется внизу <button onclick="this.scrollIntoView(false)">this.scrollIntoView(false)</button> ``` -## Forbid the scrolling +Как и `scrollTo`/`scrollBy`, `scrollIntoView` также принимает объект `options` как аргумент (он немного отличается): -Sometimes we need to make the document "unscrollable". For instance, when we need to cover it with a large message requiring immediate attention, and we want the visitor to interact with that message, not with the document. +```js +this.scrollIntoView(options). +``` + +`options` поддерживает три свойства: -To make the document unscrollable, it's enough to set `document.body.style.overflow = "hidden"`. The page will freeze on its current scroll. +```js +this.scrollIntoView({ + behavior: "smooth", + block: "end", + inline: "nearest" +}); +``` + +- `behavior` -- анимация прокрутки (`smooth`, `instant`, `auto`) +- `block` -- вертикальное выравнивание (`start`, `center`, `end`, `nearest`). Значение по умолчанию: `start` +- `inline` -- горизонтальное выравнивание (`start`, `center`, `end`, `nearest`). Значение по умолчанию: `nearest` + +## Запретить прокрутку + +Иногда нам нужно сделать документ "непрокручиваемым". Например, при показе большого диалогового окна над документом – чтобы посетитель мог прокручивать это окно, но не документ. + +Чтобы запретить прокрутку страницы, достаточно установить `document.body.style.overflow = "hidden"`. ```online -Try it: +Попробуйте сами: <button onclick="document.body.style.overflow = 'hidden'">`document.body.style.overflow = 'hidden'`</button> <button onclick="document.body.style.overflow = ''">`document.body.style.overflow = ''`</button> -The first button freezes the scroll, the second one resumes it. +Первая кнопка останавливает прокрутку, вторая возобновляет её. ``` +Аналогичным образом мы можем "заморозить" прокрутку для других элементов, а не только для `document.body`. -We can use the same technique to "freeze" the scroll for other elements, not just for `document.body`. - -The drawback of the method is that the scrollbar disappears. If it occupied some space, then that space is now free, and the content "jumps" to fill it. +Недостатком этого способа является то, что сама полоса прокрутки исчезает. Если она занимала некоторую ширину, то теперь эта ширина освободится, и содержимое страницы расширится, текст «прыгнет», заняв освободившееся место. -That looks a bit odd, but can be worked around if we compare `clientWidth` before and after the freeze, and if it increased (the scrollbar disappeared) then add `padding` to `document.body` in place of the scrollbar, to keep the content width the same. +Это выглядит немного странно, но это можно обойти, если сравнить `clientWidth` до и после остановки, и если `clientWidth` увеличится (значит полоса прокрутки исчезла), то добавить `padding` в `document.body` вместо полосы прокрутки, чтобы оставить ширину содержимого прежней. -## Summary +## Итого -Geometry: +Размеры: -- Width/height of the visible part of the document (content area width/height): `document.documentElement.clientWidth/Height` -- Width/height of the whole document, with the scrolled out part: +- Ширина/высота видимой части документа (ширина/высота области содержимого): `document.documentElement.clientWidth/Height` +- Ширина/высота всего документа со всей прокручиваемой областью страницы: ```js let scrollHeight = Math.max( @@ -162,11 +216,12 @@ Geometry: ); ``` -Scrolling: +Прокрутка: -- Read the current scroll: `window.pageYOffset/pageXOffset`. -- Change the current scroll: +- Прокрутку окна можно получить так: `window.pageYOffset/pageXOffset`. +- Изменить текущую прокрутку: - - `window.scrollTo(pageX,pageY)` -- absolute coordinates, - - `window.scrollBy(x,y)` -- scroll relative the current place, - - `elem.scrollIntoView(top)` -- scroll to make `elem` visible (align with the top/bottom of the window). + - `window.scrollTo(pageX,pageY)` -- абсолютные координаты, + - `window.scrollBy(x,y)` -- прокрутка относительно текущего места, + - `elem.scrollIntoView(top)` -- прокрутить страницу так, чтобы сделать `elem` видимым + (выровнять относительно верхней/нижней части окна). diff --git a/2-ui/1-document/10-size-and-scroll-window/document-client-width-height.png b/2-ui/1-document/10-size-and-scroll-window/document-client-width-height.png deleted file mode 100644 index a3cf95f118..0000000000 Binary files a/2-ui/1-document/10-size-and-scroll-window/document-client-width-height.png and /dev/null differ diff --git a/2-ui/1-document/10-size-and-scroll-window/document-client-width-height.svg b/2-ui/1-document/10-size-and-scroll-window/document-client-width-height.svg new file mode 100644 index 0000000000..829d27ee85 --- /dev/null +++ b/2-ui/1-document/10-size-and-scroll-window/document-client-width-height.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="508" height="203" viewBox="0 0 508 203"><defs><style>@import url(https://fonts.googleapis.com/css?family=Open+Sans:bold,italic,bolditalic%7CPT+Mono);@font-face{font-family:'PT Mono';font-weight:700;font-style:normal;src:local('PT MonoBold'),url(/font/PTMonoBold.woff2) format('woff2'),url(/font/PTMonoBold.woff) format('woff'),url(/font/PTMonoBold.ttf) format('truetype')}</style></defs><g id="dom" fill="none" fill-rule="evenodd" stroke="none" stroke-width="1"><g id="document-client-width-height.svg"><g id="noun_69008_cc" fill="#DBAF88" transform="translate(275 8)"><path id="Shape" d="M179.188 145H3.813C1.708 145 0 143.369 0 141.375V3.625C0 1.631 1.708 0 3.813 0h175.375C181.284 0 183 1.631 183 3.625v137.75c0 1.994-1.716 3.625-3.813 3.625zM7.625 137.75h167.75V7.25H7.625v130.5z"/><path id="Shape" d="M175.375 36.25H7.625c-2.104 0-3.813-1.631-3.813-3.625S5.521 29 7.626 29h167.75c2.097 0 3.813 1.631 3.813 3.625s-1.716 3.625-3.813 3.625zM61.32 126.578c-1.266 0-2.508-.595-3.233-1.682L34.892 89.849a3.486 3.486 0 01.16-4.082l23.195-29.906c1.25-1.631 3.63-1.964 5.33-.746 1.693 1.188 2.059 3.458.8 5.067l-21.646 27.92 21.807 32.951c1.12 1.69.587 3.937-1.197 4.988a3.936 3.936 0 01-2.02.537zM122 126.578a3.802 3.802 0 01-2.028-.559c-1.784-1.058-2.326-3.298-1.197-4.988l21.807-32.95-21.655-27.928c-1.25-1.617-.892-3.878.8-5.067 1.694-1.197 4.08-.849 5.33.76l23.188 29.907a3.462 3.462 0 01.16 4.082L125.21 124.88c-.709 1.102-1.944 1.697-3.21 1.697zM76.25 119.937a4.07 4.07 0 01-1.86-.457c-1.838-.979-2.494-3.183-1.465-4.959l30.18-51.359c1.03-1.755 3.34-2.385 5.2-1.37 1.83.978 2.486 3.175 1.457 4.937l-30.18 51.359c-.701 1.182-1.998 1.849-3.332 1.849zM22.875 18.125c0 2.003-1.706 3.625-3.813 3.625-2.106 0-3.812-1.622-3.812-3.625s1.706-3.625 3.813-3.625c2.106 0 3.812 1.622 3.812 3.625zM38.125 18.125c0 2.003-1.706 3.625-3.813 3.625-2.106 0-3.812-1.622-3.812-3.625s1.706-3.625 3.813-3.625c2.106 0 3.812 1.622 3.812 3.625zM53.375 18.125c0 2.003-1.706 3.625-3.813 3.625-2.106 0-3.812-1.622-3.812-3.625s1.706-3.625 3.813-3.625c2.106 0 3.812 1.622 3.812 3.625z"/></g><text id="documentElement.clie" fill="#AF6E24" font-family="PTMono-Regular, PT Mono" font-size="14" font-weight="normal"><tspan x="12" y="101">documentElement.clientHeight</tspan></text><text id="documentElement.clie" fill="#AF6E24" font-family="PTMono-Regular, PT Mono" font-size="14" font-weight="normal"><tspan x="257" y="195">documentElement.clientWidth</tspan></text><path id="Line-3-Copy" fill="#C06334" fill-rule="nonzero" d="M431.025 158.653l19 9.5-19 9.5-.001-8h-132v8l-19-9.5 19-9.5v8h132v-8z" transform="matrix(-1 0 0 1 730.025 0)"/><path id="Line" fill="#C06334" fill-rule="nonzero" d="M256.5 45l9.5 19-8-.001v64l8 .001-9.5 19-9.5-19 8-.001v-64L247 64l9.5-19z"/></g></g></svg> \ No newline at end of file diff --git a/2-ui/1-document/10-size-and-scroll-window/document-client-width-height@2x.png b/2-ui/1-document/10-size-and-scroll-window/document-client-width-height@2x.png deleted file mode 100644 index 868dc8a266..0000000000 Binary files a/2-ui/1-document/10-size-and-scroll-window/document-client-width-height@2x.png and /dev/null differ diff --git a/2-ui/1-document/11-coordinates/1-find-point-coordinates/solution.md b/2-ui/1-document/11-coordinates/1-find-point-coordinates/solution.md index f3547096fa..ac013283ce 100644 --- a/2-ui/1-document/11-coordinates/1-find-point-coordinates/solution.md +++ b/2-ui/1-document/11-coordinates/1-find-point-coordinates/solution.md @@ -1,8 +1,8 @@ -# Outer corners +# Внешние углы -Outer corners are basically what we get from [elem.getBoundingClientRect()](https://developer.mozilla.org/en-US/docs/DOM/element.getBoundingClientRect). +Координаты внешних углов -- это как раз то, что возвращает функция [elem.getBoundingClientRect()](https://developer.mozilla.org/ru/docs/DOM/element.getBoundingClientRect). -Coordinates of the upper-left corner `answer1` and the bottom-right corner `answer2`: +Координаты верхнего левого внешнего угла будут в переменной `answer1` и нижнего правого -- в `answer2`: ```js let coords = elem.getBoundingClientRect(); @@ -11,19 +11,19 @@ let answer1 = [coords.left, coords.top]; let answer2 = [coords.right, coords.bottom]; ``` -# Left-upper inner corner +# Верхний левый внутренний угол -That differs from the outer corner by the border width. A reliable way to get the distance is `clientLeft/clientTop`: +Тут значения отличаются на ширину рамки. Надёжный способ получить интересующее значение -- это использовать `clientLeft/clientTop`: ```js let answer3 = [coords.left + field.clientLeft, coords.top + field.clientTop]; ``` -# Right-bottom inner corner +# Нижний правый внутренний угол -In our case we need to substract the border size from the outer coordinates. +В нашем случае нужно вычесть размеры рамки из внешних координат. -We could use CSS way: +Это может быть сделано с помощью CSS: ```js let answer4 = [ @@ -32,7 +32,7 @@ let answer4 = [ ]; ``` -An alternative way would be to add `clientWidth/clientHeight` to coordinates of the left-upper corner. That's probably even better: +Другим вариантом решения было бы добавление `clientWidth/clientHeight` к координатам верхнего левого угла. Так даже было бы лучше. ```js let answer4 = [ diff --git a/2-ui/1-document/11-coordinates/1-find-point-coordinates/solution.view/index.html b/2-ui/1-document/11-coordinates/1-find-point-coordinates/solution.view/index.html index 229c871865..311510cc0c 100755 --- a/2-ui/1-document/11-coordinates/1-find-point-coordinates/solution.view/index.html +++ b/2-ui/1-document/11-coordinates/1-find-point-coordinates/solution.view/index.html @@ -5,7 +5,7 @@ <meta charset="utf-8"> <link rel="stylesheet" href="index.css"> <script> - document.onclick = function(e) { // shows click coordinates + document.onclick = function(e) { // показывает координаты точки клика coords.innerHTML = e.clientX + ':' + e.clientY; }; </script> @@ -13,10 +13,10 @@ <body> - Click anywhere to get window coordinates. - <br> That's for testing, to check the result you get by JavaScript. + Кликните в любом месте для получения координат в контексте окна. + <br> Это для тестирования, чтобы проверить результат, который вы получили с помощью JavaScript. <br> - <div id="coords">(click coordinates show up here)</div> + <div id="coords">(координаты покажутся здесь)</div> <div id="field"> diff --git a/2-ui/1-document/11-coordinates/1-find-point-coordinates/source.view/index.html b/2-ui/1-document/11-coordinates/1-find-point-coordinates/source.view/index.html index dd168f7832..65481cee46 100755 --- a/2-ui/1-document/11-coordinates/1-find-point-coordinates/source.view/index.html +++ b/2-ui/1-document/11-coordinates/1-find-point-coordinates/source.view/index.html @@ -5,7 +5,7 @@ <meta charset="utf-8"> <link rel="stylesheet" href="index.css"> <script> - document.onclick = function(e) { // shows click coordinates + document.onclick = function(e) { // показывает координаты точки клика coords.innerHTML = e.clientX + ':' + e.clientY; }; </script> @@ -13,10 +13,10 @@ <body> - Click anywhere to get window coordinates. - <br> That's for testing, to check the result you get by JavaScript. + Кликните в любом месте для получения координат в контексте окна. + <br> Это для тестирования, чтобы проверить результат, который вы получили с помощью JavaScript. <br> - <div id="coords">(click coordinates show up here)</div> + <div id="coords">(координаты покажутся здесь)</div> <div id="field"> @@ -32,7 +32,7 @@ <script> - // ...your code... + // ...ваш код... </script> </body> diff --git a/2-ui/1-document/11-coordinates/1-find-point-coordinates/task.md b/2-ui/1-document/11-coordinates/1-find-point-coordinates/task.md index 6bbb9fe131..800722d068 100644 --- a/2-ui/1-document/11-coordinates/1-find-point-coordinates/task.md +++ b/2-ui/1-document/11-coordinates/1-find-point-coordinates/task.md @@ -2,23 +2,23 @@ importance: 5 --- -# Find window coordinates of the field +# Найдите координаты точек относительно окна браузера -In the iframe below you can see a document with the green "field". +В ифрейме ниже располагается документ с зелёным "полем". -Use JavaScript to find window coordinates of corners pointed by with arrows. +Используйте JavaScript, чтобы найти координаты углов, обозначенных стрелками. -There's a small feature implemented in the document for convenience. A click at any place shows coordinates there. +В документе уже реализована функциональность, когда при клике на любом месте показываются соответствующие координаты. [iframe border=1 height=360 src="source" link edit] -Your code should use DOM to get window coordinates of: +Ваш код должен при помощи DOM получить четыре пары координат: -1. Upper-left, outer corner (that's simple). -2. Bottom-right, outer corner (simple too). -3. Upper-left, inner corner (a bit harder). -4. Bottom-right, inner corner (there are several ways, choose one). +1. верхний левый, внешний угол (это просто). +2. нижний правый, внешний угол (тоже просто). +3. верхний левый, внутренний угол (чуть сложнее). +4. нижний правый, внутренний угол (есть несколько способов, выберите один). -The coordinates that you calculate should be the same as those returned by the mouse click. +Координаты, вычисленные вами, должны совпадать с теми, которые возвращаются по клику мыши. -P.S. The code should also work if the element has another size or border, not bound to any fixed values. +P.S. Код должен работать, если у элемента другие размеры или есть рамка, без привязки к конкретным числам. diff --git a/2-ui/1-document/11-coordinates/2-position-at/solution.md b/2-ui/1-document/11-coordinates/2-position-at/solution.md index 353eb65ddd..3f7d51e372 100644 --- a/2-ui/1-document/11-coordinates/2-position-at/solution.md +++ b/2-ui/1-document/11-coordinates/2-position-at/solution.md @@ -1,4 +1,4 @@ -In this task we only need to accurately calculate the coordinates. See the code for details. +В этой задаче нам нужно только аккуратно вычислить координаты. Смотрите код для изучения деталей реализации. -Please note: the elements must be in the document to read `offsetHeight` and other properties. -A hidden (`display:none`) or out of the document element has no size. +Обратите внимание, что элементы должны уже быть в документе перед чтением `offsetHeight` и других свойств. +Спрятанный (`display:none`) элемент или элемент вне документа не имеют размеров. diff --git a/2-ui/1-document/11-coordinates/2-position-at/solution.view/index.html b/2-ui/1-document/11-coordinates/2-position-at/solution.view/index.html index f931fbeace..400a294e3e 100755 --- a/2-ui/1-document/11-coordinates/2-position-at/solution.view/index.html +++ b/2-ui/1-document/11-coordinates/2-position-at/solution.view/index.html @@ -24,13 +24,13 @@ <script> /** - * Positions elem relative to anchor as said in position. + * Позиционирует элемент elem относительно элемента anchor в соответствии со значением position. * - * @param {Node} anchor Anchor element for positioning - * @param {string} position One of: top/right/bottom - * @param {Node} elem Element to position + * @param {Node} anchor элемент, около которого позиционируется другой элемент + * @param {string} position одно из: top/right/bottom + * @param {Node} elem элемент, который позиционируется * - * Both elements: elem and anchor must be in the document + * Оба элемента elem и anchor должны присутствовать в документе */ function positionAt(anchor, position, elem) { @@ -56,8 +56,8 @@ } /** - * Shows a note with the given html at the given position - * relative to the anchor element. + * Показывает заметку с заданным содержимым на заданной позиции + * относительно элемента anchor. */ function showNote(anchor, position, html) { @@ -69,7 +69,7 @@ positionAt(anchor, position, note); } - // test it + // проверка let blockquote = document.querySelector('blockquote'); showNote(blockquote, "top", "note above"); diff --git a/2-ui/1-document/11-coordinates/2-position-at/source.view/index.html b/2-ui/1-document/11-coordinates/2-position-at/source.view/index.html index 730f815d94..462359d11d 100755 --- a/2-ui/1-document/11-coordinates/2-position-at/source.view/index.html +++ b/2-ui/1-document/11-coordinates/2-position-at/source.view/index.html @@ -24,24 +24,29 @@ <script> /** - * Positions elem relative to anchor as said in position. + * Позиционирует элемент elem относительно элемента anchor в соответствии со значением position. * - * @param {Node} anchor Anchor element for positioning - * @param {string} position One of: top/right/bottom - * @param {Node} elem Element to position + * @param {Node} anchor элемент, около которого позиционируется другой элемент + * @param {string} position одно из: top/right/bottom + * @param {Node} elem элемент, который позиционируется * - * Both elements: elem and anchor must be in the document + * Оба элемента elem и anchor должны присутствовать в документе */ function positionAt(anchor, position, elem) { - // ... your code ... + // ... ваш код ... } /** - * Shows a note with the given html at the given position - * relative to the anchor element. + * Показывает заметку с заданным содержимым на заданной позиции + * относительно элемента anchor. */ function showNote(anchor, position, html) { - // ... your code ... + let note = document.createElement('div'); + note.className = "note"; + note.innerHTML = html; + document.body.append(note); + + positionAt(anchor, position, note); } // test it diff --git a/2-ui/1-document/11-coordinates/2-position-at/task.md b/2-ui/1-document/11-coordinates/2-position-at/task.md index 1d4f06771c..15b782b92b 100644 --- a/2-ui/1-document/11-coordinates/2-position-at/task.md +++ b/2-ui/1-document/11-coordinates/2-position-at/task.md @@ -2,14 +2,18 @@ importance: 5 --- -# Show a note near the element +# Покажите заметку рядом с элементом -Create a function `positionAt(anchor, position, elem)` that positions `elem`, depending on `position` either at the top (`"top"`), right (`"right"`) or bottom (`"bottom"`) of the element `anchor`. +Создайте функцию `positionAt(anchor, position, elem)`, которая позиционирует элемент `elem` в зависимости от значения свойства `position` рядом с элементом `anchor`. -Call it inside the function `showNote(anchor, position, html)` that shows an element with the class `"note"` and the text `html` at the given position near the anchor. +Аргумент `position` - строка с одним из 3 значений: +- `"top"` - расположить `elem` прямо над `anchor` +- `"right"` - расположить `elem` непосредственно справа от `anchor` +- `"bottom"` - расположить `elem` прямо под `anchor` -Show the notes like here: +Она используется внутри функции `showNote(anchor, position, html)`, которая уже есть в исходном коде задачи. Она создаёт и показывает элемент-"заметку" с текстом `html` на заданной позиции `position` рядом с элементом `anchor`. + +Демо заметки: -[iframe src="solution" height="350" border="1" link] -P.S. The note should have `position:fixed` for this task. +[iframe src="solution" height="350" border="1" link] diff --git a/2-ui/1-document/11-coordinates/3-position-at-absolute/solution.md b/2-ui/1-document/11-coordinates/3-position-at-absolute/solution.md index 014e505ed1..75a70500d7 100644 --- a/2-ui/1-document/11-coordinates/3-position-at-absolute/solution.md +++ b/2-ui/1-document/11-coordinates/3-position-at-absolute/solution.md @@ -1,4 +1,4 @@ -The solution is actually pretty simple: +Решение достаточно простое: -- Use `position:absolute` in CSS instead of `position:fixed` for `.note`. -- Use the function [getCoords()](info:coordinates#getCoords) from the chapter <info:coordinates> to get document-relative coordinates. +- Используйте `position:absolute` в CSS вместо `position:fixed` для элемента с классом `.note`. +- Используйте функцию [getCoords()](info:coordinates#getCoords) из главы <info:coordinates>, чтобы получить координаты относительно документа. diff --git a/2-ui/1-document/11-coordinates/3-position-at-absolute/solution.view/index.html b/2-ui/1-document/11-coordinates/3-position-at-absolute/solution.view/index.html index d627bee84f..abb2f0b884 100644 --- a/2-ui/1-document/11-coordinates/3-position-at-absolute/solution.view/index.html +++ b/2-ui/1-document/11-coordinates/3-position-at-absolute/solution.view/index.html @@ -66,7 +66,7 @@ positionAt(anchor, position, note); } - // test it + // проверка let blockquote = document.querySelector('blockquote'); showNote(blockquote, "top", "note above"); diff --git a/2-ui/1-document/11-coordinates/3-position-at-absolute/task.md b/2-ui/1-document/11-coordinates/3-position-at-absolute/task.md index 0554da862f..35c5df1c01 100644 --- a/2-ui/1-document/11-coordinates/3-position-at-absolute/task.md +++ b/2-ui/1-document/11-coordinates/3-position-at-absolute/task.md @@ -2,10 +2,10 @@ importance: 5 --- -# Show a note near the element (absolute) +# Покажите заметку около элемента (абсолютное позиционирование) -Modify the solution of the [previous task](info:task/position-at) so that the note uses `position:absolute` instead of `position:fixed`. +Измените код решения [предыдущего задания](info:task/position-at) так, чтобы элемент заметки использовал свойство `position:absolute` вместо `position:fixed`. -That will prevent its "runaway" from the element when the page scrolls. +Это предотвратит расхождение элементов при прокрутке страницы. -Take the solution of that task as a starting point. To test the scroll, add the style `<body style="height: 2000px">`. +Используйте решение предыдущего задания для начала. Чтобы проверить решение в условиях с прокруткой, добавьте стиль элементу `<body style="height: 2000px">`. diff --git a/2-ui/1-document/11-coordinates/4-position-inside-absolute/task.md b/2-ui/1-document/11-coordinates/4-position-inside-absolute/task.md index de45b54986..da937f51e5 100644 --- a/2-ui/1-document/11-coordinates/4-position-inside-absolute/task.md +++ b/2-ui/1-document/11-coordinates/4-position-inside-absolute/task.md @@ -2,27 +2,27 @@ importance: 5 --- -# Position the note inside (absolute) +# Расположите заметку внутри элемента (абсолютное позиционирование) -Extend the previous task <info:task/position-at-absolute>: teach the function `positionAt(anchor, position, elem)` to insert `elem` inside the `anchor`. +Усовершенствуйте решение предыдущего задания <info:task/position-at-absolute>: научите функцию `positionAt(anchor, position, elem)` вставлять `elem` внутрь `anchor`. -New values for `position`: +Новые значения для аргумента `position`: -- `top-out`, `right-out`, `bottom-out` -- work the same as before, they insert the `elem` over/right/under `anchor`. -- `top-in`, `right-in`, `bottom-in` -- insert `elem` inside the `anchor`: stick it to the upper/right/bottom edge. +- `top-out`, `right-out`, `bottom-out` -- работают так же, как раньше, они вставляют `elem` сверху/справа/снизу `anchor`. +- `top-in`, `right-in`, `bottom-in` -- вставляют `elem` внутрь `anchor`: приклеивают его к верхнему/правому/нижнему краю. -For instance: +Например: ```js -// shows the note above blockquote +// показывает заметку поверх цитаты positionAt(blockquote, "top-out", note); -// shows the note inside blockquote, at the top +// показывает заметку внутри цитаты вблизи верхнего края элемента positionAt(blockquote, "top-in", note); ``` -The result: +Результат: [iframe src="solution" height="310" border="1" link] -As the source code, take the solution of the task <info:task/position-at-absolute>. +Для начала возьмите решение задания <info:task/position-at-absolute>. diff --git a/2-ui/1-document/11-coordinates/article.md b/2-ui/1-document/11-coordinates/article.md index 61b5161eb7..24bde095e1 100644 --- a/2-ui/1-document/11-coordinates/article.md +++ b/2-ui/1-document/11-coordinates/article.md @@ -1,74 +1,119 @@ -# Coordinates +# Координаты -To move elements around we should be familiar with coordinates. +Чтобы передвигать элементы по экрану, нам следует познакомиться с системами координат. -Most JavaScript methods deal with one of two coordinate systems: +Большинство соответствующих методов JavaScript работают в одной из двух указанных ниже систем координат: -1. Relative to the window(or another viewport) top/left. -2. Relative to the document top/left. +1. **Относительно окна браузера** - как `position:fixed`, отсчёт идёт от верхнего левого угла окна. + - мы будем обозначать эти координаты как `clientX/clientY`, причина выбора таких имён будет ясна позже, когда мы изучим свойства событий. +2. **Относительно документа** - как `position:absolute` на уровне документа, отсчёт идёт от верхнего левого угла документа. + - мы будем обозначать эти координаты как `pageX/pageY`. -It's important to understand the difference and which type is where. +Когда страница полностью прокручена в самое начало, то верхний левый угол окна совпадает с левым верхним углом документа, при этом обе этих системы координат тоже совпадают. Но если происходит прокрутка, то координаты элементов в контексте окна меняются, так как они двигаются, но в то же время их координаты относительно документа остаются такими же. -## Window coordinates: getBoundingClientRect +На приведённой картинке взята точка в документе и показаны её координаты до прокрутки (слева) и после (справа): -Window coordinates start at the upper-left corner of the window. +![](document-and-window-coordinates-scrolled.svg) -The method `elem.getBoundingClientRect()` returns window coordinates for `elem` as an object with properties: +При прокрутке документа: +- `pageY` - координата точки относительно документа осталась без изменений, так как отсчёт по-прежнему ведётся от верхней границы документа (сейчас она прокручена наверх). +- `clientY` - координата точки относительно окна изменилась (стрелка на рисунке стала короче), так как точка стала ближе к верхней границе окна. -- `top` -- Y-coordinate for the top element edge, -- `left` -- X-coordinate for the left element edge, -- `right` -- X-coordinate for the right element edge, -- `bottom` -- Y-coordinate for the bottom element edge. +## Координаты относительно окна: getBoundingClientRect -Like this: +Метод `elem.getBoundingClientRect()` возвращает координаты в контексте окна для минимального по размеру прямоугольника, который заключает в себе элемент `elem`, в виде объекта встроенного класса [DOMRect](https://www.w3.org/TR/geometry-1/#domrect). -![](coords.png) +Основные свойства объекта типа `DOMRect`: +- `x/y` -- X/Y-координаты начала прямоугольника относительно окна, +- `width/height` -- ширина/высота прямоугольника (могут быть отрицательными). -Window coordinates do not take the scrolled out part of the document into account, they are calculated from the window's upper-left corner. +Дополнительные, "зависимые", свойства: -In other words, when we scroll the page, the element goes up or down, *its window coordinates change*. That's very important. +- `top/bottom` -- Y-координата верхней/нижней границы прямоугольника, +- `left/right` -- X-координата левой/правой границы прямоугольника. ```online -Click the button to see its window coordinates: +Кликните на кнопку, чтобы увидеть её координаты относительно окна: -<input id="brTest" type="button" value="Show button.getBoundingClientRect() for this button" onclick='showRect(this)'/> +<p><input id="brTest" type="button" style="max-width: 90vw;" value="Показать результат вызова button.getBoundingClientRect() для этой кнопки" onclick='showRect(this)'/></p> <script> function showRect(elem) { let r = elem.getBoundingClientRect(); - alert("{top:"+r.top+", left:"+r.left+", right:"+r.right+", bottom:"+ r.bottom + "}"); + alert(`x:${r.x} +y:${r.y} +width:${r.width} +height:${r.height} +top:${r.top} +bottom:${r.bottom} +left:${r.left} +right:${r.right} +`); } </script> -If you scroll the page, the button position changes, and window coordinates as well. +Если вы прокрутите страницу, то расположение кнопки в окне поменяется, и, соответственно, её координаты в контексте окна тоже (при вертикальной прокрутке - `y/top/bottom`). ``` -Also: +Вот картинка с результатами вызова `elem.getBoundingClientRect()`: -- Coordinates may be decimal fractions. That's normal, internally browser uses them for calculations. We don't have to round them when setting to `style.position.left/top`, the browser is fine with fractions. -- Coordinates may be negative. For instance, if the page is scrolled down and the top `elem` is now above the window. Then, `elem.getBoundingClientRect().top` is negative. -- Some browsers (like Chrome) provide additional properties, `width` and `height` of the element that invoked the method to `getBoundingClientRect` as the result. We can also get them by subtraction: `height=bottom-top`, `width=right-left`. +![](coordinates.svg) -```warn header="Coordinates right/bottom are different from CSS properties" -If we compare window coordinates versus CSS positioning, then there are obvious similarities to `position:fixed`. The positioning of an element is also relative to the viewport. +Как вы видите, `x/y` и `width/height` уже точно задают прямоугольник. Остальные свойства могут быть легко вычислены на их основе: +- `left = x` +- `top = y` +- `right = x + width` +- `bottom = y + height` -But in CSS, the `right` property means the distance from the right edge, and the `bottom` property means the distance from the bottom edge. +Заметим: -If we just look at the picture above, we can see that in JavaScript it is not so. All window coordinates are counted from the upper-left corner, including these ones. +- Координаты могут считаться с десятичной частью, например `10.5`. Это нормально, ведь браузер использует дроби в своих внутренних вычислениях. Мы не обязаны округлять значения при установке `style.left/top`. +- Координаты могут быть отрицательными. Например, если страница прокручена так, что элемент `elem` ушёл вверх за пределы окна, то вызов `elem.getBoundingClientRect().top` вернёт отрицательное значение. + +```smart header="Зачем вообще нужны зависимые свойства? Для чего существуют `top/left`, если есть `x/y`?" +С математической точки зрения, прямоугольник однозначно задаётся начальной точкой `(x,y)` и вектором направления `(width,height)`. + +Так что дополнительные зависимые свойства существуют лишь для удобства. + +Что же касается `top/left`, то они на самом деле не всегда равны `x/y`. Технически, значения `width/height` могут быть отрицательными. Это позволяет задать "направленный" прямоугольник, например, для выделения мышью с отмеченным началом и концом. + +То есть, отрицательные значения `width/height` означают, что прямоугольник "растет" влево-вверх из правого угла. + +Вот прямоугольник с отрицательными `width` и `height` (например, `width=-200`, `height=-100`): + +![](coordinates-negative.svg) + +Как вы видите, свойства `left/top` при этом не равны `x/y`. + +Впрочем, на практике результат вызова `elem.getBoundingClientRect()` всегда возвращает положительные значения для ширины/высоты. Здесь мы упомянули отрицательные `width/height` лишь для того, чтобы вы поняли, зачем существуют эти с виду дублирующие свойства. +``` + +```warn header="Internet Explorer и Edge: не поддерживают `x/y`" +Internet Explorer и Edge не поддерживают свойства `x/y` по историческим причинам. + +Таким образом, мы можем либо сделать полифил (добавив соответствующие геттеры в `DomRect.prototype`), либо использовать `top/left`, так как это всегда одно и то же при положительных `width/height`, в частности - в результате вызова `elem.getBoundingClientRect()`. +``` + +```warn header="Координаты right/bottom отличаются от одноимённых CSS-свойств" +Есть очевидное сходство между координатами относительно окна и CSS `position:fixed`. + +Но в CSS свойство `right` означает расстояние от правого края, и свойство `bottom` означает расстояние от нижнего края окна браузера. + +Если взглянуть на картинку выше, то видно, что в JavaScript это не так. Все координаты в контексте окна считаются от верхнего левого угла, включая `right/bottom`. ``` ## elementFromPoint(x, y) [#elementFromPoint] -The call to `document.elementFromPoint(x, y)` returns the most nested element at window coordinates `(x, y)`. +Вызов `document.elementFromPoint(x, y)` возвращает самый глубоко вложенный элемент в окне, находящийся по координатам `(x, y)`. -The syntax is: +Синтаксис: ```js let elem = document.elementFromPoint(x, y); ``` -For instance, the code below highlights and outputs the tag of the element that is now in the middle of the window: +Например, код ниже выделяет с помощью стилей и выводит имя тега элемента, который сейчас в центре окна браузера: ```js run let centerX = document.documentElement.clientWidth / 2; @@ -80,45 +125,43 @@ elem.style.background = "red"; alert(elem.tagName); ``` -As it uses window coordinates, the element may be different depending on the current scroll position. - -````warn header="For out-of-window coordinates the `elementFromPoint` returns `null`" -The method `document.elementFromPoint(x,y)` only works if `(x,y)` are inside the visible area. +Поскольку используются координаты в контексте окна, то элемент может быть разным, в зависимости от того, какая сейчас прокрутка. -If any of the coordinates is negative or exceeds the window width/height, then it returns `null`. +````warn header="Для координат за пределами окна метод `elementFromPoint` возвращает `null`" +Метод `document.elementFromPoint(x,y)` работает, только если координаты `(x,y)` относятся к видимой части содержимого окна. -In most cases such behavior is not a problem, but we should keep that in mind. +Если любая из координат представляет собой отрицательное число или превышает размеры окна, то возвращается `null`. -Here's a typical error that may occur if we don't check for it: +Вот типичная ошибка, которая может произойти, если в коде нет соответствующей проверки: ```js let elem = document.elementFromPoint(x, y); -// if the coordinates happen to be out of the window, then elem = null +// если координаты ведут за пределы окна, то elem = null *!* -elem.style.background = ''; // Error! +elem.style.background = ''; // Ошибка! */!* ``` ```` -## Using for position:fixed +## Применение для fixed позиционирования -Most of time we need coordinates to position something. In CSS, to position an element relative to the viewport we use `position:fixed` together with `left/top` (or `right/bottom`). +Чаще всего нам нужны координаты для позиционирования чего-либо. -We can use `getBoundingClientRect` to get coordinates of an element, and then to show something near it. +Чтобы показать что-то около нужного элемента, мы можем вызвать `getBoundingClientRect`, чтобы получить его координаты, а затем использовать CSS-свойство `position` вместе с `left/top` (или `right/bottom`). -For instance, the function `createMessageUnder(elem, html)` below shows the message under `elem`: +Например, функция `createMessageUnder(elem, html)` ниже показывает сообщение под элементом `elem`: ```js let elem = document.getElementById("coords-show-mark"); function createMessageUnder(elem, html) { - // create message element + // создаём элемент, который будет содержать сообщение let message = document.createElement('div'); - // better to use a css class for the style here + // для стилей лучше было бы использовать css-класс здесь message.style.cssText = "position:fixed; color: red"; *!* - // assign coordinates, don't forget "px"! + // устанавливаем координаты элементу, не забываем про "px"! let coords = elem.getBoundingClientRect(); message.style.left = coords.left + "px"; @@ -130,80 +173,84 @@ function createMessageUnder(elem, html) { return message; } -// Usage: -// add it for 5 seconds in the document +// Использование: +// добавим сообщение на страницу на 5 секунд let message = createMessageUnder(elem, 'Hello, world!'); document.body.append(message); setTimeout(() => message.remove(), 5000); ``` ```online -Click the button to run it: +Кликните кнопку, чтобы увидеть пример в действии: -<button id="coords-show-mark">Button with id="coords-show-mark", the message will appear under it</button> +<button id="coords-show-mark">Кнопка с id="coords-show-mark", сообщение появится под ней</button> ``` -The code can be modified to show the message at the left, right, below, apply CSS animations to "fade it in" and so on. That's easy, as we have all the coordinates and sizes of the element. +Код можно изменить, чтобы показывать сообщение слева, справа, снизу, применять к нему CSS-анимации и так далее. Это просто, так как в нашем распоряжении имеются все координаты и размеры элемента. -But note the important detail: when the page is scrolled, the message flows away from the button. +Но обратите внимание на одну важную деталь: при прокрутке страницы сообщение уплывает от кнопки. -The reason is obvious: the message element relies on `position:fixed`, so it remains at the same place of the window while the page scrolls away. +Причина весьма очевидна: сообщение позиционируется с помощью `position:fixed`, поэтому оно остаётся всегда на том же самом месте в окне при прокрутке страницы. -To change that, we need to use document-based coordinates and `position:absolute`. +Чтобы изменить это, нам нужно использовать другую систему координат, где сообщение позиционировалось бы относительно документа, и свойство `position:absolute`. -## Document coordinates +## Координаты относительно документа [#getCoords] -Document-relative coordinates start from the upper-left corner of the document, not the window. +В такой системе координат отсчёт ведётся от левого верхнего угла документа, не окна. -In CSS, window coordinates correspond to `position:fixed`, while document coordinates are similar to `position:absolute` on top. +В CSS координаты относительно окна браузера соответствуют свойству `position:fixed`, а координаты относительно документа -- свойству `position:absolute` на самом верхнем уровне вложенности. -We can use `position:absolute` and `top/left` to put something at a certain place of the document, so that it remains there during a page scroll. But we need the right coordinates first. +Мы можем воспользоваться свойствами `position:absolute` и `top/left`, чтобы привязать что-нибудь к конкретному месту в документе. При этом прокрутка страницы не имеет значения. Но сначала нужно получить верные координаты. -For clarity we'll call window coordinates `(clientX,clientY)` and document coordinates `(pageX,pageY)`. +Не существует стандартного метода, который возвращал бы координаты элемента относительно документа, но мы можем написать его сами. -When the page is not scrolled, then window coordinate and document coordinates are actually the same. Their zero points match too: +Две системы координат связаны следующими формулами: +- `pageY` = `clientY` + высота вертикально прокрученной части документа. +- `pageX` = `clientX` + ширина горизонтально прокрученной части документа. -![](document-window-coordinates-zero.png) +Функция `getCoords(elem)` берёт координаты в контексте окна с помощью `elem.getBoundingClientRect()` и добавляет к ним значение соответствующей прокрутки: -And if we scroll it, then `(clientX,clientY)` change, because they are relative to the window, but `(pageX,pageY)` remain the same. - -Here's the same page after the vertical scroll: +```js +// получаем координаты элемента в контексте документа +function getCoords(elem) { + let box = elem.getBoundingClientRect(); -![](document-window-coordinates-scroll.png) + return { + top: box.top + window.pageYOffset, + right: box.right + window.pageXOffset, + bottom: box.bottom + window.pageYOffset, + left: box.left + window.pageXOffset + }; +} +``` -- `clientY` of the header `"From today's featured article"` became `0`, because the element is now on window top. -- `clientX` didn't change, as we didn't scroll horizontally. -- `pageX` and `pageY` coordinates of the element are still the same, because they are relative to the document. +Если бы в примере выше мы использовали её вместе с `position:absolute`, то при прокрутке сообщение оставалось бы рядом с элементом. -## Getting document coordinates [#getCoords] +Модифицированная функция `createMessageUnder`: -There's no standard method to get the document coordinates of an element. But it's easy to write it. +```js +function createMessageUnder(elem, html) { + let message = document.createElement('div'); + message.style.cssText = "*!*position:absolute*/!*; color: red"; -The two coordinate systems are connected by the formula: -- `pageY` = `clientY` + height of the scrolled-out vertical part of the document. -- `pageX` = `clientX` + width of the scrolled-out horizontal part of the document. + let coords = *!*getCoords(elem);*/!* -The function `getCoords(elem)` will take window coordinates from `elem.getBoundingClientRect()` and add the current scroll to them: + message.style.left = coords.left + "px"; + message.style.top = coords.bottom + "px"; -```js -// get document coordinates of the element -function getCoords(elem) { - let box = elem.getBoundingClientRect(); + message.innerHTML = html; - return { - top: box.top + pageYOffset, - left: box.left + pageXOffset - }; + return message; } ``` -## Summary +## Итого -Any point on the page has coordinates: +Любая точка на странице имеет координаты: -1. Relative to the window -- `elem.getBoundingClientRect()`. -2. Relative to the document -- `elem.getBoundingClientRect()` plus the current page scroll. +1. Относительно окна браузера -- `elem.getBoundingClientRect()`. +2. Относительно документа -- `elem.getBoundingClientRect()` плюс текущая прокрутка страницы. -Window coordinates are great to use with `position:fixed`, and document coordinates do well with `position:absolute`. +Координаты в контексте окна подходят для использования с `position:fixed`, а координаты относительно документа -- для использования с `position:absolute`. -Both coordinate systems have their "pro" and "contra", there are times we need one or the other one, just like CSS `position` `absolute` and `fixed`. +Каждая из систем координат имеет свои преимущества и недостатки. Иногда будет лучше применить одну, а иногда -- другую, как это и происходит с позиционированием в CSS, где мы выбираем между `absolute` и `fixed`. diff --git a/2-ui/1-document/11-coordinates/coordinates-negative.svg b/2-ui/1-document/11-coordinates/coordinates-negative.svg new file mode 100644 index 0000000000..4f2e78687c --- /dev/null +++ b/2-ui/1-document/11-coordinates/coordinates-negative.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="521" height="355" viewBox="0 0 521 355"><defs><style>@import url(https://fonts.googleapis.com/css?family=Open+Sans:bold,italic,bolditalic%7CPT+Mono);@font-face{font-family:'PT Mono';font-weight:700;font-style:normal;src:local('PT MonoBold'),url(/font/PTMonoBold.woff2) format('woff2'),url(/font/PTMonoBold.woff) format('woff'),url(/font/PTMonoBold.ttf) format('truetype')}</style></defs><defs><filter id="filter-1" width="118%" height="209.1%" x="-9%" y="-54.5%" filterUnits="objectBoundingBox"><feGaussianBlur in="SourceGraphic" stdDeviation="4"/></filter></defs><g id="dom" fill="none" fill-rule="evenodd" stroke="none" stroke-width="1"><g id="coordinates-negative.svg"><g id="noun_69008_cc" fill="#DBAF88" transform="translate(13 11)"><path id="Shape" d="M455.313 330H9.688C4.34 330 0 326.288 0 321.75V8.25C0 3.713 4.34 0 9.688 0h445.625C460.64 0 465 3.712 465 8.25v313.5c0 4.538-4.36 8.25-9.688 8.25zM9.432 318.939h444.25V11.06H9.432V318.94z"/><path id="Shape" d="M19.674 46C14.334 46 10 44.875 10 43.5s4.334-2.5 9.674-2.5h425.652c5.32 0 9.674 1.125 9.674 2.5s-4.353 2.5-9.674 2.5H19.674zM31 27.5c0 3.039-2.461 5.5-5.5 5.5a5.498 5.498 0 01-5.5-5.5c0-3.039 2.461-5.5 5.5-5.5s5.5 2.461 5.5 5.5zM59 27.5c0 3.039-2.461 5.5-5.5 5.5a5.498 5.498 0 01-5.5-5.5c0-3.039 2.461-5.5 5.5-5.5s5.5 2.461 5.5 5.5zM87 27.5c0 3.039-2.461 5.5-5.5 5.5a5.498 5.498 0 01-5.5-5.5c0-3.039 2.461-5.5 5.5-5.5s5.5 2.461 5.5 5.5z"/></g><text id="bottom" fill="#7E7C7B" font-family="PTMono-Bold, PT Mono" font-size="14" font-weight="bold"><tspan x="414" y="196">bottom</tspan></text><path id="Line-30-Copy-2" stroke="#C06334" stroke-dasharray="3,6" stroke-linecap="square" stroke-width="2" d="M392.5 290v37"/><path id="Line-29-Copy" stroke="#C06334" stroke-dasharray="3,6" stroke-linecap="square" stroke-width="2" d="M394 288h69"/><text id="(x,y)" fill="#C06334" font-family="PTMono-Bold, PT Mono" font-size="14" font-weight="bold"><tspan x="395" y="305">(x,y)</tspan></text><text id="(x,y)-copy" fill="#C06334" font-family="PTMono-Bold, PT Mono" font-size="14" font-weight="bold"><tspan x="395" y="305">(x,y)</tspan></text><text id="left" fill="#7E7C7B" font-family="PTMono-Bold, PT Mono" font-size="14" font-weight="bold"><tspan x="50" y="129">left</tspan></text><path id="Line-Copy" fill="#7E7C7B" fill-rule="nonzero" d="M410 56.5v213h8l-9.5 19-9.5-19h8v-213h3z"/><text id="right" fill="#7E7C7B" font-family="PTMono-Bold, PT Mono" font-size="14" font-weight="bold"><tspan x="188" y="323">right</tspan></text><path id="Line-3-Copy-2" fill="#7E7C7B" fill-rule="nonzero" d="M371.498 295.5l19 9.5-19 9.5-.001-8H21.502v-3h349.995v-8z"/><path id="Rectangle-1" fill="#DBAF88" d="M393 113v175H118V113h275zm-10.643 10H129.015v155h253.342V123z"/><text id="Introduction-This-Ec" fill="#643B0C" font-family="OpenSans-Bold, Open Sans" font-size="16" font-weight="bold" opacity=".7"><tspan x="139" y="148">Introduction</tspan> <tspan x="139" y="176" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">This Ecma Standard is based on </tspan> <tspan x="139" y="195" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">several originating technologies, </tspan> <tspan x="139" y="214" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">the most well known being </tspan> <tspan x="139" y="233" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">JavaScript (Netscape) and JScript </tspan> <tspan x="139" y="252" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">(Microsoft). The language was </tspan> <tspan x="139" y="271" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">invented by Brendan Eich at </tspan></text><text id="top" fill="#7E7C7B" font-family="PTMono-Bold, PT Mono" font-size="14" font-weight="bold"><tspan x="125" y="90">top</tspan></text><path id="Line-3" fill="#7E7C7B" fill-rule="nonzero" d="M100.548 104.403l18.95 9.6-19.05 9.4.041-8.001-76.5-.402-1.5-.008.017-3 1.5.008 76.499.402.043-8z"/><path id="Line-2" fill="#C06334" fill-rule="nonzero" d="M118.076 113.232l21.132 2.162-4.288 6.754 255.882 162.436 1.266.804-1.608 2.532-1.266-.804-255.882-162.435-4.287 6.754-10.95-18.203z"/><path id="Line-3-Copy" fill="#7E7C7B" fill-rule="nonzero" d="M118.998 56.5V58L119 94.499h8l-9.498 19.001-9.501-19H116l-.002-36.5v-1.5h3z"/><g id="Group" transform="rotate(32.5 -148.357 454.177)"><path id="Rectangle" fill="#FFF" d="M0 0h133v22H0z" filter="url(#filter-1)"/><text id="(width,height)" fill="#C06334" font-family="PTMono-Bold, PT Mono" font-size="14" font-weight="bold"><tspan x="8.682" y="15.378">(width,height)</tspan></text></g></g></g></svg> \ No newline at end of file diff --git a/2-ui/1-document/11-coordinates/coordinates.svg b/2-ui/1-document/11-coordinates/coordinates.svg new file mode 100644 index 0000000000..261ff66966 --- /dev/null +++ b/2-ui/1-document/11-coordinates/coordinates.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="521" height="411" viewBox="0 0 521 411"><defs><style>@import url(https://fonts.googleapis.com/css?family=Open+Sans:bold,italic,bolditalic%7CPT+Mono);@font-face{font-family:'PT Mono';font-weight:700;font-style:normal;src:local('PT MonoBold'),url(/font/PTMonoBold.woff2) format('woff2'),url(/font/PTMonoBold.woff) format('woff'),url(/font/PTMonoBold.ttf) format('truetype')}</style></defs><g id="dom" fill="none" fill-rule="evenodd" stroke="none" stroke-width="1"><g id="coordinates.svg"><g id="noun_69008_cc" fill="#DBAF88" transform="translate(13 11)"><path id="Shape" d="M490.563 386H10.438C4.676 386 0 381.658 0 376.35V9.65C0 4.342 4.676 0 10.438 0h480.125C496.303 0 501 4.343 501 9.65v366.7c0 5.308-4.697 9.65-10.438 9.65zm-480.4-12.939h478.642V12.94H10.162V373.06z"/><path id="Shape" d="M20.859 54.1c-5.753 0-10.422-1.147-10.422-2.55 0-1.402 4.67-2.55 10.422-2.55H479.4c5.732 0 10.422 1.148 10.422 2.55 0 1.403-4.69 2.55-10.422 2.55H20.86zM33.8 31.627a6.024 6.024 0 11-12.05 0 6.024 6.024 0 1112.05 0zM63.988 31.627a6.024 6.024 0 11-12.05 0 6.024 6.024 0 1112.05 0zM94.177 31.627a6.024 6.024 0 11-12.05 0 6.024 6.024 0 1112.05 0z"/></g><text id="height" fill="#C06334" font-family="PTMono-Bold, PT Mono" font-size="14" font-weight="bold"><tspan x="417" y="208">height</tspan></text><text id="bottom" fill="#7E7C7B" font-family="PTMono-Bold, PT Mono" font-size="14" font-weight="bold"><tspan x="451" y="311">bottom</tspan></text><path id="Line-28" stroke="#C06334" stroke-dasharray="3,6" stroke-linecap="square" stroke-width="2" d="M395 293h103"/><path id="Line-30" stroke="#C06334" stroke-dasharray="3,6" stroke-linecap="square" stroke-width="2" d="M392 296v74.5"/><path id="Line-30-Copy" stroke="#C06334" stroke-dasharray="3,6" stroke-linecap="square" stroke-width="2" d="M119 296v46"/><path id="Line-29" stroke="#C06334" stroke-dasharray="3,6" stroke-linecap="square" stroke-width="2" d="M396 120h30"/><text id="x" fill="#C06334" font-family="PTMono-Bold, PT Mono" font-size="14" font-weight="bold"><tspan x="62" y="113">x</tspan></text><text id="left" fill="#7E7C7B" font-family="PTMono-Bold, PT Mono" font-size="14" font-weight="bold"><tspan x="50" y="135">left</tspan></text><text id="y" fill="#C06334" font-family="PTMono-Bold, PT Mono" font-size="14" font-weight="bold"><tspan x="125" y="79">y</tspan></text><text id="width" fill="#C06334" font-family="PTMono-Bold, PT Mono" font-size="14" font-weight="bold"><tspan x="234" y="339">width</tspan></text><path id="Line" fill="#C06334" fill-rule="nonzero" d="M414 118.5V273h8l-9.5 19-9.5-19h8V118.5h3z"/><path id="Line-Copy" fill="#7E7C7B" fill-rule="nonzero" d="M476 65v208h8l-9.5 19-9.5-19h8V65h3z"/><path id="Line-2" fill="#C06334" fill-rule="nonzero" d="M371 309l19 9.5-19 9.5-.001-8H116v-3h254.999l.001-8z"/><path id="Line-3" fill="#C06334" fill-rule="nonzero" d="M100.05 109.903l18.95 9.6-19.05 9.4.042-8.001-76.5-.402-1.5-.008.016-3 1.5.008 76.5.402.042-8z"/><text id="right" fill="#7E7C7B" font-family="PTMono-Bold, PT Mono" font-size="14" font-weight="bold"><tspan x="180" y="375">right</tspan></text><path id="Line-3-Copy-2" fill="#7E7C7B" fill-rule="nonzero" d="M371 350l19 9.5-19 9.5v-8H21.005v-3H371v-8z"/><path id="Line-3-Copy" fill="#C06334" fill-rule="nonzero" d="M119 65v1.5l.003 34.249h8l-9.498 19.001-9.502-19h8L116 66.5V65h3z" transform="matrix(-1 0 0 1 235.005 0)"/><path id="Rectangle-1" fill="#DBAF88" d="M392.629 119v175H118V119h274.629zM382 129H129v155h253V129z"/><text id="Introduction-This-Ec" fill="#643B0C" font-family="OpenSans-Bold, Open Sans" font-size="16" font-weight="bold" opacity=".8"><tspan x="138.946" y="153.8">Introduction</tspan> <tspan x="138.946" y="181.8" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">This Ecma Standard is based on </tspan> <tspan x="138.946" y="200.8" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">several originating technologies, </tspan> <tspan x="138.946" y="219.8" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">the most well known being </tspan> <tspan x="138.946" y="238.8" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">JavaScript (Netscape) and JScript </tspan> <tspan x="138.946" y="257.8" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">(Microsoft). The language was </tspan> <tspan x="138.946" y="276.8" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">invented by Brendan Eich at </tspan></text><text id="top" fill="#7E7C7B" font-family="PTMono-Bold, PT Mono" font-size="14" font-weight="bold"><tspan x="125" y="96">top</tspan></text></g></g></svg> \ No newline at end of file diff --git a/2-ui/1-document/11-coordinates/coords.png b/2-ui/1-document/11-coordinates/coords.png deleted file mode 100644 index 79d1fdbb65..0000000000 Binary files a/2-ui/1-document/11-coordinates/coords.png and /dev/null differ diff --git a/2-ui/1-document/11-coordinates/coords@2x.png b/2-ui/1-document/11-coordinates/coords@2x.png deleted file mode 100644 index d3e340f50b..0000000000 Binary files a/2-ui/1-document/11-coordinates/coords@2x.png and /dev/null differ diff --git a/2-ui/1-document/11-coordinates/document-and-window-coordinates-scrolled.svg b/2-ui/1-document/11-coordinates/document-and-window-coordinates-scrolled.svg new file mode 100644 index 0000000000..f03317f0c3 --- /dev/null +++ b/2-ui/1-document/11-coordinates/document-and-window-coordinates-scrolled.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="728" height="358" viewBox="0 0 728 358"><defs><style>@import url(https://fonts.googleapis.com/css?family=Open+Sans:bold,italic,bolditalic%7CPT+Mono);@font-face{font-family:'PT Mono';font-weight:700;font-style:normal;src:local('PT MonoBold'),url(/font/PTMonoBold.woff2) format('woff2'),url(/font/PTMonoBold.woff) format('woff'),url(/font/PTMonoBold.ttf) format('truetype')}</style></defs><g id="dom" fill="none" fill-rule="evenodd" stroke="none" stroke-width="1"><g id="document-and-window-coordinates-scrolled.svg"><path fill="#FFF" d="M0 0h728v358H0z"/><text id="Introduction-This-Ec" fill="#AF6E24" font-family="OpenSans-Bold, Open Sans" font-size="16" font-weight="bold" opacity=".5"><tspan x="34" y="176">Introduction</tspan> <tspan x="34" y="204" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">This Ecma Standard is based on several </tspan> <tspan x="34" y="223" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">originating technologies, the most well </tspan> <tspan x="34" y="242" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">known being JavaScript (Netscape) and </tspan> <tspan x="34" y="261" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">JScript (Microsoft). The language was </tspan> <tspan x="34" y="280" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">invented by Brendan Eich at Netscape and </tspan> <tspan x="34" y="299" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">first appeared in that company’s Navigator </tspan> <tspan x="34" y="318" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">2.0 browser. </tspan> <tspan x="34" y="337" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal"> </tspan> <tspan x="34" y="356" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">It has appeared in all subsequent browsers </tspan> <tspan x="34" y="375" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">from Netscape and in all browsers from </tspan> <tspan x="34" y="394" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">Microsoft starting with Internet Explorer </tspan> <tspan x="34" y="413" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">3.0. The development of this Standard </tspan> <tspan x="34" y="432" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">started in November 1996. </tspan> <tspan x="34" y="451" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">The first edition of this Ecma Standard was </tspan> <tspan x="34" y="470" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">adopted by the Ecma General Assembly of </tspan> <tspan x="34" y="489" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">June 1997.</tspan> <tspan x="34" y="508" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">That Ecma Standard was submitted to ISO/</tspan> <tspan x="34" y="527" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">IEC JTC 1 for adoption under the fast-track </tspan> <tspan x="34" y="546" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">procedure, and approved as international </tspan> <tspan x="34" y="565" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">standard ISO/IEC 16262, in April 1998. The </tspan> <tspan x="34" y="584" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">Ecma General Assembly of June 1998 </tspan> <tspan x="34" y="603" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">approved the second edition of ECMA-262 </tspan> <tspan x="34" y="622" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">to keep it fully aligned with ISO/IEC 16262. </tspan> <tspan x="34" y="641" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">Changes between the first and the second </tspan> <tspan x="34" y="660" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">edition are editorial in nature.</tspan></text><text id="😍" fill="#643B0C" font-family="AppleColorEmoji, Apple Color Emoji" font-size="18" font-weight="normal"><tspan x="139" y="330">😍</tspan></text><path id="Line-39" fill="#C06334" fill-rule="nonzero" d="M150.5 131v167h6l-7 14-7-14h6V131h2z"/><text id="pageY" fill="#C06334" font-family="PTMono-Bold, PT Mono" font-size="16" font-weight="bold"><tspan x="157" y="155">pageY</tspan></text><text id="clientY" fill="#C06334" font-family="PTMono-Bold, PT Mono" font-size="16" font-weight="bold"><tspan x="156.4" y="177">clientY</tspan></text><path id="Line-39-Copy" fill="#C06334" fill-rule="nonzero" d="M126 315.5l14 7-14 7-.001-6.001L13 323.5h-1v-2h1l112.999-.001.001-5.999z"/><text id="pageX" fill="#C06334" font-family="PTMono-Bold, PT Mono" font-size="16" font-weight="bold"><tspan x="23" y="316">pageX</tspan></text><text id="clientX" fill="#C06334" font-family="PTMono-Bold, PT Mono" font-size="16" font-weight="bold"><tspan x="22.4" y="337">clientX</tspan></text><g id="np_browser_551045_E8C48F" fill="#DBAF88" fill-rule="nonzero" transform="translate(-1 68)"><path id="Shape" d="M22.8 0C7.76 0 0 8.157 0 24v242c0 15.843 7.759 24 22.8 24h309.346c15.042 0 21.854-7.157 21.854-23V24c0-15.843-6.812-24-21.854-24H22.8zm-5.425 15.286h318.177c2.535 0 4.187 2.012 4.187 5.1L338 55.328H13.188V20.386c0-3.088 1.652-5.1 4.187-5.1zm21.159 13.857c-3.902 0-7.067 3.327-7.067 7.428S34.632 44 38.534 44s7.067-3.328 7.067-7.429c0-4.1-3.165-7.428-7.067-7.428zm31.467 0c-3.902 0-7.068 3.327-7.068 7.428S66.1 44 70.001 44c3.901 0 7.067-3.328 7.067-7.429 0-4.1-3.166-7.428-7.067-7.428zm31.466 0c-3.901 0-7.067 3.327-7.067 7.428S97.566 44 101.467 44c3.902 0 7.068-3.328 7.068-7.429 0-4.1-3.166-7.428-7.068-7.428zM14 71.172h324l1.865 198.376c0 2.782-1.651 4.595-4.186 4.595H17.5c-2.535 0-4.187-1.813-4.187-4.595L14 71.172z"/></g><text id="Introduction-This-Ec" fill="#AF6E24" font-family="OpenSans-Bold, Open Sans" font-size="16" font-weight="bold" opacity=".5"><tspan x="409" y="34">Introduction</tspan> <tspan x="409" y="62" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">This Ecma Standard is based on several </tspan> <tspan x="409" y="81" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">originating technologies, the most well </tspan> <tspan x="409" y="100" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">known being JavaScript (Netscape) and </tspan> <tspan x="409" y="119" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">JScript (Microsoft). The language was </tspan> <tspan x="409" y="138" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">invented by Brendan Eich at Netscape and </tspan> <tspan x="409" y="157" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">first appeared in that company’s Navigator </tspan> <tspan x="409" y="176" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">2.0 browser. </tspan> <tspan x="409" y="195" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal"> </tspan> <tspan x="409" y="214" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">It has appeared in all subsequent browsers </tspan> <tspan x="409" y="233" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">from Netscape and in all browsers from </tspan> <tspan x="409" y="252" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">Microsoft starting with Internet Explorer </tspan> <tspan x="409" y="271" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">3.0. The development of this Standard </tspan> <tspan x="409" y="290" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">started in November 1996. </tspan> <tspan x="409" y="309" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">The first edition of this Ecma Standard was </tspan> <tspan x="409" y="328" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">adopted by the Ecma General Assembly of </tspan> <tspan x="409" y="347" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">June 1997.</tspan> <tspan x="409" y="366" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">That Ecma Standard was submitted to ISO/</tspan> <tspan x="409" y="385" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">IEC JTC 1 for adoption under the fast-track </tspan> <tspan x="409" y="404" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">procedure, and approved as international </tspan> <tspan x="409" y="423" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">standard ISO/IEC 16262, in April 1998. The </tspan> <tspan x="409" y="442" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">Ecma General Assembly of June 1998 </tspan> <tspan x="409" y="461" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">approved the second edition of ECMA-262 </tspan> <tspan x="409" y="480" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">to keep it fully aligned with ISO/IEC 16262. </tspan> <tspan x="409" y="499" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">Changes between the first and the second </tspan> <tspan x="409" y="518" font-family="OpenSans-Regular, Open Sans" font-size="14" font-weight="normal">edition are editorial in nature.</tspan></text><path id="Rectangle" fill="#FFF" d="M392 87h307v42H392z" opacity=".6"/><g id="np_browser_551045_E8C48F" fill="#DBAF88" fill-rule="nonzero" transform="translate(374 71)"><path id="Shape" d="M22.8 0C7.76 0 0 8.157 0 24v242c0 15.843 7.759 24 22.8 24h309.346c15.042 0 21.854-7.157 21.854-23V24c0-15.843-6.812-24-21.854-24H22.8zm-5.425 15.286h318.177c2.535 0 4.187 2.012 4.187 5.1L338 55.328H13.188V20.386c0-3.088 1.652-5.1 4.187-5.1zm21.159 13.857c-3.902 0-7.067 3.327-7.067 7.428S34.632 44 38.534 44s7.067-3.328 7.067-7.429c0-4.1-3.165-7.428-7.067-7.428zm31.467 0c-3.902 0-7.068 3.327-7.068 7.428S66.1 44 70.001 44c3.901 0 7.067-3.328 7.067-7.429 0-4.1-3.166-7.428-7.067-7.428zm31.466 0c-3.901 0-7.067 3.327-7.067 7.428S97.566 44 101.467 44c3.902 0 7.068-3.328 7.068-7.429 0-4.1-3.166-7.428-7.068-7.428zM14 71.172h324l1.865 198.376c0 2.782-1.651 4.595-4.186 4.595H17.5c-2.535 0-4.187-1.813-4.187-4.595L14 71.172z"/></g><path id="Rectangle-2" stroke="#DBAF88" stroke-width="2" d="M387 7h326v351H387z"/><path id="Rectangle-14" fill="#FFF" d="M395 8h312v61H395z"/><path id="Line-39" fill="#C06334" fill-rule="nonzero" d="M519.5 7v159h6l-7 14-7-14h6V7h2z"/><path id="Line-39-Copy-2" fill="#C06334" fill-rule="nonzero" d="M531.014 141.987l2 .027-.014 1-.311 23 6 .082L531.5 180l-6.81-14.093 5.999.08.311-23 .014-1z"/><text id="pageY" fill="#C06334" font-family="PTMono-Bold, PT Mono" font-size="16" font-weight="bold"><tspan x="525" y="23">pageY</tspan></text><text id="clientY" fill="#C06334" font-family="PTMono-Bold, PT Mono" font-size="16" font-weight="bold"><tspan x="542.4" y="172">clientY</tspan></text><path id="Line-39-Copy" fill="#C06334" fill-rule="nonzero" d="M501 182.5l14 7-14 7-.001-6.001L389 190.5h-1v-2h1l111.999-.001.001-5.999z"/><text id="pageX" fill="#C06334" font-family="PTMono-Bold, PT Mono" font-size="16" font-weight="bold"><tspan x="398" y="183">pageX</tspan></text><text id="clientX" fill="#C06334" font-family="PTMono-Bold, PT Mono" font-size="16" font-weight="bold"><tspan x="397.4" y="204">clientX</tspan></text><text id="😍" fill="#643B0C" font-family="AppleColorEmoji, Apple Color Emoji" font-size="18" font-weight="normal"><tspan x="514" y="197">😍</tspan></text></g></g></svg> \ No newline at end of file diff --git a/2-ui/1-document/11-coordinates/document-window-coordinates-scroll.png b/2-ui/1-document/11-coordinates/document-window-coordinates-scroll.png deleted file mode 100644 index 29c59aad1c..0000000000 Binary files a/2-ui/1-document/11-coordinates/document-window-coordinates-scroll.png and /dev/null differ diff --git a/2-ui/1-document/11-coordinates/document-window-coordinates-scroll@2x.png b/2-ui/1-document/11-coordinates/document-window-coordinates-scroll@2x.png deleted file mode 100644 index 3c188ee455..0000000000 Binary files a/2-ui/1-document/11-coordinates/document-window-coordinates-scroll@2x.png and /dev/null differ diff --git a/2-ui/1-document/11-coordinates/document-window-coordinates-zero.png b/2-ui/1-document/11-coordinates/document-window-coordinates-zero.png deleted file mode 100644 index 3dc17f0256..0000000000 Binary files a/2-ui/1-document/11-coordinates/document-window-coordinates-zero.png and /dev/null differ diff --git a/2-ui/1-document/11-coordinates/document-window-coordinates-zero@2x.png b/2-ui/1-document/11-coordinates/document-window-coordinates-zero@2x.png deleted file mode 100644 index 45f6b9e7fd..0000000000 Binary files a/2-ui/1-document/11-coordinates/document-window-coordinates-zero@2x.png and /dev/null differ diff --git a/2-ui/1-document/11-coordinates/head.html b/2-ui/1-document/11-coordinates/head.html index d5b366a9db..b6947df6a3 100644 --- a/2-ui/1-document/11-coordinates/head.html +++ b/2-ui/1-document/11-coordinates/head.html @@ -1,30 +1,29 @@ <script> document.addEventListener('DOMContentLoaded', function() { -let elem = document.getElementById('coords-show-mark'); + let elem = document.getElementById('coords-show-mark'); -// no elem in ebook mode -if (elem) { - elem.onclick = function() { + if (elem) { + elem.onclick = function() { - function createMessageUnder(elem, text) { - let coords = elem.getBoundingClientRect(); - let message = document.createElement('div'); - message.style.cssText = "position:fixed; color: red"; + function createMessageUnder(elem, text) { + let coords = elem.getBoundingClientRect(); + let message = document.createElement('div'); + message.style.cssText = "position:fixed; color: red"; - message.style.left = coords.left + "px"; - message.style.top = coords.bottom + "px"; + message.style.left = coords.left + "px"; + message.style.top = coords.bottom + "px"; - message.innerHTML = text; + message.innerHTML = text; - return message; - } + return message; + } - let message = createMessageUnder(elem, 'Hello, world!'); - document.body.append(message); - setTimeout(() => message.remove(), 5000); + let message = createMessageUnder(elem, 'Hello, world!'); + document.body.append(message); + setTimeout(() => message.remove(), 5000); + } } -} }); diff --git a/2-ui/1-document/index.md b/2-ui/1-document/index.md index 1b60cdf2cf..101591112f 100644 --- a/2-ui/1-document/index.md +++ b/2-ui/1-document/index.md @@ -1,3 +1,3 @@ -# Document +# Документ -Here we'll learn to manipulate a web-page using JavaScript. +Здесь мы научимся изменять страницу при помощи JavaScript. diff --git a/2-ui/2-events/01-introduction-browser-events/01-hide-other/solution.view/index.html b/2-ui/2-events/01-introduction-browser-events/01-hide-other/solution.view/index.html index 7228a45de3..96ddb5fb51 100644 --- a/2-ui/2-events/01-introduction-browser-events/01-hide-other/solution.view/index.html +++ b/2-ui/2-events/01-introduction-browser-events/01-hide-other/solution.view/index.html @@ -7,13 +7,13 @@ <body> - <input type="button" id="hider" value="Click to hide the text" /> + <input type="button" id="hider" value="Нажмите, чтобы спрятать текст" /> - <div id="text">Text</div> + <div id="text">Текст</div> <script> - // Here it doesn't matter how we hide the text, - // could also use style.display: + // Здесь не важно, как мы скрываем текст. + // Также можно использовать style.display: document.getElementById('hider').onclick = function() { document.getElementById('text').hidden = true; } diff --git a/2-ui/2-events/01-introduction-browser-events/01-hide-other/source.view/index.html b/2-ui/2-events/01-introduction-browser-events/01-hide-other/source.view/index.html index 17c5be6d13..3bdccb760d 100644 --- a/2-ui/2-events/01-introduction-browser-events/01-hide-other/source.view/index.html +++ b/2-ui/2-events/01-introduction-browser-events/01-hide-other/source.view/index.html @@ -7,12 +7,12 @@ <body> - <input type="button" id="hider" value="Click to hide the text" /> + <input type="button" id="hider" value="Нажмите, чтобы спрятать текст" /> - <div id="text">Text</div> + <div id="text">Текст</div> <script> - /* your code */ + /* ваш код */ </script> </body> diff --git a/2-ui/2-events/01-introduction-browser-events/01-hide-other/task.md b/2-ui/2-events/01-introduction-browser-events/01-hide-other/task.md index 7cb0cb0c99..ad80788862 100644 --- a/2-ui/2-events/01-introduction-browser-events/01-hide-other/task.md +++ b/2-ui/2-events/01-introduction-browser-events/01-hide-other/task.md @@ -2,10 +2,10 @@ importance: 5 --- -# Hide on click +# Скрыть элемент по нажатию кнопки -Add JavaScript to the `button` to make `<div id="text">` disappear when we click it. +Добавьте JavaScript к кнопке `button`, чтобы при нажатии элемент `<div id="text">` исчезал. -The demo: +Демо: [iframe border=1 src="solution" height=80] diff --git a/2-ui/2-events/01-introduction-browser-events/02-hide-self-onclick/solution.md b/2-ui/2-events/01-introduction-browser-events/02-hide-self-onclick/solution.md index ec28066963..73b3826a8a 100644 --- a/2-ui/2-events/01-introduction-browser-events/02-hide-self-onclick/solution.md +++ b/2-ui/2-events/01-introduction-browser-events/02-hide-self-onclick/solution.md @@ -1,5 +1,5 @@ -Can use `this` in the handler to reference "itself" here: +Можем использовать `this` в обработчике для доступа к самому элементу: ```html run height=50 -<input type="button" onclick="this.hidden=true" value="Click to hide"> +<input type="button" onclick="this.hidden=true" value="Нажми, чтобы спрятать"> ``` diff --git a/2-ui/2-events/01-introduction-browser-events/02-hide-self-onclick/task.md b/2-ui/2-events/01-introduction-browser-events/02-hide-self-onclick/task.md index 9ee8f18e14..5f89d9fac4 100644 --- a/2-ui/2-events/01-introduction-browser-events/02-hide-self-onclick/task.md +++ b/2-ui/2-events/01-introduction-browser-events/02-hide-self-onclick/task.md @@ -2,11 +2,11 @@ importance: 5 --- -# Hide self +# Спрятать себя -Create a button that hides itself on click. +Создайте кнопку, которая будет скрывать себя по нажатию. ```online -Like this: -<input type="button" onclick="this.hidden=true" value="Click to hide"> +Например: +<input type="button" onclick="this.hidden=true" value="Нажмите, чтобы спрятать"> ``` diff --git a/2-ui/2-events/01-introduction-browser-events/03-which-handlers-run/solution.md b/2-ui/2-events/01-introduction-browser-events/03-which-handlers-run/solution.md index d569f0e4d3..a1ab056a63 100644 --- a/2-ui/2-events/01-introduction-browser-events/03-which-handlers-run/solution.md +++ b/2-ui/2-events/01-introduction-browser-events/03-which-handlers-run/solution.md @@ -1,8 +1,8 @@ -The answer: `1` and `2`. +Ответ: `1` и `2`. -The first handler triggers, because it's not removed by `removeEventListener`. To remove the handler we need to pass exactly the function that was assigned. And in the code a new function is passed, that looks the same, but is still another function. +Первый обработчик сработает, потому что он не был удалён методом `removeEventListener`. Чтобы удалить обработчик, необходимо передать именно ту функцию, которая была назначена в качестве обработчика. Несмотря на то, что код идентичен, в `removeEventListener` передаётся новая, другая функция. -To remove a function object, we need to store a reference to it, like this: +Для того чтобы удалить функцию-обработчик, нужно где-то сохранить ссылку на неё, например: ```js function handler() { @@ -13,4 +13,4 @@ button.addEventListener("click", handler); button.removeEventListener("click", handler); ``` -The handler `button.onclick` works independently and in addition to `addEventListener`. +Обработчик `button.onclick` сработает независимо от `addEventListener`. diff --git a/2-ui/2-events/01-introduction-browser-events/03-which-handlers-run/task.md b/2-ui/2-events/01-introduction-browser-events/03-which-handlers-run/task.md index f8cd75d5a7..9eab541554 100644 --- a/2-ui/2-events/01-introduction-browser-events/03-which-handlers-run/task.md +++ b/2-ui/2-events/01-introduction-browser-events/03-which-handlers-run/task.md @@ -2,11 +2,11 @@ importance: 5 --- -# Which handlers run? +# Какой обработчик запустится? -There's a button in the variable. There are no handlers on it. +В переменной `button` находится кнопка. Изначально на ней нет обработчиков. -Which handlers run on click after the following code? Which alerts show up? +Который из обработчиков запустится? Что будет выведено при клике после выполнения кода? ```js no-beautify button.addEventListener("click", () => alert("1")); diff --git a/2-ui/2-events/01-introduction-browser-events/04-move-ball-field/move-ball-coords.png b/2-ui/2-events/01-introduction-browser-events/04-move-ball-field/move-ball-coords.png deleted file mode 100644 index 4a0549582e..0000000000 Binary files a/2-ui/2-events/01-introduction-browser-events/04-move-ball-field/move-ball-coords.png and /dev/null differ diff --git a/2-ui/2-events/01-introduction-browser-events/04-move-ball-field/move-ball-coords.svg b/2-ui/2-events/01-introduction-browser-events/04-move-ball-field/move-ball-coords.svg new file mode 100644 index 0000000000..fc26b023c5 --- /dev/null +++ b/2-ui/2-events/01-introduction-browser-events/04-move-ball-field/move-ball-coords.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="521" height="330" viewBox="0 0 521 330"><defs><style>@import url(https://fonts.googleapis.com/css?family=Open+Sans:bold,italic,bolditalic%7CPT+Mono);@font-face{font-family:'PT Mono';font-weight:700;font-style:normal;src:local('PT MonoBold'),url(/font/PTMonoBold.woff2) format('woff2'),url(/font/PTMonoBold.woff) format('woff'),url(/font/PTMonoBold.ttf) format('truetype')}</style></defs><defs><filter id="filter-2" width="195.6%" height="153.8%" x="-42.9%" y="-25.8%" filterUnits="objectBoundingBox"><feMorphology in="SourceAlpha" operator="dilate" radius="1" result="shadowSpreadOuter1"/><feOffset dy="1" in="shadowSpreadOuter1" result="shadowOffsetOuter1"/><feGaussianBlur in="shadowOffsetOuter1" result="shadowBlurOuter1" stdDeviation="1.5"/><feComposite in="shadowBlurOuter1" in2="SourceAlpha" operator="out" result="shadowBlurOuter1"/><feColorMatrix in="shadowBlurOuter1" values="0 0 0 0 0.0941176471 0 0 0 0 0.0901960784 0 0 0 0 0.0901960784 0 0 0 1 0"/></filter><path id="path-1" d="M263 170v21.429l5.25-5.358 4.375 8.929h1.75s1.13-1.161.875-1.786c-1.203-2.947-4.375-8.928-4.375-8.928H277L263 170z"/></defs><g id="dom" fill="none" fill-rule="evenodd" stroke="none" stroke-width="1"><g id="move-ball-coords.svg"><g id="noun_69008_cc" fill="#DBAF88" transform="translate(12 6)"><path id="Shape" d="M10.438 316C4.676 316 0 312.445 0 308.1V7.9C0 3.555 4.676 0 10.438 0h480.125C496.303 0 501 3.555 501 7.9v300.2c0 4.345-4.697 7.9-10.438 7.9H10.438zm-.276-12h478.643V12.939H10.162V304z"/><path id="Shape" d="M20.859 54.1c-5.753 0-10.422-1.147-10.422-2.55 0-1.402 4.67-2.55 10.422-2.55H479.4c5.732 0 10.422 1.148 10.422 2.55 0 1.403-4.69 2.55-10.422 2.55H20.86zM33.75 31.6c0 3.315-2.685 6-6 6s-6-2.685-6-6 2.685-6 6-6 6 2.685 6 6zM63.938 31.6c0 3.315-2.685 6-6 6s-6-2.685-6-6 2.685-6 6-6 6 2.685 6 6zM94.127 31.6c0 3.315-2.685 6-6 6s-6-2.685-6-6 2.685-6 6-6 6 2.685 6 6z"/></g><image id="Screen-Shot-2017-02-25-at-23.45.22" width="224" height="150" x="183" y="100" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAcAAAAEsCAYAAABUo2OKAAAABGdBTUEAALGOfPtRkwAAAERlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAABwKADAAQAAAABAAABLAAAAACEEXZQAAAS70lEQVR4Ae3cUU7rTBIGUDxiNzywIdbCKtgFe2ERILEB3uDB08woqLuRYsdxmfL1CbKwk3SlfartLz93NMNYHjceBAgQIEDgYAL/Odj5Ol0CBAgQIPA/AQFoIRAgQIDAIQUE4CHb7qQJECBA4LYnGIahf8oxAQIECBDYvUD/P3nxX4C7b6kTIECAAIElAgJwiZoxBAgQILB7AQG4+xY6AQIECBBYIiAAl6gZQ4AAAQK7FxCAu2+hEyBAgACBJQICcImaMQQIECCwewEBuPsWOgECBAgQWCIgAJeoGUOAAAECuxcQgLtvoRMgQIAAgSUCAnCJmjEECBAgsHsBAbj7FjoBAgQIEFgiIACXqBlDgAABArsXEIC7b6ETIECAAIElAgJwiZoxBAgQILB7AQG4+xY6AQIECBBYIiAAl6gZQ4AAAQK7FxCAu2+hEyBAgACBJQICcImaMQQIECCwewEBuPsWOgECBAgQWCJwu2TQRWPGi97tzQQIECBA4P8CQyyE/wKM9VWdAAECBJIKCMCkjTEtAgQIEIgVEICxvqoTIECAQFIBAZi0MaZFgAABArECAjDWV3UCBAgQSCogAJM2xrQIECBAIFZAAMb6qk6AAAECSQUEYNLGmBYBAgQIxAoIwFhf1QkQIEAgqYAATNoY0yJAgACBWAEBGOurOgECBAgkFRCASRtjWgQIECAQKyAAY31VJ0CAAIGkAgIwaWNMiwABAgRiBQRgrK/qBAgQIJBUQAAmbYxpESBAgECsgACM9VWdAAECBJIKCMCkjTEtAgQIEIgVEICxvqoTIECAQFIBAZi0MaZFgAABArECAjDWV3UCBAgQSCogAJM2xrQIECBAIFZAAMb6qk6AAAECSQVuN53XV/m0p7K9l+2hbPdlu+RhPD/rx/Xj/nHM++clWTH3vWP3KOPGVbex1Dv9PHa1P39eOb3j/G/j297wO79e+letH+unvr+5fvor5PzxX1w/db9W2O/ibtz2T6AvXSy/dcdTh8a3Qvxaj6kj66cVsn5aj6kj66cV2nr9tJ++zlGfiKVq+y3x2uP6O8VzV7t+bc6+8W1v5pjV7+HHr76e67UxZ9/6sX62Xj/1562w3+fd8P1EHaXDMNSH1+831Uu517J9lO1uYWnj+Vk/rh/3j2U30L3dP9eOozbubrYPwGVtM4oAAQIEjiYQHIDb/hvg0ZrnfAkQIEAgrYAATNsaEyNAgACBSAEBGKmrNgECBAikFRCAaVtjYgQIECAQKSAAI3XVJkCAAIG0AgIwbWtMjAABAgQiBQRgpK7aBAgQIJBWQACmbY2JESBAgECkgACM1FWbAAECBNIKCMC0rTExAgQIEIgUEICRumoTIECAQFoBAZi2NSZGgAABApECAjBSV20CBAgQSCsgANO2xsQIECBAIFJAAEbqqk2AAAECaQUEYNrWmBgBAgQIRAoIwEhdtQkQIEAgrYAATNsaEyNAgACBSAEBGKmrNgECBAikFRCAaVtjYgQIECAQKSAAI3XVJkCAAIG0AgIwbWtMjAABAgQiBQRgpK7aBAgQIJBWQACmbY2JESBAgECkgACM1FWbAAECBNIKCMC0rTExAgQIEIgUEICRumoTIECAQFoBAZi2NSZGgAABApECAjBSV20CBAgQSCsgANO2xsQIECBAIFLgNrL4r9pf5Zmnsr2X7aFs92W75GE8P+vH9eP+ccz75yVZMfe9Y/co48ZVt7HUO/08drU/f145veP8b+Pb3vA7v176V60f66e+v7l++ivk/PFfXD91v1bY7+Ju3PZPoC9dLL91x1OHxrdC/FqPqSPrpxWyflqPqSPrpxXaev20n77OUZ+IpWr7LfHa4/o7xXNXu35tzr7xbW/mmNXv4cevvp7rtTFn3/qxfrZeP/XnrbDf593w/UQdpcMw1IfX7zfVS7nXsn2U7W5haeP5WT+uH/ePZTfQvd0/146jNu5utg/AZW0zigABAgSOJhAcgNv+G+DRmud8CRAgQCCtgABM2xoTI0CAAIFIAQEYqas2AQIECKQVEIBpW2NiBAgQIBApIAAjddUmQIAAgbQCAjBta0yMAAECBCIFBGCkrtoECBAgkFZAAKZtjYkRIECAQKSAAIzUVZsAAQIE0goIwLStMTECBAgQiBQQgJG6ahMgQIBAWgEBmLY1JkaAAAECkQICMFJXbQIECBBIKyAA07bGxAgQIEAgUkAARuqqTYAAAQJpBQRg2taYGAECBAhECgjASF21CRAgQCCtgABM2xoTI0CAAIFIAQEYqas2AQIECKQVEIBpW2NiBAgQIBApIAAjddUmQIAAgbQCAjBta0yMAAECBCIFBGCkrtoECBAgkFZAAKZtjYkRIECAQKSAAIzUVZsAAQIE0goIwLStMTECBAgQiBQQgJG6ahMgQIBAWgEBmLY1JkaAAAECkQICMFJXbQIECBBIKyAA07bGxAgQIEAgUuA2sviv2l/lmaeyvZftoWz3ZbvkYTw/68f14/5xzPvnJVkx971j9yjjxlW3sdQ7/Tx2tT9/Xjm94/xv49ve8Du/XvpXrR/rp76/uX76K+T88V9cP3W/Vtjv4m7c9k+gL10sv3XHU4fGt0L8Wo+pI+unFbJ+Wo+pI+unFdp6/bSfvs5Rn4ilavst8drj+jvFc1e7fm3OvvFtb+aY1e/hx6++nuu1MWff+rF+tl4/9eetsN/n3fD9RB2lwzDUh9fvN9VLudeyfZTtbmFp4/lZP64f949lN9C93T/XjqM27m62D8BlbTOKAAECBI4mEByA2/4b4NGa53wJECBAIK2AAEzbGhMjQIAAgUgBARipqzYBAgQIpBUQgGlbY2IECBAgECkgACN11SZAgACBtAICMG1rTIwAAQIEIgUEYKSu2gQIECCQVkAApm2NiREgQIBApIAAjNRVmwABAgTSCgjAtK0xMQIECBCIFBCAkbpqEyBAgEBaAQGYtjUmRoAAAQKRAgIwUldtAgQIEEgrIADTtsbECBAgQCBSQABG6qpNgAABAmkFBGDa1pgYAQIECEQKCMBIXbUJECBAIK2AAEzbGhMjQIAAgUgBARipqzYBAgQIpBUQgGlbY2IECBAgECkgACN11SZAgACBtAICMG1rTIwAAQIEIgUEYKSu2gQIECCQVkAApm2NiREgQIBApIAAjNRVmwABAgTSCgjAtK0xMQIECBCIFBCAkbpqEyBAgEBaAQGYtjUmRoAAAQKRAgIwUldtAgQIEEgrIADTtsbECBAgQCBS4Day+K/aX+WZp7K9l+2hbPdlu+RhPD/rx/Xj/nHM++clWTH3vWP3KOPGVbex1Dv9PHa1P39eOb3j/G/j297wO79e+letH+unvr+5fvor5PzxX1w/db9W2O/ibtz2T6AvXSy/dcdTh8a3Qvxaj6kj66cVsn5aj6kj66cV2nr9tJ++zlGfiKVq+y3x2uP6O8VzV7t+bc6+8W1v5pjV7+HHr76e67UxZ9/6sX62Xj/1562w3+fd8P1EHaXDMNSH1+831Uu517J9lO1uYWnj+Vk/rh/3j2U30L3dP9eOozbubrYPwGVtM4oAAQIEjiYQHIDb/hvg0ZrnfAkQIEAgrYAATNsaEyNAgACBSAEBGKmrNgECBAikFRCAaVtjYgQIECAQKSAAI3XVJkCAAIG0AgIwbWtMjAABAgQiBQRgpK7aBAgQIJBWQACmbY2JESBAgECkgACM1FWbAAECBNIKCMC0rTExAgQIEIgUEICRumoTIECAQFoBAZi2NSZGgAABApECAjBSV20CBAgQSCsgANO2xsQIECBAIFJAAEbqqk2AAAECaQUEYNrWmBgBAgQIRAoIwEhdtQkQIEAgrYAATNsaEyNAgACBSAEBGKmrNgECBAikFRCAaVtjYgQIECAQKSAAI3XVJkCAAIG0AgIwbWtMjAABAgQiBQRgpK7aBAgQIJBWQACmbY2JESBAgECkgACM1FWbAAECBNIKCMC0rTExAgQIEIgUEICRumoTIECAQFoBAZi2NSZGgAABApECAjBSV20CBAgQSCsgANO2xsQIECBAIFLgNrL4r9pf5Zmnsr2X7aFs92W75GE8P+vH9eP+ccz75yVZMfe9Y/co48ZVt7HUO/08drU/f145veP8b+Pb3vA7v176V60f66e+v7l++ivk/PFfXD91v1bY7+Ju3PZPoC9dLL91x1OHxrdC/FqPqSPrpxWyflqPqSPrpxXaev20n77OUZ+IpWr7LfHa4/o7xXNXu35tzr7xbW/mmNXv4cevvp7rtTFn3/qxfrZeP/XnrbDf593w/UQdpcMw1IfX7zfVS7nXsn2U7W5haeP5WT+uH/ePZTfQvd0/146jNu5utg/AZW0zigABAgSOJhAcgNv+G+DRmud8CRAgQCCtgABM2xoTI0CAAIFIAQEYqas2AQIECKQVEIBpW2NiBAgQIBApIAAjddUmQIAAgbQCAjBta0yMAAECBCIFBGCkrtoECBAgkFZAAKZtjYkRIECAQKSAAIzUVZsAAQIE0goIwLStMTECBAgQiBQQgJG6ahMgQIBAWgEBmLY1JkaAAAECkQICMFJXbQIECBBIKyAA07bGxAgQIEAgUkAARuqqTYAAAQJpBQRg2taYGAECBAhECgjASF21CRAgQCCtgABM2xoTI0CAAIFIAQEYqas2AQIECKQVEIBpW2NiBAgQIBApIAAjddUmQIAAgbQCAjBta0yMAAECBCIFBGCkrtoECBAgkFZAAKZtjYkRIECAQKSAAIzUVZsAAQIE0goIwLStMTECBAgQiBQQgJG6ahMgQIBAWgEBmLY1JkaAAAECkQICMFJXbQIECBBIKyAA07bGxAgQIEAgUuA2sviv2l/lmaeyvZftoWz3ZbvkYTw/68f14/5xzPvnJVkx971j9yjjxlW3sdQ7/Tx2tT9/Xjm94/xv49ve8Du/XvpXrR/rp76/uX76K+T88V9cP3W/Vtjv4m7c9k+gL10sv3XHU4fGt0L8Wo+pI+unFbJ+Wo+pI+unFdp6/bSfvs5Rn4ilavst8drj+jvFc1e7fm3OvvFtb+aY1e/hx6++nuu1MWff+rF+tl4/9eetsN/n3fD9RB2lwzDUh9fvN9VLudeyfZTtbmFp4/lZP64f949lN9C93T/XjqM27m62D8BlbTOKAAECBI4mEByA2/4b4NGa53wJECBAIK2AAEzbGhMjQIAAgUgBARipqzYBAgQIpBUQgGlbY2IECBAgECkgACN11SZAgACBtAICMG1rTIwAAQIEIgUEYKSu2gQIECCQVkAApm2NiREgQIBApIAAjNRVmwABAgTSCgjAtK0xMQIECBCIFBCAkbpqEyBAgEBaAQGYtjUmRoAAAQKRAgIwUldtAgQIEEgrIADTtsbECBAgQCBSQABG6qpNgAABAmkFBGDa1pgYAQIECEQKCMBIXbUJECBAIK2AAEzbGhMjQIAAgUgBARipqzYBAgQIpBUQgGlbY2IECBAgECkgACN11SZAgACBtAICMG1rTIwAAQIEIgUEYKSu2gQIECCQVkAApm2NiREgQIBApIAAjNRVmwABAgTSCgjAtK0xMQIECBCIFBCAkbpqEyBAgEBaAQGYtjUmRoAAAQKRAgIwUldtAgQIEEgrIADTtsbECBAgQCBS4Day+K/aX+WZp7K9l+2hbPdlu+RhPD/rx/Xj/nHM++clWTH3vWP3KOPGVbex1Dv9PHa1P39eOb3j/G/j297wO79e+letH+unvr+5fvor5PzxX1w/db9W2O/ibtz2T6AvXSy/dcdTh8a3Qvxaj6kj66cVsn5aj6kj66cV2nr9tJ++zlGfiKVq+y3x2uP6O8VzV7t+bc6+8W1v5pjV7+HHr76e67UxZ9/6sX62Xj/1562w3+fd8P1EHaXDMNSH1+831Uu517J9lO1uYWnj+Vk/rh/3j2U30L3dP9eOozbubrYPwGVtM4oAAQIEjiYQHIDb/hvg0ZrnfAkQIEAgrYAATNsaEyNAgACBSAEBGKmrNgECBAikFRCAaVtjYgQIECAQKSAAI3XVJkCAAIG0AgIwbWtMjAABAgQiBeL/v0BX/p+xRmKoTYAAAQLHEfBfgMfptTMlQIAAgUpAAFYYdgkQIEDgOAIC8Di9dqYECBAgUAkIwArDLgECBAgcR0AAHqfXzpQAAQIEKgEBWGHYJUCAAIHjCAjA4/TamRIgQIBAJSAAKwy7BAgQIHAcAQF4nF47UwIECBCoBARghWGXAAECBI4jIACP02tnSoAAAQKVgACsMOwSIECAwHEEBOBxeu1MCRAgQKASEIAVhl0CBAgQOI6AADxOr50pAQIECFQCArDCsEuAAAECxxEQgMfptTMlQIAAgUpAAFYYdgkQIEDgOAK3/akOw9A/5ZgAAQIECPxzAsNYHv/cWTkhAgQIECAwIeBPoBNAXiZAgACBf1NAAP6bfXVWBAgQIDAhIAAngLxMgAABAv+mwH8BIoXpK3F6REIAAAAASUVORK5CYII="/><text id="ball.style.left" fill="#C06334" font-family="PTMono-Regular, PT Mono" font-size="12" font-weight="normal"><tspan x="174" y="262">ball.style.left</tspan></text><text id="?" fill="#C06334" font-family="PTMono-Regular, PT Mono" font-size="40" font-weight="normal"><tspan x="217.5" y="229">?</tspan></text><text id="fieldCoords.left" fill="#C06334" font-family="PTMono-Regular, PT Mono" font-size="12" font-weight="normal"><tspan x="48.9" y="189.32">fieldCoords.left</tspan></text><text id="event.clientX" fill="#C06334" font-family="PTMono-Regular, PT Mono" font-size="12" font-weight="normal"><tspan x="59.7" y="161.32">event.clientX</tspan></text><path id="Line-41" fill="#C06334" fill-rule="nonzero" d="M168.41 187.91l14 7-14 7v-6H39.589l.001 6-14-7 14-7-.001 6H168.41v-6z"/><path id="Line-41-Copy-2" fill="#C06334" fill-rule="nonzero" d="M248 160.91l14 7-14 7v-6H39.589l.001 6-14-7 14-7-.001 6H248v-6z"/><g id="ball" opacity=".7" transform="translate(261 168)"><circle id="Oval-4" cx="18.753" cy="18.753" r="18.753" fill="#FFF"/><path id="Shape" fill="#181717" fill-rule="nonzero" d="M37.792 18.204a.229.229 0 00-.003-.123l-.004-.016a18.9 18.9 0 00-6.003-12.94c-.445-.461-1.107-1.006-1.863-1.534A19.043 19.043 0 0012.899.935c-.412.106-.98.322-1.64.622a18.593 18.593 0 00-3.53 2.014A18.856 18.856 0 004.68 6.344c-6.874 7.794-6.085 19.762 1.758 26.679a19.043 19.043 0 004.876 3.128c.254.121.465.194.645.272 7.417 2.964 15.919.96 21.169-4.992 3.216-3.65 4.87-8.343 4.663-13.227zm-.677-.785l-.05-.135c-.387-1.04-1.095-2.957-2.828-4.492-.255-3.228-1.405-5.115-2.43-6.774a18.297 18.297 0 015.308 11.401zM24.485 3.158a.476.476 0 00.044-.013c1.666.291 4.245 1.237 6.464 2.748a.274.274 0 00.03.068c.06.1.214.347.214.347.99 1.6 2.109 3.414 2.362 6.494-1.007.741-1.724 1.531-2.42 2.295-.414.456-.84.925-1.326 1.383-1.63-1.516-4.52-2.813-7.641-3.42-.306-1.35-.919-3.854-2.423-6.628l.116-.132c2.418-2.743 3.269-2.9 4.255-3.08.105-.019.213-.039.325-.062zm-8.729 15.517l6.255-5.001c3.137.597 6.025 1.903 7.547 3.413.372 1.153.468 3.636.54 5.464.035.935.069 1.754.127 2.246-2.457 2.24-5.717 4.539-6.78 4.778l-6.843-2.677c-.512-2.867-.662-4.83-.846-8.223zm.205 8.273l-4.955 3.485a.249.249 0 00-.148-.012c-.923.192-4.479-2.415-6.21-3.929a.264.264 0 00-.065-.04c-.073-.495-.23-1.125-.408-1.84-.469-1.885-1.05-4.221-.528-5.673l.028-.028c.006-.007.013-.015.018-.024.339-.487.665-.908 1-1.286 1.058-1.2 2.256-2.053 4.12-2.927l.379.276c1.735 1.266 4.094 2.99 5.927 3.8.185 3.374.335 5.339.842 8.198zM9.35 3.678c.07-.079.145-.147.233-.226l.049-.044c.185-.102.424-.243.702-.405.653-.381 1.546-.905 2.527-1.398a18.483 18.483 0 018.294-.832c0 .093.052.18.137.223.17.089.343.172.515.255.733.354 1.432.692 2.073 1.37-1.001.19-2.025.496-4.464 3.262l-.156.177c-2.457-.245-5.282-.38-7.822.37-.94-1.119-1.73-1.651-2.429-2.123l-.019-.013c.153-.357.266-.51.36-.616zM1.413 15.43a.25.25 0 00-.398-.123 1.375 1.375 0 00-.076.066 18.024 18.024 0 014.08-8.465c.12-.1.274-.233.448-.385.724-.63 2.41-2.101 2.946-1.847a13.536 13.536 0 00.092.063l.147.1c.652.44 1.39.937 2.258 1.96-.78 1.657-2.416 5.752-2.408 7.314-1.927.907-3.177 1.802-4.288 3.062-.295.334-.584.699-.878 1.108l-.106-.1c-.557-.52-1.396-1.306-1.817-2.753zm5.438 17.124a18.323 18.323 0 01-3.95-4.92c.691-.195 1.023-.323 1.411-.586.638.539 4.586 3.827 6.334 4.006a36.692 36.692 0 003.055 4.295c-.572.352-1.042.473-1.648.437a18.311 18.311 0 01-5.202-3.232zm6.469 3.698c.295-.108.593-.268.92-.49 2.756.407 5.828.152 7.99-.152.031.021.066.034.105.04.52.068 2.062.226 3.92.015a18.292 18.292 0 01-12.935.587zm18.778-4.768a.256.256 0 00-.042.181 17.964 17.964 0 01-3.83 3.004.236.236 0 00-.111.003c-2.244.59-4.392.479-5.354.382.433-1.078.73-2.68.902-4.876 1.262-.352 4.456-2.594 7.01-4.925.636.105 1.07.204 1.421.286.858.198 1.25.29 2.81.086-.201 1.17-.765 2.896-2.806 5.86zm2.797-3.623c.438-1.046.605-1.827.69-2.448a.237.237 0 00-.014-.125c.39-.886 1.186-3.392 1.594-5.443a18.028 18.028 0 01-2.27 8.016z"/></g><g id="Default"><use fill="#000" filter="url(#filter-2)" xlink:href="#path-1"/><path fill="#FFF" stroke="#A7333A" d="M262.5 168.775l15.69 16.01h-6.488c.886 1.695 3.06 5.91 4.01 8.24.318.776-.979 2.324-.979 2.324h0l-2.42.151-4.2-8.574-5.613 5.727v-23.878z"/></g><path id="Line-16-Copy-2" stroke="#C06334" stroke-dasharray="3,6" stroke-linecap="square" stroke-width="2" d="M195.535 180.945s20.728 10.637 32.233 10.637c11.504 0 32.232-10.637 32.232-10.637"/></g></g></svg> \ No newline at end of file diff --git a/2-ui/2-events/01-introduction-browser-events/04-move-ball-field/move-ball-coords@2x.png b/2-ui/2-events/01-introduction-browser-events/04-move-ball-field/move-ball-coords@2x.png deleted file mode 100644 index ecf4e5d2ff..0000000000 Binary files a/2-ui/2-events/01-introduction-browser-events/04-move-ball-field/move-ball-coords@2x.png and /dev/null differ diff --git a/2-ui/2-events/01-introduction-browser-events/04-move-ball-field/solution.md b/2-ui/2-events/01-introduction-browser-events/04-move-ball-field/solution.md index bda48e2bb4..04d9878ca3 100644 --- a/2-ui/2-events/01-introduction-browser-events/04-move-ball-field/solution.md +++ b/2-ui/2-events/01-introduction-browser-events/04-move-ball-field/solution.md @@ -1,11 +1,11 @@ -First we need to choose a method of positioning the ball. +Сначала мы должны выбрать метод позиционирования мяча. -We can't use `position:fixed` for it, because scrolling the page would move the ball from the field. +Мы не можем использовать `position:fixed`, поскольку прокрутка страницы будет перемещать мяч с поля. -So we should use `position:absolute` and, to make the positioning really solid, make `field` itself positioned. +Правильнее использовать `position:absolute`, и, чтобы сделать позиционирование действительно надёжным, сделаем само поле (`field`) позиционированным. -Then the ball will be positioned relatively to the field: +Тогда мяч будет позиционирован относительно поля: ```css #field { @@ -16,36 +16,36 @@ Then the ball will be positioned relatively to the field: #ball { position: absolute; - left: 0; /* relative to the closest positioned ancestor (field) */ + left: 0; /* по отношению к ближайшему расположенному предку (поле) */ top: 0; - transition: 1s all; /* CSS animation for left/top makes the ball fly */ + transition: 1s all; /* CSS-анимация для значений left/top делает передвижение мяча плавным */ } ``` -Next we need to assign the correct `ball.style.position.left/top`. They contain field-relative coordinates now. +Далее мы должны назначить корректные значения `ball.style.left/top`. Сейчас они содержат координаты относительно поля. -Here's the picture: +Картинка: -![](move-ball-coords.png) +![](move-ball-coords.svg) -We have `event.clientX/clientY` -- window-relative coordinates of the click. +У нас есть значения `event.clientX/clientY` -- координаты нажатия мышки относительно окна браузера; -To get field-relative `left` coordinate of the click, we can substract the field left edge and the border width: +Чтобы получить значение `left` для мяча после нажатия мышки относительно поля, мы должны из координаты нажатия мышки вычесть координату левого края поля и ширину границы: ```js let left = event.clientX - fieldCoords.left - field.clientLeft; ``` -Normally, `ball.style.position.left` means the "left edge of the element" (the ball). So if we assign that `left`, then the ball edge would be under the mouse cursor. +Значение `ball.style.left` означает "левый край элемента" (мяча). И если мы назначим такой `left` для мяча, тогда его левая граница, а не центр, будет под курсором мыши. -We need to move the ball half-width left and half-height up to make it center. +Нам нужно сдвинуть мяч на половину его высоты вверх и половину его ширины влево, чтобы центр мяча точно совпадал с точкой нажатия мышки. -So the final `left` would be: +В итоге значение для `left` будет таким: ```js let left = event.clientX - fieldCoords.left - field.clientLeft - ball.offsetWidth/2; ``` -The vertical coordinate is calculated using the same logic. +Вертикальная координата будет вычисляться по той же логике. -Please note that the ball width/height must be known at the time we access `ball.offsetWidth`. Should be specified in HTML or CSS. +Следует помнить, что ширина и высота мяча должна быть известна в тот момент, когда мы получаем значение `ball.offsetWidth`. Это значение может задаваться в HTML или CSS. diff --git a/2-ui/2-events/01-introduction-browser-events/04-move-ball-field/solution.view/index.html b/2-ui/2-events/01-introduction-browser-events/04-move-ball-field/solution.view/index.html index 3ebe8739e1..e7beb85dbf 100644 --- a/2-ui/2-events/01-introduction-browser-events/04-move-ball-field/solution.view/index.html +++ b/2-ui/2-events/01-introduction-browser-events/04-move-ball-field/solution.view/index.html @@ -27,41 +27,41 @@ <body style="height:2000px"> - Click on a field to move the ball there. + Нажмите на поле для перемещения мяча. <br> <div id="field"> - <img src="https://en.js.cx/clipart/ball.svg" id="ball"> . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + <img src="https://ru.js.cx/clipart/ball.svg" id="ball"> . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . </div> <script> field.onclick = function(event) { - // window-relative field coordinates + // координаты поля относительно окна браузера let fieldCoords = this.getBoundingClientRect(); - // the ball has position:absolute, the field: position:relative - // so ball coordinates are relative to the field inner left-upper corner + // мяч имеет абсолютное позиционирование (position:absolute), поле - относительное (position:relative) + // таким образом, координаты мяча рассчитываются относительно внутреннего, верхнего левого угла поля let ballCoords = { top: event.clientY - fieldCoords.top - field.clientTop - ball.clientHeight / 2, left: event.clientX - fieldCoords.left - field.clientLeft - ball.clientWidth / 2 }; - // prevent crossing the top field boundary + // запрещаем пересекать верхнюю границу поля if (ballCoords.top < 0) ballCoords.top = 0; - // prevent crossing the left field boundary + // запрещаем пересекать левую границу поля if (ballCoords.left < 0) ballCoords.left = 0; - // // prevent crossing the right field boundary + // // запрещаем пересекать правую границу поля if (ballCoords.left + ball.clientWidth > field.clientWidth) { ballCoords.left = field.clientWidth - ball.clientWidth; } - // prevent crossing the bottom field boundary + // запрещаем пересекать нижнюю границу поля if (ballCoords.top + ball.clientHeight > field.clientHeight) { ballCoords.top = field.clientHeight - ball.clientHeight; } diff --git a/2-ui/2-events/01-introduction-browser-events/04-move-ball-field/source.view/index.html b/2-ui/2-events/01-introduction-browser-events/04-move-ball-field/source.view/index.html index 34f9c88aea..8f6862a124 100644 --- a/2-ui/2-events/01-introduction-browser-events/04-move-ball-field/source.view/index.html +++ b/2-ui/2-events/01-introduction-browser-events/04-move-ball-field/source.view/index.html @@ -16,12 +16,12 @@ <body style="height:2000px"> - Click on a field to move the ball there. - <br> The ball should never leave the field. + Нажмите на поле для перемещения мяча. + <br> Мяч не должен выходить за границы поля. <div id="field"> - <img src="https://en.js.cx/clipart/ball.svg" id="ball"> . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + <img src="https://ru.js.cx/clipart/ball.svg" id="ball"> . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . </div> diff --git a/2-ui/2-events/01-introduction-browser-events/04-move-ball-field/task.md b/2-ui/2-events/01-introduction-browser-events/04-move-ball-field/task.md index d5269147a0..08caa63756 100644 --- a/2-ui/2-events/01-introduction-browser-events/04-move-ball-field/task.md +++ b/2-ui/2-events/01-introduction-browser-events/04-move-ball-field/task.md @@ -2,20 +2,20 @@ importance: 5 --- -# Move the ball across the field +# Передвиньте мяч по полю -Move the ball across the field to a click. Like this: +Пусть мяч перемещается при клике на поле, туда, куда был клик, вот так: [iframe src="solution" height="260" link] -Requirements: +Требования: -- The ball center should come exactly under the pointer on click (if possible without crossing the field edge). -- CSS-animation is welcome. -- The ball must not cross field boundaries. -- When the page is scrolled, nothing should break. +- Центр мяча должен совпадать с местом нажатия мыши (если это возможно без пересечения краёв поля); +- CSS-анимация желательна, но не обязательна; +- Мяч ни в коем случае не должен пересекать границы поля; +- При прокрутке страницы ничего не должно ломаться; -Notes: +Заметки: -- The code should also work with different ball and field sizes, not be bound to any fixed values. -- Use properties `event.clientX/event.clientY` for click coordinates. +- Код должен уметь работать с различными размерами мяча и поля, не привязываться к каким-либо фиксированным значениям. +- Используйте свойства `event.clientX/event.clientY` для определения координат мыши при клике. diff --git a/2-ui/2-events/01-introduction-browser-events/05-sliding-menu/solution.md b/2-ui/2-events/01-introduction-browser-events/05-sliding-menu/solution.md index eed0962314..5ba75e9f07 100644 --- a/2-ui/2-events/01-introduction-browser-events/05-sliding-menu/solution.md +++ b/2-ui/2-events/01-introduction-browser-events/05-sliding-menu/solution.md @@ -1,47 +1,47 @@ # HTML/CSS -First let's create HTML/CSS. +Для начала создадим разметку HTML/CSS нашего меню. -A menu is a standalone graphical component on the page, so it's better to put it into a single DOM element. +Меню - это отдельный графический компонент на странице, так что его лучше вынести в отдельный DOM-элемент. -A list of menu items can be laid out as a list `ul/li`. +Список пунктов меню может быть представлен в виде списка `ul/li`. -Here's the example structure: +Пример HTML-структуры: ```html <div class="menu"> - <span class="title">Sweeties (click me)!</span> + <span class="title">Сладости (нажми меня)!</span> <ul> - <li>Cake</li> - <li>Donut</li> - <li>Honey</li> + <li>Пирожное</li> + <li>Пончик</li> + <li>Мёд</li> </ul> </div> ``` -We use `<span>` for the title, because `<div>` has an implicit `display:block` on it, and it will occupy 100% of the horizontal width. +Для заголовка мы используем тег `<span>`, потому что `<div>`, как и любой блочный элемент, имеет скрытое свойство `display:block`, а значит, занимает ширину 100%. -Like this: +Например: ```html autorun height=50 -<div style="border: solid red 1px" onclick="alert(1)">Sweeties (click me)!</div> +<div style="border: solid red 1px" onclick="alert(1)">Сладости (нажми меня)!</div> ``` -So if we set `onclick` on it, then it will catch clicks to the right of the text. +Таким образом, если мы зададим обработчик события `onclick`, то он будет срабатывать по клику на всей ширине поля. -...but `<span>` has an implicit `display: inline`, so it occupies exactly enough place to fit all the text: +...тег `<span>` -- строчный элемент, по умолчанию имеет свойство `display: inline`, который занимает ровно столько места, сколько занимает сам текст: ```html autorun height=50 -<span style="border: solid red 1px" onclick="alert(1)">Sweeties (click me)!</span> +<span style="border: solid red 1px" onclick="alert(1)">Сладости (нажми меня)!</span> ``` -# Toggling the menu +# Переключение меню -Toggling the menu should change the arrow and show/hide the menu list. +Переключение меню должно менять стрелку и скрывать или показывать список элементов меню. -All these changes are perfectly handled by CSS. In JavaScript we should label the current state of the menu by adding/removing the class `.open`. +Все эти изменения прекрасно обрабатываются средствами CSS. Посредством JavaScript мы будем отмечать текущее состояние меню, добавляя или удаляя класс `.open`. -Without it, the menu will be closed: +Без класса `.open` меню будет закрыто: ```css .menu ul { @@ -58,7 +58,7 @@ Without it, the menu will be closed: } ``` -...And with `.open` the arrow changes and the list shows up: +...А с ним (с классом `.open`) стрелка будет меняться, и список будет показываться: ```css .menu.open .title::before { diff --git a/2-ui/2-events/01-introduction-browser-events/05-sliding-menu/solution.view/index.html b/2-ui/2-events/01-introduction-browser-events/05-sliding-menu/solution.view/index.html index 0c1996a7e0..5ffa1a5e13 100644 --- a/2-ui/2-events/01-introduction-browser-events/05-sliding-menu/solution.view/index.html +++ b/2-ui/2-events/01-introduction-browser-events/05-sliding-menu/solution.view/index.html @@ -35,11 +35,11 @@ <body> <div id="sweeties" class="menu"> - <span class="title">Sweeties (click me)!</span> + <span class="title">Сладости (нажми меня)!</span> <ul> - <li>Cake</li> - <li>Donut</li> - <li>Honey</li> + <li>Пирожное</li> + <li>Пончик</li> + <li>Мёд</li> </ul> </div> diff --git a/2-ui/2-events/01-introduction-browser-events/05-sliding-menu/source.view/index.html b/2-ui/2-events/01-introduction-browser-events/05-sliding-menu/source.view/index.html index 03dded0cd8..a6d42ab091 100644 --- a/2-ui/2-events/01-introduction-browser-events/05-sliding-menu/source.view/index.html +++ b/2-ui/2-events/01-introduction-browser-events/05-sliding-menu/source.view/index.html @@ -5,11 +5,11 @@ </head> <body> - ▶ ▼ Sweeties (click me)! + ▶ ▼ Сладости (нажми меня)! <ul> - <li>Cake</li> - <li>Donut</li> - <li>Honey</li> + <li>Пирожное</li> + <li>Пончик</li> + <li>Мёд</li> </ul> </body> diff --git a/2-ui/2-events/01-introduction-browser-events/05-sliding-menu/task.md b/2-ui/2-events/01-introduction-browser-events/05-sliding-menu/task.md index 34c3137103..585f7e267a 100644 --- a/2-ui/2-events/01-introduction-browser-events/05-sliding-menu/task.md +++ b/2-ui/2-events/01-introduction-browser-events/05-sliding-menu/task.md @@ -2,10 +2,10 @@ importance: 5 --- -# Create a sliding menu +# Создать раскрывающееся меню -Create a menu that opens/collapses on click: +Создать меню, которое по нажатию открывается либо закрывается: [iframe border=1 height=100 src="solution"] -P.S. HTML/CSS of the source document is to be modified. +P.S. HTML/CSS исходного документа можно и нужно менять. diff --git a/2-ui/2-events/01-introduction-browser-events/06-hide-message/solution.md b/2-ui/2-events/01-introduction-browser-events/06-hide-message/solution.md index 022a0d9774..c2a5f01e13 100644 --- a/2-ui/2-events/01-introduction-browser-events/06-hide-message/solution.md +++ b/2-ui/2-events/01-introduction-browser-events/06-hide-message/solution.md @@ -1,12 +1,13 @@ -To add the button we can use either `position:absolute` (and make the pane `position:relative`) or `float:right`. The `float:right` has the benefit that the button never overlaps the text, but `position:absolute` gives more freedom. So the choice is yours. +Чтобы добавить кнопку закрытия, мы можем использовать либо `position:absolute` (и сделать плитку (`pane`) `position:relative`) либо `float:right`. Преимущество варианта с `float:right` в том, что кнопка закрытия никогда не перекроет текст, но вариант `position:absolute` даёт больше свободы для действий. В общем, выбор за вами. + +Тогда для каждой плитки код может выглядеть следующим образом: -Then for each pane the code can be like: ```js pane.insertAdjacentHTML("afterbegin", '<button class="remove-button">[x]</button>'); ``` -Then the `<button>` becomes `pane.firstChild`, so we can add a handler to it like this: +Элемент `<button>` становится `pane.firstChild`, таким образом мы можем добавить на него обработчик события: ```js pane.firstChild.onclick = () => pane.remove(); diff --git a/2-ui/2-events/01-introduction-browser-events/06-hide-message/solution.view/index.html b/2-ui/2-events/01-introduction-browser-events/06-hide-message/solution.view/index.html index ff9d8cc2bb..82a2227e3e 100644 --- a/2-ui/2-events/01-introduction-browser-events/06-hide-message/solution.view/index.html +++ b/2-ui/2-events/01-introduction-browser-events/06-hide-message/solution.view/index.html @@ -10,17 +10,16 @@ <div> <div class="pane"> - <h3>Horse</h3> - <p>The horse is one of two extant subspecies of Equus ferus. It is an odd-toed ungulate mammal belonging to the taxonomic family Equidae. The horse has evolved over the past 45 to 55 million years from a small multi-toed creature, Eohippus, into the large, single-toed animal of today.</p> + <h3>Лошадь</h3> + <p>Домашняя лошадь — животное семейства непарнокопытных, одомашненный и единственный сохранившийся подвид дикой лошади, вымершей в дикой природе, за исключением небольшой популяции лошади Пржевальского.</p> </div> <div class="pane"> - <h3>Donkey</h3> - <p>The donkey or ass (Equus africanus asinus) is a domesticated member of the horse family, Equidae. The wild ancestor of the donkey is the African wild ass, E. africanus. The donkey has been used as a working animal for at least 5000 years.</p> + <h3>Осёл</h3> + <p>Домашний осёл (лат. Equus asinus asinus), или ишак, — одомашненный подвид дикого осла (Equus asinus), сыгравший важную историческую роль в развитии хозяйства и культуры человека и по-прежнему широко в хозяйстве многих развивающихся стран.</p> </div> <div class="pane"> - <h3>Cat</h3> - <p>The domestic cat (Latin: Felis catus) is a small, typically furry, carnivorous mammal. They are often called house cats when kept as indoor pets or simply cats when there is no need to distinguish them from other felids and felines. Cats are often valued by humans for companionship and for their ability to hunt vermin. - </p> + <h3>Кошка</h3> + <p>Кошка, или домашняя кошка (лат. Felis silvestris catus), — домашнее животное, одно из наиболее популярных(наряду с собакой) «животных-компаньонов». Являясь одиночным охотником на грызунов и других мелких животных, кошка — социальное животное, использующее для общения широкий диапазон звуковых сигналов.</p> </div> </div> @@ -30,7 +29,7 @@ <h3>Cat</h3> for(let pane of panes) { pane.insertAdjacentHTML("afterbegin", '<button class="remove-button">[x]</button>'); - // button becomes the first child of pane + // кнопка становится первым потомком плитки (pane) pane.firstChild.onclick = () => pane.remove(); } </script> diff --git a/2-ui/2-events/01-introduction-browser-events/06-hide-message/source.view/index.html b/2-ui/2-events/01-introduction-browser-events/06-hide-message/source.view/index.html index 07adbf985f..9cb2d2b3e9 100644 --- a/2-ui/2-events/01-introduction-browser-events/06-hide-message/source.view/index.html +++ b/2-ui/2-events/01-introduction-browser-events/06-hide-message/source.view/index.html @@ -8,22 +8,21 @@ <body> - The button code (may need to adjust CSS): + Код кнопки (возможно вам нужно будет изменить CSS): <button class="remove-button">[x]</button> <div> <div class="pane"> - <h3>Horse</h3> - <p>The horse is one of two extant subspecies of Equus ferus. It is an odd-toed ungulate mammal belonging to the taxonomic family Equidae. The horse has evolved over the past 45 to 55 million years from a small multi-toed creature, Eohippus, into the large, single-toed animal of today.</p> + <h3>Лошадь</h3> + <p>Домашняя лошадь — животное семейства непарнокопытных, одомашненный и единственный сохранившийся подвид дикой лошади, вымершей в дикой природе, за исключением небольшой популяции лошади Пржевальского.</p> </div> <div class="pane"> - <h3>Donkey</h3> - <p>The donkey or ass (Equus africanus asinus) is a domesticated member of the horse family, Equidae. The wild ancestor of the donkey is the African wild ass, E. africanus. The donkey has been used as a working animal for at least 5000 years.</p> + <h3>Осёл</h3> + <p>Домашний осёл (лат. Equus asinus asinus), или ишак, — одомашненный подвид дикого осла (Equus asinus), сыгравший важную историческую роль в развитии хозяйства и культуры человека и по-прежнему широко в хозяйстве многих развивающихся стран.</p> </div> <div class="pane"> - <h3>Cat</h3> - <p>The domestic cat (Latin: Felis catus) is a small, typically furry, carnivorous mammal. They are often called house cats when kept as indoor pets or simply cats when there is no need to distinguish them from other felids and felines. Cats are often valued by humans for companionship and for their ability to hunt vermin. - </p> + <h3>Кошка</h3> + <p>Кошка, или домашняя кошка (лат. Felis silvestris catus), — домашнее животное, одно из наиболее популярных(наряду с собакой) «животных-компаньонов». Являясь одиночным охотником на грызунов и других мелких животных, кошка — социальное животное, использующее для общения широкий диапазон звуковых сигналов.</p> </div> </div> diff --git a/2-ui/2-events/01-introduction-browser-events/06-hide-message/task.md b/2-ui/2-events/01-introduction-browser-events/06-hide-message/task.md index 152cf41fe6..86943c3dfe 100644 --- a/2-ui/2-events/01-introduction-browser-events/06-hide-message/task.md +++ b/2-ui/2-events/01-introduction-browser-events/06-hide-message/task.md @@ -2,12 +2,12 @@ importance: 5 --- -# Add a closing button +# Добавить кнопку закрытия -There's a list of messages. +Есть список сообщений. -Use JavaScript to add a closing button to the right-upper corner of each message. +При помощи JavaScript для каждого сообщения добавьте в верхний правый угол кнопку закрытия. -The result should look like this: +Результат должен выглядеть, как показано здесь: [iframe src="solution" height=450] diff --git a/2-ui/2-events/01-introduction-browser-events/07-carousel/carousel1.png b/2-ui/2-events/01-introduction-browser-events/07-carousel/carousel1.png deleted file mode 100644 index 93defb0bbb..0000000000 Binary files a/2-ui/2-events/01-introduction-browser-events/07-carousel/carousel1.png and /dev/null differ diff --git a/2-ui/2-events/01-introduction-browser-events/07-carousel/carousel1.svg b/2-ui/2-events/01-introduction-browser-events/07-carousel/carousel1.svg new file mode 100644 index 0000000000..5bb161f6cb --- /dev/null +++ b/2-ui/2-events/01-introduction-browser-events/07-carousel/carousel1.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="488" height="246" viewBox="0 0 488 246"><defs><style>@import url(https://fonts.googleapis.com/css?family=Open+Sans:bold,italic,bolditalic%7CPT+Mono);@font-face{font-family:'PT Mono';font-weight:700;font-style:normal;src:local('PT MonoBold'),url(/font/PTMonoBold.woff2) format('woff2'),url(/font/PTMonoBold.woff) format('woff'),url(/font/PTMonoBold.ttf) format('truetype')}</style></defs><g id="combined" fill="none" fill-rule="evenodd" stroke="none" stroke-width="1"><g id="carousel1.svg"><rect id="Rectangle-18" width="63" height="63" x="41.5" y="98.5" fill="#FBF2EC" stroke="#AF6E24" stroke-width="3" rx="10"/><rect id="Rectangle-19" width="63" height="63" x="109.5" y="98.5" fill="#FBF2EC" stroke="#AF6E24" stroke-width="3" rx="10"/><rect id="Rectangle-20" width="63" height="63" x="177.5" y="98.5" fill="#FBF2EC" stroke="#AF6E24" stroke-width="3" rx="10"/><rect id="Rectangle-21" width="63" height="63" x="245.5" y="98.5" fill="#FBF2EC" stroke="#AF6E24" stroke-width="3" opacity=".5" rx="10"/><rect id="Rectangle-22" width="63" height="63" x="313.5" y="98.5" fill="#FBF2EC" stroke="#AF6E24" stroke-width="3" opacity=".5" rx="10"/><rect id="Rectangle-23" width="63" height="63" x="381.5" y="98.5" fill="#FBF2EC" stroke="#AF6E24" stroke-width="3" opacity=".5" rx="10"/><text id="…-2" fill="#AF6E24" font-family="PTMono-Regular, PT Mono" font-size="24" font-weight="normal"><tspan x="406" y="131">…</tspan></text><path id="Rectangle-24" stroke="#C06334" stroke-width="2" d="M38 95h412v70H38z"/><path id="Rectangle-1" fill="#DBAF88" d="M261 42v171H19V42h242zm-17 17H36v137h208V59z"/><text id="div-(container)" fill="#AF6E24" font-family="PTMono-Regular, PT Mono" font-size="14" font-weight="normal"><tspan x="82.5" y="32">div (container)</tspan></text><text id="130x130" fill="#AF6E24" font-family="PTMono-Regular, PT Mono" font-size="13" font-weight="normal"><tspan x="46" y="133">130x130</tspan></text><text id="ul-(width:-9999px)" fill="#C06334" font-family="PTMono-Regular, PT Mono" font-size="14" font-weight="normal"><tspan x="71.5" y="82">ul (width: 9999px)</tspan></text><path id="Rectangle-36" fill="#FFF" d="M447 90h16v84h-16z"/></g></g></svg> \ No newline at end of file diff --git a/2-ui/2-events/01-introduction-browser-events/07-carousel/carousel1@2x.png b/2-ui/2-events/01-introduction-browser-events/07-carousel/carousel1@2x.png deleted file mode 100644 index 7aa0d0f84b..0000000000 Binary files a/2-ui/2-events/01-introduction-browser-events/07-carousel/carousel1@2x.png and /dev/null differ diff --git a/2-ui/2-events/01-introduction-browser-events/07-carousel/carousel2.png b/2-ui/2-events/01-introduction-browser-events/07-carousel/carousel2.png deleted file mode 100644 index a61bf5e579..0000000000 Binary files a/2-ui/2-events/01-introduction-browser-events/07-carousel/carousel2.png and /dev/null differ diff --git a/2-ui/2-events/01-introduction-browser-events/07-carousel/carousel2.svg b/2-ui/2-events/01-introduction-browser-events/07-carousel/carousel2.svg new file mode 100644 index 0000000000..81aea5b89e --- /dev/null +++ b/2-ui/2-events/01-introduction-browser-events/07-carousel/carousel2.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="639" height="246" viewBox="0 0 639 246"><defs><style>@import url(https://fonts.googleapis.com/css?family=Open+Sans:bold,italic,bolditalic%7CPT+Mono);@font-face{font-family:'PT Mono';font-weight:700;font-style:normal;src:local('PT MonoBold'),url(/font/PTMonoBold.woff2) format('woff2'),url(/font/PTMonoBold.woff) format('woff'),url(/font/PTMonoBold.ttf) format('truetype')}</style></defs><g id="dom" fill="none" fill-rule="evenodd" stroke="none" stroke-width="1"><g id="carousel2.svg"><rect id="Rectangle-18" width="63" height="63" x="41.5" y="98.5" fill="#FBF2EC" stroke="#AF6E24" stroke-width="3" opacity=".5" rx="10"/><rect id="Rectangle-19" width="63" height="63" x="109.5" y="98.5" fill="#FBF2EC" stroke="#AF6E24" stroke-width="3" opacity=".5" rx="10"/><rect id="Rectangle-20" width="63" height="63" x="177.5" y="98.5" fill="#FBF2EC" stroke="#AF6E24" stroke-width="3" opacity=".5" rx="10"/><rect id="Rectangle-21" width="63" height="63" x="245.5" y="98.5" fill="#FBF2EC" stroke="#AF6E24" stroke-width="3" rx="10"/><rect id="Rectangle-22" width="63" height="63" x="313.5" y="98.5" fill="#FBF2EC" stroke="#AF6E24" stroke-width="3" rx="10"/><rect id="Rectangle-23" width="63" height="63" x="381.5" y="98.5" fill="#FBF2EC" stroke="#AF6E24" stroke-width="3" rx="10"/><rect id="Rectangle-26" width="63" height="63" x="449.5" y="98.5" fill="#FBF2EC" stroke="#AF6E24" stroke-width="3" opacity=".5" rx="10"/><rect id="Rectangle-25" width="63" height="63" x="517.5" y="98.5" fill="#FBF2EC" stroke="#AF6E24" stroke-width="3" opacity=".5" rx="10"/><text id="…-3" fill="#AF6E24" font-family="PTMono-Regular, PT Mono" font-size="24" font-weight="normal"><tspan x="542" y="131">…</tspan></text><path id="Rectangle-24" stroke="#C06334" stroke-width="2" d="M38 95h546v70H38z"/><path id="Rectangle-1" fill="#DBAF88" d="M467 42v171H225V42h242zm-17 17H242v137h208V59z"/><text id="div-(container)" fill="#AF6E24" font-family="PTMono-Regular, PT Mono" font-size="14" font-weight="normal"><tspan x="288.5" y="32">div (container)</tspan></text><text id="130x130" fill="#AF6E24" font-family="PTMono-Regular, PT Mono" font-size="13" font-weight="normal"><tspan x="252" y="133">130x130</tspan></text><text id="ul-(margin-left:--35" fill="#C06334" font-family="PTMono-Regular, PT Mono" font-size="14" font-weight="normal"><tspan x="246.5" y="82">ul (margin-left: -350px)</tspan></text><path id="Rectangle-36" fill="#FFF" d="M582 83h16v84h-16z"/></g></g></svg> \ No newline at end of file diff --git a/2-ui/2-events/01-introduction-browser-events/07-carousel/carousel2@2x.png b/2-ui/2-events/01-introduction-browser-events/07-carousel/carousel2@2x.png deleted file mode 100644 index 4742579db6..0000000000 Binary files a/2-ui/2-events/01-introduction-browser-events/07-carousel/carousel2@2x.png and /dev/null differ diff --git a/2-ui/2-events/01-introduction-browser-events/07-carousel/solution.md b/2-ui/2-events/01-introduction-browser-events/07-carousel/solution.md index 25d83ab6d3..c57a6d3161 100644 --- a/2-ui/2-events/01-introduction-browser-events/07-carousel/solution.md +++ b/2-ui/2-events/01-introduction-browser-events/07-carousel/solution.md @@ -1,17 +1,17 @@ -The images ribbon can be represented as `ul/li` list of images `<img>`. +Лента изображений в разметке должна быть представлена как список `ul/li` с картинками `<img>`. -Normally, such a ribbon is wide, but we put a fixed-size `<div>` around to "cut" it, so that only a part of the ribbon is visibble: +Нужно расположить ленту внутри `<div>` фиксированного размера, так чтобы в один момент была видна только нужная часть списка: -![](carousel1.png) +![](carousel1.svg) -To make the list show horizontally we need to apply correct CSS properties for `<li>`, like `display: inline-block`. +Чтобы список сделать горизонтальным, нам нужно применить CSS-свойство `display: inline-block` для `<li>`. -For `<img>` we should also adjust `display`, because by default it's `inline`. There's extra space reserved under `inline` elements for "letter tails", so we can use `display:block` to remove it. +Для тега `<img>` мы также должны настроить `display`, поскольку по умолчанию он `inline`. Во всех элементах типа `inline` резервируется дополнительное место под "хвосты" символов. И чтобы его убрать, нам нужно прописать `display:block`. -To do the scrolling, we can shift `<ul>`. There are many ways to do it, for instance by changing `margin-left` or (better performance) use `transform: translateX()`: +Для «прокрутки» будем сдвигать `<ul>`. Это можно делать по-разному, например, назначением CSS-свойства `transform: translateX()` (лучше для производительности) или `margin-left`: -![](carousel2.png) +![](carousel2.svg) -The outer `<div>` has a fixed width, so "extra" images are cut. +У внешнего `<div>` фиксированная ширина, поэтому «лишние» изображения обрезаются. -The whole carousel is a self-contained "graphical component" on the page, so we'd better wrap it into a single `<div class="carousel">` and style things inside it. +Вся карусель -- это самостоятельный "графический компонент" на странице, таким образом нам лучше его "обернуть" в отдельный `<div class="carousel">` и уже модифицировать стили внутри него. diff --git a/2-ui/2-events/01-introduction-browser-events/07-carousel/solution.view/index.html b/2-ui/2-events/01-introduction-browser-events/07-carousel/solution.view/index.html index 2c60733165..bbd0507fe0 100644 --- a/2-ui/2-events/01-introduction-browser-events/07-carousel/solution.view/index.html +++ b/2-ui/2-events/01-introduction-browser-events/07-carousel/solution.view/index.html @@ -1,5 +1,5 @@ <!DOCTYPE html> - +<html> <head> <meta charset="utf-8"> <link rel="stylesheet" href="style.css"> @@ -10,24 +10,24 @@ <div id="carousel" class="carousel"> <button class="arrow prev">⇦</button> <div class="gallery"> - <ul class="images"> - <li><img src="https://en.js.cx/carousel/1.png"></li> - <li><img src="https://en.js.cx/carousel/2.png"></li> - <li><img src="https://en.js.cx/carousel/3.png"></li> - <li><img src="https://en.js.cx/carousel/4.png"></li> - <li><img src="https://en.js.cx/carousel/5.png"></li> - <li><img src="https://en.js.cx/carousel/6.png"></li> - <li><img src="https://en.js.cx/carousel/7.png"></li> - <li><img src="https://en.js.cx/carousel/8.png"></li> - <li><img src="https://en.js.cx/carousel/9.png"></li> - <li><img src="https://en.js.cx/carousel/10.png"></li> + <ul> + <li><img src="https://ru.js.cx/carousel/1.png"></li> + <li><img src="https://ru.js.cx/carousel/2.png"></li> + <li><img src="https://ru.js.cx/carousel/3.png"></li> + <li><img src="https://ru.js.cx/carousel/4.png"></li> + <li><img src="https://ru.js.cx/carousel/5.png"></li> + <li><img src="https://ru.js.cx/carousel/6.png"></li> + <li><img src="https://ru.js.cx/carousel/7.png"></li> + <li><img src="https://ru.js.cx/carousel/8.png"></li> + <li><img src="https://ru.js.cx/carousel/9.png"></li> + <li><img src="https://ru.js.cx/carousel/10.png"></li> </ul> </div> <button class="arrow next">⇨</button> </div> <script> - /* label the images, just for convenience, to visually track them */ + /* этот код помечает картинки, для удобства разработки */ let i = 1; for(let li of carousel.querySelectorAll('li')) { li.style.position = 'relative'; @@ -35,27 +35,27 @@ i++; } - /* configuration */ - let width = 130; // image width - let count = 3; // visible images count + /* конфигурация */ + let width = 130; // ширина картинки + let count = 3; // видимое количество изображений let list = carousel.querySelector('ul'); let listElems = carousel.querySelectorAll('li'); - let position = 0; // ribbon scroll position + let position = 0; // положение ленты прокрутки carousel.querySelector('.prev').onclick = function() { - // shift left + // сдвиг влево position += width * count; - // can't move to the left too much, end of images + // последнее передвижение влево может быть не на 3, а на 2 или 1 элемент position = Math.min(position, 0) list.style.marginLeft = position + 'px'; }; carousel.querySelector('.next').onclick = function() { - // shift right + // сдвиг вправо position -= width * count; - // can only shift the ribbbon for (total ribbon length - visible count) images + // последнее передвижение вправо может быть не на 3, а на 2 или 1 элемент position = Math.max(position, -width * (listElems.length - count)); list.style.marginLeft = position + 'px'; }; diff --git a/2-ui/2-events/01-introduction-browser-events/07-carousel/solution.view/style.css b/2-ui/2-events/01-introduction-browser-events/07-carousel/solution.view/style.css index bbbc6327e0..2c4d7cdcb6 100644 --- a/2-ui/2-events/01-introduction-browser-events/07-carousel/solution.view/style.css +++ b/2-ui/2-events/01-introduction-browser-events/07-carousel/solution.view/style.css @@ -14,7 +14,7 @@ body { .carousel img { width: 130px; height: 130px; - /* make it block to remove space around images */ + /* делаем этот элемент блочным, чтобы убрать лишнее пространство вокруг картинок */ display: block; } @@ -60,7 +60,7 @@ body { padding: 0; list-style: none; transition: margin-left 250ms; - /* remove white-space between inline-block'ed li */ + /* удаляем пустое пространство между элементами li, у которых установлен inline-block */ /* http://davidwalsh.name/remove-whitespace-inline-block */ font-size: 0; } diff --git a/2-ui/2-events/01-introduction-browser-events/07-carousel/source.view/index.html b/2-ui/2-events/01-introduction-browser-events/07-carousel/source.view/index.html index e9a9bc8d4f..12f0676f15 100644 --- a/2-ui/2-events/01-introduction-browser-events/07-carousel/source.view/index.html +++ b/2-ui/2-events/01-introduction-browser-events/07-carousel/source.view/index.html @@ -1,5 +1,5 @@ <!DOCTYPE html> - +<html> <head> <meta charset="utf-8"> <link rel="stylesheet" href="style.css"> @@ -7,37 +7,37 @@ <body> - <!-- create your markup and styles --> + <!-- создайте свою разметку со стилями --> <button class="arrow">⇦</button> <button class="arrow">⇨</button> <ul> - <li><img src="https://en.js.cx/carousel/1.png"></li> - <li><img src="https://en.js.cx/carousel/2.png"></li> - <li><img src="https://en.js.cx/carousel/3.png"></li> - <li><img src="https://en.js.cx/carousel/4.png"></li> - <li><img src="https://en.js.cx/carousel/5.png"></li> - <li><img src="https://en.js.cx/carousel/6.png"></li> - <li><img src="https://en.js.cx/carousel/7.png"></li> - <li><img src="https://en.js.cx/carousel/8.png"></li> - <li><img src="https://en.js.cx/carousel/9.png"></li> - <li><img src="https://en.js.cx/carousel/10.png"></li> + <li><img src="https://ru.js.cx/carousel/1.png"></li> + <li><img src="https://ru.js.cx/carousel/2.png"></li> + <li><img src="https://ru.js.cx/carousel/3.png"></li> + <li><img src="https://ru.js.cx/carousel/4.png"></li> + <li><img src="https://ru.js.cx/carousel/5.png"></li> + <li><img src="https://ru.js.cx/carousel/6.png"></li> + <li><img src="https://ru.js.cx/carousel/7.png"></li> + <li><img src="https://ru.js.cx/carousel/8.png"></li> + <li><img src="https://ru.js.cx/carousel/9.png"></li> + <li><img src="https://ru.js.cx/carousel/10.png"></li> </ul> <script> - // label the images to visually track them, just for convenience, - // this code can be removed + // отметить картинки для удобства разработки + // этот код может быть удалён по вашему усмотрению let i = 1; - for(let li of carousel.querySelectorAll('li')) { + for(let li of document.querySelectorAll('li')) { li.style.position = 'relative'; li.insertAdjacentHTML('beforeend', `<span style="position:absolute;left:0;top:0">${i}</span>`); i++; } - // ...your code to make carousel alive! + // ...ваш код, который сделает карусель рабочей </script> </body> diff --git a/2-ui/2-events/01-introduction-browser-events/07-carousel/source.view/style.css b/2-ui/2-events/01-introduction-browser-events/07-carousel/source.view/style.css index 75c68f01ab..adb84a82b7 100644 --- a/2-ui/2-events/01-introduction-browser-events/07-carousel/source.view/style.css +++ b/2-ui/2-events/01-introduction-browser-events/07-carousel/source.view/style.css @@ -30,9 +30,9 @@ ul { ul img { width: 130px; height: 130px; - display: block; /* removes extra space near images */ + display: block; /* убираем пустое место вокруг картинок */ } ul li { - display: inline-block; /* removes extra space between list items + display: inline-block; /* убираем пустое место между элементами списка */ } diff --git a/2-ui/2-events/01-introduction-browser-events/07-carousel/task.md b/2-ui/2-events/01-introduction-browser-events/07-carousel/task.md index a6adbffa5d..08de940a0a 100644 --- a/2-ui/2-events/01-introduction-browser-events/07-carousel/task.md +++ b/2-ui/2-events/01-introduction-browser-events/07-carousel/task.md @@ -2,12 +2,12 @@ importance: 4 --- -# Carousel +# Карусель -Create a "carousel" -- a ribbon of images that can be scrolled by clicking on arrows. +Создайте «Карусель» –- ленту изображений, которую можно листать влево-вправо нажатием на стрелочки. [iframe height=200 src="solution"] -Later we can add more features to it: infinite scrolling, dynamic loading etc. +В дальнейшем к ней можно будет добавить анимацию, динамическую подгрузку и другие возможности. -P.S. For this task HTML/CSS structure is actually 90% of the solution. +P.S. В этой задаче разработка структуры HTML/CSS составляет 90% решения. diff --git a/2-ui/2-events/01-introduction-browser-events/article.md b/2-ui/2-events/01-introduction-browser-events/article.md index f169e3de7b..2badd2e9c7 100644 --- a/2-ui/2-events/01-introduction-browser-events/article.md +++ b/2-ui/2-events/01-introduction-browser-events/article.md @@ -1,8 +1,8 @@ # Введение в браузерные события -*Событие* - это сигнал от браузера о том, что что-то произошло. Все DOM объекты создают такие сигналы (хотя события бывают и не только на DOM). +*Событие* - это сигнал от браузера о том, что что-то произошло. Все DOM-узлы подают такие сигналы (хотя события бывают и не только в DOM). -Посмотрим список самых часто используемых DOM-событий, пока просто для ознакомления: +Вот список самых часто используемых DOM-событий, пока просто для ознакомления: **События мыши:** - `click` -- происходит, когда кликнули на элемент левой кнопкой мыши (на устройствах с сенсорными экранами оно происходит при касании). @@ -24,7 +24,7 @@ **CSS events:** - `transitionend` -- когда CSS-анимация завершена. -Существует множество других событий. Мы расскажем о них более подробно в следующих главах. +Существует множество других событий. Мы подробно разберём их в последующих главах. ## Обработчики событий @@ -38,7 +38,7 @@ Обработчик может быть назначен прямо в разметке, в атрибуте, который называется `on<событие>`. -Например, чтобы назначить обработчик события `click` на элементе`input`, можно использовать атрибут `onclick`, вот так: +Например, чтобы назначить обработчик события `click` на элементе `input`, можно использовать атрибут `onclick`, вот так: ```html run <input value="Нажми меня" *!*onclick="alert('Клик!')"*/!* type="button"> @@ -68,7 +68,7 @@ ### Использование свойства DOM-объекта -Можно назначать обработчик, используя свойство DOM-элемента `on<event>`. +Можно назначать обработчик, используя свойство DOM-элемента `on<событие>`. К примеру, `elem.onclick`: @@ -94,7 +94,7 @@ 1. Только HTML: ```html autorun height=50 - <input type="button" *!*onclick="alert('Click!')"*/!* value="Кнопка"> + <input type="button" *!*onclick="alert('Клик!')"*/!* value="Кнопка"> ``` 2. HTML + JS: @@ -114,11 +114,11 @@ В примере ниже назначение через JavaScript перезапишет обработчик из атрибута: ```html run height=50 autorun -<input type="button" id="elem" onclick="alert('Before')" value="Нажми меня"> +<input type="button" id="elem" onclick="alert('Было')" value="Нажми меня"> <script> *!* elem.onclick = function() { // перезапишет существующий обработчик - alert('After'); // выведется только это + alert('Станет'); // выведется только это }; */!* </script> @@ -140,7 +140,7 @@ elem.onclick = sayThanks; Внутри обработчика события `this` ссылается на текущий элемент, то есть на тот, на котором, как говорят, "висит" (т.е. назначен) обработчик. -В коде ниже `button` выводит свое содержимое, используя `this.innerHTML`: +В коде ниже `button` выводит своё содержимое, используя `this.innerHTML`: ```html height=50 autorun <button onclick="alert(this.innerHTML)">Нажми меня</button> @@ -148,7 +148,7 @@ elem.onclick = sayThanks; ## Частые ошибки -Если вы только начинаете работать с событиями –- обратите внимание на следующие моменты. +Если вы только начинаете работать с событиями, обратите внимание на следующие моменты. **Функция должна быть присвоена как `sayThanks`, а не `sayThanks()`.** @@ -162,15 +162,15 @@ button.onclick = sayThanks(); Если добавить скобки, то `sayThanks()` -- это уже вызов функции, результат которого (равный `undefined`, так как функция ничего не возвращает) будет присвоен `onclick`. Так что это не будет работать. -...А вот в разметке как раз скобки нужны: +...А вот в разметке, в отличие от свойства, скобки нужны: ```html <input type="button" id="button" onclick="sayThanks()"> ``` -Это различие просто объяснить. При создании обработчика браузером из атрибута, он автоматически создает функцию с телом из значения атрибута. +Это различие просто объяснить. При создании обработчика браузером из атрибута, он автоматически создаёт функцию с *телом из значения атрибута*: `sayThanks()`. -Поэтому последний пример – фактически то же самое, что: +Так что разметка генерирует такое свойство: ```js button.onclick = function() { *!* @@ -199,11 +199,11 @@ document.body.setAttribute('onclick', function() { alert(1) }); ## addEventListener -Фундаментальный недостаток описанных выше способов назначения обработчика –- невозможность повесить несколько обработчиков на одно событие. +Фундаментальный недостаток описанных выше способов назначения обработчика – невозможность повесить несколько обработчиков на одно событие. -Например, одна часть кода хочет при клике на кнопку делать ее подсвеченной, а другая – выдавать сообщение. +Например, одна часть кода хочет при клике на кнопку делать её подсвеченной, а другая – выдавать сообщение. -Мы хотим назначить два обработчика для этого. Но новое DOM-свойство перетрет предыдущее: +Мы хотим назначить два обработчика для этого. Но новое DOM-свойство перезапишет предыдущее: ```js no-beautify input.onclick = function() { alert(1); } @@ -216,7 +216,7 @@ input.onclick = function() { alert(2); } // заменит предыдущий Синтаксис добавления обработчика: ```js -element.addEventListener(event, handler[, options]); +element.addEventListener(event, handler, [options]); ``` `event` @@ -227,15 +227,15 @@ element.addEventListener(event, handler[, options]); `options` : Дополнительный объект со свойствами: - - `once`: если `true`, тогда обработчик будет автоматически удален после выполнения. - - `capture`: фаза, на которой должен сработать обработчик, подробнее об этом будет рассказано в главе <info:bubbling-and-capturing>. Так исторически сложилось, что `options` может быть `false/true`, это тоже самое, что `{capture: false/true}`. + - `once`: если `true`, тогда обработчик будет автоматически удалён после выполнения. + - `capture`: фаза, на которой должен сработать обработчик, подробнее об этом будет рассказано в главе <info:bubbling-and-capturing>. Так исторически сложилось, что `options` может быть `false/true`, это то же самое, что `{capture: false/true}`. - `passive`: если `true`, то указывает, что обработчик никогда не вызовет `preventDefault()`, подробнее об этом будет рассказано в главе <info:default-browser-action>. Для удаления обработчика следует использовать `removeEventListener`: ```js -element.removeEventListener(event, handler[, options]); +element.removeEventListener(event, handler, [options]); ``` ````warn header="Удаление требует именно ту же функцию" @@ -249,7 +249,7 @@ elem.addEventListener( "click" , () => alert('Спасибо!')); elem.removeEventListener( "click", () => alert('Спасибо!')); ``` -Обработчик не будет удален, т.к. в `removeEventListener` передана не та же функция, а другая, с одинаковым кодом, но это не важно. +Обработчик не будет удалён, т.к. в `removeEventListener` передана не та же функция, а другая, с одинаковым кодом, но это не важно. Вот так правильно: @@ -290,44 +290,28 @@ input.removeEventListener("click", handler); Как видно из примера выше, можно одновременно назначать обработчики и через DOM-свойство и через `addEventListener`. Однако, во избежание путаницы, рекомендуется выбрать один способ. -````warn header="Для некоторых событий, обработчики можно назначать только через `addEventListener`" +````warn header="Обработчики некоторых событий можно назначать только через `addEventListener`" Существуют события, которые нельзя назначить через DOM-свойство, но можно через `addEventListener`. -Например, таково событие `transitionend`, то есть окончание CSS-анимации. +Например, таково событие `DOMContentLoaded`, которое срабатывает, когда завершена загрузка и построение DOM документа. -Вы можете проверить это, запустив код в примере ниже. В большинстве браузеров, сработает лишь второй обработчик, но не первый. - -```html run -<style> - input { - transition: width 1s; - width: 100px; - } - - .wide { - width: 300px; - } -</style> - -<input type="button" id="elem" onclick="this.classList.toggle('wide')" value="Нажми меня"> - -<script> - elem.ontransitionend = function() { - alert("DOM property"); // не сработает - }; +```js +document.onDOMContentLoaded = function() { + alert("DOM построен"); // не будет работать +}; +``` -*!* - elem.addEventListener("transitionend", function() { - alert("addEventListener"); // сработает по окончании анимации - }); -*/!* -</script> +```js +document.addEventListener("DOMContentLoaded", function() { + alert("DOM построен"); // а вот так сработает +}); ``` +Так что `addEventListener` более универсален. Хотя заметим, что таких событий меньшинство, это скорее исключение, чем правило. ```` ## Объект события -Чтобы хорошо обработать событие, могут понадобиться детали того, что произошло. Не просто "клик" или "нажатие клавиши", а также -- какие координаты курсора, какая клавиша нажата и так далее. +Чтобы хорошо обработать событие, могут понадобиться детали того, что произошло. Не просто "клик" или "нажатие клавиши", а также -- какие координаты указателя мыши, какая клавиша нажата и так далее. Когда происходит событие, браузер создаёт *объект события*, записывает в него детали и передаёт его в качестве аргумента функции-обработчику. @@ -339,8 +323,8 @@ input.removeEventListener("click", handler); <script> elem.onclick = function(*!*event*/!*) { // вывести тип события, элемент и координаты клика - alert(event.type + " at " + event.currentTarget); - alert("Coordinates: " + event.clientX + ":" + event.clientY); + alert(event.type + " на " + event.currentTarget); + alert("Координаты: " + event.clientX + ":" + event.clientY); }; </script> ``` @@ -351,7 +335,7 @@ input.removeEventListener("click", handler); : Тип события, в данном случае `"click"`. `event.currentTarget` -: Элемент, на котором сработал обработчик. Значение -- обычно такое же, как и у `this`, но если обработчик является функцией-стрелкой или при помощи `bind` привязан другой объект в качестве `this`, то мы можем использовать `event.currentTarget`. +: Элемент, на котором сработал обработчик. Значение -- обычно такое же, как и у `this`, но если обработчик является функцией-стрелкой или при помощи `bind` привязан другой объект в качестве `this`, то мы можем получить элемент из `event.currentTarget`. `event.clientX / event.clientY` : Координаты курсора в момент клика относительно окна, для событий мыши. @@ -371,7 +355,7 @@ input.removeEventListener("click", handler); ## Объект-обработчик: handleEvent -Мы можем назначить объект как обработчик события используя `addEventListener`. Когда происходит события, вызывается его метод `handleEvent`. +Мы можем назначить обработчиком не только функцию, но и объект при помощи `addEventListener`. В этом случае, когда происходит событие, вызывается метод объекта `handleEvent`. К примеру: @@ -388,7 +372,7 @@ input.removeEventListener("click", handler); </script> ``` -Другими словами, в случае если `addEventListener` получает объект в качестве обработчика, он вызывает `object.handleEvent(event)`, когда происходит событие. +Как видим, если `addEventListener` получает объект в качестве обработчика, он вызывает `object.handleEvent(event)`, когда происходит событие. Мы также можем использовать класс для этого: @@ -430,7 +414,7 @@ input.removeEventListener("click", handler); handleEvent(event) { // mousedown -> onMousedown let method = 'on' + event.type[0].toUpperCase() + event.type.slice(1); - this[method](event); + this[method](); } onMousedown() { @@ -462,7 +446,7 @@ HTML-атрибуты используются редко потому, что J DOM-свойства вполне можно использовать, но мы не можем назначить больше одного обработчика на один тип события. Во многих случаях с этим ограничением можно мириться. -Последний способ самый гибкий, однако нужно писать больше всего кода. Есть несколько типов событий, которые работают только через него, к примеру `transtionend` и `DOMContentLoaded`. Также `addEventListener` поддерживает объекты в качестве обработчиков событий. В этом случае метод `handleEvent` вызывается, когда происходит событие. +Последний способ самый гибкий, однако нужно писать больше всего кода. Есть несколько типов событий, которые работают только через него, например, `DOMContentLoaded`. Также `addEventListener` поддерживает объекты в качестве обработчиков событий. В этом случае вызывается метод объекта `handleEvent`. Не важно, как вы назначаете обработчик -- он получает объект события первым аргументом. Этот объект содержит подробности о том, что произошло. diff --git a/2-ui/2-events/02-bubbling-and-capturing/article.md b/2-ui/2-events/02-bubbling-and-capturing/article.md index 82ec8a84ae..f4328b8409 100644 --- a/2-ui/2-events/02-bubbling-and-capturing/article.md +++ b/2-ui/2-events/02-bubbling-and-capturing/article.md @@ -1,24 +1,24 @@ -# Bubbling and capturing +# Всплытие и погружение -Let's start with an example. +Давайте начнём с примера. -This handler is assigned to `<div>`, but also runs if you click any nested tag like `<em>` or `<code>`: +Этот обработчик для `<div>` сработает, если вы кликните по любому из вложенных тегов, будь то `<em>` или `<code>`: ```html autorun height=60 -<div onclick="alert('The handler!')"> - <em>If you click on <code>EM</code>, the handler on <code>DIV</code> runs.</em> +<div onclick="alert('Обработчик!')"> + <em>Если вы кликните на <code>EM</code>, сработает обработчик на <code>DIV</code></em> </div> ``` -Isn't it a bit strange? Why does the handler on `<div>` run if the actual click was on `<em>`? +Вам не кажется это странным? Почему же сработал обработчик на `<div>`, если клик произошёл на `<em>`? -## Bubbling +## Всплытие -The bubbling principle is simple. +Принцип всплытия очень простой. -**When an event happens on an element, it first runs the handlers on it, then on its parent, then all the way up on other ancestors.** +**Когда на элементе происходит событие, обработчики сначала срабатывают на нём, потом на его родителе, затем выше и так далее, вверх по цепочке предков.** -Let's say we have 3 nested elements `FORM > DIV > P` with a handler on each of them: +Например, есть 3 вложенных элемента `FORM > DIV > P` с обработчиком на каждом: ```html run autorun <style> @@ -35,125 +35,123 @@ Let's say we have 3 nested elements `FORM > DIV > P` with a handler on each of t </form> ``` -A click on the inner `<p>` first runs `onclick`: -1. On that `<p>`. -2. Then on the outer `<div>`. -3. Then on the outer `<form>`. -4. And so on upwards till the `document` object. +Клик по внутреннему `<p>` вызовет обработчик `onclick`: +1. Сначала на самом `<p>`. +2. Потом на внешнем `<div>`. +3. Затем на внешнем `<form>`. +4. И так далее вверх по цепочке до самого `document`. -![](event-order-bubbling.png) +![](event-order-bubbling.svg) -So if we click on `<p>`, then we'll see 3 alerts: `p` -> `div` -> `form`. +Поэтому если кликнуть на `<p>`, то мы увидим три оповещения: `p` -> `div` -> `form`. -The process is called "bubbling", because events "bubble" from the inner element up through parents like a bubble in the water. +Этот процесс называется "всплытием", потому что события "всплывают" от внутреннего элемента вверх через родителей подобно тому, как всплывает пузырёк воздуха в воде. -```warn header="*Almost* all events bubble." -The key word in this phrase is "almost". +```warn header="*Почти* все события всплывают." +Ключевое слово в этой фразе -- "почти". -For instance, a `focus` event does not bubble. There are other examples too, we'll meet them. But still it's an exception, rather than a rule, most events do bubble. +Например, событие `focus` не всплывает. В дальнейшем мы увидим и другие примеры. Однако, стоит понимать, что это скорее исключение, чем правило, всё-таки большинство событий всплывают. ``` ## event.target -A handler on a parent element can always get the details about where it actually happened. +Всегда можно узнать, на каком конкретно элементе произошло событие. -**The most deeply nested element that caused the event is called a *target* element, accessible as `event.target`.** +**Самый глубокий элемент, который вызывает событие, называется *целевым* элементом, и он доступен через `event.target`.** -Note the differences from `this` (=`event.currentTarget`): +Отличия от `this` (=`event.currentTarget`): -- `event.target` -- is the "target" element that initiated the event, it doesn't change through the bubbling process. -- `this` -- is the "current" element, the one that has a currently running handler on it. +- `event.target` -- это "целевой" элемент, на котором произошло событие, в процессе всплытия он неизменен. +- `this` -- это "текущий" элемент, до которого дошло всплытие, на нём сейчас выполняется обработчик. -For instance, if we have a single handler `form.onclick`, then it can "catch" all clicks inside the form. No matter where the click happened, it bubbles up to `<form>` and runs the handler. +Например, если стоит только один обработчик `form.onclick`, то он "поймает" все клики внутри формы. Где бы ни был клик внутри -- он всплывёт до элемента `<form>`, на котором сработает обработчик. -In `form.onclick` handler: +При этом внутри обработчика `form.onclick`: -- `this` (`=event.currentTarget`) is the `<form>` element, because the handler runs on it. -- `event.target` is the concrete element inside the form that actually was clicked. +- `this` (=`event.currentTarget`) всегда будет элемент `<form>`, так как обработчик сработал на ней. +- `event.target` будет содержать ссылку на конкретный элемент внутри формы, на котором произошёл клик. -Check it out: +Попробуйте сами: [codetabs height=220 src="bubble-target"] -It's possible that `event.target` equals `this` -- when the click is made directly on the `<form>` element. +Возможна и ситуация, когда `event.target` и `this` -- один и тот же элемент, например, если клик был непосредственно на самом элементе `<form>`, а не на его подэлементе. -## Stopping bubbling +## Прекращение всплытия -A bubbling event goes from the target element straight up. Normally it goes upwards till `<html>`, and then to `document` object, and some events even reach `window`, calling all handlers on the path. +Всплытие идёт с "целевого" элемента прямо наверх. По умолчанию событие будет всплывать до элемента `<html>`, а затем до объекта `document`, а иногда даже до `window`, вызывая все обработчики на своём пути. -But any handler may decide that the event has been fully processed and stop the bubbling. +Но любой промежуточный обработчик может решить, что событие полностью обработано, и остановить всплытие. -The method for it is `event.stopPropagation()`. +Для этого нужно вызвать метод `event.stopPropagation()`. -For instance, here `body.onclick` doesn't work if you click on `<button>`: +Например, здесь при клике на кнопку `<button>` обработчик `body.onclick` не сработает: ```html run autorun height=60 -<body onclick="alert(`the bubbling doesn't reach here`)"> - <button onclick="event.stopPropagation()">Click me</button> +<body onclick="alert(`сюда всплытие не дойдёт`)"> + <button onclick="event.stopPropagation()">Кликни меня</button> </body> ``` ```smart header="event.stopImmediatePropagation()" -If an element has multiple event handlers on a single event, then even if one of them stops the bubbling, the other ones still execute. +Если у элемента есть несколько обработчиков на одно событие, то даже при прекращении всплытия все они будут выполнены. -In other words, `event.stopPropagation()` stops the move upwards, but on the current element all other handlers will run. +То есть, `event.stopPropagation()` препятствует продвижению события дальше, но на текущем элементе все обработчики будут вызваны. -To stop the bubbling and prevent handlers on the current element from running, there's a method `event.stopImmediatePropagation()`. After it no other handlers execute. +Для того, чтобы полностью остановить обработку, существует метод `event.stopImmediatePropagation()`. Он не только предотвращает всплытие, но и останавливает обработку событий на текущем элементе. ``` -```warn header="Don't stop bubbling without a need!" -Bubbling is convenient. Don't stop it without a real need: obvious and architecturally well-thought. +```warn header="Не прекращайте всплытие без необходимости!" +Всплытие -- это удобно. Не прекращайте его без явной нужды, очевидной и архитектурно прозрачной. -Sometimes `event.stopPropagation()` creates hidden pitfalls that later may become problems. +Зачастую прекращение всплытия через `event.stopPropagation()` имеет свои подводные камни, которые со временем могут стать проблемами. -For instance: +Например: -1. We create a nested menu. Each submenu handles clicks on its elements and calls `stopPropagation` so that the outer menu won't trigger. -2. Later we decide to catch clicks on the whole window, to track users' behavior (where people click). Some analytic systems do that. Usually the code uses `document.addEventListener('click'…)` to catch all clicks. -3. Our analytic won't work over the area where clicks are stopped by `stopPropagation`. We've got a "dead zone". +1. Мы делаем вложенное меню. Каждое подменю обрабатывает клики на своих элементах и делает для них `stopPropagation`, чтобы не срабатывало внешнее меню. +2. Позже мы решили отслеживать все клики в окне для какой-то своей функциональности, к примеру, для статистики – где вообще у нас кликают люди. Некоторые системы аналитики так делают. Обычно используют `document.addEventListener('click'…)`, чтобы отлавливать все клики. +3. Наша аналитика не будет работать над областью, где клики прекращаются `stopPropagation`. Увы, получилась "мёртвая зона". -There's usually no real need to prevent the bubbling. A task that seemingly requires that may be solved by other means. One of them is to use custom events, we'll cover them later. Also we can write our data into the `event` object in one handler and read it in another one, so we can pass to handlers on parents information about the processing below. +Зачастую нет никакой необходимости прекращать всплытие. Задача, которая, казалось бы, требует этого, может быть решена иначе. Например, с помощью создания своего уникального события, о том, как это делать, мы поговорим позже. Также мы можем записывать какую-то служебную информацию в объект `event` в одном обработчике, а читать в другом, таким образом мы можем сообщить обработчикам на родительских элементах информацию о том, что событие уже было как-то обработано. ``` -## Capturing +## Погружение -There's another phase of event processing called "capturing". It is rarely used in real code, but sometimes can be useful. +Существует ещё одна фаза из жизненного цикла события -- "погружение" (иногда её называют "перехват"). Она очень редко используется в реальном коде, однако тоже может быть полезной. -The standard [DOM Events](http://www.w3.org/TR/DOM-Level-3-Events/) describes 3 phases of event propagation: +Стандарт [DOM Events](https://www.w3.org/TR/DOM-Level-3-Events/) описывает 3 фазы прохода события: -1. Capturing phase -- the event goes down to the element. -2. Target phase -- the event reached the target element. -3. Bubbling phase -- the event bubbles up from the element. +1. Фаза погружения (capturing phase) -- событие сначала идёт сверху вниз. +2. Фаза цели (target phase) -- событие достигло целевого(исходного) элемента. +3. Фаза всплытия (bubbling stage) -- событие начинает всплывать. -Here's the picture of a click on `<td>` inside a table, taken from the specification: +Картинка из спецификации демонстрирует, как это работает при клике по ячейке `<td>`, расположенной внутри таблицы: -![](eventflow.png) +![](eventflow.svg) -That is: for a click on `<td>` the event first goes through the ancestors chain down to the element (capturing), then it reaches the target, and then it goes up (bubbles), calling handlers on its way. +То есть при клике на `<td>` событие путешествует по цепочке родителей сначала вниз к элементу (погружается), затем оно достигает целевой элемент (фаза цели), а потом идёт наверх (всплытие), вызывая по пути обработчики. -**Before we only talked about bubbling, because the capturing phase is rarely used. Normally it is invisible to us.** +**Ранее мы говорили только о всплытии, потому что другие стадии, как правило, не используются и проходят незаметно для нас.** -Handlers added using `on<event>`-property or using HTML attributes or using `addEventListener(event, handler)` don't know anything about capturing, they only run on the 2nd and 3rd phases. +Обработчики, добавленные через `on<event>`-свойство или через HTML-атрибуты, или через `addEventListener(event, handler)` с двумя аргументами, ничего не знают о фазе погружения, а работают только на 2-ой и 3-ей фазах. -To catch an event on the capturing phase, we need to set the handler `capture` option to `true`: +Чтобы поймать событие на стадии погружения, нужно использовать третий аргумент `capture` вот так: ```js elem.addEventListener(..., {capture: true}) -// or, just "true" is an alias to {capture: true} +// или просто "true", как сокращение для {capture: true} elem.addEventListener(..., true) ``` -There are two possible values of the `capture` option: +Существуют два варианта значений опции `capture`: -- If it's `false` (default), then the handler is set on the bubbling phase. -- If it's `true`, then the handler is set on the capturing phase. +- Если аргумент `false` (по умолчанию), то событие будет поймано при всплытии. +- Если аргумент `true`, то событие будет перехвачено при погружении. -Note that while formally there are 3 phases, the 2nd phase ("target phase": the event reached the element) is not handled separately: handlers on both capturing and bubbling phases trigger at that phase. +Обратите внимание, что хоть и формально существует 3 фазы, 2-ую фазу ("фазу цели": событие достигло элемента) нельзя обработать отдельно, при её достижении вызываются все обработчики: и на всплытие, и на погружение. -If one puts capturing and bubbling handlers on the target element, the capture handler triggers last in the capturing phase and the bubble handler triggers first in the bubbling phase. - -Let's see it in action: +Давайте посмотрим и всплытие и погружение в действии: ```html run autorun height=140 edit <style> @@ -171,47 +169,55 @@ Let's see it in action: <script> for(let elem of document.querySelectorAll('*')) { - elem.addEventListener("click", e => alert(`Capturing: ${elem.tagName}`), true); - elem.addEventListener("click", e => alert(`Bubbling: ${elem.tagName}`)); + elem.addEventListener("click", e => alert(`Погружение: ${elem.tagName}`), true); + elem.addEventListener("click", e => alert(`Всплытие: ${elem.tagName}`)); } </script> ``` -The code sets click handlers on *every* element in the document to see which ones are working. +Здесь обработчики навешиваются на *каждый* элемент в документе, чтобы увидеть в каком порядке они вызываются по мере прохода события. + +Если вы кликните по `<p>`, то последовательность следующая: -If you click on `<p>`, then the sequence is: +1. `HTML` -> `BODY` -> `FORM` -> `DIV` (фаза погружения, первый обработчик) +2. `P` (фаза цели, срабатывают обработчики, установленные и на погружение и на всплытие, так что выведется два раза) +3. `DIV` -> `FORM` -> `BODY` -> `HTML` (фаза всплытия, второй обработчик) -1. `HTML` -> `BODY` -> `FORM` -> `DIV` -> `P` (capturing phase, the first listener), and then: -2. `P` -> `DIV` -> `FORM` -> `BODY` -> `HTML` (bubbling phase, the second listener). +Существует свойство `event.eventPhase`, содержащее номер фазы, на которой событие было поймано. Но оно используется редко, мы обычно и так знаем об этом в обработчике. -Please note that `P` shows up two times: at the end of capturing and at the start of bubbling. +```smart header="Чтобы убрать обработчик `removeEventListener`, нужна та же фаза" +Если мы добавили обработчик вот так `addEventListener(..., true)`, то мы должны передать то же значение аргумента `capture` в `removeEventListener(..., true)`, когда снимаем обработчик. +``` -There's a property `event.eventPhase` that tells us the number of the phase on which the event was caught. But it's rarely used, because we usually know it in the handler. +````smart header="На каждой фазе разные обработчики на одном элементе срабатывают в порядке назначения" +Если у нас несколько обработчиков одного события, назначенных `addEventListener` на один элемент, в рамках одной фазы, то их порядок срабатывания - тот же, в котором они установлены: -```smart header="To remove the handler, `removeEventListener` needs the same phase" -If we `addEventListener(..., true)`, then we should mention the same phase in `removeEventListener(..., true)` to correctly remove the handler. +```js +elem.addEventListener("click", e => alert(1)); // всегда сработает перед следующим +elem.addEventListener("click", e => alert(2)); ``` +```` -## Summary +## Итого -The event handling process: +При наступлении события - самый глубоко вложенный элемент, на котором оно произошло, помечается как "целевой" (`event.target`). -- When an event happens -- the most nested element where it happens gets labeled as the "target element" (`event.target`). -- Then the event first moves from the document root down to the `event.target`, calling handlers assigned with `addEventListener(...., true)` on the way (`true` is a shorthand for `{capture: true}`). -- Then the event moves from `event.target` up to the root, calling handlers assigned using `on<event>` and `addEventListener` without the 3rd argument or with the 3rd argument `false`. +- Затем событие сначала двигается вниз от корня документа к `event.target`, по пути вызывая обработчики, поставленные через `addEventListener(...., true)`, где `true` -- это сокращение для `{capture: true}`. +- Далее обработчики вызываются на целевом элементе. +- Далее событие двигается от `event.target` вверх к корню документа, по пути вызывая обработчики, поставленные через `on<event>` и `addEventListener` без третьего аргумента или с третьим аргументом равным `false`. -Each handler can access `event` object properties: +Каждый обработчик имеет доступ к свойствам события `event`: -- `event.target` -- the deepest element that originated the event. -- `event.currentTarget` (=`this`) -- the current element that handles the event (the one that has the handler on it) -- `event.eventPhase` -- the current phase (capturing=1, bubbling=3). +- `event.target` -- самый глубокий элемент, на котором произошло событие. +- `event.currentTarget` (=`this`) -- элемент, на котором в данный момент сработал обработчик (тот, на котором "висит" конкретный обработчик) +- `event.eventPhase` -- на какой фазе он сработал (погружение=1, фаза цели=2, всплытие=3). -Any event handler can stop the event by calling `event.stopPropagation()`, but that's not recommended, because we can't really be sure we won't need it above, maybe for completely different things. +Любой обработчик может остановить событие вызовом `event.stopPropagation()`, но делать это не рекомендуется, так как в дальнейшем это событие может понадобиться, иногда для самых неожиданных вещей. -The capturing phase is used very rarely, usually we handle events on bubbling. And there's a logic behind that. +В современной разработке стадия погружения используется очень редко, обычно события обрабатываются во время всплытия. И в этом есть логика. -In real world, when an accident happens, local authorities react first. They know best the area where it happened. Then higher-level authorities if needed. +В реальном мире, когда происходит чрезвычайная ситуация, местные службы реагируют первыми. Они знают лучше всех местность, в которой это произошло, и другие детали. Вышестоящие инстанции подключаются уже после этого и при необходимости. -The same for event handlers. The code that set the handler on a particular element knows maximum details about the element and what it does. A handler on a particular `<td>` may be suited for that exactly `<td>`, it knows everything about it, so it should get the chance first. Then its immediate parent also knows about the context, but a little bit less, and so on till the very top element that handles general concepts and runs the last. +Тоже самое справедливо для обработчиков событий. Код, который "навесил" обработчик на конкретный элемент, знает максимум деталей об элементе и его предназначении. Например, обработчик на определённом `<td>` скорее всего подходит только для этого конкретного `<td>`, он знает все о нём, поэтому он должен отработать первым. Далее имеет смысл передать обработку события родителю -- он тоже понимает, что происходит, но уже менее детально, далее – выше, и так далее, до самого объекта `document`, обработчик на котором реализовывает самую общую функциональность уровня документа. -Bubbling and capturing lay the foundation for "event delegation" -- an extremely powerful event handling pattern that we study in the next chapter. +Всплытие и погружение являются основой для "делегирования событий" -- очень мощного приёма обработки событий. Его мы изучим в следующей главе. diff --git a/2-ui/2-events/02-bubbling-and-capturing/bubble-target.view/index.html b/2-ui/2-events/02-bubbling-and-capturing/bubble-target.view/index.html index 8313ec29fe..9dabcad90f 100644 --- a/2-ui/2-events/02-bubbling-and-capturing/bubble-target.view/index.html +++ b/2-ui/2-events/02-bubbling-and-capturing/bubble-target.view/index.html @@ -7,7 +7,7 @@ </head> <body> - A click shows both <code>event.target</code> and <code>this</code> to compare: + Клик покажет оба: и <code>event.target</code>, и <code>this</code> для сравнения: <form id="form">FORM <div>DIV diff --git a/2-ui/2-events/02-bubbling-and-capturing/bubble-target.view/script.js b/2-ui/2-events/02-bubbling-and-capturing/bubble-target.view/script.js index b1353712fa..45d3459fe7 100644 --- a/2-ui/2-events/02-bubbling-and-capturing/bubble-target.view/script.js +++ b/2-ui/2-events/02-bubbling-and-capturing/bubble-target.view/script.js @@ -2,7 +2,7 @@ form.onclick = function(event) { event.target.style.backgroundColor = 'yellow'; - // chrome needs some time to paint yellow + // браузеру нужно некоторое время, чтобы зарисовать всё жёлтым setTimeout(() => { alert("target = " + event.target.tagName + ", this=" + this.tagName); event.target.style.backgroundColor = '' diff --git a/2-ui/2-events/02-bubbling-and-capturing/event-order-bubbling.png b/2-ui/2-events/02-bubbling-and-capturing/event-order-bubbling.png deleted file mode 100644 index 9de1f20483..0000000000 Binary files a/2-ui/2-events/02-bubbling-and-capturing/event-order-bubbling.png and /dev/null differ diff --git a/2-ui/2-events/02-bubbling-and-capturing/event-order-bubbling.svg b/2-ui/2-events/02-bubbling-and-capturing/event-order-bubbling.svg new file mode 100644 index 0000000000..fd2a13f7bc --- /dev/null +++ b/2-ui/2-events/02-bubbling-and-capturing/event-order-bubbling.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="353" height="216" viewBox="0 0 353 216"><defs><style>@import url(https://fonts.googleapis.com/css?family=Open+Sans:bold,italic,bolditalic%7CPT+Mono);@font-face{font-family:'PT Mono';font-weight:700;font-style:normal;src:local('PT MonoBold'),url(/font/PTMonoBold.woff2) format('woff2'),url(/font/PTMonoBold.woff) format('woff'),url(/font/PTMonoBold.ttf) format('truetype')}</style></defs><g id="dom" fill="none" fill-rule="evenodd" stroke="none" stroke-width="1"><g id="event-order-bubbling.svg"><path id="Rectangle-210" fill="#DBAF88" d="M159.488 140L174 186H60l14.512-46z"/><path id="Rectangle-209" stroke="#91C2A3" stroke-width="18" d="M173.634 81l16.09 51H43.276l16.09-51h114.267z"/><path id="Rectangle-208" stroke="#EFA39F" stroke-width="18" d="M193.014 29l22.71 72H17.276l22.71-72h153.028z"/><path id="Fill-46" fill="#166388" d="M121.5 141v13.816a4.5 4.5 0 11-9 0V141h9zm0-31v13h-9v-13h9zM117 20.53a4.471 4.471 0 013.362 1.3l17.64 17.64a4.5 4.5 0 01-6.364 6.364L121.5 35.698V92h-9V35.7l-10.138 10.136a4.5 4.5 0 11-6.363-6.364l17.639-17.64a4.477 4.477 0 013.363-1.3z"/><text id="1" fill="#643B0C" font-family="PTMono-Bold, PT Mono" font-size="14" font-weight="bold"><tspan x="210" y="105">1</tspan></text><text id="2" fill="#643B0C" font-family="PTMono-Bold, PT Mono" font-size="14" font-weight="bold"><tspan x="185" y="136">2</tspan></text><text id="3" fill="#C06334" font-family="PTMono-Bold, PT Mono" font-size="14" font-weight="bold"><tspan x="157" y="181">3</tspan></text><text id="Most-deeply-nested-e" fill="#AF6E24" font-family="OpenSans-Bold, Open Sans" font-size="14" font-weight="bold"><tspan x="218.48" y="175">Самый глубоко</tspan> <tspan x="199.306" y="194">вложенный элемент</tspan></text><path id="Line-30" stroke="#C06334" stroke-dasharray="3,6" stroke-linecap="square" stroke-width="2" d="M179.5 177.5h30"/></g></g></svg> \ No newline at end of file diff --git a/2-ui/2-events/02-bubbling-and-capturing/event-order-bubbling@2x.png b/2-ui/2-events/02-bubbling-and-capturing/event-order-bubbling@2x.png deleted file mode 100644 index 81bb24c703..0000000000 Binary files a/2-ui/2-events/02-bubbling-and-capturing/event-order-bubbling@2x.png and /dev/null differ diff --git a/2-ui/2-events/02-bubbling-and-capturing/eventflow.png b/2-ui/2-events/02-bubbling-and-capturing/eventflow.png deleted file mode 100644 index 56fa230a60..0000000000 Binary files a/2-ui/2-events/02-bubbling-and-capturing/eventflow.png and /dev/null differ diff --git a/2-ui/2-events/02-bubbling-and-capturing/eventflow.svg b/2-ui/2-events/02-bubbling-and-capturing/eventflow.svg new file mode 100644 index 0000000000..566064cd6b --- /dev/null +++ b/2-ui/2-events/02-bubbling-and-capturing/eventflow.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="641" height="633" viewBox="0 0 641 633"><defs><style>@import url(https://fonts.googleapis.com/css?family=Open+Sans:bold,italic,bolditalic%7CPT+Mono);@font-face{font-family:'PT Mono';font-weight:700;font-style:normal;src:local('PT MonoBold'),url(/font/PTMonoBold.woff2) format('woff2'),url(/font/PTMonoBold.woff) format('woff'),url(/font/PTMonoBold.ttf) format('truetype')}</style></defs><g id="dom" fill="none" fill-rule="evenodd" stroke="none" stroke-width="1"><g id="eventflow" transform="translate(22 28)"><g id="nodes"><g id="Group" transform="translate(224.685)"><path id="Rectangle-path" fill="#D1CFCD" stroke="#181717" d="M0 0h134.427v34.567H0z"/><text id="Window" fill="#181717" fill-rule="nonzero" font-family="OpenSans-Regular, Open Sans" font-size="15.555" font-weight="normal"><tspan x="39.368" y="21.913">Window</tspan></text></g><g id="Group" transform="translate(234.287 60.493)"><path id="Rectangle-path" fill="#D1CFCD" stroke="#181717" d="M0 0h115.223v34.567H0z"/><text id="Document" fill="#181717" fill-rule="nonzero" font-family="OpenSans-Regular, Open Sans" font-size="15.555" font-weight="normal"><tspan x="22.084" y="21.913">Document</tspan></text></g><g id="html-node" transform="translate(243.889 120.985)"><rect id="Rectangle-path" width="96.019" height="34.567" x="0" y="0" fill="#C9DCEA" stroke="#181717" rx="5"/><text id="<html>" fill="#181717" fill-rule="nonzero" font-family="OpenSans-Regular, Open Sans" font-size="15.555" font-weight="normal"><tspan x="23.045" y="21.913"><html></tspan></text></g><g id="body-node" transform="translate(243.889 181.478)"><rect id="Rectangle-path" width="96.019" height="34.567" x="0" y="0" fill="#C9DCEA" stroke="#181717" rx="5"/><text id="<body>" fill="#181717" fill-rule="nonzero" font-family="OpenSans-Regular, Open Sans" font-size="15.555" font-weight="normal"><tspan x="23.045" y="21.913"><body></tspan></text></g><g id="table-node" transform="translate(243.889 241.97)"><rect id="Rectangle-path" width="96.019" height="34.567" x="0" y="0" fill="#C9DCEA" stroke="#181717" rx="5"/><text id="<table>" fill="#181717" fill-rule="nonzero" font-family="OpenSans-Regular, Open Sans" font-size="15.555" font-weight="normal"><tspan x="23.045" y="21.913"><table></tspan></text></g><g id="tbody-node" transform="translate(243.889 302.463)"><rect id="Rectangle-path" width="96.019" height="34.567" x="0" y="0" fill="#C9DCEA" stroke="#181717" rx="5"/><text id="<tbody>" fill="#181717" fill-rule="nonzero" font-family="OpenSans-Regular, Open Sans" font-size="15.555" font-weight="normal"><tspan x="19.204" y="21.913"><tbody></tspan></text></g><g id="tr_1-node" transform="translate(80.656 380.239)"><rect id="Rectangle-path" width="96.019" height="34.567" x="0" y="0" fill="#C9DCEA" stroke="#181717" rx="5"/><text id="<tr>" fill="#181717" fill-rule="nonzero" font-family="OpenSans-Regular, Open Sans" font-size="15.555" font-weight="normal"><tspan x="35.527" y="21.913"><tr></tspan></text></g><g id="tr_2-node" transform="translate(426.325 380.239)"><rect id="Rectangle-path" width="96.019" height="34.567" x="0" y="0" fill="#C9DCEA" stroke="#181717" rx="5"/><text id="<tr>" fill="#181717" fill-rule="nonzero" font-family="OpenSans-Regular, Open Sans" font-size="15.555" font-weight="normal"><tspan x="33.607" y="21.913"><tr></tspan></text></g><g id="tr_1_td_1-node" transform="translate(13.443 458.015)"><rect id="Rectangle-path" width="96.019" height="34.567" x="0" y="0" fill="#C9DCEA" stroke="#181717" rx="5"/><text id="<td>" fill="#181717" fill-rule="nonzero" font-family="OpenSans-Regular, Open Sans" font-size="15.555" font-weight="normal"><tspan x="31.686" y="21.913"><td></tspan></text></g><g id="tr_1_td_1_text-node" transform="translate(0 518.507)"><ellipse id="Oval" cx="61.452" cy="30.246" fill="#1C85B5" stroke="#181717" rx="61.452" ry="30.246"/><text id="Shady-Grove" fill="#FFF" fill-rule="nonzero" font-family="OpenSans-Regular, Open Sans" font-size="12.963" font-weight="normal"><tspan x="19.204" y="34.604">Shady Grove</tspan></text></g><g id="tr_1_td_2-node" transform="translate(147.87 458.015)"><rect id="Rectangle-path" width="96.019" height="34.567" x="0" y="0" fill="#C9DCEA" stroke="#181717" rx="5"/><text id="<td>" fill="#181717" fill-rule="nonzero" font-family="OpenSans-Regular, Open Sans" font-size="15.555" font-weight="normal"><tspan x="33.607" y="21.913"><td></tspan></text></g><g id="tr_1_td_2_text-node" transform="translate(134.427 518.507)"><ellipse id="Oval" cx="61.452" cy="30.246" fill="#1C85B5" stroke="#181717" rx="61.452" ry="30.246"/><text id="Aeolian" fill="#FFF" fill-rule="nonzero" font-family="OpenSans-Regular, Open Sans" font-size="12.963" font-weight="normal"><tspan x="36.967" y="34.604">Aeolian</tspan></text></g><g id="tr_2_td_1-node" transform="translate(359.111 458.015)"><rect id="Rectangle-path" width="96.019" height="34.567" x="0" y="0" fill="#FFF" stroke="#181717" rx="5"/><text id="<td>" fill="#C06334" fill-rule="nonzero" font-family="PTMono-Regular, PT Mono" font-size="15.555" font-weight="normal"><tspan x="27.846" y="22.37"><td></tspan></text></g><g id="tr_2_td_1_text-node" transform="translate(345.669 518.507)"><ellipse id="Oval" cx="61.452" cy="30.246" fill="#1C85B5" stroke="#181717" rx="61.452" ry="30.246"/><g id="Group" fill="#FFF" fill-rule="nonzero" font-family="OpenSans-Regular, Open Sans" font-size="12.963" font-weight="normal" transform="translate(12.482 14.555)"><text id="Over-the-River,"><tspan x=".48" y="14">Over the River,</tspan></text><text id="Charlie"><tspan x="25.925" y="31.284">Charlie</tspan></text></g></g><g id="tr_2_td_2-node" transform="translate(493.538 458.015)"><rect id="Rectangle-path" width="96.019" height="34.567" x="0" y="0" fill="#C9DCEA" stroke="#181717" rx="5"/><text id="<td>" fill="#181717" fill-rule="nonzero" font-family="OpenSans-Regular, Open Sans" font-size="15.555" font-weight="normal"><tspan x="33.607" y="21.913"><td></tspan></text></g><g id="tr_2_td_2_text-node" transform="translate(480.096 518.507)"><ellipse id="Oval" cx="61.452" cy="30.246" fill="#1C85B5" stroke="#181717" rx="61.452" ry="30.246"/><text id="Dorian" fill="#FFF" fill-rule="nonzero" font-family="OpenSans-Regular, Open Sans" font-size="12.963" font-weight="normal"><tspan x="39.848" y="34.604">Dorian</tspan></text></g></g><g id="edges" stroke="#7E7C7B" stroke-width="2" transform="translate(61.452 34.567)"><path id="window-document" fill="#7E7C7B" d="M230.446 0v19.876"/><path id="document-html" fill="#7E7C7B" d="M230.446 60.493v19.876"/><path id="html-body" fill="#7E7C7B" d="M230.446 120.985v19.876"/><path id="body-table" fill="#7E7C7B" d="M230.446 181.478v19.876"/><path id="table-tbody" fill="#7E7C7B" d="M230.446 241.97v19.876"/><path id="tbody-tr_1" d="M230.446 302.463c0 11.522-16.003 17.283-48.01 17.283H86.417c-12.802 0-19.204 6.626-19.204 19.876"/><path id="tbody-tr_2" d="M230.446 302.463c0 11.522 22.404 17.283 67.213 17.283h96.02c12.802 0 19.203 6.626 19.203 19.876"/><path id="tr_1-tr_2_td_1" d="M67.213 380.239c0 11.522-6.4 17.283-19.203 17.283H19.204C6.4 397.522 0 404.148 0 417.4"/><path id="tr_1-tr_2_td_2" d="M67.213 380.239c0 11.522 6.402 17.283 19.204 17.283h28.806c12.802 0 19.204 6.626 19.204 19.877"/><path id="tr_2-tr_2_td_1" d="M412.882 380.239c0 11.522-6.401 17.283-19.204 17.283h-28.805c-12.803 0-19.204 6.626-19.204 19.877"/><path id="tr_2-tr_2_td_2" d="M412.882 380.239c0 11.522 12.803 17.283 38.408 17.283h9.602c12.802 0 19.204 6.626 19.204 19.877"/><path id="tr_1_td_1-text" fill="#7E7C7B" d="M0 458.015v19.876"/><path id="tr_1_td_2-text" fill="#7E7C7B" d="M134.427 458.015v19.876"/><path id="tr_2_td_1-text" fill="#7E7C7B" d="M345.669 458.015v19.876"/><path id="tr_2_td_2-text" fill="#7E7C7B" d="M482.016 458.015v19.876"/></g><g id="event-flow" transform="translate(103.7 15.443)"><g id="target_phase" transform="translate(186.757 442.572)"><text id="Target" fill="#C06334" fill-rule="nonzero" font-family="OpenSans-Regular, Open Sans" font-size="17.284" font-weight="normal"><tspan x=".48" y="34.148">Target</tspan></text><text id="Phase" fill="#C06334" fill-rule="nonzero" font-family="OpenSans-Regular, Open Sans" font-size="17.284" font-weight="normal"><tspan x="0" y="51.431">Phase</tspan></text><text id="(2)" fill="#C06334" fill-rule="nonzero" font-family="OpenSans-Regular, Open Sans" font-size="17.284" font-weight="normal"><tspan x="15.363" y="68.715">(2)</tspan></text><rect id="Rectangle-path" width="96.019" height="34.567" x="68.654" y="0" stroke="#C06334" stroke-width="4" rx="5"/></g><g id="capture_phase"><text id="Capture" fill="#D35155" fill-rule="nonzero" font-family="OpenSans-Regular, Open Sans" font-size="17.284" font-weight="normal"><tspan x="0" y="165.615">Capture</tspan></text><text id="Phase" fill="#D35155" fill-rule="nonzero" font-family="OpenSans-Regular, Open Sans" font-size="17.284" font-weight="normal"><tspan x="7.201" y="182.899">Phase</tspan></text><text id="(1)" fill="#D35155" fill-rule="nonzero" font-family="OpenSans-Regular, Open Sans" font-size="17.284" font-weight="normal"><tspan x="22.564" y="200.182">(1)</tspan></text><path id="capture_phase_arrow" stroke="#D35155" stroke-width="3" d="M116.183.112c-38.408-2.593-38.408 40.616 2.88 49.258"/><g id="capture_phase_arrow-link" stroke="#D35155" stroke-width="3" transform="translate(92.178 59.74)"><path id="capture_phase_arrow" d="M28.806.864c-38.408-2.592-38.408 40.617 2.88 49.258"/></g><g id="capture_phase_arrow-link" stroke="#D35155" stroke-width="3" transform="translate(96.98 120.233)"><path id="capture_phase_arrow" d="M28.806.864c-38.408-2.592-38.408 40.617 2.88 49.258"/></g><g id="capture_phase_arrow-link" stroke="#D35155" stroke-width="3" transform="translate(96.98 180.725)"><path id="capture_phase_arrow" d="M28.806.864c-38.408-2.592-38.408 40.617 2.88 49.258"/></g><g id="capture_phase_arrow-link" stroke="#D35155" stroke-width="3" transform="translate(96.98 241.218)"><path id="capture_phase_arrow" d="M28.806.864c-38.408-2.592-38.408 40.617 2.88 49.258"/></g><path id="capture_phase_arrow2" stroke="#D35155" stroke-width="3" d="M125.785 302.575c-38.408-2.593-38.408 82.097 175.715 69.134"/><path id="capture_phase_arrow3" stroke="#D35155" stroke-width="3" d="M301.5 384.672c-94.099-2.593-94.099 51.85-62.412 64.813"/></g><g id="bubble_phase" transform="translate(250.61 12.21)"><text id="Bubbling" fill="#478964" fill-rule="nonzero" font-family="OpenSans-Regular, Open Sans" font-size="17.284" font-weight="normal"><tspan x="72.975" y="239.822">Bubbling</tspan></text><text id="Phase" fill="#478964" fill-rule="nonzero" font-family="OpenSans-Regular, Open Sans" font-size="17.284" font-weight="normal"><tspan x="83.057" y="257.106">Phase</tspan></text><text id="(3)" fill="#478964" fill-rule="nonzero" font-family="OpenSans-Regular, Open Sans" font-size="17.284" font-weight="normal"><tspan x="98.42" y="274.39">(3)</tspan></text><path id="bubble_phase_arrow3" stroke="#478964" stroke-width="3" d="M112.342 437.275c132.507-56.172 132.507-67.406 67.214-64.814"/><path id="bubble_phase_arrow2" stroke="#478964" stroke-width="3" d="M182.436 358.634c38.408-8.641 38.408-50.986-180.516-59.628"/><path id="bubble_phase_arrow" stroke="#478964" stroke-width="3" d="M0 287.772c38.408-2.593 38.408-37.16 0-45.802"/><g id="bubble_phase_arrow-link" stroke="#478964" stroke-width="3" transform="translate(0 181.478)"><path id="bubble_phase_arrow" d="M0 45.801C38.408 43.21 38.408 8.641 0 0"/></g><g id="bubble_phase_arrow-link" stroke="#478964" stroke-width="3" transform="translate(0 120.985)"><path id="bubble_phase_arrow" d="M0 45.801C38.408 43.21 38.408 8.641 0 0"/></g><path id="bubble_phase_arrow4" stroke="#478964" stroke-width="3" d="M0 106.294c48.01-2.593 48.01-37.16 9.602-45.801"/><path id="bubble_phase_arrow" stroke="#478964" stroke-width="3" d="M9.602 45.801C57.612 43.21 57.612 8.641 19.204 0"/></g></g></g></g></svg> \ No newline at end of file diff --git a/2-ui/2-events/02-bubbling-and-capturing/eventflow@2x.png b/2-ui/2-events/02-bubbling-and-capturing/eventflow@2x.png deleted file mode 100644 index 17ab1bf8ee..0000000000 Binary files a/2-ui/2-events/02-bubbling-and-capturing/eventflow@2x.png and /dev/null differ diff --git a/2-ui/2-events/03-event-delegation/1-hide-message-delegate/solution.view/index.html b/2-ui/2-events/03-event-delegation/1-hide-message-delegate/solution.view/index.html index 6f8ed9c4d4..70d7b19432 100644 --- a/2-ui/2-events/03-event-delegation/1-hide-message-delegate/solution.view/index.html +++ b/2-ui/2-events/03-event-delegation/1-hide-message-delegate/solution.view/index.html @@ -10,18 +10,18 @@ <div id="container"> <div class="pane"> - <h3>Horse</h3> - <p>The horse is one of two extant subspecies of Equus ferus. It is an odd-toed ungulate mammal belonging to the taxonomic family Equidae. The horse has evolved over the past 45 to 55 million years from a small multi-toed creature, Eohippus, into the large, single-toed animal of today.</p> + <h3>Лошадь</h3> + <p>Домашняя лошадь - животное семейства непарнокопытных, одомашненный и единственный сохранившийся подвид дикой лошади, вымершей в дикой природе, за исключением небольшой популяции лошади Пржевальского.</p> <button class="remove-button">[x]</button> </div> <div class="pane"> - <h3>Donkey</h3> - <p>The donkey or ass (Equus africanus asinus) is a domesticated member of the horse family, Equidae. The wild ancestor of the donkey is the African wild ass, E. africanus. The donkey has been used as a working animal for at least 5000 years.</p> + <h3>Осёл</h3> + <p>Домашний осёл или ишак — одомашненный подвид дикого осла, сыгравший важную историческую роль в развитии хозяйства и культуры человека. Все одомашненные ослы относятся к африканским ослам.</p> <button class="remove-button">[x]</button> </div> <div class="pane"> - <h3>Cat</h3> - <p>The domestic cat (Latin: Felis catus) is a small, typically furry, carnivorous mammal. They are often called house cats when kept as indoor pets or simply cats when there is no need to distinguish them from other felids and felines. Cats are often valued by humans for companionship and for their ability to hunt vermin. + <h3>Кошка</h3> + <p>Кошка, или домашняя кошка (лат. Félis silvéstris cátus), — домашнее животное, одно из наиболее популярных (наряду с собакой) "животных-компаньонов". С точки зрения научной систематики, домашняя кошка — млекопитающее семейства кошачьих отряда хищных. Ранее домашнюю кошку нередко рассматривали как отдельный биологический вид. </p> <button class="remove-button">[x]</button> </div> diff --git a/2-ui/2-events/03-event-delegation/1-hide-message-delegate/source.view/index.html b/2-ui/2-events/03-event-delegation/1-hide-message-delegate/source.view/index.html index 8a9ed9a09b..044f139e5b 100644 --- a/2-ui/2-events/03-event-delegation/1-hide-message-delegate/source.view/index.html +++ b/2-ui/2-events/03-event-delegation/1-hide-message-delegate/source.view/index.html @@ -10,18 +10,18 @@ <div id="container"> <div class="pane"> - <h3>Horse</h3> - <p>The horse is one of two extant subspecies of Equus ferus. It is an odd-toed ungulate mammal belonging to the taxonomic family Equidae. The horse has evolved over the past 45 to 55 million years from a small multi-toed creature, Eohippus, into the large, single-toed animal of today.</p> + <h3>Лошадь</h3> + <p>Домашняя лошадь - животное семейства непарнокопытных, одомашненный и единственный сохранившийся подвид дикой лошади, вымершей в дикой природе, за исключением небольшой популяции лошади Пржевальского.</p> <button class="remove-button">[x]</button> </div> <div class="pane"> - <h3>Donkey</h3> - <p>The donkey or ass (Equus africanus asinus) is a domesticated member of the horse family, Equidae. The wild ancestor of the donkey is the African wild ass, E. africanus. The donkey has been used as a working animal for at least 5000 years.</p> + <h3>Осёл</h3> + <p>Домашний осёл или ишак — одомашненный подвид дикого осла, сыгравший важную историческую роль в развитии хозяйства и культуры человека. Все одомашненные ослы относятся к африканским ослам.</p> <button class="remove-button">[x]</button> </div> <div class="pane"> - <h3>Cat</h3> - <p>The domestic cat (Latin: Felis catus) is a small, typically furry, carnivorous mammal. They are often called house cats when kept as indoor pets or simply cats when there is no need to distinguish them from other felids and felines. Cats are often valued by humans for companionship and for their ability to hunt vermin. + <h3>Кошка</h3> + <p>Кошка, или домашняя кошка (лат. Félis silvéstris cátus), — домашнее животное, одно из наиболее популярных (наряду с собакой) "животных-компаньонов". С точки зрения научной систематики, домашняя кошка — млекопитающее семейства кошачьих отряда хищных. Ранее домашнюю кошку нередко рассматривали как отдельный биологический вид. </p> <button class="remove-button">[x]</button> </div> diff --git a/2-ui/2-events/03-event-delegation/1-hide-message-delegate/task.md b/2-ui/2-events/03-event-delegation/1-hide-message-delegate/task.md index 62c0a8ab00..9729a45a06 100644 --- a/2-ui/2-events/03-event-delegation/1-hide-message-delegate/task.md +++ b/2-ui/2-events/03-event-delegation/1-hide-message-delegate/task.md @@ -2,12 +2,12 @@ importance: 5 --- -# Hide messages with delegation +# Спрячьте сообщения с помощью делегирования -There's a list of messages with removal buttons `[x]`. Make the buttons work. +Дан список сообщений с кнопками для удаления `[x]`. Заставьте кнопки работать. -Like this: +В результате должно работать вот так: [iframe src="solution" height=420] -P.S. Should be only one event listener on the container, use event delegation. +P.S. Используйте делегирование событий. Должен быть лишь один обработчик на элементе-контейнере для всего. diff --git a/2-ui/2-events/03-event-delegation/2-sliding-tree/solution.md b/2-ui/2-events/03-event-delegation/2-sliding-tree/solution.md index 09c14a08c1..b7d9697077 100644 --- a/2-ui/2-events/03-event-delegation/2-sliding-tree/solution.md +++ b/2-ui/2-events/03-event-delegation/2-sliding-tree/solution.md @@ -1,4 +1,4 @@ -The solution has two parts. +Решение состоит из двух шагов: -1. Wrap every tree node title into `<span>`. Then we can CSS-style them on `:hover` and handle clicks exactly on text, because `<span>` width is exactly the text width (unlike without it). -2. Set a handler to the `tree` root node and handle clicks on that `<span>` titles. +1. Оборачиваем текст каждого заголовка дерева в элемент `<span>`. Затем мы можем добавить стили CSS на `:hover` и обрабатывать клики только на тексте, т.к. ширина элемента `<span>` в точности совпадает с шириной текста. +2. Устанавливаем обработчик на корневой узел дерева `tree` и ловим клики на элементах `<span>`, содержащих заголовки. diff --git a/2-ui/2-events/03-event-delegation/2-sliding-tree/solution.view/index.html b/2-ui/2-events/03-event-delegation/2-sliding-tree/solution.view/index.html index 0e85e457e5..a436cb69e7 100644 --- a/2-ui/2-events/03-event-delegation/2-sliding-tree/solution.view/index.html +++ b/2-ui/2-events/03-event-delegation/2-sliding-tree/solution.view/index.html @@ -17,36 +17,36 @@ <body> <ul class="tree" id="tree"> - <li>Animals + <li>Животные <ul> - <li>Mammals + <li>Млекопитающие <ul> - <li>Cows</li> - <li>Donkeys</li> - <li>Dogs</li> - <li>Tigers</li> + <li>Коровы</li> + <li>Ослы</li> + <li>Собаки</li> + <li>Тигры</li> </ul> </li> - <li>Other + <li>Другие <ul> - <li>Snakes</li> - <li>Birds</li> - <li>Lizards</li> + <li>Змеи</li> + <li>Птицы</li> + <li>Ящерицы</li> </ul> </li> </ul> </li> - <li>Fishes + <li>Рыбы <ul> - <li>Aquarium + <li>Аквариумные <ul> - <li>Guppy</li> - <li>Angelfish</li> + <li>Гуппи</li> + <li>Скалярии</li> </ul> </li> - <li>Sea + <li>Морские <ul> - <li>Sea trout</li> + <li>Морская форель</li> </ul> </li> </ul> @@ -54,15 +54,15 @@ </ul> <script> - // move all text into <span> - // they occupy exactly the place necessary for the text, + // поместить все текстовые узлы в элемент <span> + // он занимает только то место, которое необходимо для текста for (let li of tree.querySelectorAll('li')) { let span = document.createElement('span'); li.prepend(span); - span.append(span.nextSibling); // move the text node into span + span.append(span.nextSibling); // поместить текстовый узел внутрь элемента <span> } - // catch clicks on whole tree + // ловим клики на всём дереве tree.onclick = function(event) { if (event.target.tagName != 'SPAN') { @@ -70,7 +70,7 @@ } let childrenContainer = event.target.parentNode.querySelector('ul'); - if (!childrenContainer) return; // no children + if (!childrenContainer) return; // нет детей childrenContainer.hidden = !childrenContainer.hidden; } diff --git a/2-ui/2-events/03-event-delegation/2-sliding-tree/source.view/index.html b/2-ui/2-events/03-event-delegation/2-sliding-tree/source.view/index.html index 8572517f24..011dfeefa8 100644 --- a/2-ui/2-events/03-event-delegation/2-sliding-tree/source.view/index.html +++ b/2-ui/2-events/03-event-delegation/2-sliding-tree/source.view/index.html @@ -6,36 +6,36 @@ <body> <ul class="tree" id="tree"> - <li>Animals + <li>Животные <ul> - <li>Mammals + <li>Млекопитающие <ul> - <li>Cows</li> - <li>Donkeys</li> - <li>Dogs</li> - <li>Tigers</li> + <li>Коровы</li> + <li>Ослы</li> + <li>Собаки</li> + <li>Тигры</li> </ul> </li> - <li>Other + <li>Другие <ul> - <li>Snakes</li> - <li>Birds</li> - <li>Lizards</li> + <li>Змеи</li> + <li>Птицы</li> + <li>Ящерицы</li> </ul> </li> </ul> </li> - <li>Fishes + <li>Рыбы <ul> - <li>Aquarium + <li>Аквариумные <ul> - <li>Guppy</li> - <li>Angelfish</li> + <li>Гуппи</li> + <li>Скалярии</li> </ul> </li> - <li>Sea + <li>Морские <ul> - <li>Sea trout</li> + <li>Морская форель</li> </ul> </li> </ul> diff --git a/2-ui/2-events/03-event-delegation/2-sliding-tree/task.md b/2-ui/2-events/03-event-delegation/2-sliding-tree/task.md index bdcf2a510b..32eaf3c302 100644 --- a/2-ui/2-events/03-event-delegation/2-sliding-tree/task.md +++ b/2-ui/2-events/03-event-delegation/2-sliding-tree/task.md @@ -2,13 +2,13 @@ importance: 5 --- -# Tree menu +# Раскрывающееся дерево -Create a tree that shows/hides node children on click: +Создайте дерево, которое по клику на заголовок скрывает-показывает потомков: [iframe border=1 src="solution"] -Requirements: +Требования: -- Only one event handler (use delegation) -- A click outside the node title (on an empty space) should not do anything. +- Использовать только один обработчик событий (применить делегирование) +- Клик вне текста заголовка (на пустом месте) ничего делать не должен. diff --git a/2-ui/2-events/03-event-delegation/3-sortable-table/solution.view/index.html b/2-ui/2-events/03-event-delegation/3-sortable-table/solution.view/index.html index d5d2d640ba..f354f709bd 100644 --- a/2-ui/2-events/03-event-delegation/3-sortable-table/solution.view/index.html +++ b/2-ui/2-events/03-event-delegation/3-sortable-table/solution.view/index.html @@ -25,30 +25,30 @@ <table id="grid"> <thead> <tr> - <th data-type="number">Age</th> - <th data-type="string">Name</th> + <th data-type="number">Возраст</th> + <th data-type="string">Имя</th> </tr> </thead> <tbody> <tr> <td>5</td> - <td>John</td> + <td>Вася</td> </tr> <tr> <td>2</td> - <td>Pete</td> + <td>Петя</td> </tr> <tr> <td>12</td> - <td>Ann</td> + <td>Женя</td> </tr> <tr> <td>9</td> - <td>Eugene</td> + <td>Маша</td> </tr> <tr> <td>1</td> - <td>Ilya</td> + <td>Илья</td> </tr> </tbody> </table> @@ -59,10 +59,10 @@ if (e.target.tagName != 'TH') return; let th = e.target; - // if TH, then sort - // cellIndex is the number of th: - // 0 for the first column - // 1 for the second column, etc + // если ячейка TH, тогда сортировать + // cellIndex - это номер ячейки th: + // 0 для первого столбца + // 1 для второго и т.д. sortGrid(th.cellIndex, th.dataset.type); }; @@ -71,7 +71,7 @@ let rowsArray = Array.from(tbody.rows); - // compare(a, b) compares two rows, need for sorting + // compare(a, b) сравнивает две строки, нужен для сортировки let compare; switch (type) { @@ -87,7 +87,7 @@ break; } - // sort + // сортировка rowsArray.sort(compare); tbody.append(...rowsArray); diff --git a/2-ui/2-events/03-event-delegation/3-sortable-table/source.view/index.html b/2-ui/2-events/03-event-delegation/3-sortable-table/source.view/index.html index 8fac06246f..920b851be9 100644 --- a/2-ui/2-events/03-event-delegation/3-sortable-table/source.view/index.html +++ b/2-ui/2-events/03-event-delegation/3-sortable-table/source.view/index.html @@ -25,36 +25,36 @@ <table id="grid"> <thead> <tr> - <th data-type="number">Age</th> - <th data-type="string">Name</th> + <th data-type="number">Возраст</th> + <th data-type="string">Имя</th> </tr> </thead> <tbody> <tr> <td>5</td> - <td>John</td> + <td>Вася</td> </tr> <tr> <td>2</td> - <td>Pete</td> + <td>Петя</td> </tr> <tr> <td>12</td> - <td>Ann</td> + <td>Женя</td> </tr> <tr> <td>9</td> - <td>Eugene</td> + <td>Маша</td> </tr> <tr> <td>1</td> - <td>Ilya</td> + <td>Илья</td> </tr> </tbody> </table> <script> - // ...your code... + // ...ваш код... </script> </body> diff --git a/2-ui/2-events/03-event-delegation/3-sortable-table/task.md b/2-ui/2-events/03-event-delegation/3-sortable-table/task.md index ec85a473c8..a503ea09ba 100644 --- a/2-ui/2-events/03-event-delegation/3-sortable-table/task.md +++ b/2-ui/2-events/03-event-delegation/3-sortable-table/task.md @@ -2,42 +2,42 @@ importance: 4 --- -# Sortable table +# Сортируемая таблица -Make the table sortable: clicks on `<th>` elements should sort it by corresponding column. +Сделать таблицу сортируемой: при клике на элемент `<th>` строки таблицы должны сортироваться по соответствующему столбцу. -Each `<th>` has the type in the attribute, like this: +Каждый элемент `<th>` имеет атрибут data-type: ```html <table id="grid"> <thead> <tr> *!* - <th data-type="number">Age</th> - <th data-type="string">Name</th> + <th data-type="number">Возраст</th> + <th data-type="string">Имя</th> */!* </tr> </thead> <tbody> <tr> <td>5</td> - <td>John</td> + <td>Вася</td> </tr> <tr> <td>10</td> - <td>Ann</td> + <td>Петя</td> </tr> ... </tbody> </table> ``` -In the example above the first column has numbers, and the second one -- strings. The sorting function should handle sort according to the type. +В примере выше первый столбец содержит числа, а второй -- строки. Функция сортировки должна это учитывать, ведь числа сортируются иначе, чем строки. -Only `"string"` and `"number"` types should be supported. +Сортировка должна поддерживать только типы `"string"` и `"number"`. -The working example: +Работающий пример: [iframe border=1 src="solution" height=190] -P.S. The table can be big, with any number of rows and columns. +P.S. Таблица может быть большой, с любым числом строк и столбцов. diff --git a/2-ui/2-events/03-event-delegation/4-behavior-tooltip/solution.view/index.html b/2-ui/2-events/03-event-delegation/4-behavior-tooltip/solution.view/index.html index 1ef37cdc90..1524231968 100644 --- a/2-ui/2-events/03-event-delegation/4-behavior-tooltip/solution.view/index.html +++ b/2-ui/2-events/03-event-delegation/4-behavior-tooltip/solution.view/index.html @@ -6,7 +6,7 @@ <style> body { height: 2000px; - /* make body scrollable, the tooltip should work after the scroll */ + /* добавим body прокрутку, подсказка должна работать и с прокруткой */ } .tooltip { @@ -25,13 +25,13 @@ <body> - <p>LaLaLa LaLaLa LaLaLa LaLaLa LaLaLa LaLaLa LaLaLa LaLaLa LaLaLa</p> - <p>LaLaLa LaLaLa LaLaLa LaLaLa LaLaLa LaLaLa LaLaLa LaLaLa LaLaLa</p> + <p>ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя</p> + <p>ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя</p> - <button data-tooltip="the tooltip is longer than the element">Short button</button> - <button data-tooltip="HTML<br>tooltip">One more button</button> + <button data-tooltip="эта подсказка должна быть длиннее, чем элемент">Короткая кнопка</button> + <button data-tooltip="HTML<br>подсказка">Ещё кнопка</button> - <p>Scroll the page to make buttons appear on the top, check if the tooltips show up correctly.</p> + <p>Прокрутите страницу, чтобы кнопки оказались у верхнего края, а затем проверьте, правильно ли выводятся подсказки.</p> <script> @@ -40,25 +40,25 @@ document.onmouseover = function(event) { let target = event.target; - // if we have tooltip HTML... + // если у нас есть подсказка... let tooltipHtml = target.dataset.tooltip; if (!tooltipHtml) return; - // ...create the tooltip element + // ...создадим элемент для подсказки tooltipElem = document.createElement('div'); tooltipElem.className = 'tooltip'; tooltipElem.innerHTML = tooltipHtml; document.body.append(tooltipElem); - // position it above the annotated element (top-center) + // спозиционируем его сверху от аннотируемого элемента (top-center) let coords = target.getBoundingClientRect(); let left = coords.left + (target.offsetWidth - tooltipElem.offsetWidth) / 2; - if (left < 0) left = 0; // don't cross the left window edge + if (left < 0) left = 0; // не заезжать за левый край окна let top = coords.top - tooltipElem.offsetHeight - 5; - if (top < 0) { // if crossing the top window edge, show below instead + if (top < 0) { // если подсказка не помещается сверху, то отображать её снизу top = coords.top + target.offsetHeight + 5; } diff --git a/2-ui/2-events/03-event-delegation/4-behavior-tooltip/source.view/index.html b/2-ui/2-events/03-event-delegation/4-behavior-tooltip/source.view/index.html index add3b21442..7e75a9fa80 100644 --- a/2-ui/2-events/03-event-delegation/4-behavior-tooltip/source.view/index.html +++ b/2-ui/2-events/03-event-delegation/4-behavior-tooltip/source.view/index.html @@ -6,11 +6,11 @@ <style> body { height: 2000px; - /* make body scrollable, the tooltip should work after the scroll */ + /* добавим body прокрутку, подсказка должна работать и с прокруткой */ } .tooltip { - /* some styles for the tooltip, you can use your own instead */ + /* немного стилизуем подсказку, можете использовать свои стили вместо этих */ position: fixed; padding: 10px 20px; border: 1px solid #b3c9ce; @@ -26,17 +26,17 @@ <body> - <p>LaLaLa LaLaLa LaLaLa LaLaLa LaLaLa LaLaLa LaLaLa LaLaLa LaLaLa</p> - <p>LaLaLa LaLaLa LaLaLa LaLaLa LaLaLa LaLaLa LaLaLa LaLaLa LaLaLa</p> + <p>ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя</p> + <p>ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя</p> - <button data-tooltip="the tooltip is longer than the element">Short button</button> - <button data-tooltip="HTML<br>tooltip">One more button</button> + <button data-tooltip="эта подсказка должна быть длиннее, чем элемент">Короткая кнопка</button> + <button data-tooltip="HTML<br>подсказка">Ещё кнопка</button> - <p>Scroll the page to make buttons appear on the top, check if the tooltips show up correctly.</p> + <p>Прокрутите страницу, чтобы кнопки оказались у верхнего края, а затем проверьте правильно ли выводятся подсказки.</p> <script> - // ...your code... + // ...ваш код... </script> </body> diff --git a/2-ui/2-events/03-event-delegation/4-behavior-tooltip/task.md b/2-ui/2-events/03-event-delegation/4-behavior-tooltip/task.md index fc2462289c..692cffa8bd 100644 --- a/2-ui/2-events/03-event-delegation/4-behavior-tooltip/task.md +++ b/2-ui/2-events/03-event-delegation/4-behavior-tooltip/task.md @@ -2,35 +2,37 @@ importance: 5 --- -# Tooltip behavior +# Поведение "подсказка" -Create JS-code for the tooltip behavior. +Напишите JS-код, реализующий поведение "подсказка". -When a mouse comes over an element with `data-tooltip`, the tooltip should appear over it, and when it's gone then hide. +При наведении мыши на элемент с атрибутом `data-tooltip`, над ним должна показываться подсказка и скрываться при переходе на другой элемент. -An example of annotated HTML: +Пример HTML с подсказками: ```html -<button data-tooltip="the tooltip is longer than the element">Short button</button> -<button data-tooltip="HTML<br>tooltip">One more button</button> +<button data-tooltip="эта подсказка длиннее, чем элемент">Короткая кнопка</button> +<button data-tooltip="HTML<br>подсказка">Ещё кнопка</button> ``` -Should work like this: +Результат в ифрейме с документом: [iframe src="solution" height=200 border=1] -In this task we assume that all elements with `data-tooltip` have only text inside. No nested tags. +В этой задаче мы полагаем, что во всех элементах с атрибутом `data-tooltip` -- только текст. То есть, в них нет вложенных тегов (пока). -Details: +Детали оформления: -- The tooltip should not cross window edges. Normally it should be above the element, but if the element is at the page top and there's no space for the tooltip, then below it. -- The tooltip is given in the `data-tooltip` attribute. It can be arbitrary HTML. +- Отступ от подсказки до элемента с `data-tooltip` должен быть `5px` по высоте. +- Подсказка должна быть, по возможности, посередине элемента. +- Подсказка не должна вылезать за границы экрана, в том числе если страница частично прокручена, если нельзя показать сверху -- показывать снизу элемента. +- Текст подсказки брать из значения атрибута `data-tooltip`. Это может быть произвольный HTML. -You'll need two events here: -- `mouseover` triggers when a pointer comes over an element. -- `mouseout` triggers when a pointer leaves an element. +Для решения вам понадобятся два события: +- `mouseover` срабатывает, когда указатель мыши заходит на элемент. +- `mouseout` срабатывает, когда указатель мыши уходит с элемента. -Please use event delegation: set up two handlers on `document` to track all "overs" and "outs" from elements with `data-tooltip` and manage tooltips from there. +Примените делегирование событий: установите оба обработчика на элемент `document`, чтобы отслеживать "заход" и "уход" курсора на элементы с атрибутом `data-tooltip` и управлять подсказками с их же помощью. -After the behavior is implemented, even people unfamiliar with JavaScript can add annotated elements. +После реализации поведения -- люди, даже не знакомые с JavaScript смогут добавлять подсказки к элементам. -P.S. To keep things natural and simple: only one tooltip may show up at a time. +P.S. В один момент может быть показана только одна подсказка. diff --git a/2-ui/2-events/03-event-delegation/article.md b/2-ui/2-events/03-event-delegation/article.md index 83b4b6ce0c..ef3fe512d4 100644 --- a/2-ui/2-events/03-event-delegation/article.md +++ b/2-ui/2-events/03-event-delegation/article.md @@ -1,91 +1,91 @@ -# Event delegation +# Делегирование событий -Capturing and bubbling allow us to implement one of most powerful event handling patterns called *event delegation*. +Всплытие и перехват событий позволяет реализовать один из самых важных приёмов разработки -- *делегирование*. -The idea is that if we have a lot of elements handled in a similar way, then instead of assigning a handler to each of them -- we put a single handler on their common ancestor. +Идея в том, что если у нас есть много элементов, события на которых нужно обрабатывать похожим образом, то вместо того, чтобы назначать обработчик каждому, мы ставим один обработчик на их общего предка. -In the handler we get `event.target`, see where the event actually happened and handle it. +Из него можно получить целевой элемент `event.target`, понять на каком именно потомке произошло событие и обработать его. -Let's see an example -- the [Ba-Gua diagram](http://en.wikipedia.org/wiki/Ba_gua) reflecting the ancient Chinese philosophy. +Рассмотрим пример -- [диаграмму Ба-Гуа](https://ru.wikipedia.org/wiki/%D0%92%D0%BE%D1%81%D0%B5%D0%BC%D1%8C_%D1%82%D1%80%D0%B8%D0%B3%D1%80%D0%B0%D0%BC%D0%BC). Это таблица, отражающая древнюю китайскую философию. -Here it is: +Вот она: [iframe height=350 src="bagua" edit link] -The HTML is like this: +Её HTML (схематично): ```html <table> <tr> - <th colspan="3"><em>Bagua</em> Chart: Direction, Element, Color, Meaning</th> + <th colspan="3">Квадрат <em>Bagua</em>: Направление, Элемент, Цвет, Значение</th> </tr> <tr> - <td>...<strong>Northwest</strong>...</td> + <td>...<strong>Северо-Запад</strong>...</td> <td>...</td> <td>...</td> </tr> - <tr>...2 more lines of this kind...</tr> - <tr>...2 more lines of this kind...</tr> + <tr>...ещё 2 строки такого же вида...</tr> + <tr>...ещё 2 строки такого же вида...</tr> </table> ``` -The table has 9 cells, but there could be 99 or 9999, doesn't matter. +В этой таблице всего 9 ячеек, но могло бы быть и 99, и даже 9999, не важно. -**Our task is to highlight a cell `<td>` on click.** +**Наша задача - реализовать подсветку ячейки `<td>` при клике.** -Instead of assign an `onclick` handler to each `<td>` (can be many) -- we'll setup the "catch-all" handler on `<table>` element. +Вместо того, чтобы назначать обработчик `onclick` для каждой ячейки `<td>` (их может быть очень много) -- мы повесим "единый" обработчик на элемент `<table>`. -It will use `event.target` to get the clicked element and highlight it. +Он будет использовать `event.target`, чтобы получить элемент, на котором произошло событие, и подсветить его. -The code: +Код будет таким: ```js let selectedTd; *!* table.onclick = function(event) { - let target = event.target; // where was the click? + let target = event.target; // где был клик? - if (target.tagName != 'TD') return; // not on TD? Then we're not interested + if (target.tagName != 'TD') return; // не на TD? тогда не интересует - highlight(target); // highlight it + highlight(target); // подсветить TD }; */!* function highlight(td) { - if (selectedTd) { // remove the existing highlight if any + if (selectedTd) { // убрать существующую подсветку, если есть selectedTd.classList.remove('highlight'); } selectedTd = td; - selectedTd.classList.add('highlight'); // highlight the new td + selectedTd.classList.add('highlight'); // подсветить новый td } ``` -Such a code doesn't care how many cells there are in the table. We can add/remove `<td>` dynamically at any time and the highlighting will still work. +Такому коду нет разницы, сколько ячеек в таблице. Мы можем добавлять, удалять `<td>` из таблицы динамически в любое время, и подсветка будет стабильно работать. -Still, there's a drawback. +Однако, у текущей версии кода есть недостаток. -The click may occur not on the `<td>`, but inside it. +Клик может быть не на теге `<td>`, а внутри него. -In our case if we take a look inside the HTML, we can see nested tags inside `<td>`, like `<strong>`: +В нашем случае, если взглянуть на HTML-код таблицы внимательно, видно, что ячейка `<td>` содержит вложенные теги, например `<strong>`: ```html <td> *!* - <strong>Northwest</strong> + <strong>Северо-Запад</strong> */!* ... </td> ``` -Naturally, if a click happens on that `<strong>` then it becomes the value of `event.target`. +Естественно, если клик произойдёт на элементе `<strong>`, то он станет значением `event.target`. -![](bagua-bubble.png) +![](bagua-bubble.svg) -In the handler `table.onclick` we should take such `event.target` and find out whether the click was inside `<td>` or not. +Внутри обработчика `table.onclick` мы должны по `event.target` разобраться, был клик внутри `<td>` или нет. -Here's the improved code: +Вот улучшенный код: ```js table.onclick = function(event) { @@ -99,52 +99,51 @@ table.onclick = function(event) { }; ``` -Explanations: -1. The method `elem.closest(selector)` returns the nearest ancestor that matches the selector. In our case we look for `<td>` on the way up from the source element. -2. If `event.target` is not inside any `<td>`, then the call returns `null`, and we don't have to do anything. -3. In case of nested tables, `event.target` may be a `<td>` lying outside of the current table. So we check if that's actually *our table's* `<td>`. -4. And, if it's so, then highlight it. +Разберём пример: +1. Метод `elem.closest(selector)` возвращает ближайшего предка, соответствующего селектору. В данном случае нам нужен `<td>`, находящийся выше по дереву от исходного элемента. +2. Если `event.target` не содержится внутри элемента `<td>`, то вызов вернёт `null`, и ничего не произойдёт. +3. Если таблицы вложенные, `event.target` может содержать элемент `<td>`, находящийся вне текущей таблицы. В таких случаях мы должны проверить, действительно ли это `<td>` *нашей таблицы*. +4. И если это так, то подсвечиваем его. -## Delegation example: actions in markup +В итоге мы получили короткий код подсветки, быстрый и эффективный, которому совершенно не важно, сколько всего в таблице `<td>`. -The event delegation may be used to optimize event handling. We use a single handler for similar actions on many elements. Like we did it for highlighting `<td>`. +## Применение делегирования: действия в разметке -But we can also use a single handler as an entry point for many different things. +Есть и другие применения делегирования. -For instance, we want to make a menu with buttons "Save", "Load", "Search" and so on. And there's an object with methods `save`, `load`, `search`.... +Например, нам нужно сделать меню с разными кнопками: "Сохранить (save)", "Загрузить (load)", "Поиск (search)" и т.д. И есть объект с соответствующими методами `save`, `load`, `search`... Как их состыковать? -The first idea may be to assign a separate handler to each button. But there's a more elegant solution. We can add a handler for the whole menu and `data-action` attributes for buttons that has the method to call: +Первое, что может прийти в голову – это найти каждую кнопку и назначить ей свой обработчик среди методов объекта. Но существует более элегантное решение. Мы можем добавить один обработчик для всего меню и атрибуты `data-action` для каждой кнопки в соответствии с методами, которые они вызывают: ```html -<button *!*data-action="save"*/!*>Click to Save</button> +<button *!*data-action="save"*/!*>Нажмите, чтобы Сохранить</button> ``` -The handler reads the attribute and executes the method. Take a look at the working example: +Обработчик считывает содержимое атрибута и выполняет метод. Взгляните на рабочий пример: -```html autorun height=60 run +```html autorun height=60 run untrusted <div id="menu"> - <button data-action="save">Save</button> - <button data-action="load">Load</button> - <button data-action="search">Search</button> + <button data-action="save">Сохранить</button> + <button data-action="load">Загрузить</button> + <button data-action="search">Поиск</button> </div> <script> class Menu { constructor(elem) { - this._elem = elem; elem.onclick = this.onClick.bind(this); // (*) } save() { - alert('saving'); + alert('сохраняю'); } load() { - alert('loading'); + alert('загружаю'); } search() { - alert('searching'); + alert('ищу'); } onClick(event) { @@ -154,44 +153,44 @@ The handler reads the attribute and executes the method. Take a look at the work this[action](); } */!* - }; + } } new Menu(menu); </script> ``` -Please note that `this.onClick` is bound to `this` in `(*)`. That's important, because otherwise `this` inside it would reference the DOM element (`elem`), not the menu object, and `this[action]` would not be what we need. +Обратите внимание, что метод `this.onClick` в строке, отмеченной звёздочкой `(*)`, привязывается к контексту текущего объекта `this`. Это важно, т.к. иначе `this` внутри него будет ссылаться на DOM-элемент (`elem`), а не на объект `Menu`, и `this[action]` будет не тем, что нам нужно. -So, what the delegation gives us here? +Так что же даёт нам здесь делегирование? ```compare -+ We don't need to write the code to assign a handler to each button. Just make a method and put it in the markup. -+ The HTML structure is flexible, we can add/remove buttons at any time. ++ Не нужно писать код, чтобы присвоить обработчик каждой кнопке. Достаточно просто создать один метод и поместить его в разметку. ++ Структура HTML становится по-настоящему гибкой. Мы можем добавлять/удалять кнопки в любое время. ``` -We could also use classes `.action-save`, `.action-load`, but an attribute `data-action` is better semantically. And we can use it in CSS rules too. +Мы также можем использовать классы `.action-save`, `.action-load`, но подход с использованием атрибутов `data-action` является более семантичным. Их можно использовать и для стилизации в правилах CSS. -## The "behavior" pattern +## Приём проектирования "поведение" -We can also use event delegation to add "behaviors" to elements *declaratively*, with special attributes and classes. +Делегирование событий можно использовать для добавления элементам "поведения" (behavior), *декларативно* задавая хитрые обработчики установкой специальных HTML-атрибутов и классов. -The pattern has two parts: -1. We add a special attribute to an element. -2. A document-wide handler tracks events, and if an event happens on an attributed element -- performs the action. +Приём проектирования "поведение" состоит из двух частей: +1. Элементу ставится пользовательский атрибут, описывающий его поведение. +2. При помощи делегирования ставится обработчик на документ, который ловит все клики (или другие события) и, если элемент имеет нужный атрибут, производит соответствующее действие. -### Counter +### Поведение: "Счётчик" -For instance, here the attribute `data-counter` adds a behavior: "increase on click" to buttons: +Например, здесь HTML-атрибут `data-counter` добавляет кнопкам поведение: "увеличить значение при клике": ```html run autorun height=60 -Counter: <input type="button" value="1" data-counter> -One more counter: <input type="button" value="2" data-counter> +Счётчик: <input type="button" value="1" data-counter> +Ещё счётчик: <input type="button" value="2" data-counter> <script> document.addEventListener('click', function(event) { - if (event.target.dataset.counter != undefined) { // if the attribute exists... + if (event.target.dataset.counter != undefined) { // если есть атрибут... event.target.value++; } @@ -199,27 +198,27 @@ One more counter: <input type="button" value="2" data-counter> </script> ``` -If we click a button -- its value is increased. Not buttons, but the general approach is important here. +Если нажать на кнопку -- значение увеличится. Конечно, нам важны не счётчики, а общий подход, который здесь продемонстрирован. -There can be as many attributes with `data-counter` as we want. We can add new ones to HTML at any moment. Using the event delegation we "extended" HTML, added an attribute that describes a new behavior. +Элементов с атрибутом `data-counter` может быть сколько угодно. Новые могут добавляться в HTML-код в любой момент. При помощи делегирования мы фактически добавили новый "псевдостандартный" атрибут в HTML, который добавляет элементу новую возможность ("поведение"). -```warn header="For document-level handlers -- always `addEventListener`" -When we assign an event handler to the `document` object, we should always use `addEventListener`, not `document.onclick`, because the latter will cause conflicts: new handlers overwrite old ones. +```warn header="Всегда используйте метод `addEventListener` для обработчиков на уровне документа" +Когда мы устанавливаем обработчик событий на объект `document`, мы всегда должны использовать метод `addEventListener`, а не `document.on<событие>`, т.к. в случае последнего могут возникать конфликты: новые обработчики будут перезаписывать уже существующие. -For real projects it's normal that there are many handlers on `document` set by different parts of the code. +Для реального проекта совершенно нормально иметь много обработчиков на элементе `document`, установленных из разных частей кода. ``` -### Toggler +### Поведение: "Переключатель" (Toggler) -One more example. A click on an element with the attribute `data-toggle-id` will show/hide the element with the given `id`: +Ещё один пример поведения. Сделаем так, что при клике на элемент с атрибутом `data-toggle-id` будет скрываться/показываться элемент с заданным `id`: ```html autorun run height=60 <button *!*data-toggle-id="subscribe-mail"*/!*> - Show the subscription form + Показать форму подписки </button> <form id="subscribe-mail" hidden> - Your mail: <input type="email"> + Ваша почта: <input type="email"> </form> <script> @@ -236,37 +235,37 @@ One more example. A click on an element with the attribute `data-toggle-id` will </script> ``` -Let's note once again what we did. Now, to add toggling functionality to an element -- there's no need to know JavaScript, just use the attribute `data-toggle-id`. +Ещё раз подчеркнём, что мы сделали. Теперь для того, чтобы добавить скрытие-раскрытие любому элементу, даже не надо знать JavaScript, можно просто написать атрибут `data-toggle-id`. -That may become really convenient -- no need to write JavaScript for every such element. Just use the behavior. The document-level handler makes it work for any element of the page. +Это бывает очень удобно -- не нужно писать JavaScript-код для каждого элемента, который должен так себя вести. Просто используем поведение. Обработчики на уровне документа сделают это возможным для элемента в любом месте страницы. -We can combine multiple behaviors on a single element as well. +Мы можем комбинировать несколько вариантов поведения на одном элементе. -The "behavior" pattern can be an alternative of mini-fragments of JavaScript. +Шаблон "поведение" может служить альтернативой для фрагментов JS-кода в вёрстке. -## Summary +## Итого -Event delegation is really cool! It's one of the most helpful patterns for DOM events. +Делегирование событий -- это здорово! Пожалуй, это один из самых полезных приёмов для работы с DOM. -It's often used to add same handling for many similar elements, but not only for that. +Он часто используется, если есть много элементов, обработка которых очень схожа, но не только для этого. -The algorithm: +Алгоритм: -1. Put a single handler on the container. -2. In the handler -- check the source element `event.target`. -3. If the event happened inside an element that interests us, then handle the event. +1. Вешаем обработчик на контейнер. +2. В обработчике проверяем исходный элемент `event.target`. +3. Если событие произошло внутри нужного нам элемента, то обрабатываем его. -Benefits: +Зачем использовать: ```compare -+ Simplifies initialization and saves memory: no need to add many handlers. -+ Less code: when adding or removing elements, no need to add/remove handlers. -+ DOM modifications: we can mass add/remove elements with `innerHTML` and alike. ++ Упрощает процесс инициализации и экономит память: не нужно вешать много обработчиков. ++ Меньше кода: при добавлении и удалении элементов не нужно ставить или снимать обработчики. ++ Удобство изменений DOM: можно массово добавлять или удалять элементы путём изменения `innerHTML` и ему подобных. ``` -The delegation has its limitations of course: +Конечно, у делегирования событий есть свои ограничения: ```compare -- First, the event must be bubbling. Some events do not bubble. Also, low-level handlers should not use `event.stopPropagation()`. -- Second, the delegation may add CPU load, because the container-level handler reacts on events in any place of the container, no matter if they interest us or not. But usually the load is negligible, so we don't take it into account. +- Во-первых, событие должно всплывать. Некоторые события этого не делают. Также, низкоуровневые обработчики не должны вызывать `event.stopPropagation()`. +- Во-вторых, делегирование создаёт дополнительную нагрузку на браузер, ведь обработчик запускается, когда событие происходит в любом месте контейнера, не обязательно на элементах, которые нам интересны. Но обычно эта нагрузка настолько пустяковая, что её даже не стоит принимать во внимание. ``` diff --git a/2-ui/2-events/03-event-delegation/bagua-bubble.png b/2-ui/2-events/03-event-delegation/bagua-bubble.png deleted file mode 100644 index 55adbd0c13..0000000000 Binary files a/2-ui/2-events/03-event-delegation/bagua-bubble.png and /dev/null differ diff --git a/2-ui/2-events/03-event-delegation/bagua-bubble.svg b/2-ui/2-events/03-event-delegation/bagua-bubble.svg new file mode 100644 index 0000000000..4ae67102dd --- /dev/null +++ b/2-ui/2-events/03-event-delegation/bagua-bubble.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="369" height="216" viewBox="0 0 369 216"><defs><style>@import url(https://fonts.googleapis.com/css?family=Open+Sans:bold,italic,bolditalic%7CPT+Mono);@font-face{font-family:'PT Mono';font-weight:700;font-style:normal;src:local('PT MonoBold'),url(/font/PTMonoBold.woff2) format('woff2'),url(/font/PTMonoBold.woff) format('woff'),url(/font/PTMonoBold.ttf) format('truetype')}</style></defs><g id="dom" fill="none" fill-rule="evenodd" stroke="none" stroke-width="1"><g id="bagua-bubble.svg"><path id="Rectangle-210" fill="#DBAF88" d="M202.488 140L217 186H103l14.512-46z"/><path id="Rectangle-209" stroke="#91C2A3" stroke-width="18" d="M216.634 81l16.09 51H86.276l16.09-51h114.267z"/><path id="Rectangle-208" stroke="#EFA39F" stroke-width="18" d="M236.014 29l22.71 72H60.276l22.71-72h153.028z"/><path id="Fill-46" fill="#166388" d="M164.5 141v13.816a4.5 4.5 0 11-9 0V141h9zm0-31v13h-9v-13h9zM160 20.53a4.471 4.471 0 013.362 1.3l17.64 17.64a4.5 4.5 0 01-6.364 6.364L164.5 35.698V92h-9V35.7l-10.138 10.136a4.5 4.5 0 11-6.363-6.364l17.639-17.64a4.477 4.477 0 013.363-1.3z"/><text id="<table>" fill="#D35155" font-family="PTMono-Bold, PT Mono" font-size="14" font-weight="bold"><tspan x="9" y="30"><table></tspan></text><text id="<td>" fill="#478964" font-family="PTMono-Bold, PT Mono" font-size="14" font-weight="bold"><tspan x="36" y="136"><td></tspan></text><text id="<strong>" fill="#AF6E24" font-family="PTMono-Bold, PT Mono" font-size="14" font-weight="bold"><tspan x="35" y="181"><strong></tspan></text><text id="event.target" fill="#AF6E24" font-family="OpenSans-Bold, Open Sans" font-size="14" font-weight="bold"><tspan x="271.537" y="181">event.target</tspan></text><path id="Line-30" stroke="#C06334" stroke-dasharray="3,6" stroke-linecap="square" stroke-width="2" d="M229.5 177.5h30"/></g></g></svg> \ No newline at end of file diff --git a/2-ui/2-events/03-event-delegation/bagua-bubble@2x.png b/2-ui/2-events/03-event-delegation/bagua-bubble@2x.png deleted file mode 100644 index 86b6d68076..0000000000 Binary files a/2-ui/2-events/03-event-delegation/bagua-bubble@2x.png and /dev/null differ diff --git a/2-ui/2-events/03-event-delegation/bagua.view/index.html b/2-ui/2-events/03-event-delegation/bagua.view/index.html index 685b35f9ed..487fa5dfb2 100644 --- a/2-ui/2-events/03-event-delegation/bagua.view/index.html +++ b/2-ui/2-events/03-event-delegation/bagua.view/index.html @@ -7,57 +7,57 @@ <table id="bagua-table"> <tr> - <th colspan="3"><em>Bagua</em> Chart: Direction, Element, Color, Meaning</th> + <th colspan="3">Квадрат<em>Bagua</em>: Направление, Элемент, Цвет, Значение</th> </tr> <tr> - <td class="nw"><strong>Northwest</strong> - <br>Metal - <br>Silver - <br>Elders + <td class="nw"><strong>Северо-Запад</strong> + <br>Металл + <br>Серебро + <br>Старейшины </td> - <td class="n"><strong>North</strong> - <br>Water - <br>Blue - <br>Change + <td class="n"><strong>Север</strong> + <br>Вода + <br>Синий + <br>Перемены </td> - <td class="ne"><strong>Northeast</strong> - <br>Earth - <br>Yellow - <br>Direction + <td class="ne"><strong>Северо-Восток</strong> + <br>Земля + <br>Жёлтый + <br>Направление </td> </tr> <tr> - <td class="w"><strong>West</strong> - <br>Metal - <br>Gold - <br>Youth + <td class="w"><strong>Запад</strong> + <br>Металл + <br>Золото + <br>Молодость </td> - <td class="c"><strong>Center</strong> - <br>All - <br>Purple - <br>Harmony + <td class="c"><strong>Центр</strong> + <br>Всё + <br>Пурпурный + <br>Гармония </td> - <td class="e"><strong>East</strong> - <br>Wood - <br>Blue - <br>Future + <td class="e"><strong>Восток</strong> + <br>Дерево + <br>Синий + <br>Будущее </td> </tr> <tr> - <td class="sw"><strong>Southwest</strong> - <br>Earth - <br>Brown - <br>Tranquility + <td class="sw"><strong>Юго-Запад</strong> + <br>Земля + <br>Коричневый + <br>Спокойствие </td> - <td class="s"><strong>South</strong> - <br>Fire - <br>Orange - <br>Fame + <td class="s"><strong>Юг</strong> + <br>Огонь + <br>Оранжевый + <br>Слава </td> - <td class="se"><strong>Southeast</strong> - <br>Wood - <br>Green - <br>Romance + <td class="se"><strong>Юго-Восток</strong> + <br>Дерево + <br>Зелёный + <br>Роман </td> </tr> @@ -91,4 +91,4 @@ </body> -</html> \ No newline at end of file +</html> diff --git a/2-ui/2-events/04-default-browser-action/1-why-return-false-fails/solution.md b/2-ui/2-events/04-default-browser-action/1-why-return-false-fails/solution.md index 2862e60063..33c2f6ac7d 100644 --- a/2-ui/2-events/04-default-browser-action/1-why-return-false-fails/solution.md +++ b/2-ui/2-events/04-default-browser-action/1-why-return-false-fails/solution.md @@ -1,16 +1,16 @@ -When the browser reads the `on*` attribute like `onclick`, it creates the handler from its content. +Когда браузер считывает атрибут `on*`, например `onclick`, он создаёт функцию-обработчик с содержимым этого атрибута в качестве тела функции. -For `onclick="handler()"` the function will be: +Функция для `onclick="handler()"` будет: ```js function(event) { - handler() // the content of onclick + handler() // содержимое onclick } ``` -Now we can see that the value returned by `handler()` is not used and does not affect the result. +Сейчас нам видно, что возвращаемое значение `handler()` не используется и не влияет на результат. -The fix is simple: +Исправить очень просто: ```html run <script> @@ -20,10 +20,10 @@ The fix is simple: } </script> -<a href="http://w3.org" onclick="*!*return handler()*/!*">w3.org</a> +<a href="https://w3.org" onclick="*!*return handler()*/!*">w3.org</a> ``` -Also we can use `event.preventDefault()`, like this: +Также мы можем использовать `event.preventDefault()`, например: ```html run <script> @@ -35,5 +35,5 @@ Also we can use `event.preventDefault()`, like this: */!* </script> -<a href="http://w3.org" onclick="*!*handler(event)*/!*">w3.org</a> +<a href="https://w3.org" onclick="*!*handler(event)*/!*">w3.org</a> ``` diff --git a/2-ui/2-events/04-default-browser-action/1-why-return-false-fails/task.md b/2-ui/2-events/04-default-browser-action/1-why-return-false-fails/task.md index 8fa4fb29a9..606ae793b4 100644 --- a/2-ui/2-events/04-default-browser-action/1-why-return-false-fails/task.md +++ b/2-ui/2-events/04-default-browser-action/1-why-return-false-fails/task.md @@ -2,9 +2,9 @@ importance: 3 --- -# Why "return false" doesn't work? +# Почему не работает return false? -Why in the code below `return false` doesn't work at all? +Почему в коде ниже `return false` не работает? ```html autorun run <script> @@ -14,9 +14,9 @@ Why in the code below `return false` doesn't work at all? } </script> -<a href="http://w3.org" onclick="handler()">the browser will go to w3.org</a> +<a href="https://w3.org" onclick="handler()">браузер откроет w3.org</a> ``` -The browser follows the URL on click, but we don't want it. +Браузер переходит по указанной ссылке, но нам этого не нужно. -How to fix? +Как поправить? diff --git a/2-ui/2-events/04-default-browser-action/2-catch-link-navigation/solution.md b/2-ui/2-events/04-default-browser-action/2-catch-link-navigation/solution.md index 25079cb8d4..e5df504cbe 100644 --- a/2-ui/2-events/04-default-browser-action/2-catch-link-navigation/solution.md +++ b/2-ui/2-events/04-default-browser-action/2-catch-link-navigation/solution.md @@ -1,5 +1,5 @@ -That's a great use of the event delegation pattern. +Это – классическая задача на тему делегирования. -In real life instead of asking we can send a "logging" request to the server that saves the information about where the visitor left. Or we can load the content and show it right in the page (if allowable). +В реальной жизни мы можем перехватить событие и создать AJAX-запрос к серверу, который сохранит информацию о том, по какой ссылке ушёл посетитель. Или мы можем загрузить содержимое и отобразить его прямо на странице (если допустимо). -All we need is to catch the `contents.onclick` and use `confirm` to ask the user. A good idea would be to use `link.getAttribute('href')` instead of `link.href` for the URL. See the solution for details. +Всё, что нам необходимо, это поймать событие `contents.onclick` и использовать функцию `confirm`, чтобы задать вопрос пользователю. Хорошей идеей было бы использовать `link.getAttribute('href')` вместо `link.href` для ссылок. Смотрите решение в песочнице. diff --git a/2-ui/2-events/04-default-browser-action/2-catch-link-navigation/solution.view/index.html b/2-ui/2-events/04-default-browser-action/2-catch-link-navigation/solution.view/index.html index 90aecd9dc7..083cfac0d1 100644 --- a/2-ui/2-events/04-default-browser-action/2-catch-link-navigation/solution.view/index.html +++ b/2-ui/2-events/04-default-browser-action/2-catch-link-navigation/solution.view/index.html @@ -16,7 +16,7 @@ <fieldset id="contents"> <legend>#contents</legend> <p> - How about to read <a href="http://wikipedia.org">Wikipedia</a> or visit <a href="http://w3.org"><i>W3.org</i></a> and learn about modern standards? + Как насчёт того, чтобы прочитать <a href="https://wikipedia.org">Википедию</a> или посетить <a href="https://w3.org"><i>W3.org</i></a> и узнать о современных стандартах? </p> </fieldset> diff --git a/2-ui/2-events/04-default-browser-action/2-catch-link-navigation/source.view/index.html b/2-ui/2-events/04-default-browser-action/2-catch-link-navigation/source.view/index.html index b88834c96f..b8e6509f6d 100644 --- a/2-ui/2-events/04-default-browser-action/2-catch-link-navigation/source.view/index.html +++ b/2-ui/2-events/04-default-browser-action/2-catch-link-navigation/source.view/index.html @@ -16,7 +16,7 @@ <fieldset id="contents"> <legend>#contents</legend> <p> - How about to read <a href="http://wikipedia.org">Wikipedia</a> or visit <a href="http://w3.org"><i>W3.org</i></a> and learn about modern standards? + Как насчёт того, чтобы прочитать <a href="https://wikipedia.org">Википедию</a> или посетить <a href="https://w3.org"><i>W3.org</i></a> и узнать о современных стандартах? </p> </fieldset> diff --git a/2-ui/2-events/04-default-browser-action/2-catch-link-navigation/task.md b/2-ui/2-events/04-default-browser-action/2-catch-link-navigation/task.md index 6439376bf8..4670b17ba4 100644 --- a/2-ui/2-events/04-default-browser-action/2-catch-link-navigation/task.md +++ b/2-ui/2-events/04-default-browser-action/2-catch-link-navigation/task.md @@ -2,15 +2,15 @@ importance: 5 --- -# Catch links in the element +# Поймайте переход по ссылке -Make all links inside the element with `id="contents"` ask the user if they really want to leave. And if they don't then don't follow. +Сделайте так, чтобы при клике на ссылки внутри элемента `id="contents"` пользователю выводился вопрос о том, действительно ли он хочет покинуть страницу, и если он не хочет, то прерывать переход по ссылке. -Like this: +Так это должно работать: [iframe height=100 border=1 src="solution"] -Details: +Детали: -- HTML inside the element may be loaded or regenerated dynamically at any time, so we can't find all links and put handlers on them. Use the event delegation. -- The content may have nested tags. Inside links too, like `<a href=".."><i>...</i></a>`. +- Содержимое `#contents` может быть загружено динамически и присвоено при помощи `innerHTML`. Так что найти все ссылки и поставить на них обработчики нельзя. Используйте делегирование. +- Содержимое может иметь вложенные теги, *в том числе внутри ссылок*, например, `<a href=".."><i>...</i></a>`. diff --git a/2-ui/2-events/04-default-browser-action/3-image-gallery/solution.md b/2-ui/2-events/04-default-browser-action/3-image-gallery/solution.md index 5ff60b5c08..79ec19b6ba 100644 --- a/2-ui/2-events/04-default-browser-action/3-image-gallery/solution.md +++ b/2-ui/2-events/04-default-browser-action/3-image-gallery/solution.md @@ -1 +1 @@ -The solution is to assign the handler to the container and track clicks. If a click is on the `<a>` link, then change `src` of `#largeImg` to the `href` of the thumbnail. +Решение состоит в том, чтобы добавить обработчик на контейнер `#thumbs` и отслеживать клики на ссылках. Если клик происходит по ссылке `<a>`, тогда меняем атрибут `src` элемента `#largeImg` на `href` уменьшенного изображения. diff --git a/2-ui/2-events/04-default-browser-action/3-image-gallery/solution.view/gallery.css b/2-ui/2-events/04-default-browser-action/3-image-gallery/solution.view/gallery.css index 4522006aee..8d6472ee63 100644 --- a/2-ui/2-events/04-default-browser-action/3-image-gallery/solution.view/gallery.css +++ b/2-ui/2-events/04-default-browser-action/3-image-gallery/solution.view/gallery.css @@ -4,16 +4,6 @@ body { font: 75%/120% sans-serif; } -h2 { - font: bold 190%/100% sans-serif; - margin: 0 0 .2em; -} - -h2 em { - font: normal 80%/100% sans-serif; - color: #999999; -} - #largeImg { border: solid 1px #ccc; width: 550px; diff --git a/2-ui/2-events/04-default-browser-action/3-image-gallery/solution.view/index.html b/2-ui/2-events/04-default-browser-action/3-image-gallery/solution.view/index.html index 524bc71529..98389fbf27 100644 --- a/2-ui/2-events/04-default-browser-action/3-image-gallery/solution.view/index.html +++ b/2-ui/2-events/04-default-browser-action/3-image-gallery/solution.view/index.html @@ -2,7 +2,7 @@ <html> <head> - <title>Gallery + Галерея @@ -12,7 +12,7 @@

Large image