diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000000..490051876d --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: iliakan diff --git a/.gitignore b/.gitignore index 6f90fd1907..1a71fb7c82 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,4 @@ sftp-config.json Thumbs.db +/svgs \ No newline at end of file diff --git a/1-js/01-getting-started/1-intro/article.md b/1-js/01-getting-started/1-intro/article.md index 64ef99485d..829491ced0 100644 --- a/1-js/01-getting-started/1-intro/article.md +++ b/1-js/01-getting-started/1-intro/article.md @@ -1,10 +1,18 @@ # Введение в JavaScript +<<<<<<< HEAD Давайте посмотрим, что такого особенного в JavaScript, чего можно достичь с его помощью и какие другие технологии хорошо с ним работают. +======= +Let's see what's so special about JavaScript, what we can achieve with it, and what other technologies play well with it. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## Что такое JavaScript? +<<<<<<< HEAD Изначально *JavaScript* был создан, чтобы *"сделать веб-страницы живыми"*. +======= +*JavaScript* was initially created to "make web pages alive". +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Программы на этом языке называются *скриптами*. Они могут встраиваться в HTML и выполняться автоматически при загрузке веб-страницы. @@ -12,8 +20,13 @@ Это отличает JavaScript от другого языка - [Java](https://ru.wikipedia.org/wiki/Java). +<<<<<<< HEAD ```smart header="Почему JavaScript?" Когда JavaScript создавался, у него было другое имя - "LiveScript". Однако, язык Java был очень популярен в то время, и было решено, что позиционирование JavaScript как "младшего брата" Java будет полезно. +======= +```smart header="Why is it called JavaScript?" +When JavaScript was created, it initially had another name: "LiveScript". But Java was very popular at that time, so it was decided that positioning a new language as a "younger brother" of Java would help. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Со временем JavaScript стал полностью независимым языком со своей собственной спецификацией, называющейся [ECMAScript](https://ru.wikipedia.org/wiki/ECMAScript), и сейчас не имеет никакого отношения к Java. ``` @@ -24,26 +37,46 @@ Разные движки имеют разные "кодовые имена". Например: +<<<<<<< HEAD - [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. - ...Ещё есть "Chakra" для IE, "JavaScriptCore", "Nitro" и "SquirrelFish" для Safari и т.д. Эти названия полезно знать, так как они часто используются в статьях для разработчиков. Мы тоже будем их использовать. Например, если "функциональность X поддерживается V8", тогда "Х", скорее всего, работает в Chrome, Opera и Edge. +======= +- [V8](https://en.wikipedia.org/wiki/V8_(JavaScript_engine)) -- in Chrome, Opera and Edge. +- [SpiderMonkey](https://en.wikipedia.org/wiki/SpiderMonkey) -- in Firefox. +- ...There are other codenames like "Chakra" for IE, "JavaScriptCore", "Nitro" and "SquirrelFish" for Safari, etc. + +The terms above are good to remember because they are used in developer articles on the internet. We'll use them too. For instance, if "a feature X is supported by V8", then it probably works in Chrome, Opera and Edge. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```smart header="Как работают движки?" Движки сложны. Но основы понять легко. +<<<<<<< HEAD 1. Движок (встроенный, если это браузер) читает ("парсит") текст скрипта. 2. Затем он преобразует ("компилирует") скрипт в машинный язык. 3. После этого машинный код запускается и работает достаточно быстро. Движок применяет оптимизации на каждом этапе. Он даже просматривает скомпилированный скрипт во время его работы, анализируя проходящие через него данные, и применяет оптимизации к машинному коду, полагаясь на полученные знания. В результате скрипты работают очень быстро. +======= +1. The engine (embedded if it's a browser) reads ("parses") the script. +2. Then it converts ("compiles") the script to machine code. +3. And then the machine code runs, pretty fast. + +The engine applies optimizations at each step of the process. It even watches the compiled script as it runs, analyzes the data that flows through it, and further optimizes the machine code based on that knowledge. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` ## Что может JavaScript в браузере? +<<<<<<< HEAD Современный JavaScript - это "безопасный" язык программирования. Он не предоставляет низкоуровневый доступ к памяти или процессору, потому что изначально был создан для браузеров, не требующих этого. +======= +Modern JavaScript is a "safe" programming language. It does not provide low-level access to memory or the CPU, because it was initially created for browsers which do not require it. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Возможности JavaScript сильно зависят от окружения, в котором он работает. Например, [Node.js](https://ru.wikipedia.org/wiki/Node.js) поддерживает функции чтения/записи произвольных файлов, выполнения сетевых запросов и т.д. @@ -59,14 +92,23 @@ ## Чего НЕ может JavaScript в браузере? +<<<<<<< HEAD Возможности JavaScript в браузере ограничены ради безопасности пользователя. Цель заключается в предотвращении доступа недобросовестной веб-страницы к личной информации или нанесения ущерба данным пользователя. +======= +JavaScript's abilities in the browser are limited to protect the user's safety. The aim is to prevent an evil webpage from accessing private information or harming the user's data. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Примеры таких ограничений включают в себя: +<<<<<<< HEAD - JavaScript на веб-странице не может читать/записывать произвольные файлы на жёстком диске, копировать их или запускать программы. Он не имеет прямого доступа к системным функциям ОС. +======= +- JavaScript on a webpage may not read/write arbitrary files on the hard disk, copy them or execute programs. It has no direct access to OS functions. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Современные браузеры позволяют ему работать с файлами, но с ограниченным доступом, и предоставляют его, только если пользователь выполняет определённые действия, такие как "перетаскивание" файла в окно браузера или его выбор с помощью тега ``. +<<<<<<< HEAD Существуют способы взаимодействия с камерой/микрофоном и другими устройствами, но они требуют явного разрешения пользователя. Таким образом, страница с поддержкой 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 с одной страницы не имеет доступа к другой, если они пришли с разных сайтов (с другого домена, протокола или порта). @@ -78,21 +120,44 @@ ![](limitations.svg) Подобные ограничения не действуют, если JavaScript используется вне браузера, например — на сервере. Современные браузеры предоставляют плагины/расширения, с помощью которых можно запрашивать дополнительные разрешения. +======= + There are ways to interact with the camera/microphone and other devices, but they require a user's explicit permission. So a JavaScript-enabled page may not sneakily enable a web-camera, observe the surroundings and send the information to the [NSA](https://en.wikipedia.org/wiki/National_Security_Agency). +- Different tabs/windows generally do not know about each other. Sometimes they do, for example when one window uses JavaScript to open the other one. But even in this case, JavaScript from one page may not access the other page if they come from different sites (from a different domain, protocol or port). + + This is called the "Same Origin Policy". To work around that, *both pages* must agree for data exchange and must contain special JavaScript code that handles it. We'll cover that in the tutorial. + + This limitation is, again, for the user's safety. A page from `http://anysite.com` which a user has opened must not be able to access another browser tab with the URL `http://gmail.com`, for example, and steal information from there. +- JavaScript can easily communicate over the net to the server where the current page came from. But its ability to receive data from other sites/domains is crippled. Though possible, it requires explicit agreement (expressed in HTTP headers) from the remote side. Once again, that's a safety limitation. + +![](limitations.svg) + +Such limitations do not exist if JavaScript is used outside of the browser, for example on a server. Modern browsers also allow plugins/extensions which may ask for extended permissions. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## Что делает JavaScript особенным? Как минимум, *три* сильные стороны JavaScript: ```compare +<<<<<<< HEAD + Полная интеграция с HTML/CSS. + Простые вещи делаются просто. + Поддерживается всеми основными браузерами и включён по умолчанию. +======= ++ Full integration with HTML/CSS. ++ Simple things are done simply. ++ Supported by all major browsers and enabled by default. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` JavaScript - это единственная браузерная технология, сочетающая в себе все эти три вещи. Вот что делает JavaScript особенным. Вот почему это самый распространённый инструмент для создания интерфейсов в браузере. +<<<<<<< HEAD Хотя, конечно, JavaScript позволяет делать приложения не только в браузерах, но и на сервере, на мобильных устройствах и т.п. +======= +That said, JavaScript can be used to create servers, mobile applications, etc. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## Языки "над" JavaScript @@ -100,12 +165,17 @@ JavaScript - это единственная браузерная техноло Это естественно, потому что проекты разные и требования к ним тоже разные. +<<<<<<< HEAD Так, в последнее время появилось много новых языков, которые *транспилируются* (конвертируются) в JavaScript, прежде чем запустятся в браузере. +======= +So, recently a plethora of new languages appeared, which are *transpiled* (converted) to JavaScript before they run in the browser. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Современные инструменты делают транспиляцию очень быстрой и прозрачной, фактически позволяя разработчикам писать код на другом языке, автоматически преобразуя его в JavaScript "под капотом". Примеры таких языков: +<<<<<<< HEAD - [CoffeeScript](https://coffeescript.org/) добавляет "синтаксический сахар" для JavaScript. Он вводит более короткий синтаксис, который позволяет писать чистый и лаконичный код. Обычно такое нравится Ruby-программистам. - [TypeScript](https://www.typescriptlang.org/) концентрируется на добавлении "строгой типизации" для упрощения разработки и поддержки больших и сложных систем. Разработан Microsoft. - [Flow](https://flow.org/) тоже добавляет типизацию, но иначе. Разработан Facebook. @@ -113,9 +183,25 @@ JavaScript - это единственная браузерная техноло - [Brython](https://brython.info/) транспилирует Python в JavaScript, что позволяет писать приложения на чистом Python без JavaScript. Есть и другие. Но даже если мы используем один из этих языков, мы должны знать JavaScript, чтобы действительно понимать, что мы делаем. +======= +- [CoffeeScript](https://coffeescript.org/) is "syntactic sugar" for JavaScript. It introduces shorter syntax, allowing us to write clearer and more precise code. Usually, Ruby devs like it. +- [TypeScript](https://www.typescriptlang.org/) is concentrated on adding "strict data typing" to simplify the development and support of complex systems. It is developed by Microsoft. +- [Flow](https://flow.org/) also adds data typing, but in a different way. Developed by Facebook. +- [Dart](https://www.dartlang.org/) is a standalone language that has its own engine that runs in non-browser environments (like mobile apps), but also can be transpiled to JavaScript. Developed by Google. +- [Brython](https://brython.info/) is a Python transpiler to JavaScript that enables the writing of applications in pure Python without JavaScript. +- [Kotlin](https://kotlinlang.org/docs/reference/js-overview.html) is a modern, concise and safe programming language that can target the browser or Node. + +There are more. Of course, even if we use one of these transpiled languages, we should also know JavaScript to really understand what we're doing. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## Итого +<<<<<<< HEAD - JavaScript изначально создавался только для браузера, но сейчас используется на многих других платформах. - Сегодня JavaScript занимает уникальную позицию в качестве самого распространённого языка для браузера, обладающего полной интеграцией с HTML/CSS. - Многие языки могут быть "транспилированы" в JavaScript для предоставления дополнительных функций. Рекомендуется хотя бы кратко рассмотреть их после освоения JavaScript. +======= +- JavaScript was initially created as a browser-only language, but it is now used in many other environments as well. +- Today, JavaScript has a unique position as the most widely-adopted browser language, fully integrated with HTML/CSS. +- There are many languages that get "transpiled" to JavaScript and provide certain features. It is recommended to take a look at them, at least briefly, after mastering JavaScript. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/1-js/01-getting-started/2-manuals-specifications/article.md b/1-js/01-getting-started/2-manuals-specifications/article.md index df109cd933..2667398f51 100644 --- a/1-js/01-getting-started/2-manuals-specifications/article.md +++ b/1-js/01-getting-started/2-manuals-specifications/article.md @@ -1,7 +1,11 @@ # Справочники и спецификации +<<<<<<< HEAD Эта книга является *учебником* и нацелена на то, чтобы помочь вам постепенно освоить язык. Но когда вы хорошо изучите основы, вам понадобятся дополнительные источники информации. +======= +This book is a *tutorial*. It aims to help you gradually learn the language. But once you're familiar with the basics, you'll need other resources. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## Спецификация @@ -9,14 +13,23 @@ Вначале спецификация может показаться тяжеловатой для понимания из-за слишком формального стиля изложения. Если вы ищете источник самой достоверной информации, то это правильное место, но она не для ежедневного использования. +<<<<<<< HEAD Новая версия спецификации появляется каждый год. А пока она не вышла официально, все желающие могут ознакомиться с текущим черновиком на . +======= +A new specification version is released every year. Between these releases, the latest specification draft is at . +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Чтобы почитать о самых последних возможностях, включая те, которые "почти в стандарте" (так называемые "stage 3 proposals"), посетите . +<<<<<<< HEAD Если вы разрабатываете под браузеры, то существуют и другие спецификации, о которых рассказывается во [второй части](info:browser-environment) этого учебника. +======= +Also, if you're developing for the browser, then there are other specifications covered in the [second part](info:browser-environment) of the tutorial. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## Справочники +<<<<<<< HEAD - **MDN (Mozilla) JavaScript Reference** -- это справочник с примерами и другой информацией. Хороший источник для получения подробных сведений о функциях языка, методах встроенных объектов и так далее. Располагается по адресу . @@ -28,10 +41,28 @@ JavaScript -- это развивающийся язык, в который постоянно добавляется что-то новое. Посмотреть, какие возможности поддерживаются в разных браузерах и других движках, можно в следующих источниках: +======= +- **MDN (Mozilla) JavaScript Reference** is the main manual with examples and other information. It's great to get in-depth information about individual language functions, methods etc. + + You can find it at . + +Although, it's often best to use an internet search instead. Just use "MDN [term]" in the query, e.g. to search for the `parseInt` function. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 - - таблицы с информацией о поддержке по каждой возможности языка. Например, чтобы узнать, какие движки поддерживают современные криптографические функции, посетите: . - - таблица с возможностями языка и движками, которые их поддерживают и не поддерживают. Все эти ресурсы полезны в ежедневной работе программиста, так как они содержат ценную информацию о возможностях использования языка, их поддержке и так далее. +<<<<<<< HEAD Пожалуйста, запомните эти ссылки (или ссылку на эту страницу) на случай, когда вам понадобится подробная информация о какой-нибудь конкретной возможности JavaScript. +======= +To see their support among browser-based and other engines, see: + +- - per-feature tables of support, e.g. to see which engines support modern cryptography functions: . +- - a table with language features and engines that support those or don't support. + +All these resources are useful in real-life development, as they contain valuable information about language details, their support, etc. + +Please remember them (or this page) for the cases when you need in-depth information about a particular feature. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/1-js/01-getting-started/3-code-editors/article.md b/1-js/01-getting-started/3-code-editors/article.md index c2078d5efa..086a34cc82 100644 --- a/1-js/01-getting-started/3-code-editors/article.md +++ b/1-js/01-getting-started/3-code-editors/article.md @@ -12,8 +12,13 @@ IDE загружает проект (который может состоять Если вы ещё не выбрали себе IDE, присмотритесь к этим: +<<<<<<< HEAD - [Visual Studio Code](https://code.visualstudio.com/) (кросс-платформенная, бесплатная). - [WebStorm](https://www.jetbrains.com/webstorm/) (кросс-платформенная, бесплатная для некоммерческого использования). +======= +- [Visual Studio Code](https://code.visualstudio.com/) (cross-platform, free). +- [WebStorm](https://www.jetbrains.com/webstorm/) (cross-platform, paid). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Для Windows есть ещё Visual Studio (не путать с Visual Studio Code). Visual Studio - это платная мощная среда разработки, которая работает только на Windows. Она хорошо подходит для .NET платформы. У неё есть бесплатная версия, которая называется [Visual Studio Community](https://www.visualstudio.com/vs/community/). @@ -29,11 +34,19 @@ IDE загружает проект (который может состоять На практике "лёгкие" редакторы могут иметь множество плагинов, включая автодополнение и анализаторы синтаксиса на уровне директории, поэтому границы между IDE и "лёгкими" редакторами размыты. +<<<<<<< HEAD Следующие варианты заслуживают вашего внимания: - [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/) тоже хороши, если знать, как ими пользоваться. +======= +There are many options, for instance: + +- [Sublime Text](https://www.sublimetext.com/) (cross-platform, shareware). +- [Notepad++](https://notepad-plus-plus.org/) (Windows, free). +- [Vim](https://www.vim.org/) and [Emacs](https://www.gnu.org/software/emacs/) are also cool if you know how to use them. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## Не будем ссориться @@ -41,4 +54,13 @@ IDE загружает проект (который может состоять Конечно же, есть много других отличных редакторов. Выбирайте тот, который вам больше нравится. +<<<<<<< HEAD Выбор редактора, как и любого другого инструмента, индивидуален и зависит от ваших проектов, привычек и личных предпочтений. +======= +The choice of an editor, like any other tool, is individual and depends on your projects, habits, and personal preferences. + +The author's personal opinion: + +- I'd use [Visual Studio Code](https://code.visualstudio.com/) if I develop mostly frontend. +- Otherwise, if it's mostly another language/platform and partially frontend, then consider other editors, such as XCode (Mac), Visual Studio (Windows) or Jetbrains family (Webstorm, PHPStorm, RubyMine etc, depending on the language). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/1-js/01-getting-started/4-devtools/article.md b/1-js/01-getting-started/4-devtools/article.md index 495dcfe2a2..bbb1186f3e 100644 --- a/1-js/01-getting-started/4-devtools/article.md +++ b/1-js/01-getting-started/4-devtools/article.md @@ -29,7 +29,11 @@ Chrome и Firefox снискали любовь подавляющего бол - В консоли мы можем увидеть сообщение об ошибке, отрисованное красным цветом. В нашем случае скрипт содержит неизвестную команду "lalala". - Справа присутствует ссылка на исходный код `bug.html:12` с номером строки кода, в которой эта ошибка и произошла. +<<<<<<< HEAD Под сообщением об ошибке находится синий символ `>`. Он обозначает командную строку, в ней мы можем редактировать и запускать JavaScript-команды. Для их запуска нажмите `key:Enter`. +======= +Below the error message, there is a blue `>` symbol. It marks a "command line" where we can type JavaScript commands. Press `key:Enter` to run them. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```smart header="Многострочный ввод" Обычно при нажатии `key:Enter` введённая строка кода сразу выполняется. @@ -39,6 +43,11 @@ Chrome и Firefox снискали любовь подавляющего бол Теперь мы явно видим ошибки, для начала этого вполне достаточно. Мы ещё вернёмся к инструментам разработчика позже и более подробно рассмотрим отладку кода в главе . +```smart header="Multi-line input" +Usually, when we put a line of code into the console, and then press `key:Enter`, it executes. + +To insert multiple lines, press `key:Shift+Enter`. This way one can enter long fragments of JavaScript code. +``` ## Firefox, Edge и другие @@ -56,9 +65,18 @@ Safari (браузер для Mac, не поддерживается в сист Теперь консоль можно активировать нажатием клавиш `key:Cmd+Opt+C`. Также обратите внимание на новый элемент меню "Разработка" ("Develop"). В нем содержится большое количество команд и настроек. +<<<<<<< HEAD ## Итого - Инструменты разработчика позволяют нам смотреть ошибки, выполнять команды, проверять значение переменных и ещё много всего полезного. - В большинстве браузеров, работающих под Windows, инструменты разработчика можно открыть, нажав `key:F12`. В Chrome для Mac используйте комбинацию `key:Cmd+Opt+J`, Safari: `key:Cmd+Opt+C` (необходимо предварительное включение "Меню разработчика"). Теперь наше окружение полностью настроено. В следующем разделе мы перейдём непосредственно к JavaScript. +======= +## Summary + +- Developer tools allow us to see errors, run commands, examine variables, and much more. +- They can be opened with `key:F12` for most browsers on Windows. Chrome for Mac needs `key:Cmd+Opt+J`, Safari: `key:Cmd+Opt+C` (need to enable first). + +Now we have the environment ready. In the next section, we'll get down to JavaScript. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/1-js/02-first-steps/01-hello-world/1-hello-alert/index.html b/1-js/02-first-steps/01-hello-world/1-hello-alert/index.html new file mode 100644 index 0000000000..ff1d871b08 --- /dev/null +++ b/1-js/02-first-steps/01-hello-world/1-hello-alert/index.html @@ -0,0 +1,12 @@ + + + + + + + + + + diff --git a/1-js/02-first-steps/01-hello-world/1-hello-alert/solution.md b/1-js/02-first-steps/01-hello-world/1-hello-alert/solution.md index e69de29bb2..81552913b9 100644 --- a/1-js/02-first-steps/01-hello-world/1-hello-alert/solution.md +++ b/1-js/02-first-steps/01-hello-world/1-hello-alert/solution.md @@ -0,0 +1,2 @@ + +[html src="index.html"] 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 b42e30a558..10a9396805 100644 --- a/1-js/02-first-steps/01-hello-world/article.md +++ b/1-js/02-first-steps/01-hello-world/article.md @@ -10,7 +10,13 @@ Программы на JavaScript могут быть вставлены в любое место HTML-документа с помощью тега ` ``` +<<<<<<< HEAD Здесь `/path/to/script.js` - это абсолютный путь от корневой папки до необходимого файла. Корневой папкой может быть корень диска или корень сайта, в зависимости от условий работы сайта. Также можно указать относительный путь от текущей страницы. Например, `src="script.js"` или `src="./script.js"` будет означать, что файл `"script.js"` находится в текущей папке. +======= +Here, `/path/to/script.js` is an absolute path to the script from the site root. One can also provide a relative path from the current page. For instance, `src="script.js"`, just like `src="./script.js"`, would mean a file `"script.js"` in the current folder. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Можно указать и полный URL-адрес. Например: ```html - + ``` Для подключения нескольких скриптов используйте несколько тегов: diff --git a/1-js/02-first-steps/02-structure/article.md b/1-js/02-first-steps/02-structure/article.md index 88a6e2bcea..431581787b 100644 --- a/1-js/02-first-steps/02-structure/article.md +++ b/1-js/02-first-steps/02-structure/article.md @@ -46,7 +46,11 @@ alert(3 + + 2); ``` +<<<<<<< HEAD Код выведет `6`, потому что JavaScript не вставляет здесь точку с запятой. Интуитивно очевидно, что, если строка заканчивается знаком `"+"`, значит, это "незавершённое выражение", поэтому точка с запятой не требуется. И в этом случае всё работает, как задумано. +======= +The code outputs `6` because JavaScript does not insert semicolons here. It is intuitively obvious that if the line ends with a plus `"+"`, then it is an "incomplete expression", so a semicolon there would be incorrect. And in this case, that works as intended. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 **Но есть ситуации, где JavaScript "забывает" вставить точку с запятой там, где она нужна.** @@ -56,21 +60,35 @@ alert(3 + Если вы хотите увидеть конкретный пример такой ошибки, обратите внимание на этот код: ```js run +<<<<<<< HEAD alert('Hello'); +======= +alert("Hello"); +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 [1, 2].forEach(alert); ``` +<<<<<<< HEAD Пока нет необходимости знать значение скобок `[]` и `forEach`. Мы изучим их позже. Пока что просто запомните результат выполнения этого кода: выводится `Hello`, затем `1`, затем `2`. А теперь давайте уберем точку с запятой после `alert`: ```js run no-beautify alert('Hello') +======= +No need to think about the meaning of the brackets `[]` and `forEach` yet. We'll study them later. For now, just remember the result of running the code: it shows `Hello`, then `1`, then `2`. + +Now let's remove the semicolon after the `alert`: + +```js run no-beautify +alert("Hello") +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 [1, 2].forEach(alert); ``` +<<<<<<< HEAD Этот код отличается от кода, приведенного выше, только в одном: пропала точка с запятой в конце первой строки. Если мы запустим этот код, выведется только первый `alert`, а затем мы получим ошибку (вам может потребоваться открыть консоль, чтобы увидеть её)! @@ -86,11 +104,32 @@ alert('Hello')[1, 2].forEach(alert); Выглядит странно, правда? Такое слияние в данном случае неправильное. Мы должны поставить точку с запятой после `alert`, чтобы код работал правильно. Это может произойти и в некоторых других ситуациях. +======= +The difference compared to the code above is only one character: the semicolon at the end of the first line is gone. + +If we run this code, only the first `Hello` shows (and there's an error, you may need to open the console to see it). There are no numbers any more. + +That's because JavaScript does not assume a semicolon before square brackets `[...]`. So, the code in the last example is treated as a single statement. + +Here's how the engine sees it: + +```js run no-beautify +alert("Hello")[1, 2].forEach(alert); +``` + +Looks weird, right? Such merging in this case is just wrong. We need to put a semicolon after `alert` for the code to work correctly. + +This can happen in other situations also. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```` Мы рекомендуем ставить точку с запятой между инструкциями, даже если они отделены переносами строк. Это правило широко используется в сообществе разработчиков. Стоит отметить ещё раз -- в большинстве случаев *можно* не ставить точку с запятой. Но безопаснее, особенно для новичка, ставить её. +<<<<<<< HEAD ## Комментарии +======= +## Comments [#code-comments] +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Со временем программы становятся всё сложнее и сложнее. Возникает необходимость добавлять *комментарии*, которые бы описывали, что делает код и почему. @@ -132,8 +171,13 @@ alert('Привет'); alert('Мир'); ``` +<<<<<<< HEAD ```smart header="Используйте горячие клавиши!" В большинстве редакторов строку кода можно закомментировать, нажав комбинацию клавиш `key:Ctrl+/` для однострочного комментария и что-то вроде `key:Ctrl+Shift+/` -- для многострочных комментариев (выделите кусок кода и нажмите комбинацию клавиш). В системе Mac попробуйте `key:Cmd` вместо `key:Ctrl` и `key:Option` вместо `key:Shift`. +======= +```smart header="Use hotkeys!" +In most editors, a line of code can be commented out by pressing the `key:Ctrl+/` hotkey for a single-line comment and something like `key:Ctrl+Shift+/` -- for multiline comments (select a piece of code and press the hotkey). For Mac, try `key:Cmd` instead of `key:Ctrl` and `key:Option` instead of `key:Shift`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` ````warn header="Вложенные комментарии не поддерживаются!" 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 15290f9dd4..ddf954daee 100644 --- a/1-js/02-first-steps/03-strict-mode/article.md +++ b/1-js/02-first-steps/03-strict-mode/article.md @@ -19,8 +19,12 @@ ... ``` +<<<<<<< HEAD Совсем скоро мы начнём изучать функции (способ группировки команд), поэтому заранее отметим, что в начале большинства видов функций можно поставить `"use strict"`. Это позволяет включить строгий режим только в конкретной функции. Но обычно люди используют его для всего файла. +======= +Quite soon we're going to learn functions (a way to group commands), so let's note in advance that `"use strict"` can be put at the beginning of a function. Doing that enables strict mode in that function only. But usually people use it for the whole script. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ````warn header="Убедитесь, что \"use strict\" находится в начале" Проверьте, что `"use strict"` находится в первой исполняемой строке скрипта, иначе строгий режим может не включиться. @@ -42,18 +46,32 @@ alert("some code"); ```warn header="Нет никакого способа отменить `use strict`" Нет директивы типа `"no use strict"`, которая возвращала бы движок к старому поведению. +<<<<<<< HEAD Как только мы входим в строгий режим, отменить это невозможно. +======= +Once we enter strict mode, there's no going back. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` ## Консоль браузера +<<<<<<< HEAD В дальнейшем, когда вы будете использовать [консоль браузера](info:devtools) для тестирования функций, обратите внимание, что `use strict` по умолчанию в ней выключен. +======= +When you use a [developer console](info:devtools) to run code, please note that it doesn't `use strict` by default. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Иногда, когда `use strict` имеет значение, вы можете получить неправильные результаты. +<<<<<<< HEAD Итак, как можно включить `use strict` в консоли? Можно использовать `key:Shift+Enter` для ввода нескольких строк и написать в верхней строке `use strict`: +======= +So, how to actually `use strict` in the console? + +First, you can try to press `key:Shift+Enter` to input multiple lines, and put `use strict` on top, like this: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js 'use strict'; @@ -63,12 +81,17 @@ alert("some code"); В большинстве браузеров, включая Chrome и Firefox, это работает. +<<<<<<< HEAD Если этого не происходит, например, в старом браузере, есть некрасивый, но надежный способ обеспечить `use strict`. Поместите его в следующую обёртку: +======= +If it doesn't, e.g. in an old browser, there's an ugly, but reliable way to ensure `use strict`. Put it inside this kind of wrapper: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js (function() { 'use strict'; +<<<<<<< HEAD // ...ваш код... })() ``` @@ -88,3 +111,24 @@ alert("some code"); В следующих главах, по мере расширения знаний о возможностях языка, мы яснее увидим отличия между строгим и стандартным режимом. К счастью, их не так много, и все они делают жизнь разработчика лучше. Все примеры в этом учебнике подразумевают исполнение в строгом режиме, за исключением случаев (очень редких), когда оговорено иное. +======= + // ...your code here... +})() +``` + +## Should we "use strict"? + +The question may sound obvious, but it's not so. + +One could recommend to start scripts with `"use strict"`... But you know what's cool? + +Modern JavaScript supports "classes" and "modules" - advanced language structures (we'll surely get to them), that enable `use strict` automatically. So we don't need to add the `"use strict"` directive, if we use them. + +**So, for now `"use strict";` is a welcome guest at the top of your scripts. Later, when your code is all in classes and modules, you may omit it.** + +As of now, we've got to know about `use strict` in general. + +In the next chapters, as we learn language features, we'll see the differences between the strict and old modes. Luckily, there aren't many and they actually make our lives better. + +All examples in this tutorial assume strict mode unless (very rarely) specified otherwise. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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 d9da22ea3b..713de51f6b 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 @@ -6,7 +6,13 @@ let ourPlanetName = "Земля"; ``` +<<<<<<< HEAD Обратите внимание, мы могли бы использовать короткое имя `planet`, но тогда будет непонятно, о какой планете мы говорим. Лучше описать содержимое переменной подробнее, по крайней мере, до тех пор, пока имя переменной неСтанетСлишкомДлинным. +======= +Note, we could use a shorter name `planet`, but it might not be obvious what planet it refers to. It's nice to be more verbose. At least until the variable isNotTooLong. + +## The name of the current visitor +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## Имя текущего посетителя: ```js 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 04bf580663..10bf3448ee 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 @@ -12,13 +12,24 @@ const birthday = '18.04.1982'; const age = someCode(birthday); ``` +<<<<<<< HEAD У нас есть константа `birthday`, а также `age`, которая вычисляется при помощи некоторого кода, используя значение из `birthday` (в данном случае детали не имеют значения, поэтому код не рассматривается). +======= +Here we have a constant `birthday` for the date, and also the `age` constant. + +The `age` is calculated from `birthday` using `someCode()`, which means a function call that we didn't explain yet (we will soon!), but the details don't matter here, the point is that `age` is calculated somehow based on the `birthday`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Можно ли использовать заглавные буквы для имени `birthday`? А для `age`? Или одновременно для обеих переменных? ```js +<<<<<<< HEAD const BIRTHDAY = '18.04.1982'; // использовать заглавные буквы? const AGE = someCode(BIRTHDAY); // а здесь? -``` +======= +const BIRTHDAY = '18.04.1982'; // make birthday uppercase? +const AGE = someCode(BIRTHDAY); // make age uppercase? +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 +``` diff --git a/1-js/02-first-steps/04-variables/article.md b/1-js/02-first-steps/04-variables/article.md index b1bdebc933..c9edabd871 100644 --- a/1-js/02-first-steps/04-variables/article.md +++ b/1-js/02-first-steps/04-variables/article.md @@ -24,7 +24,11 @@ let message; let message; *!* +<<<<<<< HEAD message = 'Hello'; // сохранить строку 'Hello' в переменной с именем message +======= +message = 'Hello'; // store the string 'Hello' in the variable named message +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 */!* ``` @@ -63,7 +67,11 @@ let age = 25; let message = 'Hello'; ``` +<<<<<<< HEAD Некоторые люди также определяют несколько переменных в таком вот многострочном стиле: +======= +Some people also define multiple variables in this multiline style: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js no-beautify let user = 'John', @@ -81,23 +89,38 @@ let user = 'John' В принципе, все эти варианты работают одинаково. Так что это вопрос личного вкуса и эстетики. +<<<<<<< HEAD ````smart header="`var` вместо `let`" В старых скриптах вы также можете найти другое ключевое слово: `var` вместо `let`: +======= +````smart header="`var` instead of `let`" +In older scripts, you may also find another keyword: `var` instead of `let`: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js *!*var*/!* message = 'Hello'; ``` +<<<<<<< HEAD Ключевое слово `var` - *почти* то же самое, что и `let`. Оно объявляет переменную, но немного по-другому, "устаревшим" способом. Есть тонкие различия между `let` и `var`, но они пока не имеют для нас значения. Мы подробно рассмотрим их в главе . +======= +The `var` keyword is *almost* the same as `let`. It also declares a variable but in a slightly different, "old-school" way. + +There are subtle differences between `let` and `var`, but they do not matter to us yet. We'll cover them in detail in the chapter . +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```` ## Аналогия из жизни Мы легко поймём концепцию "переменной", если представим её в виде "коробки" для данных с уникальным названием на ней. +<<<<<<< HEAD Например, переменную `message` можно представить как коробку с названием `"message"` и значением `"Hello!"` внутри: +======= +For instance, the variable `message` can be imagined as a box labelled `"message"` with the value `"Hello!"` in it: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ![](variable.svg) @@ -105,6 +128,11 @@ let user = 'John' Мы также можем изменить его столько раз, сколько захотим: +<<<<<<< HEAD +======= +We can also change it as many times as we want: + +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let message; @@ -136,11 +164,31 @@ alert(hello); // Hello world! alert(message); // Hello world! ``` +<<<<<<< HEAD ````warn header="Повторное объявление вызывает ошибку" Переменная может быть объявлена только один раз. +======= +````warn header="Declaring twice triggers an error" +A variable should be declared only once. + +A repeated declaration of the same variable is an error: + +```js run +let message = "This"; + +// repeated 'let' leads to an error +let message = "That"; // SyntaxError: 'message' has already been declared +``` +So, we should declare a variable once and then refer to it without `let`. +```` + +```smart header="Functional languages" +It's interesting to note that there exist so-called [pure functional](https://en.wikipedia.org/wiki/Purely_functional_programming) programming languages, such as [Haskell](https://en.wikipedia.org/wiki/Haskell), that forbid changing variable values. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Повторное объявление той же переменной является ошибкой: +<<<<<<< HEAD ```js run let message = "Это"; @@ -156,6 +204,9 @@ let message = "Другое"; // SyntaxError: 'message' has already been declare В таких языках однажды сохранённое "в коробку" значение остаётся там навсегда. Если нам нужно сохранить что-то другое, язык заставляет нас создать новую коробку (объявить новую переменную). Мы не можем использовать старую переменную. Хотя на первый взгляд это может показаться немного странным, эти языки вполне подходят для серьёзной разработки. Более того, есть такая область, как параллельные вычисления, где это ограничение даёт определённые преимущества. Изучение такого языка (даже если вы не планируете использовать его в ближайшее время) рекомендуется для расширения кругозора. +======= +Though it may seem a little odd at first sight, these languages are quite capable of serious development. More than that, there are areas like parallel computations where this limitation confers certain benefits. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` ## Имена переменных [#variable-naming] @@ -193,19 +244,32 @@ let 1a; // не может начинаться с цифры let my-name; // дефис '-' не разрешён в имени ``` +<<<<<<< HEAD ```smart header="Регистр имеет значение" Переменные с именами `apple` и `APPLE` -- это две разные переменные. ``` ````smart header="Нелатинские буквы разрешены, но не рекомендуются" Можно использовать любой язык, включая кириллицу или даже иероглифы, например: +======= +```smart header="Case matters" +Variables named `apple` and `APPLE` are two different variables. +``` + +````smart header="Non-Latin letters are allowed, but not recommended" +It is possible to use any language, including Cyrillic letters, Chinese logograms and so on, like this: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js let имя = '...'; let 我 = '...'; ``` +<<<<<<< HEAD Технически здесь нет ошибки, такие имена разрешены, но есть международная традиция использовать английский язык в именах переменных. Даже если мы пишем небольшой скрипт, у него может быть долгая жизнь впереди. Людям из других стран, возможно, придётся прочесть его не один раз. +======= +Technically, there is no error here. Such names are allowed, but there is an international convention to use English in variable names. Even if we're writing a small script, it may have a long life ahead. People from other countries may need to read it sometime. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```` ````warn header="Зарезервированные имена" @@ -260,13 +324,21 @@ const myBirthday = '18.04.1982'; myBirthday = '01.01.2001'; // ошибка, константу нельзя перезаписать! ``` +<<<<<<< HEAD Если программист уверен, что переменная никогда не будет меняться, он может гарантировать это и наглядно донести до каждого, объявив её через `const`. ### Константы в верхнем регистре +======= +When a programmer is sure that a variable will never change, they can declare it with `const` to guarantee and communicate that fact to everyone. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Широко распространена практика использования констант в качестве псевдонимов для трудно запоминаемых значений, которые известны до начала исполнения скрипта. +<<<<<<< HEAD Названия таких констант пишутся с использованием заглавных букв и подчёркивания. +======= +There is a widespread practice to use constants as aliases for difficult-to-remember values that are known before execution. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Например, сделаем константы для различных цветов в "шестнадцатеричном формате": @@ -289,17 +361,30 @@ alert(color); // #FF7F00 Когда мы должны использовать для констант заглавные буквы, а когда называть их нормально? Давайте разберёмся и с этим. +<<<<<<< HEAD Название "константа" просто означает, что значение переменной никогда не меняется. Но есть константы, которые известны до выполнения (например, шестнадцатеричное значение для красного цвета), а есть константы, которые *вычисляются* во время выполнения сценария, но не изменяются после их первоначального назначения. Например: +======= +Being a "constant" just means that a variable's value never changes. But some constants are known before execution (like a hexadecimal value for red) and some constants are *calculated* in run-time, during the execution, but do not change after their initial assignment. + +For instance: + +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js const pageLoadTime = /* время, потраченное на загрузку веб-страницы */; ``` +<<<<<<< HEAD Значение `pageLoadTime` неизвестно до загрузки страницы, поэтому её имя записано обычными, а не прописными буквами. Но это всё ещё константа, потому что она не изменяется после назначения. Другими словами, константы с именами, записанными заглавными буквами, используются только как псевдонимы для "жёстко закодированных" значений. +======= +The value of `pageLoadTime` is not known before the page load, so it's named normally. But it's still a constant because it doesn't change after the assignment. + +In other words, capital-named constants are only used as aliases for "hard-coded" values. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## Придумывайте правильные имена @@ -307,18 +392,31 @@ const pageLoadTime = /* время, потраченное на загрузку Название переменной должно иметь ясный и понятный смысл, говорить о том, какие данные в ней хранятся. +<<<<<<< HEAD Именование переменных -- это один из самых важных и сложных навыков в программировании. Быстрый взгляд на имена переменных может показать, какой код был написан новичком, а какой -- опытным разработчиком. В реальном проекте большая часть времени тратится на изменение и расширение существующей кодовой базы, а не на написание чего-то совершенно нового с нуля. Когда мы возвращаемся к коду после какого-то промежутка времени, гораздо легче найти информацию, которая хорошо размечена. Или, другими словами, когда переменные имеют хорошие имена. +======= +Variable naming is one of the most important and complex skills in programming. A glance at variable names can reveal which code was written by a beginner versus an experienced developer. + +In a real project, most of the time is spent modifying and extending an existing code base rather than writing something completely separate from scratch. When we return to some code after doing something else for a while, it's much easier to find information that is well-labelled. Or, in other words, when the variables have good names. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Пожалуйста, потратьте время на обдумывание правильного имени переменной перед её объявлением. Делайте так, и будете вознаграждены. Несколько хороших правил: +<<<<<<< HEAD - Используйте легко читаемые имена, такие как `userName` или `shoppingCart`. - Избегайте использования аббревиатур или коротких имён, таких как `a`, `b`, `c`, за исключением тех случаев, когда вы точно знаете, что так нужно. - Делайте имена максимально описательными и лаконичными. Примеры плохих имён: `data` и `value`. Такие имена ничего не говорят. Их можно использовать только в том случае, если из контекста кода очевидно, какие данные хранит переменная. - Договоритесь с вашей командой об используемых терминах. Если посетитель сайта называется "user", тогда мы должны называть связанные с ним переменные `currentUser` или `newUser`, а не, к примеру, `currentVisitor` или `newManInTown`. +======= +- Use human-readable names like `userName` or `shoppingCart`. +- Stay away from abbreviations or short names like `a`, `b`, and `c`, unless you know what you're doing. +- Make names maximally descriptive and concise. Examples of bad names are `data` and `value`. Such names say nothing. It's only okay to use them if the context of the code makes it exceptionally obvious which data or value the variable is referencing. +- Agree on terms within your team and in your mind. If a site visitor is called a "user" then we should name related variables `currentUser` or `newUser` instead of `currentVisitor` or `newManInTown`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Звучит просто? Действительно, это так, но на практике для создания описательных и кратких имён переменных зачастую требуется подумать. Действуйте. diff --git a/1-js/02-first-steps/05-types/article.md b/1-js/02-first-steps/05-types/article.md index 5410236e3c..9ce3fc8eb8 100644 --- a/1-js/02-first-steps/05-types/article.md +++ b/1-js/02-first-steps/05-types/article.md @@ -1,10 +1,18 @@ # Типы данных +<<<<<<< HEAD Значение в JavaScript всегда относится к данным определённого типа. Например, это может быть строка или число. Есть восемь основных типов данных в JavaScript. В этой главе мы рассмотрим их в общем, а в следующих главах поговорим подробнее о каждом. Переменная в JavaScript может содержать любые данные. В один момент там может быть строка, а в другой - число: +======= +A value in JavaScript is always of a certain type. For example, a string or a number. + +There are eight 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. + +We can put any type in a variable. For example, a variable can at one moment be a string and then store a number: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js // Не будет ошибкой @@ -12,9 +20,15 @@ let message = "hello"; message = 123456; ``` +<<<<<<< HEAD Языки программирования, в которых такое возможно, называются "динамически типизированными". Это значит, что типы данных есть, но переменные не привязаны ни к одному из них. ## Число +======= +Programming languages that allow such things, such as JavaScript, are called "dynamically typed", meaning that there exist data types, but variables are not bound to any of them. + +## Number +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js let n = 123; @@ -46,15 +60,26 @@ n = 12.345; alert( "не число" / 2 ); // NaN, такое деление является ошибкой ``` +<<<<<<< HEAD Значение `NaN` "прилипчиво". Любая математическая операция с `NaN` возвращает `NaN`: +======= + `NaN` is sticky. Any further mathematical operation on `NaN` returns `NaN`: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run alert( NaN + 1 ); // NaN alert( 3 * NaN ); // NaN +<<<<<<< HEAD alert( "не число" / 2 - 1 ); // NaN ``` Если где-то в математическом выражении есть `NaN`, то оно распространяется на весь результат (есть только одно исключение: `NaN ** 0` равно `1`). +======= + alert( "not a number" / 2 - 1 ); // NaN + ``` + + So, if there's a `NaN` somewhere in a mathematical expression, it propagates to the whole result (there's only one exception to that: `NaN ** 0` is `1`). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```smart header="Математические операции -- безопасны" Математические операции в JavaScript "безопасны". Мы можем делать что угодно: делить на ноль, обращаться с нечисловыми строками как с числами и т.д. @@ -66,7 +91,39 @@ n = 12.345; Подробнее о работе с числами мы поговорим в главе . +<<<<<<< HEAD ## BigInt +======= +## BigInt [#bigint-type] + +In JavaScript, the "number" type cannot safely represent integer values larger than (253-1) (that's `9007199254740991`), or less than -(253-1) for negatives. + +To be really precise, the "number" type can store larger integers (up to 1.7976931348623157 * 10308), but outside of the safe integer range ±(253-1) there'll be a precision error, because not all digits fit into the fixed 64-bit storage. So an "approximate" value may be stored. + +For example, these two numbers (right above the safe range) are the same: + +```js +console.log(9007199254740991 + 1); // 9007199254740992 +console.log(9007199254740991 + 2); // 9007199254740992 +``` + +So to say, all odd integers greater than (253-1) can't be stored at all in the "number" type. + +For most purposes ±(253-1) range is quite enough, but sometimes we need the entire range of really big integers, e.g. for cryptography or microsecond-precision timestamps. + +`BigInt` type was recently added to the language to represent integers of arbitrary length. + +A `BigInt` value is created by appending `n` to the end of an integer: + +```js +// the "n" at the end means it's a BigInt +const bigInt = 1234567890123456789012345678901234567890n; +``` + +As `BigInt` numbers are rarely needed, we don't cover them here, but devoted them a separate chapter . Read it when you need such big numbers. + +## String +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 В JavaScript тип `number` не может безопасно работать с числами, большими, чем (253-1) (т. е. `9007199254740991`) или меньшими, чем -(253-1) для отрицательных чисел. @@ -75,17 +132,62 @@ n = 12.345; Например, эти два числа (прямо за пределами безопасного диапазона) совпадают: ```js +<<<<<<< HEAD console.log(9007199254740991 + 1); // 9007199254740992 console.log(9007199254740991 + 2); // 9007199254740992 +======= +let str = "Hello"; +let str2 = 'Single quotes are ok too'; +let phrase = `can embed another ${str}`; +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` То есть все нечетные целые числа, большие чем (253-1), вообще не могут храниться в типе `number`. В большинстве случаев безопасного диапазона чисел от -(253-1) до (253-1) вполне достаточно, но иногда нам требуется весь диапазон действительно гигантских целых чисел без каких-либо ограничений или пропущенных значений внутри него. Например, в криптографии или при использовании метки времени («timestamp») с микросекундами. +<<<<<<< HEAD Тип `BigInt` был добавлен в JavaScript, чтобы дать возможность работать с целыми числами произвольной длины. Чтобы создать значение типа `BigInt`, необходимо добавить `n` в конец числового литерала: +======= +Double and single quotes are "simple" quotes. There's practically 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: + +```js run +let name = "John"; + +// embed a variable +alert( `Hello, *!*${name}*/!*!` ); // Hello, John! + +// embed an expression +alert( `the result is *!*${1 + 2}*/!*` ); // the result is 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. + +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) +``` + +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 called "char". + +In JavaScript, there is no such type. There's only one type: `string`. A string may consist of zero characters (be empty), one character or many of them. +``` + +## Boolean (logical type) + +The boolean type has only two values: `true` and `false`. + +This type is commonly used to store yes/no values: `true` means "yes, correct", and `false` means "no, incorrect". + +For instance: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js // символ "n" в конце означает, что это BigInt @@ -180,7 +282,11 @@ let age = null; Это просто специальное значение, которое представляет собой "ничего", "пусто" или "значение неизвестно". +<<<<<<< HEAD В приведённом выше коде указано, что значение переменной `age` неизвестно. +======= +The code above states that `age` is unknown. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## Значение "undefined" @@ -193,6 +299,7 @@ let age = null; ```js run let age; +<<<<<<< HEAD alert(age); // выведет "undefined" ``` @@ -202,23 +309,47 @@ alert(age); // выведет "undefined" let age = 123; // изменяем значение на undefined +======= +alert(age); // shows "undefined" +``` + +Technically, it is possible to explicitly assign `undefined` to a variable: + +```js run +let age = 100; + +// change the value to undefined +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 age = undefined; alert(age); // "undefined" ``` +<<<<<<< HEAD ...Но так делать не рекомендуется. Обычно `null` используется для присвоения переменной "пустого" или "неизвестного" значения, а `undefined` -- для проверок, была ли переменная назначена. +======= +...But we don't recommend doing that. Normally, one uses `null` to assign an "empty" or "unknown" value to a variable, while `undefined` is reserved as a default initial value for unassigned things. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## Объекты и символы Тип `object` (объект) -- особенный. +<<<<<<< HEAD Все остальные типы называются "примитивными", потому что их значениями могут быть только простые значения (будь то строка, или число, или что-то ещё). В объектах же хранят коллекции данных или более сложные структуры. Объекты занимают важное место в языке и требуют особого внимания. Мы разберёмся с ними в главе после того, как узнаем больше о примитивах. +======= +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. + +Being that important, objects deserve a special treatment. 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 the sake of completeness, but also postpone the details till we know objects. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Тип `symbol` (символ) используется для создания уникальных идентификаторов в объектах. Мы упоминаем здесь о нём для полноты картины, изучим этот тип после объектов. +<<<<<<< HEAD ## Оператор typeof [#type-typeof] Оператор `typeof` возвращает тип аргумента. Это полезно, когда мы хотим обрабатывать значения различных типов по-разному или просто хотим сделать проверку. @@ -241,6 +372,11 @@ typeof (50 + " Квартир"); // Выведет "string" Другими словами, скобки необходимы для определения типа значения, которое получилось в результате выполнения выражения в них. Вызов `typeof x` возвращает строку с именем типа: +======= +The `typeof` operator returns the type of the operand. It's useful when we want to process values of different types differently or just want to do a quick check. + +A call to `typeof x` returns a string with the type name: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js typeof undefined // "undefined" @@ -270,13 +406,29 @@ typeof alert // "function" (3) Последние три строки нуждаются в пояснении: +<<<<<<< HEAD 1. `Math` — это встроенный объект, который предоставляет математические операции и константы. Мы рассмотрим его подробнее в главе . Здесь он служит лишь примером объекта. 2. Результатом вызова `typeof null` является `"object"`. Это официально признанная ошибка в `typeof`, ведущая начало с времён создания JavaScript и сохранённая для совместимости. Конечно, `null` не является объектом. Это специальное значение с отдельным типом. 3. Вызов `typeof alert` возвращает `"function"`, потому что `alert` является функцией. Мы изучим функции в следующих главах, где заодно увидим, что в JavaScript нет специального типа "функция". Функции относятся к объектному типу. Но `typeof` обрабатывает их особым образом, возвращая `"function"`. Так тоже повелось от создания JavaScript. Формально это неверно, но может быть удобным на практике. +======= +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 an officially recognized error in `typeof`, coming from very early days of JavaScript and kept for compatibility. Definitely, `null` is not an object. It is a special value with a separate type of its own. The behavior of `typeof` is wrong here. +3. The result of `typeof alert` is `"function"`, because `alert` is a function. We'll study functions in the next chapters where we'll also see that there's no special "function" type in JavaScript. Functions belong to the object type. But `typeof` treats them differently, returning `"function"`. That also comes from the early days of JavaScript. Technically, such behavior isn't correct, but can be convenient in practice. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 + +```smart header="The `typeof(x)` syntax" +You may also come across another syntax: `typeof(x)`. It's the same as `typeof x`. +To put it clear: `typeof` is an operator, not a function. The parentheses here aren't a part of `typeof`. It's the kind of parentheses used for mathematical grouping. + +Usually, such parentheses contain a mathematical expression, such as `(2 + 2)`, but here they contain only one argument `(x)`. Syntactically, they allow to avoid a space between the `typeof` operator and its argument, and some people like it. + +Some people prefer `typeof(x)`, although the `typeof x` syntax is much more common. +``` ## Итого +<<<<<<< HEAD В JavaScript есть 8 основных типов данных. - Семь из них называют «примитивными» типами данных: @@ -289,11 +441,31 @@ typeof alert // "function" (3) - `symbol` для уникальных идентификаторов. - И один не является «примитивным» и стоит особняком: - `object` для более сложных структур данных. +======= +There are 8 basic data types in JavaScript. + +- Seven primitive data types: + - `number` for numbers of any kind: integer or floating-point, integers are limited by ±(253-1). + - `bigint` for integer numbers of arbitrary length. + - `string` for strings. A string may have zero 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`. + - `symbol` for unique identifiers. +- And one non-primitive data type: + - `object` for more complex data structures. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Оператор `typeof` позволяет нам увидеть, какой тип данных сохранён в переменной. +<<<<<<< HEAD - Имеет две формы: `typeof x` или `typeof(x)`. - Возвращает строку с именем типа. Например, `"string"`. - Для `null` возвращается `"object"` -- это ошибка в языке, на самом деле это не объект. +======= +- Usually used as `typeof x`, but `typeof(x)` is also possible. +- 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 В следующих главах мы сконцентрируемся на примитивных значениях, а когда познакомимся с ними, перейдём к объектам. diff --git a/1-js/02-first-steps/06-alert-prompt-confirm/1-simple-page/task.md b/1-js/02-first-steps/06-alert-prompt-confirm/1-simple-page/task.md index c758136a6f..045cf1de75 100644 --- a/1-js/02-first-steps/06-alert-prompt-confirm/1-simple-page/task.md +++ b/1-js/02-first-steps/06-alert-prompt-confirm/1-simple-page/task.md @@ -2,8 +2,14 @@ importance: 4 --- +<<<<<<< HEAD # Простая страница Создайте страницу, которая спрашивает имя у пользователя и выводит его. +======= +# A simple page + +Create a web-page that asks for a name and outputs it. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 [demo] 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 index 6a0bce1713..0329a037fe 100644 --- a/1-js/02-first-steps/06-alert-prompt-confirm/article.md +++ b/1-js/02-first-steps/06-alert-prompt-confirm/article.md @@ -1,3 +1,4 @@ +<<<<<<< HEAD # Взаимодействие: alert, prompt, confirm Так как мы будем использовать браузер как демо-среду, нам нужно познакомиться с несколькими функциями его интерфейса, а именно: `alert`, `prompt` и `confirm`. @@ -7,21 +8,41 @@ С этой функцией мы уже знакомы. Она показывает сообщение и ждёт, пока пользователь нажмёт кнопку "ОК". Например: +======= +# Interaction: alert, prompt, confirm + +As we'll be using the browser as our demo environment, let's see a couple of functions to interact with the user: `alert`, `prompt` and `confirm`. + +## alert + +This one we've seen already. It shows a message and waits for the user to press "OK". + +For example: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run alert("Hello"); ``` +<<<<<<< HEAD Это небольшое окно с сообщением называется *модальным окном*. Понятие *модальное* означает, что пользователь не может взаимодействовать с интерфейсом остальной части страницы, нажимать на другие кнопки и т.д. до тех пор, пока взаимодействует с окном. В данном случае -- пока не будет нажата кнопка "OK". ## prompt Функция `prompt` принимает два аргумента: +======= +The mini-window with the message is called a *modal window*. The word "modal" means that the visitor can't interact with the rest of the page, press other buttons, etc, until they have dealt with the window. In this case -- until they press "OK". + +## prompt + +The function `prompt` accepts two arguments: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js no-beautify result = prompt(title, [default]); ``` +<<<<<<< HEAD Этот код отобразит модальное окно с текстом, полем для ввода текста и кнопками OK/Отмена. `title` @@ -50,26 +71,68 @@ alert(`Тебе ${age} лет!`); // Тебе 100 лет! Второй параметр является необязательным, но если не указать его, то Internet Explorer вставит строку `"undefined"` в поле для ввода. Запустите код в Internet Explorer и посмотрите на результат: +======= +It shows a modal window with a text message, an input field for the visitor, and the buttons OK/Cancel. + +`title` +: The text to show the visitor. + +`default` +: An optional second parameter, the initial value for the input field. + +```smart header="The square brackets in syntax `[...]`" +The square brackets around `default` in the syntax above denote that the parameter is optional, not required. +``` + +The visitor can type something in the prompt input field and press OK. Then we get that text in the `result`. Or they can cancel the input by pressing Cancel or hitting the `key:Esc` key, then we get `null` as the `result`. + +The call to `prompt` returns the text from the input field or `null` if the input was canceled. + +For instance: + +```js run +let age = prompt('How old are you?', 100); + +alert(`You are ${age} years old!`); // You are 100 years old! +``` + +````warn header="In IE: always supply a `default`" +The second parameter is optional, but if we don't supply it, Internet Explorer will insert the text `"undefined"` into the prompt. + +Run this code in Internet Explorer to see: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let test = prompt("Test"); ``` +<<<<<<< HEAD Чтобы `prompt` хорошо выглядел в IE, рекомендуется всегда указывать второй параметр: ```js run let test = prompt("Test", ''); // <-- для IE +======= +So, for prompts to look good in IE, we recommend always providing the second argument: + +```js run +let test = prompt("Test", ''); // <-- for IE +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` ```` ## confirm +<<<<<<< HEAD Синтаксис: +======= +The syntax: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js result = confirm(question); ``` +<<<<<<< HEAD Функция `confirm` отображает модальное окно с текстом вопроса `question` и двумя кнопками: OK и Отмена. Результат -- `true`, если нажата кнопка OK. В других случаях -- `false`. @@ -103,3 +166,38 @@ alert( isBoss ); // true, если нажата OK 2. Визуальное отображение окон зависит от браузера, и мы не можем изменить их вид. Такова цена простоты. Есть другие способы показать более приятные глазу окна с богатой функциональностью для взаимодействия с пользователем, но если "навороты" не имеют значения, то данные методы работают отлично. +======= +The function `confirm` shows a modal window with a `question` and two buttons: OK and Cancel. + +The result is `true` if OK is pressed and `false` otherwise. + +For example: + +```js run +let isBoss = confirm("Are you the boss?"); + +alert( isBoss ); // true if OK is pressed +``` + +## Summary + +We covered 3 browser-specific functions to interact with visitors: + +`alert` +: shows a message. + +`prompt` +: shows a message asking the user to input text. It returns the text or, if Cancel button or `key:Esc` is clicked, `null`. + +`confirm` +: shows a message and waits for the user to press "OK" or "Cancel". It returns `true` for OK and `false` for Cancel/`key:Esc`. + +All these methods are modal: they pause script execution and don't allow the visitor to interact with the rest of the page until the window has been dismissed. + +There are two limitations shared by all the methods above: + +1. The exact location of the modal window is determined by the browser. Usually, it's in the center. +2. The exact look of the window also depends on the browser. We can't modify it. + +That is the price for simplicity. There are other ways to show nicer windows and richer interaction with the visitor, but if "bells and whistles" do not matter much, these methods work just fine. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/1-js/02-first-steps/07-type-conversions/article.md b/1-js/02-first-steps/07-type-conversions/article.md index bd413fa539..876c3c0f68 100644 --- a/1-js/02-first-steps/07-type-conversions/article.md +++ b/1-js/02-first-steps/07-type-conversions/article.md @@ -1,3 +1,4 @@ +<<<<<<< HEAD # Преобразование типов Чаще всего операторы и функции автоматически приводят переданные им значения к нужному типу. @@ -19,17 +20,45 @@ Например, `alert(value)` преобразует значение к строке. Также мы можем использовать функцию `String(value)`, чтобы преобразовать значение к строке: +======= +# Type Conversions + +Most of the time, operators and functions automatically convert the values given to them to the right type. + +For example, `alert` automatically converts any value to a string to show it. Mathematical operations convert values to numbers. + +There are also cases when we need to explicitly convert a value to the expected type. + +```smart header="Not talking about objects yet" +In this chapter, we won't cover objects. For now, we'll just be talking about primitives. + +Later, after we learn about objects, in the chapter we'll see how objects fit in. +``` + +## String Conversion + +String conversion happens when we need the string form of a value. + +For example, `alert(value)` does it to show the value. + +We can also call the `String(value)` function to convert a value to a string: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let value = true; alert(typeof value); // boolean *!* +<<<<<<< HEAD value = String(value); // теперь value это строка "true" +======= +value = String(value); // now value is a string "true" +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 alert(typeof value); // string */!* ``` +<<<<<<< HEAD Преобразование происходит очевидным образом. `false` становится `"false"`, `null` становится `"null"` и т.п. ## Численное преобразование @@ -43,16 +72,36 @@ alert( "6" / "2" ); // 3, строки преобразуются в числа ``` Мы можем использовать функцию `Number(value)`, чтобы явно преобразовать `value` к числу: +======= +String conversion is mostly obvious. A `false` becomes `"false"`, `null` becomes `"null"`, etc. + +## Numeric Conversion + +Numeric conversion in mathematical functions and expressions happens automatically. + +For example, when division `/` is applied to non-numbers: + +```js run +alert( "6" / "2" ); // 3, strings are converted to numbers +``` + +We can use the `Number(value)` function to explicitly convert a `value` to a number: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let str = "123"; alert(typeof str); // string +<<<<<<< HEAD let num = Number(str); // становится числом 123 +======= +let num = Number(str); // becomes a number 123 +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 alert(typeof num); // number ``` +<<<<<<< HEAD Явное преобразование часто применяется, когда мы ожидаем получить число из строкового контекста, например из текстовых полей форм. Если строка не может быть явно приведена к числу, то результатом преобразования будет `NaN`. Например: @@ -77,10 +126,37 @@ alert(age); // NaN, преобразование не удалось ```js run alert( Number(" 123 ") ); // 123 alert( Number("123z") ); // NaN (ошибка чтения числа на месте символа "z") +======= +Explicit conversion is usually required when we read a value from a string-based source like a text form but expect a number to be entered. + +If the string is not a valid number, the result of such a conversion is `NaN`. For instance: + +```js run +let age = Number("an arbitrary string instead of a number"); + +alert(age); // NaN, conversion failed +``` + +Numeric conversion rules: + +| Value | Becomes... | +|-------|-------------| +|`undefined`|`NaN`| +|`null`|`0`| +|true and false | `1` and `0` | +| `string` | Whitespaces (includes spaces, tabs `\t`, newlines `\n` etc.) from the start and end are removed. If the remaining string is empty, the result is `0`. Otherwise, the number is "read" from the string. An error gives `NaN`. | + +Examples: + +```js run +alert( Number(" 123 ") ); // 123 +alert( Number("123z") ); // NaN (error reading a number at "z") +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 alert( Number(true) ); // 1 alert( Number(false) ); // 0 ``` +<<<<<<< HEAD Учтите, что `null` и `undefined` ведут себя по-разному. Так, `null` становится нулём, тогда как `undefined` приводится к `NaN`. Большинство математических операторов также производит данное преобразование, как мы увидим в следующей главе. @@ -97,11 +173,30 @@ alert( Number(false) ); // 0 - Все остальные значения становятся `true`. Например: +======= +Please note that `null` and `undefined` behave differently here: `null` becomes zero while `undefined` becomes `NaN`. + +Most mathematical operators also perform such conversion, we'll see that in the next chapter. + +## Boolean Conversion + +Boolean conversion is the simplest one. + +It happens in logical operations (later we'll meet condition tests and other similar things) but can also be performed explicitly with a call to `Boolean(value)`. + +The conversion rule: + +- Values that are intuitively "empty", like `0`, an empty string, `null`, `undefined`, and `NaN`, become `false`. +- Other values become `true`. + +For instance: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run alert( Boolean(1) ); // true alert( Boolean(0) ); // false +<<<<<<< HEAD alert( Boolean("Привет!") ); // true alert( Boolean("") ); // false ``` @@ -126,10 +221,37 @@ alert( Boolean(" ") ); // пробел это тоже true (любая непу Преобразование подчиняется правилам: | Значение | Становится... | +======= +alert( Boolean("hello") ); // true +alert( Boolean("") ); // false +``` + +````warn header="Please note: the string with zero `\"0\"` is `true`" +Some languages (namely PHP) treat `"0"` as `false`. But in JavaScript, a non-empty string is always `true`. + +```js run +alert( Boolean("0") ); // true +alert( Boolean(" ") ); // spaces, also true (any non-empty string is true) +``` +```` + +## Summary + +The three most widely used type conversions are to string, to number, and to boolean. + +**`String Conversion`** -- Occurs when we output something. Can be performed with `String(value)`. The conversion to string is usually obvious for primitive values. + +**`Numeric Conversion`** -- Occurs in math operations. Can be performed with `Number(value)`. + +The conversion follows the rules: + +| Value | Becomes... | +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 |-------|-------------| |`undefined`|`NaN`| |`null`|`0`| |true / false | `1 / 0` | +<<<<<<< HEAD | `string` | Пробельные символы по краям обрезаются. Далее, если остаётся пустая строка, то получаем `0`, иначе из непустой строки "считывается" число. При ошибке результат `NaN`.| **`Логическое`** -- Происходит в логических операциях. Может быть вызвано с помощью `Boolean(value)`. @@ -148,3 +270,23 @@ alert( Boolean(" ") ); // пробел это тоже true (любая непу - `"0"` и строки из одних пробелов типа `" "` при логическом преобразовании всегда `true`. В этой главе мы не говорили об объектах. Мы вернёмся к ним позже, в главе , посвящённой только объектам, сразу после того, как узнаем больше про основы JavaScript. +======= +| `string` | The string is read "as is", whitespaces (includes spaces, tabs `\t`, newlines `\n` etc.) from both sides are ignored. An empty string becomes `0`. An error gives `NaN`. | + +**`Boolean Conversion`** -- Occurs in logical operations. Can be performed with `Boolean(value)`. + +Follows the rules: + +| Value | Becomes... | +|-------|-------------| +|`0`, `null`, `undefined`, `NaN`, `""` |`false`| +|any other value| `true` | + + +Most of these rules are easy to understand and memorize. The notable exceptions where people usually make mistakes are: + +- `undefined` is `NaN` as a number, not `0`. +- `"0"` and space-only strings like `" "` are true as a boolean. + +Objects aren't covered here. We'll return to them later in the chapter that is devoted exclusively to objects after we learn more basic things about JavaScript. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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 index 77ebe8974f..20e70da07f 100644 --- 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 @@ -1,4 +1,9 @@ +<<<<<<< HEAD Ответ: +======= + +The answer is: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 - `a = 2` - `b = 2` @@ -8,10 +13,18 @@ ```js run no-beautify let a = 1, b = 1; +<<<<<<< HEAD alert( ++a ); // 2, префиксная форма возвращает новое значение alert( b++ ); // 1, постфиксная форма возвращает старое значение alert( a ); // 2, значение увеличено один раз alert( b ); // 2, значение увеличено один раз +======= +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 +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` 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 index c4ea257da1..4936bcb51f 100644 --- 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 @@ -2,9 +2,15 @@ importance: 5 --- +<<<<<<< HEAD # Постфиксная и префиксная формы Чему будут равны переменные `a`, `b`, `c` и `d` в примере ниже? +======= +# The postfix and prefix forms + +What are the final values of all variables `a`, `b`, `c` and `d` after the code below? +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js let a = 1, b = 1; 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 index 6c7306fb43..e55a4a1449 100644 --- 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 @@ -1,5 +1,12 @@ +<<<<<<< HEAD Ответ: - `a = 4` (умножено на 2) - `x = 5` (вычислено как 1 + 4) +======= +The answer is: + +- `a = 4` (multiplied by 2) +- `x = 5` (calculated as 1 + 4) +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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 index 242266f946..1f3669aa17 100644 --- 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 @@ -2,9 +2,15 @@ importance: 3 --- +<<<<<<< HEAD # Результат присваивания Чему будут равны переменные `a` и `x` после исполнения кода в примере ниже? +======= +# Assignment result + +What are the values of `a` and `x` after the code below? +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js let 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 index 24d683549d..3e7e43f290 100644 --- 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 @@ -16,6 +16,7 @@ undefined + 1 = NaN // (6) " \t \n" - 2 = -2 // (7) ``` +<<<<<<< HEAD 1. Сложение со строкой `"" + 1` преобразует `1` к строке: `"" + 1 = "1"`, и в следующем случае `"1" + 0` работает то же самое правило. 2. Вычитание `-` (как и большинство математических операторов) работает только с числами, пустая строка `""` приводится к `0`. 3. Сложение со строкой превращает число `5` в строку и добавляет к строке. @@ -23,3 +24,12 @@ undefined + 1 = NaN // (6) 5. `null` становится `0` после численного преобразования. 6. `undefined` становится `NaN` после численного преобразования. 7. Пробельные символы, такие как `\t` и `\n`, по краям строки игнорируются при преобразовании в число, так что строка `" \t \n"`, аналогично пустой строке, становится `0` после численного преобразования. +======= +1. The addition with a string `"" + 1` converts `1` to a string: `"" + 1 = "1"`, and then we have `"1" + 0`, the same rule is applied. +2. The subtraction `-` (like most math operations) only works with numbers, it converts an empty string `""` to `0`. +3. The addition with a string appends the number `5` to the string. +4. The subtraction always converts to numbers, so it makes `" -9 "` a number `-9` (ignoring spaces around it). +5. `null` becomes `0` after the numeric conversion. +6. `undefined` becomes `NaN` after the numeric conversion. +7. Space characters are trimmed off string start and end when a string is converted to a number. Here the whole string consists of space characters, such as `\t`, `\n` and a "regular" space between them. So, similarly to an empty string, it becomes `0`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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 index ccabd3689b..e6fbf7a141 100644 --- 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 @@ -2,9 +2,15 @@ importance: 5 --- +<<<<<<< HEAD # Преобразование типов Какой результат будет у выражений ниже? +======= +# Type conversions + +What are results of these expressions? +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js no-beautify "" + 1 + 0 @@ -23,4 +29,8 @@ undefined + 1 " \t \n" - 2 ``` +<<<<<<< HEAD Подумайте как следует, запишите ответы и сверьтесь с решением. +======= +Think well, write down and then compare with the answer. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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 index ed5f105448..5ac0229b1b 100644 --- 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 @@ -1,3 +1,4 @@ +<<<<<<< HEAD Причина в том, что окно запроса возвращает пользовательский ввод как строку. Поэтому переменные получают значения `"1"` и `"2"` соответственно. @@ -5,10 +6,20 @@ ```js run let a = "1"; // prompt("Первое число?", 1); let b = "2"; // prompt("Второе число?", 2); +======= +The reason is that prompt returns user input as a string. + +So variables have values `"1"` and `"2"` respectively. + +```js run +let a = "1"; // prompt("First number?", 1); +let b = "2"; // prompt("Second number?", 2); +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 alert(a + b); // 12 ``` +<<<<<<< HEAD Нам нужно привести строки к числам перед применением оператора `+`. Например, с помощью `Number()` или вставки `+` перед ними. Вставить `+` можно прямо перед `prompt`: @@ -16,17 +27,38 @@ alert(a + b); // 12 ```js run let a = +prompt("Первое число?", 1); let b = +prompt("Второе число?", 2); +======= +What we should do is to convert strings to numbers before `+`. For example, using `Number()` or prepending them with `+`. + +For example, right before `prompt`: + +```js run +let a = +prompt("First number?", 1); +let b = +prompt("Second number?", 2); +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 alert(a + b); // 3 ``` +<<<<<<< HEAD Или внутри `alert`: ```js run let a = prompt("Первое число?", 1); let b = prompt("Второе число?", 2); +======= +Or in the `alert`: + +```js run +let a = prompt("First number?", 1); +let b = prompt("Second number?", 2); +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 alert(+a + +b); // 3 ``` +<<<<<<< HEAD В последнем варианте унарный и бинарный `+` используются вместе. Выглядит забавно, правда? +======= +Using both unary and binary `+` in the latest code. Looks funny, doesn't it? +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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 index e390f8c62e..792058354a 100644 --- 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 @@ -2,6 +2,7 @@ importance: 5 --- +<<<<<<< HEAD # Исправьте сложение Ниже приведён код, который запрашивает у пользователя два числа и показывает их сумму. @@ -13,6 +14,19 @@ importance: 5 ```js run let a = prompt("Первое число?", 1); let b = prompt("Второе число?", 2); +======= +# Fix the addition + +Here's a code that asks the user for two numbers and shows their sum. + +It works incorrectly. The output in the example below is `12` (for default prompt values). + +Why? Fix it. The result should be `3`. + +```js run +let a = prompt("First number?", 1); +let b = prompt("Second number?", 2); +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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 index 95685286e7..5d0e84560d 100644 --- a/1-js/02-first-steps/08-operators/article.md +++ b/1-js/02-first-steps/08-operators/article.md @@ -1,3 +1,4 @@ +<<<<<<< HEAD # Базовые операторы, математика Многие операторы знакомы нам ещё со школы: сложение `+`, умножение `*`, вычитание `-` и так далее. @@ -10,6 +11,20 @@ - *Операнд* -- то, к чему применяется оператор. Например, в умножении `5 * 2` есть два операнда: левый операнд равен `5`, а правый операнд равен `2`. Иногда их называют "аргументами" вместо "операндов". - *Унарным* называется оператор, который применяется к одному операнду. Например, оператор унарный минус `"-"` меняет знак числа на противоположный: +======= +# Basic operators, maths + +We know many operators from school. They are things like addition `+`, multiplication `*`, subtraction `-`, and so on. + +In this chapter, we’ll start with simple operators, then concentrate on JavaScript-specific aspects, 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: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let x = 1; @@ -17,6 +32,7 @@ *!* x = -x; */!* +<<<<<<< HEAD alert( x ); // -1, применили унарный минус ``` - *Бинарным* называется оператор, который применяется к двум операндам. Тот же минус существует и в бинарной форме: @@ -62,6 +78,53 @@ alert( 8 % 4 ); // 0, остаток от деления 8 на 4 В школьной математике мы записываем это как ab. Например: +======= + 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, in the examples above we have two different operators that share the same symbol: the negation operator, a unary operator that reverses the sign, and the subtraction operator, a binary operator that subtracts one number from another. + +## Maths + +The following math operations are supported: + +- Addition `+`, +- Subtraction `-`, +- Multiplication `*`, +- Division `/`, +- Remainder `%`, +- Exponentiation `**`. + +The first four are straightforward, while `%` and `**` need a few words about them. + +### Remainder % + +The remainder operator `%`, despite its appearance, is not related to percents. + +The result of `a % b` is the [remainder](https://en.wikipedia.org/wiki/Remainder) of the integer division of `a` by `b`. + +For instance: + +```js run +alert( 5 % 2 ); // 1, the remainder of 5 divided by 2 +alert( 8 % 3 ); // 2, the remainder of 8 divided by 3 +alert( 8 % 4 ); // 0, the remainder of 8 divided by 4 +``` + +### Exponentiation ** + +The exponentiation operator `a ** b` raises `a` to the power of `b`. + +In school maths, we write that as ab. + +For instance: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run alert( 2 ** 2 ); // 2² = 4 @@ -69,6 +132,7 @@ alert( 2 ** 3 ); // 2³ = 8 alert( 2 ** 4 ); // 2⁴ = 16 ``` +<<<<<<< HEAD Математически, оператор работает и для нецелых чисел. Например, квадратный корень является возведением в степень ½: ```js run @@ -91,12 +155,41 @@ alert(s); // моястрока Обратите внимание, если хотя бы один операнд является строкой, то второй будет также преобразован в строку. Например: +======= +Just like in maths, the exponentiation operator is defined for non-integer numbers as well. + +For example, a square root is an exponentiation by ½: + +```js run +alert( 4 ** (1/2) ); // 2 (power of 1/2 is the same as a square root) +alert( 8 ** (1/3) ); // 2 (power of 1/3 is the same as a cubic root) +``` + + +## String concatenation with binary + + +Let's meet the 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 any of the operands is a string, then the other one is converted to a string too. + +For example: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run alert( '1' + 2 ); // "12" alert( 2 + '1' ); // "21" ``` +<<<<<<< HEAD Как видите, не важно, первый или второй операнд является строкой. Вот пример посложнее: @@ -126,6 +219,42 @@ alert( '6' / '2' ); // 3, оба операнда приводятся к чис ```js run // Не влияет на числа +======= +See, it doesn't matter whether the first operand is a string or the second one. + +Here's a more complex example: + +```js run +alert(2 + 2 + '1' ); // "41" and not "221" +``` + +Here, operators work one after another. The first `+` sums two numbers, so it returns `4`, then the next `+` adds the string `1` to it, so it's like `4 + '1' = '41'`. + +```js run +alert('1' + 2 + 2); // "122" and not "14" +``` +Here, the first operand is a string, the compiler treats the other two operands as strings too. The `2` gets concatenated to `'1'`, so it's like `'1' + 2 = "12"` and `"12" + 2 = "122"`. + +The binary `+` is the only operator that supports strings in such a way. Other arithmetic operators work only with numbers and always convert their operands to numbers. + +Here's the demo for subtraction and division: + +```js run +alert( 6 - '2' ); // 4, converts '2' to a number +alert( '6' / '2' ); // 3, converts both operands to numbers +``` + +## 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 +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 let x = 1; alert( +x ); // 1 @@ -133,32 +262,52 @@ let y = -2; alert( +y ); // -2 *!* +<<<<<<< HEAD // Преобразует не числа в числа +======= +// Converts non-numbers +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 alert( +true ); // 1 alert( +"" ); // 0 */!* ``` +<<<<<<< HEAD На самом деле это то же самое, что и `Number(...)`, только короче. Необходимость преобразовывать строки в числа возникает очень часто. Например, обычно значения полей HTML-формы — это строки. А что, если их нужно, к примеру, сложить? Бинарный плюс сложит их как строки: +======= +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: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let apples = "2"; let oranges = "3"; +<<<<<<< HEAD alert( apples + oranges ); // "23", так как бинарный плюс объединяет строки ``` Поэтому используем унарный плюс, чтобы преобразовать к числу: +======= +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: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let apples = "2"; let oranges = "3"; *!* +<<<<<<< HEAD // оба операнда предварительно преобразованы в числа alert( +apples + +oranges ); // 5 */!* @@ -204,6 +353,53 @@ alert( +apples + +oranges ); // 5 Давайте отметим, что в таблице приоритетов также есть оператор присваивания `=`. У него один из самых низких приоритетов: `2`. Именно поэтому, когда переменной что-либо присваивают, например, `x = 2 * 2 + 1`, то сначала выполнится арифметика, а уже затем произойдёт присваивание `=` с сохранением результата в `x`. +======= +// 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 default 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 default order, we can use them to change it. For example, write `(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-US/docs/Web/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 | +|------------|------|------| +| ... | ... | ... | +| 14 | unary plus | `+` | +| 14 | unary negation | `-` | +| 13 | exponentiation | `**` | +| 12 | multiplication | `*` | +| 12 | division | `/` | +| 11 | addition | `+` | +| 11 | subtraction | `-` | +| ... | ... | ... | +| 2 | assignment | `=` | +| ... | ... | ... | + +As we can see, the "unary plus" has a priority of `14` which is higher than the `11` 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 `2`. + +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`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js let x = 2 * 2 + 1; @@ -211,6 +407,7 @@ let x = 2 * 2 + 1; alert( x ); // 5 ``` +<<<<<<< HEAD ### Присваивание = возвращает значение Тот факт, что `=` является оператором, а не "магической" конструкцией языка, имеет интересные последствия. @@ -220,6 +417,17 @@ alert( x ); // 5 Вызов `x = value` записывает `value` в `x` *и возвращает его*. Благодаря этому присваивание можно использовать как часть более сложного выражения: +======= +### Assignment = returns a value + +The fact of `=` being an operator, not a "magical" language construct has an interesting implication. + +All operators in JavaScript return a value. That's obvious for `+` and `-`, but also true for `=`. + +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: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let a = 1; @@ -233,6 +441,7 @@ alert( a ); // 3 alert( c ); // 0 ``` +<<<<<<< HEAD В примере выше результатом `(a = b + 1)` будет значение, которое присваивается переменной `a` (то есть `3`). Потом оно используется для дальнейших вычислений. Забавное применение присваивания, не так ли? Нам нужно понимать, как это работает, потому что иногда это можно увидеть в JavaScript-библиотеках. @@ -242,6 +451,17 @@ alert( c ); // 0 ### Присваивание по цепочке Рассмотрим ещё одну интересную возможность: цепочку присваиваний. +======= +In the example above, the result of expression `(a = b + 1)` is the value which was assigned to `a` (that is `3`). It is then used for further evaluations. + +Funny code, isn't it? We should understand how it works, because sometimes we see it in JavaScript libraries. + +Although, please don't write the code like that. Such tricks definitely don't make code clearer or readable. + +### Chaining assignments + +Another interesting feature is the ability to chain assignments: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let a, b, c; @@ -255,15 +475,22 @@ alert( b ); // 4 alert( c ); // 4 ``` +<<<<<<< HEAD Такое присваивание работает справа налево. Сначала вычисляется самое правое выражение `2 + 2`, и затем результат присваивается переменным слева: `c`, `b` и `a`. В конце у всех переменных будет одно значение. Опять-таки, чтобы код читался легче, лучше разделять подобные конструкции на несколько строчек: +======= +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. + +Once again, for the purposes of readability it's better to split such code into few lines: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js c = 2 + 2; b = c; a = c; ``` +<<<<<<< HEAD Польза от такого стиля особенно ощущается при быстром просмотре кода. ## Сокращённая арифметика с присваиванием @@ -271,6 +498,15 @@ a = c; Часто нужно применить оператор к переменной и сохранить результат в ней же. Например: +======= +That's easier to read, especially when eye-scanning the code fast. + +## Modify-in-place + +We often need to apply an operator to a variable and store the new result in that same variable. + +For example: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js let n = 2; @@ -278,23 +514,39 @@ n = n + 5; n = n * 2; ``` +<<<<<<< HEAD Эту запись можно укоротить при помощи совмещённых операторов `+=` и `*=`: ```js run let n = 2; n += 5; // теперь n = 7 (работает как n = n + 5) n *= 2; // теперь n = 14 (работает как 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) +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 alert( n ); // 14 ``` +<<<<<<< HEAD Подобные краткие формы записи существуют для **всех** арифметических и побитовых операторов: `/=`, `-=`, `**=` и так далее. Вызов с присваиванием имеет в точности такой же приоритет, как обычное присваивание, то есть выполнится после большинства других операций: +======= +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: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let n = 2; +<<<<<<< HEAD n *= 3 + 5; alert( n ); // 16 (сначала выполнится правая часть, выражение идентично n *= 8) @@ -320,10 +572,38 @@ alert( n ); // 16 (сначала выполнится правая часть, ```js run no-beautify let counter = 2; counter--; // работает как counter = counter - 1, просто запись короче +======= +n *= 3 + 5; // right part evaluated first, same as n *= 8 + +alert( n ); // 16 +``` + +## 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 +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 alert( counter ); // 1 ``` ```warn +<<<<<<< HEAD Инкремент/декремент можно применить только к переменной. Попытка использовать его на значении, типа 5++, приведёт к ошибке. ``` @@ -339,6 +619,23 @@ alert( n ); // 16 (сначала выполнится правая часть, Давайте проясним этот момент. Как мы знаем, все операторы возвращают значение. Операторы инкремента/декремента не исключение. Префиксная форма возвращает новое значение, в то время как постфиксная форма возвращает старое (до увеличения/уменьшения числа). Чтобы увидеть разницу, вот небольшой пример: +======= +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: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let counter = 1; @@ -347,6 +644,7 @@ let a = ++counter; // (*) alert(a); // *!*2*/!* ``` +<<<<<<< HEAD В строке `(*)` *префиксная* форма `++counter` увеличивает `counter` и возвращает новое значение `2`. Так что `alert` покажет `2`. Теперь посмотрим на постфиксную форму: @@ -354,45 +652,80 @@ alert(a); // *!*2*/!* ```js run let counter = 1; let a = counter++; // (*) меняем ++counter на counter++ +======= +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++ +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 alert(a); // *!*1*/!* ``` +<<<<<<< HEAD В строке `(*)` *постфиксная* форма `counter++` также увеличивает `counter`, но возвращает *старое* значение (которое было до увеличения). Так что `alert` покажет `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: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let counter = 0; counter++; ++counter; +<<<<<<< HEAD alert( counter ); // 2, обе строки сделали одно и то же ``` - Если хочется тут же использовать результат, то нужна префиксная форма: +======= + 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: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let counter = 0; alert( ++counter ); // 1 ``` +<<<<<<< HEAD - Если нужно увеличить и при этом получить значение переменной *до увеличения* – нужна постфиксная форма: +======= +- If we'd like to increment a value but use its previous value, we need the postfix form: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let counter = 0; alert( counter++ ); // 0 ``` +<<<<<<< HEAD ````smart header="Инкремент/декремент можно использовать в любых выражениях" Операторы `++/--` могут также использоваться внутри выражений. Их приоритет выше, чем у большинства других арифметических операций. Например: +======= +````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: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let counter = 1; alert( 2 * ++counter ); // 4 ``` +<<<<<<< HEAD Сравните с: ```js run @@ -405,6 +738,20 @@ alert( 2 * counter++ ); // 2, потому что counter++ возвращает При беглом чтении кода можно с лёгкостью пропустить такой `counter++`, и будет неочевидно, что переменная увеличивается. Лучше использовать стиль "одна строка -- одно действие": +======= +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": +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let counter = 1; @@ -413,6 +760,7 @@ counter++; ``` ```` +<<<<<<< HEAD ## Побитовые операторы Побитовые операторы работают с 32-разрядными целыми числами (при необходимости приводят к ним), на уровне их внутреннего двоичного представления. @@ -440,12 +788,40 @@ 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, when we need to fiddle with numbers on the very lowest (bitwise) level. We won't need these operators any time soon, as web development has little use of them, but in some special areas, such as cryptography, they are useful. You can read the [Bitwise Operators](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#bitwise_operators) chapter on MDN when a need arises. + +## 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: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run *!* let a = (1 + 2, 3 + 4); */!* +<<<<<<< HEAD alert( a ); // 7 (результат вычисления 3 + 4) ``` @@ -475,9 +851,34 @@ alert(a); // 3 ```js // три операции в одной строке +======= +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 the rest is ignored. It's like `(a = 1 + 2), 3 + 4`. +``` + +Why do we need an operator that throws away everything except the last expression? + +Sometimes, people use it in more complex constructs to put several actions in one line. + +For example: + +```js +// three operations in one line +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 for (*!*a = 1, b = 3, c = a * b*/!*; a < 10; a++) { ... } ``` +<<<<<<< HEAD Такие трюки используются во многих JavaScript-фреймворках. Вот почему мы упоминаем их. Но обычно они не улучшают читабельность кода, поэтому стоит хорошо подумать, прежде чем их использовать. +======= +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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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 index 16e343a8aa..3219f93115 100644 --- 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 @@ -2,7 +2,11 @@ ```js no-beautify 5 > 4 → true +<<<<<<< HEAD "ананас" > "яблоко" → false +======= +"apple" > "pineapple" → false +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 "2" > "12" → true undefined == null → true undefined === null → false @@ -10,6 +14,7 @@ null == "\n0\n" → false null === +"\n0\n" → false ``` +<<<<<<< HEAD Разъяснения: 1. Очевидно, `true`. @@ -19,3 +24,14 @@ null === +"\n0\n" → false 5. Строгое сравнение разных типов, поэтому `false`. 6. Аналогично `(4)`, `null` равен только `undefined`. 7. Строгое сравнение разных типов. +======= +Some of the reasons: + +1. Obviously, true. +2. Dictionary comparison, hence false. `"a"` is smaller than `"p"`. +3. Again, dictionary comparison, first char `"2"` is greater than the first char `"1"`. +4. Values `null` and `undefined` equal each other only. +5. Strict equality is strict. Different types from both sides lead to false. +6. Similar to `(4)`, `null` only equals `undefined`. +7. Strict equality of different types. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/1-js/02-first-steps/09-comparison/1-comparison-questions/task.md b/1-js/02-first-steps/09-comparison/1-comparison-questions/task.md index 4491ae4c3e..d1d52b1475 100644 --- a/1-js/02-first-steps/09-comparison/1-comparison-questions/task.md +++ b/1-js/02-first-steps/09-comparison/1-comparison-questions/task.md @@ -2,6 +2,7 @@ importance: 5 --- +<<<<<<< HEAD # Операторы сравнения Каким будет результат этих выражений? @@ -9,6 +10,15 @@ importance: 5 ```js no-beautify 5 > 4 "ананас" > "яблоко" +======= +# Comparisons + +What will be the result for these expressions? + +```js no-beautify +5 > 4 +"apple" > "pineapple" +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 "2" > "12" undefined == null undefined === null diff --git a/1-js/02-first-steps/09-comparison/article.md b/1-js/02-first-steps/09-comparison/article.md index ced30d93d8..80ca2bf6f4 100644 --- a/1-js/02-first-steps/09-comparison/article.md +++ b/1-js/02-first-steps/09-comparison/article.md @@ -1,3 +1,4 @@ +<<<<<<< HEAD # Операторы сравнения Многие операторы сравнения известны нам из математики. @@ -83,12 +84,102 @@ alert( '01' == 1 ); // true, строка '01' становится числом Логическое значение `true` становится `1`, а `false` – `0`. Например: +======= +# Comparisons + +We know many comparison operators from maths. + +In JavaScript they are written like this: + +- Greater/less than: a > b, a < b. +- Greater/less than or equals: a >= b, a <= b. +- Equals: `a == b`, please note the double equality sign `==` means the equality test, while a single one `a = b` means an assignment. +- Not equals: In maths the notation is , but in JavaScript it's written as a != b. + +In this article we'll learn more about different types of comparisons, how JavaScript makes them, including important peculiarities. + +At the end you'll find a good recipe to avoid "JavaScript quirks"-related issues. + +## Boolean is the result + +All comparison operators return a boolean value: + +- `true` -- means "yes", "correct" or "the truth". +- `false` -- means "no", "wrong" or "not the truth". + +For example: + +```js run +alert( 2 > 1 ); // true (correct) +alert( 2 == 1 ); // false (wrong) +alert( 2 != 1 ); // true (correct) +``` + +A comparison result can be assigned to a variable, just like any value: + +```js run +let result = 5 > 4; // assign the result of the comparison +alert( result ); // true +``` + +## String comparison + +To see whether a string is greater than another, JavaScript uses the so-called "dictionary" or "lexicographical" order. + +In other words, strings are compared letter-by-letter. + +For example: + +```js run +alert( 'Z' > 'A' ); // true +alert( 'Glow' > 'Glee' ); // true +alert( 'Bee' > 'Be' ); // true +``` + +The algorithm to compare two strings is simple: + +1. Compare the first character of both strings. +2. If the first character from the first string is greater (or less) than the other string's, then the first string is greater (or less) than the second. We're done. +3. Otherwise, if both strings' first characters are the same, compare the second characters the same way. +4. Repeat until the end of either string. +5. If both strings end at the same length, then they are equal. Otherwise, the longer string is greater. + +In the first example above, the comparison `'Z' > 'A'` gets to a result at the first step. + +The second comparison `'Glow'` and `'Glee'` needs more steps as strings are compared character-by-character: + +1. `G` is the same as `G`. +2. `l` is the same as `l`. +3. `o` is greater than `e`. Stop here. The first string is greater. + +```smart header="Not a real dictionary, but Unicode order" +The comparison algorithm given above is roughly equivalent to the one used in dictionaries or phone books, but it's not exactly the same. + +For instance, case matters. A capital letter `"A"` is not equal to the lowercase `"a"`. Which one is greater? The lowercase `"a"`. Why? Because the lowercase character has a greater index in the internal encoding table JavaScript uses (Unicode). We'll get back to specific details and consequences of this in the chapter . +``` + +## Comparison of different types + +When comparing values of different types, JavaScript converts the values to numbers. + +For example: + +```js run +alert( '2' > 1 ); // true, string '2' becomes a number 2 +alert( '01' == 1 ); // true, string '01' becomes a number 1 +``` + +For boolean values, `true` becomes `1` and `false` becomes `0`. + +For example: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run alert( true == 1 ); // true alert( false == 0 ); // true ``` +<<<<<<< HEAD ````smart header="Забавное следствие" Возможна следующая ситуация: @@ -96,6 +187,15 @@ alert( false == 0 ); // true - Одно из них `true` как логическое значение, другое – `false`. Например: +======= +````smart header="A funny consequence" +It is possible that at the same time: + +- Two values are equal. +- One of them is `true` as a boolean and the other one is `false` as a boolean. + +For example: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let a = 0; @@ -107,23 +207,37 @@ alert( Boolean(b) ); // true alert(a == b); // true! ``` +<<<<<<< HEAD С точки зрения JavaScript, результат ожидаем. Равенство преобразует значения, используя числовое преобразование, поэтому `"0"` становится `0`. В то время как явное преобразование с помощью `Boolean` использует другой набор правил. ```` ## Строгое сравнение Использование обычного сравнения `==` может вызывать проблемы. Например, оно не отличает `0` от `false`: +======= +From JavaScript's standpoint, this result is quite normal. An equality check converts values using the numeric conversion (hence `"0"` becomes `0`), while the explicit `Boolean` conversion uses another set of rules. +```` + +## Strict equality + +A regular equality check `==` has a problem. It cannot differentiate `0` from `false`: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run alert( 0 == false ); // true ``` +<<<<<<< HEAD Та же проблема с пустой строкой: +======= +The same thing happens with an empty string: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run alert( '' == false ); // true ``` +<<<<<<< HEAD Это происходит из-за того, что операнды разных типов преобразуются оператором `==` к числу. В итоге, и пустая строка, и `false` становятся нулём. Как же тогда отличать `0` от `false`? @@ -148,18 +262,50 @@ alert( 0 === false ); // false, так как сравниваются разн При строгом равенстве `===` : Эти значения различны, так как различны их типы. +======= +This happens because operands of different types are converted to numbers by the equality operator `==`. An empty string, just like `false`, becomes a zero. + +What to do if we'd like to differentiate `0` from `false`? + +**A strict equality operator `===` checks the equality without type conversion.** + +In other words, if `a` and `b` are of different types, then `a === b` immediately returns `false` without an attempt to convert them. + +Let's try it: + +```js run +alert( 0 === false ); // false, because the types are different +``` + +There is also a "strict non-equality" operator `!==` analogous to `!=`. + +The strict equality operator is a bit longer to write, but makes it obvious what's going on and leaves less room for errors. + +## Comparison with null and undefined + +There's a non-intuitive behavior when `null` or `undefined` are compared to other values. + +For a strict equality check `===` +: These values are different, because each of them is a different type. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run alert( null === undefined ); // false ``` +<<<<<<< HEAD При нестрогом равенстве `==` : Эти значения равны друг другу и не равны никаким другим значениям. Это специальное правило языка. +======= +For a non-strict check `==` +: There's a special rule. These two are a "sweet couple": they equal each other (in the sense of `==`), but not any other value. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run alert( null == undefined ); // true ``` +<<<<<<< HEAD При использовании математических операторов и других операторов сравнения `< > <= >=` : Значения `null/undefined` преобразуются к числам: `null` становится `0`, а `undefined` – `NaN`. @@ -168,6 +314,16 @@ alert( 0 === false ); // false, так как сравниваются разн ### Странный результат сравнения null и 0 Сравним `null` с нулём: +======= +For maths and other comparisons `< > <= >=` +: `null/undefined` are converted to numbers: `null` becomes `0`, while `undefined` becomes `NaN`. + +Now let's see some funny things that happen when we apply these rules. And, what's more important, how to not fall into a trap with them. + +### Strange result: null vs 0 + +Let's compare `null` with a zero: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run alert( null > 0 ); // (1) false @@ -175,6 +331,7 @@ alert( null == 0 ); // (2) false alert( null >= 0 ); // (3) *!*true*/!* ``` +<<<<<<< HEAD С точки зрения математики это странно. Результат последнего сравнения говорит о том, что "`null` больше или равно нулю", тогда результат одного из сравнений выше должен быть `true`, но они оба ложны. Причина в том, что нестрогое равенство и сравнения `> < >= <=` работают по-разному. Сравнения преобразуют `null` в число, рассматривая его как `0`. Поэтому выражение (3) `null >= 0` истинно, а `null > 0` ложно. @@ -184,6 +341,17 @@ alert( null >= 0 ); // (3) *!*true*/!* ### Несравненное значение undefined Значение `undefined` несравнимо с другими значениями: +======= +Mathematically, that's strange. The last result states that "`null` is greater than or equal to zero", so in one of the comparisons above it must be `true`, but they are both false. + +The reason is that an equality check `==` and comparisons `> < >= <=` work differently. Comparisons convert `null` to a number, treating it as `0`. That's why (3) `null >= 0` is true and (1) `null > 0` is false. + +On the other hand, the equality check `==` for `undefined` and `null` is defined such that, without any conversions, they equal each other and don't equal anything else. That's why (2) `null == 0` is false. + +### An incomparable undefined + +The value `undefined` shouldn't be compared to other values: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run alert( undefined > 0 ); // false (1) @@ -191,6 +359,7 @@ alert( undefined < 0 ); // false (2) alert( undefined == 0 ); // false (3) ``` +<<<<<<< HEAD Почему же сравнение `undefined` с нулём всегда ложно? На это есть следующие причины: @@ -212,3 +381,26 @@ alert( undefined == 0 ); // false (3) - Значения разных типов при сравнении приводятся к числу. Исключением является сравнение с помощью операторов строгого равенства/неравенства. - Значения `null` и `undefined` равны `==` друг другу и не равны любому другому значению. - Будьте осторожны при использовании операторов сравнений вроде `>` и `<` с переменными, которые могут принимать значения `null/undefined`. Хорошей идеей будет сделать отдельную проверку на `null/undefined`. +======= +Why does it dislike zero so much? Always false! + +We get these results because: + +- Comparisons `(1)` and `(2)` return `false` because `undefined` gets converted to `NaN` and `NaN` is a special numeric value which returns `false` for all comparisons. +- The equality check `(3)` returns `false` because `undefined` only equals `null`, `undefined`, and no other value. + +### Avoid problems + +Why did we go over these examples? Should we remember these peculiarities all the time? Well, not really. Actually, these tricky things will gradually become familiar over time, but there's a solid way to avoid problems with them: + +- Treat any comparison with `undefined/null` except the strict equality `===` with exceptional care. +- Don't use comparisons `>= > < <=` with a variable which may be `null/undefined`, unless you're really sure of what you're doing. If a variable can have these values, check for them separately. + +## Summary + +- Comparison operators return a boolean value. +- Strings are compared letter-by-letter in the "dictionary" order. +- When values of different types are compared, they get converted to numbers (with the exclusion of a strict equality check). +- The values `null` and `undefined` equal `==` each other and do not equal any other value. +- Be careful when using comparisons like `>` or `<` with variables that can occasionally be `null/undefined`. Checking for `null/undefined` separately is a good idea. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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 45386667a1..37a1d69a79 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 @@ -6,7 +6,11 @@ importance: 2 Используя конструкцию `if..else`, напишите код, который будет спрашивать: 'Какое "официальное" название JavaScript?' +<<<<<<< HEAD Если пользователь вводит "ECMAScript", то показать: "Верно!", в противном случае -- отобразить: "Не знаете? ECMAScript!" +======= +If the visitor enters "ECMAScript", then output "Right!", otherwise -- output: "You don't know? ECMAScript!" +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ![](ifelse_task2.svg) diff --git a/1-js/02-first-steps/10-ifelse/article.md b/1-js/02-first-steps/10-ifelse/article.md index 598a1d25b5..8c23f9e8d8 100644 --- a/1-js/02-first-steps/10-ifelse/article.md +++ b/1-js/02-first-steps/10-ifelse/article.md @@ -1,4 +1,8 @@ +<<<<<<< HEAD # Условное ветвление: if, '?' +======= +# Conditional branching: if, '?' +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Иногда нам нужно выполнить различные действия в зависимости от условий. @@ -68,9 +72,13 @@ if (condition) { ## Блок "else" +<<<<<<< HEAD Инструкция `if` может содержать необязательный блок "else" ("иначе"). Он выполняется, когда условие ложно. Например: +======= +The `if` statement may contain an optional `else` block. It executes when the condition is falsy. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let year = prompt('В каком году была опубликована спецификация ECMAScript-2015?', ''); @@ -183,10 +191,17 @@ alert( message ); Поначалу может быть сложно понять, что происходит. Но при ближайшем рассмотрении мы видим, что это обычная последовательная проверка: +<<<<<<< HEAD 1. Первый знак вопроса проверяет `age < 3`. 2. Если верно -- возвращает `'Здравствуй, малыш!'`. В противном случае, проверяет выражение после двоеточия ':', вычисляет `age < 18`. 3. Если это верно -- возвращает `'Привет!'`. В противном случае, проверяет выражение после следующего двоеточия ':', вычисляет `age < 100`. 4. Если это верно -- возвращает `'Здравствуйте!'`. В противном случае, возвращает выражение после последнего двоеточия -- `'Какой необычный возраст!'`. +======= +1. The first question mark checks whether `age < 3`. +2. If true -- it returns `'Hi, baby!'`. Otherwise, it continues to the expression after the colon ":", checking `age < 18`. +3. If that's true -- it returns `'Hello!'`. Otherwise, it continues to the expression after the next colon ":", checking `age < 100`. +4. If that's true -- it returns `'Greetings!'`. Otherwise, it continues to the expression after the last colon ":", returning `'What an unusual age!'`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Вот как это выглядит при использовании `if..else`: diff --git a/1-js/02-first-steps/11-logical-operators/2-alert-or/solution.md b/1-js/02-first-steps/11-logical-operators/2-alert-or/solution.md index 935087ab0a..eac2307a80 100644 --- a/1-js/02-first-steps/11-logical-operators/2-alert-or/solution.md +++ b/1-js/02-first-steps/11-logical-operators/2-alert-or/solution.md @@ -6,8 +6,14 @@ alert( alert(1) || 2 || alert(3) ); Вызов `alert` не возвращает значения, или, иначе говоря, возвращает `undefined`. +<<<<<<< HEAD 1. Первый оператор ИЛИ `||` выполнит первый `alert(1)`. 2. Получит `undefined` и пойдёт дальше, ко второму операнду в поисках истинного значения. 3. Так как второй операнд `2` является истинным, то вычисления завершатся, результатом `undefined || 2` будет `2`, которое будет выведено внешним `alert( .... )`. +======= +1. The first OR `||` evaluates its left operand `alert(1)`. That shows the first message with `1`. +2. The `alert` returns `undefined`, so OR goes on to the second operand searching for a truthy value. +3. The second operand `2` is truthy, so the execution is halted, `2` is returned and then shown by the outer alert. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Второй оператор `||` не будет выполнен, выполнение до `alert(3)` не дойдёт, поэтому `3` выведено не будет. diff --git a/1-js/02-first-steps/11-logical-operators/3-alert-1-null-2/solution.md b/1-js/02-first-steps/11-logical-operators/3-alert-1-null-2/solution.md index ffe2b32a19..addcf93f4b 100644 --- a/1-js/02-first-steps/11-logical-operators/3-alert-1-null-2/solution.md +++ b/1-js/02-first-steps/11-logical-operators/3-alert-1-null-2/solution.md @@ -1,6 +1,6 @@ Ответ: `null`, потому что это первое "ложное" значение из списка. ```js run -alert( 1 && null && 2 ); +alert(1 && null && 2); ``` diff --git a/1-js/02-first-steps/11-logical-operators/6-check-if-in-range/task.md b/1-js/02-first-steps/11-logical-operators/6-check-if-in-range/task.md new file mode 100644 index 0000000000..fc9e336c15 --- /dev/null +++ b/1-js/02-first-steps/11-logical-operators/6-check-if-in-range/task.md @@ -0,0 +1,9 @@ +importance: 3 + +--- + +# Check the range between + +Write an `if` condition to check that `age` is between `14` and `90` inclusively. + +"Inclusively" means that `age` can reach the edges `14` or `90`. diff --git a/1-js/02-first-steps/11-logical-operators/7-check-if-out-range/task.md b/1-js/02-first-steps/11-logical-operators/7-check-if-out-range/task.md new file mode 100644 index 0000000000..9b947d00f8 --- /dev/null +++ b/1-js/02-first-steps/11-logical-operators/7-check-if-out-range/task.md @@ -0,0 +1,9 @@ +importance: 3 + +--- + +# Check the range outside + +Write an `if` condition to check that `age` is NOT between `14` and `90` inclusively. + +Create two variants: the first one using NOT `!`, the second one -- without it. diff --git a/1-js/02-first-steps/11-logical-operators/9-check-login/ifelse_task.svg b/1-js/02-first-steps/11-logical-operators/9-check-login/ifelse_task.svg new file mode 100644 index 0000000000..d22b518a91 --- /dev/null +++ b/1-js/02-first-steps/11-logical-operators/9-check-login/ifelse_task.svg @@ -0,0 +1 @@ +BeginCanceledCanceledWelcome!I don't know youWrong passwordWho's there?Password?CancelCancelAdminTheMasterOtherOther \ No newline at end of file diff --git a/1-js/02-first-steps/11-logical-operators/9-check-login/solution.md b/1-js/02-first-steps/11-logical-operators/9-check-login/solution.md new file mode 100644 index 0000000000..604606259f --- /dev/null +++ b/1-js/02-first-steps/11-logical-operators/9-check-login/solution.md @@ -0,0 +1,25 @@ + + +```js run demo +let userName = prompt("Who's there?", ''); + +if (userName === 'Admin') { + + let pass = prompt('Password?', ''); + + if (pass === 'TheMaster') { + alert( 'Welcome!' ); + } else if (pass === '' || pass === null) { + alert( 'Canceled' ); + } else { + alert( 'Wrong password' ); + } + +} else if (userName === '' || userName === null) { + alert( 'Canceled' ); +} else { + alert( "I don't know you" ); +} +``` + +Note the vertical indents inside the `if` blocks. They are technically not required, but make the code more readable. diff --git a/1-js/02-first-steps/11-logical-operators/article.md b/1-js/02-first-steps/11-logical-operators/article.md index 9a0465e822..f08902cee0 100644 --- a/1-js/02-first-steps/11-logical-operators/article.md +++ b/1-js/02-first-steps/11-logical-operators/article.md @@ -1,6 +1,10 @@ # Логические операторы +<<<<<<< HEAD В JavaScript есть семь логических операторов: +======= +There are four logical operators in JavaScript: `||` (OR), `&&` (AND), `!` (NOT), `??` (Nullish Coalescing). Here we cover the first three, the `??` operator is in the next article. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 - `||` (ИЛИ) - `||=` (Оператор логического присваивания ИЛИ) @@ -74,7 +78,11 @@ if (hour < 10 || hour > 18 || isWeekend) { } ``` +<<<<<<< HEAD ### ИЛИ "||" находит первое истинное значение [#or-finds-the-first-truthy-value] +======= +## OR "||" finds the first truthy value [#or-finds-the-first-truthy-value] +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Описанная выше логика соответствует традиционной. Теперь давайте поработаем с "дополнительными" возможностями JavaScript. @@ -94,30 +102,50 @@ result = value1 || value2 || value3; Значение возвращается в исходном виде, без преобразования. +<<<<<<< HEAD Другими словами, цепочка ИЛИ `||` возвращает первое истинное значение или последнее, если такое значение не найдено. +======= +In other words, a chain of OR `||` returns the first truthy value or the last one if no truthy value is found. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Например: ```js run +<<<<<<< HEAD alert( 1 || 0 ); // 1 (1 - истинное значение) alert( true || 'какая-то строка' ); // true alert( null || 1 ); // 1 (первое истинное значение) alert( null || 0 || 1 ); // 1 (первое истинное значение) alert( undefined || null || 0 ); // 0 (поскольку все ложно, возвращается последнее значение) +======= +alert( 1 || 0 ); // 1 (1 is truthy) + +alert( null || 1 ); // 1 (1 is the first truthy value) +alert( null || 0 || 1 ); // 1 (the first truthy value) + +alert( undefined || null || 0 ); // 0 (all falsy, returns the last value) +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` Это делает возможным более интересное применение оператора по сравнению с "чистым, традиционным, только булевым ИЛИ". 1. **Получение первого истинного значения из списка переменных или выражений.** +<<<<<<< HEAD Например, у нас есть переменные `firstName`, `lastName` и `nickName`, все они необязательные (т.е. они могут быть неопределенными или иметь ложные значения). Давайте воспользуемся оператором ИЛИ `||`, чтобы выбрать ту переменную, в которой есть данные, и показать её (или "Аноним", если ни в одной переменной данных нет): +======= + For instance, we have `firstName`, `lastName` and `nickName` variables, all optional (i.e. can be undefined or have falsy values). + + Let's use OR `||` to choose the one that has the data and show it (or `"Anonymous"` if nothing set): +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let firstName = ""; let lastName = ""; +<<<<<<< HEAD let nickName = "Суперкодер"; *!*alert( firstName || lastName || nickName || "Аноним"); // Суперкодер*/!* @@ -154,6 +182,35 @@ alert( undefined || null || 0 ); // 0 (поскольку все ложно, в ```js a ||= b; ``` +======= + let nickName = "SuperCoder"; + + *!* + alert( firstName || lastName || nickName || "Anonymous"); // SuperCoder + */!* + ``` + + If all variables were falsy, `"Anonymous"` would show up. + +2. **Short-circuit evaluation.** + + Another feature of OR `||` operator is the so-called "short-circuit" evaluation. + + It means that `||` processes its arguments until the first truthy value is reached, and then the value is returned immediately, without even touching the other argument. + + The importance of this feature becomes obvious if an operand isn't just a value, but an expression with a side effect, such as a variable assignment or a function call. + + In the example below, only the second message is printed: + + ```js run no-beautify + *!*true*/!* || alert("not printed"); + *!*false*/!* || alert("printed"); + ``` + + In the first line, the OR `||` operator stops the evaluation immediately upon seeing `true`, so the `alert` isn't run. + + Sometimes, people use this feature to execute commands only if the condition on the left part is falsy. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Оператор `||=` принимает два операнда и выполняет следующие действия: @@ -300,8 +357,13 @@ alert( 1 && 2 && 3 ); // 3 Таким образом, код `a && b || c && d` по существу такой же, как если бы выражения `&&` были в круглых скобках: `(a && b) || (c && d)`. ```` +<<<<<<< HEAD ````warn header="Не заменяйте `if` на `||` или `&&`" Иногда люди используют оператор И `&&` как «более короткий способ записи `if`-выражения». +======= +````warn header="Don't replace `if` with `||` or `&&`" +Sometimes, people use the AND `&&` operator as a "shorter way to write `if`". +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Например: @@ -318,6 +380,7 @@ let x = 1; ```js run let x = 1; +<<<<<<< HEAD if (x > 0) alert( 'x больше нуля!' ); ``` @@ -369,6 +432,14 @@ alert( greeting ) // "Привет, пользователь!" На практике, в отличие от `||=`, оператор `&&=` используется достаточно редко -- обычно, в комбинации с более сложными языковыми конструкциями, о которых мы будем говорить позже. Подобрать контекст для применения данного оператора -- довольно непростая задача. ## ! (НЕ) +======= +if (x > 0) alert( 'Greater than zero!' ); +``` + +Although, the variant with `&&` appears shorter, `if` is more obvious and tends to be a little bit more readable. So we recommend using every construct for its purpose: use `if` if we want `if` and use `&&` if we want AND. +```` + +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Оператор НЕ представлен восклицательным знаком `!`. diff --git a/1-js/02-first-steps/12-nullish-coalescing-operator/article.md b/1-js/02-first-steps/12-nullish-coalescing-operator/article.md new file mode 100644 index 0000000000..0b2f092ab7 --- /dev/null +++ b/1-js/02-first-steps/12-nullish-coalescing-operator/article.md @@ -0,0 +1,169 @@ +# Nullish coalescing operator '??' + +[recent browser="new"] + +The nullish coalescing operator is written as two question marks `??`. + +As it treats `null` and `undefined` similarly, we'll use a special term here, in this article. For brevity, we'll say that a value is "defined" when it's neither `null` nor `undefined`. + +The result of `a ?? b` is: +- if `a` is defined, then `a`, +- if `a` isn't defined, then `b`. + +In other words, `??` returns the first argument if it's not `null/undefined`. Otherwise, the second one. + +The nullish coalescing operator isn't anything completely new. It's just a nice syntax to get the first "defined" value of the two. + +We can rewrite `result = a ?? b` using the operators that we already know, like this: + +```js +result = (a !== null && a !== undefined) ? a : b; +``` + +Now it should be absolutely clear what `??` does. Let's see where it helps. + +The common use case for `??` is to provide a default value. + +For example, here we show `user` if its value isn't `null/undefined`, otherwise `Anonymous`: + +```js run +let user; + +alert(user ?? "Anonymous"); // Anonymous (user is undefined) +``` + +Here's the example with `user` assigned to a name: + +```js run +let user = "John"; + +alert(user ?? "Anonymous"); // John (user is not null/undefined) +``` + +We can also use a sequence of `??` to select the first value from a list that isn't `null/undefined`. + +Let's say we have a user's data in variables `firstName`, `lastName` or `nickName`. All of them may be not defined, if the user decided not to fill in the corresponding values. + +We'd like to display the user name using one of these variables, or show "Anonymous" if all of them are `null/undefined`. + +Let's use the `??` operator for that: + +```js run +let firstName = null; +let lastName = null; +let nickName = "Supercoder"; + +// shows the first defined value: +*!* +alert(firstName ?? lastName ?? nickName ?? "Anonymous"); // Supercoder +*/!* +``` + +## Comparison with || + +The OR `||` operator can be used in the same way as `??`, as it was described in the [previous chapter](info:logical-operators#or-finds-the-first-truthy-value). + +For example, in the code above we could replace `??` with `||` and still get the same result: + +```js run +let firstName = null; +let lastName = null; +let nickName = "Supercoder"; + +// shows the first truthy value: +*!* +alert(firstName || lastName || nickName || "Anonymous"); // Supercoder +*/!* +``` + +Historically, the OR `||` operator was there first. It's been there since the beginning of JavaScript, so developers were using it for such purposes for a long time. + +On the other hand, the nullish coalescing operator `??` was added to JavaScript only recently, and the reason for that was that people weren't quite happy with `||`. + +The important difference between them is that: +- `||` returns the first *truthy* value. +- `??` returns the first *defined* value. + +In other words, `||` doesn't distinguish between `false`, `0`, an empty string `""` and `null/undefined`. They are all the same -- falsy values. If any of these is the first argument of `||`, then we'll get the second argument as the result. + +In practice though, we may want to use default value only when the variable is `null/undefined`. That is, when the value is really unknown/not set. + +For example, consider this: + +```js run +let height = 0; + +alert(height || 100); // 100 +alert(height ?? 100); // 0 +``` + +- The `height || 100` checks `height` for being a falsy value, and it's `0`, falsy indeed. + - so the result of `||` is the second argument, `100`. +- The `height ?? 100` checks `height` for being `null/undefined`, and it's not, + - so the result is `height` "as is", that is `0`. + +In practice, the zero height is often a valid value, that shouldn't be replaced with the default. So `??` does just the right thing. + +## Precedence + +The precedence of the `??` operator is the same as `||`. They both equal `3` in the [MDN table](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence#Table). + +That means that, just like `||`, the nullish coalescing operator `??` is evaluated before `=` and `?`, but after most other operations, such as `+`, `*`. + +So we may need to add parentheses in expressions like this: + +```js run +let height = null; +let width = null; + +// important: use parentheses +let area = (height ?? 100) * (width ?? 50); + +alert(area); // 5000 +``` + +Otherwise, if we omit parentheses, then as `*` has the higher precedence than `??`, it would execute first, leading to incorrect results. + +```js +// without parentheses +let area = height ?? 100 * width ?? 50; + +// ...works this way (not what we want): +let area = height ?? (100 * width) ?? 50; +``` + +### Using ?? with && or || + +Due to safety reasons, JavaScript forbids using `??` together with `&&` and `||` operators, unless the precedence is explicitly specified with parentheses. + +The code below triggers a syntax error: + +```js run +let x = 1 && 2 ?? 3; // Syntax error +``` + +The limitation is surely debatable, it was added to the language specification with the purpose to avoid programming mistakes, when people start to switch from `||` to `??`. + +Use explicit parentheses to work around it: + +```js run +*!* +let x = (1 && 2) ?? 3; // Works +*/!* + +alert(x); // 2 +``` + +## Summary + +- The nullish coalescing operator `??` provides a short way to choose the first "defined" value from a list. + + It's used to assign default values to variables: + + ```js + // set height=100, if height is null or undefined + height = height ?? 100; + ``` + +- The operator `??` has a very low precedence, only a bit higher than `?` and `=`, so consider adding parentheses when using it in an expression. +- It's forbidden to use it with `||` or `&&` without explicit parentheses. diff --git a/1-js/02-first-steps/13-while-for/1-loop-last-value/solution.md b/1-js/02-first-steps/13-while-for/1-loop-last-value/solution.md index 8cb301626d..c040064b75 100644 --- a/1-js/02-first-steps/13-while-for/1-loop-last-value/solution.md +++ b/1-js/02-first-steps/13-while-for/1-loop-last-value/solution.md @@ -1,4 +1,8 @@ +<<<<<<< HEAD Ответ: `1`. +======= +The answer: `1`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let i = 3; @@ -8,13 +12,20 @@ while (i) { } ``` +<<<<<<< HEAD Каждое выполнение цикла уменьшает `i`. Проверка `while(i)` остановит цикл при `i = 0`. Соответственно, будет такая последовательность шагов цикла ("развернём" цикл): +======= +Every loop iteration decreases `i` by `1`. The check `while(i)` stops the loop when `i = 0`. + +Hence, the steps of the loop form the following sequence ("loop unrolled"): +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js let i = 3; +<<<<<<< HEAD alert(i--); // выведет 3, затем уменьшит i до 2 alert(i--) // выведет 2, затем уменьшит i до 1 @@ -22,4 +33,13 @@ alert(i--) // выведет 2, затем уменьшит i до 1 alert(i--) // выведет 1, затем уменьшит i до 0 // все, проверка while(i) не даст выполняться циклу дальше +======= +alert(i--); // shows 3, decreases i to 2 + +alert(i--) // shows 2, decreases i to 1 + +alert(i--) // shows 1, decreases i to 0 + +// done, while(i) check stops the loop +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` diff --git a/1-js/02-first-steps/13-while-for/1-loop-last-value/task.md b/1-js/02-first-steps/13-while-for/1-loop-last-value/task.md index a688d7f701..d53b4205fe 100644 --- a/1-js/02-first-steps/13-while-for/1-loop-last-value/task.md +++ b/1-js/02-first-steps/13-while-for/1-loop-last-value/task.md @@ -2,9 +2,15 @@ importance: 3 --- +<<<<<<< HEAD # Последнее значение цикла Какое последнее значение выведет этот код? Почему? +======= +# Last loop value + +What is the last value alerted by this code? Why? +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js let i = 3; diff --git a/1-js/02-first-steps/13-while-for/2-which-value-while/solution.md b/1-js/02-first-steps/13-while-for/2-which-value-while/solution.md index 2f053d259d..e8b680bdf8 100644 --- a/1-js/02-first-steps/13-while-for/2-which-value-while/solution.md +++ b/1-js/02-first-steps/13-while-for/2-which-value-while/solution.md @@ -1,24 +1,40 @@ +<<<<<<< HEAD Задача демонстрирует, как постфиксные/префиксные варианты могут повлиять на результат, когда используются в сравнениях. 1. **От 1 до 4** +======= +The task demonstrates how postfix/prefix forms can lead to different results when used in comparisons. + +1. **From 1 to 4** +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let i = 0; while (++i < 5) alert( i ); ``` +<<<<<<< HEAD Первое значение: `i = 1`, так как операция `++i` сначала увеличит `i`, а потом уже произойдёт сравнение и выполнение `alert`. Далее `2, 3, 4…` Значения выводятся одно за другим. Для каждого значения сначала происходит увеличение, а потом – сравнение, так как `++` стоит перед переменной. При `i = 4` произойдёт увеличение `i` до `5`, а потом сравнение `while (5 < 5)` – это неверно. Поэтому на этом цикл остановится, и значение `5` выведено не будет. 2. **От 1 до 5** +======= + The first value is `i = 1`, because `++i` first increments `i` and then returns the new value. So the first comparison is `1 < 5` and the `alert` shows `1`. + + Then follow `2, 3, 4…` -- the values show up one after another. The comparison always uses the incremented value, because `++` is before the variable. + + Finally, `i = 4` is incremented to `5`, the comparison `while(5 < 5)` fails, and the loop stops. So `5` is not shown. +2. **From 1 to 5** +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let i = 0; while (i++ < 5) alert( i ); ``` +<<<<<<< HEAD Первое значение: `i = 1`. Остановимся на нём подробнее. Оператор `i++` увеличивает `i`, возвращая старое значение, так что в сравнении `i++ < 5` будет участвовать `i = 0` (в отличие от `++i < 5`). Но последующий вызов `alert` уже не относится к этому выражению, так что получит новый `i = 1`. @@ -28,3 +44,14 @@ Остановимся на `i = 4`. Префиксная форма `++i` увеличила бы `i` и использовала бы в сравнении `5`. Но здесь мы имеем постфиксную форму `i++`, поэтому она увеличивает `i` до `5`, но возвращает старое значение. Таким образом, сравнение фактически равно `while (4 < 5)` -- `true`, поэтому срабатывает `alert`. Значение `i = 5` -- последнее, так как на следующем шаге `while (5 < 5)` -- `false`. +======= + The first value is again `i = 1`. The postfix form of `i++` increments `i` and then returns the *old* value, so the comparison `i++ < 5` will use `i = 0` (contrary to `++i < 5`). + + But the `alert` call is separate. It's another statement which executes after the increment and the comparison. So it gets the current `i = 1`. + + Then follow `2, 3, 4…` + + Let's stop on `i = 4`. The prefix form `++i` would increment it and use `5` in the comparison. But here we have the postfix form `i++`. So it increments `i` to `5`, but returns the old value. Hence the comparison is actually `while(4 < 5)` -- true, and the control goes on to `alert`. + + The value `i = 5` is the last one, because on the next step `while(5 < 5)` is false. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/1-js/02-first-steps/13-while-for/2-which-value-while/task.md b/1-js/02-first-steps/13-while-for/2-which-value-while/task.md index b07a3d8639..4b26d7f0b4 100644 --- a/1-js/02-first-steps/13-while-for/2-which-value-while/task.md +++ b/1-js/02-first-steps/13-while-for/2-which-value-while/task.md @@ -2,6 +2,7 @@ importance: 4 --- +<<<<<<< HEAD # Какие значения выведет цикл while? Для каждого цикла запишите, какие значения он выведет. Потом сравните с ответом. @@ -9,12 +10,25 @@ importance: 4 Оба цикла выводят `alert` с одинаковыми значениями или нет? 1. Префиксный вариант `++i`: +======= +# Which values does the while loop show? + +For every loop iteration, write down which value it outputs and then compare it with the solution. + +Both loops `alert` the same values, or not? + +1. The prefix form `++i`: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js let i = 0; while (++i < 5) alert( i ); ``` +<<<<<<< HEAD 2. Постфиксный вариант `i++` +======= +2. The postfix form `i++` +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js let i = 0; diff --git a/1-js/02-first-steps/13-while-for/3-which-value-for/solution.md b/1-js/02-first-steps/13-while-for/3-which-value-for/solution.md index a5a77699f2..eb87cacee0 100644 --- a/1-js/02-first-steps/13-while-for/3-which-value-for/solution.md +++ b/1-js/02-first-steps/13-while-for/3-which-value-for/solution.md @@ -1,4 +1,8 @@ +<<<<<<< HEAD **Ответ: от `0` до `4` в обоих случаях.** +======= +**The answer: from `0` to `4` in both cases.** +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run for (let i = 0; i < 5; ++i) alert( i ); @@ -6,6 +10,7 @@ for (let i = 0; i < 5; ++i) alert( i ); for (let i = 0; i < 5; i++) alert( i ); ``` +<<<<<<< HEAD Такой результат обусловлен алгоритмом работы `for`: 1. Выполнить единожды присваивание `i = 0` перед чем-либо (начало). @@ -13,3 +18,14 @@ for (let i = 0; i < 5; i++) alert( i ); 3. Если `true` -- выполнить тело цикла `alert(i)`, и затем `i++` Увеличение `i++` выполняется отдельно от проверки условия `(2)`, значение `i` при этом не используется, поэтому нет никакой разницы между `i++` и `++i`. +======= +That can be easily deducted from the algorithm of `for`: + +1. Execute once `i = 0` before everything (begin). +2. Check the condition `i < 5` +3. If `true` -- execute the loop body `alert(i)`, and then `i++` + +The increment `i++` is separated from the condition check (2). That's just another statement. + +The value returned by the increment is not used here, so there's no difference between `i++` and `++i`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/1-js/02-first-steps/13-while-for/3-which-value-for/task.md b/1-js/02-first-steps/13-while-for/3-which-value-for/task.md index 734b52cde8..c795233b4c 100644 --- a/1-js/02-first-steps/13-while-for/3-which-value-for/task.md +++ b/1-js/02-first-steps/13-while-for/3-which-value-for/task.md @@ -2,6 +2,7 @@ importance: 4 --- +<<<<<<< HEAD # Какие значения выведет цикл for? Для каждого цикла запишите, какие значения он выведет. Потом сравните с ответом. @@ -9,11 +10,24 @@ importance: 4 Оба цикла выведут `alert` с одинаковыми значениями или нет? 1. Постфиксная форма: +======= +# Which values get shown by the "for" loop? + +For each loop write down which values it is going to show. Then compare with the answer. + +Both loops `alert` same values or not? + +1. The postfix form: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js for (let i = 0; i < 5; i++) alert( i ); ``` +<<<<<<< HEAD 2. Префиксная форма: +======= +2. The prefix form: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js for (let i = 0; i < 5; ++i) alert( i ); diff --git a/1-js/02-first-steps/13-while-for/4-for-even/solution.md b/1-js/02-first-steps/13-while-for/4-for-even/solution.md index 61a5426bc5..241ee8fcab 100644 --- a/1-js/02-first-steps/13-while-for/4-for-even/solution.md +++ b/1-js/02-first-steps/13-while-for/4-for-even/solution.md @@ -8,4 +8,8 @@ for (let i = 2; i <= 10; i++) { } ``` +<<<<<<< HEAD Для проверки на чётность мы здесь используем оператор получения остатка от деления `%`. +======= +We use the "modulo" operator `%` to get the remainder and check for the evenness here. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/1-js/02-first-steps/13-while-for/4-for-even/task.md b/1-js/02-first-steps/13-while-for/4-for-even/task.md index 5674088a2f..b36febe2b5 100644 --- a/1-js/02-first-steps/13-while-for/4-for-even/task.md +++ b/1-js/02-first-steps/13-while-for/4-for-even/task.md @@ -2,8 +2,14 @@ importance: 5 --- +<<<<<<< HEAD # Выведите чётные числа При помощи цикла `for` выведите чётные числа от `2` до `10`. +======= +# Output even numbers in the loop + +Use the `for` loop to output even numbers from `2` to `10`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 [demo] diff --git a/1-js/02-first-steps/13-while-for/5-replace-for-while/task.md b/1-js/02-first-steps/13-while-for/5-replace-for-while/task.md index 0cdfe6a893..d8f5f0ac36 100644 --- a/1-js/02-first-steps/13-while-for/5-replace-for-while/task.md +++ b/1-js/02-first-steps/13-while-for/5-replace-for-while/task.md @@ -2,12 +2,22 @@ importance: 5 --- +<<<<<<< HEAD # Замените for на while Перепишите код, заменив цикл `for` на `while`, без изменения поведения цикла. +======= +# Replace "for" with "while" + +Rewrite the code changing the `for` loop to `while` without altering its behavior (the output should stay same). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run for (let i = 0; i < 3; i++) { alert( `number ${i}!` ); } ``` +<<<<<<< HEAD +======= + +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/1-js/02-first-steps/13-while-for/6-repeat-until-correct/solution.md b/1-js/02-first-steps/13-while-for/6-repeat-until-correct/solution.md index 721300cb0a..b1f2a61d66 100644 --- a/1-js/02-first-steps/13-while-for/6-repeat-until-correct/solution.md +++ b/1-js/02-first-steps/13-while-for/6-repeat-until-correct/solution.md @@ -3,6 +3,7 @@ let num; do { +<<<<<<< HEAD num = prompt("Введите число больше 100?", 0); } while (num <= 100 && num); ``` @@ -13,3 +14,15 @@ do { 2. Проверка `&& num` вычисляется в `false`, когда `num` имеет значение `null` или пустая строка `''`. В этом случае цикл `while` тоже нужно прекратить. Кстати, сравнение `num <= 100` при вводе `null` даст `true`, так что вторая проверка необходима. +======= + num = prompt("Enter a number greater than 100?", 0); +} while (num <= 100 && num); +``` + +The loop `do..while` repeats while both checks are truthy: + +1. The check for `num <= 100` -- that is, the entered value is still not greater than `100`. +2. The check `&& num` is false when `num` is `null` or an empty string. Then the `while` loop stops too. + +P.S. If `num` is `null` then `num <= 100` is `true`, so without the 2nd check the loop wouldn't stop if the user clicks CANCEL. Both checks are required. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/1-js/02-first-steps/13-while-for/6-repeat-until-correct/task.md b/1-js/02-first-steps/13-while-for/6-repeat-until-correct/task.md index 524e0c94e1..49b883ea17 100644 --- a/1-js/02-first-steps/13-while-for/6-repeat-until-correct/task.md +++ b/1-js/02-first-steps/13-while-for/6-repeat-until-correct/task.md @@ -2,6 +2,7 @@ importance: 5 --- +<<<<<<< HEAD # Повторять цикл, пока ввод неверен Напишите цикл, который предлагает `prompt` ввести число, большее `100`. Если посетитель ввёл другое число – попросить ввести ещё раз, и так далее. @@ -9,5 +10,14 @@ importance: 5 Цикл должен спрашивать число пока либо посетитель не введёт число, большее `100`, либо не нажмёт кнопку Отмена (ESC). Предполагается, что посетитель вводит только числа. Предусматривать обработку нечисловых строк в этой задаче необязательно. +======= +# Repeat until the input is correct + +Write a loop which prompts for a number greater than `100`. If the visitor enters another number -- ask them to input again. + +The loop must ask for a number until either the visitor enters a number greater than `100` or cancels the input/enters an empty line. + +Here we can assume that the visitor only inputs numbers. There's no need to implement a special handling for a non-numeric input in this task. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 [demo] diff --git a/1-js/02-first-steps/13-while-for/7-list-primes/solution.md b/1-js/02-first-steps/13-while-for/7-list-primes/solution.md index b98092afd5..541cf064a5 100644 --- a/1-js/02-first-steps/13-while-for/7-list-primes/solution.md +++ b/1-js/02-first-steps/13-while-for/7-list-primes/solution.md @@ -1,3 +1,4 @@ +<<<<<<< HEAD Существует множество алгоритмов решения этой задачи. Давайте воспользуемся вложенными циклами: @@ -11,11 +12,27 @@ ``` Решение с использованием метки: +======= +There are many algorithms for this task. + +Let's use a nested loop: + +```js +For each i in the interval { + check if i has a divisor from 1..i + if yes => the value is not a prime + if no => the value is a prime, show it +} +``` + +The code using a label: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let n = 10; nextPrime: +<<<<<<< HEAD for (let i = 2; i <= n; i++) { // Для всех i... for (let j = 2; j < i; j++) { // проверить, делится ли число.. @@ -27,3 +44,16 @@ for (let i = 2; i <= n; i++) { // Для всех i... ``` Конечно же, его можно оптимизировать с точки зрения производительности. Например, проверять все `j` не от `2` до `i`, а от `2` до квадратного корня из `i`. А для очень больших чисел – существуют более эффективные специализированные алгоритмы проверки простоты числа, например [квадратичное решето](https://ru.wikipedia.org/wiki/%D0%9C%D0%B5%D1%82%D0%BE%D0%B4_%D0%BA%D0%B2%D0%B0%D0%B4%D1%80%D0%B0%D1%82%D0%B8%D1%87%D0%BD%D0%BE%D0%B3%D0%BE_%D1%80%D0%B5%D1%88%D0%B5%D1%82%D0%B0) и [решето числового поля](https://ru.wikipedia.org/wiki/%D0%9E%D0%B1%D1%89%D0%B8%D0%B9_%D0%BC%D0%B5%D1%82%D0%BE%D0%B4_%D1%80%D0%B5%D1%88%D0%B5%D1%82%D0%B0_%D1%87%D0%B8%D1%81%D0%BB%D0%BE%D0%B2%D0%BE%D0%B3%D0%BE_%D0%BF%D0%BE%D0%BB%D1%8F). +======= +for (let i = 2; i <= n; i++) { // for each i... + + for (let j = 2; j < i; j++) { // look for a divisor.. + if (i % j == 0) continue nextPrime; // not a prime, go next i + } + + alert( i ); // a prime +} +``` + +There's a lot of space to optimize it. For instance, we could look for the divisors from `2` to square root of `i`. But anyway, if we want to be really efficient for large intervals, we need to change the approach and rely on advanced maths and complex algorithms like [Quadratic sieve](https://en.wikipedia.org/wiki/Quadratic_sieve), [General number field sieve](https://en.wikipedia.org/wiki/General_number_field_sieve) etc. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/1-js/02-first-steps/13-while-for/7-list-primes/task.md b/1-js/02-first-steps/13-while-for/7-list-primes/task.md index 2a9c03ac7d..d9abea5715 100644 --- a/1-js/02-first-steps/13-while-for/7-list-primes/task.md +++ b/1-js/02-first-steps/13-while-for/7-list-primes/task.md @@ -2,6 +2,7 @@ importance: 3 --- +<<<<<<< HEAD # Вывести простые числа Натуральное число, большее `1`, называется [простым](https://ru.wikipedia.org/wiki/%D0%9F%D1%80%D0%BE%D1%81%D1%82%D0%BE%D0%B5_%D1%87%D0%B8%D1%81%D0%BB%D0%BE), если оно ни на что не делится, кроме себя и `1`. @@ -15,3 +16,18 @@ importance: 3 Для `n = 10` результат должен быть `2,3,5,7`. P.S. Код также должен легко модифицироваться для любых других интервалов. +======= +# Output prime numbers + +An integer number greater than `1` is called a [prime](https://en.wikipedia.org/wiki/Prime_number) if it cannot be divided without a remainder by anything except `1` and itself. + +In other words, `n > 1` is a prime if it can't be evenly divided by anything except `1` and `n`. + +For example, `5` is a prime, because it cannot be divided without a remainder by `2`, `3` and `4`. + +**Write the code which outputs prime numbers in the interval from `2` to `n`.** + +For `n = 10` the result will be `2,3,5,7`. + +P.S. The code should work for any `n`, not be hard-tuned for any fixed value. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/1-js/02-first-steps/13-while-for/article.md b/1-js/02-first-steps/13-while-for/article.md index b7f3d4f67c..246d0f0921 100644 --- a/1-js/02-first-steps/13-while-for/article.md +++ b/1-js/02-first-steps/13-while-for/article.md @@ -1,3 +1,4 @@ +<<<<<<< HEAD # Циклы while и for При написании скриптов зачастую встаёт задача сделать однотипное действие много раз. @@ -37,11 +38,53 @@ while (condition) { ```js run let i = 0; while (i < 3) { // выводит 0, затем 1, затем 2 +======= +# Loops: while and for + +We often need to repeat actions. + +For example, outputting goods from a list one after another or just running the same code for each number from 1 to 10. + +*Loops* are a way to repeat the same code multiple times. + +```smart header="The for..of and for..in loops" +A small announcement for advanced readers. + +This article covers only basic loops: `while`, `do..while` and `for(..;..;..)`. + +If you came to this article searching for other types of loops, here are the pointers: + +- See [for..in](info:object#forin) to loop over object properties. +- See [for..of](info:array#loops) and [iterables](info:iterable) for looping over arrays and iterable objects. + +Otherwise, please read on. +``` + +## The "while" loop + +The `while` loop has the following syntax: + +```js +while (condition) { + // code + // so-called "loop body" +} +``` + +While the `condition` is truthy, the `code` from the loop body is executed. + +For instance, the loop below outputs `i` while `i < 3`: + +```js run +let i = 0; +while (i < 3) { // shows 0, then 1, then 2 +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 alert( i ); i++; } ``` +<<<<<<< HEAD Одно выполнение тела цикла по-научному называется *итерация*. Цикл в примере выше совершает три итерации. Если бы строка `i++` отсутствовала в примере выше, то цикл бы повторялся (в теории) вечно. На практике, конечно, браузер не позволит такому случиться, он предоставит пользователю возможность остановить "подвисший" скрипт, а JavaScript на стороне сервера придётся "убить" процесс. @@ -49,19 +92,37 @@ while (i < 3) { // выводит 0, затем 1, затем 2 Любое выражение или переменная может быть условием цикла, а не только сравнение: условие `while` вычисляется и преобразуется в логическое значение. Например, `while (i)` -- более краткий вариант `while (i != 0)`: +======= +A single execution of the loop body is called *an iteration*. The loop in the example above makes three iterations. + +If `i++` was missing from the example above, the loop would repeat (in theory) forever. In practice, the browser provides ways to stop such loops, and in server-side JavaScript, we can kill the process. + +Any expression or variable can be a loop condition, not just comparisons: the condition is evaluated and converted to a boolean by `while`. + +For instance, a shorter way to write `while (i != 0)` is `while (i)`: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let i = 3; *!* +<<<<<<< HEAD while (i) { // когда i будет равно 0, условие станет ложным, и цикл остановится +======= +while (i) { // when i becomes 0, the condition becomes falsy, and the loop stops +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 */!* alert( i ); i--; } ``` +<<<<<<< HEAD ````smart header="Фигурные скобки не требуются для тела цикла из одной строки" Если тело цикла состоит лишь из одной инструкции, мы можем опустить фигурные скобки `{…}`: +======= +````smart header="Curly braces are not required for a single-line body" +If the loop body has a single statement, we can omit the curly braces `{…}`: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let i = 3; @@ -71,6 +132,7 @@ while (i) alert(i--); ``` ```` +<<<<<<< HEAD ## Цикл "do…while" Проверку условия можно разместить под телом цикла, используя специальный синтаксис `do..while`: @@ -84,6 +146,21 @@ do { Цикл сначала выполнит тело, а затем проверит условие `condition`, и пока его значение равно `true`, он будет выполняться снова и снова. Например: +======= +## The "do..while" loop + +The condition check can be moved *below* the loop body using the `do..while` syntax: + +```js +do { + // loop body +} while (condition); +``` + +The loop will first execute the body, then check the condition, and, while it's truthy, execute it again and again. + +For example: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let i = 0; @@ -93,6 +170,7 @@ do { } while (i < 3); ``` +<<<<<<< HEAD Такая форма синтаксиса оправдана, если вы хотите, чтобы тело цикла выполнилось **хотя бы один раз**, даже если условие окажется ложным. На практике чаще используется форма с предусловием: `while(…) {…}`. ## Цикл "for" @@ -111,10 +189,31 @@ for (начало; условие; шаг) { ```js run for (let i = 0; i < 3; i++) { // выведет 0, затем 1, затем 2 +======= +This form of syntax should only be used when you want the body of the loop to execute **at least once** regardless of the condition being truthy. Usually, the other form is preferred: `while(…) {…}`. + +## The "for" loop + +The `for` loop is more complex, but it's also the most commonly used loop. + +It looks like this: + +```js +for (begin; condition; step) { + // ... loop body ... +} +``` + +Let's learn the meaning of these parts by example. The loop below runs `alert(i)` for `i` from `0` up to (but not including) `3`: + +```js run +for (let i = 0; i < 3; i++) { // shows 0, then 1, then 2 +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 alert(i); } ``` +<<<<<<< HEAD Рассмотрим конструкцию `for` подробней: | часть | | | @@ -139,10 +238,37 @@ for (let i = 0; i < 3; i++) { // выведет 0, затем 1, затем 2 Если тема циклов для вас нова, может быть полезным вернуться к примеру выше и воспроизвести его работу на листе бумаги, шаг за шагом. Вот в точности то, что происходит в нашем случае: +======= +Let's examine the `for` statement part-by-part: + +| part | | | +|-------|----------|----------------------------------------------------------------------------| +| begin | `let i = 0` | Executes once upon entering the loop. | +| condition | `i < 3`| Checked before every loop iteration. If false, the loop stops. | +| body | `alert(i)`| Runs again and again while the condition is truthy. | +| step| `i++` | Executes after the body on each iteration. | + +The general loop algorithm works like this: + +``` +Run begin +→ (if condition → run body and run step) +→ (if condition → run body and run step) +→ (if condition → run body and run step) +→ ... +``` + +That is, `begin` executes once, and then it iterates: after each `condition` test, `body` and `step` are executed. + +If you are new to loops, it could help to go back to the example and reproduce how it runs step-by-step on a piece of paper. + +Here's exactly what happens in our case: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js // for (let i = 0; i < 3; i++) alert(i) +<<<<<<< HEAD // Выполнить начало let i = 0; // Если условие == true → Выполнить тело, Выполнить шаг @@ -156,19 +282,42 @@ if (i < 3) { alert(i); i++ } ````smart header="Встроенное объявление переменной" В примере переменная счётчика `i` была объявлена прямо в цикле. Это так называемое "встроенное" объявление переменной. Такие переменные существуют только внутри цикла. +======= +// run begin +let i = 0 +// if condition → run body and run step +if (i < 3) { alert(i); i++ } +// if condition → run body and run step +if (i < 3) { alert(i); i++ } +// if condition → run body and run step +if (i < 3) { alert(i); i++ } +// ...finish, because now i == 3 +``` + +````smart header="Inline variable declaration" +Here, the "counter" variable `i` is declared right in the loop. This is called an "inline" variable declaration. Such variables are visible only inside the loop. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run for (*!*let*/!* i = 0; i < 3; i++) { alert(i); // 0, 1, 2 } +<<<<<<< HEAD alert(i); // ошибка, нет такой переменной ``` Вместо объявления новой переменной мы можем использовать уже существующую: +======= +alert(i); // error, no such variable +``` + +Instead of defining a variable, we could use an existing one: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let i = 0; +<<<<<<< HEAD for (i = 0; i < 3; i++) { // используем существующую переменную alert(i); // 0, 1, 2 } @@ -189,11 +338,37 @@ alert(i); // 3, переменная доступна, т.к. была объя let i = 0; // мы уже имеем объявленную i с присвоенным значением for (; i < 3; i++) { // нет необходимости в "начале" +======= +for (i = 0; i < 3; i++) { // use an existing variable + alert(i); // 0, 1, 2 +} + +alert(i); // 3, visible, because declared outside of the loop +``` +```` + +### Skipping parts + +Any part of `for` can be skipped. + +For example, we can omit `begin` if we don't need to do anything at the loop start. + +Like here: + +```js run +let i = 0; // we have i already declared and assigned + +for (; i < 3; i++) { // no need for "begin" +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 alert( i ); // 0, 1, 2 } ``` +<<<<<<< HEAD Можно убрать и `шаг`: +======= +We can also remove the `step` part: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let i = 0; @@ -203,6 +378,7 @@ for (; i < 3;) { } ``` +<<<<<<< HEAD Это сделает цикл аналогичным `while (i < 3)`. А можно и вообще убрать всё, получив бесконечный цикл: @@ -222,13 +398,38 @@ for (;;) { Но мы можем выйти из цикла в любой момент с помощью специальной директивы `break`. Например, следующий код подсчитывает сумму вводимых чисел до тех пор, пока посетитель их вводит, а затем – выдаёт: +======= +This makes the loop identical to `while (i < 3)`. + +We can actually remove everything, creating an infinite loop: + +```js +for (;;) { + // repeats without limits +} +``` + +Please note that the two `for` semicolons `;` must be present. Otherwise, there would be a syntax error. + +## Breaking the loop + +Normally, a loop exits when its condition becomes falsy. + +But we can force the exit at any time using the special `break` directive. + +For example, the loop below asks the user for a series of numbers, "breaking" when no number is entered: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let sum = 0; while (true) { +<<<<<<< HEAD let value = +prompt("Введите число", ''); +======= + let value = +prompt("Enter a number", ''); +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 *!* if (!value) break; // (*) @@ -237,6 +438,7 @@ while (true) { sum += value; } +<<<<<<< HEAD alert( 'Сумма: ' + sum ); ``` @@ -251,10 +453,27 @@ alert( 'Сумма: ' + sum ); Её используют, если понятно, что на текущем повторе цикла делать больше нечего. Например, цикл ниже использует `continue`, чтобы выводить только нечётные значения: +======= +alert( 'Sum: ' + sum ); +``` + +The `break` directive is activated at the line `(*)` if the user enters an empty line or cancels the input. It stops the loop immediately, passing control to the first line after the loop. Namely, `alert`. + +The combination "infinite loop + `break` as needed" is great for situations when a loop's condition must be checked not in the beginning or end of the loop, but in the middle or even in several places of its body. + +## Continue to the next iteration [#continue] + +The `continue` directive is a "lighter version" of `break`. It doesn't stop the whole loop. Instead, it stops the current iteration and forces the loop to start a new one (if the condition allows). + +We can use it if we're done with the current iteration and would like to move on to the next one. + +The loop below uses `continue` to output only odd values: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run no-beautify for (let i = 0; i < 10; i++) { +<<<<<<< HEAD // если true, пропустить оставшуюся часть тела цикла *!*if (i % 2 == 0) continue;*/!* @@ -266,6 +485,19 @@ for (let i = 0; i < 10; i++) { ````smart header="Директива `continue` позволяет избегать вложенности" Цикл, который обрабатывает только нечётные значения, мог бы выглядеть так: +======= + // if true, skip the remaining part of the body + *!*if (i % 2 == 0) continue;*/!* + + alert(i); // 1, then 3, 5, 7, 9 +} +``` + +For even values of `i`, the `continue` directive stops executing the body and passes control to the next iteration of `for` (with the next number). So the `alert` is only called for odd values. + +````smart header="The `continue` directive helps decrease nesting" +A loop that shows odd values could look like this: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run for (let i = 0; i < 10; i++) { @@ -277,6 +509,7 @@ for (let i = 0; i < 10; i++) { } ``` +<<<<<<< HEAD С технической точки зрения он полностью идентичен. Действительно, вместо `continue` можно просто завернуть действия в блок `if`. Однако мы получили дополнительный уровень вложенности фигурных скобок. Если код внутри `if` более длинный, то это ухудшает читаемость, в отличие от варианта с `continue`. @@ -286,6 +519,17 @@ for (let i = 0; i < 10; i++) { Обратите внимание, что эти синтаксические конструкции не являются выражениями и не могут быть использованы с тернарным оператором `?`. В частности, использование таких директив, как `break/continue`, вызовет ошибку. Например, если мы возьмём этот код: +======= +From a technical point of view, this is identical to the example above. Surely, we can just wrap the code in an `if` block instead of using `continue`. + +But as a side effect, this created one more level of nesting (the `alert` call inside the curly braces). If the code inside of `if` is longer than a few lines, that may decrease the overall readability. +```` + +````warn header="No `break/continue` to the right side of '?'" +Please note that syntax constructs that are not expressions cannot be used with the ternary operator `?`. In particular, directives such as `break/continue` aren't allowed there. + +For example, if we take this code: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js if (i > 5) { @@ -295,6 +539,7 @@ if (i > 5) { } ``` +<<<<<<< HEAD ...и перепишем его, используя вопросительный знак: ```js no-beautify @@ -311,12 +556,31 @@ if (i > 5) { Бывает, нужно выйти одновременно из нескольких уровней цикла сразу. Например, в коде ниже мы проходимся циклами по `i` и `j`, запрашивая с помощью `prompt` координаты `(i, j)` с `(0,0)` до `(2,2)`: +======= +...and rewrite it using a question mark: + +```js no-beautify +(i > 5) ? alert(i) : *!*continue*/!*; // continue isn't allowed here +``` + +...it stops working: there's a syntax error. + +This is just another reason not to use the question mark operator `?` instead of `if`. +```` + +## Labels for break/continue + +Sometimes we need to break out from multiple nested loops at once. + +For example, in the code below we loop over `i` and `j`, prompting for the coordinates `(i, j)` from `(0,0)` to `(2,2)`: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run no-beautify for (let i = 0; i < 3; i++) { for (let j = 0; j < 3; j++) { +<<<<<<< HEAD let input = prompt(`Значение на координатах (${i},${j})`, ''); // Что если мы захотим перейти к Готово (ниже) прямо отсюда? @@ -331,6 +595,22 @@ alert('Готово!'); Обычный `break` после `input` лишь прервёт внутренний цикл, но этого недостаточно. Достичь желаемого поведения можно с помощью меток. *Метка* имеет вид идентификатора с двоеточием перед циклом: +======= + let input = prompt(`Value at coords (${i},${j})`, ''); + + // what if we want to exit from here to Done (below)? + } +} + +alert('Done!'); +``` + +We need a way to stop the process if the user cancels the input. + +The ordinary `break` after `input` would only break the inner loop. That's not sufficient -- labels, come to the rescue! + +A *label* is an identifier with a colon before a loop: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js labelName: for (...) { @@ -338,13 +618,18 @@ labelName: for (...) { } ``` +<<<<<<< HEAD Вызов `break ` в цикле ниже ищет ближайший внешний цикл с такой меткой и переходит в его конец. +======= +The `break ` statement in the loop below breaks out to the label: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run no-beautify *!*outer:*/!* for (let i = 0; i < 3; i++) { for (let j = 0; j < 3; j++) { +<<<<<<< HEAD let input = prompt(`Значение на координатах (${i},${j})`, ''); // если пустая строка или Отмена, то выйти из обоих циклов @@ -362,12 +647,32 @@ alert('Готово!'); Таким образом управление перейдёт со строки, помеченной `(*)`, к `alert('Готово!')`. Можно размещать метку на отдельной строке: +======= + let input = prompt(`Value at coords (${i},${j})`, ''); + + // if an empty string or canceled, then break out of both loops + if (!input) *!*break outer*/!*; // (*) + + // do something with the value... + } +} + +alert('Done!'); +``` + +In the code above, `break outer` looks upwards for the label named `outer` and breaks out of that loop. + +So the control goes straight from `(*)` to `alert('Done!')`. + +We can also move the label onto a separate line: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js no-beautify outer: for (let i = 0; i < 3; i++) { ... } ``` +<<<<<<< HEAD Директива `continue` также может быть использована с меткой. В этом случае управление перейдёт на следующую итерацию цикла с меткой. ````warn header="Метки не позволяют \"прыгнуть\" куда угодно" @@ -377,20 +682,40 @@ for (let i = 0; i < 3; i++) { ... } ```js break label; // не прыгает к метке ниже +======= +The `continue` directive can also be used with a label. In this case, code execution jumps to the next iteration of the labeled loop. + +````warn header="Labels do not allow to \"jump\" anywhere" +Labels do not allow us to jump into an arbitrary place in the code. + +For example, it is impossible to do this: + +```js +break label; // jump to the label below (doesn't work) +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 label: for (...) ``` +<<<<<<< HEAD Директива `break` должна находиться внутри блока кода. Технически, подойдет любой маркированный блок кода, например: +======= +A `break` directive must be inside a code block. Technically, any labelled code block will do, e.g.: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js label: { // ... +<<<<<<< HEAD break label; // работает +======= + break label; // works +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 // ... } ``` +<<<<<<< HEAD ...Хотя в 99.9% случаев `break` используется внутри циклов, как мы видели в примерах выше. К слову, `continue` возможно только внутри цикла. @@ -411,3 +736,23 @@ label: { Обе этих директивы поддерживают *метки*, которые ставятся перед циклом. Метки – единственный способ для `break/continue` выйти за пределы текущего цикла, повлиять на выполнение внешнего. Заметим, что метки не позволяют прыгнуть в произвольное место кода, в JavaScript нет такой возможности. +======= +...Although, 99.9% of the time `break` is used inside loops, as we've seen in the examples above. + +A `continue` is only possible from inside a loop. +```` + +## Summary + +We covered 3 types of loops: + +- `while` -- The condition is checked before each iteration. +- `do..while` -- The condition is checked after each iteration. +- `for (;;)` -- The condition is checked before each iteration, additional settings available. + +To make an "infinite" loop, usually the `while(true)` construct is used. Such a loop, just like any other, can be stopped with the `break` directive. + +If we don't want to do anything in the current iteration and would like to forward to the next one, we can use the `continue` directive. + +`break/continue` support labels before the loop. A label is the only way for `break/continue` to escape a nested loop to go to an outer one. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/1-js/02-first-steps/14-switch/1-rewrite-switch-if-else/solution.md b/1-js/02-first-steps/14-switch/1-rewrite-switch-if-else/solution.md index 3f3bff539a..c90427ed36 100644 --- a/1-js/02-first-steps/14-switch/1-rewrite-switch-if-else/solution.md +++ b/1-js/02-first-steps/14-switch/1-rewrite-switch-if-else/solution.md @@ -1,6 +1,12 @@ +<<<<<<< HEAD Если совсем точно следовать работе `switch`, то `if` должен выполнять строгое сравнение `'==='`. Впрочем, для таких строк, подойдёт и обычное сравнение `'=='`. +======= +To precisely match the functionality of `switch`, the `if` must use a strict comparison `'==='`. + +For given strings though, a simple `'=='` works too. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js no-beautify if(browser == 'Edge') { @@ -15,6 +21,12 @@ if(browser == 'Edge') { } ``` +<<<<<<< HEAD Обратите внимание: конструкция `browser == 'Chrome' || browser == 'Firefox' ...` разбита на несколько строк для лучшей читаемости. Но всё равно запись через `switch` нагляднее. +======= +Please note: the construct `browser == 'Chrome' || browser == 'Firefox' …` is split into multiple lines for better readability. + +But the `switch` construct is still cleaner and more descriptive. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/1-js/02-first-steps/14-switch/2-rewrite-if-switch/solution.md b/1-js/02-first-steps/14-switch/2-rewrite-if-switch/solution.md index 420899c18a..f5163c5aa6 100644 --- a/1-js/02-first-steps/14-switch/2-rewrite-if-switch/solution.md +++ b/1-js/02-first-steps/14-switch/2-rewrite-if-switch/solution.md @@ -1,3 +1,4 @@ +<<<<<<< HEAD Первые две проверки – обычный `case`, третья разделена на два `case`: ```js run @@ -10,17 +11,41 @@ switch (number) { case 1: alert('Вы ввели число 1'); +======= +The first two checks turn into two `case`. The third check is split into two cases: + +```js run +let a = +prompt('a?', ''); + +switch (a) { + case 0: + alert( 0 ); + break; + + case 1: + alert( 1 ); +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 break; case 2: case 3: +<<<<<<< HEAD alert('Вы ввели число 2, а может и 3'); +======= + alert( '2,3' ); +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 *!* break; */!* } ``` +<<<<<<< HEAD Обратите внимание: `break` внизу не обязателен, но ставится по «правилам хорошего тона». Допустим, он не стоит. Есть шанс, что в будущем нам понадобится добавить в конец ещё один `case`, например `case 4`, и мы, вполне вероятно, забудем этот `break` поставить. В результате выполнение `case 2/case 3` продолжится на `case 4` и будет ошибка. +======= +Please note: the `break` at the bottom is not required. But we put it to make the code future-proof. + +In the future, there is a chance that we'd want to add one more `case`, for example `case 4`. And if we forget to add a break before it, at the end of `case 3`, there will be an error. So that's a kind of self-insurance. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/1-js/02-first-steps/14-switch/2-rewrite-if-switch/task.md b/1-js/02-first-steps/14-switch/2-rewrite-if-switch/task.md index 213dfcfe19..5d0f78c948 100644 --- a/1-js/02-first-steps/14-switch/2-rewrite-if-switch/task.md +++ b/1-js/02-first-steps/14-switch/2-rewrite-if-switch/task.md @@ -2,6 +2,7 @@ importance: 4 --- +<<<<<<< HEAD # Переписать условия "if" на "switch" Перепишите код с использованием одной конструкции `switch`: @@ -21,3 +22,24 @@ if (number === 2 || number === 3) { alert('Вы ввели число 2, а может и 3'); } ``` +======= +# Rewrite "if" into "switch" + +Rewrite the code below using a single `switch` statement: + +```js run +let a = +prompt('a?', ''); + +if (a == 0) { + alert( 0 ); +} +if (a == 1) { + alert( 1 ); +} + +if (a == 2 || a == 3) { + alert( '2,3' ); +} +``` + +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/1-js/02-first-steps/14-switch/article.md b/1-js/02-first-steps/14-switch/article.md index 7f356dc2da..0468382693 100644 --- a/1-js/02-first-steps/14-switch/article.md +++ b/1-js/02-first-steps/14-switch/article.md @@ -1,3 +1,4 @@ +<<<<<<< HEAD # Конструкция "switch" Конструкция `switch` заменяет собой сразу несколько `if`. @@ -9,6 +10,19 @@ Конструкция `switch` имеет один или более блок `case` и необязательный блок `default`. Выглядит она так: +======= +# The "switch" statement + +A `switch` statement can replace multiple `if` checks. + +It gives a more descriptive way to compare a value with multiple variants. + +## The syntax + +The `switch` has one or more `case` blocks and an optional default. + +It looks like this: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js no-beautify switch(x) { @@ -26,6 +40,7 @@ switch(x) { } ``` +<<<<<<< HEAD - Переменная `x` проверяется на строгое равенство первому значению `value1`, затем второму `value2` и так далее. - Если соответствие установлено – `switch` начинает выполняться от соответствующей директивы `case` и далее, до ближайшего `break` (или до конца `switch`). - Если ни один `case` не совпал – выполняется (если есть) вариант `default`. @@ -33,12 +48,22 @@ switch(x) { ## Пример работы Пример использования `switch` (сработавший код выделен): +======= +- The value of `x` is checked for a strict equality to the value from the first `case` (that is, `value1`) then to the second (`value2`) and so on. +- If the equality is found, `switch` starts to execute the code starting from the corresponding `case`, until the nearest `break` (or until the end of `switch`). +- If no case is matched then the `default` code is executed (if it exists). + +## An example + +An example of `switch` (the executed code is highlighted): +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let a = 2 + 2; switch (a) { case 3: +<<<<<<< HEAD alert( 'Маловато' ); break; *!* @@ -61,12 +86,37 @@ switch (a) { **Если `break` нет, то выполнение пойдёт ниже по следующим `case`, при этом остальные проверки игнорируются.** Пример без `break`: +======= + alert( 'Too small' ); + break; +*!* + case 4: + alert( 'Exactly!' ); + break; +*/!* + case 5: + alert( 'Too big' ); + break; + default: + alert( "I don't know such values" ); +} +``` + +Here the `switch` starts to compare `a` from the first `case` variant that is `3`. The match fails. + +Then `4`. That's a match, so the execution starts from `case 4` until the nearest `break`. + +**If there is no `break` then the execution continues with the next `case` without any checks.** + +An example without `break`: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let a = 2 + 2; switch (a) { case 3: +<<<<<<< HEAD alert( 'Маловато' ); *!* case 4: @@ -75,10 +125,21 @@ switch (a) { alert( 'Перебор' ); default: alert( "Нет таких значений" ); +======= + alert( 'Too small' ); +*!* + case 4: + alert( 'Exactly!' ); + case 5: + alert( 'Too big' ); + default: + alert( "I don't know such values" ); +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 */!* } ``` +<<<<<<< HEAD В примере выше последовательно выполнятся три `alert`: ```js @@ -91,6 +152,20 @@ alert( "Нет таких значений" ); И `switch` и `case` допускают любое выражение в качестве аргумента. Например: +======= +In the example above we'll see sequential execution of three `alert`s: + +```js +alert( 'Exactly!' ); +alert( 'Too big' ); +alert( "I don't know such values" ); +``` + +````smart header="Any expression can be a `switch/case` argument" +Both `switch` and `case` allow arbitrary expressions. + +For example: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let a = "1"; @@ -99,11 +174,16 @@ let b = 0; switch (+a) { *!* case b + 1: +<<<<<<< HEAD alert("Выполнится, т.к. значением +a будет 1, что в точности равно b+1"); +======= + alert("this runs, because +a is 1, exactly equals b+1"); +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 break; */!* default: +<<<<<<< HEAD alert("Это не выполнится"); } ``` @@ -115,12 +195,26 @@ switch (+a) { Несколько вариантов `case`, использующих один код, можно группировать. Для примера, выполним один и тот же код для `case 3` и `case 5`, сгруппировав их: +======= + alert("this doesn't run"); +} +``` +Here `+a` gives `1`, that's compared with `b + 1` in `case`, and the corresponding code is executed. +```` + +## Grouping of "case" + +Several variants of `case` which share the same code can be grouped. + +For example, if we want the same code to run for `case 3` and `case 5`: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run no-beautify let a = 3; switch (a) { case 4: +<<<<<<< HEAD alert('Правильно!'); break; @@ -129,10 +223,21 @@ switch (a) { case 5: alert('Неправильно!'); alert("Может вам посетить урок математики?"); +======= + alert('Right!'); + break; + +*!* + case 3: // (*) grouped two cases + case 5: + alert('Wrong!'); + alert("Why don't you take a math class?"); +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 break; */!* default: +<<<<<<< HEAD alert('Результат выглядит странновато. Честно.'); } ``` @@ -170,3 +275,42 @@ switch (arg) { 1. Для `'0'` и `'1'` выполнится первый `alert`. 2. Для `'2'` -- второй `alert`. 3. Но для `3`, результат выполнения `prompt` будет строка `"3"`, которая не соответствует строгому равенству `===` с числом `3`. Таким образом, мы имеем "мёртвый код" в `case 3`! Выполнится вариант `default`. +======= + alert('The result is strange. Really.'); +} +``` + +Now both `3` and `5` show the same message. + +The ability to "group" cases is a side effect of how `switch/case` works without `break`. Here the execution of `case 3` starts from the line `(*)` and goes through `case 5`, because there's no `break`. + +## Type matters + +Let's emphasize that the equality check is always strict. The values must be of the same type to match. + +For example, let's consider the code: + +```js run +let arg = prompt("Enter a value?"); +switch (arg) { + case '0': + case '1': + alert( 'One or zero' ); + break; + + case '2': + alert( 'Two' ); + break; + + case 3: + alert( 'Never executes!' ); + break; + default: + alert( 'An unknown value' ); +} +``` + +1. For `0`, `1`, the first `alert` runs. +2. For `2` the second `alert` runs. +3. But for `3`, the result of the `prompt` is a string `"3"`, which is not strictly equal `===` to the number `3`. So we've got a dead code in `case 3`! The `default` variant will execute. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/1-js/02-first-steps/15-function-basics/1-if-else-required/solution.md b/1-js/02-first-steps/15-function-basics/1-if-else-required/solution.md index a1853a8110..e38ac00fec 100644 --- a/1-js/02-first-steps/15-function-basics/1-if-else-required/solution.md +++ b/1-js/02-first-steps/15-function-basics/1-if-else-required/solution.md @@ -1 +1,7 @@ +<<<<<<< HEAD Оба варианта функций работают одинаково, отличий нет. +======= +No difference! + +In both cases, `return confirm('Did parents allow you?')` executes exactly when the `if` condition is falsy. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/1-js/02-first-steps/15-function-basics/1-if-else-required/task.md b/1-js/02-first-steps/15-function-basics/1-if-else-required/task.md index 132e55d860..083875af7e 100644 --- a/1-js/02-first-steps/15-function-basics/1-if-else-required/task.md +++ b/1-js/02-first-steps/15-function-basics/1-if-else-required/task.md @@ -2,11 +2,19 @@ importance: 4 --- +<<<<<<< HEAD # Обязателен ли "else"? Следующая функция возвращает `true`, если параметр `age` больше `18`. В ином случае она запрашивает подтверждение через `confirm` и возвращает его результат: +======= +# Is "else" required? + +The following function returns `true` if the parameter `age` is greater than `18`. + +Otherwise it asks for a confirmation and returns its result: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js function checkAge(age) { @@ -15,13 +23,21 @@ function checkAge(age) { *!* } else { // ... +<<<<<<< HEAD return confirm('Родители разрешили?'); +======= + return confirm('Did parents allow you?'); +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 } */!* } ``` +<<<<<<< HEAD Будет ли эта функция работать как-то иначе, если убрать `else`? +======= +Will the function work differently if `else` is removed? +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js function checkAge(age) { @@ -30,9 +46,17 @@ function checkAge(age) { } *!* // ... +<<<<<<< HEAD return confirm('Родители разрешили?'); +======= + return confirm('Did parents allow you?'); +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 */!* } ``` +<<<<<<< HEAD Есть ли хоть одно отличие в поведении этого варианта? +======= +Is there any difference in the behavior of these two variants? +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/1-js/02-first-steps/15-function-basics/2-rewrite-function-question-or/solution.md b/1-js/02-first-steps/15-function-basics/2-rewrite-function-question-or/solution.md index 7b44f9ff2a..b2d2aea27a 100644 --- a/1-js/02-first-steps/15-function-basics/2-rewrite-function-question-or/solution.md +++ b/1-js/02-first-steps/15-function-basics/2-rewrite-function-question-or/solution.md @@ -1,3 +1,4 @@ +<<<<<<< HEAD Используя оператор `?`: ```js @@ -15,3 +16,22 @@ function checkAge(age) { ``` Обратите внимание, что круглые скобки вокруг `age > 18` не обязательны. Они здесь для лучшей читаемости кода. +======= +Using a question mark operator `'?'`: + +```js +function checkAge(age) { + return (age > 18) ? true : confirm('Did parents allow you?'); +} +``` + +Using OR `||` (the shortest variant): + +```js +function checkAge(age) { + return (age > 18) || confirm('Did parents allow you?'); +} +``` + +Note that the parentheses around `age > 18` are not required here. They exist for better readability. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/1-js/02-first-steps/15-function-basics/2-rewrite-function-question-or/task.md b/1-js/02-first-steps/15-function-basics/2-rewrite-function-question-or/task.md index 35757b697f..0ec9fdc324 100644 --- a/1-js/02-first-steps/15-function-basics/2-rewrite-function-question-or/task.md +++ b/1-js/02-first-steps/15-function-basics/2-rewrite-function-question-or/task.md @@ -2,25 +2,46 @@ importance: 4 --- +<<<<<<< HEAD # Перепишите функцию, используя оператор '?' или '||' Следующая функция возвращает `true`, если параметр `age` больше `18`. В ином случае она задаёт вопрос `confirm` и возвращает его результат. +======= +# Rewrite the function using '?' or '||' + +The following function returns `true` if the parameter `age` is greater than `18`. + +Otherwise it asks for a confirmation and returns its result. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js function checkAge(age) { if (age > 18) { return true; } else { +<<<<<<< HEAD return confirm('Родители разрешили?'); +======= + return confirm('Did parents allow you?'); +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 } } ``` +<<<<<<< HEAD Перепишите функцию, чтобы она делала то же самое, но без `if`, в одну строку. Сделайте два варианта функции `checkAge`: 1. Используя оператор `?` 2. Используя оператор `||` +======= +Rewrite it, to perform the same, but without `if`, in a single line. + +Make two variants of `checkAge`: + +1. Using a question mark operator `?` +2. Using OR `||` +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/1-js/02-first-steps/15-function-basics/3-min/solution.md b/1-js/02-first-steps/15-function-basics/3-min/solution.md index 7151a7e6cd..799419ad55 100644 --- a/1-js/02-first-steps/15-function-basics/3-min/solution.md +++ b/1-js/02-first-steps/15-function-basics/3-min/solution.md @@ -1,4 +1,8 @@ +<<<<<<< HEAD Вариант решения с использованием `if`: +======= +A solution using `if`: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js function min(a, b) { @@ -10,7 +14,11 @@ function min(a, b) { } ``` +<<<<<<< HEAD Вариант решения с оператором `?`: +======= +A solution with a question mark operator `'?'`: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js function min(a, b) { @@ -18,4 +26,8 @@ function min(a, b) { } ``` +<<<<<<< HEAD P.S. В случае равенства `a == b` не имеет значения, что возвращать. +======= +P.S. In the case of an equality `a == b` it does not matter what to return. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/1-js/02-first-steps/15-function-basics/3-min/task.md b/1-js/02-first-steps/15-function-basics/3-min/task.md index e886045cb1..7587da0db2 100644 --- a/1-js/02-first-steps/15-function-basics/3-min/task.md +++ b/1-js/02-first-steps/15-function-basics/3-min/task.md @@ -2,15 +2,27 @@ importance: 1 --- +<<<<<<< HEAD # Функция min(a, b) Напишите функцию `min(a,b)`, которая возвращает меньшее из чисел `a` и `b`. Пример вызовов: +======= +# Function min(a, b) + +Write a function `min(a,b)` which returns the least of two numbers `a` and `b`. + +For instance: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js min(2, 5) == 2 min(3, -1) == -1 min(1, 1) == 1 ``` +<<<<<<< HEAD +======= + +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/1-js/02-first-steps/15-function-basics/4-pow/task.md b/1-js/02-first-steps/15-function-basics/4-pow/task.md index e4e513c313..582a6446eb 100644 --- a/1-js/02-first-steps/15-function-basics/4-pow/task.md +++ b/1-js/02-first-steps/15-function-basics/4-pow/task.md @@ -2,9 +2,15 @@ importance: 4 --- +<<<<<<< HEAD # Функция pow(x,n) Напишите функцию `pow(x,n)`, которая возводит `x` в степень `n` и возвращает результат. +======= +# Function pow(x,n) + +Write a function `pow(x,n)` that returns `x` in power `n`. Or, in other words, multiplies `x` by itself `n` times and returns the result. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js pow(3, 2) = 3 * 3 = 9 @@ -12,8 +18,16 @@ pow(3, 3) = 3 * 3 * 3 = 27 pow(1, 100) = 1 * 1 * ...* 1 = 1 ``` +<<<<<<< HEAD Создайте страницу, которая запрашивает `x` и `n`, а затем выводит результат `pow(x,n)`. [demo] P.S. В этой задаче функция обязана поддерживать только натуральные значения `n`, т.е. целые от `1` и выше. +======= +Create a web-page that prompts for `x` and `n`, and then shows the result of `pow(x,n)`. + +[demo] + +P.S. In this task the function should support only natural values of `n`: integers up from `1`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/1-js/02-first-steps/15-function-basics/article.md b/1-js/02-first-steps/15-function-basics/article.md index 4f3bacf687..6eeba7dcfa 100644 --- a/1-js/02-first-steps/15-function-basics/article.md +++ b/1-js/02-first-steps/15-function-basics/article.md @@ -1,3 +1,4 @@ +<<<<<<< HEAD # Функции Зачастую нам надо повторять одно и то же действие во многих частях программы. @@ -35,6 +36,45 @@ function имя(параметры) { ```js run function showMessage() { alert( 'Всем привет!' ); +======= +# Functions + +Quite often we need to perform a similar action in many places of the script. + +For example, we need to show a nice-looking message when a visitor logs in, logs out and maybe somewhere else. + +Functions are the main "building blocks" of the program. They allow the code to be called many times without repetition. + +We've already seen examples of built-in functions, like `alert(message)`, `prompt(message, default)` and `confirm(question)`. But we can create functions of our own as well. + +## Function Declaration + +To create a function we can use a *function declaration*. + +It looks like this: + +```js +function showMessage() { + alert( 'Hello everyone!' ); +} +``` + +The `function` keyword goes first, then goes the *name of the function*, then a list of *parameters* between the parentheses (comma-separated, empty in the example above, we'll see examples later) and finally the code of the function, also named "the function body", between curly braces. + +```js +function name(parameter1, parameter2, ... parameterN) { + // body +} +``` + +Our new function can be called by its name: `showMessage()`. + +For instance: + +```js run +function showMessage() { + alert( 'Hello everyone!' ); +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 } *!* @@ -43,6 +83,7 @@ showMessage(); */!* ``` +<<<<<<< HEAD Вызов `showMessage()` выполняет код функции. Здесь мы увидим сообщение дважды. Этот пример явно демонстрирует одно из главных предназначений функций: избавление от дублирования кода. @@ -54,16 +95,34 @@ showMessage(); Переменные, объявленные внутри функции, видны только внутри этой функции. Например: +======= +The call `showMessage()` executes the code of the function. Here we will see the message two times. + +This example clearly demonstrates one of the main purposes of functions: to avoid code duplication. + +If we ever need to change the message or the way it is shown, it's enough to modify the code in one place: the function which outputs it. + +## Local variables + +A variable declared inside a function is only visible inside that function. + +For example: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run function showMessage() { *!* +<<<<<<< HEAD let message = "Привет, я JavaScript!"; // локальная переменная +======= + let message = "Hello, I'm JavaScript!"; // local variable +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 */!* alert( message ); } +<<<<<<< HEAD showMessage(); // Привет, я JavaScript! alert( message ); // <-- будет ошибка, т.к. переменная видна только внутри функции @@ -156,17 +215,113 @@ showMessage('Аня', "Как дела?"); // Аня: Как дела? (**) Вот ещё один пример: у нас есть переменная `from`, и мы передаём её функции. Обратите внимание: функция изменяет значение `from`, но это изменение не видно снаружи. Функция всегда получает только копию значения: +======= +showMessage(); // Hello, I'm JavaScript! + +alert( message ); // <-- Error! The variable is local to the function +``` + +## Outer variables + +A function can access an outer variable as well, for example: + +```js run no-beautify +let *!*userName*/!* = 'John'; + +function showMessage() { + let message = 'Hello, ' + *!*userName*/!*; + alert(message); +} + +showMessage(); // Hello, John +``` + +The function has full access to the outer variable. It can modify it as well. + +For instance: + +```js run +let *!*userName*/!* = 'John'; + +function showMessage() { + *!*userName*/!* = "Bob"; // (1) changed the outer variable + + let message = 'Hello, ' + *!*userName*/!*; + alert(message); +} + +alert( userName ); // *!*John*/!* before the function call + +showMessage(); + +alert( userName ); // *!*Bob*/!*, the value was modified by the function +``` + +The outer variable is only used if there's no local one. + +If a same-named variable is declared inside the function then it *shadows* the outer one. For instance, in the code below the function uses the local `userName`. The outer one is ignored: + +```js run +let userName = 'John'; + +function showMessage() { +*!* + let userName = "Bob"; // declare a local variable +*/!* + + let message = 'Hello, ' + userName; // *!*Bob*/!* + alert(message); +} + +// the function will create and use its own userName +showMessage(); + +alert( userName ); // *!*John*/!*, unchanged, the function did not access the outer variable +``` + +```smart header="Global variables" +Variables declared outside of any function, such as the outer `userName` in the code above, are called *global*. + +Global variables are visible from any function (unless shadowed by locals). + +It's a good practice to minimize the use of global variables. Modern code has few or no globals. Most variables reside in their functions. Sometimes though, they can be useful to store project-level data. +``` + +## Parameters + +We can pass arbitrary data to functions using parameters. + +In the example below, the function has two parameters: `from` and `text`. + +```js run +function showMessage(*!*from, text*/!*) { // parameters: from, text + alert(from + ': ' + text); +} + +*!*showMessage('Ann', 'Hello!');*/!* // Ann: Hello! (*) +*!*showMessage('Ann', "What's up?");*/!* // Ann: What's up? (**) +``` + +When the function is called in lines `(*)` and `(**)`, the given values are copied to local variables `from` and `text`. Then the function uses them. + +Here's one more example: we have a variable `from` and pass it to the function. Please note: the function changes `from`, but the change is not seen outside, because a function always gets a copy of the value: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run function showMessage(from, text) { *!* +<<<<<<< HEAD from = '*' + from + '*'; // немного украсим "from" +======= + from = '*' + from + '*'; // make "from" look nicer +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 */!* alert( from + ': ' + text ); } +<<<<<<< HEAD let from = "Аня"; showMessage(from, "Привет"); // *Аня*: Привет @@ -231,12 +386,91 @@ function showMessage(from, text = anotherFunction()) { Ранние версии JavaScript не поддерживали параметры по умолчанию. Поэтому существуют альтернативные способы, которые могут встречаться в старых скриптах. Например, явная проверка на `undefined`: +======= +let from = "Ann"; + +showMessage(from, "Hello"); // *Ann*: Hello + +// the value of "from" is the same, the function modified a local copy +alert( from ); // Ann +``` + +When a value is passed as a function parameter, it's also called an *argument*. + +In other words, to put these terms straight: + +- A parameter is the variable listed inside the parentheses in the function declaration (it's a declaration time term). +- An argument is the value that is passed to the function when it is called (it's a call time term). + +We declare functions listing their parameters, then call them passing arguments. + +In the example above, one might say: "the function `showMessage` is declared with two parameters, then called with two arguments: `from` and `"Hello"`". + + +## Default values + +If a function is called, but an argument is not provided, then the corresponding value becomes `undefined`. + +For instance, the aforementioned function `showMessage(from, text)` can be called with a single argument: + +```js +showMessage("Ann"); +``` + +That's not an error. Such a call would output `"*Ann*: undefined"`. As the value for `text` isn't passed, it becomes `undefined`. + +We can specify the so-called "default" (to use if omitted) value for a parameter in the function declaration, using `=`: + +```js run +function showMessage(from, *!*text = "no text given"*/!*) { + alert( from + ": " + text ); +} + +showMessage("Ann"); // Ann: no text given +``` + +Now if the `text` parameter is not passed, it will get the value `"no text given"`. + +The default value also jumps in if the parameter exists, but strictly equals `undefined`, like this: + +```js +showMessage("Ann", undefined); // Ann: no text given +``` + +Here `"no text given"` is a string, but it can be a more complex expression, which is only evaluated and assigned if the parameter is missing. So, this is also possible: + +```js run +function showMessage(from, text = anotherFunction()) { + // anotherFunction() only executed if no text given + // its result becomes the value of text +} +``` + +```smart header="Evaluation of default parameters" +In JavaScript, a default parameter is evaluated every time the function is called without the respective parameter. + +In the example above, `anotherFunction()` isn't called at all, if the `text` parameter is provided. + +On the other hand, it's independently called every time when `text` is missing. +``` + +````smart header="Default parameters in old JavaScript code" +Several years ago, JavaScript didn't support the syntax for default parameters. So people used other ways to specify them. + +Nowadays, we can come across them in old scripts. + +For example, an explicit check for `undefined`: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js function showMessage(from, text) { *!* if (text === undefined) { +<<<<<<< HEAD text = 'текст не добавлен'; +======= + text = 'no text given'; +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 } */!* @@ -244,6 +478,7 @@ function showMessage(from, text) { } ``` +<<<<<<< HEAD ...Или с помощью оператора `||`: ```js @@ -251,20 +486,39 @@ function showMessage(from, text) { // Если значение text ложно, тогда присвоить параметру text значение по умолчанию // заметим, что при этом пустая строка text === "" будет также считаться отсутствующим значением text = text || 'текст не добавлен'; +======= +...Or using the `||` operator: + +```js +function showMessage(from, text) { + // If the value of text is falsy, assign the default value + // this assumes that text == "" is the same as no text at all + text = text || 'no text given'; +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ... } ``` ```` +<<<<<<< HEAD ### Альтернативные параметры по умолчанию Иногда имеет смысл присваивать значения по умолчанию для параметров не в объявлении функции, а на более позднем этапе. Во время выполнения функции мы можем проверить, передан ли параметр, сравнив его с `undefined`: +======= + +### Alternative default parameters + +Sometimes it makes sense to assign default values for parameters at a later stage after the function declaration. + +We can check if the parameter is passed during the function execution, by comparing it with `undefined`: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run function showMessage(text) { // ... +<<<<<<< HEAD *!* if (text === undefined) { // если параметр отсутствует text = 'пустое сообщение'; @@ -281,10 +535,32 @@ showMessage(); // пустое сообщение function showMessage(text) { // если значение text ложно или равняется undefined, тогда присвоить text значение 'пусто' text = text || 'пусто'; +======= + +*!* + if (text === undefined) { // if the parameter is missing + text = 'empty message'; + } +*/!* + + alert(text); +} + +showMessage(); // empty message +``` + +...Or we could use the `||` operator: + +```js +function showMessage(text) { + // if text is undefined or otherwise falsy, set it to 'empty' + text = text || 'empty'; +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ... } ``` +<<<<<<< HEAD Современные движки JavaScript поддерживают [оператор нулевого слияния](info:nullish-operators) `??`. Его использование будет лучшей практикой, в случае, если большинство ложных значений, таких как `0`, следует расценивать как "нормальные". ```js run @@ -302,6 +578,26 @@ showCount(); // неизвестно Функция может вернуть результат, который будет передан в вызвавший её код. Простейшим примером может служить функция сложения двух чисел: +======= +Modern JavaScript engines support the [nullish coalescing operator](info:nullish-coalescing-operator) `??`, it's better when most falsy values, such as `0`, should be considered "normal": + +```js run +function showCount(count) { + // if count is undefined or null, show "unknown" + alert(count ?? "unknown"); +} + +showCount(0); // 0 +showCount(null); // unknown +showCount(); // unknown +``` + +## Returning a value + +A function can return a value back into the calling code as the result. + +The simplest example would be a function that sums two values: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run no-beautify function sum(a, b) { @@ -312,9 +608,15 @@ let result = sum(1, 2); alert( result ); // 3 ``` +<<<<<<< HEAD Директива `return` может находиться в любом месте тела функции. Как только выполнение доходит до этого места, функция останавливается, и значение возвращается в вызвавший её код (присваивается переменной `result` выше). Вызовов `return` может быть несколько, например: +======= +The directive `return` can be in any place of the function. When the execution reaches it, the function stops, and the value is returned to the calling code (assigned to `result` above). + +There may be many occurrences of `return` in a single function. For instance: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run function checkAge(age) { @@ -324,11 +626,16 @@ function checkAge(age) { */!* } else { *!* +<<<<<<< HEAD return confirm('А родители разрешили?'); +======= + return confirm('Do you have permission from your parents?'); +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 */!* } } +<<<<<<< HEAD let age = prompt('Сколько вам лет?', 18); if ( checkAge(age) ) { @@ -341,6 +648,20 @@ if ( checkAge(age) ) { Возможно использовать `return` и без значения. Это приведёт к немедленному выходу из функции. Например: +======= +let age = prompt('How old are you?', 18); + +if ( checkAge(age) ) { + alert( 'Access granted' ); +} else { + alert( 'Access denied' ); +} +``` + +It is possible to use `return` without a value. That causes the function to exit immediately. + +For example: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js function showMovie(age) { @@ -350,11 +671,16 @@ function showMovie(age) { */!* } +<<<<<<< HEAD alert( "Вам показывается кино" ); // (*) +======= + alert( "Showing you the movie" ); // (*) +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 // ... } ``` +<<<<<<< HEAD В коде выше, если `checkAge(age)` вернёт `false`, `showMovie` не выполнит `alert`. ````smart header="Результат функции с пустым `return` или без него - `undefined`" @@ -362,11 +688,24 @@ function showMovie(age) { ```js run function doNothing() { /* пусто */ } +======= +In the code above, if `checkAge(age)` returns `false`, then `showMovie` won't proceed to the `alert`. + +````smart header="A function with an empty `return` or without it returns `undefined`" +If a function does not return a value, it is the same as if it returns `undefined`: + +```js run +function doNothing() { /* empty */ } +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 alert( doNothing() === undefined ); // true ``` +<<<<<<< HEAD Пустой `return` аналогичен `return undefined`: +======= +An empty `return` is also the same as `return undefined`: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run function doNothing() { @@ -377,23 +716,38 @@ alert( doNothing() === undefined ); // true ``` ```` +<<<<<<< HEAD ````warn header="Никогда не добавляйте перевод строки между `return` и его значением" Для длинного выражения в `return` может быть заманчиво разместить его на нескольких отдельных строках, например так: +======= +````warn header="Never add a newline between `return` and the value" +For a long expression in `return`, it might be tempting to put it on a separate line, like this: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js return (some + long + expression + or + whatever * f(a) + f(b)) ``` +<<<<<<< HEAD Код не выполнится, потому что интерпретатор JavaScript подставит точку с запятой после `return`. Для него это будет выглядеть так: +======= +That doesn't work, because JavaScript assumes a semicolon after `return`. That'll work the same as: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js return*!*;*/!* (some + long + expression + or + whatever * f(a) + f(b)) ``` +<<<<<<< HEAD Таким образом, это фактически стало пустым `return`. Если мы хотим, чтобы возвращаемое выражение занимало несколько строк, нужно начать его на той же строке, что и `return`. Или, хотя бы, поставить там открывающую скобку, вот так: +======= +So, it effectively becomes an empty return. + +If we want the returned expression to wrap across multiple lines, we should start it at the same line as `return`. Or at least put the opening parentheses there as follows: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js return ( @@ -402,6 +756,7 @@ return ( whatever * f(a) + f(b) ) ``` +<<<<<<< HEAD И тогда всё сработает, как задумано. ```` @@ -464,6 +819,69 @@ checkPermission(..) // проверяет доступ, возвращая true/ Например, сравним ниже две функции `showPrimes(n)`. Каждая из них выводит [простое число](https://ru.wikipedia.org/wiki/%D0%9F%D1%80%D0%BE%D1%81%D1%82%D0%BE%D0%B5_%D1%87%D0%B8%D1%81%D0%BB%D0%BE) до `n`. Первый вариант использует метку `nextPrime`: +======= +And it will work just as we expect it to. +```` + +## Naming a function [#function-naming] + +Functions are actions. So their name is usually a verb. It should be brief, as accurate as possible and describe what the function does, so that someone reading the code gets an indication of what the function does. + +It is a widespread practice to start a function with a verbal prefix which vaguely describes the action. There must be an agreement within the team on the meaning of the prefixes. + +For instance, functions that start with `"show"` usually show something. + +Function starting with... + +- `"get…"` -- return a value, +- `"calc…"` -- calculate something, +- `"create…"` -- create something, +- `"check…"` -- check something and return a boolean, etc. + +Examples of such names: + +```js no-beautify +showMessage(..) // shows a message +getAge(..) // returns the age (gets it somehow) +calcSum(..) // calculates a sum and returns the result +createForm(..) // creates a form (and usually returns it) +checkPermission(..) // checks a permission, returns true/false +``` + +With prefixes in place, a glance at a function name gives an understanding what kind of work it does and what kind of value it returns. + +```smart header="One function -- one action" +A function should do exactly what is suggested by its name, no more. + +Two independent actions usually deserve two functions, even if they are usually called together (in that case we can make a 3rd function that calls those two). + +A few examples of breaking this rule: + +- `getAge` -- would be bad if it shows an `alert` with the age (should only get). +- `createForm` -- would be bad if it modifies the document, adding a form to it (should only create it and return). +- `checkPermission` -- would be bad if it displays the `access granted/denied` message (should only perform the check and return the result). + +These examples assume common meanings of prefixes. You and your team are free to agree on other meanings, but usually they're not much different. In any case, you should have a firm understanding of what a prefix means, what a prefixed function can and cannot do. All same-prefixed functions should obey the rules. And the team should share the knowledge. +``` + +```smart header="Ultrashort function names" +Functions that are used *very often* sometimes have ultrashort names. + +For example, the [jQuery](https://jquery.com/) framework defines a function with `$`. The [Lodash](https://lodash.com/) library has its core function named `_`. + +These are exceptions. Generally function names should be concise and descriptive. +``` + +## Functions == Comments + +Functions should be short and do exactly one thing. If that thing is big, maybe it's worth it to split the function into a few smaller functions. Sometimes following this rule may not be that easy, but it's definitely a good thing. + +A separate function is not only easier to test and debug -- its very existence is a great comment! + +For instance, compare the two functions `showPrimes(n)` below. Each one outputs [prime numbers](https://en.wikipedia.org/wiki/Prime_number) up to `n`. + +The first variant uses a label: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js function showPrimes(n) { @@ -473,12 +891,20 @@ function showPrimes(n) { if (i % j == 0) continue nextPrime; } +<<<<<<< HEAD alert( i ); // простое +======= + alert( i ); // a prime +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 } } ``` +<<<<<<< HEAD Второй вариант использует дополнительную функцию `isPrime(n)` для проверки на простое: +======= +The second variant uses an additional function `isPrime(n)` to test for primality: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js function showPrimes(n) { @@ -486,7 +912,11 @@ function showPrimes(n) { for (let i = 2; i < n; i++) { *!*if (!isPrime(i)) continue;*/!* +<<<<<<< HEAD alert(i); // простое +======= + alert(i); // a prime +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 } } @@ -498,6 +928,7 @@ function isPrime(n) { } ``` +<<<<<<< HEAD Второй вариант легче для понимания, не правда ли? Вместо куска кода мы видим название действия (`isPrime`). Иногда разработчики называют такой код *самодокументируемым*. Таким образом, допустимо создавать функции, даже если мы не планируем повторно использовать их. Такие функции структурируют код и делают его более понятным. @@ -527,3 +958,34 @@ function имя(параметры, через, запятую) { - Есть много общепринятых префиксов, таких как: `create…`, `show…`, `get…`, `check…` и т.д. Пользуйтесь ими как подсказками, поясняющими, что делает функция. Функции являются основными строительными блоками скриптов. Мы рассмотрели лишь основы функций в JavaScript, но уже сейчас можем создавать и использовать их. Это только начало пути. Мы будем неоднократно возвращаться к функциям и изучать их всё более и более глубоко. +======= +The second variant is easier to understand, isn't it? Instead of the code piece we see a name of the action (`isPrime`). Sometimes people refer to such code as *self-describing*. + +So, functions can be created even if we don't intend to reuse them. They structure the code and make it readable. + +## Summary + +A function declaration looks like this: + +```js +function name(parameters, delimited, by, comma) { + /* code */ +} +``` + +- Values passed to a function as parameters are copied to its local variables. +- A function may access outer variables. But it works only from inside out. The code outside of the function doesn't see its local variables. +- A function can return a value. If it doesn't, then its result is `undefined`. + +To make the code clean and easy to understand, it's recommended to use mainly local variables and parameters in the function, not outer variables. + +It is always easier to understand a function which gets parameters, works with them and returns a result than a function which gets no parameters, but modifies outer variables as a side effect. + +Function naming: + +- A name should clearly describe what the function does. When we see a function call in the code, a good name instantly gives us an understanding what it does and returns. +- A function is an action, so function names are usually verbal. +- There exist many well-known function prefixes like `create…`, `show…`, `get…`, `check…` and so on. Use them to hint what a function does. + +Functions are the main building blocks of scripts. Now we've covered the basics, so we actually can start creating and using them. But that's only the beginning of the path. We are going to return to them many times, going more deeply into their advanced features. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/1-js/02-first-steps/16-function-expressions/article.md b/1-js/02-first-steps/16-function-expressions/article.md index 795217fc39..e2df1c0c3e 100644 --- a/1-js/02-first-steps/16-function-expressions/article.md +++ b/1-js/02-first-steps/16-function-expressions/article.md @@ -1,3 +1,4 @@ +<<<<<<< HEAD # Function Expression Функция в JavaScript - это не магическая языковая структура, а особого типа значение. @@ -89,6 +90,101 @@ let func = sayHi; ````smart header="Зачем нужна точка с запятой в конце?" У вас мог возникнуть вопрос: Почему в Function Expression ставится точка с запятой `;` на конце, а в Function Declaration нет: +======= +# Function expressions + +In JavaScript, a function is not a "magical language structure", but a special kind of value. + +The syntax that we used before is called a *Function Declaration*: + +```js +function sayHi() { + alert( "Hello" ); +} +``` + +There is another syntax for creating a function that is called a *Function Expression*. + +It allows us to create a new function in the middle of any expression. + +For example: + +```js +let sayHi = function() { + alert( "Hello" ); +}; +``` + +Here we can see a variable `sayHi` getting a value, the new function, created as `function() { alert("Hello"); }`. + +As the function creation happens in the context of the assignment expression (to the right side of `=`), this is a *Function Expression*. + +Please note, there's no name after the `function` keyword. Omitting a name is allowed for Function Expressions. + +Here we immediately assign it to the variable, so the meaning of these code samples is the same: "create a function and put it into the variable `sayHi`". + +In more advanced situations, that we'll come across later, a function may be created and immediately called or scheduled for a later execution, not stored anywhere, thus remaining anonymous. + +## Function is a value + +Let's reiterate: no matter how the function is created, a function is a value. Both examples above store a function in the `sayHi` variable. + +We can even print out that value using `alert`: + +```js run +function sayHi() { + alert( "Hello" ); +} + +*!* +alert( sayHi ); // shows the function code +*/!* +``` + +Please note that the last line does not run the function, because there are no parentheses after `sayHi`. There are programming languages where any mention of a function name causes its execution, but JavaScript is not like that. + +In JavaScript, a function is a value, so we can deal with it as a value. The code above shows its string representation, which is the source code. + +Surely, a function is a special value, in the sense that we can call it like `sayHi()`. + +But it's still a value. So we can work with it like with other kinds of values. + +We can copy a function to another variable: + +```js run no-beautify +function sayHi() { // (1) create + alert( "Hello" ); +} + +let func = sayHi; // (2) copy + +func(); // Hello // (3) run the copy (it works)! +sayHi(); // Hello // this still works too (why wouldn't it) +``` + +Here's what happens above in detail: + +1. The Function Declaration `(1)` creates the function and puts it into the variable named `sayHi`. +2. Line `(2)` copies it into the variable `func`. Please note again: there are no parentheses after `sayHi`. If there were, then `func = sayHi()` would write *the result of the call* `sayHi()` into `func`, not *the function* `sayHi` itself. +3. Now the function can be called as both `sayHi()` and `func()`. + +We could also have used a Function Expression to declare `sayHi`, in the first line: + +```js +let sayHi = function() { // (1) create + alert( "Hello" ); +}; + +let func = sayHi; //(2) +// ... +``` + +Everything would work the same. + + +````smart header="Why is there a semicolon at the end?" +You might wonder, why do Function Expressions have a semicolon `;` at the end, but Function Declarations do not: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js function sayHi() { @@ -100,6 +196,7 @@ let sayHi = function() { }*!*;*/!* ``` +<<<<<<< HEAD Ответ прост: Function Expression создаётся здесь как `function(...) {...}` внутри выражения присваивания: `let sayHi = …;`. Точку с запятой `;` рекомендуется ставить в конце выражения, она не является частью синтаксиса функции. Точка с запятой нужна там для более простого присваивания, такого как `let sayHi = 5;`, а также для присваивания функции. @@ -121,6 +218,29 @@ let sayHi = function() { : Функция, которая будет вызываться, если ответ будет "No" Наша функция должна задать вопрос `question` и, в зависимости от того, как ответит пользователь, вызвать `yes()` или `no()`: +======= +The answer is simple: a Function Expression is created here as `function(…) {…}` inside the assignment statement: `let sayHi = …;`. The semicolon `;` is recommended at the end of the statement, it's not a part of the function syntax. + +The semicolon would be there for a simpler assignment, such as `let sayHi = 5;`, and it's also there for a function assignment. +```` + +## Callback functions + +Let's look at more examples of passing functions as values and using function expressions. + +We'll write a function `ask(question, yes, no)` with three parameters: + +`question` +: Text of the question + +`yes` +: Function to run if the answer is "Yes" + +`no` +: Function to run if the answer is "No" + +The function should ask the `question` and, depending on the user's answer, call `yes()` or `no()`: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run *!* @@ -131,6 +251,7 @@ function ask(question, yes, no) { */!* function showOk() { +<<<<<<< HEAD alert( "Вы согласны." ); } @@ -149,6 +270,26 @@ ask("Вы согласны?", showOk, showCancel); Ключевая идея в том, что мы передаём функцию и ожидаем, что она вызовется обратно (от англ. "call back" - обратный вызов) когда-нибудь позже, если это будет необходимо. В нашем случае, `showOk` становится *колбэком* для ответа "yes", а `showCancel` -- для ответа "no". Мы можем переписать этот пример значительно короче, используя Function Expression: +======= + alert( "You agreed." ); +} + +function showCancel() { + alert( "You canceled the execution." ); +} + +// usage: functions showOk, showCancel are passed as arguments to ask +ask("Do you agree?", showOk, showCancel); +``` + +In practice, such functions are quite useful. The major difference between a real-life `ask` and the example above is that real-life functions use more complex ways to interact with the user than a simple `confirm`. In the browser, such functions usually draw a nice-looking question window. But that's another story. + +**The arguments `showOk` and `showCancel` of `ask` are called *callback functions* or just *callbacks*.** + +The idea is that we pass a function and expect it to be "called back" later if necessary. In our case, `showOk` becomes the callback for "yes" answer, and `showCancel` for "no" answer. + +We can use Function Expressions to write an equivalent, shorter function: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run no-beautify function ask(question, yes, no) { @@ -158,13 +299,20 @@ function ask(question, yes, no) { *!* ask( +<<<<<<< HEAD "Вы согласны?", function() { alert("Вы согласились."); }, function() { alert("Вы отменили выполнение."); } +======= + "Do you agree?", + function() { alert("You agreed."); }, + function() { alert("You canceled the execution."); } +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ); */!* ``` +<<<<<<< HEAD Здесь функции объявляются прямо внутри вызова `ask(...)`. У них нет имён, поэтому они называются *анонимными*. Такие функции недоступны снаружи `ask` (потому что они не присвоены переменным), но это как раз то, что нам нужно. @@ -188,6 +336,28 @@ ask( Во-первых, синтаксис: как отличить их друг от друга в коде. - *Function Declaration*: функция объявляется отдельной конструкцией "function..." в основном потоке кода. +======= +Here, functions are declared right inside the `ask(...)` call. They have no name, and so are called *anonymous*. Such functions are not accessible outside of `ask` (because they are not assigned to variables), but that's just what we want here. + +Such code appears in our scripts very naturally, it's in the spirit of JavaScript. + +```smart header="A function is a value representing an \"action\"" +Regular values like strings or numbers represent the *data*. + +A function can be perceived as an *action*. + +We can pass it between variables and run when we want. +``` + + +## Function Expression vs Function Declaration + +Let's formulate the key differences between Function Declarations and Expressions. + +First, the syntax: how to differentiate between them in the code. + +- *Function Declaration:* a function, declared as a separate statement, in the main code flow: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js // Function Declaration @@ -195,7 +365,11 @@ ask( return a + b; } ``` +<<<<<<< HEAD - *Function Expression*: функция, созданная внутри другого выражения или синтаксической конструкции. В данном случае функция создаётся в правой части "выражения присваивания" `=`: +======= +- *Function Expression:* a function, created inside an expression or inside another syntax construct. Here, the function is created on the right side of the "assignment expression" `=`: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js // Function Expression @@ -204,6 +378,7 @@ ask( }; ``` +<<<<<<< HEAD Более тонкое отличие состоит в том, *когда* создаётся функция движком JavaScript. **Function Expression создаётся, когда выполнение доходит до него, и затем уже может использоваться.** @@ -264,22 +439,93 @@ if (age < 18) { function welcome() { alert("Привет!"); +======= +The more subtle difference is *when* a function is created by the JavaScript engine. + +**A Function Expression is created when the execution reaches it and is usable only from that moment.** + +Once the execution flow passes to the right side of the assignment `let sum = function…` -- here we go, the function is created and can be used (assigned, called, etc. ) from now on. + +Function Declarations are different. + +**A Function Declaration can be called earlier than it is defined.** + +For example, a global Function Declaration is visible in the whole script, no matter where it is. + +That's due to internal algorithms. When JavaScript prepares to run the script, it first looks for global Function Declarations in it and creates the functions. We can think of it as an "initialization stage". + +And after all Function Declarations are processed, the code is executed. So it has access to these functions. + +For example, this works: + +```js run refresh untrusted +*!* +sayHi("John"); // Hello, John +*/!* + +function sayHi(name) { + alert( `Hello, ${name}` ); +} +``` + +The Function Declaration `sayHi` is created when JavaScript is preparing to start the script and is visible everywhere in it. + +...If it were a Function Expression, then it wouldn't work: + +```js run refresh untrusted +*!* +sayHi("John"); // error! +*/!* + +let sayHi = function(name) { // (*) no magic any more + alert( `Hello, ${name}` ); +}; +``` + +Function Expressions are created when the execution reaches them. That would happen only in the line `(*)`. Too late. + +Another special feature of Function Declarations is their block scope. + +**In strict mode, when a Function Declaration is within a code block, it's visible everywhere inside that block. But not outside of it.** + +For instance, let's imagine that we need to declare a function `welcome()` depending on the `age` variable that we get during runtime. And then we plan to use it some time later. + +If we use Function Declaration, it won't work as intended: + +```js run +let age = prompt("What is your age?", 18); + +// conditionally declare a function +if (age < 18) { + + function welcome() { + alert("Hello!"); +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 } } else { function welcome() { +<<<<<<< HEAD alert("Здравствуйте!"); +======= + alert("Greetings!"); +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 } } +<<<<<<< HEAD // ...не работает +======= +// ...use it later +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 *!* welcome(); // Error: welcome is not defined */!* ``` +<<<<<<< HEAD Это произошло, так как объявление Function Declaration видимо только внутри блока кода, в котором располагается. Вот ещё один пример: @@ -298,10 +544,31 @@ if (age < 18) { // | *!* welcome(); // / (выполнится) +======= +That's because a Function Declaration is only visible inside the code block in which it resides. + +Here's another example: + +```js run +let age = 16; // take 16 as an example + +if (age < 18) { +*!* + welcome(); // \ (runs) +*/!* + // | + function welcome() { // | + alert("Hello!"); // | Function Declaration is available + } // | everywhere in the block where it's declared + // | +*!* + welcome(); // / (runs) +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 */!* } else { +<<<<<<< HEAD function welcome() { alert("Здравствуйте!"); } @@ -323,24 +590,56 @@ welcome(); // Ошибка: welcome is not defined ```js run let age = prompt("Сколько Вам лет?", 18); +======= + function welcome() { + alert("Greetings!"); + } +} + +// Here we're out of curly braces, +// so we can not see Function Declarations made inside of them. + +*!* +welcome(); // Error: welcome is not defined +*/!* +``` + +What can we do to make `welcome` visible outside of `if`? + +The correct approach would be to use a Function Expression and assign `welcome` to the variable that is declared outside of `if` and has the proper visibility. + +This code works as intended: + +```js run +let age = prompt("What is your age?", 18); +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 let welcome; if (age < 18) { welcome = function() { +<<<<<<< HEAD alert("Привет!"); +======= + alert("Hello!"); +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 }; } else { welcome = function() { +<<<<<<< HEAD alert("Здравствуйте!"); +======= + alert("Greetings!"); +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 }; } *!* +<<<<<<< HEAD welcome(); // теперь всё в порядке */!* ``` @@ -356,10 +655,28 @@ let welcome = (age < 18) ? *!* welcome(); // теперь всё в порядке +======= +welcome(); // ok now +*/!* +``` + +Or we could simplify it even further using a question mark operator `?`: + +```js run +let age = prompt("What is your age?", 18); + +let welcome = (age < 18) ? + function() { alert("Hello!"); } : + function() { alert("Greetings!"); }; + +*!* +welcome(); // ok now +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 */!* ``` +<<<<<<< HEAD ```smart header="Когда использовать Function Declaration, а когда Function Expression?" Как правило, если нам понадобилась функция, в первую очередь нужно рассматривать синтаксис Function Declaration, который мы использовали до этого. Он даёт нам больше свободы в том, как мы можем организовывать код. Функции, объявленные таким образом, можно вызывать до их объявления. @@ -379,3 +696,24 @@ welcome(); // теперь всё в порядке В большинстве случаев, когда нам нужно объявить функцию, Function Declaration предпочтительнее, т.к функция будет видна до своего объявления в коде. Это даёт нам больше гибкости в организации кода, и, как правило, делает его более читабельным. Исходя из этого, мы должны использовать Function Expression только тогда, когда Function Declaration не подходит для нашей задачи. Мы рассмотрели несколько таких примеров в этой главе, и увидим ещё больше в будущем. +======= +```smart header="When to choose Function Declaration versus Function Expression?" +As a rule of thumb, when we need to declare a function, the first thing to consider is Function Declaration syntax. It gives more freedom in how to organize our code, because we can call such functions before they are declared. + +That's also better for readability, as it's easier to look up `function f(…) {…}` in the code than `let f = function(…) {…};`. Function Declarations are more "eye-catching". + +...But if a Function Declaration does not suit us for some reason, or we need a conditional declaration (we've just seen an example), then Function Expression should be used. +``` + +## Summary + +- Functions are values. They can be assigned, copied or declared in any place of the code. +- If the function is declared as a separate statement in the main code flow, that's called a "Function Declaration". +- If the function is created as a part of an expression, it's called a "Function Expression". +- Function Declarations are processed before the code block is executed. They are visible everywhere in the block. +- Function Expressions are created when the execution flow reaches them. + +In most cases when we need to declare a function, a Function Declaration is preferable, because it is visible prior to the declaration itself. That gives us more flexibility in code organization, and is usually more readable. + +So we should use a Function Expression only when a Function Declaration is not fit for the task. We've seen a couple of examples of that in this chapter, and will see more in the future. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/1-js/02-first-steps/17-arrow-functions-basics/1-rewrite-arrow/solution.md b/1-js/02-first-steps/17-arrow-functions-basics/1-rewrite-arrow/solution.md index dedd9fac02..82e14c1899 100644 --- a/1-js/02-first-steps/17-arrow-functions-basics/1-rewrite-arrow/solution.md +++ b/1-js/02-first-steps/17-arrow-functions-basics/1-rewrite-arrow/solution.md @@ -1,17 +1,32 @@ ```js run function ask(question, yes, no) { +<<<<<<< HEAD if (confirm(question)) yes() +======= + if (confirm(question)) yes(); +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 else no(); } ask( +<<<<<<< HEAD "Вы согласны?", *!* () => alert("Вы согласились."), () => alert("Вы отменили выполнение.") +======= + "Do you agree?", +*!* + () => alert("You agreed."), + () => alert("You canceled the execution.") +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 */!* ); ``` +<<<<<<< HEAD Выглядит короче и понятней, правда? +======= +Looks short and clean, right? +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/1-js/02-first-steps/17-arrow-functions-basics/1-rewrite-arrow/task.md b/1-js/02-first-steps/17-arrow-functions-basics/1-rewrite-arrow/task.md index 3e27c78b63..2f493792c5 100644 --- a/1-js/02-first-steps/17-arrow-functions-basics/1-rewrite-arrow/task.md +++ b/1-js/02-first-steps/17-arrow-functions-basics/1-rewrite-arrow/task.md @@ -1,4 +1,5 @@ +<<<<<<< HEAD # Перепишите с использованием функции-стрелки Замените код Function Expression стрелочной функцией: @@ -6,12 +7,27 @@ ```js run function ask(question, yes, no) { if (confirm(question)) yes() +======= +# Rewrite with arrow functions + +Replace Function Expressions with arrow functions in the code below: + +```js run +function ask(question, yes, no) { + if (confirm(question)) yes(); +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 else no(); } ask( +<<<<<<< HEAD "Вы согласны?", function() { alert("Вы согласились."); }, function() { alert("Вы отменили выполнение."); } +======= + "Do you agree?", + function() { alert("You agreed."); }, + function() { alert("You canceled the execution."); } +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ); ``` diff --git a/1-js/02-first-steps/17-arrow-functions-basics/article.md b/1-js/02-first-steps/17-arrow-functions-basics/article.md index c41aa01f4d..ce5bfcf6ec 100644 --- a/1-js/02-first-steps/17-arrow-functions-basics/article.md +++ b/1-js/02-first-steps/17-arrow-functions-basics/article.md @@ -1,3 +1,4 @@ +<<<<<<< HEAD # Стрелочные функции, основы Существует ещё один очень простой и лаконичный синтаксис для создания функций, который часто лучше, чем Function Expression. @@ -14,16 +15,42 @@ let func = (arg1, arg2, ...argN) => expression; ```js let func = function(arg1, arg2, ...argN) { +======= +# Arrow functions, the basics + +There's another very simple and concise syntax for creating functions, that's often better than Function Expressions. + +It's called "arrow functions", because it looks like this: + +```js +let func = (arg1, arg2, ..., argN) => expression; +``` + +This creates a function `func` that accepts arguments `arg1..argN`, then evaluates the `expression` on the right side with their use and returns its result. + +In other words, it's the shorter version of: + +```js +let func = function(arg1, arg2, ..., argN) { +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 return expression; }; ``` +<<<<<<< HEAD Давайте рассмотрим конкретный пример: +======= +Let's see a concrete example: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let sum = (a, b) => a + b; +<<<<<<< HEAD /* Эта стрелочная функция представляет собой более короткую форму: +======= +/* This arrow function is a shorter form of: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 let sum = function(a, b) { return a + b; @@ -31,23 +58,41 @@ let sum = function(a, b) { */ alert( sum(1, 2) ); // 3 +<<<<<<< HEAD ``` Как вы можете видеть, `(a, b) => a + b` задаёт функцию, которая принимает два аргумента с именами `a` и `b`. И при выполнении она вычисляет выражение `a + b` и возвращает результат. - Если у нас только один аргумент, то круглые скобки вокруг параметров можно опустить, сделав запись ещё короче: +======= +``` + +As you can see, `(a, b) => a + b` means a function that accepts two arguments named `a` and `b`. Upon the execution, it evaluates the expression `a + b` and returns the result. + +- If we have only one argument, then parentheses around parameters can be omitted, making that even shorter. + + For example: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run *!* let double = n => n * 2; +<<<<<<< HEAD // примерно тоже что и: let double = function(n) { return n * 2 } +======= + // roughly the same as: let double = function(n) { return n * 2 } +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 */!* alert( double(3) ); // 6 ``` +<<<<<<< HEAD - Если аргументов нет, круглые скобки будут пустыми, но они должны присутствовать: +======= +- If there are no arguments, parentheses are empty, but they must be present: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let sayHi = () => alert("Hello!"); @@ -55,6 +100,7 @@ alert( sum(1, 2) ); // 3 sayHi(); ``` +<<<<<<< HEAD Стрелочные функции можно использовать так же, как и Function Expression. Например, для динамического создания функции: @@ -65,10 +111,23 @@ let age = prompt("Сколько Вам лет?", 18); let welcome = (age < 18) ? () => alert('Привет!') : () => alert("Здравствуйте!"); +======= +Arrow functions can be used in the same way as Function Expressions. + +For instance, to dynamically create a function: + +```js run +let age = prompt("What is your age?", 18); + +let welcome = (age < 18) ? + () => alert('Hello!') : + () => alert("Greetings!"); +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 welcome(); ``` +<<<<<<< HEAD Поначалу стрелочные функции могут показаться необычными и даже трудночитаемыми, но это быстро пройдёт по мере того, как глаза привыкнут к этим конструкциям. Они очень удобны для простых однострочных действий, когда лень писать много слов. @@ -86,12 +145,32 @@ let sum = (a, b) => { // фигурная скобка, открывающая let result = a + b; *!* return result; // если мы используем фигурные скобки, то нам нужно явно указать "return" +======= +Arrow functions may appear unfamiliar and not very readable at first, but that quickly changes as the eyes get used to the structure. + +They are very convenient for simple one-line actions, when we're just too lazy to write many words. + +## Multiline arrow functions + +The arrow functions that we've seen so far were very simple. They took arguments from the left of `=>`, evaluated and returned the right-side expression with them. + +Sometimes we need a more complex function, with multiple expressions and statements. In that case, we can enclose them in curly braces. The major difference is that curly braces require a `return` within them to return a value (just like a regular function does). + +Like this: + +```js run +let sum = (a, b) => { // the curly brace opens a multiline function + let result = a + b; +*!* + return result; // if we use curly braces, then we need an explicit "return" +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 */!* }; alert( sum(1, 2) ); // 3 ``` +<<<<<<< HEAD ```smart header="Дальше - больше" Здесь мы представили главной целью стрелочных функций краткость. Но это ещё не всё! @@ -110,3 +189,21 @@ alert( sum(1, 2) ); // 3 1. Без фигурных скобок: `(...args) => expression` -- правая сторона выражения: функция вычисляет его и возвращает результат. Скобки можно не ставить, если аргумент только один: `n => n * 2`. 2. С фигурными скобками: `(...args) => { body }` -- скобки позволяют нам писать несколько инструкций внутри функции, но при этом необходимо явно вызывать `return`, чтобы вернуть значение. +======= +```smart header="More to come" +Here we praised arrow functions for brevity. But that's not all! + +Arrow functions have other interesting features. + +To study them in-depth, we first need to get to know some other aspects of JavaScript, so we'll return to arrow functions later in the chapter . + +For now, we can already use arrow functions for one-line actions and callbacks. +``` + +## Summary + +Arrow functions are handy for simple actions, especially for one-liners. They come in two flavors: + +1. Without curly braces: `(...args) => expression` -- the right side is an expression: the function evaluates it and returns the result. Parentheses can be omitted, if there's only a single argument, e.g. `n => n*2`. +2. With curly braces: `(...args) => { body }` -- brackets allow us to write multiple statements inside the function, but we need an explicit `return` to return something. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/1-js/02-first-steps/18-javascript-specials/article.md b/1-js/02-first-steps/18-javascript-specials/article.md index 4bd0206e36..7e517a0f98 100644 --- a/1-js/02-first-steps/18-javascript-specials/article.md +++ b/1-js/02-first-steps/18-javascript-specials/article.md @@ -1,3 +1,4 @@ +<<<<<<< HEAD # Особенности JavaScript Давайте кратко повторим изученный материал и отметим наиболее "тонкие" моменты. @@ -21,10 +22,36 @@ alert('Мир') ```js run alert("После этого сообщения ждите ошибку") +======= +# JavaScript specials + +This chapter briefly recaps the features of JavaScript that we've learned by now, paying special attention to subtle moments. + +## Code structure + +Statements are delimited with a semicolon: + +```js run no-beautify +alert('Hello'); alert('World'); +``` + +Usually, a line-break is also treated as a delimiter, so that would also work: + +```js run no-beautify +alert('Hello') +alert('World') +``` + +That's called "automatic semicolon insertion". Sometimes it doesn't work, for instance: + +```js run +alert("There will be an error after this message") +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 [1, 2].forEach(alert) ``` +<<<<<<< HEAD Большинство руководств по стилю кода рекомендуют ставить точку с запятой после каждой инструкции. Точка с запятой не требуется после блоков кода {...} и синтаксических конструкций с ними, таких как, например, циклы: @@ -46,6 +73,29 @@ for(;;) { ## Строгий режим Чтобы по максимуму использовать возможности современного JavaScript, все скрипты рекомендуется начинать с добавления директивы `"use strict"`. +======= +Most codestyle guides agree that we should put a semicolon after each statement. + +Semicolons are not required after code blocks `{...}` and syntax constructs with them like loops: + +```js +function f() { + // no semicolon needed after function declaration +} + +for(;;) { + // no semicolon needed after the loop +} +``` + +...But even if we can put an "extra" semicolon somewhere, that's not an error. It will be ignored. + +More in: . + +## Strict mode + +To fully enable all features of modern JavaScript, we should start scripts with `"use strict"`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js 'use strict'; @@ -53,6 +103,7 @@ for(;;) { ... ``` +<<<<<<< HEAD Эту директиву следует размещать в первой строке скрипта или в начале тела функции. Без `"use strict"` код также запустится, но некоторые возможности будут работать в "режиме совместимости" со старыми версиями языка JavaScript. Нам же предпочтительнее современное поведение. @@ -157,12 +208,119 @@ JavaScript поддерживает следующие операторы: Сравнение : Проверка на равенство `==` значений разных типов конвертирует их в число (за исключением `null` и `undefined`, которые могут равняться только друг другу), так что примеры ниже равны: +======= +The directive must be at the top of a script or at the beginning of a function body. + +Without `"use strict"`, everything still works, but some features behave in the old-fashioned, "compatible" way. We'd generally prefer the modern behavior. + +Some modern features of the language (like classes that we'll study in the future) enable strict mode implicitly. + +More in: . + +## Variables + +Can be declared using: + +- `let` +- `const` (constant, can't be changed) +- `var` (old-style, will see later) + +A variable name can include: +- Letters and digits, but the first character may not be a digit. +- Characters `$` and `_` are normal, on par with letters. +- Non-Latin alphabets and hieroglyphs are also allowed, but commonly not used. + +Variables are dynamically typed. They can store any value: + +```js +let x = 5; +x = "John"; +``` + +There are 8 data types: + +- `number` for both floating-point and integer numbers, +- `bigint` for integer numbers of arbitrary length, +- `string` for strings, +- `boolean` for logical values: `true/false`, +- `null` -- a type with a single value `null`, meaning "empty" or "does not exist", +- `undefined` -- a type with a single value `undefined`, meaning "not assigned", +- `object` and `symbol` -- for complex data structures and unique identifiers, we haven't learnt them yet. + +The `typeof` operator returns the type for a value, with two exceptions: +```js +typeof null == "object" // error in the language +typeof function(){} == "function" // functions are treated specially +``` + +More in: and . + +## Interaction + +We're using a browser as a working environment, so basic UI functions will be: + +[`prompt(question, [default])`](https://developer.mozilla.org/en-US/docs/Web/API/Window/prompt) +: Ask a `question`, and return either what the visitor entered or `null` if they clicked "cancel". + +[`confirm(question)`](https://developer.mozilla.org/en-US/docs/Web/API/Window/confirm) +: Ask a `question` and suggest to choose between Ok and Cancel. The choice is returned as `true/false`. + +[`alert(message)`](https://developer.mozilla.org/en-US/docs/Web/API/Window/alert) +: Output a `message`. + +All these functions are *modal*, they pause the code execution and prevent the visitor from interacting with the page until they answer. + +For instance: + +```js run +let userName = prompt("Your name?", "Alice"); +let isTeaWanted = confirm("Do you want some tea?"); + +alert( "Visitor: " + userName ); // Alice +alert( "Tea wanted: " + isTeaWanted ); // true +``` + +More in: . + +## Operators + +JavaScript supports the following operators: + +Arithmetical +: Regular: `* + - /`, also `%` for the remainder and `**` for power of a number. + + The binary plus `+` concatenates strings. And if any of the operands is a string, the other one is converted to string too: + + ```js run + alert( '1' + 2 ); // '12', string + alert( 1 + '2' ); // '12', string + ``` + +Assignments +: There is a simple assignment: `a = b` and combined ones like `a *= 2`. + +Bitwise +: Bitwise operators work with 32-bit integers at the lowest, bit-level: see the [docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#bitwise_operators) when they are needed. + +Conditional +: The only operator with three parameters: `cond ? resultA : resultB`. If `cond` is truthy, returns `resultA`, otherwise `resultB`. + +Logical operators +: Logical AND `&&` and OR `||` perform short-circuit evaluation and then return the value where it stopped (not necessary `true`/`false`). Logical NOT `!` converts the operand to boolean type and returns the inverse value. + +Nullish coalescing operator +: The `??` operator provides a way to choose a defined value from a list of variables. The result of `a ?? b` is `a` unless it's `null/undefined`, then `b`. + +Comparisons +: Equality check `==` for values of different types converts them to a number (except `null` and `undefined` that equal each other and nothing else), so these are equal: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run alert( 0 == false ); // true alert( 0 == '' ); // true ``` +<<<<<<< HEAD Другие операторы сравнения тоже конвертируют значения разных типов в числовой тип. Оператор строгого равенства `===` не выполняет конвертирования: разные типы для него всегда означают разные значения. @@ -179,6 +337,24 @@ JavaScript поддерживает следующие операторы: ## Циклы - Мы изучили три вида циклов: +======= + Other comparisons convert to a number as well. + + The strict equality operator `===` doesn't do the conversion: different types always mean different values for it. + + Values `null` and `undefined` are special: they equal `==` each other and don't equal anything else. + + Greater/less comparisons compare strings character-by-character, other types are converted to a number. + +Other operators +: There are few others, like a comma operator. + +More in: , , , . + +## Loops + +- We covered 3 types of loops: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js // 1 @@ -197,6 +373,7 @@ JavaScript поддерживает следующие операторы: } ``` +<<<<<<< HEAD - Переменная, объявленная в цикле `for(let...)`, видна только внутри цикла. Но мы также можем опустить `let` и переиспользовать существующую переменную. - Директивы `break/continue` позволяют выйти из цикла/текущей итерации. Используйте метки для выхода из вложенных циклов. @@ -233,6 +410,45 @@ switch (age) { Мы рассмотрели три способа создания функции в JavaScript: 1. Function Declaration: функция в основном потоке кода +======= +- The variable declared in `for(let...)` loop is visible only inside the loop. But we can also omit `let` and reuse an existing variable. +- Directives `break/continue` allow to exit the whole loop/current iteration. Use labels to break nested loops. + +Details in: . + +Later we'll study more types of loops to deal with objects. + +## The "switch" construct + +The "switch" construct can replace multiple `if` checks. It uses `===` (strict equality) for comparisons. + +For instance: + +```js run +let age = prompt('Your age?', 18); + +switch (age) { + case 18: + alert("Won't work"); // the result of prompt is a string, not a number + break; + + case "18": + alert("This works!"); + break; + + default: + alert("Any value not equal to one above"); +} +``` + +Details in: . + +## Functions + +We covered three ways to create a function in JavaScript: + +1. Function Declaration: the function in the main code flow +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js function sum(a, b) { @@ -242,7 +458,11 @@ switch (age) { } ``` +<<<<<<< HEAD 2. Function Expression: функция как часть выражения +======= +2. Function Expression: the function in the context of an expression +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js let sum = function(a, b) { @@ -252,6 +472,7 @@ switch (age) { }; ``` +<<<<<<< HEAD 3. Стрелочные функции: ```js @@ -259,19 +480,36 @@ switch (age) { let sum = (a, b) => a + b; // многострочный код в фигурных скобках { ... }, здесь нужен return: +======= +3. Arrow functions: + + ```js + // expression on the right side + let sum = (a, b) => a + b; + + // or multi-line syntax with { ... }, need return here: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 let sum = (a, b) => { // ... return a + b; } +<<<<<<< HEAD // без аргументов let sayHi = () => alert("Привет"); // с одним аргументом +======= + // without arguments + let sayHi = () => alert("Hello"); + + // with a single argument +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 let double = n => n * 2; ``` +<<<<<<< HEAD - У функций могут быть локальные переменные: т.е. объявленные в теле функции. Такие переменные видимы только внутри функции. - У параметров могут быть значения по умолчанию: `function sum(a = 1, b = 2) {...}`. - Функции всегда что-нибудь возвращают. Если нет оператора `return`, результатом будет `undefined`. @@ -281,3 +519,14 @@ switch (age) { ## Далее мы изучим больше Это был краткий список возможностей JavaScript. На данный момент мы изучили только основы. Далее в учебнике вы найдёте больше особенностей и продвинутых возможностей JavaScript. +======= +- Functions may have local variables: those declared inside its body or its parameter list. Such variables are only visible inside the function. +- Parameters can have default values: `function sum(a = 1, b = 2) {...}`. +- Functions always return something. If there's no `return` statement, then the result is `undefined`. + +Details: see , . + +## More to come + +That was a brief list of JavaScript features. As of now we've studied only basics. Further in the tutorial you'll find more specials and advanced features of JavaScript. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/1-js/03-code-quality/01-debugging-chrome/article.md b/1-js/03-code-quality/01-debugging-chrome/article.md index 7181d98caa..229fa9f07a 100644 --- a/1-js/03-code-quality/01-debugging-chrome/article.md +++ b/1-js/03-code-quality/01-debugging-chrome/article.md @@ -1,10 +1,18 @@ +<<<<<<< HEAD # Отладка в браузере +======= +# Debugging in the browser +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Давайте отвлечёмся от написания кода и поговорим о его отладке. [Отладка](https://ru.wikipedia.org/wiki/%D0%9E%D1%82%D0%BB%D0%B0%D0%B4%D0%BA%D0%B0_%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D1%8B) - это процесс поиска и исправления ошибок в скрипте. Все современные браузеры и большинство других сред разработки поддерживают инструменты для отладки - специальный графический интерфейс, который сильно упрощает отладку. Он также позволяет по шагам отследить, что именно происходит в нашем коде. +<<<<<<< HEAD Мы будем использовать браузер Chrome, так как у него достаточно возможностей, в большинстве других браузеров процесс будет схожим. +======= +We'll be using Chrome here, because it has enough features, most other browsers have a similar process. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## Панель "Исходный код" ("Sources") @@ -18,12 +26,17 @@ ![](chrome-open-sources.svg) +<<<<<<< HEAD Кнопка-переключатель откроет вкладку со списком файлов. +======= +The toggler button opens the tab with files. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Кликните на неё и выберите `hello.js` в дереве файлов. Вот что появится: ![](chrome-tabs.svg) +<<<<<<< HEAD Интерфейс состоит из трёх зон: 1. В зоне **File Navigator** (панель для навигации файлов) показаны файлы HTML, JavaScript, CSS, включая изображения, используемые на странице. Здесь также могут быть файлы различных расширений Chrome. @@ -31,6 +44,15 @@ 3. Наконец, зона **JavaScript Debugging** (панель отладки JavaScript) отведена для отладки, скоро мы к ней вернёмся. Чтобы скрыть список ресурсов и освободить экранное место для исходного кода, щёлкните по тому же переключателю . +======= +The Sources panel has 3 parts: + +1. The **File Navigator** pane lists HTML, JavaScript, CSS and other files, including images that are attached to the page. Chrome extensions may appear here too. +2. The **Code Editor** pane shows the source code. +3. The **JavaScript Debugging** pane is for debugging, we'll explore it soon. + +Now you could click the same toggler again to hide the resources list and give the code some space. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## Консоль @@ -38,7 +60,11 @@ Результат выполнения инструкций сразу же отображается в консоли. +<<<<<<< HEAD Например, результатом `1+2` будет `3`, а вызов функции `hello("debugger")` ничего не возвращает, так что результатом будет `undefined`: +======= +For example, here `1+2` results in `3`, while the function call `hello("debugger")` returns nothing, so the result is `undefined`: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ![](chrome-sources-console.svg) @@ -62,13 +88,22 @@ - Удалите точку останова, щёлкнув правой кнопкой мыши и выбрав Remove (Удалить). - ...и так далее. +<<<<<<< HEAD ```smart header="Условные точки останова" *Щелчок правой кнопкой мыши* по номеру строки позволяет создать *условную* точку останова. Она сработает только в тот момент, когда выражение, которое вы должны указать при создании такой точки, истинно. +======= +```smart header="Conditional breakpoints" +*Right click* on the line number allows to create a *conditional* breakpoint. It only triggers when the given expression, that you should provide when you create it, is truthy. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Это удобно, когда нам нужно остановиться только при определённом значении переменной или для определённых параметров функции. ``` +<<<<<<< HEAD ## Команда debugger +======= +## The command "debugger" +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Выполнение кода можно также приостановить с помощью команды `debugger` прямо изнутри самого кода: @@ -84,9 +119,13 @@ function hello(name) { } ``` +<<<<<<< HEAD Такая команда сработает только если открыты инструменты разработки, иначе браузер ее проигнорирует. ## Остановимся и оглядимся +======= +Such command works only when the development tools are open, otherwise the browser ignores it. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 В нашем примере функция `hello()` вызывается во время загрузки страницы, поэтому для начала отладки (после того, как мы поставили точки останова) проще всего её перезагрузить. Нажмите `key:F5` (Windows, Linux) или `key:Cmd+R` (Mac). @@ -98,7 +137,11 @@ function hello(name) { 1. **`Watch`– показывает текущие значения для любых выражений.** +<<<<<<< HEAD Вы можете нажать на `+` и ввести выражение. Отладчик покажет его значение, автоматически пересчитывая его в процессе выполнения. +======= + You can click the plus `+` and input an expression. The debugger will show its value, automatically recalculating it in the process of execution. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 2. **`Call Stack` – показывает цепочку вложенных вызовов.** @@ -116,12 +159,19 @@ function hello(name) { ## Пошаговое выполнение скрипта +<<<<<<< HEAD А теперь давайте *пошагаем* по нашему скрипту. Для этого есть кнопки в верхней части правой панели. Давайте рассмотрим их. – "Resume": продолжить выполнение, быстрая клавиша `key:F8`. : Возобновляет выполнение кода. Если больше нет точек останова, то выполнение просто продолжается, без контроля отладчиком. +======= +There are buttons for it at the top of the right panel. Let's engage them. + + -- "Resume": continue the execution, hotkey `key:F8`. +: Resumes the execution. If there are no additional breakpoints, then the execution just continues and the debugger loses control. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Вот, что мы увидим, кликнув на неё: @@ -129,6 +179,7 @@ function hello(name) { Выполнение кода возобновилось, дошло до другой точки останова внутри `say()`, и отладчик снова приостановил выполнение. Обратите внимание на пункт "Call stack" справа: в списке появился ещё один вызов. Сейчас мы внутри `say()`. +<<<<<<< HEAD – "Step": выполнить следующую команду, быстрая клавиша `key:F9`. : Выполняет следующую инструкцию. Если мы нажмём на неё сейчас, появится `alert`. @@ -156,6 +207,35 @@ function hello(name) { – включить/отключить автоматическую паузу в случае ошибки. : При включении, если открыты инструменты разработчика, ошибка при выполнении скрипта автоматически приостанавливает его. Затем мы можем проанализировать переменные в отладчике, чтобы понять, что пошло не так. Поэтому, если наш скрипт умирает с ошибкой, мы можем открыть отладчик, включить эту опцию и перезагрузить страницу, чтобы увидеть, где он умирает и каков контекст в этот момент. +======= + -- "Step": run the next command, hotkey `key:F9`. +: Run the next statement. If we click it now, `alert` will be shown. + + Clicking this again and again will step through all script statements one by one. + + -- "Step over": run the next command, but *don't go into a function*, hotkey `key:F10`. +: Similar to the previous "Step" command, but behaves differently if the next statement is a function call (not a built-in, like `alert`, but a function of our own). + + If we compare them, the "Step" command goes into a nested function call and pauses the execution at its first line, while "Step over" executes the nested function call invisibly to us, skipping the function internals. + + The execution is then paused immediately after that function call. + + That's good if we're not interested to see what happens inside the function call. + + -- "Step into", hotkey `key:F11`. +: That's similar to "Step", but behaves differently in case of asynchronous function calls. If you're only starting to learn JavaScript, then you can ignore the difference, as we don't have asynchronous calls yet. + + For the future, just note that "Step" command ignores async actions, such as `setTimeout` (scheduled function call), that execute later. The "Step into" goes into their code, waiting for them if necessary. See [DevTools manual](https://developers.google.com/web/updates/2018/01/devtools#async) for more details. + + -- "Step out": continue the execution till the end of the current function, hotkey `key:Shift+F11`. +: Continue the execution and stop it at the very last line of the current function. That's handy when we accidentally entered a nested call using , but it does not interest us, and we want to continue to its end as soon as possible. + + -- enable/disable all breakpoints. +: That button does not move the execution. Just a mass on/off for breakpoints. + + -- enable/disable automatic pause in case of an error. +: When enabled, if the developer tools is open, an error during the script execution automatically pauses it. Then we can analyze variables in the debugger to see what went wrong. So if our script dies with an error, we can open debugger, enable this option and reload the page to see where it dies and what's the context at that moment. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```smart header="Continue to here" Щелчок правой кнопкой мыши по строке кода открывает контекстное меню с отличной опцией под названием "Continue to here" ("продолжить до этого места"). @@ -182,11 +262,20 @@ for (let i = 0; i < 5; i++) { ## Итого +<<<<<<< HEAD Приостановить выполнение скрипта можно тремя способами: 1. Точками останова (breakpoints). 2. Использованием в коде команд `debugger`. 3. При ошибке (если инструменты разработчика открыты и кнопка "включена"). +======= +As we can see, there are three main ways to pause a script: +1. A breakpoint. +2. The `debugger` statements. +3. An error (if dev tools are open and the button is "on"). + +When paused, we can debug: examine variables and trace the code to see where the execution goes wrong. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 При остановке мы можем отлаживать: анализировать переменные и пошагово пройти по процессу, чтобы отыскать проблему. diff --git a/1-js/03-code-quality/02-coding-style/1-style-errors/solution.md b/1-js/03-code-quality/02-coding-style/1-style-errors/solution.md index 26c8058e93..b089dd502d 100644 --- a/1-js/03-code-quality/02-coding-style/1-style-errors/solution.md +++ b/1-js/03-code-quality/02-coding-style/1-style-errors/solution.md @@ -10,12 +10,21 @@ function pow(x,n) // <- отсутствует пробел между аргу return result; } +<<<<<<< HEAD let x=prompt("x?",''), n=prompt("n?",'') // <-- технически допустимо, // но лучше написать в 2 строки, также нет пробелов и точки с запятой if (n<=0) // <- нет пробелов, стоит добавить отступ в одну строку сверху { // <- фигурная скобка на отдельной строке // ниже - слишком длинная строка, лучше разбить для улучшения читаемости alert(`Степень ${n} не поддерживается, введите целую степень, большую 0`); +======= +let x=prompt("x?",''), n=prompt("n?",'') // <-- technically possible, +// but better make it 2 lines, also there's no spaces and missing ; +if (n<=0) // <- no spaces inside (n <= 0), and should be extra line above it +{ // <- figure bracket on a separate line + // below - long lines can be split into multiple lines for improved readability + alert(`Power ${n} is not supported, please enter an integer number greater than zero`); +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 } else // <- можно на одной строке, вместе: "} else {" { @@ -40,8 +49,13 @@ let x = prompt("x?", ""); let n = prompt("n?", ""); if (n <= 0) { +<<<<<<< HEAD alert(`Степень ${n} не поддерживается, введите целую степень, большую 0`); +======= + alert(`Power ${n} is not supported, + please enter an integer number greater than zero`); +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 } else { alert( pow(x, n) ); } diff --git a/1-js/03-code-quality/02-coding-style/article.md b/1-js/03-code-quality/02-coding-style/article.md index e7c3974760..973c97cf28 100644 --- a/1-js/03-code-quality/02-coding-style/article.md +++ b/1-js/03-code-quality/02-coding-style/article.md @@ -85,9 +85,15 @@ if (condition) { ```js // обратные кавычки ` позволяют разделять строку на части let str = ` +<<<<<<< HEAD Рабочая группа TC39 организации Ecma International - это группа JavaScript-разработчиков, теоретиков и авторов движков JavaScript, которые вместе с сообществом занимаются поддержкой и развитием языка JavaScript. +======= + ECMA International's TC39 is a group of JavaScript developers, + implementers, academics, and more, collaborating with the community + to maintain and evolve the definition of JavaScript. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 `; ``` @@ -115,7 +121,11 @@ if ( Одно из преимуществ пробелов над табуляцией заключается в том, что пробелы допускают более гибкие конфигурации отступов, чем символ табуляции. +<<<<<<< HEAD Например, мы можем выровнять аргументы относительно открывающей скобки: +======= + For instance, we can align the parameters with the opening bracket, like this: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js no-beautify show(parameters, @@ -284,9 +294,15 @@ function pow(x, n) { Некоторые популярные руководства: +<<<<<<< HEAD - [Google JavaScript Style Guide](https://google.github.io/styleguide/javascriptguide.xml) - [Airbnb JavaScript Style Guide](https://github.com/airbnb/javascript) (есть [перевод](https://leonidlebedev.github.io/javascript-airbnb/)) - [Idiomatic.JS](https://github.com/rwaldron/idiomatic.js) (есть [перевод](https://github.com/rwaldron/idiomatic.js/tree/master/translations/ru_RU)) +======= +- [Google JavaScript Style Guide](https://google.github.io/styleguide/jsguide.html) +- [Airbnb JavaScript Style Guide](https://github.com/airbnb/javascript) +- [Idiomatic.JS](https://github.com/rwaldron/idiomatic.js) +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 - [StandardJS](https://standardjs.com/) - (и ещё множество других) @@ -300,11 +316,19 @@ function pow(x, n) { Вот некоторые известные инструменты для проверки: +<<<<<<< HEAD - [JSLint](https://www.jslint.com/) -- проверяет код на соответствие [стилю JSLint](https://www.jslint.com/index.html), в онлайн-интерфейсе вверху можно ввести код, а внизу -- различные настройки проверки, чтобы попробовать её в действии. - [JSHint](https://jshint.com/) -- больше проверок, чем в JSLint. - [ESLint](https://eslint.org/) -- пожалуй, самый современный линтер. Все они, в общем-то, работают. Автор пользуется [ESLint](https://eslint.org/). +======= +- [JSLint](https://www.jslint.com/) -- one of the first linters. +- [JSHint](https://jshint.com/) -- more settings than JSLint. +- [ESLint](https://eslint.org/) -- probably the newest one. + +All of them can do the job. The author uses [ESLint](https://eslint.org/). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Большинство линтеров интегрированы со многими популярными редакторами: просто включите плагин в редакторе и настройте стиль. @@ -327,14 +351,18 @@ function pow(x, n) { }, "rules": { "no-console": 0, - "indent": ["warning", 2] + "indent": 2 } } ``` Здесь директива `"extends"` означает, что конфигурация основана на наборе настроек "eslint:recommended". После этого мы уточняем наши собственные. +<<<<<<< HEAD Кроме того, возможно загрузить наборы правил стиля из сети и расширить их. Смотрите для получения более подробной информации об установке. +======= +It is also possible to download style rule sets from the web and extend them instead. See for more details about installation. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Также некоторые среды разработки имеют встроенные линтеры, возможно, удобные, но не такие гибкие в настройке, как ESLint. diff --git a/1-js/03-code-quality/03-comments/article.md b/1-js/03-code-quality/03-comments/article.md index 0f1e25b0c1..fe7256f54c 100644 --- a/1-js/03-code-quality/03-comments/article.md +++ b/1-js/03-code-quality/03-comments/article.md @@ -125,6 +125,7 @@ function addJuice(container) { Документируйте параметры и использование функций : Есть специальный синтаксис [JSDoc](https://ru.wikipedia.org/wiki/JSDoc) для документирования функций: использование, параметры, возвращаемое значение. +<<<<<<< HEAD Например: ```js /** @@ -133,17 +134,35 @@ function addJuice(container) { * @param {number} x Возводимое в степень число. * @param {number} n Степень, должна быть натуральным числом. * @return {number} x, возведённое в n-ную степень. +======= +For instance: +```js +/** + * Returns x raised to the n-th power. + * + * @param {number} x The number to raise. + * @param {number} n The power, must be a natural number. + * @return {number} x raised to the n-th power. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 */ function pow(x, n) { ... } ``` +<<<<<<< HEAD Подобные комментарии позволяют нам понимать назначение функции и правильно её использовать без необходимости заглядывать в код. Кстати, многие редакторы, такие как [WebStorm](https://www.jetbrains.com/webstorm/), прекрасно их распознают для того, чтобы выполнить автодополнение ввода и различные автоматические проверки кода. Также существуют инструменты, например, [JSDoc 3](https://github.com/jsdoc/jsdoc), которые умеют генерировать HTML-документацию из комментариев. Получить больше информации о JSDoc вы можете здесь: . +======= +Such comments allow us to understand the purpose of the function and use it the right way without looking in its code. + +By the way, many editors like [WebStorm](https://www.jetbrains.com/webstorm/) can understand them as well and use them to provide autocomplete and some automatic code-checking. + +Also, there are tools like [JSDoc 3](https://github.com/jsdoc/jsdoc) that can generate HTML-documentation from the comments. You can read more information about JSDoc at . +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Почему задача решена именно таким способом? : Важно то, что написано. Но то, что *не* написано, может быть даже более важным, чтобы понимать происходящее. Почему задача решена именно этим способом? Код не даёт ответа. diff --git a/1-js/03-code-quality/04-ninja-code/article.md b/1-js/03-code-quality/04-ninja-code/article.md index f8c4e5c16a..8f34bd2d1a 100644 --- a/1-js/03-code-quality/04-ninja-code/article.md +++ b/1-js/03-code-quality/04-ninja-code/article.md @@ -2,6 +2,7 @@ Предлагаю вашему вниманию советы мастеров древности. +<<<<<<< HEAD Ниндзя-разработчики прошлого использовали их, чтобы усложнить код и заострить разум тех, кто будет поддерживать его после них. При найме проверяющие тщательно ищут их применение в коде соискателей. @@ -12,6 +13,10 @@ ```warn header="Осторожно, ирония!" Многие пытались пройти по пути ниндзя! Мало кто преуспел. +======= +```quote author="Confucius (Analects)" +Learning without thought is labor lost; thought without learning is perilous. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` ## Краткость – сестра таланта! @@ -35,7 +40,11 @@ i = i ? i < 0 ? Math.max(0, len + i) : i : 0; Кто знает — не говорит. Кто говорит — не знает. ``` +<<<<<<< HEAD Ещё один способ писать быстрее - использовать короткие имена переменных. Называйте их `a`, `b` или `c`. +======= +Another way to code shorter is to use single-letter variable names everywhere. Like `a`, `b` or `c`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Короткая переменная прячется в коде лучше, чем ниндзя в лесу. Никто не сможет найти её, используя функцию "Поиск" текстового редактора. Более того, даже найдя – никто не сможет "расшифровать" её и догадаться, что она означает. @@ -89,6 +98,7 @@ i = i ? i < 0 ? Math.max(0, len + i) : i : 0; ## Проверка внимания +<<<<<<< HEAD Только истинно внимательный программист достоин понять ваш код. Но как проверить, достоин ли читающий? **Один из способов – использовать похожие имена переменных, например, `date` и `data`.** @@ -111,6 +121,10 @@ i = i ? i < 0 ? Math.max(0, len + i) : i : 0; ```quote author="Конфуций" Очень трудно найти чёрную кошку в тёмной комнате, особенно, когда её там нет. +======= +```quote author="Laozi (Tao Te Ching)" +The Tao that can be told is not the eternal Tao. The name that can be named is not the eternal name. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` **Чтобы было не скучно – используйте *похожие* названия для обозначения *одинаковых* действий.** diff --git a/1-js/03-code-quality/05-testing-mocha/article.md b/1-js/03-code-quality/05-testing-mocha/article.md index 9279fcd7b3..e374b3ff2e 100644 --- a/1-js/03-code-quality/05-testing-mocha/article.md +++ b/1-js/03-code-quality/05-testing-mocha/article.md @@ -2,7 +2,11 @@ Далее у нас будут задачи, для проверки которых используется автоматическое тестирование. Также его часто применяют в реальных проектах. +<<<<<<< HEAD ## Зачем нам нужны тесты? +======= +## Why do we need tests? +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Обычно, когда мы пишем функцию, мы легко можем представить, что она должна делать, и как она будет вести себя в зависимости от переданных параметров. @@ -50,8 +54,13 @@ describe("pow", function() { Спецификация состоит из трёх основных блоков: +<<<<<<< HEAD `describe("заголовок", function() { ... })` : Какую функциональность мы описываем. В нашем случае мы описываем функцию `pow`. Используется для группировки рабочих лошадок -- блоков `it`. +======= +`describe("title", function() { ... })` +: What functionality we're describing? In our case we're describing the function `pow`. Used to group "workers" -- the `it` blocks. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 `it("описание", function() { ... })` : В первом аргументе блока `it` мы *человеческим языком* описываем конкретный способ использования функции, а во втором -- пишем функцию, которая тестирует данный случай. @@ -67,6 +76,7 @@ describe("pow", function() { Процесс разработки обычно выглядит следующим образом: +<<<<<<< HEAD 1. Пишется начальная спецификация с тестами, проверяющими основную функциональность. 2. Создаётся начальная реализация. 3. Для запуска тестов мы используем фреймворк [Mocha](https://mochajs.org/) (подробнее о нём чуть позже). Пока функция не готова, будут ошибки. Вносим изменения до тех пор, пока всё не начнёт работать так, как нам нужно. @@ -74,20 +84,39 @@ describe("pow", function() { 5. Мы добавляем новые способы использования в спецификацию, возможно, ещё не реализованные в тестируемом коде. Тесты начинают "падать" (выдавать ошибки). 6. Возвращаемся на шаг 3, дописываем реализацию до тех пор, пока тесты не начнут завершаться без ошибок. 7. Повторяем шаги 3-6, пока требуемая функциональность не будет готова. +======= +1. An initial spec is written, with tests for the most basic functionality. +2. An initial implementation is created. +3. To check whether it works, we run the testing framework [Mocha](https://mochajs.org/) (more details soon) that runs the spec. While the functionality is not complete, errors are displayed. We make corrections until everything works. +4. Now we have a working initial implementation with tests. +5. We add more use cases to the spec, probably not yet supported by the implementations. Tests start to fail. +6. Go to 3, update the implementation till tests give no errors. +7. Repeat steps 3-6 till the functionality is ready. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Таким образом, разработка проходит *итеративно*. Мы пишем спецификацию, реализуем её, проверяем, что тесты выполняются без ошибок, пишем ещё тесты, снова проверяем, что они проходят и т.д. Давайте посмотрим этот поток разработки на нашем примере. +<<<<<<< HEAD Первый шаг уже завершён. У нас есть спецификация для функции `pow`. Теперь, перед тем, как писать реализацию, давайте подключим библиотеки для пробного запуска тестов, просто чтобы убедиться, что тесты работают (разумеется, они завершатся ошибками). +======= +The first step is already complete: we have an initial spec for `pow`. Now, before making the implementation, let's use a few JavaScript libraries to run the tests, just to see that they are working (they will all fail). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## Спецификация в действии В этой главе мы будем пользоваться следующими JavaScript-библиотеками для тестов: +<<<<<<< HEAD - [Mocha](https://mochajs.org/) -- основной фреймворк. Он предоставляет общие функции тестирования, такие как `describe` и `it`, а также функцию запуска тестов. - [Chai](https://chaijs.com) -- библиотека, предоставляющая множество функций проверки утверждений. Пока мы будем использовать только `assert.equal`. - [Sinon](https://sinonjs.org/) -- библиотека, позволяющая наблюдать за функциями, эмулировать встроенные функции и многое другое. Нам она пригодится позднее. +======= +- [Mocha](https://mochajs.org/) -- the core framework: it provides common testing functions including `describe` and `it` and the main function that runs tests. +- [Chai](https://www.chaijs.com/) -- the library with many assertions. It allows to use a lot of different assertions, for now we need only `assert.equal`. +- [Sinon](https://sinonjs.org/) -- a library to spy over functions, emulate built-in functions and more, we'll need it much later. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Эти библиотеки подходят как для тестирования внутри браузера, так и на стороне сервера. Мы рассмотрим вариант с браузером. @@ -160,8 +189,13 @@ function pow(x, n) { assert.equal(pow(2, 3), 8); }); +<<<<<<< HEAD it("3 в степени 3 будет 27", function() { assert.equal(pow(3, 3), 27); +======= + it("3 raised to power 4 is 81", function() { + assert.equal(pow(3, 4), 81); +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 }); }); @@ -183,7 +217,11 @@ function pow(x, n) { [iframe height=250 src="pow-2" edit border="1"] +<<<<<<< HEAD Как мы и ожидали, второй тест провалился. Естественно, наша функция всегда возвращает `8`, в то время как `assert` ожидает `27`. +======= +As we could expect, the second test failed. Sure, our function always returns `8`, while the `assert` expects `81`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## Улучшаем реализацию @@ -339,6 +377,7 @@ describe("pow", function() { ```smart header="Другие функции сравнения" Обратите внимание на `assert.isNaN`. Это проверка того, что переданное значение равно `NaN`. +<<<<<<< HEAD Библиотека [Chai](https://chaijs.com) содержит множество других подобных функций, например: - `assert.equal(value1, value2)` -- проверяет равенство `value1 == value2`. @@ -347,6 +386,16 @@ describe("pow", function() { - `assert.isTrue(value)` -- проверяет, что `value === true` - `assert.isFalse(value)` -- проверяет, что `value === false` - ...с полным списком можно ознакомиться в [документации](https://chaijs.com/api/assert/) +======= +There are other assertions in [Chai](https://www.chaijs.com/) as well, for instance: + +- `assert.equal(value1, value2)` -- checks the equality `value1 == value2`. +- `assert.strictEqual(value1, value2)` -- checks the strict equality `value1 === value2`. +- `assert.notEqual`, `assert.notStrictEqual` -- inverse checks to the ones above. +- `assert.isTrue(value)` -- checks that `value === true` +- `assert.isFalse(value)` -- checks that `value === false` +- ...the full list is in the [docs](https://www.chaijs.com/api/assert/) +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` Итак, нам нужно добавить пару строчек в функцию `pow`: 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 2116ec3b01..6a30317a96 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,7 +1,20 @@ +<<<<<<< HEAD describe("тест", function () { before(() => alert("Тестирование началось – перед тестами")); after(() => alert("Тестирование закончилось – после всех тестов")); +======= +describe("test", function() { + + // Mocha usually waits for the tests for 2 seconds before considering them wrong + + this.timeout(200000); // With this code we increase this - in this case to 200,000 milliseconds + + // This is because of the "alert" function, because if you delay pressing the "OK" button the tests will not pass! + + before(() => alert("Testing started – before all tests")); + after(() => alert("Testing finished – after all tests")); +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 beforeEach(() => alert("Перед тестом – начинаем выполнять тест")); afterEach(() => alert("После теста – заканчиваем выполнение теста")); 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 6df4aabed4..86650ad05e 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 @@ -4,8 +4,13 @@ describe("pow", function() { assert.equal(pow(2, 3), 8); }); +<<<<<<< HEAD it("3 в степени 3 будет 27", function () { assert.equal(pow(3, 3), 27); +======= + it("3 raised to power 4 is 81", function() { + assert.equal(pow(3, 4), 81); +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 }); }); diff --git a/1-js/03-code-quality/06-polyfills/article.md b/1-js/03-code-quality/06-polyfills/article.md index 870054631c..f68538b64c 100644 --- a/1-js/03-code-quality/06-polyfills/article.md +++ b/1-js/03-code-quality/06-polyfills/article.md @@ -1,16 +1,29 @@ +<<<<<<< HEAD # Полифилы JavaScript - динамично развивающийся язык программирования. Регулярно появляются предложения о добавлении в JS новых возможностей, они анализируются, и, если предложения одобряются, то описания новых возможностей языка переносятся в черновик , а затем публикуются в [спецификации](https://www.ecma-international.org/publications/standards/Ecma-262.htm). +======= +# Polyfills and transpilers + +The JavaScript language steadily evolves. New proposals to the language appear regularly, they are analyzed and, if considered worthy, are appended to the list at and then progress to the [specification](https://www.ecma-international.org/publications-and-standards/standards/ecma-262/). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Разработчики JavaScript-движков сами решают, какие предложения реализовывать в первую очередь. Они могут заранее добавить в браузеры поддержку функций, которые всё ещё находятся в черновике, и отложить разработку функций, которые уже перенесены в спецификацию, потому что они менее интересны разработчикам или более сложные в реализации. +<<<<<<< HEAD Таким образом, довольно часто реализуется только часть стандарта. Можно проверить текущее состояние поддержки различных возможностей JavaScript на странице (нам ещё предстоит изучить многое из этого списка). +======= +So it's quite common for an engine to implement only part of the standard. + +A good page to see the current state of support for language features is (it's big, we have a lot to study yet). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 -## Babel +As programmers, we'd like to use most recent features. The more good stuff - the better! +<<<<<<< HEAD Когда мы используем современные возможности JavaScript, некоторые движки могут не поддерживать их. Как было сказано выше, не везде реализованы все функции. И тут приходит на помощь Babel. @@ -50,3 +63,81 @@ alert('Нажмите кнопку "Play" в крайнем правом угл ``` Google Chrome обычно поддерживает современные функции, можно запускать новейшие примеры без каких-либо транспилеров, но и другие современные браузеры тоже хорошо работают. +======= +On the other hand, how to make our modern code work on older engines that don't understand recent features yet? + +There are two tools for that: + +1. Transpilers. +2. Polyfills. + +Here, in this chapter, our purpose is to get the gist of how they work, and their place in web development. + +## Transpilers + +A [transpiler](https://en.wikipedia.org/wiki/Source-to-source_compiler) is a special piece of software that translates source code to another source code. It can parse ("read and understand") modern code and rewrite it using older syntax constructs, so that it'll also work in outdated engines. + +E.g. JavaScript before year 2020 didn't have the "nullish coalescing operator" `??`. So, if a visitor uses an outdated browser, it may fail to understand the code like `height = height ?? 100`. + +A transpiler would analyze our code and rewrite `height ?? 100` into `(height !== undefined && height !== null) ? height : 100`. + +```js +// before running the transpiler +height = height ?? 100; + +// after running the transpiler +height = (height !== undefined && height !== null) ? height : 100; +``` + +Now the rewritten code is suitable for older JavaScript engines. + +Usually, a developer runs the transpiler on their own computer, and then deploys the transpiled code to the server. + +Speaking of names, [Babel](https://babeljs.io) is one of the most prominent transpilers out there. + +Modern project build systems, such as [webpack](https://webpack.js.org/), provide a means to run a transpiler automatically on every code change, so it's very easy to integrate into the development process. + +## Polyfills + +New language features may include not only syntax constructs and operators, but also built-in functions. + +For example, `Math.trunc(n)` is a function that "cuts off" the decimal part of a number, e.g `Math.trunc(1.23)` returns `1`. + +In some (very outdated) JavaScript engines, there's no `Math.trunc`, so such code will fail. + +As we're talking about new functions, not syntax changes, there's no need to transpile anything here. We just need to declare the missing function. + +A script that updates/adds new functions is called "polyfill". It "fills in" the gap and adds missing implementations. + +For this particular case, the polyfill for `Math.trunc` is a script that implements it, like this: + +```js +if (!Math.trunc) { // if no such function + // implement it + Math.trunc = function(number) { + // Math.ceil and Math.floor exist even in ancient JavaScript engines + // they are covered later in the tutorial + return number < 0 ? Math.ceil(number) : Math.floor(number); + }; +} +``` + +JavaScript is a highly dynamic language. Scripts may add/modify any function, even built-in ones. + +One interesting polyfill library is [core-js](https://github.com/zloirock/core-js), which supports a wide range of features and allows you to include only the ones you need. + +## Summary + +In this chapter we'd like to motivate you to study modern and even "bleeding-edge" language features, even if they aren't yet well-supported by JavaScript engines. + +Just don't forget to use a transpiler (if using modern syntax or operators) and polyfills (to add functions that may be missing). They'll ensure that the code works. + +For example, later when you're familiar with JavaScript, you can setup a code build system based on [webpack](https://webpack.js.org/) with the [babel-loader](https://github.com/babel/babel-loader) plugin. + +Good resources that show the current state of support for various features: +- - for pure JavaScript. +- - for browser-related functions. + +P.S. Google Chrome is usually the most up-to-date with language features, try it if a tutorial demo fails. Most tutorial demos work with any modern browser though. + +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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 c00c0d1ba7..b05e5ed85f 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,9 +2,15 @@ importance: 3 --- +<<<<<<< HEAD # Умножаем все числовые свойства на 2 Создайте функцию `multiplyNumeric(obj)`, которая умножает все числовые свойства объекта `obj` на `2`. +======= +# Multiply numeric property values by 2 + +Create a function `multiplyNumeric(obj)` that multiplies all numeric property values of `obj` by `2`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Например: diff --git a/1-js/04-object-basics/01-object/article.md b/1-js/04-object-basics/01-object/article.md index 3c5474b851..89684277d1 100644 --- a/1-js/04-object-basics/01-object/article.md +++ b/1-js/04-object-basics/01-object/article.md @@ -1,7 +1,11 @@ # Объекты +<<<<<<< HEAD Как мы знаем из главы , в JavaScript существует 8 типов данных. Семь из них называются "примитивными", так как содержат только одно значение (будь то строка, число или что-то другое). +======= +As we know from the chapter , there are eight data types in JavaScript. Seven of them are called "primitive", because their values contain only a single thing (be it a string or a number or whatever). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Объекты же используются для хранения коллекций различных значений и более сложных сущностей. В JavaScript объекты используются очень часто, это одна из основ языка. Поэтому мы должны понять их, прежде чем углубляться куда-либо ещё. @@ -44,7 +48,11 @@ let user = { // объект ![user object](object-user.svg) +<<<<<<< HEAD Мы можем в любой момент добавить в него новые папки, удалить папки или прочитать содержимое любой папки. +======= +We can add, remove and read files from it at any time. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Для обращения к свойствам используется запись "через точку": @@ -62,7 +70,11 @@ user.isAdmin = true; ![user object 2](object-user-isadmin.svg) +<<<<<<< HEAD Для удаления свойства мы можем использовать оператор `delete`: +======= +To remove a property, we can use the `delete` operator: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js delete user.age; @@ -125,7 +137,13 @@ alert(user.name); // Pete user.likes birds = true ``` +<<<<<<< HEAD JavaScript видит, что мы обращаемся к свойству `user.likes`, а затем идёт непонятное слово `birds`. В итоге синтаксическая ошибка. +======= +JavaScript doesn't understand that. It thinks that we address `user.likes`, and then gives a syntax error when comes across unexpected `birds`. + +The dot requires the key to be a valid variable identifier. That implies: contains no spaces, doesn't start with a digit and doesn't include special characters (`$` and `_` are allowed). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Точка требует, чтобы ключ был именован по правилам именования переменных. То есть не имел пробелов, не начинался с цифры и не содержал специальные символы, кроме `$` и `_`. @@ -185,7 +203,11 @@ alert( user.key ); // undefined ### Вычисляемые свойства +<<<<<<< HEAD Мы можем использовать квадратные скобки в литеральной нотации для создания *вычисляемого свойства*. +======= +We can use square brackets in an object literal, when creating an object. That's called *computed properties*. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Пример: @@ -226,22 +248,39 @@ let bag = { }; ``` +<<<<<<< HEAD Квадратные скобки дают намного больше возможностей, чем запись через точку. Они позволяют использовать любые имена свойств и переменные, хотя и требуют более громоздких конструкций кода. +======= +Square brackets are much more powerful than dot notation. They allow any property names and variables. But they are also more cumbersome to write. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Подведём итог: в большинстве случаев, когда имена свойств известны и просты, используется запись через точку. Если же нам нужно что-то более сложное, то мы используем квадратные скобки. +<<<<<<< HEAD ## Свойство из переменной В реальном коде часто нам необходимо использовать существующие переменные как значения для свойств с тем же именем. Например: +======= +## Property value shorthand + +In real code, we often use existing variables as values for property names. + +For instance: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run function makeUser(name, age) { return { name: name, +<<<<<<< HEAD age: age // ...другие свойства +======= + age: age, + // ...other properties +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 }; } @@ -249,32 +288,52 @@ let user = makeUser("John", 30); alert(user.name); // John ``` +<<<<<<< HEAD В примере выше название свойств `name` и `age` совпадают с названиями переменных, которые мы подставляем в качестве значений этих свойств. Такой подход настолько распространён, что существуют специальные *короткие свойства* для упрощения этой записи. Вместо `name:name` мы можем написать просто `name`: +======= +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. + +Instead of `name:name` we can just write `name`, like this: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js function makeUser(name, age) { *!* return { +<<<<<<< HEAD name, // то же самое, что и name: name age // то же самое, что и age: age +======= + name, // same as name: name + age, // same as age: age +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 // ... }; */!* } ``` +<<<<<<< HEAD Мы можем использовать как обычные свойства, так и короткие в одном и том же объекте: ```js let user = { name, // тоже самое, что и name:name +======= +We can use both normal properties and shorthands in the same object: + +```js +let user = { + name, // same as name:name +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 age: 30 }; ``` +<<<<<<< HEAD ## Ограничения на имена свойств Как мы уже знаем, имя переменной не может совпадать с зарезервированными словами, такими как "for", "let", "return" и т.д. @@ -283,6 +342,16 @@ let user = { ```js run // эти имена свойств допустимы +======= +## Property names limitations + +As we already know, a variable cannot have a name equal to one of the language-reserved words like "for", "let", "return" etc. + +But for an object property, there's no such restriction: + +```js run +// these properties are all right +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 let obj = { for: 1, let: 2, @@ -292,6 +361,7 @@ let obj = { alert( obj.for + obj.let + obj.return ); // 6 ``` +<<<<<<< HEAD Иными словами, нет никаких ограничений к именам свойств. Они могут быть в виде строк или символов (специальный тип для идентификаторов, который будет рассмотрен позже). Все другие типы данных будут автоматически преобразованы к строке. @@ -325,6 +395,41 @@ alert(obj.__proto__); // [object Object], значение - это объект В отличие от многих других языков, особенность JavaScript-объектов в том, что можно получить доступ к любому свойству. Даже если свойства не существует - ошибки не будет! При обращении к свойству, которого нет, возвращается `undefined`. Это позволяет просто проверить существование свойства: +======= +In short, there are no limitations on property names. They can be any strings or symbols (a special type for identifiers, to be covered later). + +Other types are automatically 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) +``` + +There's a minor gotcha with a special property named `__proto__`. We can't set it to a non-object value: + +```js run +let obj = {}; +obj.__proto__ = 5; // assign a number +alert(obj.__proto__); // [object Object] - the value is an object, didn't work as intended +``` + +As we see from the code, the assignment to a primitive `5` is ignored. + +We'll cover the special nature of `__proto__` in [subsequent chapters](info:prototype-inheritance), and suggest the [ways to fix](info:prototype-methods) such behavior. + +## Property existence test, "in" operator + +A notable feature of objects in JavaScript, compared to many other languages, is that it's possible to access any property. There will be no error if the property doesn't exist! + +Reading a non-existing property just returns `undefined`. So we can easily test whether the property exists: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let user = {}; @@ -332,7 +437,11 @@ let user = {}; alert( user.noSuchProperty === undefined ); // true означает "свойства нет" ``` +<<<<<<< HEAD Также существует специальный оператор `"in"` для проверки существования свойства в объекте. +======= +There's also a special operator `"in"` for that. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Синтаксис оператора: ```js @@ -350,16 +459,29 @@ alert( "blabla" in user ); // false, user.blabla не существует Обратите внимание, что слева от оператора `in` должно быть *имя свойства*. Обычно это строка в кавычках. +<<<<<<< HEAD Если мы опускаем кавычки, это значит, что мы указываем переменную, в которой находится имя свойства. Например: +======= +If we omit quotes, that means a variable should contain the actual name to be tested. For instance: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let user = { age: 30 }; let key = "age"; +<<<<<<< HEAD alert( *!*key*/!* in user ); // true, имя свойства было взято из переменной key ``` Для чего вообще нужен оператор `in`? Разве недостаточно сравнения с `undefined`? +======= +alert( *!*key*/!* in user ); // true, property "age" exists +``` + +Why does the `in` operator exist? Isn't it enough to compare against `undefined`? + +Well, most of the time the comparison with `undefined` works fine. But there's a special case when it fails, but `"in"` works correctly. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 В большинстве случаев прекрасно сработает сравнение с `undefined`. Но есть особый случай, когда оно не подходит и нужно использовать `"in"`. @@ -374,6 +496,7 @@ alert( obj.test ); // выведет undefined, значит свойство alert( "test" in obj ); // true, свойство существует! ``` +<<<<<<< HEAD В примере выше свойство `obj.test` технически существует в объекте. Оператор `in` сработал правильно. Подобные ситуации случаются очень редко, так как `undefined` обычно явно не присваивается. Для "неизвестных" или "пустых" свойств мы используем значение `null`. @@ -381,6 +504,14 @@ alert( "test" in obj ); // true, свойство существует! ## Цикл "for..in" [#forin] Для перебора всех свойств объекта используется цикл `for..in`. Этот цикл отличается от изученного ранее цикла `for(;;)`. +======= +In the code above, the property `obj.test` technically exists. So the `in` operator works right. + +Situations like this happen very rarely, because `undefined` should not be explicitly assigned. We mostly use `null` for "unknown" or "empty" values. So the `in` operator is an exotic guest in the code. + + +## The "for..in" loop [#forin] +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Синтаксис: @@ -411,9 +542,13 @@ for (let key in user) { Кроме того, мы могли бы использовать другое имя переменной. Например, часто используется вариант `"for (let prop in obj)"`. +<<<<<<< HEAD ### Упорядочение свойств объекта Упорядочены ли свойства объекта? Другими словами, если мы будем в цикле перебирать все свойства объекта, получим ли мы их в том же порядке, в котором мы их добавляли? Можем ли мы на это рассчитывать? +======= +### Ordered like an object +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Короткий ответ: свойства упорядочены особым образом: свойства с целочисленными ключами сортируются по возрастанию, остальные располагаются в порядке создания. Разберёмся подробнее. @@ -435,7 +570,11 @@ for (let code in codes) { */!* ``` +<<<<<<< HEAD Если мы делаем сайт для немецкой аудитории, то, вероятно, мы хотим, чтобы код `49` был первым. +======= +The object may be used to suggest a list of options to the user. If we're making a site mainly for a German audience then we probably want `49` to be the first. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Но если мы запустим код, мы увидим совершенно другую картину: @@ -447,6 +586,7 @@ for (let code in codes) { ````smart header="Целочисленные свойства? Это что?" Термин "целочисленное свойство" означает строку, которая может быть преобразована в целое число и обратно без изменений. +<<<<<<< HEAD То есть, `"49"` - это целочисленное имя свойства, потому что если его преобразовать в целое число, а затем обратно в строку, то оно не изменится. А вот свойства `"+49"` или `"1.2"` таковыми не являются: ```js run @@ -454,6 +594,16 @@ for (let code in codes) { 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" ⇒ свойство не целочисленное +======= +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: + +```js run +// Number(...) explicitly converts to a number +// 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 +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` ```` @@ -494,6 +644,7 @@ for (let code in codes) { Теперь код работает так, как мы задумывали. +<<<<<<< HEAD ## Итого Объекты - это ассоциативные массивы с рядом дополнительных возможностей. @@ -524,3 +675,34 @@ for (let code in codes) { У них есть свои особенности, которые мы изучим позже. Иногда люди говорят что-то вроде "тип данных Array" или "тип данных Date", но формально они не являются отдельными типами, а относятся к типу данных `Object`. Они лишь расширяют его различными способами. Объекты в JavaScript очень мощные. Здесь мы только немного углубились в действительно огромную тему. Мы будем плотно работать с объектами и узнаем о них больше в следующих частях учебника. +======= +## 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 taking 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. + +What we've studied in this chapter is called a "plain object", or just `Object`. + +There are many other kinds of objects in 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. + +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. + +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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/1-js/04-object-basics/02-object-copy/article.md b/1-js/04-object-basics/02-object-copy/article.md index e648d34098..fc5e8fb029 100644 --- a/1-js/04-object-basics/02-object-copy/article.md +++ b/1-js/04-object-basics/02-object-copy/article.md @@ -1,3 +1,4 @@ +<<<<<<< HEAD # Копирование объектов и ссылки @@ -25,6 +26,34 @@ let phrase = message; **Переменная, которой присвоен объект, хранит не сам объект, а его "адрес в памяти" – другими словами, "ссылку" на него.** Давайте рассмотрим пример такой переменной: +======= +# Object references and copying + +One of the fundamental differences of objects versus primitives is that objects are stored and copied "by reference", whereas primitive values: strings, numbers, booleans, etc -- are always copied "as a whole value". + +That's easy to understand if we look a bit under the hood of what happens when we copy a value. + +Let's start with a primitive, such as a string. + +Here we put a copy of `message` into `phrase`: + +```js +let message = "Hello!"; +let phrase = message; +``` + +As a result we have two independent variables, each one storing the string `"Hello!"`. + +![](variable-copy-value.svg) + +Quite an obvious result, right? + +Objects are not like that. + +**A variable assigned to an object stores not the object itself, but its "address in memory" -- in other words "a reference" to it.** + +Let's look at an example of such a variable: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js let user = { @@ -32,6 +61,7 @@ let user = { }; ``` +<<<<<<< HEAD И вот как это на самом деле хранится в памяти: ![](variable-contains-reference.svg) @@ -47,10 +77,28 @@ let user = { **При копировании переменной объекта копируется ссылка, но сам объект не дублируется.** Например: +======= +And here's how it's actually stored in memory: + +![](variable-contains-reference.svg) + +The object is stored somewhere in memory (at the right of the picture), while the `user` variable (at the left) has a "reference" to it. + +We may think of an object variable, such as `user`, like a sheet of paper with the address of the object on it. + +When we perform actions with the object, e.g. take a property `user.name`, the JavaScript engine looks at what's at that address and performs the operation on the actual object. + +Now here's why it's important. + +**When an object variable is copied, the reference is copied, but the object itself is not duplicated.** + +For instance: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js no-beautify let user = { name: "John" }; +<<<<<<< HEAD let admin = user; // копируется ссылка ``` @@ -61,6 +109,18 @@ let admin = user; // копируется ссылка Как вы можете видеть, все ещё есть один объект, но теперь с двумя переменными, которые ссылаются на него. Мы можем использовать любую переменную для доступа к объекту и изменения его содержимого: +======= +let admin = user; // copy the reference +``` + +Now we have two variables, each storing a reference to the same object: + +![](variable-copy-reference.svg) + +As you can see, there's still one object, but now with two variables that reference it. + +We can use either variable to access the object and modify its contents: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let user = { name: 'John' }; @@ -68,6 +128,7 @@ let user = { name: 'John' }; let admin = user; *!* +<<<<<<< HEAD admin.name = 'Pete'; // изменено по ссылке из переменной "admin" */!* @@ -95,10 +156,40 @@ alert( a === b ); // true ```js run let a = {}; let b = {}; // два независимых объекта +======= +admin.name = 'Pete'; // changed by the "admin" reference +*/!* + +alert(*!*user.name*/!*); // 'Pete', changes are seen from the "user" reference +``` + +It's as if we had a cabinet with two keys and used one of them (`admin`) to get into it and make changes. Then, if we later use another key (`user`), we are still opening the same cabinet and can access the changed contents. + +## Comparison by reference + +Two objects are equal only if they are the same object. + +For instance, here `a` and `b` reference the same object, thus 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 they look alike (both are empty): + +```js run +let a = {}; +let b = {}; // two independent objects +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 alert( a == b ); // false ``` +<<<<<<< HEAD Для сравнений типа `obj1 > obj2` или для сравнения с примитивом `obj == 5` объекты преобразуются в примитивы. Очень скоро мы изучим, как работают преобразования объектов, но, по правде говоря, такие сравнения требуются очень редко и обычно они появляются в результате ошибок программиста. ## Клонирование и объединение, Object.assign [#cloning-and-merging-object-assign] @@ -236,6 +327,14 @@ alert(clone.sizes.width); // 51, видим результат в другом Важным побочным эффектом хранения объектов в качестве ссылок является то, что объект, объявленный как `const`, *может* быть изменён. Например: +======= +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 needed very rarely -- usually they appear as a result of a programming mistake. + +````smart header="Const objects can be modified" +An important side effect of storing objects as references is that an object declared as `const` *can* be modified. + +For instance: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run const user = { @@ -249,6 +348,7 @@ user.name = "Pete"; // (*) alert(user.name); // Pete ``` +<<<<<<< HEAD Может показаться, что строка `(*)` вызовет ошибку, но, это не так. Значение `user` это константа, оно всегда должно ссылаться на один и тот же объект, но свойства этого объекта могут свободно изменяться. Другими словами, `const user` выдаст ошибку только в том случае, если мы попытаемся задать `user=...` в целом. @@ -263,3 +363,211 @@ alert(user.name); // Pete Все операции с использованием скопированных ссылок (например, добавление/удаление свойств) выполняются с одним и тем же объектом. Чтобы создать "реальную копию" (клон), мы можем использовать `Object.assign` для так называемой "поверхностной копии" (вложенные объекты копируются по ссылке) или функцию "глубокого клонирования", такую как [_.cloneDeep(obj)](https://lodash.com/docs#cloneDeep). +======= +It might seem that the line `(*)` would cause an error, but it does not. The value of `user` is constant, it must always reference the same object, but properties of that object are free to change. + +In other words, the `const user` gives an error only if we try to set `user=...` as a whole. + +That said, if we really need to make constant object properties, it's also possible, but using totally different methods. We'll mention that in the chapter . +```` + +## Cloning and merging, Object.assign [#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? + +We can 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 object with the same content +clone.name = "Pete"; // changed the data in it + +alert( user.name ); // still John in the original object +``` + +We can also use the method [Object.assign](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign). + +The syntax is: + +```js +Object.assign(dest, ...sources) +``` + +- The first argument `dest` is a target object. +- Further arguments is a list of source objects. + +It copies the properties of all source objects into the target `dest`, and then returns it as the result. + +For example, we have `user` object, let's add a couple of permissions to it: + +```js run +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 } +alert(user.name); // John +alert(user.canView); // true +alert(user.canEdit); // true +``` + +If the copied property name already exists, it gets overwritten: + +```js run +let user = { name: "John" }; + +Object.assign(user, { name: "Pete" }); + +alert(user.name); // now user = { name: "Pete" } +``` + +We also can use `Object.assign` to perform a simple object cloning: + +```js run +let user = { + name: "John", + age: 30 +}; + +*!* +let clone = Object.assign({}, user); +*/!* + +alert(clone.name); // John +alert(clone.age); // 30 +``` + +Here it copies all properties of `user` into the empty object and returns it. + +There are also other methods of cloning an object, e.g. using the [spread syntax](info:rest-parameters-spread) `clone = {...user}`, covered later in the tutorial. + +## Nested cloning + +Until now we assumed that all properties of `user` are primitive. But properties can be references to other objects. + +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 `user.sizes` is an object, and will be copied by reference, so `clone` and `user` will share the same sizes: + +```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 = 60; // change a property from one place +alert(clone.sizes.width); // 60, get the result from the other one +``` + +To fix that and make `user` and `clone` truly separate objects, we should use a 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" or "structured cloning". There's [structuredClone](https://developer.mozilla.org/en-US/docs/Web/API/structuredClone) method that implements deep cloning. + + +### structuredClone + +The call `structuredClone(object)` clones the `object` with all nested properties. + +Here's how we can use it in our example: + +```js run +let user = { + name: "John", + sizes: { + height: 182, + width: 50 + } +}; + +*!* +let clone = structuredClone(user); +*/!* + +alert( user.sizes === clone.sizes ); // false, different objects + +// user and clone are totally unrelated now +user.sizes.width = 60; // change a property from one place +alert(clone.sizes.width); // 50, not related +``` + +The `structuredClone` method can clone most data types, such as objects, arrays, primitive values. + +It also supports circular references, when an object property references the object itself (directly or via a chain or references). + +For instance: + +```js run +let user = {}; +// let's create a circular reference: +// user.me references the user itself +user.me = user; + +let clone = structuredClone(user); +alert(clone.me === clone); // true +``` + +As you can see, `clone.me` references the `clone`, not the `user`! So the circular reference was cloned correctly as well. + +Although, there are cases when `structuredClone` fails. + +For instance, when an object has a function property: + +```js run +// error +structuredClone({ + f: function() {} +}); +``` + +Function properties aren't supported. + +To handle such complex cases we may need to use a combination of cloning methods, write custom code or, to not reinvent the wheel, take an existing implementation, for instance [_.cloneDeep(obj)](https://lodash.com/docs#cloneDeep) from the JavaScript library [lodash](https://lodash.com). + +## Summary + +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 itself. + +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` for the so-called "shallow copy" (nested objects are copied by reference) or a "deep cloning" function `structuredClone` or use a custom cloning implementation, such as [_.cloneDeep(obj)](https://lodash.com/docs#cloneDeep). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/1-js/04-object-basics/03-garbage-collection/article.md b/1-js/04-object-basics/03-garbage-collection/article.md index 4c627d2687..5f3c2920b7 100644 --- a/1-js/04-object-basics/03-garbage-collection/article.md +++ b/1-js/04-object-basics/03-garbage-collection/article.md @@ -1,3 +1,4 @@ +<<<<<<< HEAD # Сборка мусора Управление памятью в JavaScript выполняется автоматически и незаметно. Мы создаём примитивы, объекты, функции... Всё это занимает память. @@ -33,6 +34,43 @@ ```js // в user находится ссылка на объект +======= +# 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: + + - The currently executing function, its local variables and parameters. + - Other functions on the current chain of nested calls, their local variables and parameters. + - 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 global 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 +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 let user = { name: "John" }; @@ -40,9 +78,15 @@ let user = { ![](memory-user-john.svg) +<<<<<<< HEAD Здесь стрелка обозначает ссылку на объект. Глобальная переменная `user` ссылается на объект `{name: "John"}` (мы будем называть его просто "John" для краткости). В свойстве `"name"` объекта John хранится примитив, поэтому оно нарисовано внутри объекта. Если перезаписать значение `user`, то ссылка потеряется: +======= +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: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js user = null; @@ -50,6 +94,7 @@ user = null; ![](memory-user-john-lost.svg) +<<<<<<< HEAD Теперь объект John становится недостижимым. К нему нет доступа, на него нет ссылок. Сборщик мусора удалит эти данные и освободит память. ## Две ссылки @@ -58,6 +103,16 @@ user = null; ```js // в user находится ссылка на объект +======= +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 +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 let user = { name: "John" }; @@ -69,16 +124,28 @@ let admin = user; ![](memory-user-john-admin.svg) +<<<<<<< HEAD Теперь, если мы сделаем то же самое: +======= +Now if we do the same: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js user = null; ``` +<<<<<<< HEAD ...то объект John всё ещё достижим через глобальную переменную `admin`, поэтому он находится в памяти. Если бы мы также перезаписали `admin`, то John был бы удалён. ## Взаимосвязанные объекты Теперь более сложный пример. Семья: +======= +...Then the object is still reachable via `admin` global variable, so it must stay in memory. If we overwrite `admin` too, then it can be removed. + +## Interlinked objects + +Now a more complex example. The family: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js function marry(man, woman) { @@ -98,6 +165,7 @@ let family = marry({ }); ``` +<<<<<<< HEAD Функция `marry` "женит" два объекта, давая им ссылки друг на друга, и возвращает новый объект, содержащий ссылки на два предыдущих. В результате получаем такую структуру памяти: @@ -107,6 +175,17 @@ let family = marry({ На данный момент все объекты достижимы. Теперь удалим две ссылки: +======= +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.svg) + +As of now, all objects are reachable. + +Now let's remove two references: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js delete family.father; @@ -115,6 +194,7 @@ delete family.mother.husband; ![](family-delete-refs.svg) +<<<<<<< HEAD Недостаточно удалить только одну из этих двух ссылок, потому что все объекты останутся достижимыми. Но если мы удалим обе, то увидим, что у объекта John больше нет входящих ссылок: @@ -132,11 +212,31 @@ delete family.mother.husband; Вполне возможна ситуация, при которой целый "остров" взаимосвязанных объектов может стать недостижимым и удалиться из памяти. Возьмём объект `family` из примера выше. А затем: +======= +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.svg) + +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.svg) + +## 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: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js family = null; ``` +<<<<<<< HEAD Структура в памяти теперь станет такой: ![](family-no-family.svg) @@ -210,3 +310,78 @@ family = null; Также в [блоге V8](https://v8.dev/) время от времени публикуются статьи об изменениях в управлении памятью. Разумеется, чтобы изучить сборку мусора, вам лучше подготовиться, узнав о том как устроен движок V8 внутри в целом и почитав блог [Вячеслава Егорова](https://mrale.ph), одного из инженеров, разрабатывавших V8. Я говорю про "V8", потому что он лучше всего освещается в статьях в Интернете. Для других движков многие подходы схожи, но сборка мусора отличается во многих аспектах. Глубокое понимание работы движков полезно, когда вам нужна низкоуровневая оптимизация. Было бы разумно запланировать их изучение как следующий шаг после того, как вы познакомитесь с языком. +======= +The in-memory picture becomes: + +![](family-no-family.svg) + +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 every reachable (from the roots) references are visited. +- All objects except marked ones are removed. + +For instance, let our object structure look like this: + +![](garbage-collection-1.svg) + +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.svg) + +Then we follow their references and mark referenced objects: + +![](garbage-collection-3.svg) + +...And continue to follow further references, while possible: + +![](garbage-collection-4.svg) + +Now the objects that could not be visited in the process are considered unreachable and will be removed: + +![](garbage-collection-5.svg) + +We can also imagine the process as spilling a huge bucket of paint from the roots, that flows through all references and marks all reachable objects. The unmarked ones are then removed. + +That's the concept of how garbage collection works. JavaScript engines apply many optimizations to make it run faster and not introduce any delays into the code execution. + +Some of the optimizations: + +- **Generational collection** -- objects are split into two sets: "new ones" and "old ones". In typical code, many objects have a short life span: they appear, do their job and die fast, so it makes sense to track new objects and clear the memory from them if that's the case. 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 splits the whole set of existing objects into multiple parts. And then clear these parts one after another. There are many small garbage collections instead of a total one. That requires some extra bookkeeping between them to track changes, but we get 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 exist 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 studying 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, as we've seen in the example above. + +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, more detailed information about V8's garbage collector is in the article [A tour of V8: Garbage Collection](https://jayconrod.com/posts/55/a-tour-of-v8-garbage-collection). + +The [V8 blog](https://v8.dev/) also publishes articles about changes in memory management from time to time. Naturally, to learn more about garbage collection, you'd better prepare by learning about V8 internals in general and read the blog of [Vyacheslav Egorov](https://mrale.ph) who worked as one of the V8 engineers. I'm saying: "V8", because it is best covered by articles on 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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 8856617f58..a0c211f31f 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 @@ -22,18 +22,32 @@ alert( user.ref.name ); // Error: Cannot read property 'name' of undefined Таким образом, `ref: this` фактически принимает текущее `this` функции `makeUser()`. +<<<<<<< HEAD Мы можем переписать функцию и вернуть то же самое `this` со значением `undefined`: ```js run function makeUser(){ return this; // на этот раз нет литерала объекта +======= +We can rewrite the function and return the same `this` with `undefined` value: + +```js run +function makeUser(){ + return this; // this time there's no object literal +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 } alert( makeUser().name ); // Error: Cannot read property 'name' of undefined ``` +<<<<<<< HEAD Как вы можете видеть, результат `alert( makeUser().name )` совпадает с результатом `alert( user.ref.name )` из предыдущего примера. Вот противоположный случай: +======= +As you can see the result of `alert( makeUser().name )` is the same as the result of `alert( user.ref.name )` from the previous example. + +Here's the opposite case: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run function makeUser() { 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 bde14d6cd4..905fd163df 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 @@ -16,7 +16,11 @@ describe("калькулятор", function() { prompt.restore(); }); +<<<<<<< HEAD it('read получает два значения и сохраняет их как свойства объекта', function () { +======= + it('the read get two values and saves them as object properties', function () { +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 assert.equal(calculator.a, 2); assert.equal(calculator.b, 3); }); 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 f6c93164fb..13d2209f8e 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 @@ -6,9 +6,15 @@ importance: 5 Создайте объект `calculator` (калькулятор) с тремя методами: +<<<<<<< HEAD - `read()` (читать) запрашивает два значения и сохраняет их как свойства объекта с именами `a` и `b`. - `sum()` (суммировать) возвращает сумму сохранённых значений. - `mul()` (умножить) перемножает сохранённые значения и возвращает результат. +======= +- `read()` prompts for two values and saves them as object properties with names `a` and `b` respectively. +- `sum()` returns the sum of saved values. +- `mul()` multiplies saved values and returns the result. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js let calculator = { @@ -21,4 +27,3 @@ alert( calculator.mul() ); ``` [demo] - 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 c44088c7c0..34ba003430 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 @@ -33,7 +33,11 @@ describe('Лестница', function() { assert.equal(ladder.down().up().up().up().step, 2); }); +<<<<<<< HEAD it('showStep() должен возвращать this', function() { +======= + it('showStep() should return this', function() { +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 assert.equal(ladder.showStep(), ladder); }); 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 f42c4e0eb1..af47ee2aed 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 @@ -23,7 +23,11 @@ let ladder = { } }; +<<<<<<< HEAD ladder.up().up().down().showStep().down().showStep(); // показывает 1 затем 0 +======= +ladder.up().up().down().showStep().down().showStep(); // shows 1 then 0 +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` Мы также можем записать один вызов на одной строке. Для длинных цепей вызовов это более читабельно: 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 e24f15ce83..c34b0bd5d1 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 @@ -4,7 +4,11 @@ importance: 2 # Цепь вызовов +<<<<<<< HEAD У нас есть объект `ladder` (лестница), который позволяет подниматься и спускаться: +======= +There's a `ladder` object that allows you to go up and down: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js let ladder = { @@ -21,7 +25,11 @@ let ladder = { }; ``` +<<<<<<< HEAD Теперь, если нам нужно выполнить несколько последовательных вызовов, мы можем сделать это так: +======= +Now, if we need to make several calls in sequence, we can do it like this: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js ladder.up(); @@ -32,6 +40,7 @@ ladder.down(); ladder.showStep(); // 0 ``` +<<<<<<< HEAD Измените код методов `up`, `down` и `showStep` таким образом, чтобы их вызов можно было сделать по цепочке, например так: ```js @@ -39,3 +48,12 @@ ladder.up().up().down().showStep().down().showStep(); // показывает 1 ``` Такой подход широко используется в библиотеках JavaScript. +======= +Modify the code of `up`, `down`, and `showStep` to make the calls chainable, like this: + +```js +ladder.up().up().down().showStep().down().showStep(); // shows 1 then 0 +``` + +Such an approach is widely used across JavaScript libraries. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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 2596e90361..09ab4abf68 100644 --- a/1-js/04-object-basics/04-object-methods/article.md +++ b/1-js/04-object-basics/04-object-methods/article.md @@ -33,11 +33,19 @@ user.sayHi = function() { user.sayHi(); // Привет! ``` +<<<<<<< HEAD Здесь мы просто использовали Function Expression (функциональное выражение), чтобы создать функцию приветствия, и присвоили её свойству `user.sayHi` нашего объекта. Затем мы можем вызвать ee как `user.sayHi()`. Теперь пользователь может говорить! Функцию, которая является свойством объекта, называют *методом* этого объекта. +======= +Here we've just used a Function Expression to create a function and assign it to the property `user.sayHi` of the object. + +Then we can call it as `user.sayHi()`. The user can now speak! + +A function that is a property of an object is called its *method*. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Итак, мы получили метод `sayHi` объекта `user`. @@ -51,7 +59,11 @@ let user = { *!* // сначала, объявляем function sayHi() { +<<<<<<< HEAD alert("Привет!"); +======= + alert("Hello!"); +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 } // затем добавляем в качестве метода @@ -64,7 +76,11 @@ user.sayHi(); // Привет! ```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) или сокращённо: "ООП". +<<<<<<< HEAD ООП является большой предметной областью и интересной наукой самой по себе. Как выбрать правильные сущности? Как организовать взаимодействие между ними? Это -- создание архитектуры, и на эту тему есть отличные книги, такие как "Приёмы объектно-ориентированного проектирования. Паттерны проектирования" авторов Эрих Гамма, Ричард Хелм, Ральф Джонсон, Джон Влиссидес или "Объектно-ориентированный анализ и проектирование с примерами приложений" Гради Буча, а также ещё множество других книг. +======= +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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` ### Сокращённая запись метода @@ -82,7 +98,11 @@ user = { // сокращённая запись выглядит лучше, не так ли? user = { *!* +<<<<<<< HEAD sayHi() { // то же самое, что и "sayHi: function(){...}" +======= + sayHi() { // same as "sayHi: function(){...}" +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 */!* alert("Привет"); } @@ -91,7 +111,11 @@ user = { Как было показано, мы можем пропустить ключевое слово `"function"` и просто написать `sayHi()`. +<<<<<<< HEAD Нужно отметить, что эти две записи не полностью эквивалентны. Есть тонкие различия, связанные с наследованием объектов (что будет рассмотрено позже), но на данном этапе изучения это неважно. Почти во всех случаях сокращённый синтаксис предпочтителен. +======= +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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## Ключевое слово "this" в методах @@ -162,7 +186,11 @@ let admin = user; user = null; // перезапишем переменную для наглядности, теперь она не хранит ссылку на объект. *!* +<<<<<<< HEAD admin.sayHi(); // TypeError: Cannot read property 'name' of null +======= +admin.sayHi(); // TypeError: Cannot read property 'name' of null +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 */!* ``` @@ -170,7 +198,11 @@ admin.sayHi(); // TypeError: Cannot read property 'name' of null ## "this" не является фиксированным +<<<<<<< HEAD В JavaScript ключевое слово "this" ведёт себя иначе, чем в большинстве других языков программирования. Его можно использовать в любой функции, даже если это не метод объекта. +======= +In JavaScript, keyword `this` behaves unlike most other programming languages. It can be used in any function, even if it's not a method of an object. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 В следующем примере нет синтаксической ошибки: @@ -236,11 +268,19 @@ sayHi(); // undefined Здесь наша позиция заключается не в том, чтобы судить, является ли это архитектурное решение в языке хорошим или плохим. Скоро мы поймем, как с этим работать, как получить выгоду и избежать проблем. ``` +<<<<<<< HEAD ## У стрелочных функций нет "this" Стрелочные функции особенные: у них нет своего "собственного" `this`. Если мы ссылаемся на `this` внутри такой функции, то оно берётся из внешней "нормальной" функции. Например, здесь `arrow()` использует значение `this` из внешнего метода `user.sayHi()`: +======= +## Arrow functions have no "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. + +For instance, here `arrow()` uses `this` from the outer `user.sayHi()` method: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let user = { 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 33b9cf13d6..71cf65a949 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,7 +4,11 @@ importance: 2 # Две функции - один объект +<<<<<<< HEAD Возможно ли создать функции `A` и `B`, чтобы `new A() == new B()`? +======= +Is it possible to create functions `A` and `B` so that `new A() == new B()`? +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js no-beautify function A() { ... } 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 cdc406e6f4..81529fd3ba 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 @@ -6,9 +6,15 @@ importance: 5 Создайте функцию-конструктор `Calculator`, которая создаёт объекты с тремя методами: +<<<<<<< HEAD - `read()` запрашивает два значения при помощи `prompt` и сохраняет их значение в свойствах объекта. - `sum()` возвращает сумму этих свойств. - `mul()` возвращает произведение этих свойств. +======= +- `read()` prompts for two values and saves them as object properties with names `a` and `b` respectively. +- `sum()` returns the sum of these properties. +- `mul()` returns the multiplication product of these properties. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Например: 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 a7a785ce90..388d6e4ba9 100644 --- a/1-js/04-object-basics/06-constructor-new/article.md +++ b/1-js/04-object-basics/06-constructor-new/article.md @@ -1,6 +1,10 @@ # Конструктор, оператор "new" +<<<<<<< HEAD Обычный синтаксис `{...}` позволяет создать только один объект. Но зачастую нам нужно создать множество похожих, однотипных объектов, таких как пользователи, элементы меню и так далее. +======= +The regular `{...}` syntax allows us to create one object. But often we need to create many similar objects, like multiple users or menu items and so on. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Это можно сделать при помощи функции-конструктора и оператора `"new"`. @@ -64,6 +68,7 @@ let user = { Это и является основной целью конструкторов - реализовать код для многократного создания однотипных объектов. +<<<<<<< HEAD Давайте ещё раз отметим - технически любая функция (кроме стрелочных функций, поскольку у них нет `this`) может использоваться в качестве конструктора. Его можно запустить с помощью `new`, и он выполнит выше указанный алгоритм. Подобные функции должны начинаться с заглавной буквы - это общепринятое соглашение, чтобы было ясно, что функция должна вызываться с помощью "new". ````smart header="new function() { ... }" @@ -71,6 +76,15 @@ let user = { ```js // создаём функцию и сразу же вызываем её с помощью new +======= +Let's note once again -- technically, any function (except arrow functions, as they don't have `this`) can be used as a constructor. It can be run with `new`, and it will execute the algorithm above. The "capital letter first" is a common agreement, to make it clear that a function is to be run with `new`. + +````smart header="new function() { ... }" +If we have many lines of code all about creation of a single complex object, we can wrap them in an immediately called constructor function, like this: + +```js +// create a function and immediately call it with new +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 let user = new function() { this.name = "John"; this.isAdmin = false; @@ -81,7 +95,11 @@ let user = new function() { }; ``` +<<<<<<< HEAD Такой конструктор не может быть вызван снова, так как он нигде не сохраняется, просто создаётся и тут же вызывается. Таким образом, этот трюк направлен на инкапсуляцию кода, который создаёт отдельный объект, без возможности повторного использования в будущем. +======= +This constructor can't be called again, because it is not saved anywhere, just created and called. So this trick aims to encapsulate the code that constructs the single object, without future reuse. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```` ## Проверка на вызов в режиме конструктора: new.target @@ -92,7 +110,11 @@ let user = new function() { Используя специальное свойство `new.target` внутри функции, мы можем проверить, вызвана ли функция при помощи оператора `new` или без него. +<<<<<<< HEAD В случае обычного вызова функции `new.target` будет `undefined`. Если же она была вызвана при помощи `new`, `new.target` будет равен самой функции. +======= +It is undefined for regular calls and equals the function if called with `new`: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run function User() { @@ -170,8 +192,13 @@ alert( new SmallUser().name ); // John Обычно у конструкторов отсутствует `return`. Здесь мы упомянули особое поведение с возвращаемыми объектами в основном для полноты картины. +<<<<<<< HEAD ````smart header="Пропуск скобок" Кстати, мы можем не ставить круглые скобки после `new`: +======= +````smart header="Omitting parentheses" +By the way, we can omit parentheses after `new`: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js let user = new User; // <-- без скобок diff --git a/1-js/04-object-basics/07-optional-chaining/article.md b/1-js/04-object-basics/07-optional-chaining/article.md index 53005e7356..4d1a53b7f4 100644 --- a/1-js/04-object-basics/07-optional-chaining/article.md +++ b/1-js/04-object-basics/07-optional-chaining/article.md @@ -1,4 +1,5 @@ +<<<<<<< HEAD # Опциональная цепочка '?.' [recent browser="new"] @@ -37,6 +38,46 @@ let html = document.querySelector('.elem').innerHTML; // ошибка, если Как мы можем это сделать? Очевидным решением было бы проверить значение с помощью `if` или условного оператора `?`, прежде чем обращаться к его свойству, вот так: +======= +# Optional chaining '?.' + +[recent browser="new"] + +The optional chaining `?.` is a safe way to access nested object properties, even if an intermediate property doesn't exist. + +## The "non-existing property" problem + +If you've just started to read the tutorial and learn JavaScript, maybe the problem hasn't touched you yet, but it's quite common. + +As an example, let's say we have `user` objects that hold the information about our users. + +Most of our users have addresses in `user.address` property, with the street `user.address.street`, but some did not provide them. + +In such case, when we attempt to get `user.address.street`, and the user happens to be without an address, we get an error: + +```js run +let user = {}; // a user without "address" property + +alert(user.address.street); // Error! +``` + +That's the expected result. JavaScript works like this. As `user.address` is `undefined`, an attempt to get `user.address.street` fails with an error. + +In many practical cases we'd prefer to get `undefined` instead of an error here (meaning "no street"). + +...and another example. In Web development, we can get an object that corresponds to a web page element using a special method call, such as `document.querySelector('.elem')`, and it returns `null` when there's no such element. + +```js run +// document.querySelector('.elem') is null if there's no element +let html = document.querySelector('.elem').innerHTML; // error if it's null +``` + +Once again, if the element doesn't exist, we'll get an error accessing `.innerHTML` property of `null`. And in some cases, when the absence of the element is normal, we'd like to avoid the error and just accept `html = null` as the result. + +How can we do this? + +The obvious solution would be to check the value using `if` or the conditional operator `?`, before accessing its property, like this: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js let user = {}; @@ -44,14 +85,21 @@ let user = {}; alert(user.address ? user.address.street : undefined); ``` +<<<<<<< HEAD Это работает, тут нет ошибки... Но это довольно неэлегантно. Как вы можете видеть, `"user.address"` появляется в коде дважды. Вот как то же самое выглядело бы для `document.querySelector`: +======= +It works, there's no error... But it's quite inelegant. As you can see, the `"user.address"` appears twice in the code. + +Here's how the same would look for `document.querySelector`: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let html = document.querySelector('.elem') ? document.querySelector('.elem').innerHTML : null; ``` +<<<<<<< HEAD Как видно, поиск элемента `document.querySelector('.elem')` здесь вызывается дважды, что не очень хорошо. Для более глубоко вложенных свойств это ещё менее красиво, поскольку потребуется больше повторений. @@ -62,10 +110,21 @@ let html = document.querySelector('.elem') ? document.querySelector('.elem').inn ```js let user = {}; // у пользователя нет адреса +======= +We can see that the element search `document.querySelector('.elem')` is actually called twice here. Not good. + +For more deeply nested properties, it becomes even uglier, as more repetitions are required. + +E.g. let's get `user.address.street.name` in a similar fashion. + +```js +let user = {}; // user has no address +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 alert(user.address ? user.address.street ? user.address.street.name : null : null); ``` +<<<<<<< HEAD Это просто ужасно, у кого-то могут даже возникнуть проблемы с пониманием такого кода. Есть немного лучший способ написать это, используя оператор `&&`: @@ -109,6 +168,51 @@ let html = document.querySelector('.elem')?.innerHTML; // будет undefined, ``` Считывание адреса с помощью `user?.address` работает, даже если объект `user` не существует: +======= +That's just awful, one may even have problems understanding such code. + +There's a little better way to write it, using the `&&` operator: + +```js run +let user = {}; // user has no address + +alert( user.address && user.address.street && user.address.street.name ); // undefined (no error) +``` + +AND'ing the whole path to the property ensures that all components exist (if not, the evaluation stops), but also isn't ideal. + +As you can see, property names are still duplicated in the code. E.g. in the code above, `user.address` appears three times. + +That's why the optional chaining `?.` was added to the language. To solve this problem once and for all! + +## Optional chaining + +The optional chaining `?.` stops the evaluation if the value before `?.` is `undefined` or `null` and returns `undefined`. + +**Further in this article, for brevity, we'll be saying that something "exists" if it's not `null` and not `undefined`.** + +In other words, `value?.prop`: +- works as `value.prop`, if `value` exists, +- otherwise (when `value` is `undefined/null`) it returns `undefined`. + +Here's the safe way to access `user.address.street` using `?.`: + +```js run +let user = {}; // user has no address + +alert( user?.address?.street ); // undefined (no error) +``` + +The code is short and clean, there's no duplication at all. + +Here's an example with `document.querySelector`: + +```js run +let html = document.querySelector('.elem')?.innerHTML; // will be undefined, if there's no element +``` + +Reading the address with `user?.address` works even if `user` object doesn't exist: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let user = null; @@ -117,6 +221,7 @@ alert( user?.address ); // undefined alert( user?.address.street ); // undefined ``` +<<<<<<< HEAD Обратите внимание: синтаксис `?.` делает необязательным значение перед ним, но не какое-либо последующее. Так например, в записи `user?.address.street.name` `?.` позволяет `user` безопасно быть `null/undefined` (и в этом случае возвращает `undefined`), но это так только для `user`. Доступ к последующим свойствам осуществляется обычным способом. Если мы хотим, чтобы некоторые из них были необязательными, тогда нам нужно будет заменить больше `.` на `?.`. @@ -131,11 +236,28 @@ alert( user?.address.street ); // undefined ````warn header="Переменная перед `?.` должна быть объявлена" Если переменной `user` вообще нет, то `user?.anything` приведёт к ошибке: +======= +Please note: the `?.` syntax makes optional the value before it, but not any further. + +E.g. in `user?.address.street.name` the `?.` allows `user` to safely be `null/undefined` (and returns `undefined` in that case), but that's only for `user`. Further properties are accessed in a regular way. If we want some of them to be optional, then we'll need to replace more `.` with `?.`. + +```warn header="Don't overuse the optional chaining" +We should use `?.` only where it's ok that something doesn't exist. + +For example, if according to our code logic `user` object must exist, but `address` is optional, then we should write `user.address?.street`, but not `user?.address?.street`. + +Then, if `user` happens to be undefined, we'll see a programming error about it and fix it. Otherwise, if we overuse `?.`, coding errors can be silenced where not appropriate, and become more difficult to debug. +``` + +````warn header="The variable before `?.` must be declared" +If there's no variable `user` at all, then `user?.anything` triggers an error: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run // ReferenceError: user is not defined user?.address; ``` +<<<<<<< HEAD Переменная должна быть объявлена (к примеру, как `let/const/var user` или как параметр функции). Опциональная цепочка работает только с объявленными переменными. ```` @@ -146,11 +268,24 @@ user?.address; Так что если после `?.` есть какие-то вызовы функций или операции, то они не произойдут. Например: +======= +The variable must be declared (e.g. `let/const/var user` or as a function parameter). The optional chaining works only for declared variables. +```` + +## Short-circuiting + +As it was said before, the `?.` immediately stops ("short-circuits") the evaluation if the left part doesn't exist. + +So, if there are any further function calls or operations to the right of `?.`, they won't be made. + +For instance: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let user = null; let x = 0; +<<<<<<< HEAD user?.sayHi(x++); // нет "user", поэтому выполнение не достигает вызова sayHi и x++ alert(x); // 0, значение не увеличилось @@ -163,17 +298,36 @@ alert(x); // 0, значение не увеличилось Например, `?.()` используется для вызова функции, которая может не существовать. В приведённом ниже коде у некоторых наших пользователей есть метод `admin`, а у некоторых его нет: +======= +user?.sayHi(x++); // no "user", so the execution doesn't reach sayHi call and x++ + +alert(x); // 0, value not incremented +``` + +## Other variants: ?.(), ?.[] + +The optional chaining `?.` is not an operator, but a special syntax construct, that also works with functions and square brackets. + +For example, `?.()` is used to call a function that may not exist. + +In the code below, some of our users have `admin` method, and some don't: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let userAdmin = { admin() { +<<<<<<< HEAD alert("Я админ"); +======= + alert("I am admin"); +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 } }; let userGuest = {}; *!* +<<<<<<< HEAD userAdmin.admin?.(); // Я админ */!* @@ -187,6 +341,21 @@ userGuest.admin?.(); // ничего не произойдет (такого м Затем `?.()` проверяет левую часть: если функция `admin` существует, то она запускается (это так для `userAdmin`). В противном случае (для `userGuest`) вычисление остановится без ошибок. Синтаксис `?.[]` также работает, если мы хотим использовать скобки `[]` для доступа к свойствам вместо точки `.`. Как и в предыдущих случаях, он позволяет безопасно считывать свойство из объекта, который может не существовать. +======= +userAdmin.admin?.(); // I am admin +*/!* + +*!* +userGuest.admin?.(); // nothing happens (no such method) +*/!* +``` + +Here, in both lines we first use the dot (`userAdmin.admin`) to get `admin` property, because we assume that the `user` object exists, so it's safe read from it. + +Then `?.()` checks the left part: if the `admin` function exists, then it runs (that's so for `userAdmin`). Otherwise (for `userGuest`) the evaluation stops without errors. + +The `?.[]` syntax also works, if we'd like to use brackets `[]` to access properties instead of dot `.`. Similar to previous cases, it allows to safely read a property from an object that may not exist. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let key = "firstName"; @@ -201,6 +370,7 @@ alert( user1?.[key] ); // John alert( user2?.[key] ); // undefined ``` +<<<<<<< HEAD Также мы можем использовать `?.` с `delete`: ```js run @@ -216,10 +386,28 @@ let user = null; user?.name = "John"; // Ошибка, не работает // то же самое что написать undefined = "John" +======= +Also we can use `?.` with `delete`: + +```js run +delete user?.name; // delete user.name if user exists +``` + +````warn header="We can use `?.` for safe reading and deleting, but not writing" +The optional chaining `?.` has no use on the left side of an assignment. + +For example: +```js run +let user = null; + +user?.name = "John"; // Error, doesn't work +// because it evaluates to: undefined = "John" +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` ```` +<<<<<<< HEAD ## Итого Синтаксис опциональной цепочки `?.` имеет три формы: @@ -233,3 +421,18 @@ user?.name = "John"; // Ошибка, не работает Цепочка `?.` позволяет безопасно получать доступ к вложенным свойствам. Тем не менее, мы должны использовать `?.` осторожно, только там, где по логике кода допустимо, что левая часть не существует. Чтобы он не скрывал от нас ошибки программирования, если они возникнут. +======= +## Summary + +The optional chaining `?.` syntax has three forms: + +1. `obj?.prop` -- returns `obj.prop` if `obj` exists, otherwise `undefined`. +2. `obj?.[prop]` -- returns `obj[prop]` if `obj` exists, otherwise `undefined`. +3. `obj.method?.()` -- calls `obj.method()` if `obj.method` exists, otherwise returns `undefined`. + +As we can see, all of them are straightforward and simple to use. The `?.` checks the left part for `null/undefined` and allows the evaluation to proceed if it's not so. + +A chain of `?.` allows to safely access nested properties. + +Still, we should apply `?.` carefully, only where it's acceptable, according to our code logic, that the left part doesn't exist. So that it won't hide programming errors from us, if they occur. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/1-js/04-object-basics/08-symbol/article.md b/1-js/04-object-basics/08-symbol/article.md index 6edf9869ce..e6d4d5eb85 100644 --- a/1-js/04-object-basics/08-symbol/article.md +++ b/1-js/04-object-basics/08-symbol/article.md @@ -1,4 +1,5 @@ +<<<<<<< HEAD # Тип данных Symbol По спецификации, в качестве ключей для свойств объекта могут использоваться только строки или символы. Ни числа, ни логические значения не подходят, разрешены только эти два типа данных. @@ -26,6 +27,41 @@ let id = Symbol("id"); Символы гарантированно уникальны. Даже если мы создадим множество символов с одинаковым описанием, это всё равно будут разные символы. Описание -- это просто метка, которая ни на что не влияет. Например, вот два символа с одинаковым описанием -- но они не равны: +======= +# Symbol type + +By specification, only two primitive types may serve as object property keys: + +- string type, or +- symbol type. + +Otherwise, if one uses another type, such as number, it's autoconverted to string. So that `obj[1]` is the same as `obj["1"]`, and `obj[true]` is the same as `obj["true"]`. + +Until now we've been using only strings. + +Now let's explore symbols, see what they can do for us. + +## Symbols + +A "symbol" represents a unique identifier. + +A value of this type can be created using `Symbol()`: + +```js +let id = Symbol(); +``` + +Upon creation, we can give symbols a description (also called a symbol name), mostly useful for debugging purposes: + +```js +// 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 exactly 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: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let id1 = Symbol("id"); @@ -36,12 +72,23 @@ alert(id1 == id2); // false */!* ``` +<<<<<<< HEAD Если вы знаете Ruby или какой-то другой язык программирования, в котором есть своего рода "символы" -- пожалуйста, будьте внимательны. Символы в JavaScript имеют свои особенности, и не стоит думать о них, как о символах в Ruby или в других языках. ````warn header="Символы не преобразуются автоматически в строки" Большинство типов данных в JavaScript могут быть неявно преобразованы в строку. Например, функция `alert` принимает практически любое значение, автоматически преобразовывает его в строку, а затем выводит это значение, не сообщая об ошибке. Символы же особенные и не преобразуются автоматически. К примеру, `alert` ниже выдаст ошибку: +======= +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. + +So, to summarize, a symbol is a "primitive unique value" with an optional description. Let's see where we can use them. + +````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: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let id = Symbol("id"); @@ -50,18 +97,32 @@ alert(id); // TypeError: Cannot convert a Symbol value to a string */!* ``` +<<<<<<< HEAD Это -- языковая "защита" от путаницы, ведь строки и символы -- принципиально разные типы данных и не должны неконтролируемо преобразовываться друг в друга. Если же мы действительно хотим вывести символ с помощью `alert`, то необходимо явно преобразовать его с помощью метода `.toString()`, вот так: +======= +That's a "language guard" against messing up, because strings and symbols are fundamentally different and should not accidentally convert one into another. + +If we really want to show a symbol, we need to explicitly call `.toString()` on it, like here: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let id = Symbol("id"); *!* +<<<<<<< HEAD alert(id.toString()); // Symbol(id), теперь работает */!* ``` Или мы можем обратиться к свойству `symbol.description`, чтобы вывести только описание: +======= +alert(id.toString()); // Symbol(id), now it works +*/!* +``` + +Or get `symbol.description` property to show the description only: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let id = Symbol("id"); @@ -72,6 +133,7 @@ alert(id.description); // id ```` +<<<<<<< HEAD ## "Скрытые" свойства Символы позволяют создавать "скрытые" свойства объектов, к которым нельзя нечаянно обратиться и перезаписать их из других частей программы. @@ -83,12 +145,27 @@ alert(id.description); // id ```js run let user = { name: "Вася" +======= +## "Hidden" properties + + +Symbols allow us to create "hidden" properties of an object, that no other part of code can accidentally access or overwrite. + +For instance, if we're working with `user` objects, that belong to a third-party code. We'd like to add identifiers to them. + +Let's use a symbol key for it: + +```js run +let user = { // belongs to another code + name: "John" +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 }; let id = Symbol("id"); user[id] = 1; +<<<<<<< HEAD alert( user[id] ); // мы можем получить доступ к данным по ключу-символу ``` @@ -99,11 +176,24 @@ alert( user[id] ); // мы можем получить доступ к данн Кроме того, предположим, что другой скрипт для каких-то своих целей хочет записать собственный идентификатор в объект `user`. Этот скрипт может быть какой-то JavaScript-библиотекой, абсолютно не связанной с нашим скриптом. Сторонний код может создать для этого свой символ `Symbol("id")`: +======= +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"`? + +As `user` objects belong to another codebase, it's unsafe to add fields to them, since we might affect pre-defined behavior in that other codebase. However, symbols cannot be accessed accidentally. The third-party code won't be aware of newly defined symbols, so it's safe to add symbols to the `user` objects. + +Also, imagine that another script wants to have its own identifier inside `user`, for its own purposes. + +Then that script can create its own `Symbol("id")`, like this: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js // ... let id = Symbol("id"); +<<<<<<< HEAD user[id] = "Их идентификатор"; ``` @@ -128,11 +218,38 @@ user.id = "Их идентификатор" Если мы хотим использовать символ при литеральном объявлении объекта `{...}`, его необходимо заключить в квадратные скобки. Вот так: +======= +user[id] = "Their id value"; +``` + +There will be no conflict between our and their identifiers, because symbols are always different, even if they have the same name. + +...But if we used a string `"id"` instead of a symbol for the same purpose, then there *would* be a conflict: + +```js +let user = { name: "John" }; + +// Our script uses "id" property +user.id = "Our id value"; + +// ...Another script also wants "id" for its purposes... + +user.id = "Their id value" +// Boom! overwritten by another script! +``` + +### Symbols in an object literal + +If we want to use a symbol in an object literal `{...}`, we need square brackets around it. + +Like this: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js let id = Symbol("id"); let user = { +<<<<<<< HEAD name: "Вася", *!* [id]: 123 // просто "id: 123" не сработает @@ -146,16 +263,36 @@ let user = { Свойства, чьи ключи -- символы, не перебираются циклом `for..in`. Например: +======= + name: "John", +*!* + [id]: 123 // not "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: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let id = Symbol("id"); let user = { +<<<<<<< HEAD name: "Вася", +======= + name: "John", +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 age: 30, [id]: 123 }; *!* +<<<<<<< HEAD for (let key in user) alert(key); // name, age (свойства с ключом-символом нет среди перечисленных) */!* @@ -166,6 +303,18 @@ alert( "Напрямую: " + user[id] ); Это -- часть общего принципа "сокрытия символьных свойств". Если другая библиотека или скрипт будут работать с нашим объектом, то при переборе они не получат ненароком наше символьное свойство. `Object.keys(user)` также игнорирует символы. А вот [Object.assign](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Object/assign), в отличие от цикла `for..in`, копирует и строковые, и символьные свойства: +======= +for (let key in user) alert(key); // name, age (no symbols) +*/!* + +// the direct access by the symbol works +alert( "Direct: " + user[id] ); // Direct: 123 +``` + +[Object.keys(user)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys) also ignores them. That's a part of the general "hiding symbolic properties" principle. 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: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let id = Symbol("id"); @@ -178,6 +327,7 @@ let clone = Object.assign({}, user); alert( clone[id] ); // 123 ``` +<<<<<<< HEAD Здесь нет никакого парадокса или противоречия. Так и задумано. Идея заключается в том, что, когда мы клонируем или объединяем объекты, мы обычно хотим скопировать *все* свойства (включая такие свойства с ключами-символами, как, например, `id` в примере выше). @@ -210,10 +360,44 @@ alert( id === idAgain ); // true В некоторых языках программирования, например, Ruby, на одно имя (описание) приходится один символ, и не могут существовать разные символы с одинаковым именем. В JavaScript, как мы видим, это утверждение верно только для глобальных символов. +======= +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`). + +## Global symbols + +As we've seen, usually all symbols are different, even if they have the same name. 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 read (create if absent) a symbol from 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 (maybe from another part of the code) +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 true for global symbols. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` ### Symbol.keyFor +<<<<<<< HEAD Для глобальных символов, кроме `Symbol.for(key)`, который ищет символ по имени, существует обратный метод: `Symbol.keyFor(sym)`, который, наоборот, принимает глобальный символ и возвращает его имя. К примеру: @@ -224,36 +408,70 @@ let sym = Symbol.for("name"); let sym2 = Symbol.for("id"); // получаем имя по символу +======= +We have seen that for global symbols, `Symbol.for(key)` returns a symbol by name. To do the opposite -- return a name by global symbol -- we can use: `Symbol.keyFor(sym)`: + +For instance: + +```js run +// get symbol by name +let sym = Symbol.for("name"); +let sym2 = Symbol.for("id"); + +// get name by symbol +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 alert( Symbol.keyFor(sym) ); // name alert( Symbol.keyFor(sym2) ); // id ``` +<<<<<<< HEAD Внутри метода `Symbol.keyFor` используется глобальный реестр символов для нахождения имени символа. Так что этот метод не будет работать для неглобальных символов. Если символ неглобальный, метод не сможет его найти и вернёт `undefined`. Впрочем, для любых символов доступно свойство `description`. Например: +======= +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 returns `undefined`. + +That said, all symbols have the `description` property. + +For instance: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let globalSymbol = Symbol.for("name"); let localSymbol = Symbol("name"); +<<<<<<< HEAD alert( Symbol.keyFor(globalSymbol) ); // name, глобальный символ alert( Symbol.keyFor(localSymbol) ); // undefined для неглобального символа +======= +alert( Symbol.keyFor(globalSymbol) ); // name, global symbol +alert( Symbol.keyFor(localSymbol) ); // undefined, not global +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 alert( localSymbol.description ); // name ``` +<<<<<<< HEAD ## Системные символы Существует множество "системных" символов, использующихся внутри самого JavaScript, и мы можем использовать их, чтобы настраивать различные аспекты поведения объектов. Эти символы перечислены в спецификации в таблице [Well-known symbols](https://tc39.github.io/ecma262/#sec-well-known-symbols): +======= +## 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: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 - `Symbol.hasInstance` - `Symbol.isConcatSpreadable` - `Symbol.iterator` - `Symbol.toPrimitive` +<<<<<<< HEAD - ...и так далее. В частности, `Symbol.toPrimitive` позволяет описать правила для объекта, согласно которым он будет преобразовываться к примитиву. Мы скоро увидим его применение. @@ -279,3 +497,30 @@ alert( localSymbol.description ); // name 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), который возвращает *все* ключи объекта, включая символьные. Так что они не совсем спрятаны. Но большинство библиотек, встроенных методов и синтаксических конструкций не используют эти методы. +======= +- ...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 (name). + +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` with the same `key` 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 accidentally processed together with other properties. Also it won't be accessed directly, because another script does not have our symbol. So the property will be protected from accidental use or overwrite. + + 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. But most libraries, built-in functions and syntax constructs don't use these methods. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/1-js/04-object-basics/09-object-toprimitive/article.md b/1-js/04-object-basics/09-object-toprimitive/article.md index ed6a70e668..ff2177039d 100644 --- a/1-js/04-object-basics/09-object-toprimitive/article.md +++ b/1-js/04-object-basics/09-object-toprimitive/article.md @@ -1,4 +1,5 @@ +<<<<<<< HEAD # Преобразование объектов в примитивы Что произойдёт, если сложить два объекта `obj1 + obj2`, вычесть один из другого `obj1 - obj2` или вывести их на экран, воспользовавшись `alert(obj)`? @@ -46,10 +47,60 @@ JavaScript совершенно не позволяет настраивать, alert(obj); // используем объект в качестве ключа +======= +# Object to primitive conversion + +What happens when objects are added `obj1 + obj2`, subtracted `obj1 - obj2` or printed using `alert(obj)`? + +JavaScript doesn't allow you to customize how operators work on objects. Unlike some other programming languages, such as Ruby or C++, we can't implement a special object method to handle addition (or other operators). + +In case of such operations, objects are auto-converted to primitives, and then the operation is carried out over these primitives and results in a primitive value. + +That's an important limitation: the result of `obj1 + obj2` (or another math operation) can't be another object! + +E.g. we can't make objects representing vectors or matrices (or achievements or whatever), add them and expect a "summed" object as the result. Such architectural feats are automatically "off the board". + +So, because we can't technically do much here, there's no maths with objects in real projects. When it happens, with rare exceptions, it's because of a coding mistake. + +In this chapter we'll cover how an object converts to primitive and how to customize it. + +We have two purposes: + +1. It will allow us to understand what's going on in case of coding mistakes, when such an operation happened accidentally. +2. There are exceptions, where such operations are possible and look good. E.g. subtracting or comparing dates (`Date` objects). We'll come across them later. + +## Conversion rules + +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 fill it. + +1. There's no conversion to boolean. All objects are `true` in a boolean context, as simple as that. There exist only numeric and string conversions. +2. 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. +3. As for the string conversion -- it usually happens when we output an object with `alert(obj)` and in similar contexts. + +We can implement string and numeric conversion by ourselves, using special object methods. + +Now let's get into technical details, because it's the only way to cover the topic in-depth. + +## Hints + +How does JavaScript decide which conversion to apply? + +There are three variants of type conversion, that happen in various situations. They're called "hints", as described in the [specification](https://tc39.github.io/ecma262/#sec-toprimitive): + +`"string"` +: For an object-to-string conversion, when we're doing an operation on an object that expects a string, like `alert`: + + ```js + // output + alert(obj); + + // using object as a property key +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 anotherObj[obj] = 123; ``` `"number"` +<<<<<<< HEAD : Для преобразования объекта к числу, в случае математических операций: ```js @@ -110,6 +161,70 @@ obj[Symbol.toPrimitive] = function(hint) { Если метод `Symbol.toPrimitive` существует, он используется для всех хинтов, и больше никаких методов не требуется. Например, здесь объект `user` реализует его: +======= +: For an object-to-number conversion, like when we're doing 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; + ``` + + Most built-in mathematical functions also include such conversion. + +`"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 if a binary plus gets an object as an argument, it uses the `"default"` hint to convert it. + + Also, if an object is compared using `==` with a string, number or a symbol, it's also unclear which conversion should be done, so the `"default"` hint is used. + + ```js + // binary plus uses the "default" hint + let total = obj1 + obj2; + + // obj == number uses the "default" hint + if (user == 1) { ... }; + ``` + + The greater and less comparison operators, such as `<` `>`, can work with both strings and numbers too. Still, they use the `"number"` hint, not `"default"`. That's for historical reasons. + +In practice though, things are a bit simpler. + +All built-in objects except for one case (`Date` object, we'll learn it later) implement `"default"` conversion the same way as `"number"`. And we probably should do the same. + +Still, it's important to know about all 3 hints, soon we'll see why. + +**To do the conversion, JavaScript tries to find and call three object methods:** + +1. Call `obj[Symbol.toPrimitive](hint)` - the method with the symbolic key `Symbol.toPrimitive` (system symbol), if such method exists, +2. Otherwise if hint is `"string"` + - try calling `obj.toString()` or `obj.valueOf()`, whatever exists. +3. Otherwise if hint is `"number"` or `"default"` + - try calling `obj.valueOf()` or `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) { + // here goes the code to convert this object to a primitive + // it must return a primitive value + // hint = one of "string", "number", "default" +}; +``` + +If the method `Symbol.toPrimitive` exists, it's used for all hints, and no more methods are needed. + +For instance, here `user` object implements it: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let user = { @@ -122,12 +237,17 @@ let user = { } }; +<<<<<<< HEAD // демонстрация результатов преобразований: +======= +// conversions demo: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 alert(user); // hint: string -> {name: "John"} alert(+user); // hint: number -> 1000 alert(user + 500); // hint: default -> 1500 ``` +<<<<<<< HEAD Как мы можем видеть из кода, `user` становится либо строкой со своим описанием, либо суммой денег в зависимости от преобразования. Единый метод `user[Symbol.toPrimitive]` обрабатывает все случаи преобразования. @@ -148,6 +268,27 @@ alert(user + 500); // hint: default -> 1500 - Метод `valueOf` возвращает сам объект. Взгляните на пример: +======= +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 + +If there's no `Symbol.toPrimitive` then JavaScript tries to find methods `toString` and `valueOf`: + +- For the `"string"` hint: call `toString` method, and if it doesn't exist or if it returns an object instead of a primitive value, then call `valueOf` (so `toString` has the priority for string conversions). +- For other hints: call `valueOf`, and if it doesn't exist or if it returns an object instead of a primitive value, then call `toString` (so `valueOf` has the priority for maths). + +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. + +These methods must return a primitive value. If `toString` or `valueOf` returns an object, then it's ignored (same as if there were no method). + +By default, a plain object has following `toString` and `valueOf` methods: + +- The `toString` method returns a string `"[object Object]"`. +- The `valueOf` method returns the object itself. + +Here's the demo: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let user = {name: "John"}; @@ -156,6 +297,7 @@ alert(user); // [object Object] alert(user.valueOf() === user); // true ``` +<<<<<<< HEAD Таким образом, если мы попытаемся использовать объект в качестве строки, как например в `alert` или вроде того, то по умолчанию мы увидим `[object Object]`. Значение по умолчанию `valueOf` упоминается здесь только для полноты картины, чтобы избежать какой-либо путаницы. Как вы можете видеть, он возвращает сам объект и поэтому игнорируется. Не спрашивайте меня почему, это по историческим причинам. Так что мы можем предположить, что его не существует. @@ -163,18 +305,35 @@ alert(user.valueOf() === user); // true Давайте применим эти методы для настройки преобразования. Для примера, используем их в реализации всё того же объекта `user`. Но уже используя комбинацию `toString` и `valueOf` вместо `Symbol.toPrimitive`: +======= +So if we try to use an object as a string, like in an `alert` or so, then by default we see `[object Object]`. + +The default `valueOf` is mentioned here only for the sake of completeness, to avoid any confusion. As you can see, it returns the object itself, and so is ignored. Don't ask me why, that's for historical reasons. So we can assume it doesn't exist. + +Let's implement these methods to customize the conversion. + +For instance, here `user` does the same as above using a combination of `toString` and `valueOf` instead of `Symbol.toPrimitive`: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let user = { name: "John", money: 1000, +<<<<<<< HEAD // для хинта равного "string" +======= + // for hint="string" +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 toString() { return `{name: "${this.name}"}`; }, +<<<<<<< HEAD // для хинта равного "number" или "default" +======= + // for hint="number" or "default" +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 valueOf() { return this.money; } @@ -186,9 +345,15 @@ alert(+user); // valueOf -> 1000 alert(user + 500); // valueOf -> 1500 ``` +<<<<<<< HEAD Как видим, получилось то же поведение, что и в предыдущем примере с `Symbol.toPrimitive`. Довольно часто нам нужно единое "универсальное" место для обработки всех примитивных преобразований. В этом случае мы можем реализовать только `toString`: +======= +As we can see, the behavior is the same as the previous example with `Symbol.toPrimitive`. + +Often we want a single "catch-all" place to handle all primitive conversions. In this case, we can implement `toString` only, like this: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let user = { @@ -203,6 +368,7 @@ alert(user); // toString -> John alert(user + 500); // toString -> John500 ``` +<<<<<<< HEAD В отсутствие `Symbol.toPrimitive` и `valueOf`, `toString` обработает все примитивные преобразования. ### Преобразование может вернуть любой примитивный тип @@ -232,11 +398,43 @@ alert(user + 500); // toString -> John500 ```js run let obj = { // toString обрабатывает все преобразования в случае отсутствия других методов +======= +In the absence of `Symbol.toPrimitive` and `valueOf`, `toString` will handle all primitive conversions. + +### A conversion can return any primitive type + +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 the hint `"number"`. + +The only mandatory thing: these methods must return a primitive, not an object. + +```smart header="Historical notes" +For historical reasons, if `toString` or `valueOf` returns an object, there's no error, but such value is ignored (like if the method didn't exist). That's because in ancient times there was no good "error" concept in JavaScript. + +In contrast, `Symbol.toPrimitive` is stricter, it *must* return a primitive, otherwise there will be an error. +``` + +## Further conversions + +As we know already, many operators and functions perform type conversions, e.g. multiplication `*` converts operands to numbers. + +If we pass an object as an argument, then there are two stages of calculations: +1. The object is converted to a primitive (using the rules described above). +2. If necessary for further calculations, the resulting primitive is also converted. + +For instance: + +```js run +let obj = { + // toString handles all conversions in the absence of other methods +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 toString() { return "2"; } }; +<<<<<<< HEAD alert(obj * 2); // 4, объект был преобразован к примитиву "2", затем умножение сделало его числом ``` @@ -244,6 +442,15 @@ alert(obj * 2); // 4, объект был преобразован к прими 2. Затем `"2" * 2` становится `2 * 2` (строка преобразуется в число). А вот, к примеру, бинарный плюс в подобной ситуации соединил бы строки, так как он совсем не брезгует строк: +======= +alert(obj * 2); // 4, object converted to primitive "2", then multiplication made it a number +``` + +1. The multiplication `obj * 2` first converts the object to primitive (that's a string `"2"`). +2. Then `"2" * 2` becomes `2 * 2` (the string is converted to number). + +Binary plus will concatenate strings in the same situation, as it gladly accepts a string: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let obj = { @@ -252,6 +459,7 @@ let obj = { } }; +<<<<<<< HEAD alert(obj + 2); // "22" ("2" + 2), преобразование к примитиву вернуло строку => конкатенация ``` @@ -277,3 +485,30 @@ alert(obj + 2); // "22" ("2" + 2), преобразование к примит Все эти методы должны возвращать примитив (если определены). На практике часто бывает достаточно реализовать только `obj.toString()` в качестве универсального метода для преобразований к строке, который должен возвращать удобочитаемое представление объекта для целей логирования или отладки. +======= +alert(obj + 2); // "22" ("2" + 2), conversion to primitive returned a string => concatenation +``` + +## 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 operations that need a string) +- `"number"` (for maths) +- `"default"` (few operators, usually objects implement it the same way as `"number"`) + +The specification describes explicitly which operator uses which hint. + +The conversion algorithm is: + +1. Call `obj[Symbol.toPrimitive](hint)` if the method exists, +2. Otherwise if hint is `"string"` + - try calling `obj.toString()` or `obj.valueOf()`, whatever exists. +3. Otherwise if hint is `"number"` or `"default"` + - try calling `obj.valueOf()` or `obj.toString()`, whatever exists. + +All these methods must return a primitive to work (if defined). + +In practice, it's often enough to implement only `obj.toString()` as a "catch-all" method for string conversions that should return a "human-readable" representation of an object, for logging or debugging purposes. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/1-js/05-data-types/01-primitives-methods/1-string-new-property/task.md b/1-js/05-data-types/01-primitives-methods/1-string-new-property/task.md index 80534dfbe9..d8a3ef49cb 100644 --- a/1-js/05-data-types/01-primitives-methods/1-string-new-property/task.md +++ b/1-js/05-data-types/01-primitives-methods/1-string-new-property/task.md @@ -15,4 +15,8 @@ str.test = 5; alert(str.test); ``` +<<<<<<< HEAD Как вы думаете, это сработает? Что выведется на экран? +======= +What do you think, will it work? What will be shown? +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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 ba94c8f491..fbdc984371 100644 --- a/1-js/05-data-types/01-primitives-methods/article.md +++ b/1-js/05-data-types/01-primitives-methods/article.md @@ -6,8 +6,13 @@ JavaScript позволяет нам работать с примитивным Примитив +<<<<<<< HEAD - Это - значение «примитивного» типа. - Есть 7 примитивных типов: `string`, `number`, `boolean`, `symbol`, `null`, `undefined` и `bigint`. +======= +- Is a value of a primitive type. +- There are 7 primitive types: `string`, `number`, `bigint`, `boolean`, `symbol`, `null` and `undefined`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Объект @@ -39,8 +44,13 @@ roma.sayHi(); // Привет, дружище! Вот парадокс, с которым столкнулся создатель JavaScript: +<<<<<<< HEAD - Есть много всего, что хотелось бы сделать с примитивами, такими как строка или число. Было бы замечательно, если бы мы могли обращаться к ним при помощи методов. - Примитивы должны быть лёгкими и быстрыми насколько это возможно. +======= +- There are many things one would want to do with a primitive, like a string or a number. It would be great to access them using methods. +- Primitives must be as fast and lightweight as possible. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Выбранное решение, хотя выглядит оно немного неуклюже: @@ -48,7 +58,11 @@ roma.sayHi(); // Привет, дружище! 2. Язык позволяет осуществлять доступ к методам и свойствам строк, чисел, булевых значений и символов. 3. Чтобы это работало, при таком доступе создаётся специальный "объект-обёртка", который предоставляет нужную функциональность, а после удаляется. +<<<<<<< HEAD Каждый примитив имеет свой собственный "объект-обёртку", которые называются: `String`, `Number`, `Boolean`, `Symbol` и `BigInt`. Таким образом, они имеют разный набор методов. +======= +The "object wrappers" are different for each primitive type and are called: `String`, `Number`, `Boolean`, `Symbol` and `BigInt`. Thus, they provide different sets of methods. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 К примеру, существует метод [str.toUpperCase()](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/String/toUpperCase), который возвращает строку в верхнем регистре. @@ -105,10 +119,17 @@ if (zero) { } ``` +<<<<<<< HEAD С другой стороны, использование функций `String/Number/Boolean` без оператора `new` - вполне разумно и полезно. Они превращают значение в соответствующий примитивный тип: в строку, в число, в булевый тип. К примеру, следующее вполне допустимо: +======= +On the other hand, using the same functions `String/Number/Boolean` without `new` is totally fine and useful thing. They convert a value to the corresponding type: to a string, a number, or a boolean (primitive). + +For example, this is entirely valid: + +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js let num = Number("123"); // превращает строку в число ``` diff --git a/1-js/05-data-types/02-number/article.md b/1-js/05-data-types/02-number/article.md index 28bf6154db..999fdfa50c 100644 --- a/1-js/05-data-types/02-number/article.md +++ b/1-js/05-data-types/02-number/article.md @@ -1,10 +1,20 @@ # Числа +<<<<<<< HEAD В современном 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` числа нужны достаточно редко, мы рассмотрим их в отдельной главе . В данной главе мы рассмотрим только первый тип чисел: числа типа `number`. Давайте глубже изучим, как с ними работать в JavaScript. +======= +In modern JavaScript, there are two types of numbers: + +1. Regular numbers in JavaScript are stored in 64-bit format [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754), also known as "double precision floating point numbers". These are numbers that we're using most of the time, and we'll talk about them in this chapter. + +2. BigInt numbers represent integers of arbitrary length. They are sometimes needed because a regular integer number can't safely exceed (253-1) or be less than -(253-1), as we mentioned earlier in the chapter . As bigints are used in a few special areas, we devote them to a special chapter . + +So here we'll talk about regular numbers. Let's expand our knowledge of them. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## Способы записи числа @@ -14,6 +24,7 @@ let billion = 1000000000; ``` +<<<<<<< HEAD Мы также можем использовать символ нижнего подчёркивания `_` в качестве разделителя: ```js @@ -25,10 +36,24 @@ let billion = 1_000_000_000 Однако в реальной жизни мы в основном стараемся не писать длинные последовательности нулей, так как можно легко ошибиться. Укороченная запись может выглядеть как `"1млрд"` или `"7.3млрд"` для 7 миллиардов 300 миллионов. Такой принцип работает для всех больших чисел. В JavaScript, чтобы укоротить запись числа, мы можем добавить к нему букву `"e"` и указать необходимое количество нулей: +======= +We also can use underscore `_` as the separator: + +```js +let billion = 1_000_000_000; +``` + +Here the underscore `_` plays the role of the "[syntactic sugar](https://en.wikipedia.org/wiki/Syntactic_sugar)", it makes the number more readable. The JavaScript engine simply ignores `_` between digits, so it's exactly the same one billion as above. + +In real life though, we try to avoid writing long sequences of zeroes. We're too lazy for that. We'll try to write something like `"1bn"` for a billion or `"7.3bn"` for 7 billion 300 million. The same is true for most large numbers. + +In JavaScript, we can shorten a number by appending the letter `"e"` to it and specifying the zeroes count: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let billion = 1e9; // 1 миллиард, буквально: 1 и 9 нулей +<<<<<<< HEAD alert( 7.3e9 ); // 7.3 миллиарда (7,300,000,000) ``` @@ -52,15 +77,51 @@ let ms = 1e-6; // шесть нулей слева от 1 ``` Если мы подсчитаем количество нулей в `0.000001`, их будет 6. Естественно, верная запись `1e-6`. +======= +alert( 7.3e9 ); // 7.3 billions (same as 7300000000 or 7_300_000_000) +``` + +In other words, `e` multiplies the number by `1` with the given zeroes count. + +```js +1e3 === 1 * 1000; // e3 means *1000 +1.23e6 === 1.23 * 1000000; // e6 means *1000000 +``` + +Now let's write something very small. Say, 1 microsecond (one-millionth of a second): + +```js +let mсs = 0.000001; +``` + +Just like before, using `"e"` can help. If we'd like to avoid writing the zeroes explicitly, we could write the same as: + +```js +let mcs = 1e-6; // five zeroes to the left from 1 +``` + +If we count the zeroes in `0.000001`, there are 6 of them. So naturally it's `1e-6`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Другими словами, отрицательное число после `"e"` подразумевает деление на `1` с указанным количеством нулей: ```js +<<<<<<< HEAD // 1 делится на 1 с 3 нулями 1e-3 === 1 / 1000 (=0.001) // 1.23 делится на 1 с 6 нулями 1.23e-6 === 1.23 / 1000000 (=0.00000123) +======= +// -3 divides by 1 with 3 zeroes +1e-3 === 1 / 1000; // 0.001 + +// -6 divides by 1 with 6 zeroes +1.23e-6 === 1.23 / 1000000; // 0.00000123 + +// an example with a bigger number +1234e-2 === 1234 / 100; // 12.34, decimal point moves 2 times +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` ### Шестнадцатеричные, двоичные и восьмеричные числа @@ -97,13 +158,23 @@ alert( num.toString(16) ); // ff alert( num.toString(2) ); // 11111111 ``` +<<<<<<< HEAD `base` может варьироваться от `2` до `36` (по умолчанию `10`). +======= +The `base` can vary from `2` to `36`. By default, it's `10`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Часто используемые: +<<<<<<< HEAD - **base=16** — для шестнадцатеричного представления цвета, кодировки символов и т.д., цифры могут быть `0..9` или `A..F`. - **base=2** — обычно используется для отладки побитовых операций, цифры `0` или `1`. - **base=36** — максимальное основание, цифры могут быть `0..9` или `A..Z`. То есть, используется весь латинский алфавит для представления числа. Забавно, но можно использовать `36`-разрядную систему счисления для получения короткого представления большого числового идентификатора. К примеру, для создания короткой ссылки. Для этого просто преобразуем его в `36`-разрядную систему счисления: +======= +- **base=16** is used for hex colors, character encodings etc, digits can be `0..9` or `A..F`. +- **base=2** is mostly for debugging bitwise operations, digits can be `0` or `1`. +- **base=36** is the maximum, digits can be `0..9` or `A..Z`. The whole Latin alphabet is used to represent a number. A funny, but useful case for `36` is when we need to turn a long numeric identifier into something shorter, for example, to make a short url. Can simply represent it in the numeral system with base `36`: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run alert( 123456..toString(36) ); // 2n9c @@ -112,9 +183,16 @@ alert( num.toString(2) ); // 11111111 ```warn header="Две точки для вызова метода" Внимание! Две точки в `123456..toString(36)` это не опечатка. Если нам надо вызвать метод непосредственно на числе, как `toString` в примере выше, то нам надо поставить две точки `..` после числа. +<<<<<<< HEAD Если мы поставим одну точку: `123456.toString(36)`, тогда это будет ошибкой, поскольку синтаксис JavaScript предполагает, что после первой точки начинается десятичная часть. А если поставить две точки, то JavaScript понимает, что десятичная часть отсутствует, и начинается метод. Также можно записать как `(123456).toString(36)`. +======= +If we placed a single dot: `123456.toString(36)`, then there would be an error, because JavaScript syntax implies the decimal part after the first dot. And if we place one more dot, then JavaScript knows that the decimal part is empty and now uses the method. + +Also could write `(123456).toString(36)`. + +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` ## Округление @@ -130,7 +208,11 @@ alert( num.toString(2) ); // 11111111 : Округление в большую сторону: `3.1` становится `4`, а `-1.1` — `-1`. `Math.round` +<<<<<<< HEAD : Округление до ближайшего целого: `3.1` становится `3`, `3.6` — `4`, а `-1.1` — `-1`. +======= +: Rounds to the nearest integer: `3.1` becomes `3`, `3.6` becomes `4`. In the middle cases `3.5` rounds up to `4`, and `-3.5` rounds up to `-3`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 `Math.trunc` (не поддерживается в Internet Explorer) : Производит удаление дробной части без округления: `3.1` становится `3`, а `-1.1` — `-1`. @@ -140,8 +222,10 @@ alert( num.toString(2) ); // 11111111 | | `Math.floor` | `Math.ceil` | `Math.round` | `Math.trunc` | |---|---------|--------|---------|---------| |`3.1`| `3` | `4` | `3` | `3` | +|`3.5`| `3` | `4` | `4` | `3` | |`3.6`| `3` | `4` | `4` | `3` | |`-1.1`| `-2` | `-1` | `-1` | `-1` | +|`-1.5`| `-2` | `-1` | `-1` | `-1` | |`-1.6`| `-2` | `-1` | `-2` | `-1` | @@ -174,20 +258,34 @@ alert( num.toString(2) ); // 11111111 alert( num.toFixed(1) ); // "12.4" ``` +<<<<<<< HEAD Обратите внимание, что результатом `toFixed` является строка. Если десятичная часть короче, чем необходима, будут добавлены нули в конец строки: +======= + Please note that the result of `toFixed` is a string. If the decimal part is shorter than required, zeroes are appended to the end: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let num = 12.34; alert( num.toFixed(5) ); // "12.34000", добавлены нули, чтобы получить 5 знаков после запятой ``` +<<<<<<< HEAD Мы можем преобразовать полученное значение в число, используя унарный оператор `+` или `Number()`, пример с унарным оператором: `+num.toFixed(5)`. +======= + We can convert it to a number using the unary plus or a `Number()` call, e.g. write `+num.toFixed(5)`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## Неточные вычисления +<<<<<<< HEAD Внутри JavaScript число представлено в виде 64-битного формата [IEEE-754](https://ru.wikipedia.org/wiki/IEEE_754-1985). Для хранения числа используется 64 бита: 52 из них используется для хранения цифр, 11 для хранения положения десятичной точки и один бит отведён на хранение знака. Если число слишком большое, оно переполнит 64-битное хранилище, JavaScript вернёт бесконечность: +======= +Internally, a number is represented in 64-bit format [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754), so there are exactly 64 bits to store a number: 52 of them are used to store the digits, 11 of them store the position of the decimal point, and 1 bit is for the sign. + +If a number is really huge, it may overflow the 64-bit storage and become a special numeric value `Infinity`: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run alert( 1e500 ); // Infinity @@ -195,7 +293,11 @@ alert( 1e500 ); // Infinity Наиболее часто встречающаяся ошибка при работе с числами в JavaScript - это потеря точности. +<<<<<<< HEAD Посмотрите на это (неверное!) сравнение: +======= +Consider this (falsy!) equality test: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run alert( 0.1 + 0.2 == 0.3 ); // *!*false*/!* @@ -209,7 +311,11 @@ alert( 0.1 + 0.2 == 0.3 ); // *!*false*/!* alert( 0.1 + 0.2 ); // 0.30000000000000004 ``` +<<<<<<< HEAD Ой! Здесь гораздо больше последствий, чем просто некорректное сравнение. Представьте, вы делаете интернет-магазин и посетители формируют заказ из 2-х позиций за `$0.10` и `$0.20`. Итоговый заказ будет `$0.30000000000000004`. Это будет сюрпризом для всех. +======= +Ouch! Imagine you're making an e-shopping site and the visitor puts `$0.10` and `$0.20` goods into their cart. The order total will be `$0.30000000000000004`. That would surprise anyone. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Но почему это происходит? @@ -220,6 +326,11 @@ alert(0.1.toString(2)); // 0.000110011001100110011001100110011001100110011001100 alert(0.2.toString(2)); // 0.001100110011001100110011001100110011001100110011001101 alert((0.1 + 0.2).toString(2)); // 0.0100110011001100110011001100110011001100110011001101 ``` +<<<<<<< HEAD +======= + +What is `0.1`? It is one divided by ten `1/10`, one-tenth. In the decimal numeral system, such numbers are easily representable. Compare it to one-third: `1/3`. It becomes an endless fraction `0.33333(3)`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Другими словами, что такое `0.1`? Это единица делённая на десять — `1/10`, одна десятая. В десятичной системе счисления такие числа легко представимы, по сравнению с одной третьей: `1/3`, которая становится бесконечной дробью `0.33333(3)`. @@ -238,8 +349,15 @@ alert( 0.1.toFixed(20) ); // 0.10000000000000000555 Вот почему `0.1 + 0.2` - это не совсем `0.3`. +<<<<<<< HEAD ```smart header="Не только в JavaScript" Справедливости ради заметим, что ошибка в точности вычислений для чисел с плавающей точкой сохраняется в любом другом языке, где используется формат IEEE 754, включая PHP, Java, C, Perl и Ruby. +======= +```smart header="Not only JavaScript" +The same issue exists in many other programming languages. + +PHP, Java, C, Perl, and Ruby give exactly the same result, because they are based on the same numeric format. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` Можно ли обойти проблему? Конечно, наиболее надёжный способ — это округлить результат используя метод [toFixed(n)](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Number/toFixed): @@ -263,7 +381,11 @@ alert( (0.1 * 10 + 0.2 * 10) / 10 ); // 0.3 alert( (0.28 * 100 + 0.14 * 100) / 100); // 0.4200000000000001 ``` +<<<<<<< HEAD Таким образом, метод умножения/деления уменьшает погрешность, но полностью её не решает. +======= +So, the multiply/divide approach reduces the error, but doesn't remove it totally. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Иногда можно попробовать полностью отказаться от дробей. Например, если мы в нашем интернет-магазине начнём использовать центы вместо долларов. Но что будет, если мы применим скидку 30%? На практике у нас не получится полностью избавиться от дроби. Просто используйте округление, чтобы отрезать "хвосты", когда надо. @@ -283,6 +405,7 @@ alert( 9999999999999999 ); // покажет 10000000000000000 ```smart header="Два нуля" Другим забавным следствием внутреннего представления чисел является наличие двух нулей: `0` и `-0`. +<<<<<<< HEAD Все потому, что знак представлен отдельным битом, так что, любое число может быть положительным и отрицательным, включая нуль. В большинстве случаев это поведение незаметно, так как операторы в JavaScript воспринимают их одинаковыми. @@ -291,6 +414,14 @@ alert( 9999999999999999 ); // покажет 10000000000000000 ## Проверка: isFinite и isNaN +======= +That's because a sign is represented by a single bit, so it can be set or not set for any number including a zero. + +In most cases, the distinction is unnoticeable, because operators are suited to treat them as the same. +``` + +## Tests: isFinite and isNaN +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Помните эти специальные числовые значения? @@ -307,7 +438,11 @@ alert( 9999999999999999 ); // покажет 10000000000000000 alert( isNaN("str") ); // true ``` +<<<<<<< HEAD Нужна ли нам эта функция? Разве не можем ли мы просто сравнить `=== NaN`? К сожалению, нет. Значение `NaN` уникально тем, что оно не является равным ничему другому, даже самому себе: +======= + But do we need this function? Can't we just use the comparison `=== NaN`? Unfortunately not. The value `NaN` is unique in that it does not equal anything, including itself: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run alert( NaN === NaN ); // false @@ -331,6 +466,7 @@ let num = +prompt("Введите число:", ''); alert( isFinite(num) ); ``` +<<<<<<< HEAD Помните, что пустая строка интерпретируется как `0` во всех числовых функциях, включая`isFinite`. ````smart header="`Number.isNaN` и `Number.isFinite`" @@ -341,12 +477,51 @@ alert( isFinite(num) ); ```js run alert( Number.isNaN(NaN) ); // true alert( Number.isNaN("str" / 2) ); // true +======= +Please note that an empty or a space-only string is treated as `0` in all numeric functions including `isFinite`. + +````smart header="`Number.isNaN` and `Number.isFinite`" +[Number.isNaN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isNaN) and [Number.isFinite](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isFinite) methods are the more "strict" versions of `isNaN` and `isFinite` functions. They do not autoconvert their argument into a number, but check if it belongs to the `number` type instead. + +- `Number.isNaN(value)` returns `true` if the argument belongs to the `number` type and it is `NaN`. In any other case, it returns `false`. + + ```js run + alert( Number.isNaN(NaN) ); // true + alert( Number.isNaN("str" / 2) ); // true + + // Note the difference: + alert( Number.isNaN("str") ); // false, because "str" belongs to the string type, not the number type + alert( isNaN("str") ); // true, because isNaN converts string "str" into a number and gets NaN as a result of this conversion + ``` + +- `Number.isFinite(value)` returns `true` if the argument belongs to the `number` type and it is not `NaN/Infinity/-Infinity`. In any other case, it returns `false`. + + ```js run + alert( Number.isFinite(123) ); // true + alert( Number.isFinite(Infinity) ); // false + alert( Number.isFinite(2 / 0) ); // false + + // Note the difference: + alert( Number.isFinite("123") ); // false, because "123" belongs to the string type, not the number type + alert( isFinite("123") ); // true, because isFinite converts string "123" into a number 123 + ``` + +In a way, `Number.isNaN` and `Number.isFinite` are simpler and more straightforward than `isNaN` and `isFinite` functions. In practice though, `isNaN` and `isFinite` are mostly used, as they're shorter to write. +```` + +```smart header="Comparison with `Object.is`" +There is a special built-in method `Object.is` that compares values like `===`, but is more reliable for two edge cases: + +1. It works with `NaN`: `Object.is(NaN, NaN) === true`, that's a good thing. +2. Values `0` and `-0` are different: `Object.is(0, -0) === false`, technically that's correct because internally the number has a sign bit that may be different even if all other bits are zeroes. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 // Обратите внимание на разный результат: alert( Number.isNaN("str") ); // false, так как "str" является строкой, а не числом alert( isNaN("str") ); // true, так как isNaN сначала преобразует строку "str" в число и в результате преобразования получает NaN ``` +<<<<<<< HEAD - `Number.isFinite(value)` возвращает `true` только в том случае, если аргумент принадлежит к типу `number` и не является `NaN/Infinity/-Infinity`. Во всех остальных случаях возвращает `false`. ```js run @@ -372,6 +547,9 @@ alert( isFinite(num) ); Во всех других случаях `Object.is(a, b)` идентичен `a === b`. Этот способ сравнения часто используется в спецификации JavaScript. Когда внутреннему алгоритму необходимо сравнить 2 значения на предмет точного совпадения, он использует `Object.is` (Определение [SameValue](https://tc39.github.io/ecma262/#sec-samevalue)). +======= +We mention `Object.is` here, because it's often used in JavaScript specification. When an internal algorithm needs to compare two values for being exactly the same, it uses `Object.is` (internally called [SameValue](https://tc39.github.io/ecma262/#sec-samevalue)). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` @@ -385,7 +563,11 @@ alert( +"100px" ); // NaN Единственное исключение — это пробелы в начале строки и в конце, они игнорируются. +<<<<<<< HEAD В реальной жизни мы часто сталкиваемся со значениями у которых есть единица измерения, например `"100px"` или `"12pt"` в CSS. Также во множестве стран символ валюты записывается после номинала `"19€"`. Так как нам получить числовое значение из таких строк? +======= +But in real life, we often have values in units, like `"100px"` or `"12pt"` in CSS. Also in many countries, the currency symbol goes after the amount, so we have `"19€"` and would like to extract a numeric value out of that. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Для этого есть `parseInt` и `parseFloat`. @@ -423,7 +605,11 @@ alert( parseInt('2n9c', 36) ); // 123456 Несколько примеров: `Math.random()` +<<<<<<< HEAD : Возвращает псевдослучайное число в диапазоне от 0 (включительно) до 1 (но не включая 1) +======= +: Returns a random number from 0 to 1 (not including 1). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run alert( Math.random() ); // 0.1234567894322 @@ -431,8 +617,13 @@ alert( parseInt('2n9c', 36) ); // 123456 alert( Math.random() ); // ... (любое количество псевдослучайных чисел) ``` +<<<<<<< HEAD `Math.max(a, b, c...)` / `Math.min(a, b, c...)` : Возвращает наибольшее/наименьшее число из перечисленных аргументов. +======= +`Math.max(a, b, c...)` and `Math.min(a, b, c...)` +: Returns the greatest and smallest from the arbitrary number of arguments. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run alert( Math.max(3, 5, -10, 0, 1) ); // 5 @@ -440,28 +631,58 @@ alert( parseInt('2n9c', 36) ); // 123456 ``` `Math.pow(n, power)` +<<<<<<< HEAD : Возвращает число `n`, возведённое в степень `power` +======= +: Returns `n` raised to the given power. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run alert( Math.pow(2, 10) ); // 2 в степени 10 = 1024 ``` +<<<<<<< HEAD В объекте `Math` есть множество функций и констант, включая тригонометрические функции, подробнее можно ознакомиться в документации по объекту [Math](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Math). +======= +There are more functions and constants in `Math` object, including trigonometry, which you can find in the [docs for the Math object](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Math). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## Итого +<<<<<<< HEAD Чтобы писать числа с большим количеством нулей: - Используйте краткую форму записи чисел - `"e"`, с указанным количеством нулей. Например: `123e6` это `123` с 6-ю нулями `123000000`. - Отрицательное число после `"e"` приводит к делению числа на 1 с указанным количеством нулей. Например: `123e-6` это `0.000123` (`123` миллионных). +======= +To write numbers with many zeroes: + +- Append `"e"` with the zeroes count to the number. Like: `123e6` is the same as `123` with 6 zeroes `123000000`. +- A negative number after `"e"` causes the number to be divided by 1 with given zeroes. E.g. `123e-6` means `0.000123` (`123` millionths). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Для других систем счисления: +<<<<<<< HEAD - Можно записывать числа сразу в шестнадцатеричной (`0x`), восьмеричной (`0o`) и бинарной (`0b`) системах счисления - `parseInt(str, base)` преобразует строку в целое число в соответствии с указанной системой счисления: `2 ≤ base ≤ 36`. - `num.toString(base)` представляет число в строковом виде в указанной системе счисления `base`. Для проверки на `NaN` и `Infinity`: +======= +- Can write numbers directly in hex (`0x`), octal (`0o`) and binary (`0b`) systems. +- `parseInt(str, base)` parses the string `str` into an integer in numeral system with given `base`, `2 ≤ base ≤ 36`. +- `num.toString(base)` converts a number to a string in the numeral system with the given `base`. + +For regular number tests: + +- `isNaN(value)` converts its argument to a number and then tests it for being `NaN` +- `Number.isNaN(value)` checks whether its argument belongs to the `number` type, and if so, tests it for being `NaN` +- `isFinite(value)` converts its argument to a number and then tests it for not being `NaN/Infinity/-Infinity` +- `Number.isFinite(value)` checks whether its argument belongs to the `number` type, and if so, tests it for not being `NaN/Infinity/-Infinity` + +For converting values like `12pt` and `100px` to a number: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 - `isNaN(value)` преобразует аргумент в число и проверяет, является ли оно `NaN` - `Number.isNaN(value)` проверяет, является ли аргумент числом, и если да, то проверяет, является ли оно `NaN` @@ -474,9 +695,13 @@ alert( parseInt('2n9c', 36) ); // 123456 Для дробей: +<<<<<<< HEAD - Используйте округления `Math.floor`, `Math.ceil`, `Math.trunc`, `Math.round` или `num.toFixed(precision)`. - Помните, что при работе с дробями происходит потеря точности. Ещё больше математических функций: - Документация по объекту [Math](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Math). Библиотека маленькая, но содержит всё самое важное. +======= +- See the [Math](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Math) object when you need them. The library is very small but can cover basic needs. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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 859ae516b2..dcafe2a0c2 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 @@ -8,12 +8,16 @@ let newStr = str[0].toUpperCase() + str.slice(1); Однако есть небольшая проблемка. Если строка пуста, `str[0]` вернёт `undefined`, а у `undefined` нет метода `toUpperCase()`, поэтому мы получим ошибку. +<<<<<<< HEAD Выхода два: 1. Использовать `str.charAt(0)`, поскольку этот метод всегда возвращает строку (для пустой строки — пустую). 2. Добавить проверку на пустую строку. Вот второй вариант: +======= +The easiest way out is to add a test for an empty string, like this: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run demo function ucFirst(str) { 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 4f53946cb8..7a4daf8440 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,10 @@ Строка, которую мы возвращаем, должна быть не длиннее `maxlength`, поэтому, если мы обрезаем строку, то мы должны убрать на один символ больше, чем `maxlength` — чтобы хватило места на многоточие. +<<<<<<< HEAD Имейте в виду, что в качестве многоточия здесь используется `…` — ровно один специальный Юникодный символ. Это не то же самое, что `...` — три точки. +======= +Note that there is actually a single Unicode character for an ellipsis. That's not three dots. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```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 ea8e09caaf..2b48fd40e1 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 @@ -11,7 +11,13 @@ importance: 5 Например: ```js +<<<<<<< HEAD truncate("Вот, что мне хотелось бы сказать на эту тему:", 20) = "Вот, что мне хотело…" truncate("Всем привет!", 20) = "Всем привет!" +======= +truncate("What I'd like to tell on this topic is:", 20) == "What I'd like to te…" + +truncate("Hi everyone!", 20) == "Hi everyone!" +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` diff --git a/1-js/05-data-types/03-string/article.md b/1-js/05-data-types/03-string/article.md index 11acb7b03c..287a734e4c 100644 --- a/1-js/05-data-types/03-string/article.md +++ b/1-js/05-data-types/03-string/article.md @@ -47,9 +47,15 @@ let guestList = "Guests: // Error: Unexpected token ILLEGAL * John"; ``` +<<<<<<< HEAD Одинарные и двойные кавычки в языке с незапамятных времён: тогда потребность в многострочных строках не учитывалась. Что касается обратных кавычек, они появились существенно позже, и поэтому они гибче. Обратные кавычки также позволяют задавать "шаблонную функцию" перед первой обратной кавычкой. Используемый синтаксис: 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). Если перед строкой есть выражение, то шаблонная строка называется "теговым шаблоном". Это позволяет использовать свою шаблонизацию для строк, но на практике теговые шаблоны применяются редко. +======= +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. This feature is called "tagged templates", it's rarely seen, but you can read about it in the MDN: [Template literals](mdn:/JavaScript/Reference/Template_literals#Tagged_templates). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## Спецсимволы @@ -58,10 +64,17 @@ let guestList = "Guests: // Error: Unexpected token ILLEGAL ```js run let guestList = "Guests:\n * John\n * Pete\n * Mary"; +<<<<<<< HEAD alert(guestList); // список гостей, состоящий из нескольких строк ``` В частности, эти две строки эквивалентны, просто записаны по-разному: +======= +alert(guestList); // a multiline list of guests, same as above +``` + +As a simpler example, these two lines are equal, just written differently: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run // перевод строки добавлен с помощью символа перевода строки @@ -74,6 +87,7 @@ World`; alert(str1 == str2); // true ``` +<<<<<<< HEAD Есть и другие, реже используемые спецсимволы. Вот список: | Символ | Описание | @@ -90,6 +104,30 @@ alert(str1 == str2); // true Он также используется, если необходимо вставить в строку кавычку. К примеру: +======= +There are other, less common special characters: + +| Character | Description | +|-----------|-------------| +|`\n`|New line| +|`\r`|In Windows text files a combination of two characters `\r\n` represents a new break, while on non-Windows OS it's just `\n`. That's for historical reasons, most Windows software also understands `\n`. | +|`\'`, `\"`, \\`|Quotes| +|`\\`|Backslash| +|`\t`|Tab| +|`\b`, `\f`, `\v`| Backspace, Form Feed, Vertical Tab -- mentioned for completeness, coming from old times, not used nowadays (you can forget them right now). | + +As you can see, all special characters start with a backslash character `\`. It is also called an "escape character". + +Because it's so special, if we need to show an actual backslash `\` within the string, we need to double it: + +```js run +alert( `The backslash: \\` ); // The backslash: \ +``` + +So-called "escaped" quotes `\'`, `\"`, \\` are used to insert a quote into the same-quoted string. + +For instance: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run alert( 'I*!*\'*/!*m the Walrus!' ); // *!*I'm*/!* the Walrus! @@ -97,12 +135,17 @@ alert( 'I*!*\'*/!*m the Walrus!' ); // *!*I'm*/!* the Walrus! Здесь перед входящей в строку кавычкой необходимо добавить обратный слеш — `\'` — иначе она бы обозначала окончание строки. +<<<<<<< HEAD Разумеется, требование экранировать относится только к таким же кавычкам, как те, в которые заключена строка. Так что мы можем применить и более элегантное решение, использовав для этой строки двойные или обратные кавычки: +======= +Of course, only the quotes that are the same as the enclosing ones need to be escaped. So, as a more elegant solution, we could switch to double quotes or backticks instead: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run -alert( `I'm the Walrus!` ); // I'm the Walrus! +alert( "I'm the Walrus!" ); // I'm the Walrus! ``` +<<<<<<< HEAD Заметим, что обратный слеш `\` служит лишь для корректного прочтения строки интерпретатором, но он не записывается в строку после её прочтения. Когда строка сохраняется в оперативную память, в неё не добавляется символ `\`. Вы можете явно видеть это в выводах `alert` в примерах выше. Но что, если нам надо добавить в строку собственно сам обратный слеш `\`? @@ -112,6 +155,9 @@ alert( `I'm the Walrus!` ); // I'm the Walrus! ```js run alert( `The backslash: \\` ); // The backslash: \ ``` +======= +Besides these special characters, there's also a special notation for Unicode codes `\u…`, it's rarely used and is covered in the optional chapter about [Unicode](info:unicode). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## Длина строки @@ -126,12 +172,20 @@ alert( `My\n`.length ); // 3 ```warn header="`length` — это свойство" Бывает так, что люди с практикой в других языках случайно пытаются вызвать его, добавляя круглые скобки: они пишут `str.length()` вместо `str.length`. Это не работает. +<<<<<<< HEAD Так как `str.length` — это числовое свойство, а не функция, добавлять скобки не нужно. +======= +Please note that `str.length` is a numeric property, not a function. There is no need to add parenthesis after it. Not `.length()`, but `.length`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` ## Доступ к символам +<<<<<<< HEAD Получить символ, который занимает позицию `pos`, можно с помощью квадратных скобок: `[pos]`. Также можно использовать метод [str.at(pos)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/at). Первый символ занимает нулевую позицию: +======= +To get a character at position `pos`, use square brackets `[pos]` or call the method [str.at(pos)](mdn:js/String/at). The first character starts from the zero position: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let str = `Hello`; @@ -142,6 +196,7 @@ alert( str.at(0) ); // H // получаем последний символ alert( str[str.length - 1] ); // o +<<<<<<< HEAD alert( str.at(-1) ); // o ``` @@ -150,6 +205,16 @@ alert( str.at(-1) ); // o Таким образом, `.at(-1)` означает последний символ, а `.at(-2)` -- тот, что перед ним, и т.д. Квадратные скобки всегда возвращают `undefined` для отрицательных индексов. Например: +======= +alert( str.at(-1) ); +``` + +As you can see, the `.at(pos)` method has a benefit of allowing negative position. If `pos` is negative, then it's counted from the end of the string. + +So `.at(-1)` means the last character, and `.at(-2)` is the one before it, etc. + +The square brackets always return `undefined` for negative indexes, for instance: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let str = `Hello`; @@ -229,7 +294,11 @@ alert( str.indexOf('widget') ); // -1, совпадений нет, поиск alert( str.indexOf("id") ); // 1, подстрока "id" найдена на позиции 1 (..idget with id) ``` +<<<<<<< HEAD Необязательный второй аргумент позволяет начать поиск с определённой позиции. +======= +The optional second parameter allows us to start searching from a given position. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Например, первое вхождение `"id"` — на позиции `1`. Для того, чтобы найти следующее, начнём поиск с позиции `2`: @@ -300,6 +369,7 @@ if (str.indexOf("Widget") != -1) { } ``` +<<<<<<< HEAD #### Трюк с побитовым НЕ Существует старый трюк с использованием [побитового оператора НЕ](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_NOT) — `~`. Он преобразует число в 32-разрядное целое со знаком (signed 32-bit integer). Дробная часть, в случае, если она присутствует, отбрасывается. Затем все биты числа инвертируются. @@ -338,6 +408,8 @@ if (~str.indexOf("Widget")) { На данный момент такой трюк можно встретить только в старом коде, потому что в новом он просто не нужен: есть метод `.includes` (см. ниже). +======= +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ### includes, startsWith, endsWith Более современный метод [str.includes(substr, pos)](mdn:js/String/includes) возвращает `true`, если в строке `str` есть подстрока `substr`, либо `false`, если нет. @@ -360,8 +432,13 @@ alert( "Midget".includes("id", 3) ); // false, поиск начат с пози Методы [str.startsWith](mdn:js/String/startsWith) и [str.endsWith](mdn:js/String/endsWith) проверяют, соответственно, начинается ли и заканчивается ли строка определённой строкой: ```js run +<<<<<<< HEAD alert( "*!*Wid*/!*get".startsWith("Wid") ); // true, "Wid" — начало "Widget" alert( "Wid*!*get*/!*".endsWith("get") ); // true, "get" — окончание "Widget" +======= +alert( "*!*Wid*/!*get".startsWith("Wid") ); // true, "Widget" starts with "Wid" +alert( "Wid*!*get*/!*".endsWith("get") ); // true, "Widget" ends with "get" +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` ## Получение подстроки @@ -385,7 +462,11 @@ alert( "Wid*!*get*/!*".endsWith("get") ); // true, "get" — окончание ```js run let str = "st*!*ringify*/!*"; +<<<<<<< HEAD alert( str.slice(2) ); // ringify, с позиции 2 и до конца +======= + alert( str.slice(2) ); // 'ringify', from the 2nd position till the end +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` Также для `start/end` можно задавать отрицательные значения. Это означает, что позиция определена как заданное количество символов *с конца строки*: @@ -393,6 +474,7 @@ alert( "Wid*!*get*/!*".endsWith("get") ); // true, "get" — окончание ```js run let str = "strin*!*gif*/!*y"; +<<<<<<< HEAD // начинаем с позиции 4 справа, а заканчиваем на позиции 1 справа alert( str.slice(-4, -1) ); // gif ``` @@ -402,6 +484,16 @@ alert( "Wid*!*get*/!*".endsWith("get") ); // true, "get" — окончание Это — почти то же, что и `slice`, но можно задавать `start` больше `end`. Если `start` больше `end`, то метод `substring` сработает так, как если бы аргументы были поменяны местами. +======= + // start at the 4th position from the right, end at the 1st from the right + alert( str.slice(-4, -1) ); // 'gif' + ``` + +`str.substring(start [, end])` +: Returns the part of the string *between* `start` and `end` (not including `end`). + + This is almost the same as `slice`, but it allows `start` to be greater than `end` (in this case it simply swaps `start` and `end` values). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Например: @@ -427,32 +519,57 @@ alert( "Wid*!*get*/!*".endsWith("get") ); // true, "get" — окончание ```js run let str = "st*!*ring*/!*ify"; +<<<<<<< HEAD // ring, получаем 4 символа, начиная с позиции 2 alert( str.substr(2, 4) ); +======= + alert( str.substr(2, 4) ); // 'ring', from the 2nd position get 4 characters +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` Значение первого аргумента может быть отрицательным, тогда позиция определяется с конца: ```js run let str = "strin*!*gi*/!*fy"; +<<<<<<< HEAD // gi, получаем 2 символа, начиная с позиции 4 с конца строки alert( str.substr(-4, 2) ); ``` Этот метод находится в [Annex B](https://tc39.es/ecma262/#sec-string.prototype.substr) спецификации языка. Это означает, что его должны поддерживать только браузерные движки JavaScript, и использовать его не рекомендуется. Но на практике он поддерживается везде. +======= + alert( str.substr(-4, 2) ); // 'gi', from the 4th position get 2 characters + ``` + + This method resides in the [Annex B](https://tc39.es/ecma262/#sec-string.prototype.substr) of the language specification. It means that only browser-hosted Javascript engines should support it, and it's not recommended to use it. In practice, it's supported everywhere. + +Let's recap these methods to avoid any confusion: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Давайте подытожим, как работают эти методы, чтобы не запутаться: | метод | выбирает… | отрицательные значения | |--------|-----------|-----------| +<<<<<<< HEAD | `slice(start, end)` | от `start` до `end` (не включая `end`) | можно передавать отрицательные значения | | `substring(start, end)` | между `start` и `end` (не включая `end`) | отрицательные значения равнозначны `0` | | `substr(start, length)` | `length` символов, начиная от `start` | значение `start` может быть отрицательным | +======= +| `slice(start, end)` | from `start` to `end` (not including `end`) | allows negatives | +| `substring(start, end)` | between `start` and `end` (not including `end`)| negative values mean `0` | +| `substr(start, length)` | from `start` get `length` characters | allows negative `start` | +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```smart header="Какой метод выбрать?" Все эти методы эффективно выполняют задачу. Формально у метода `substr` есть небольшой недостаток: он описан не в собственно спецификации JavaScript, а в приложении к ней — Annex B. Это приложение описывает возможности языка для использования в браузерах, существующие в основном по историческим причинам. Таким образом, в другом окружении, отличном от браузера, он может не поддерживаться. Однако на практике он работает везде. +<<<<<<< HEAD Из двух других вариантов, `slice` более гибок, он поддерживает отрицательные аргументы, и его короче писать. Так что, в принципе, можно запомнить только его. +======= +Of the other two variants, `slice` is a little bit more flexible, it allows negative arguments and shorter to write. + +So, for practical use it's enough to remember only `slice`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` ## Сравнение строк @@ -475,6 +592,7 @@ alert( "Wid*!*get*/!*".endsWith("get") ); // true, "get" — окончание Это может привести к своеобразным результатам при сортировке названий стран: нормально было бы ожидать, что `Zealand` будет после `Österreich` в списке. +<<<<<<< HEAD Чтобы разобраться, что происходит, давайте ознакомимся с внутренним представлением строк в JavaScript. Строки кодируются в [UTF-16](https://ru.wikipedia.org/wiki/UTF-16). Таким образом, у любого символа есть соответствующий код. Есть специальные методы, позволяющие получить символ по его коду и наоборот. @@ -486,7 +604,20 @@ alert( "Wid*!*get*/!*".endsWith("get") ); // true, "get" — окончание // одна и та же буква в нижнем и верхнем регистре // будет иметь разные коды alert( "z".codePointAt(0) ); // 122 +======= +To understand what happens, we should be aware that strings in Javascript 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: + +`str.codePointAt(pos)` +: Returns a decimal number representing the code for the character at position `pos`: + + ```js run + // different case letters have different codes +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 alert( "Z".codePointAt(0) ); // 90 + alert( "z".codePointAt(0) ); // 122 + alert( "z".codePointAt(0).toString(16) ); // 7a (if we need a hexadecimal value) ``` `String.fromCodePoint(code)` @@ -494,9 +625,16 @@ alert( "Wid*!*get*/!*".endsWith("get") ); // true, "get" — окончание ```js run alert( String.fromCodePoint(90) ); // Z +<<<<<<< HEAD ``` Давайте сделаем строку, содержащую символы с кодами от `65` до `220` — это латиница и ещё некоторые распространённые символы: +======= + alert( String.fromCodePoint(0x5a) ); // Z (we can also use a hex value as an argument) + ``` + +Now let's see the characters with codes `65..220` (the latin alphabet and a little bit extra) by making a string of them: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let str = ''; @@ -505,6 +643,7 @@ for (let i = 65; i <= 220; i++) { str += String.fromCodePoint(i); } alert( str ); +// Output: // ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~€‚ƒ„ // ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜ ``` @@ -515,16 +654,27 @@ alert( str ); Символы сравниваются по их кодам. Больший код — больший символ. Код `a` (97) больше кода `Z` (90). +<<<<<<< HEAD - Все строчные буквы идут после заглавных, так как их коды больше. - Некоторые буквы, такие как `Ö`, вообще находятся вне основного алфавита. У этой буквы код больше, чем у любой буквы от `a` до `z`. ### Правильное сравнение [#correct-comparisons] +======= +- All lowercase letters go after uppercase letters because their codes are greater. +- Some letters like `Ö` stand apart from the main alphabet. Here, its code is greater than anything from `a` to `z`. + +### Correct comparisons [#correct-comparisons] +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 "Правильный" алгоритм сравнения строк сложнее, чем может показаться, так как разные языки используют разные алфавиты. Поэтому браузеру нужно знать, какой язык использовать для сравнения. +<<<<<<< HEAD К счастью, все современные браузеры (для IE10− нужна дополнительная библиотека [Intl.JS](https://github.com/andyearnshaw/Intl.js/)) поддерживают стандарт [ECMA 402](https://www.ecma-international.org/ecma-402/1.0/ECMA-402.pdf), обеспечивающий правильное сравнение строк на разных языках с учётом их правил. +======= +Luckily, modern browsers support the internationalization standard [ECMA-402](https://www.ecma-international.org/publications-and-standards/standards/ecma-402/). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Для этого есть соответствующий метод. @@ -542,6 +692,7 @@ alert( 'Österreich'.localeCompare('Zealand') ); // -1 У этого метода есть два дополнительных аргумента, которые указаны в [документации](mdn:js/String/localeCompare). Первый позволяет указать язык (по умолчанию берётся из окружения) — от него зависит порядок букв. Второй — определить дополнительные правила, такие как чувствительность к регистру, а также следует ли учитывать различия между `"a"` и `"á"`. +<<<<<<< HEAD ## Итого - Есть три типа кавычек. Строки, использующие обратные кавычки, могут занимать более одной строки в коде и включать выражения `${…}`. @@ -562,3 +713,24 @@ alert( 'Österreich'.localeCompare('Zealand') ); // -1 Для строк предусмотрены методы для поиска и замены с использованием регулярных выражений. Но это отдельная большая тема, поэтому ей посвящена отдельная глава учебника . Также, на данный момент важно знать, что строки основаны на кодировке Юникод, и поэтому иногда могут возникать проблемы со сравнениями. Подробнее о Юникоде в главе . +======= +## Summary + +- There are 3 types of quotes. Backticks allow a string to span multiple lines and embed expressions `${…}`. +- We can use special characters, such as a line break `\n`. +- To get a character, use: `[]` or `at` method. +- 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()` -- removes ("trims") spaces from the beginning and end of the string. +- `str.repeat(n)` -- repeats the string `n` times. +- ...and more to be found in the [manual](mdn:js/String). + +Strings also have methods for doing search/replace with regular expressions. But that's big topic, so it's explained in a separate tutorial section . + +Also, as of now it's important to know that strings are based on Unicode encoding, and hence there're issues with comparisons. There's more about Unicode in the chapter . +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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 3abf9e3a72..5e700c98e9 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 @@ -57,9 +57,15 @@ alert( getMaxSubSum([1, 2, 3]) ); // 6 alert( getMaxSubSum([100, -9, 2, -3, 5]) ); // 100 ``` +<<<<<<< HEAD Это решение имеет [оценку сложности](https://ru.wikipedia.org/wiki/«O»_большое_и_«o»_малое) O(n2). Другими словами, если мы увеличим размер массива в 2 раза, время выполнения алгоритма увеличится в 4 раза. Для больших массивов(1000, 10000 или больше элементов) такие алгоритмы могут приводить к серьёзным "тормозам". +======= +The solution has a time complexity 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. + +For big arrays (1000, 10000 or more items) such algorithms can lead to serious sluggishness. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 # Быстрое решение @@ -91,4 +97,8 @@ alert( getMaxSubSum([-1, -2, -3]) ); // 0 Этот алгоритм требует ровно 1 проход по массиву и его оценка сложности O(n). +<<<<<<< HEAD Больше информации об алгоритме тут: [Задача поиска максимальной суммы подмассива](https://en.wikipedia.org/wiki/Maximum_subarray_problem). Если всё ещё не очевидно как это работает, просмотрите алгоритм в примерах выше, это будет лучше всяких слов. +======= +You can find more detailed 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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 788913ec45..2f10cf31d5 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 @@ -10,15 +10,26 @@ importance: 2 Функция `getMaxSubSum(arr)` должна возвращать эту сумму. +<<<<<<< HEAD Например: ```js getMaxSubSum([-1, *!*2, 3*/!*, -9]) == 5 (сумма выделенных элементов) +======= +For instance: + +```js +getMaxSubSum([-1, *!*2, 3*/!*, -9]) == 5 (the sum of highlighted items) +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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 +<<<<<<< HEAD getMaxSubSum([*!*1, 2, 3*/!*]) == 6 (берём все) +======= +getMaxSubSum([*!*1, 2, 3*/!*]) == 6 (take all) +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` Если все элементы отрицательные - ничего не берём(подмассив пустой) и сумма равна "0": 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 630f777a1b..63f74c6f9f 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 @@ -6,11 +6,19 @@ importance: 5 Давайте произведём 5 операций с массивом. +<<<<<<< HEAD 1. Создайте массив `styles` с элементами "Джаз" и "Блюз". 2. Добавьте "Рок-н-ролл" в конец. 3. Замените значение в середине на "Классика". Ваш код для поиска значения в середине должен работать для массивов с любой длиной. 4. Удалите первый элемент массива и покажите его. 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 with "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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Массив по ходу выполнения операций: diff --git a/1-js/05-data-types/04-array/article.md b/1-js/05-data-types/04-array/article.md index 0229729c0c..0ec49f6e9f 100644 --- a/1-js/05-data-types/04-array/article.md +++ b/1-js/05-data-types/04-array/article.md @@ -91,7 +91,42 @@ let fruits = [ "Висячая запятая" упрощает процесс добавления/удаления элементов, так как все строки становятся идентичными. ```` +<<<<<<< HEAD ## Получение последних элементов при помощи "at" +======= +## Get last elements with "at" + +[recent browser="new"] + +Let's say we want the last element of the array. + +Some programming languages allow the use of negative indexes for the same purpose, like `fruits[-1]`. + +Although, in JavaScript it won't work. The result will be `undefined`, because the index in square brackets is treated literally. + +We can explicitly calculate the last element index and then access it: `fruits[fruits.length - 1]`. + +```js run +let fruits = ["Apple", "Orange", "Plum"]; + +alert( fruits[fruits.length-1] ); // Plum +``` + +A bit cumbersome, isn't it? We need to write the variable name twice. + +Luckily, there's a shorter syntax: `fruits.at(-1)`: + +```js run +let fruits = ["Apple", "Orange", "Plum"]; + +// same as fruits[fruits.length-1] +alert( fruits.at(-1) ); // Plum +``` + +In other words, `arr.at(i)`: +- is exactly the same as `arr[i]`, if `i >= 0`. +- for negative values of `i`, it steps back from the end of the array. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 [recent browser="new"] @@ -151,9 +186,17 @@ alert( fruits.at(-1) ); // Plum Массивы в JavaScript могут работать и как очередь, и как стек. Мы можем добавлять/удалять элементы как в начало, так и в конец массива. +<<<<<<< HEAD В компьютерных науках структура данных, делающая это возможным, называется [двусторонняя очередь](https://ru.wikipedia.org/wiki/Двухсторонняя_очередь). **Методы, работающие с концом массива:** +======= +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 this, is called [deque](https://en.wikipedia.org/wiki/Double-ended_queue). + +**Methods that work with the end of the array:** +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 `pop` : Удаляет последний элемент из массива и возвращает его: @@ -166,7 +209,11 @@ alert( fruits.at(-1) ); // Plum alert( fruits ); // Яблоко, Апельсин ``` +<<<<<<< HEAD И `fruits.pop()` и `fruits.at(-1)` возвращают последний элемент массива, но `fruits.pop()` также изменяет массив, удаляя его. +======= + Both `fruits.pop()` and `fruits.at(-1)` return the last element of the array, but `fruits.pop()` also modifies the array by removing it. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 `push` : Добавляет элемент в конец массива: @@ -187,7 +234,11 @@ alert( fruits.at(-1) ); // Plum : Удаляет из массива первый элемент и возвращает его: ```js run +<<<<<<< HEAD let fruits = ["Яблоко", "Апельсин", "Груша"]; +======= + let fruits = ["Apple", "Orange", "Pear"]; +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 alert( fruits.shift() ); // удаляем Яблоко и выводим его @@ -198,7 +249,11 @@ alert( fruits.at(-1) ); // Plum : Добавляет элемент в начало массива: ```js run +<<<<<<< HEAD let fruits = ["Апельсин", "Груша"]; +======= + let fruits = ["Orange", "Pear"]; +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 fruits.unshift('Яблоко'); @@ -223,7 +278,11 @@ alert( fruits ); Массивы расширяют объекты, так как предусматривают специальные методы для работы с упорядоченными коллекциями данных, а также свойство `length`. Но в основе всё равно лежит объект. +<<<<<<< HEAD Следует помнить, что в JavaScript существует 8 основных типов данных. Массив является объектом и, следовательно, ведёт себя как объект. +======= +Remember, there are only eight basic data types in JavaScript (see the [Data types](info:types) chapter for more info). Array is an object and thus behaves like an object. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Например, копируется по ссылке: @@ -239,7 +298,11 @@ arr.push("Груша"); // массив меняется по ссылке alert( fruits ); // Банан, Груша - теперь два элемента ``` +<<<<<<< HEAD ...Но то, что действительно делает массивы особенными - это их внутреннее представление. Движок JavaScript старается хранить элементы массива в непрерывной области памяти, один за другим, так, как это показано на иллюстрациях к этой главе. Существуют и другие способы оптимизации, благодаря которым массивы работают очень быстро. +======= +...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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Но все они утратят эффективность, если мы перестанем работать с массивом как с "упорядоченной коллекцией данных" и начнём использовать его как обычный объект. @@ -277,7 +340,11 @@ fruits.age = 25; // создаём свойство с произвольным fruits.shift(); // удаляем первый элемент с начала ``` +<<<<<<< HEAD Просто взять и удалить элемент с номером `0` недостаточно. Нужно также заново пронумеровать остальные элементы. +======= +It's not enough to take and remove the element with the index `0`. Other elements need to be renumbered as well. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Операция `shift` должна выполнить 3 действия: @@ -394,11 +461,19 @@ alert( arr[3] ); // undefined: значения не восстановилис let arr = *!*new Array*/!*("Яблоко", "Груша", "и тд"); ``` +<<<<<<< HEAD Он редко применяется, так как квадратные скобки `[]` короче. Кроме того, у него есть хитрая особенность. +======= +It's rarely used, because square brackets `[]` are shorter. Also, there's a tricky feature with it. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Если `new Array` вызывается с одним аргументом, который представляет собой число, он создаёт массив *без элементов, но с заданной длиной*. +<<<<<<< HEAD Давайте посмотрим, как можно оказать себе медвежью услугу: +======= +Let's see how one can shoot themselves in the foot: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let arr = new Array(2); // создастся ли массив [2]? @@ -408,9 +483,13 @@ alert( arr[0] ); // undefined! нет элементов. alert( arr.length ); // length 2 ``` +<<<<<<< HEAD Как мы видим, в коде, представленном выше, в `new Array(number)` все элементы равны `undefined`. Чтобы избежать появления таких неожиданных ситуаций, мы обычно используем квадратные скобки, если, конечно, не знаем точно, что по какой-то причине нужен именно `Array`. +======= +To avoid such surprises, we usually use square brackets, unless we really know what we're doing. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## Многомерные массивы @@ -423,7 +502,11 @@ let matrix = [ [7, 8, 9] ]; +<<<<<<< HEAD alert( matrix[1][1] ); // 5, центральный элемент +======= +alert( matrix[0][1] ); // 2, the second value of the first inner array +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` ## toString @@ -458,10 +541,62 @@ alert( "1" + 1 ); // "11" alert( "1,2" + 1 ); // "1,21" ``` +<<<<<<< HEAD ## Не сравнивайте массивы при помощи == +======= +## Don't compare arrays with == + +Arrays in JavaScript, unlike some other programming languages, shouldn't be compared with operator `==`. + +This operator has no special treatment for arrays, it works with them as with any objects. + +Let's recall the rules: + +- Two objects are equal `==` only if they're references to the same object. +- If one of the arguments of `==` is an object, and the other one is a primitive, then the object gets converted to primitive, as explained in the chapter . +- ...With an exception of `null` and `undefined` that equal `==` each other and nothing else. + +The strict comparison `===` is even simpler, as it doesn't convert types. + +So, if we compare arrays with `==`, they are never the same, unless we compare two variables that reference exactly the same array. + +For example: +```js run +alert( [] == [] ); // false +alert( [0] == [0] ); // false +``` + +These arrays are technically different objects. So they aren't equal. The `==` operator doesn't do item-by-item comparison. + +Comparison with primitives may give seemingly strange results as well: + +```js run +alert( 0 == [] ); // true + +alert('0' == [] ); // false +``` + +Here, in both cases, we compare a primitive with an array object. So the array `[]` gets converted to primitive for the purpose of comparison and becomes an empty string `''`. + +Then the comparison process goes on with the primitives, as described in the chapter : + +```js run +// after [] was converted to '' +alert( 0 == '' ); // true, as '' becomes converted to number 0 + +alert('0' == '' ); // false, no type conversion, different strings +``` + +So, how to compare arrays? + +That's simple: don't use the `==` operator. Instead, compare them item-by-item in a loop or using iteration methods explained in the next chapter. + +## Summary +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 В JavaScript, в отличие от некоторых других языков программирования, массивы не следует сравнивать при помощи оператора `==`. +<<<<<<< HEAD У этого оператора нет специального подхода к массивам, он работает с ними, как и с любыми другими объектами. Давайте ещё раз напомним правила: @@ -471,19 +606,42 @@ alert( "1,2" + 1 ); // "1,21" - ...За исключением `null` и `undefined`, которые равны `==` друг другу и ничему больше. Оператор строгого равенства `===` ещё проще, так как он не преобразует типы. +======= +The declaration: + +```js +// square brackets (usual) +let arr = [item1, item2...]; + +// new Array (exceptionally rare) +let arr = new Array(item1, item2...); +``` + +The call to `new Array(number)` creates an array with the given length, but without elements. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Итак, если мы всё же сравниваем массивы с помощью `==`, то они никогда не будут одинаковыми, если только мы не сравним две переменные, которые ссылаются на один и тот же массив +<<<<<<< HEAD Например: ```js run alert( [] == [] ); // false alert( [0] == [0] ); // false ``` +======= +Getting the elements: + +- we can get element by its index, like `arr[0]` +- also we can use `at(i)` method that allows negative indexes. For negative values of `i`, it steps back from the end of the array. If `i >= 0`, it works same as `arr[i]`. + +We can use an array as a deque with the following operations: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Технически эти массивы являются разными объектами. Так что они не равны. Оператор `==` не выполняет поэлементное сравнение. Сравнение с примитивами также может дать, казалось бы, странные результаты: +<<<<<<< HEAD ```js run alert( 0 == [] ); // true @@ -543,3 +701,10 @@ let arr = new Array(item1, item2...); - `for (let i in arr)` -- никогда не используйте для массивов! Мы вернёмся к массивам и изучим другие методы добавления, удаления, выделения элементов и сортировки массивов в главе: . +======= +To compare arrays, don't use the `==` operator (as well as `>`, `<` and others), as they have no special treatment for arrays. They handle them as any objects, and it's not what we usually want. + +Instead you can use `for..of` loop to compare arrays item-by-item. + +We will continue with arrays and study more methods to add, remove, extract elements and sort arrays in the next chapter . +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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 index 9164e39e33..25b300cce4 100644 --- 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 @@ -2,6 +2,7 @@ importance: 4 --- +<<<<<<< HEAD # Создайте объект с ключами из массива Допустим, мы получили массив пользователей в виде `{id:..., name:..., age:... }`. @@ -9,6 +10,15 @@ importance: 4 Создайте функцию `groupById(arr)`, которая создаст из него объект с `id` в качестве ключа и элементами массива в качестве значений. Например: +======= +# Create keyed object from array + +Let's say we received an array of users in the form `{id:..., name:..., age:... }`. + +Create a function `groupById(arr)` that creates an object from it, with `id` as the key, and array items as values. + +For example: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js let users = [ @@ -20,7 +30,11 @@ let users = [ let usersById = groupById(users); /* +<<<<<<< HEAD после вызова у нас должно получиться: +======= +// after the call we should have: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 usersById = { john: {id: 'john', name: "John Smith", age: 20}, @@ -30,8 +44,16 @@ usersById = { */ ``` +<<<<<<< HEAD Такая функция очень удобна при работе с данными, которые приходят с сервера. В этой задаче мы предполагаем, что `id` уникален. Не может быть двух элементов массива с одинаковым `id`. Используйте метод `.reduce` в решении. +======= +Such function is really handy when working with server data. + +In this task we assume that `id` is unique. There may be no two array items with the same `id`. + +Please use array `.reduce` method in the solution. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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 3fb9dd3be9..6cf1f0a083 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 @@ -4,7 +4,11 @@ importance: 4 # Фильтрация по диапазону +<<<<<<< HEAD Напишите функцию `filterRange(arr, a, b)`, которая принимает массив `arr`, ищет элементы со значениями больше или равными `a` и меньше или равными `b` и возвращает результат в виде массива. +======= +Write a function `filterRange(arr, a, b)` that gets an array `arr`, looks for elements with values higher or equal to `a` and lower or equal to `b` and return a result as an array. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Функция должна возвращать новый массив и не изменять исходный. 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 382c207219..858ad05d81 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,20 @@ importance: 4 --- +<<<<<<< HEAD # Сортировать в порядке по убыванию +======= +# Sort in decreasing order +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js let arr = [5, 2, 1, -10, 8]; +<<<<<<< HEAD // ... ваш код для сортировки по убыванию +======= +// ... your code to sort it in decreasing order +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 alert( arr ); // 8, 5, 2, 1, -10 ``` 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 45ef1619da..f62452a5f7 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 @@ -10,14 +10,14 @@ function Calculator() { let split = str.split(' '), a = +split[0], op = split[1], - b = +split[2] + b = +split[2]; if (!this.methods[op] || isNaN(a) || isNaN(b)) { return NaN; } return this.methods[op](a, b); - } + }; this.addMethod = function(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 85c8fd622c..d7bb804b38 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,8 @@ +<<<<<<< HEAD - Обратите внимание, как хранятся методы. Они просто добавляются к внутреннему объекту. - Все тесты и числовые преобразования выполняются в методе `calculate`. В будущем он может быть расширен для поддержки более сложных выражений. +======= +- Please note how methods are stored. They are simply added to `this.methods` property. +- All tests and numeric conversions are done in the `calculate` method. In future it may be extended to support more complex expressions. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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 b5dadd5e65..8e11cafd18 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 @@ -25,7 +25,11 @@ alert( usersMapped[0].id ); // 1 alert( usersMapped[0].fullName ); // Вася Пупкин ``` +<<<<<<< HEAD Обратите внимание, что для стрелочных функций мы должны использовать дополнительные скобки. +======= +Please note that in the arrow functions we need to use additional brackets. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Мы не можем написать вот так: ```js 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 5d1354e183..f45539c5b2 100644 --- a/1-js/05-data-types/05-array-methods/article.md +++ b/1-js/05-data-types/05-array-methods/article.md @@ -1,6 +1,10 @@ # Методы массивов +<<<<<<< HEAD Массивы предоставляют множество методов. Чтобы было проще, в этой главе они разбиты на группы. +======= +Arrays provide a lot of methods. To make things easier, in this chapter, they are split into groups. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## Добавление/удаление элементов @@ -32,11 +36,19 @@ alert( arr.length ); // 3 Элемент был удалён, но в массиве всё ещё три элемента, мы можем увидеть, что `arr.length == 3`. +<<<<<<< HEAD Это естественно, потому что `delete obj.key` удаляет значение по ключу `key`. Это всё, что он делает. Хорошо для объектов. Но для массивов мы обычно хотим, чтобы оставшиеся элементы сдвинулись и заняли освободившееся место. Мы ждём, что массив станет короче. +======= +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 the elements to shift and occupy the freed place. We expect to have a shorter array now. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Поэтому нужно использовать специальные методы. +<<<<<<< HEAD Метод [arr.splice](mdn:js/Array/splice) – это универсальный «швейцарский нож» для работы с массивами. Умеет всё: добавлять, удалять и заменять элементы. +======= +The [arr.splice](mdn:js/Array/splice) method is a Swiss army knife for arrays. It can do everything: insert, remove and replace elements. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Синтаксис: @@ -44,7 +56,11 @@ alert( arr.length ); // 3 arr.splice(start[, deleteCount, elem1, ..., elemN]) ``` +<<<<<<< HEAD Он изменяет `arr` начиная с индекса `start`: удаляет `deleteCount` элементов и затем вставляет `elem1, ..., elemN` на их место. Возвращает массив из удалённых элементов. +======= +It modifies `arr` starting from the index `start`: removes `deleteCount` elements and then inserts `elem1, ..., elemN` at their place. Returns the array of removed elements. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Этот метод легко понять, рассмотрев примеры. @@ -62,7 +78,11 @@ alert( arr ); // осталось ["Я", "JavaScript"] Легко, правда? Начиная с индекса `1`, он убрал `1` элемент. +<<<<<<< HEAD В следующем примере мы удалим 3 элемента и заменим их двумя другими. +======= +In the next example, we remove 3 elements and replace them with the other two: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let arr = [*!*"Я", "изучаю", "JavaScript",*/!* "прямо", "сейчас"]; @@ -84,7 +104,11 @@ let removed = arr.splice(0, 2); alert( removed ); // "Я", "изучаю" <-- массив из удалённых элементов ``` +<<<<<<< HEAD Метод `splice` также может вставлять элементы без удаления, для этого достаточно установить `deleteCount` в `0`: +======= +The `splice` method is also able to insert the elements without any removals. For that, we need to set `deleteCount` to `0`: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let arr = ["Я", "изучаю", "JavaScript"]; @@ -114,7 +138,11 @@ alert( arr ); // 1,2,3,4,5 ### slice +<<<<<<< HEAD Метод [arr.slice](mdn:js/Array/slice) намного проще, чем похожий на него `arr.splice`. +======= +The method [arr.slice](mdn:js/Array/slice) is much simpler than the similar-looking `arr.splice`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Синтаксис: @@ -124,7 +152,11 @@ arr.slice([start], [end]) Он возвращает новый массив, в который копирует все элементы с индекса `start` до `end` (не включая `end`). `start` и `end` могут быть отрицательными, в этом случае отсчёт позиции будет вестись с конца массива. +<<<<<<< HEAD Это похоже на строковый метод `str.slice`, но вместо подстрок возвращает подмассивы. +======= +It's similar to a string method `str.slice`, but instead of substrings, it makes subarrays. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Например: @@ -159,6 +191,7 @@ arr.concat(arg1, arg2...) ```js run let arr = [1, 2]; +<<<<<<< HEAD // создать массив из: arr и [3,4] alert( arr.concat([3, 4]) ); // 1,2,3,4 @@ -166,6 +199,15 @@ alert( arr.concat([3, 4]) ); // 1,2,3,4 alert( arr.concat([3, 4], [5, 6]) ); // 1,2,3,4,5,6 // создать массив из: arr и [3,4], потом добавить значения 5 и 6 +======= +// create an array from: arr and [3,4] +alert( arr.concat([3, 4]) ); // 1,2,3,4 + +// create an array from: arr and [3,4] and [5,6] +alert( arr.concat([3, 4], [5, 6]) ); // 1,2,3,4,5,6 + +// create an array from: arr and [3,4], then add values 5 and 6 +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 alert( arr.concat([3, 4], 5, 6) ); // 1,2,3,4,5,6 ``` @@ -206,7 +248,11 @@ alert( arr.concat(arrayLike) ); // 1,2,что-то,ещё Синтаксис: ```js arr.forEach(function(item, index, array) { +<<<<<<< HEAD // ... делать что-то с item +======= + // ... do something with an item +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 }); ``` @@ -234,6 +280,7 @@ arr.forEach(function(item, index, array) { ### indexOf/lastIndexOf и includes +<<<<<<< HEAD У методов [arr.indexOf](mdn:js/Array/indexOf) и [arr.includes](mdn:js/Array/includes) одинаковый синтаксис и они делают по сути то же самое, что и их строковые аналоги, но работают с элементами вместо символов: - `arr.indexOf(item, from)` ищет `item` начиная с индекса `from` и возвращает номер индекса, на котором был найден искомый элемент, в противном случае `-1`. @@ -242,6 +289,16 @@ arr.forEach(function(item, index, array) { Обычно эти методы используются только с одним аргументом: искомым `item`. По умолчанию поиск ведется с начала. Например: +======= +The methods [arr.indexOf](mdn:js/Array/indexOf) and [arr.includes](mdn:js/Array/includes) have the similar syntax and do essentially the same as their string counterparts, but operate on items instead of characters: + +- `arr.indexOf(item, from)` -- looks for `item` starting from index `from`, and returns the index where it was found, otherwise `-1`. +- `arr.includes(item, from)` -- looks for `item` starting from index `from`, returns `true` if found. + +Usually, these methods are used with only one argument: the `item` to search. By default, the search is from the beginning. + +For instance: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let arr = [1, 0, false]; @@ -253,6 +310,7 @@ alert( arr.indexOf(null) ); // -1 alert( arr.includes(1) ); // true ``` +<<<<<<< HEAD Пожалуйста, обратите внимание, что методы используют строгое сравнение `===`. Таким образом, если мы ищем `false`, он находит именно `false`, а не ноль. Если мы хотим проверить наличие элемента в массиве и нет необходимости знать его индекс, предпочтительно использовать `arr.includes`. @@ -282,6 +340,37 @@ alert( arr.includes(NaN) );// true (верно) Представьте, что у нас есть массив объектов. Как нам найти объект с определённым условием? Здесь пригодится метод [arr.find](mdn:js/Array/find). +======= +Please note that `indexOf` uses the strict equality `===` for comparison. So, if we look for `false`, it finds exactly `false` and not the zero. + +If we want to check if `item` exists in the array and don't need the index, then `arr.includes` is preferred. + +The method [arr.lastIndexOf](mdn:js/Array/lastIndexOf) is the same as `indexOf`, but looks for from right to left. + +```js run +let fruits = ['Apple', 'Orange', 'Apple'] + +alert( fruits.indexOf('Apple') ); // 0 (first Apple) +alert( fruits.lastIndexOf('Apple') ); // 2 (last Apple) +``` + +````smart header="The `includes` method handles `NaN` correctly" +A minor, but noteworthy feature of `includes` is that it correctly handles `NaN`, unlike `indexOf`: + +```js run +const arr = [NaN]; +alert( arr.indexOf(NaN) ); // -1 (wrong, should be 0) +alert( arr.includes(NaN) );// true (correct) +``` +That's because `includes` was added to JavaScript much later and uses the more up-to-date comparison algorithm internally. +```` + +### find and findIndex/findLastIndex + +Imagine we have an array of objects. How do we find an object with a specific condition? + +Here the [arr.find(fn)](mdn:js/Array/find) method comes in handy. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Синтаксис: ```js @@ -297,7 +386,11 @@ let result = arr.find(function(item, index, array) { - `index` - его индекс. - `array` - сам массив. +<<<<<<< HEAD Если функция возвращает `true`, поиск прерывается и возвращается `item`. Если ничего не найдено, возвращается `undefined`. +======= +If it returns `true`, the search is stopped, the `item` is returned. If nothing is found, `undefined` is returned. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Например, у нас есть массив пользователей, каждый из которых имеет поля `id` и `name`. Найдем пользователя с `id == 1`: @@ -313,11 +406,38 @@ let user = users.find(item => item.id == 1); alert(user.name); // Вася ``` +<<<<<<< HEAD В реальной жизни массивы объектов -- обычное дело, поэтому метод `find` крайне полезен. +======= +In real life, arrays of objects are a common thing, so the `find` method is very useful. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Обратите внимание, что в данном примере мы передаём `find` функцию `item => item.id == 1` с одним аргументом. Это типично, другие аргументы этой функции используются редко. +<<<<<<< HEAD У метода [arr.findIndex](mdn:js/Array/findIndex) такой же синтаксис, но он возвращает индекс, на котором был найден элемент, а не сам элемент. Значение `-1` возвращается, если ничего не найдено. +======= +The [arr.findIndex](mdn:js/Array/findIndex) method has the same syntax but returns the index where the element was found instead of the element itself. The value of `-1` is returned if nothing is found. + +The [arr.findLastIndex](mdn:js/Array/findLastIndex) method is like `findIndex`, but searches from right to left, similar to `lastIndexOf`. + +Here's an example: + +```js run +let users = [ + {id: 1, name: "John"}, + {id: 2, name: "Pete"}, + {id: 3, name: "Mary"}, + {id: 4, name: "John"} +]; + +// Find the index of the first John +alert(users.findIndex(user => user.name == 'John')); // 0 + +// Find the index of the last John +alert(users.findLastIndex(user => user.name == 'John')); // 3 +``` +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Метод [arr.findLastIndex](mdn:js/Array/findLastIndex) похож на `findIndex`, но ищет справа налево, наподобие `lastIndexOf`. @@ -416,12 +536,21 @@ alert( arr ); // *!*1, 15, 2*/!* **По умолчанию элементы сортируются как строки.** +<<<<<<< HEAD Буквально, элементы преобразуются в строки при сравнении. Для строк применяется лексикографический порядок, и действительно выходит, что `"2" > "15"`. +======= +Literally, all elements are converted to strings for comparisons. For strings, lexicographic ordering is applied and indeed `"2" > "15"`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Чтобы использовать наш собственный порядок сортировки, нам нужно предоставить функцию в качестве аргумента `arr.sort()`. Функция должна для пары значений возвращать: +<<<<<<< HEAD +======= +The function should compare two arbitrary values and return: + +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js function compare(a, b) { if (a > b) return 1; // если первое значение больше второго @@ -450,11 +579,19 @@ alert(arr); // *!*1, 2, 15*/!* Теперь всё работает как надо. +<<<<<<< HEAD Сделаем отступление и подумаем, что происходит. `arr` может быть массивом чего угодно, верно? Он может содержать числа, строки, объекты или что-то ещё. У нас есть набор *каких-то элементов*. Чтобы отсортировать его, нам нужна *упорядочивающая функция*, которая знает, как сравнивать его элементы. По умолчанию элементы сортируются как строки. Метод `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`, которая делает сравнение. Кстати, если мы когда-нибудь захотим узнать, какие элементы сравниваются -- ничто не мешает нам вывести их на экран: +======= +Let's step aside and think about what's happening. The `arr` can be an array of anything, right? It may contain numbers or strings or objects or whatever. We have a set of *some items*. To sort it, we need an *ordering function* that knows how to compare its elements. The default is a string order. + +The `arr.sort(fn)` method implements a generic sorting algorithm. We don't need to care how it internally works (an optimized [quicksort](https://en.wikipedia.org/wiki/Quicksort) or [Timsort](https://en.wikipedia.org/wiki/Timsort) 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. + +By the way, if we ever want to know which elements are compared -- nothing prevents us from alerting them: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run [1, -2, 15, 2, 0, 8].sort(function(a, b) { @@ -465,10 +602,17 @@ alert(arr); // *!*1, 2, 15*/!* В процессе работы алгоритм может сравнивать элемент со множеством других, но он старается сделать как можно меньше сравнений. +<<<<<<< HEAD ````smart header="Функция сравнения может вернуть любое число" На самом деле от функции сравнения требуется любое положительное число, чтобы сказать «больше», и отрицательное число, чтобы сказать «меньше». Это позволяет писать более короткие функции: +======= +````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". + +That allows to write shorter functions: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let arr = [ 1, 2, 15 ]; @@ -479,8 +623,13 @@ alert(arr); // *!*1, 2, 15*/!* ``` ```` +<<<<<<< HEAD ````smart header="Лучше использовать стрелочные функции" Помните [стрелочные функции](info:arrow-functions-basics)? Можно использовать их здесь для того, чтобы сортировка выглядела более аккуратной: +======= +````smart header="Arrow functions for the best" +Remember [arrow functions](info:arrow-functions-basics)? We can use them here for neater sorting: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js arr.sort( (a, b) => a - b ); @@ -505,6 +654,22 @@ alert( countries.sort( (a, b) => a.localeCompare(b) ) ); // Andorra,Österreich, ``` ```` +````smart header="Use `localeCompare` for strings" +Remember [strings](info:string#correct-comparisons) comparison algorithm? It compares letters by their codes by default. + +For many alphabets, it's better to use `str.localeCompare` method to correctly sort letters, such as `Ö`. + +For example, let's sort a few countries in German: + +```js run +let countries = ['Österreich', 'Andorra', 'Vietnam']; + +alert( countries.sort( (a, b) => a > b ? 1 : -1) ); // Andorra, Vietnam, Österreich (wrong) + +alert( countries.sort( (a, b) => a.localeCompare(b) ) ); // Andorra,Österreich,Vietnam (correct!) +``` +```` + ### reverse Метод [arr.reverse](mdn:js/Array/reverse) меняет порядок элементов в `arr` на обратный. @@ -526,7 +691,11 @@ alert( arr ); // 5,4,3,2,1 Метод [str.split(delim)](mdn:js/String/split) именно это и делает. Он разбивает строку на массив по заданному разделителю `delim`. +<<<<<<< HEAD В примере ниже таким разделителем является строка из запятой и пробела. +======= +In the example below, we split by a comma followed by a space: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let names = 'Вася, Петя, Маша'; @@ -588,6 +757,7 @@ let value = arr.reduce(function(accumulator, item, index, array) { Аргументы: +<<<<<<< HEAD - `accumulator` -- результат предыдущего вызова этой функции, равен `initial` при первом вызове (если передан `initial`), - `item` -- очередной элемент массива, - `index` -- его позиция, @@ -596,6 +766,18 @@ let value = arr.reduce(function(accumulator, item, index, array) { При вызове функции результат её предыдущего вызова передаётся на следующий вызов в качестве первого аргумента. Так, первый аргумент является по сути аккумулятором, который хранит объединённый результат всех предыдущих вызовов функции. По окончании он становится результатом `reduce`. +======= +- `accumulator` -- is the result of the previous function call, equals `initial` the first time (if `initial` is provided). +- `item` -- is the current array item. +- `index` -- is its position. +- `array` -- is the array. + +As the function is applied, the result of the previous function call is passed to the next one as the first argument. + +So, the first argument is essentially the accumulator that stores the combined result of all previous executions. And at the end, it becomes the result of `reduce`. + +Sounds complicated? +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Звучит сложно? @@ -625,7 +807,7 @@ alert(result); // 15 Или в виде таблицы, где каждая строка показывает вызов функции на очередном элементе массива: -| |`sum`|`current`|`result`| +| |`sum`|`current`|result| |---|-----|---------|---------| |первый вызов|`0`|`1`|`1`| |второй вызов|`1`|`2`|`3`| @@ -662,9 +844,15 @@ let arr = []; arr.reduce((sum, current) => sum + current); ``` +<<<<<<< HEAD Поэтому рекомендуется всегда указывать начальное значение. Метод [arr.reduceRight](mdn:js/Array/reduceRight) работает аналогично, но проходит по массиву справа налево. +======= +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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## Array.isArray @@ -674,7 +862,11 @@ arr.reduce((sum, current) => sum + current); ```js run alert(typeof {}); // object +<<<<<<< HEAD alert(typeof []); // тоже object +======= +alert(typeof []); // object (same) +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` ...Но массивы используются настолько часто, что для этого придумали специальный метод: [Array.isArray(value)](mdn:js/Array/isArray). Он возвращает `true`, если `value` массив, и `false`, если нет. @@ -689,7 +881,11 @@ alert(Array.isArray([])); // true Почти все методы массива, которые вызывают функции -- такие как `find`, `filter`, `map`, за исключением метода `sort`, принимают необязательный параметр `thisArg`. +<<<<<<< HEAD Этот параметр не объяснялся выше, так как очень редко используется, но для наиболее полного понимания темы мы обязаны его рассмотреть. +======= +That parameter is not explained in the sections above, because it's rarely used. But for completeness, we have to cover it. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Вот полный синтаксис этих методов: @@ -733,12 +929,17 @@ alert(soldiers[1].age); // 23 Если бы мы в примере выше использовали просто `users.filter(army.canJoin)`, то вызов `army.canJoin` был бы в режиме отдельной функции, с `this=undefined`. Это тут же привело бы к ошибке. +<<<<<<< HEAD Вызов `users.filter(army.canJoin, army)` можно заменить на `users.filter(user => army.canJoin(user))`, который делает то же самое. Последняя запись используется даже чаще, так как функция-стрелка более наглядна. +======= +A call to `users.filter(army.canJoin, army)` can be replaced with `users.filter(user => army.canJoin(user))`, that does the same. The latter is used more often, as it's a bit easier to understand for most people. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## Итого Шпаргалка по методам массива: +<<<<<<< HEAD - Для добавления/удаления элементов: - `push(...items)` -- добавляет элементы в конец, - `pop()` -- извлекает элемент с конца, @@ -753,10 +954,27 @@ alert(soldiers[1].age); // 23 - `includes(value)` -- возвращает `true`, если в массиве имеется элемент `value`, в противном случае `false`. - `find/filter(func)` -- фильтрует элементы через функцию и отдаёт первое/все значения, при прохождении которых через функцию возвращается `true`. - `findIndex` похож на `find`, но возвращает индекс вместо значения. +======= +- 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` deletes `deleteCount` elements and inserts `items`. + - `slice(start, end)` -- creates a new array, copies elements from index `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`, and 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 - Для перебора элементов: - `forEach(func)` -- вызывает `func` для каждого элемента. Ничего не возвращает. +<<<<<<< HEAD - Для преобразования массива: - `map(func)` -- создаёт новый массив из результатов вызова `func` для каждого элемента. - `sort(func)` -- сортирует массив «на месте», а потом возвращает его. @@ -766,16 +984,47 @@ alert(soldiers[1].age); // 23 - Дополнительно: - `Array.isArray(arr)` проверяет, является ли `arr` массивом. +======= +- 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/reduceRight(func, initial)` -- calculate a single value over the array by calling `func` for each element and passing an intermediate result between the calls. + +- Additionally: + - `Array.isArray(value)` checks `value` for being an array, if so returns `true`, otherwise `false`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Пожалуйста, обратите внимание, что методы `push`, `pop`, `shift`, `unshift`, `sort`, `reverse` и `splice` изменяют исходный массив. Эти методы -- самые используемые, их достаточно в 99% случаев. Но существуют и другие: +<<<<<<< HEAD - [arr.some(fn)](mdn:js/Array/some)/[arr.every(fn)](mdn:js/Array/every) проверяет массив. +======= +- [arr.some(fn)](mdn:js/Array/some)/[arr.every(fn)](mdn:js/Array/every) check the array. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Функция `fn` вызывается для каждого элемента массива аналогично `map`. Если какие-либо/все результаты вызовов являются `true`, то метод возвращает `true`, иначе `false`. +<<<<<<< HEAD Эти методы ведут себя примерно так же, как операторы `||` и `&&`: если `fn` возвращает истинное значение, `arr.some()` немедленно возвращает `true` и останавливает перебор остальных элементов; если `fn` возвращает ложное значение, `arr.every()` немедленно возвращает `false` и также прекращает перебор остальных элементов. +======= + These methods behave sort of like `||` and `&&` operators: if `fn` returns a truthy value, `arr.some()` immediately returns `true` and stops iterating over the rest of items; if `fn` returns a falsy value, `arr.every()` immediately returns `false` and stops iterating over the rest of items as well. + + We can use `every` to compare arrays: + + ```js run + function arraysEqual(arr1, arr2) { + return arr1.length === arr2.length && arr1.every((value, index) => value === arr2[index]); + } + + alert( arraysEqual([1, 2], [1, 2])); // true + ``` + +- [arr.fill(value, start, end)](mdn:js/Array/fill) -- fills the array with repeating `value` from index `start` to `end`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Мы можем использовать `every` для сравнения массивов: @@ -784,10 +1033,18 @@ alert(soldiers[1].age); // 23 return arr1.length === arr2.length && arr1.every((value, index) => value === arr2[index]); } +<<<<<<< HEAD alert( arraysEqual([1, 2], [1, 2])); // true ``` - [arr.fill(value, start, end)](mdn:js/Array/fill) -- заполняет массив повторяющимися `value`, начиная с индекса `start` до `end`. +======= +- [arr.flat(depth)](mdn:js/Array/flat)/[arr.flatMap(fn)](mdn:js/Array/flatMap) create a new flat array from a multidimensional array. + +For the full list, see the [manual](mdn:js/Array). + +At first sight, it may seem that there are so many methods, quite difficult to remember. But actually, that's much easier. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 - [arr.copyWithin(target, start, end)](mdn:js/Array/copyWithin) -- копирует свои элементы, начиная с позиции `start` и заканчивая `end`, в *себя*, на позицию `target` (перезаписывая существующие). diff --git a/1-js/05-data-types/06-iterable/article.md b/1-js/05-data-types/06-iterable/article.md index cfd643aeb3..efc7d675fb 100644 --- a/1-js/05-data-types/06-iterable/article.md +++ b/1-js/05-data-types/06-iterable/article.md @@ -1,7 +1,11 @@ # Перебираемые объекты +<<<<<<< HEAD *Перебираемые* (или *итерируемые*) объекты - это обобщение массивов. Концепция, которая позволяет использовать любой объект в цикле `for..of`. +======= +*Iterable* objects are a generalization of arrays. That's a concept that allows us to make any object useable in a `for..of` loop. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Конечно же, сами массивы являются перебираемыми объектами. Но есть и много других встроенных перебираемых объектов, например, строки. @@ -11,7 +15,11 @@ Мы легко поймём принцип устройства перебираемых объектов, создав один из них. +<<<<<<< HEAD Например, у нас есть объект. Это не массив, но он выглядит подходящим для `for..of`. +======= +For instance, we have an object that is not an array, but looks suitable for `for..of`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Например, объект `range`, который представляет собой диапазон чисел: @@ -25,12 +33,21 @@ let range = { // for(let num of range) ... num=1,2,3,4,5 ``` +<<<<<<< HEAD Чтобы сделать `range` итерируемым (и позволить `for..of` работать с ним), нам нужно добавить в объект метод с именем `Symbol.iterator` (специальный встроенный `Symbol`, созданный как раз для этого). 1. Когда цикл `for..of` запускается, он вызывает этот метод один раз (или выдаёт ошибку, если метод не найден). Этот метод должен вернуть *итератор* -- объект с методом `next`. 2. Дальше `for..of` работает *только с этим возвращённым объектом*. 3. Когда `for..of` хочет получить следующее значение, он вызывает метод `next()` этого объекта. 4. Результат вызова `next()` должен иметь вид `{done: Boolean, value: any}`, где `done=true` означает, что цикл завершён, в противном случае `value` содержит очередное значение. +======= +To make the `range` object iterable (and thus let `for..of` work) we need to add a method to the object named `Symbol.iterator` (a special built-in symbol just for that). + +1. When `for..of` starts, it calls that method once (or errors if not found). The method must return an *iterator* -- an object with the method `next`. +2. Onward, `for..of` works *only with that returned object*. +3. When `for..of` wants the next value, it calls `next()` on that object. +4. The result of `next()` must have the form `{done: Boolean, value: any}`, where `done=true` means that the loop is finished, otherwise `value` is the next value. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Вот полная реализация `range` с пояснениями: @@ -43,9 +60,14 @@ let range = { // 1. вызов for..of сначала вызывает эту функцию range[Symbol.iterator] = function() { +<<<<<<< HEAD // ...она возвращает объект итератора: // 2. Далее, for..of работает только с этим итератором, // запрашивая у него новые значения +======= + // ...it returns the iterator object: + // 2. Onward, for..of works only with the iterator object below, asking it for next values +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 return { current: this.from, last: this.to, @@ -139,9 +161,15 @@ for (let char of str) { ## Явный вызов итератора +<<<<<<< HEAD Чтобы понять устройство итераторов чуть глубже, давайте посмотрим, как их использовать явно. Мы будем перебирать строку точно так же, как цикл `for..of`, но вручную, прямыми вызовами. Нижеприведённый код получает строковый итератор и берёт из него значения: +======= +For deeper understanding, let's see how to use an iterator explicitly. + +We'll iterate over a string in exactly the same way as `for..of`, but with direct calls. This code creates a string iterator and gets values from it "manually": +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let str = "Hello"; @@ -164,16 +192,28 @@ while (true) { ## Итерируемые объекты и псевдомассивы [#array-like] +<<<<<<< HEAD Есть два официальных термина, которые очень похожи, но в то же время сильно различаются. Поэтому убедитесь, что вы как следует поняли их, чтобы избежать путаницы. +======= +Two official terms look similar, but are very different. Please make sure you understand them well to avoid the confusion. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 - *Итерируемые объекты* - это объекты, которые реализуют метод `Symbol.iterator`, как было описано выше. - *Псевдомассивы* - это объекты, у которых есть индексы и свойство `length`, то есть, они выглядят как массивы. +<<<<<<< HEAD При использовании JavaScript в браузере или других окружениях мы можем встретить объекты, которые являются итерируемыми или псевдомассивами, или и тем, и другим. +======= +When we use JavaScript for practical tasks in a browser or any other environment, we may meet objects that are iterables or array-likes, or both. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Например, строки итерируемы (для них работает `for..of`) и являются псевдомассивами (они индексированы и есть `length`). +<<<<<<< HEAD Но итерируемый объект может не быть псевдомассивом. И наоборот: псевдомассив может не быть итерируемым. +======= +But an iterable may not be array-like. And vice versa an array-like may not be iterable. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Например, объект `range` из примера выше - итерируемый, но не является псевдомассивом, потому что у него нет индексированных свойств и `length`. @@ -213,29 +253,50 @@ let arr = Array.from(arrayLike); // (*) alert(arr.pop()); // World (метод работает) ``` +<<<<<<< HEAD `Array.from` в строке `(*)` принимает объект, проверяет, является ли он итерируемым объектом или псевдомассивом, затем создаёт новый массив и копирует туда все элементы. +======= +`Array.from` at the line `(*)` takes the object, examines it for being an iterable or array-like, then makes a new array and copies all items to it. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 То же самое происходит с итерируемым объектом: ```js run // range взят из примера в начале статьи +<<<<<<< HEAD +======= +```js run +// assuming that range is taken from the example above +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 let arr = Array.from(range); alert(arr); // 1,2,3,4,5 (преобразование массива через toString работает) ``` +<<<<<<< HEAD Полный синтаксис `Array.from` позволяет указать необязательную "трансформирующую" функцию: +======= +The full syntax for `Array.from` also allows us to provide an optional "mapping" function: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js Array.from(obj[, mapFn, thisArg]) ``` +<<<<<<< HEAD Необязательный второй аргумент может быть функцией, которая будет применена к каждому элементу перед добавлением в массив, а `thisArg` позволяет установить `this` для этой функции. +======= +The optional second argument `mapFn` can be a function that will be applied to each element before adding it to the array, and `thisArg` allows us to set `this` for it. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Например: ```js run +<<<<<<< HEAD // range взят из примера в начале статьи +======= +// assuming that range is taken from the example above +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 // возводим каждое число в квадрат let arr = Array.from(range, num => num * num); @@ -271,7 +332,11 @@ for (let char of str) { alert(chars); ``` +<<<<<<< HEAD ...Но гораздо короче. +======= +...But it is shorter. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Мы можем даже создать `slice`, который поддерживает суррогатные пары: @@ -292,16 +357,31 @@ alert( str.slice(1, 3) ); // мусор (две части различных с Объекты, которые можно использовать в цикле `for..of`, называются *итерируемыми*. +<<<<<<< HEAD - Технически итерируемые объекты должны иметь метод `Symbol.iterator`. - Результат вызова `obj[Symbol.iterator]` называется *итератором*. Он управляет процессом итерации. - Итератор должен иметь метод `next()`, который возвращает объект `{done: Boolean, value: any}`, где `done:true` сигнализирует об окончании процесса итерации, в противном случае `value` - следующее значение. - Метод `Symbol.iterator` автоматически вызывается циклом `for..of`, но можно вызвать его и напрямую. - Встроенные итерируемые объекты, такие как строки или массивы, также реализуют метод `Symbol.iterator`. - Строковый итератор знает про суррогатные пары. +======= +Objects that can be used in `for..of` are called *iterable*. + +- Technically, iterables must implement the method named `Symbol.iterator`. + - The result of `obj[Symbol.iterator]()` is called an *iterator*. It handles further iteration process. + - An iterator must have the method named `next()` that returns an object `{done: Boolean, value: any}`, here `done:true` denotes the end of the iteration process, otherwise the `value` is the next value. +- The `Symbol.iterator` method is called automatically by `for..of`, but we also can do it directly. +- Built-in iterables like strings or arrays, also implement `Symbol.iterator`. +- String iterator knows about surrogate pairs. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Объекты, имеющие индексированные свойства и `length`, называются *псевдомассивами*. Они также могут иметь другие свойства и методы, но у них нет встроенных методов массивов. Если мы заглянем в спецификацию, мы увидим, что большинство встроенных методов рассчитывают на то, что они будут работать с итерируемыми объектами или псевдомассивами вместо "настоящих" массивов, потому что эти объекты более абстрактны. +<<<<<<< HEAD `Array.from(obj[, mapFn, thisArg])` создаёт настоящий `Array` из итерируемого объекта или псевдомассива `obj`, и затем мы можем применять к нему методы массивов. Необязательные аргументы `mapFn` и `thisArg` позволяют применять функцию с задаваемым контекстом к каждому элементу. +======= +`Array.from(obj[, mapFn, thisArg])` makes a real `Array` from an iterable or array-like `obj`, and we can then use array methods on it. The optional arguments `mapFn` and `thisArg` allow us to apply a function to each item. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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 index aaec82046f..ff519b9fe3 100644 --- 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 @@ -36,7 +36,7 @@ alert( aclean(arr) ); Для удобства, давайте разделим это на несколько строк: ```js -let sorted = arr[i] // PAN +let sorted = word // PAN .toLowerCase() // pan .split("") // ["p","a","n"] .sort() // ["a","n","p"] 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 index 44fe215147..95bb240bd1 100644 --- 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 @@ -4,7 +4,11 @@ importance: 5 # Перебираемые ключи +<<<<<<< HEAD Мы хотели бы получить массив ключей `map.keys()` в переменную и далее работать с ними, например, применить метод `.push`. +======= +We'd like to get an array of `map.keys()` in a variable and then apply array-specific methods to it, e.g. `.push`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Но это не выходит: diff --git a/1-js/05-data-types/07-map-set/article.md b/1-js/05-data-types/07-map-set/article.md index efff26e8f9..5360817561 100644 --- a/1-js/05-data-types/07-map-set/article.md +++ b/1-js/05-data-types/07-map-set/article.md @@ -1,19 +1,31 @@ # Map и Set +<<<<<<< HEAD Сейчас мы знаем о следующих сложных структурах данных: - Объекты для хранения именованных коллекций. - Массивы для хранения упорядоченных коллекций. +======= +Till now, we've learned about the following complex data structures: + +- Objects are used for storing keyed collections. +- Arrays are used for storing ordered collections. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Но этого не всегда достаточно для решения повседневных задач. Поэтому также существуют `Map` и `Set`. ## Map +<<<<<<< HEAD [Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) -- это коллекция ключ/значение, как и `Object`. Но основное отличие в том, что `Map` позволяет использовать ключи любого типа. +======= +[Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) is a collection of keyed data items, just like an `Object`. But the main difference is that `Map` allows keys of any type. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Методы и свойства: +<<<<<<< HEAD - [`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` отсутствует. @@ -21,6 +33,15 @@ - [`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) -- возвращает текущее количество элементов. +======= +- [`new Map()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/Map) -- creates the map. +- [`map.set(key, value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/set) -- stores the value by the key. +- [`map.get(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/get) -- returns the value by the key, `undefined` if `key` doesn't exist in map. +- [`map.has(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/has) -- returns `true` if the `key` exists, `false` otherwise. +- [`map.delete(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/delete) -- removes the element (the key/value pair) by the key. +- [`map.clear()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/clear) -- removes everything from the map. +- [`map.size`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/size) -- returns the current element count. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Например: @@ -41,8 +62,18 @@ alert(map.size); // 3 Как мы видим, в отличие от объектов, ключи не были приведены к строкам. Можно использовать любые типы данных для ключей. +<<<<<<< HEAD ```smart header="`map[key]` это не совсем правильный способ использования `Map`" Хотя `map[key]` также работает, например, мы можем установить `map[key] = 2`, в этом случае`map` рассматривался бы как обычный JavaScript объект, таким образом это ведёт ко всем соответствующим ограничениям (только строки/символьные ключи и так далее). +======= +```smart header="`map[key]` isn't the right way to use a `Map`" +Although `map[key]` also works, e.g. we can set `map[key] = 2`, this is treating `map` as a plain JavaScript object, so it implies all corresponding limitations (only string/symbol keys and so on). + +So we should use `map` methods: `set`, `get` and so on. +``` + +**Map can also use objects as keys.** +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Поэтому нам следует использовать методы `map`: `set`, `get` и так далее. ``` @@ -63,7 +94,11 @@ visitsCountMap.set(john, 123); alert(visitsCountMap.get(john)); // 123 ``` +<<<<<<< HEAD Использование объектов в качестве ключей - одна из наиболее заметных и важных функций `Map`. Это то что невозможно для `Object`. Строка в качестве ключа в `Object` - это нормально, но мы не можем использовать другой `Object` в качестве ключа в `Object`. +======= +Using objects as keys is one of the most notable and important `Map` features. The same does not count for `Object`. String as a key in `Object` is fine, but we can't use another `Object` as a key in `Object`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Давайте попробуем заменить `Map` на `Object`: @@ -73,16 +108,28 @@ let ben = { name: "Ben" }; let visitsCountObj = {}; // попробуем использовать объект +<<<<<<< HEAD visitsCountObj[ben] = 234; // пробуем использовать объект ben в качестве ключа visitsCountObj[john] = 123; // пробуем использовать объект john в качестве ключа, при этом объект ben будет замещён *!* // Вот что там было записано! +======= +visitsCountObj[ben] = 234; // try to use ben object as the key +visitsCountObj[john] = 123; // try to use john object as the key, ben object will get replaced + +*!* +// That's what got written! +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 alert( visitsCountObj["[object Object]"] ); // 123 */!* ``` +<<<<<<< HEAD Так как `visitsCountObj` является объектом, он преобразует все ключи `Object`, такие как `john` и `ben`, в одну и ту же строку `"[object Object]"`. Это определенно не то, чего мы хотим. +======= +As `visitsCountObj` is an object, it converts all `Object` keys, such as `john` and `ben` above, to same string `"[object Object]"`. Definitely not what we want. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```smart header="Как объект `Map` сравнивает ключи" Чтобы сравнивать ключи, объект `Map` использует алгоритм [SameValueZero](https://tc39.github.io/ecma262/#sec-samevaluezero). Это почти такое же сравнение, что и `===`, с той лишь разницей, что `NaN` считается равным `NaN`. Так что `NaN` также может использоваться в качестве ключа. @@ -100,15 +147,27 @@ map.set("1", "str1") ``` ```` +<<<<<<< HEAD ## Перебор Map Для перебора коллекции `Map` есть 3 метода: +======= +## Iteration over Map +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 - [`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`. +<<<<<<< HEAD Например: +======= +- [`map.keys()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/keys) -- returns an iterable for keys, +- [`map.values()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/values) -- returns an iterable for values, +- [`map.entries()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/entries) -- returns an iterable for entries `[key, value]`, it's used by default in `for..of`. + +For instance: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let recipeMap = new Map([ @@ -161,7 +220,11 @@ let map = new Map([ alert( map.get('1') ); // str1 ``` +<<<<<<< HEAD Если у нас уже есть обычный объект, и мы хотели бы создать `Map` из него, то поможет встроенный метод [Object.entries(obj)](mdn:js/Object/entries), который получает объект и возвращает массив пар ключ-значение для него, как раз в этом формате. +======= +If we have a plain object, and we'd like to create a `Map` from it, then we can use built-in method [Object.entries(obj)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries) that returns an array of key/value pairs for an object exactly in that format. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Так что мы можем создать `Map` из обычного объекта следующим образом: @@ -198,7 +261,11 @@ let prices = Object.fromEntries([ alert(prices.orange); // 2 ``` +<<<<<<< HEAD Мы можем использовать `Object.fromEntries`, чтобы получить обычный объект из `Map`. +======= +We can use `Object.fromEntries` to get a plain object from `Map`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 К примеру, у нас данные в `Map`, но их нужно передать в сторонний код, который ожидает обычный объект. @@ -220,9 +287,13 @@ let obj = Object.fromEntries(map.entries()); // создаём обычный о alert(obj.orange); // 2 ``` +<<<<<<< HEAD Вызов `map.entries()` возвращает итерируемый объект пар ключ/значение, как раз в нужном формате для `Object.fromEntries`. Мы могли бы написать строку `(*)` ещё короче: +======= +A call to `map.entries()` returns an iterable of key/value pairs, exactly in the right format for `Object.fromEntries`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js let obj = Object.fromEntries(map); // убрать .entries() @@ -232,16 +303,29 @@ let obj = Object.fromEntries(map); // убрать .entries() ## Set +<<<<<<< HEAD Объект [`Set`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set) -- это особый вид коллекции: "множество" значений (без ключей), где каждое значение может появляться только один раз. +======= +A [`Set`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set) is a special type collection - "set of values" (without keys), where each value may occur only once. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Его основные методы это: +<<<<<<< HEAD - [`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) -- возвращает количество элементов в множестве. +======= +- [`new Set([iterable])`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/Set) -- creates the set, and if an `iterable` object is provided (usually an array), copies values from it into the set. +- [`set.add(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/add) -- adds a value, returns the set itself. +- [`set.delete(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/delete) -- removes the value, returns `true` if `value` existed at the moment of the call, otherwise `false`. +- [`set.has(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/has) -- returns `true` if the value exists in the set, otherwise `false`. +- [`set.clear()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/clear) -- removes everything from the set. +- [`set.size`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/size) -- is the elements count. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Основная "изюминка" - это то, что при повторных вызовах `set.add()` с одним и тем же значением ничего не происходит, за счёт этого как раз и получается, что каждое значение появляется один раз. @@ -271,7 +355,11 @@ for (let user of set) { } ``` +<<<<<<< HEAD Альтернативой множеству `Set` может выступать массив для хранения гостей и дополнительный код для проверки уже имеющегося элемента с помощью [arr.find](mdn:js/Array/find). Но в этом случае будет хуже производительность, потому что `arr.find` проходит весь массив для проверки наличия элемента. Множество `Set` лучше оптимизировано для добавлений, оно автоматически проверяет на уникальность. +======= +The alternative to `Set` could be an array of users, and the code to check for duplicates on every insertion using [arr.find](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## Перебор объекта Set @@ -290,20 +378,35 @@ set.forEach((value, valueAgain, set) => { Заметим забавную вещь. Функция в `forEach` у `Set` имеет 3 аргумента: значение `value`, потом *снова то же самое значение* `valueAgain`, и только потом целевой объект. Это действительно так, значение появляется в списке аргументов дважды. +<<<<<<< HEAD Это сделано для совместимости с объектом `Map`, в котором колбэк `forEach` имеет 3 аргумента. Выглядит немного странно, но в некоторых случаях может помочь легко заменить `Map` на `Set` и наоборот. +======= +That's for compatibility with `Map` where the callback passed `forEach` has three arguments. Looks a bit strange, for sure. But this may help to replace `Map` with `Set` in certain cases with ease, and vice versa. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 `Set` имеет те же встроенные методы, что и `Map`: +<<<<<<< HEAD - [`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`. +======= +- [`set.keys()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/keys) -- returns an iterable object for values, +- [`set.values()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/values) -- same as `set.keys()`, for compatibility with `Map`, +- [`set.entries()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/entries) -- returns an iterable object for entries `[value, value]`, exists for compatibility with `Map`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## Итого +<<<<<<< HEAD [`Map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) -- коллекция пар ключ-значение. +======= +[`Map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) -- is a collection of keyed values. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Методы и свойства: +<<<<<<< HEAD - [`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` отсутствует. @@ -311,21 +414,43 @@ set.forEach((value, valueAgain, set) => { - [`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) -- возвращает текущее количество элементов. +======= +- [`new Map([iterable])`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/Map) -- creates the map, with optional `iterable` (e.g. array) of `[key,value]` pairs for initialization. +- [`map.set(key, value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/set) -- stores the value by the key, returns the map itself. +- [`map.get(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/get) -- returns the value by the key, `undefined` if `key` doesn't exist in map. +- [`map.has(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/has) -- returns `true` if the `key` exists, `false` otherwise. +- [`map.delete(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/delete) -- removes the element by the key, returns `true` if `key` existed at the moment of the call, otherwise `false`. +- [`map.clear()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/clear) -- removes everything from the map. +- [`map.size`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/size) -- returns the current element count. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Отличия от обычного объекта `Object`: - Что угодно может быть ключом, в том числе и объекты. - Есть дополнительные методы, свойство `size`. +<<<<<<< HEAD [`Set`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set) -- коллекция уникальных значений, так называемое "множество". +======= +[`Set`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set) -- is a collection of unique values. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Методы и свойства: +<<<<<<< HEAD - [`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) -- возвращает количество элементов в множестве. +======= +- [`new Set([iterable])`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/Set) -- creates the set, with optional `iterable` (e.g. array) of values for initialization. +- [`set.add(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/add) -- adds a value (does nothing if `value` exists), returns the set itself. +- [`set.delete(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/delete) -- removes the value, returns `true` if `value` existed at the moment of the call, otherwise `false`. +- [`set.has(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/has) -- returns `true` if the value exists in the set, otherwise `false`. +- [`set.clear()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/clear) -- removes everything from the set. +- [`set.size`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/size) -- is the elements count. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Перебор `Map` и `Set` всегда осуществляется в порядке добавления элементов, так что нельзя сказать, что это -- неупорядоченные коллекции, но поменять порядок элементов или получить элемент напрямую по его номеру нельзя. 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 index 8032287a7d..5196d459a7 100644 --- 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 @@ -1,6 +1,6 @@ Можно хранить прочитанные сообщения в `WeakSet`: -```js +```js run let messages = [ {text: "Hello", from: "John"}, {text: "How goes?", from: "John"}, @@ -25,7 +25,11 @@ messages.shift(); // теперь readMessages содержит 1 элемент (хотя технически память может быть очищена позже) ``` +<<<<<<< HEAD `WeakSet` позволяет хранить набор сообщений и легко проверять наличие сообщения в нём. +======= +The `WeakSet` allows to store a set of messages and easily check for the existence of a message in it. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Он очищается автоматически. Минус в том, что мы не можем перебрать его содержимое, не можем получить "все прочитанные сообщения" напрямую. Но мы можем сделать это, перебирая все сообщения и фильтруя те, которые находятся в `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 index 215aa27cfd..22e12c55a2 100644 --- 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 @@ -16,7 +16,11 @@ let messages = [ У вас есть к ним доступ, но управление этим массивом происходит где-то ещё. Добавляются новые сообщения и удаляются старые, и вы не знаете в какой момент это может произойти. +<<<<<<< HEAD Имея такую вводную информацию, решите, какую структуру данных вы могли бы использовать для ответа на вопрос "было ли сообщение прочитано?". Структура должна быть подходящей, чтобы можно было однозначно сказать, было ли прочитано это сообщение для каждого объекта сообщения. +======= +Now, which data structure could you use to store information about whether the message "has been read"? The structure must be well-suited to give the answer "was it read?" for the given message object. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 P.S. Когда сообщение удаляется из массива `messages`, оно должно также исчезать из структуры данных. diff --git a/1-js/05-data-types/08-weakmap-weakset/article.md b/1-js/05-data-types/08-weakmap-weakset/article.md index 4c7f717587..9d14c94d40 100644 --- a/1-js/05-data-types/08-weakmap-weakset/article.md +++ b/1-js/05-data-types/08-weakmap-weakset/article.md @@ -1,3 +1,4 @@ +<<<<<<< HEAD # WeakMap и WeakSet @@ -5,6 +6,15 @@ Например: +======= + +# WeakMap and WeakSet + +As we know from the chapter , JavaScript engine keeps a value in memory while it is "reachable" and can potentially be used. + +For instance: + +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js let john = { name: "John" }; @@ -32,8 +42,14 @@ let array = [ john ]; john = null; // перезаписываем ссылку на объект *!* +<<<<<<< HEAD // объект john хранится в массиве, поэтому он не будет удалён сборщиком мусора // мы можем взять его значение как array[0] +======= +// the object previously referenced by john is stored inside the array +// therefore it won't be garbage-collected +// we can get it as array[0] +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 */!* ``` @@ -55,13 +71,21 @@ john = null; // перезаписываем ссылку на объект */!* ``` +<<<<<<< HEAD `WeakMap` - принципиально другая структура в этом аспекте. Она не предотвращает удаление объектов сборщиком мусора, когда эти объекты выступают в качестве ключей. +======= +[`WeakMap`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap) is fundamentally different in this aspect. It doesn't prevent garbage-collection of key objects. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Давайте посмотрим, что это означает, на примерах. ## WeakMap +<<<<<<< HEAD Первое его отличие от `Map` в том, что ключи в `WeakMap` должны быть объектами, а не примитивными значениями: +======= +The first difference between [`Map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) and [`WeakMap`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap) is that keys must be objects, not primitive values: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let weakMap = new WeakMap(); @@ -95,16 +119,22 @@ john = null; // перезаписываем ссылку на объект В `WeakMap` присутствуют только следующие методы: -- `weakMap.get(key)` -- `weakMap.set(key, value)` -- `weakMap.delete(key)` -- `weakMap.has(key)` +- [`weakMap.set(key, value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap/set) +- [`weakMap.get(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap/get) +- [`weakMap.delete(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap/delete) +- [`weakMap.has(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap/has) К чему такие ограничения? Из-за особенностей технической реализации. Если объект станет недостижим (как объект `john` в примере выше), то он будет автоматически удалён сборщиком мусора. Но нет информации, *в какой момент произойдёт эта очистка*. +<<<<<<< HEAD Решение о том, когда делать сборку мусора, принимает движок JavaScript. Он может посчитать необходимым как удалить объект прямо сейчас, так и отложить эту операцию, чтобы удалить большее количество объектов за раз позже. Так что технически количество элементов в коллекции `WeakMap` неизвестно. Движок может произвести очистку сразу или потом, или сделать это частично. По этой причине методы для доступа ко всем сразу ключам/значениям недоступны. Но для чего же нам нужна такая структура данных? +======= +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 all keys/values are not supported. + +Now, where do we need such a data structure? +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## Пример: дополнительные данные @@ -142,13 +172,21 @@ function countUser(user) { // 📁 main.js let john = { name: "John" }; +<<<<<<< HEAD countUser(john); // ведём подсчёт посещений +======= +countUser(john); // count his visits +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 // пользователь покинул нас john = null; ``` +<<<<<<< HEAD Теперь объект `john` должен быть удалён сборщиком мусора, но он продолжает оставаться в памяти, так как является ключом в `visitsCountMap`. +======= +Now, `john` object should be garbage collected, but remains in memory, as it's a key in `visitsCountMap`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Нам нужно очищать `visitsCountMap` при удалении объекта пользователя, иначе коллекция будет бесконечно расти. Подобная очистка может быть неудобна в реализации при сложной архитектуре приложения. @@ -165,13 +203,23 @@ function countUser(user) { } ``` +<<<<<<< HEAD Теперь нет необходимости вручную очищать `visitsCountMap`. После того, как объект `john` стал недостижим другими способами, кроме как через `WeakMap`, он удаляется из памяти вместе с информацией по такому ключу из `WeakMap`. +======= +Now we don't have to clean `visitsCountMap`. After `john` object becomes unreachable, by all means except as a key of `WeakMap`, it gets removed from memory, along with the information by that key from `WeakMap`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## Применение для кеширования +<<<<<<< HEAD Другая частая сфера применения -- это кеширование, когда результат вызова функции должен где-то запоминаться ("кешироваться") для того, чтобы дальнейшие её вызовы на том же объекте могли просто брать уже готовый результат, повторно используя его. Для хранения результатов мы можем использовать `Map`, вот так: +======= +Another common example is caching. We can store ("cache") results from a function, so that future calls on the same object can reuse it. + +To achieve that, we can use `Map` (not optimal scenario): +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run // 📁 cache.js @@ -183,6 +231,7 @@ function process(obj) { let result = /* тут какие-то вычисления результата для объекта */ obj; cache.set(obj, result); + return result; } return cache.get(obj); @@ -208,7 +257,11 @@ alert(cache.size); // 1 (Упс! Объект всё ещё в кеше, зан Многократные вызовы `process(obj)` с тем же самым объектом в качестве аргумента ведут к тому, что результат вычисляется только в первый раз, а затем последующие вызовы берут его из кеша. Недостатком является то, что необходимо вручную очищать `cache` от ставших ненужными объектов. +<<<<<<< HEAD Но если мы будем использовать `WeakMap` вместо `Map`, то эта проблема исчезнет: закешированные результаты будут автоматически удалены из памяти сборщиком мусора. +======= +If we replace `Map` with `WeakMap`, then this problem disappears. The cached result will be removed from memory automatically after the object gets garbage collected. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run // 📁 cache.js @@ -222,6 +275,7 @@ function process(obj) { let result = /* вычисляем результат для объекта */ obj; cache.set(obj, result); + return result; } return cache.get(obj); @@ -243,6 +297,7 @@ obj = null; ## WeakSet +<<<<<<< HEAD Коллекция `WeakSet` ведёт себя похоже: - Она аналогична `Set`, но мы можем добавлять в `WeakSet` только объекты (не примитивные значения). @@ -250,6 +305,15 @@ obj = null; - Как и `Set`, она поддерживает `add`, `has` и `delete`, но не `size`, `keys()` и не является перебираемой. Будучи "слабой" версией оригинальной структуры данных, она тоже служит в качестве дополнительного хранилища. Но не для произвольных данных, а скорее для значений типа "да/нет". Присутствие во множестве `WeakSet` может что-то сказать нам об объекте. +======= +[`WeakSet`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/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`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Weakset/add), [`has`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Weakset/has) and [`delete`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Weakset/delete), but not `size`, `keys()` and no iterations. + +Being "weak", it also serves as additional storage. But not for arbitrary data, rather for "yes/no" facts. A membership in `WeakSet` may mean something about the object. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Например, мы можем добавлять пользователей в `WeakSet` для учёта тех, кто посещал наш сайт: @@ -277,10 +341,15 @@ john = null; // структура данных visitedSet будет очищена автоматически (объект john будет удалён из visitedSet) ``` +<<<<<<< HEAD Наиболее значительным ограничением `WeakMap` и `WeakSet` является то, что их нельзя перебрать или взять всё содержимое. Это может доставлять неудобства, но не мешает `WeakMap/WeakSet` выполнять их главную задачу -- быть дополнительным хранилищем данных для объектов, управляемых из каких-то других мест в коде. +======= +The most notable limitation of `WeakMap` and `WeakSet` is the absence of iterations, and the 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## Итого +<<<<<<< HEAD `WeakMap` -- это `Map`-подобная коллекция, позволяющая использовать в качестве ключей только объекты, и автоматически удаляющая их вместе с соответствующими значениями, как только они становятся недостижимыми иными путями. `WeakSet` -- это `Set`-подобная коллекция, которая хранит только объекты и удаляет их, как только они становятся недостижимыми иными путями. @@ -288,3 +357,14 @@ john = null; Обе этих структуры данных не поддерживают методы и свойства, работающие со всем содержимым сразу или возвращающие информацию о размере коллекции. Возможны только операции на отдельном элементе коллекции. `WeakMap` и `WeakSet` используются как вспомогательные структуры данных в дополнение к "основному" месту хранения объекта. Если объект удаляется из основного хранилища и нигде не используется, кроме как в качестве ключа в `WeakMap` или в `WeakSet`, то он будет удалён автоматически. +======= +[`WeakMap`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap) is `Map`-like collection that allows only objects as keys and removes them together with associated value once they become inaccessible by other means. + +[`WeakSet`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet) is `Set`-like collection that stores only objects and removes them once they become inaccessible by other means. + +Their main advantages are that they have weak reference to objects, so they can easily be removed by garbage collector. + +That comes at the cost of not having support for `clear`, `size`, `keys`, `values`... + +`WeakMap` and `WeakSet` are used as "secondary" data structures in addition to the "primary" object storage. Once the object is removed from the primary storage, if it is only found as the key of `WeakMap` or in a `WeakSet`, it will be cleaned up automatically. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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 index fd5d88041b..03accf0eea 100644 --- a/1-js/05-data-types/09-keys-values-entries/article.md +++ b/1-js/05-data-types/09-keys-values-entries/article.md @@ -75,11 +75,21 @@ for (let value of Object.values(user)) { Если мы хотели бы их применить, то можно использовать `Object.entries` с последующим вызовом `Object.fromEntries`: +<<<<<<< HEAD 1. Вызов `Object.entries(obj)` возвращает массив пар ключ/значение для `obj`. 2. На нём вызываем методы массива, например, `map`. 3. Используем `Object.fromEntries(array)` на результате, чтобы преобразовать его обратно в объект. Например, у нас есть объект с ценами, и мы хотели бы их удвоить: +======= +If we'd like to apply them, then we can use `Object.entries` followed by `Object.fromEntries`: + +1. Use `Object.entries(obj)` to get an array of key/value pairs from `obj`. +2. Use array methods on that array, e.g. `map`, to transform these key/value pairs. +3. Use `Object.fromEntries(array)` on the resulting array to turn it back into an object. + +For example, we have an object with prices, and would like to double them: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let prices = { @@ -90,14 +100,24 @@ let prices = { *!* let doublePrices = Object.fromEntries( +<<<<<<< HEAD // преобразовать в массив, затем map, затем fromEntries (обратно в объект) Object.entries(prices).map(([key, value]) => [key, value * 2]) +======= + // convert prices to array, map each key/value pair into another pair + // and then fromEntries gives back the object + Object.entries(prices).map(entry => [entry[0], entry[1] * 2]) +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ); */!* alert(doublePrices.meat); // 8 -``` +``` +<<<<<<< HEAD Это может выглядеть сложным на первый взгляд, но становится лёгким для понимания после нескольких раз использования. Можно делать и более сложные "однострочные" преобразования таким путём. Важно только сохранять баланс, чтобы код при этом был достаточно простым для понимания. +======= +It may look difficult at first sight, but becomes easy to understand after you use it once or twice. We can make powerful chains of transforms this way. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/1-js/05-data-types/10-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 index f4bd5c761c..6538af42b9 100644 --- a/1-js/05-data-types/10-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 @@ -1,16 +1,14 @@ function topSalary(salaries) { - let max = 0; + let maxSalary = 0; let maxName = null; for(const [name, salary] of Object.entries(salaries)) { - if (max < salary) { - max = salary; + if (maxSalary < salary) { + maxSalary = salary; maxName = name; } } return maxName; -} - - +} \ No newline at end of file diff --git a/1-js/05-data-types/10-destructuring-assignment/article.md b/1-js/05-data-types/10-destructuring-assignment/article.md index 0983421102..c85f350391 100644 --- a/1-js/05-data-types/10-destructuring-assignment/article.md +++ b/1-js/05-data-types/10-destructuring-assignment/article.md @@ -1,15 +1,27 @@ # Деструктурирующее присваивание +<<<<<<< HEAD В JavaScript есть две чаще всего используемые структуры данных - это `Object` и `Array`. - Объекты позволяют нам создавать одну сущность, которая хранит элементы данных по ключам. - Массивы позволяют нам собирать элементы данных в упорядоченный список. Но когда мы передаём их в функцию, то ей может понадобиться не объект/массив целиком, а элементы по отдельности. +======= +- Objects allow us to create a single entity that stores data items by key. +- Arrays allow us to gather data items into an ordered list. + +However, when we pass these to a function, we may not need all of it. The function might only require certain elements or properties. + +*Destructuring assignment* is a special syntax that allows us to "unpack" arrays or objects into a bunch of variables, as sometimes that's more convenient. + +Destructuring also works well with complex functions that have a lot of parameters, default values, and so on. Soon we'll see that. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 *Деструктурирующее присваивание* -- это специальный синтаксис, который позволяет нам "распаковать" массивы или объекты в несколько переменных, так как иногда они более удобны. +<<<<<<< HEAD Деструктуризация также прекрасно работает со сложными функциями, которые имеют много параметров, значений по умолчанию и так далее. Скоро мы увидим это. ## Деструктуризация массива @@ -19,6 +31,13 @@ ```js // у нас есть массив с именем и фамилией let arr = ["Ilya", "Kantor"]; +======= +Here's an example of how an array is destructured into variables: + +```js +// we have an array with a name and surname +let arr = ["John", "Smith"] +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 *!* // деструктурирующее присваивание @@ -27,14 +46,15 @@ let arr = ["Ilya", "Kantor"]; let [firstName, surname] = arr; */!* -alert(firstName); // Ilya -alert(surname); // Kantor +alert(firstName); // John +alert(surname); // Smith ``` Теперь мы можем использовать переменные вместо элементов массива. Отлично смотрится в сочетании со `split` или другими методами, возвращающими массив: +<<<<<<< HEAD ```js let [firstName, surname] = "Ilya Kantor".split(' '); alert(firstName); // Ilya @@ -47,6 +67,18 @@ alert(surname); // Kantor "Деструктурирующее присваивание" не уничтожает массив. Оно вообще ничего не делает с правой частью присваивания, его задача - только скопировать нужные значения в переменные. Это просто короткий вариант записи: +======= +```js run +let [firstName, surname] = "John Smith".split(' '); +alert(firstName); // John +alert(surname); // Smith +``` + +As you can see, the syntax is simple. There are several peculiar details though. Let's see more examples to understand it better. + +````smart header="\"Destructuring\" does not mean \"destructive\"." +It's called "destructuring assignment," because it "destructurizes" by copying items into variables. However, the array itself is not modified. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js // let [firstName, surname] = arr; @@ -67,7 +99,11 @@ let [firstName, , title] = ["Julius", "Caesar", "Consul", "of the Roman Republic alert( title ); // Consul ``` +<<<<<<< HEAD В примере выше второй элемент массива пропускается, а третий присваивается переменной `title`, оставшиеся элементы массива также пропускаются (так как для них нет переменных). +======= +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 items are also skipped (as there are no variables for them). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```` ````smart header="Работает с любым перебираемым объектом с правой стороны" @@ -77,6 +113,7 @@ alert( title ); // Consul let [a, b, c] = "abc"; let [one, two, three] = new Set([1, 2, 3]); ``` +<<<<<<< HEAD ```` @@ -84,20 +121,40 @@ let [one, two, three] = new Set([1, 2, 3]); Мы можем использовать что угодно "присваивающее" с левой стороны. Например, можно присвоить свойству объекта: +======= +That works, because internally a destructuring assignment works by iterating over the right value. It's a kind of syntax sugar for calling `for..of` over the value to the right of `=` and assigning the values. +```` + + +````smart header="Assign to anything at the left-side" +We can use any "assignables" on the left side. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let user = {}; -[user.name, user.surname] = "Ilya Kantor".split(' '); +[user.name, user.surname] = "John Smith".split(' '); +<<<<<<< HEAD alert(user.name); // Ilya alert(user.surname); // Kantor +======= +alert(user.name); // John +alert(user.surname); // Smith +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` ```` +<<<<<<< HEAD ````smart header="Цикл с .entries()" В предыдущей главе мы видели метод [Object.entries(obj)](mdn:js/Object/entries). Мы можем использовать его с деструктуризацией для цикличного перебора ключей и значений объекта: +======= +````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 the keys-and-values of an object: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let user = { @@ -105,7 +162,11 @@ let user = { age: 30 }; +<<<<<<< HEAD // цикл по ключам и значениям +======= +// loop over the keys-and-values +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 *!* for (let [key, value] of Object.entries(user)) { */!* @@ -113,7 +174,11 @@ for (let [key, value] of Object.entries(user)) { } ``` +<<<<<<< HEAD ...то же самое для map: +======= +The similar code for a `Map` is simpler, as it's iterable: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let user = new Map(); @@ -121,13 +186,18 @@ user.set("name", "John"); user.set("age", "30"); *!* +<<<<<<< HEAD // Map перебирает как пары [ключ, значение], что очень удобно для деструктурирования +======= +// Map iterates as [key, value] pairs, very convenient for destructuring +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 for (let [key, value] of user) { */!* alert(`${key}:${value}`); // name:John, затем age:30 } ``` ```` +<<<<<<< HEAD ````smart header="Трюк обмена переменных" Существует хорошо известный трюк для обмена значений двух переменных с использованием деструктурирующего присваивания: @@ -154,32 +224,83 @@ alert(`${guest} ${admin}`); // Pete Jane (успешно заменено!) Обычно, если массив длиннее, чем список слева, "лишние" элементы опускаются. Например, здесь берутся только первые два элемента, а остальные просто игнорируются: +======= + +````smart header="Swap variables trick" +There's a well-known trick for swapping values of two variables using a destructuring assignment: + +```js run +let guest = "Jane"; +let admin = "Pete"; + +// Let's swap the values: make guest=Pete, admin=Jane +*!* +[guest, admin] = [admin, guest]; +*/!* + +alert(`${guest} ${admin}`); // Pete Jane (successfully swapped!) +``` + +Here we create a temporary array of two variables and immediately destructure it in swapped order. + +We can swap more than two variables this way. +```` + +### The rest '...' + +Usually, if the array is longer than the list at the left, the "extra" items are omitted. + +For example, here only two items are taken, and the rest is just ignored: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let [name1, name2] = ["Julius", "Caesar", "Consul", "of the Roman Republic"]; alert(name1); // Julius alert(name2); // Caesar +<<<<<<< HEAD // Дальнейшие элементы нигде не присваиваются ``` Если мы хотим не просто получить первые значения, но и собрать все остальные, то мы можем добавить ещё один параметр, который получает остальные значения, используя оператор "остаточные параметры" -- троеточие (`"..."`): +======= +// Further items aren't assigned anywhere +``` + +If we'd like also to gather all that follows -- we can add one more parameter that gets "the rest" using three dots `"..."`: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let [name1, name2, *!*...rest*/!*] = ["Julius", "Caesar", *!*"Consul", "of the Roman Republic"*/!*]; *!* +<<<<<<< HEAD // rest это массив элементов, начиная с 3-го +======= +// rest is an array of items, starting from the 3rd one +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 alert(rest[0]); // Consul alert(rest[1]); // of the Roman Republic alert(rest.length); // 2 */!* ``` +<<<<<<< HEAD Переменная `rest` является массивом из оставшихся элементов. +======= +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. + +```js run +let [name1, name2, *!*...titles*/!*] = ["Julius", "Caesar", "Consul", "of the Roman Republic"]; +// now titles = ["Consul", "of the Roman Republic"] +``` +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Вместо `rest` можно использовать любое другое название переменной, просто убедитесь, что перед переменной есть три точки и она стоит на последнем месте в деструктурирующем присваивании. +<<<<<<< HEAD ```js run let [name1, name2, *!*...titles*/!*] = ["Julius", "Caesar", "Consul", "of the Roman Republic"]; // теперь titles = ["Consul", "of the Roman Republic"] @@ -188,6 +309,9 @@ let [name1, name2, *!*...titles*/!*] = ["Julius", "Caesar", "Consul", "of the Ro ### Значения по умолчанию Если в массиве меньше значений, чем в присваивании, то ошибки не будет. Отсутствующие значения считаются неопределёнными: +======= +If the array is shorter than the list of variables on the left, there will be no errors. Absent values are considered undefined: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run *!* @@ -212,7 +336,11 @@ alert(surname); // Anonymous (значение по умолчанию) Значения по умолчанию могут быть гораздо более сложными выражениями или даже функциями. Они выполняются, только если значения отсутствуют. +<<<<<<< HEAD Например, здесь мы используем функцию `prompt` для указания двух значений по умолчанию. +======= +For instance, here we use the `prompt` function for two defaults: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run // prompt запустится только для surname @@ -222,9 +350,13 @@ alert(name); // Julius (из массива) alert(surname); // результат prompt ``` +<<<<<<< HEAD Обратите внимание, `prompt` будет запущен только для пропущенного значения (`surname`). ## Деструктуризация объекта +======= +Please note: the `prompt` will run only for the missing value (`surname`). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Деструктурирующее присваивание также работает с объектами. @@ -234,7 +366,11 @@ alert(surname); // результат prompt let {var1, var2} = {var1:…, var2:…} ``` +<<<<<<< HEAD У нас есть существующий объект с правой стороны, который мы хотим разделить на переменные. Левая сторона содержит "шаблон" для соответствующих свойств. В простом случае это список названий переменных в `{...}`. +======= +We should have an existing object on the right side, that we want to split into variables. The left side contains an object-like "pattern" for corresponding properties. In the simplest case, that's a list of variable names in `{...}`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Например: @@ -254,9 +390,15 @@ alert(width); // 100 alert(height); // 200 ``` +<<<<<<< HEAD Свойства `options.title`, `options.width` и `options.height` присваиваются соответствующим переменным. Порядок не имеет значения. Вот так - тоже работает: +======= +Properties `options.title`, `options.width` and `options.height` are assigned to the corresponding variables. + +The order does not matter. This works too: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js // изменён порядок в let {...} @@ -265,7 +407,11 @@ let {height, width, title} = { title: "Menu", height: 200, width: 100 } Шаблон с левой стороны может быть более сложным и определять соответствие между свойствами и переменными. +<<<<<<< HEAD Если мы хотим присвоить свойство объекта переменной с другим названием, например, свойство `options.width` присвоить переменной `w`, то мы можем использовать двоеточие: +======= +If we want to assign a property to a variable with another name, for instance, make `options.width` go into the variable named `w`, then we can set the variable name using a colon: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let options = { @@ -402,7 +548,11 @@ let title, width, height; } ``` +<<<<<<< HEAD Так что здесь JavaScript считает, что видит блок кода, отсюда и ошибка. На самом-то деле у нас деструктуризация. +======= +So here JavaScript assumes that we have a code block, that's why there's an error. We want destructuring instead. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Чтобы показать JavaScript, что это не блок кода, мы можем заключить выражение в скобки `(...)`: @@ -418,9 +568,15 @@ alert( title ); // Menu ## Вложенная деструктуризация +<<<<<<< HEAD Если объект или массив содержит другие вложенные объекты или массивы, то мы можем использовать более сложные шаблоны с левой стороны, чтобы извлечь более глубокие свойства. В приведённом ниже коде `options` хранит другой объект в свойстве `size` и массив в свойстве `items`. Шаблон в левой части присваивания имеет такую же структуру, чтобы извлечь данные из них: +======= +If an object or an array contains other nested 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 on the left side of the assignment has the same structure to extract values from them: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let options = { @@ -429,7 +585,11 @@ let options = { height: 200 }, items: ["Cake", "Donut"], +<<<<<<< HEAD extra: true +======= + extra: true +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 }; // деструктуризация разбита на несколько строк для ясности @@ -449,7 +609,11 @@ alert(item1); // Cake alert(item2); // Donut ``` +<<<<<<< HEAD Весь объект `options`, кроме свойства `extra`, которое в левой части отсутствует, присваивается в соответствующие переменные: +======= +All properties of `options` object except `extra` which is absent in the left part, are assigned to corresponding variables: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ![](destructuring-complex.svg) @@ -459,9 +623,15 @@ alert(item2); // Donut ## Умные параметры функций +<<<<<<< HEAD Есть ситуации, когда функция имеет много параметров, большинство из которых не обязательны. Это особенно верно для пользовательских интерфейсов. Представьте себе функцию, которая создаёт меню. Она может иметь ширину, высоту, заголовок, список элементов и так далее. Вот так - плохой способ писать подобные функции: +======= +There are times when a function has 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, an item list and so on. + +Here's a bad way to write such a function: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js function showMenu(title = "Untitled", width = 200, height = 100, items = []) { @@ -469,7 +639,11 @@ function showMenu(title = "Untitled", width = 200, height = 100, items = []) { } ``` +<<<<<<< HEAD В реальной жизни проблема заключается в том, как запомнить порядок всех аргументов. Обычно IDE пытаются помочь нам, особенно если код хорошо документирован, но всё же... Другая проблема заключается в том, как вызвать функцию, когда большинство параметров передавать не надо, и значения по умолчанию вполне подходят. +======= +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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Разве что вот так? @@ -535,7 +709,11 @@ function showMenu({ }) ``` +<<<<<<< HEAD Тогда для объекта с параметрами будет создана переменная `varName` для свойства с именем `incomingProperty` по умолчанию равная `defaultValue`. +======= +Then, for an object of parameters, there will be a variable `varName` for the property `incomingProperty`, with `defaultValue` by default. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Пожалуйста, обратите внимание, что такое деструктурирование подразумевает, что в `showMenu()` будет обязательно передан аргумент. Если нам нужны все значения по умолчанию, то нам следует передать пустой объект: @@ -559,6 +737,7 @@ showMenu(); // Menu 100 200 ## Итого +<<<<<<< HEAD - Деструктуризация позволяет разбивать объект или массив на переменные при присвоении. - Полный синтаксис для объекта: @@ -577,5 +756,24 @@ showMenu(); // Menu 100 200 ``` Первый элемент отправляется в `item1`; второй отправляется в `item2`, все остальные элементы попадают в массив `rest`. +======= +- Destructuring assignment allows for instantly mapping an object or array onto many variables. +- The full object syntax: + ```js + let {prop : varName = defaultValue, ...rest} = 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. + + Object properties that have no mapping are copied to the `rest` object. + +- The full array syntax: + + ```js + let [item1 = defaultValue, item2, ...rest] = array + ``` + + The first item goes to `item1`; the second goes into `item2`, and all the rest makes the array `rest`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 - Можно извлекать данные из вложенных объектов и массивов, для этого левая сторона должна иметь ту же структуру, что и правая. 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 index c479b01055..b7d57ce73b 100644 --- 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 @@ -2,7 +2,17 @@ Поэтому февраль обозначается числом 1. +Here's an example with numbers as date components: + +```js run +//new Date(year, month, date, hour, minute, second, millisecond) +let d1 = new Date(2012, 1, 20, 3, 12); +alert( d1 ); +``` +We could also create a date from a string, like this: + ```js run -let d = new Date(2012, 1, 20, 3, 12); -alert( d ); +//new Date(datastring) +let d2 = new Date("2012-02-20T03:12"); +alert( d2 ); ``` 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 index 05412bd44c..cd9a566346 100644 --- 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 @@ -23,4 +23,6 @@ function getSecondsToday() { let d = new Date(); return d.getHours() * 3600 + d.getMinutes() * 60 + d.getSeconds(); } + +alert( getSecondsToday() ); ``` 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 index 46df738e26..05ade4191e 100644 --- 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 @@ -2,11 +2,19 @@ importance: 5 --- +<<<<<<< HEAD # Сколько сегодня прошло секунд? +======= +# How many seconds have passed today? +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Напишите функцию `getSecondsToday()`, возвращающую количество секунд с начала сегодняшнего дня. +<<<<<<< HEAD Например, если сейчас `10:00`, и не было перехода на зимнее/летнее время, то: +======= +For instance, if now were `10:00 am`, and there was no daylight savings shift, then: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js getSecondsToday() == 36000 // (3600 * 10) 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 index a98fe45c8a..c883d9b89c 100644 --- 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 @@ -40,7 +40,11 @@ alert( formatDate(new Date(new Date - 30 * 1000)) ); // "30 сек. назад" alert( formatDate(new Date(new Date - 5 * 60 * 1000)) ); // "5 мин. назад" +<<<<<<< HEAD // вчерашняя дата вроде 31.12.2016, 20:00 +======= +// yesterday's date like 31.12.2016 20:00 +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 alert( formatDate(new Date(new Date - 86400 * 1000)) ); ``` 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 index 0ecf640e2c..f6b825c25c 100644 --- 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 @@ -20,6 +20,10 @@ alert( formatDate(new Date(new Date - 30 * 1000)) ); // "30 сек. назад" alert( formatDate(new Date(new Date - 5 * 60 * 1000)) ); // "5 мин. назад" +<<<<<<< HEAD // вчерашняя дата вроде 31.12.2016, 20:00 +======= +// yesterday's date like 31.12.16 20:00 +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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 index a03c056025..83d135f497 100644 --- a/1-js/05-data-types/11-date/article.md +++ b/1-js/05-data-types/11-date/article.md @@ -33,6 +33,7 @@ Это -- легковесное численное представление даты. Из таймстампа всегда можно получить дату с помощью `new Date(timestamp)` и преобразовать существующий объект `Date` в таймстамп, используя метод `date.getTime()` (см. ниже). +<<<<<<< HEAD Датам до 1 января 1970 будут соответствовать отрицательные таймстампы, например: ```js run // 31 декабря 1969 года @@ -40,6 +41,15 @@ alert( Dec31_1969 ); ``` +======= + Dates before 01.01.1970 have negative timestamps, e.g.: + ```js run + // 31 Dec 1969 + let Dec31_1969 = new Date(-24 * 3600 * 1000); + alert( Dec31_1969 ); + ``` + +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 `new Date(datestring)` : Если аргумент всего один, и это строка, то из неё "прочитывается" дата. Алгоритм разбора - такой же, как в `Date.parse`, который мы рассмотрим позже. @@ -57,19 +67,34 @@ `new Date(year, month, date, hours, minutes, seconds, ms)` : Создать объект `Date` с заданными компонентами в местном часовом поясе. Обязательны только первые два аргумента. +<<<<<<< HEAD - `year` должен состоять из четырёх цифр. Для совместимости также принимаются 2 цифры и рассматриваются как `19xx`, к примеру, `98` здесь это тоже самое, что и `1998`, но настоятельно рекомендуется всегда использовать 4 цифры. - `month` начинается с `0` (январь) по `11` (декабрь). - Параметр `date` здесь представляет собой день месяца. Если параметр не задан, то принимается значение `1`. - Если параметры `hours/minutes/seconds/ms` отсутствуют, их значением становится `0`. +======= + - The `year` should have 4 digits. For compatibility, 2 digits are also accepted and considered `19xx`, e.g. `98` is the same as `1998` here, but always using 4 digits is strongly encouraged. + - 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`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Например: ```js +<<<<<<< HEAD new Date(2011, 0, 1, 0, 0, 0, 0); // // 1 Jan 2011, 00:00:00 new Date(2011, 0, 1); // то же самое, так как часы и проч. равны 0 ``` Максимальная точность – 1 мс (до 1/1000 секунды): +======= + 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 maximal precision is 1 ms (1/1000 sec): +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let date = new Date(2011, 0, 1, 2, 3, 4, 567); @@ -123,8 +148,13 @@ alert( date.getUTCHours() ); [getTime()](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Date/getTime) : Для заданной даты возвращает таймстамп - количество миллисекунд, прошедших с 1 января 1970 года UTC+0. +<<<<<<< HEAD [getTimezoneOffset()](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Date/getTimezoneOffset) : Возвращает разницу в минутах между UTC и местным часовым поясом: +======= +[getTimezoneOffset()](mdn:js/Date/getTimezoneOffset) +: Returns the difference between UTC and the local time zone, in minutes: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run // если вы в часовом поясе UTC-1, то выводится 60 @@ -348,7 +378,11 @@ let time1 = 0; let time2 = 0; *!* +<<<<<<< HEAD // bench(diffSubtract) и bench(diffGetTime) поочерёдно запускаются 10 раз +======= +// run bench(diffSubtract) and bench(diffGetTime) each 10 times alternating +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 for (let i = 0; i < 10; i++) { time1 += bench(diffSubtract); time2 += bench(diffGetTime); @@ -376,7 +410,11 @@ for (let i = 0; i < 10; i++) { ```warn header="Будьте осторожны с микробенчмарками" Современные интерпретаторы JavaScript выполняют множество оптимизаций. Они могут повлиять на результаты "искусственных тестов" по сравнению с "нормальным использованием", особенно если мы тестируем что-то очень маленькое, например, работу оператора или встроенной функции. Поэтому если хотите серьёзно понять производительность, пожалуйста, изучите, как работают интерпретаторы JavaScript. И тогда вам, вероятно, уже не понадобятся микробенчмарки. +<<<<<<< HEAD Отличный набор статей о V8 можно найти на . +======= +The great pack of articles about V8 can be found at . +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` ## Разбор строки с датой @@ -385,10 +423,17 @@ for (let i = 0; i < 10; i++) { Формат строки должен быть следующим: `YYYY-MM-DDTHH:mm:ss.sssZ`, где: +<<<<<<< HEAD - `YYYY-MM-DD` -- это дата: год-месяц-день. - Символ `"T"` используется в качестве разделителя. - `HH:mm:ss.sss` -- время: часы, минуты, секунды и миллисекунды. - Необязательная часть `'Z'` обозначает часовой пояс в формате `+-hh:mm`. Если указать просто букву `Z`, то получим UTC+0. +======= +- `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` would mean UTC+0. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Возможны и более короткие варианты, например, `YYYY-MM-DD` или `YYYY-MM`, или даже `YYYY`. @@ -407,7 +452,7 @@ alert(ms); // 1327611110417 (таймстамп) ```js run let date = new Date( Date.parse('2012-01-26T13:51:50.417-07:00') ); -alert(date); +alert(date); ``` ## Итого @@ -424,10 +469,17 @@ alert(date); Порой нам нужно измерить время с большей точностью. Собственными средствами JavaScript измерять время в микросекундах (одна миллионная секунды) нельзя, но в большинстве сред такая возможность есть. К примеру, в браузерах есть метод [performance.now()](https://developer.mozilla.org/ru/docs/Web/API/Performance/now), возвращающий количество миллисекунд с начала загрузки страницы с точностью до микросекунд (3 цифры после точки): ```js run +<<<<<<< HEAD alert(`Загрузка началась ${performance.now()}мс назад`); // Получаем что-то вроде: "Загрузка началась 34731.26000000001мс назад" // .26 –- это микросекунды (260 микросекунд) // корректными являются только первые три цифры после точки, а остальные -- это ошибка точности +======= +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, only the first 3 are correct +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` В Node.js для этого предусмотрен модуль `microtime` и ряд других способов. Технически почти любое устройство или среда позволяет добиться большей точности, просто её нет в объекте `Date`. diff --git a/1-js/05-data-types/12-json/article.md b/1-js/05-data-types/12-json/article.md index 8865c342d7..653ad865f3 100644 --- a/1-js/05-data-types/12-json/article.md +++ b/1-js/05-data-types/12-json/article.md @@ -27,7 +27,11 @@ alert(user); // {name: "John", age: 30} ## JSON.stringify +<<<<<<< HEAD [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 или любом другом языке. +======= +The [JSON](https://en.wikipedia.org/wiki/JSON) (JavaScript Object Notation) is a general format to represent values and objects. It is described as in [RFC 4627](https://tools.ietf.org/html/rfc4627) standard. Initially it was made for JavaScript, but many other languages have libraries to handle it as well. So it's easy to use JSON for data exchange when the client uses JavaScript and the server is written on Ruby/PHP/Java/Whatever. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 JavaScript предоставляет методы: @@ -41,7 +45,7 @@ let student = { age: 30, isAdmin: false, courses: ['html', 'css', 'js'], - wife: null + spouse: null }; *!* @@ -58,7 +62,7 @@ alert(json); "age": 30, "isAdmin": false, "courses": ["html", "css", "js"], - "wife": null + "spouse": null } */ */!* @@ -103,9 +107,15 @@ JSON является независимой от языка специфика А именно: +<<<<<<< HEAD - Свойства-функции (методы). - Символьные ключи и значения. - Свойства, содержащие `undefined`. +======= +- Function properties (methods). +- Symbolic keys and values. +- Properties that store `undefined`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let user = { @@ -328,7 +338,13 @@ alert(JSON.stringify(user, null, 2)); */ ``` +<<<<<<< HEAD Третьим аргументом также может быть строка. В этом случае строка будет использоваться для отступа вместо ряда пробелов. +======= +The third argument can also be a string. In this case, the string is used for indentation instead of a number of spaces. + +The `space` parameter is used solely for logging and nice-output purposes. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Параметр `space` применяется исключительно для логирования и красивого вывода. @@ -427,9 +443,9 @@ alert( numbers[1] ); // 1 Или для вложенных объектов: ```js run -let user = '{ "name": "John", "age": 35, "isAdmin": false, "friends": [0,1,2,3] }'; +let userData = '{ "name": "John", "age": 35, "isAdmin": false, "friends": [0,1,2,3] }'; -user = JSON.parse(user); +let user = JSON.parse(userData); alert( user.friends[1] ); // 1 ``` @@ -451,7 +467,11 @@ let json = `{ Кроме того, JSON не поддерживает комментарии. Добавление комментария в JSON делает его недействительным. +<<<<<<< HEAD Существует ещё один формат [JSON5](https://json5.org/), который поддерживает ключи без кавычек, комментарии и т.д. Но это самостоятельная библиотека, а не спецификация языка. +======= +There's another format named [JSON5](https://json5.org/), which allows unquoted keys, comments etc. But this is a standalone library, not in the specification of the language. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Обычный JSON настолько строг не потому, что его разработчики ленивы, а потому, что позволяет легко, надёжно и очень быстро реализовывать алгоритм кодирования и чтения. 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 7283fe945c..0d027f00f5 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 @@ -38,4 +38,8 @@ P.S. Надо ли говорить, что решение по формуле Вариант с циклом – второй по скорости. Он быстрее рекурсии, так как операций сложения столько же, но нет дополнительных вычислительных затрат на организацию вложенных вызовов. Поэтому рекурсия в данном случае работает медленнее всех. +<<<<<<< HEAD P.P.S. Некоторые движки поддерживают оптимизацию "хвостового вызова": если рекурсивный вызов является самым последним в функции, без каких-либо других вычислений, то внешней функции не нужно будет возобновлять выполнение и не нужно запоминать контекст его выполнения. В итоге требования к памяти снижаются. Но если JavaScript-движок не поддерживает это (большинство не поддерживают), будет ошибка: максимальный размер стека превышен, так как обычно существует ограничение на максимальный размер стека. +======= +P.P.S. Some engines support the "tail call" optimization: if a recursive call is the very last one in the function, with no other calculations performed, then the outer function will not need to resume the execution, so the engine doesn't need to remember its execution context. That removes the burden on memory. But if the JavaScript engine does not support tail call optimization (most of them don't), there will be an error: maximum stack size exceeded, because there's usually a limitation on the total stack size. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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 9080b6b685..36abb73b2b 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,4 +1,8 @@ +<<<<<<< HEAD По определению факториал `n!` можно записать как `n * (n-1)!`. +======= +By definition, a factorial `n!` can be written as `n * (n-1)!`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Другими словами, `factorial(n)` можно получить как `n` умноженное на результат `factorial(n-1)`. И результат для `n-1`, в свою очередь, может быть вычислен рекурсивно и так далее до `1`. 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 f198f7bad4..0c85b31bcd 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 @@ -33,7 +33,11 @@ printReverseList(list); # С использованием цикла +<<<<<<< HEAD Вариант с использованием цикла сложнее, чем в предыдущей задаче. +======= +The loop variant is also a little bit more complicated than the direct output. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Нет способа сразу получить последнее значение в списке `list`. Мы также не можем "вернуться назад", к предыдущему элементу списка. diff --git a/1-js/06-advanced-functions/01-recursion/article.md b/1-js/06-advanced-functions/01-recursion/article.md index 61efd49707..167812bf07 100644 --- a/1-js/06-advanced-functions/01-recursion/article.md +++ b/1-js/06-advanced-functions/01-recursion/article.md @@ -132,7 +132,11 @@ function pow(x, n) { +<<<<<<< HEAD Это только начало выполнения функции. Условие `n == 1` ложно, поэтому выполнение идёт во вторую ветку `if`: +======= +That's when the function starts to execute. The condition `n == 1` is falsy, so the flow continues into the second branch of `if`: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run function pow(x, n) { @@ -185,7 +189,17 @@ alert( pow(2, 3) ); Новый контекст выполнения находится на вершине стека (и выделен жирным), а предыдущие запомненные контексты -- под ним. +<<<<<<< HEAD Когда выполнение подвызова закончится, можно будет легко вернуться назад, потому что контекст сохраняет как переменные, так и точное место кода, в котором он остановился. Слово "строка" на рисунках условно, на самом деле запоминается более точное место в цепочке команд. +======= +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. + +```smart +Here in the picture we use the word "line", as in our example there's only one subcall in line, but generally a single line of code may contain multiple subcalls, like `pow(…) + pow(…) + somethingElse(…)`. + +So it would be more precise to say that the execution resumes "immediately after the subcall". +``` +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ### pow(2, 1) @@ -279,7 +293,11 @@ function pow(x, n) { **Любая рекурсия может быть переделана в цикл. Как правило, вариант с циклом будет эффективнее.** +<<<<<<< HEAD Но переделка рекурсии в цикл может быть нетривиальной, особенно когда в функции в зависимости от условий используются различные рекурсивные подвызовы, результаты которых объединяются, или когда ветвление более сложное. Оптимизация может быть ненужной и совершенно нестоящей усилий. +======= +...But sometimes the rewrite is non-trivial, especially when a 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Часто код с использованием рекурсии более короткий, лёгкий для понимания и поддержки. Оптимизация требуется не везде, как правило, нам важен хороший код, поэтому она и используется. @@ -296,7 +314,7 @@ let company = { salary: 1000 }, { name: 'Alice', - salary: 600 + salary: 1600 }], development: { @@ -343,8 +361,13 @@ let company = { ```js run +<<<<<<< HEAD let company = { // тот же самый объект, сжатый для краткости sales: [{name: 'John', salary: 1000}, {name: 'Alice', salary: 600 }], +======= +let company = { // the same object, compressed for brevity + sales: [{name: 'John', salary: 1000}, {name: 'Alice', salary: 1600 }], +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 development: { sites: [{name: 'Peter', salary: 2000}, {name: 'Alex', salary: 1800 }], internals: [{name: 'Jack', salary: 1300}] @@ -366,7 +389,7 @@ function sumSalaries(department) { } */!* -alert(sumSalaries(company)); // 6700 +alert(sumSalaries(company)); // 7700 ``` Код краток и прост для понимания (надеюсь?). В этом сила рекурсии. Она работает на любом уровне вложенности отделов. @@ -456,7 +479,11 @@ list.next.next.next = { value: 4 }; list.next.next.next.next = null; ``` +<<<<<<< HEAD Здесь мы можем ещё лучше увидеть, что есть несколько объектов, каждый из которых имеет `value` и `next`, указывающий на соседа. Переменная `list` является первым объектом в цепочке, поэтому, следуя по указателям `next` из неё, мы можем попасть в любой элемент. +======= +Here we can even more clearly 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Список можно легко разделить на несколько частей и впоследствии объединить обратно: @@ -531,7 +558,11 @@ list.next = list.next.next; list = { value, next -> list } ``` +<<<<<<< HEAD Деревья, такие как дерево HTML-элементов или дерево отделов из этой главы, также являются рекурсивными: у них есть ветви, и каждая ветвь может содержать другие ветви. +======= + Trees like HTML elements tree or the department tree from this chapter are also naturally recursive: they have branches and every branch can have other branches. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Как мы видели в примере `sumSalary`, рекурсивные функции могут быть использованы для прохода по ним. 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/article.md similarity index 68% rename from 1-js/06-advanced-functions/02-rest-parameters-spread-operator/article.md rename to 1-js/06-advanced-functions/02-rest-parameters-spread/article.md index 6aa76bddb9..ff5d8b1514 100644 --- a/1-js/06-advanced-functions/02-rest-parameters-spread-operator/article.md +++ b/1-js/06-advanced-functions/02-rest-parameters-spread/article.md @@ -1,4 +1,8 @@ +<<<<<<< HEAD:1-js/06-advanced-functions/02-rest-parameters-spread-operator/article.md # Остаточные параметры и оператор расширения +======= +# Rest parameters and spread syntax +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/06-advanced-functions/02-rest-parameters-spread/article.md Многие встроенные функции JavaScript поддерживают произвольное количество аргументов. @@ -23,7 +27,11 @@ function sum(a, b) { alert( sum(1, 2, 3, 4, 5) ); ``` +<<<<<<< HEAD:1-js/06-advanced-functions/02-rest-parameters-spread-operator/article.md Лишние аргументы не вызовут ошибку. Но, конечно, посчитаются только первые два. +======= +There will be no error because of "excessive" arguments. But of course in the result only the first two will be counted, so the result in the code above is `3`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/06-advanced-functions/02-rest-parameters-spread/article.md *Остаточные параметры* могут быть обозначены через три точки `...`. Буквально это значит: "собери оставшиеся параметры и положи их в массив". @@ -122,7 +130,11 @@ f(1); // 1 ```` +<<<<<<< HEAD:1-js/06-advanced-functions/02-rest-parameters-spread-operator/article.md ## Оператор расширения [#spread-operator] +======= +## Spread syntax [#spread-syntax] +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/06-advanced-functions/02-rest-parameters-spread/article.md Мы узнали, как получить массив из списка параметров. @@ -148,7 +160,11 @@ alert( Math.max(arr) ); // NaN Конечно, мы можем вводить числа вручную : `Math.max(arr[0], arr[1], arr[2])`. Но, во-первых, это плохо выглядит, а, во-вторых, мы не всегда знаем, сколько будет аргументов. Их может быть как очень много, так и не быть совсем. +<<<<<<< HEAD:1-js/06-advanced-functions/02-rest-parameters-spread-operator/article.md И тут нам поможет *оператор расширения*. Он похож на остаточные параметры – тоже использует `...`, но делает совершенно противоположное. +======= +*Spread syntax* to the rescue! It looks similar to rest parameters, also using `...`, but does quite the opposite. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/06-advanced-functions/02-rest-parameters-spread/article.md Когда `...arr` используется при вызове функции, он "расширяет" перебираемый объект `arr` в список аргументов. @@ -169,7 +185,12 @@ let arr2 = [8, 3, -8, 1]; alert( Math.max(...arr1, ...arr2) ); // 8 ``` +<<<<<<< HEAD:1-js/06-advanced-functions/02-rest-parameters-spread-operator/article.md Мы даже можем комбинировать оператор расширения с обычными значениями: +======= +We can even combine the spread syntax with normal values: + +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/06-advanced-functions/02-rest-parameters-spread/article.md ```js run let arr1 = [1, -2, 3, 4]; @@ -178,7 +199,11 @@ let arr2 = [8, 3, -8, 1]; alert( Math.max(1, ...arr1, 2, ...arr2, 25) ); // 25 ``` +<<<<<<< HEAD:1-js/06-advanced-functions/02-rest-parameters-spread-operator/article.md Оператор расширения можно использовать и для слияния массивов: +======= +Also, the spread syntax can be used to merge arrays: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/06-advanced-functions/02-rest-parameters-spread/article.md ```js run let arr = [3, 5, 1]; @@ -191,9 +216,15 @@ let merged = [0, ...arr, 2, ...arr2]; alert(merged); // 0,3,5,1,2,8,9,15 (0, затем arr, затем 2, в конце arr2) ``` +<<<<<<< HEAD:1-js/06-advanced-functions/02-rest-parameters-spread-operator/article.md В примерах выше мы использовали массив, чтобы продемонстрировать свойства оператора расширения, но он работает с любым перебираемым объектом. Например, оператор расширения подойдёт для того, чтобы превратить строку в массив символов: +======= +In the examples above we used an array to demonstrate the spread syntax, but any iterable will do. + +For instance, here we use the spread syntax to turn the string into array of characters: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/06-advanced-functions/02-rest-parameters-spread/article.md ```js run let str = "Привет"; @@ -201,7 +232,11 @@ let str = "Привет"; alert( [...str] ); // П,р,и,в,е,т ``` +<<<<<<< HEAD:1-js/06-advanced-functions/02-rest-parameters-spread-operator/article.md Посмотрим, что происходит. Под капотом оператор расширения использует итераторы, чтобы перебирать элементы. Так же, как это делает `for..of`. +======= +The spread syntax internally uses iterators to gather elements, the same way as `for..of` does. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/06-advanced-functions/02-rest-parameters-spread/article.md Цикл `for..of` перебирает строку как последовательность символов, поэтому из `...str` получается `"П", "р", "и", "в", "е", "т"`. Получившиеся символы собираются в массив при помощи стандартного объявления массива: `[...str]`. @@ -218,25 +253,97 @@ alert( Array.from(str) ); // П,р,и,в,е,т Но между `Array.from(obj)` и `[...obj]` есть разница: +<<<<<<< HEAD:1-js/06-advanced-functions/02-rest-parameters-spread-operator/article.md - `Array.from` работает как с псевдомассивами, так и с итерируемыми объектами - Оператор расширения работает только с итерируемыми объектами +======= +- `Array.from` operates on both array-likes and iterables. +- The spread syntax works only with iterables. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/06-advanced-functions/02-rest-parameters-spread/article.md Выходит, что если нужно сделать из чего угодно массив, то `Array.from` — более универсальный метод. +<<<<<<< HEAD:1-js/06-advanced-functions/02-rest-parameters-spread-operator/article.md ## Итого Когда мы видим `"..."` в коде, это могут быть как остаточные параметры, так и оператор расширения. +======= +## Copy an array/object + +Remember when we talked about `Object.assign()` [in the past](info:object-copy#cloning-and-merging-object-assign)? + +It is possible to do the same thing with the spread syntax. + +```js run +let arr = [1, 2, 3]; + +*!* +let arrCopy = [...arr]; // spread the array into a list of parameters + // then put the result into a new array +*/!* + +// do the arrays have the same contents? +alert(JSON.stringify(arr) === JSON.stringify(arrCopy)); // true + +// are the arrays equal? +alert(arr === arrCopy); // false (not same reference) + +// modifying our initial array does not modify the copy: +arr.push(4); +alert(arr); // 1, 2, 3, 4 +alert(arrCopy); // 1, 2, 3 +``` + +Note that it is possible to do the same thing to make a copy of an object: + +```js run +let obj = { a: 1, b: 2, c: 3 }; + +*!* +let objCopy = { ...obj }; // spread the object into a list of parameters + // then return the result in a new object +*/!* + +// do the objects have the same contents? +alert(JSON.stringify(obj) === JSON.stringify(objCopy)); // true + +// are the objects equal? +alert(obj === objCopy); // false (not same reference) + +// modifying our initial object does not modify the copy: +obj.d = 4; +alert(JSON.stringify(obj)); // {"a":1,"b":2,"c":3,"d":4} +alert(JSON.stringify(objCopy)); // {"a":1,"b":2,"c":3} +``` + +This way of copying an object is much shorter than `let objCopy = Object.assign({}, obj)` or for an array `let arrCopy = Object.assign([], arr)` so we prefer to use it whenever we can. + + +## Summary + +When we see `"..."` in the code, it is either rest parameters or the spread syntax. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/06-advanced-functions/02-rest-parameters-spread/article.md Как отличить их друг от друга: +<<<<<<< HEAD:1-js/06-advanced-functions/02-rest-parameters-spread-operator/article.md - Если `...` располагается в конце списка параметров функции, то это "остаточные параметры". Он собирает остальные неуказанные аргументы и делает из них массив. - Если `...` встретился в вызове функции или где-либо ещё, то это "оператор расширения". Он извлекает элементы из массива. +======= +- 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 syntax" and expands an array into a list. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/06-advanced-functions/02-rest-parameters-spread/article.md Полезно запомнить: +<<<<<<< HEAD:1-js/06-advanced-functions/02-rest-parameters-spread-operator/article.md - Остаточные параметры используются, чтобы создавать новые функции с неопределённым числом аргументов. - С помощью оператора расширения можно вставить массив в функцию, которая по умолчанию работает с обычным списком аргументов. +======= +- Rest parameters are used to create functions that accept any number of arguments. +- The spread syntax is used to pass an array to functions that normally require a list of many arguments. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/06-advanced-functions/02-rest-parameters-spread/article.md Вместе эти конструкции помогают легко преобразовывать наборы значений в массивы и обратно. 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 index f1119ba08e..7daaa85361 100644 --- 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 @@ -1,5 +1,13 @@ +<<<<<<< HEAD Ответ: **Pete**. Функция получает внешние переменные в том виде, в котором они находятся сейчас, она использует самые последние значения. Старые значения переменных нигде не сохраняются. Когда функция обращается к переменной, она берет текущее значение из своего или внешнего лексического окружения. +======= +The answer is: **Pete**. + +A function gets outer variables as they are now, it uses the most recent values. + +Old variable values are not saved anywhere. When a function wants a variable, it takes the current value from its own Lexical Environment or the outer one. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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 index 00d5f10099..028bb27da4 100644 --- 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 @@ -2,9 +2,15 @@ importance: 5 --- +<<<<<<< HEAD # Учитывает ли функция последние изменения? Функция `sayHi` использует имя внешней переменной. Какое значение будет использоваться при выполнении функции? +======= +# Does a function pickup latest changes? + +The function sayHi uses an external variable name. When the function runs, which value is it going to use? +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js let name = "John"; @@ -15,9 +21,18 @@ function sayHi() { name = "Pete"; +<<<<<<< HEAD sayHi(); // что будет показано: "John" или "Pete"? ``` Такие ситуации встречаются как при разработке для браузера, так и для сервера. Функция может быть назначена на выполнение позже, чем она была создана, например, после действия пользователя или сетевого запроса. Итак, вопрос: учитывает ли она последние изменения? +======= +sayHi(); // what will it show: "John" or "Pete"? +``` + +Such situations are common both in browser and server-side development. A function may be scheduled to execute later than it is created, for instance after a user action or a network request. + +So, the question is: does it pick up the latest changes? +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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 index fe79b85d8c..af067a1a74 100644 --- 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 @@ -1,14 +1,26 @@ +<<<<<<< HEAD Давайте посмотрим, что происходит внутри `makeArmy`, и решение станет очевидным. 1. Она создаёт пустой массив `shooters`: +======= +Let's examine what exactly happens inside `makeArmy`, and the solution will become obvious. + +1. It creates an empty array `shooters`: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js let shooters = []; ``` +<<<<<<< HEAD 2. В цикле заполняет его `shooters.push(function...)`. Каждый элемент -- это функция, так что получится такой массив: +======= +2. Fills it with functions via `shooters.push(function)` in the loop. + + Every element is a function, so the resulting array looks like this: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js no-beautify shooters = [ @@ -25,6 +37,7 @@ ]; ``` +<<<<<<< HEAD 3. Функция возвращает массив. Позже вызов `army[5]()` получит элемент `army[5]` из массива (это будет функция) и вызовет её. @@ -37,20 +50,42 @@ Если мы посмотрим в исходный код: +======= +3. The array is returned from the function. + + Then, later, the call to any member, e.g. `army[5]()` will get the element `army[5]` from the array (which is a function) and calls it. + + Now why do all such functions show the same value, `10`? + + That's because there's no local variable `i` inside `shooter` functions. When such a function is called, it takes `i` from its outer lexical environment. + + Then, what will be the value of `i`? + + If we look at the source: + +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js function makeArmy() { ... let i = 0; while (i < 10) { +<<<<<<< HEAD let shooter = function() { // функция shooter alert( i ); // должна выводить порядковый номер }; shooters.push(shooter); // и добавлять стрелка в массив +======= + let shooter = function() { // shooter function + alert( i ); // should show its number + }; + shooters.push(shooter); // add function to the array +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 i++; } ... } ``` +<<<<<<< HEAD ...Мы увидим, что оно живёт в лексическом окружении, связанном с текущим вызовом `makeArmy()`. Но, когда вызывается `army[5]()`, `makeArmy` уже завершила свою работу, и последнее значение `i`: `10` (конец цикла `while`). @@ -60,6 +95,17 @@ Как вы можете видеть выше, на каждой итерации блока `while {...}` создается новое лексическое окружение. Чтобы исправить это, мы можем скопировать значение `i` в переменную внутри блока `while {...}`, например, так: +======= + + We can see that all `shooter` functions are created in the lexical environment of `makeArmy()` function. But when `army[5]()` is called, `makeArmy` has already finished its job, and the final value of `i` is `10` (`while` stops at `i=10`). + + As the result, all `shooter` functions get the same value from the outer lexical environment and that is, the last value, `i=10`. + + ![](lexenv-makearmy-empty.svg) + + As you can see above, on each iteration of a `while {...}` block, a new lexical environment is created. So, to fix this, we can copy the value of `i` into a variable within the `while {...}` block, like this: + +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run function makeArmy() { let shooters = []; @@ -69,8 +115,13 @@ *!* let j = i; */!* +<<<<<<< HEAD let shooter = function() { // функция shooter alert( *!*j*/!* ); // должна выводить порядковый номер +======= + let shooter = function() { // shooter function + alert( *!*j*/!* ); // should show its number +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 }; shooters.push(shooter); i++; @@ -81,6 +132,7 @@ let army = makeArmy(); +<<<<<<< HEAD // теперь код работает правильно army[0](); // 0 army[5](); // 5 @@ -94,16 +146,38 @@ Этой проблемы также можно было бы избежать, если бы мы использовали `for` в начале, например, так: +======= + // Now the code works correctly + army[0](); // 0 + army[5](); // 5 + ``` + + Here `let j = i` declares an "iteration-local" variable `j` and copies `i` into it. Primitives are copied "by value", so we actually get an independent copy of `i`, belonging to the current loop iteration. + + The shooters work correctly, because the value of `i` now lives a little bit closer. Not in `makeArmy()` Lexical Environment, but in the Lexical Environment that corresponds to the current loop iteration: + + ![](lexenv-makearmy-while-fixed.svg) + + Such a problem could also be avoided if we used `for` in the beginning, like this: + +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run demo function makeArmy() { let shooters = []; *!* +<<<<<<< HEAD for (let i = 0; i < 10; i++) { */!* let shooter = function() { // функция shooter alert( i ); // должна выводить порядковый номер +======= + for(let i = 0; i < 10; i++) { + */!* + let shooter = function() { // shooter function + alert( i ); // should show its number +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 }; shooters.push(shooter); } @@ -116,6 +190,7 @@ army[0](); // 0 army[5](); // 5 ``` +<<<<<<< HEAD По сути, это то же самое, поскольку `for` на каждой итерации создает новое лексическое окружение со своей переменной `i`. Поэтому функция `shooter`, создаваемая на каждой итерации, ссылается на свою собственную переменную `i`, причем именно с этой итерации. @@ -126,3 +201,16 @@ Что ж, если бы вы могли легко ответить на вопрос из задачи, вы бы не стали читать решение. Так что, должно быть, эта задача помогла вам лучше понять суть дела. Кроме того, действительно встречаются случаи, когда человек предпочитает `while`, а не `for`, и другие сценарии, где такие проблемы реальны. +======= + + That's essentially the same, because `for` on each iteration generates a new lexical environment, with its own variable `i`. So `shooter` generated in every iteration references its own `i`, from that very iteration. + + ![](lexenv-makearmy-for-fixed.svg) + +Now, as you've put so much effort into reading this, and the final recipe is so simple - just use `for`, you may wonder -- was it worth that? + +Well, if you could easily answer the question, you wouldn't read the solution. So, hopefully this task must have helped you to understand things a bit better. + +Besides, there are indeed cases when one prefers `while` to `for`, and other scenarios, where such problems are real. + +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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 index dd1df3cbba..78b5a7b309 100644 --- 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 @@ -2,11 +2,19 @@ importance: 5 --- +<<<<<<< HEAD # Армия функций Следующий код создаёт массив из стрелков (`shooters`). Каждая функция предназначена выводить их порядковые номера. Но что-то пошло не так... +======= +# Army of functions + +The following code creates an array of `shooters`. + +Every function is meant to output its number. But something is wrong... +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run function makeArmy() { @@ -14,6 +22,7 @@ function makeArmy() { let i = 0; while (i < 10) { +<<<<<<< HEAD let shooter = function() { // функция shooter alert( i ); // должна выводить порядковый номер }; @@ -23,11 +32,23 @@ function makeArmy() { // ...а в конце вернуть массив из всех стрелков return shooters; +======= + let shooter = function() { // create a shooter function, + alert( i ); // that should show its number + }; + shooters.push(shooter); // and add it to the array + i++; + } + + // ...and return the array of shooters + return shooters; +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 } let army = makeArmy(); *!* +<<<<<<< HEAD // все стрелки выводят 10 вместо их порядковых номеров (0, 1, 2, 3...) army[0](); // 10 от стрелка с порядковым номером 0 army[1](); // 10 от стрелка с порядковым номером 1 @@ -38,4 +59,16 @@ army[2](); // 10 ...и т.д. Почему у всех стрелков одинаковые номера? Почините код, чтобы он работал как задумано. +======= +// all shooters show 10 instead of their numbers 0, 1, 2, 3... +army[0](); // 10 from the shooter number 0 +army[1](); // 10 from the shooter number 1 +army[2](); // 10 ...and so on. +*/!* +``` + +Why do all of the shooters show the same value? + +Fix the code so that they work as intended. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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 index f5e013d179..67c254dea4 100644 --- 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 @@ -1,3 +1,4 @@ +<<<<<<< HEAD Ответ: **Pete**. Функция `work()` в приведенном ниже коде получает `name` из места его происхождения через ссылку на внешнее лексическое окружение: @@ -7,3 +8,14 @@ Таким образом, в результате мы получаем `"Pete"`. Но если бы в `makeWorker()` не было `let name`, то поиск шел бы снаружи и брал глобальную переменную, что мы видим из приведенной выше цепочки. В этом случае результатом было бы `"John"`. +======= +The answer is: **Pete**. + +The `work()` function in the code below gets `name` from the place of its origin through the outer lexical environment reference: + +![](lexenv-nested-work.svg) + +So, the result is `"Pete"` here. + +But if there were no `let name` in `makeWorker()`, then the search would go outside and take the global variable as we can see from the chain above. In that case the result would be `"John"`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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 index 6623f93304..15b0f66c65 100644 --- 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 @@ -2,11 +2,19 @@ importance: 5 --- +<<<<<<< HEAD # Какие переменные доступны? Приведенная ниже функция `makeWorker` создает другую функцию и возвращает ее. Эта новая функция может быть вызвана из другого места. Будет ли она иметь доступ к внешним переменным из места своего создания, или из места вызова, или из обоих мест? +======= +# Which variables are available? + +The function `makeWorker` below makes another function and returns it. That new function can be called from somewhere else. + +Will it have access to the outer variables from its creation place, or the invocation place, or both? +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js function makeWorker() { @@ -19,6 +27,7 @@ function makeWorker() { let name = "John"; +<<<<<<< HEAD // создаём функцию let work = makeWorker(); @@ -27,3 +36,13 @@ work(); // что будет показано? ``` Какое значение будет показано? "Pete" или "John"? +======= +// create a function +let work = makeWorker(); + +// call it +work(); // what will it show? +``` + +Which value it will show? "Pete" or "John"? +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/1-js/06-advanced-functions/03-closure/3-counter-independent/solution.md b/1-js/06-advanced-functions/03-closure/3-counter-independent/solution.md index fd160e134b..277703c36f 100644 --- a/1-js/06-advanced-functions/03-closure/3-counter-independent/solution.md +++ b/1-js/06-advanced-functions/03-closure/3-counter-independent/solution.md @@ -1,5 +1,13 @@ +<<<<<<< HEAD Ответ: **0,1.** Функции `counter` и `counter2` созданы разными вызовами `makeCounter`. Так что у них независимые внешние лексические окружения, у каждого из которых свой собственный `count`. +======= +The answer: **0,1.** + +Functions `counter` and `counter2` are created by different invocations of `makeCounter`. + +So they have independent outer Lexical Environments, each one has its own `count`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/1-js/06-advanced-functions/03-closure/3-counter-independent/task.md b/1-js/06-advanced-functions/03-closure/3-counter-independent/task.md index 6057c2c9eb..76ec45a016 100644 --- a/1-js/06-advanced-functions/03-closure/3-counter-independent/task.md +++ b/1-js/06-advanced-functions/03-closure/3-counter-independent/task.md @@ -2,11 +2,19 @@ importance: 5 --- +<<<<<<< HEAD # Независимы ли счётчики? Здесь мы делаем два счётчика: `counter` и `counter2`, используя одну и ту же функцию `makeCounter`. Они независимы? Что покажет второй счётчик? `0,1` или `2,3` или что-то ещё? +======= +# Are counters independent? + +Here we make two counters: `counter` and `counter2` using the same `makeCounter` function. + +Are they independent? What is the second counter going to show? `0,1` or `2,3` or something else? +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js function makeCounter() { 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 index 2c70a5c533..f3883d5ec5 100644 --- 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 @@ -1,7 +1,13 @@ +<<<<<<< HEAD Несомненно, он отлично будет работать. Обе вложенные функции были созданы с одним и тем же внешним лексическим окружением, так что они имеют доступ к одной и той же переменной `count`: +======= +Surely it will work just fine. + +Both nested functions are created within the same outer Lexical Environment, so they share access to the same `count` variable: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run function Counter() { @@ -10,7 +16,11 @@ function Counter() { this.up = function() { return ++count; }; +<<<<<<< HEAD +======= + +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 this.down = function() { return --count; }; diff --git a/1-js/06-advanced-functions/03-closure/5-function-in-if/solution.md b/1-js/06-advanced-functions/03-closure/5-function-in-if/solution.md index 04f27324db..cf9a549217 100644 --- a/1-js/06-advanced-functions/03-closure/5-function-in-if/solution.md +++ b/1-js/06-advanced-functions/03-closure/5-function-in-if/solution.md @@ -1,3 +1,9 @@ +<<<<<<< HEAD Результатом будет **ошибка**. Функция `sayHi` объявлена внутри `if`, так что она живёт только внутри этого блока. Снаружи нет `sayHi`. +======= +The result is **an error**. + +The function `sayHi` is declared inside the `if`, so it only lives inside it. There is no `sayHi` outside. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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 index 9f2a9fcde6..f619713acd 100644 --- 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 @@ -1,6 +1,7 @@ importance: 5 --- +<<<<<<< HEAD # Функция внутри if Посмотрите на код. Какой будет результат у вызова на последней строке? @@ -8,6 +9,13 @@ importance: 5 **Обратите внимание:** результат зависит от режима выполнения кода. Здесь используется строгий режим `"use strict"`. ```js +======= +# Function in if + +Look at the code. What will be the result of the call at the last line? + +```js run +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 let phrase = "Hello"; if (true) { diff --git a/1-js/06-advanced-functions/03-closure/6-closure-sum/solution.md b/1-js/06-advanced-functions/03-closure/6-closure-sum/solution.md index 2bcb5efbb0..595649cd32 100644 --- a/1-js/06-advanced-functions/03-closure/6-closure-sum/solution.md +++ b/1-js/06-advanced-functions/03-closure/6-closure-sum/solution.md @@ -1,12 +1,22 @@ +<<<<<<< HEAD Чтобы вторые скобки заработали, первые -- должны вернуть функцию. Вот так: +======= +For the second parentheses to work, the first ones must return a function. + +Like this: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run function sum(a) { return function(b) { +<<<<<<< HEAD return a + b; // берёт "a" из внешнего лексического окружения +======= + return a + b; // takes "a" from the outer lexical environment +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 }; } diff --git a/1-js/06-advanced-functions/03-closure/6-closure-sum/task.md b/1-js/06-advanced-functions/03-closure/6-closure-sum/task.md index 5c378601ff..d18f968176 100644 --- a/1-js/06-advanced-functions/03-closure/6-closure-sum/task.md +++ b/1-js/06-advanced-functions/03-closure/6-closure-sum/task.md @@ -2,6 +2,7 @@ importance: 4 --- +<<<<<<< HEAD # Сумма с помощью замыканий Напишите функцию `sum`, которая работает таким образом: `sum(a)(b) = a+b`. @@ -9,6 +10,15 @@ importance: 4 Да, именно таким образом, используя двойные круглые скобки (не опечатка). Например: +======= +# Sum with closures + +Write function `sum` that works like this: `sum(a)(b) = a+b`. + +Yes, exactly this way, using double parentheses (not a mistype). + +For instance: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js sum(1)(2) = 3 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 index f2bc002d33..35f2edf26e 100644 --- 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 @@ -1,6 +1,12 @@ +<<<<<<< HEAD Ответ: **ошибка**. Попробуйте запустить этот код: +======= +The result is: **error**. + +Try running it: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let x = 1; @@ -15,6 +21,7 @@ function func() { func(); ``` +<<<<<<< HEAD В этом примере мы можем наблюдать характерную разницу между "несуществующей" и "неинициализированной" переменной. Как вы могли прочитать в статье [](info:closure), переменная находится в неинициализированном состоянии с момента входа в блок кода (или функцию). И остается неинициализированной до соответствующего оператора `let`. @@ -22,13 +29,28 @@ func(); Другими словами, переменная технически существует, но не может быть использована до `let`. Приведенный выше код демонстрирует это. +======= +In this example we can observe the peculiar difference between a "non-existing" and "uninitialized" variable. + +As you may have read in the article [](info:closure), a variable starts in the "uninitialized" state from the moment when the execution enters a code block (or a function). And it stays uninitalized until the corresponding `let` statement. + +In other words, a variable technically exists, but can't be used before `let`. + +The code above demonstrates it. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js function func() { *!* +<<<<<<< HEAD // локальная переменная x известна движку с самого начала выполнения функции, // но она является неинициализированной до let ("мёртвая зона") // следовательно, ошибка +======= + // the local variable x is known to the engine from the beginning of the function, + // but "uninitialized" (unusable) until let ("dead zone") + // hence the error +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 */!* console.log(x); // ReferenceError: Cannot access 'x' before initialization @@ -37,4 +59,8 @@ function func() { } ``` +<<<<<<< HEAD Эту зону временной непригодности переменной (от начала блока кода до `let`) иногда называют "мёртвой зоной". +======= +This zone of temporary unusability of a variable (from the beginning of the code block till `let`) is sometimes called the "dead zone". +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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 index 134f44ec91..6eaddad54d 100644 --- 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 @@ -2,9 +2,15 @@ importance: 4 --- +<<<<<<< HEAD # Видна ли переменная? Что выведет данный код? +======= +# Is variable visible? + +What will be the result of this code? +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js let x = 1; @@ -18,4 +24,8 @@ function func() { func(); ``` +<<<<<<< HEAD P.S. В этой задаче есть подвох. Решение не очевидно. +======= +P.S. There's a pitfall in this task. The solution is not obvious. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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 index 1ca8244082..31bcaa48fd 100644 --- 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 @@ -2,6 +2,7 @@ importance: 5 --- +<<<<<<< HEAD # Фильтрация с помощью функции У нас есть встроенный метод `arr.filter(f)` для массивов. Он фильтрует все элементы с помощью функции `f`. Если она возвращает `true`, то элемент добавится в возвращаемый массив. @@ -20,9 +21,33 @@ importance: 5 ```js /* .. ваш код для inBetween и inArray */ +======= +# Filter through function + +We have a built-in method `arr.filter(f)` for arrays. It filters all elements through the function `f`. If it returns `true`, then that element is returned in the resulting array. + +Make a set of "ready to use" filters: + +- `inBetween(a, b)` -- between `a` and `b` or equal to them (inclusively). +- `inArray([...])` -- in the given array. + +The usage must be like this: + +- `arr.filter(inBetween(3,6))` -- selects only values between 3 and 6. +- `arr.filter(inArray([1,2,3]))` -- selects only elements matching with one of the members of `[1,2,3]`. + +For instance: + +```js +/* .. your code for inBetween and inArray */ +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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 ``` +<<<<<<< HEAD +======= + +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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 index ca436771d9..2b61922f77 100644 --- 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 @@ -2,6 +2,7 @@ importance: 5 --- +<<<<<<< HEAD # Сортировать по полю У нас есть массив объектов, который нужно отсортировать: @@ -25,12 +26,43 @@ users.sort((a, b) => a.age > b.age ? 1 : -1); ``` Можем ли мы сделать его короче, например вот таким? +======= +# Sort by field + +We've got an array of objects to sort: + +```js +let users = [ + { name: "John", age: 20, surname: "Johnson" }, + { name: "Pete", age: 18, surname: "Peterson" }, + { name: "Ann", age: 19, surname: "Hathaway" } +]; +``` + +The usual way to do that would be: + +```js +// by name (Ann, John, Pete) +users.sort((a, b) => a.name > b.name ? 1 : -1); + +// by age (Pete, Ann, John) +users.sort((a, b) => a.age > b.age ? 1 : -1); +``` + +Can we make it even less verbose, like this? +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js users.sort(byField('name')); users.sort(byField('age')); ``` +<<<<<<< HEAD То есть чтобы вместо функции мы просто писали `byField(fieldName)`. Напишите функцию `byField`, которая может быть использована для этого. +======= +So, instead of writing a function, just put `byField(fieldName)`. + +Write the function `byField` that can be used for that. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/1-js/06-advanced-functions/03-closure/article.md b/1-js/06-advanced-functions/03-closure/article.md index 0ba1d8fb1c..a01dee5727 100644 --- a/1-js/06-advanced-functions/03-closure/article.md +++ b/1-js/06-advanced-functions/03-closure/article.md @@ -1,4 +1,5 @@ +<<<<<<< HEAD # Область видимости переменных, замыкание JavaScript - язык с сильным функционально-ориентированным уклоном. Он даёт нам много свободы. Функция может быть динамически создана, скопирована в другую переменную или передана как аргумент другой функции и позже вызвана из совершенно другого места. @@ -30,10 +31,44 @@ JavaScript - язык с сильным функционально-ориент // выполняем некоторые действия с локальной переменной, которые не должны быть видны снаружи let message = "Hello"; // переменная видна только в этом блоке +======= +# Variable scope, closure + +JavaScript is a very function-oriented language. It gives us a lot of freedom. A function can be created at any moment, passed as an argument to another function, and then called from a totally different place of code later. + +We already know that a function can access variables outside of it ("outer" variables). + +But what happens if outer variables change since a function is created? Will the function get newer values or the old ones? + +And what if a function is passed along as an argument and called from another place of code, will it get access to outer variables at the new place? + +Let's expand our knowledge to understand these scenarios and more complex ones. + +```smart header="We'll talk about `let/const` variables here" +In JavaScript, there are 3 ways to declare a variable: `let`, `const` (the modern ones), and `var` (the remnant of the past). + +- In this article we'll use `let` variables in examples. +- Variables, declared with `const`, behave the same, so this article is about `const` too. +- The old `var` has some notable differences, they will be covered in the article . +``` + +## Code blocks + +If a variable is declared inside a code block `{...}`, it's only visible inside that block. + +For example: + +```js run +{ + // do some job with local variables that should not be seen outside + + let message = "Hello"; // only visible in this block +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 alert(message); // Hello } +<<<<<<< HEAD alert(message); // ReferenceError: message is not defined ``` @@ -80,6 +115,71 @@ alert(phrase); // Ошибка, нет такой переменной! ``` В этом случае после завершения работы `if` нижний `alert` не увидит `phrase`, что и приведет к ошибке. +======= +alert(message); // Error: message is not defined +``` + +We can use this to isolate a piece of code that does its own task, with variables that only belong to it: + +```js run +{ + // show message + let message = "Hello"; + alert(message); +} + +{ + // show another message + let message = "Goodbye"; + alert(message); +} +``` + +````smart header="There'd be an error without blocks" +Please note, without separate blocks there would be an error, if we use `let` with the existing variable name: + +```js run +// show message +let message = "Hello"; +alert(message); + +// show another message +*!* +let message = "Goodbye"; // Error: variable already declared +*/!* +alert(message); +``` +```` + +For `if`, `for`, `while` and so on, variables declared in `{...}` are also only visible inside: + +```js run +if (true) { + let phrase = "Hello!"; + + alert(phrase); // Hello! +} + +alert(phrase); // Error, no such variable! +``` + +Here, after `if` finishes, the `alert` below won't see the `phrase`, hence the error. + +That's great, as it allows us to create block-local variables, specific to an `if` branch. + +The similar thing holds true for `for` and `while` loops: + +```js run +for (let i = 0; i < 3; i++) { + // the variable i is only visible inside this for + alert(i); // 0, then 1, then 2 +} + +alert(i); // Error, no such variable +``` + +Visually, `let i` is outside of `{...}`. But the `for` construct is special here: the variable, declared inside it, is considered a part of the block. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 И это замечательно, поскольку это позволяет нам создавать блочно-локальные переменные, относящиеся только к ветви `if`. @@ -120,16 +220,26 @@ function sayHiBye(firstName, lastName) { Здесь *вложенная* функция `getFullName()` создана для удобства. Она может получить доступ к внешним переменным и, значит, вывести полное имя. В JavaScript вложенные функции используются очень часто. +<<<<<<< HEAD Что ещё интереснее, вложенная функция может быть возвращена: либо в качестве свойства нового объекта (если внешняя функция создаёт объект с методами), либо сама по себе. И затем может быть использована в любом месте. Не важно где, она всё так же будет иметь доступ к тем же внешним переменным. Ниже, `makeCounter` создает функцию «счётчик», которая при каждом вызове возвращает следующее число: +======= +What's much more interesting, a nested function can be returned: either as a property of a new object or as a result by itself. It can then be used somewhere else. No matter where, it still has access to the same outer variables. + +Below, `makeCounter` creates the "counter" function that returns the next number on each invocation: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run function makeCounter() { let count = 0; return function() { +<<<<<<< HEAD return count++; // есть доступ к внешней переменной "count" +======= + return count++; +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 }; } @@ -140,6 +250,7 @@ alert( counter() ); // 1 alert( counter() ); // 2 ``` +<<<<<<< HEAD Несмотря на простоту этого примера, немного модифицированные его варианты применяются на практике, например, в [генераторе псевдослучайных чисел](https://ru.wikipedia.org/wiki/Генератор_псевдослучайных_чисел) и во многих других случаях. Как это работает? Если мы создадим несколько таких счётчиков, будут ли они независимыми друг от друга? Что происходит с переменными? @@ -250,6 +361,125 @@ JavaScript-движки также могут оптимизировать ег ### Шаг 4. Возврат функции Давайте вернёмся к примеру с `makeCounter`: +======= +Despite being simple, slightly modified variants of that code have practical uses, for instance, as a [random number generator](https://en.wikipedia.org/wiki/Pseudorandom_number_generator) to generate random values for automated tests. + +How does this work? If we create multiple counters, will they be independent? What's going on with the variables here? + +Understanding such things is great for the overall knowledge of JavaScript and beneficial for more complex scenarios. So let's go a bit in-depth. + +## Lexical Environment + +```warn header="Here be dragons!" +The in-depth technical explanation lies ahead. + +As far as I'd like to avoid low-level language details, any understanding without them would be lacking and incomplete, so get ready. +``` + +For clarity, the explanation is split into multiple steps. + +### Step 1. Variables + +In JavaScript, every running function, code block `{...}`, and the script as a whole have an internal (hidden) associated object known as the *Lexical Environment*. + +The Lexical Environment object consists of two parts: + +1. *Environment Record* -- an object that stores all local variables as its properties (and some other information like the value of `this`). +2. A reference to the *outer lexical environment*, the one associated with the outer code. + +**A "variable" is just a property of the special internal object, `Environment Record`. "To get or change a variable" means "to get or change a property of that object".** + +In this simple code without functions, there is only one Lexical Environment: + +![lexical environment](lexical-environment-global.svg) + +This is the so-called *global* Lexical Environment, associated with the whole script. + +On the picture above, the rectangle means Environment Record (variable store) and the arrow means the outer reference. The global Lexical Environment has no outer reference, that's why the arrow points to `null`. + +As the code starts executing and goes on, the Lexical Environment changes. + +Here's a little bit longer code: + +![lexical environment](closure-variable-phrase.svg) + +Rectangles on the right-hand side demonstrate how the global Lexical Environment changes during the execution: + +1. When the script starts, the Lexical Environment is pre-populated with all declared variables. + - Initially, they are in the "Uninitialized" state. That's a special internal state, it means that the engine knows about the variable, but it cannot be referenced until it has been declared with `let`. It's almost the same as if the variable didn't exist. +2. Then `let phrase` definition appears. There's no assignment yet, so its value is `undefined`. We can use the variable from this point forward. +3. `phrase` is assigned a value. +4. `phrase` changes the value. + +Everything looks simple for now, right? + +- A variable is a property of a special internal object, associated with the currently executing block/function/script. +- Working with variables is actually working with the properties of that object. + +```smart header="Lexical Environment is a specification object" +"Lexical Environment" is a specification object: it only exists "theoretically" in the [language specification](https://tc39.es/ecma262/#sec-lexical-environments) to describe how things work. We can't get this object in our code and manipulate it directly. + +JavaScript engines also may optimize it, discard variables that are unused to save memory and perform other internal tricks, as long as the visible behavior remains as described. +``` + +### Step 2. Function Declarations + +A function is also a value, like a variable. + +**The difference is that a Function Declaration is instantly fully initialized.** + +When a Lexical Environment is created, a Function Declaration immediately becomes a ready-to-use function (unlike `let`, that is unusable till the declaration). + +That's why we can use a function, declared as Function Declaration, even before the declaration itself. + +For example, here's the initial state of the global Lexical Environment when we add a function: + +![](closure-function-declaration.svg) + +Naturally, this behavior only applies to Function Declarations, not Function Expressions where we assign a function to a variable, such as `let say = function(name)...`. + +### Step 3. Inner and outer Lexical Environment + +When a function runs, at the beginning of the call, a new Lexical Environment is created automatically to store local variables and parameters of the call. + +For instance, for `say("John")`, it looks like this (the execution is at the line, labelled with an arrow): + + + +![](lexical-environment-simple.svg) + +During the function call we have two Lexical Environments: the inner one (for the function call) and the outer one (global): + +- The inner Lexical Environment corresponds to the current execution of `say`. It has a single property: `name`, the function argument. We called `say("John")`, so the value of the `name` is `"John"`. +- The outer Lexical Environment is the global Lexical Environment. It has the `phrase` variable and the function itself. + +The inner Lexical Environment has a reference to the `outer` one. + +**When the code wants to access a variable -- the inner Lexical Environment is searched first, then the outer one, then the more outer one and so on until the global one.** + +If a variable is not found anywhere, that's an error in strict mode (without `use strict`, an assignment to a non-existing variable creates a new global variable, for compatibility with old code). + +In this example the search proceeds as follows: + +- For the `name` variable, the `alert` inside `say` finds it immediately in the inner Lexical Environment. +- When it wants to access `phrase`, then there is no `phrase` locally, so it follows the reference to the outer Lexical Environment and finds it there. + +![lexical environment lookup](lexical-environment-simple-lookup.svg) + + +### Step 4. Returning a function + +Let's return to the `makeCounter` example. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js function makeCounter() { @@ -263,6 +493,7 @@ function makeCounter() { let counter = makeCounter(); ``` +<<<<<<< HEAD В начале каждого вызова `makeCounter()` создается новый объект лексического окружения, в котором хранятся переменные для конкретного запуска `makeCounter`. Таким образом, мы имеем два вложенных лексических окружения, как в примере выше: @@ -310,6 +541,55 @@ let counter = makeCounter(); В этом случае лексическое окружение остается доступным даже после завершения работы функции. Например: +======= +At the beginning of each `makeCounter()` call, a new Lexical Environment object is created, to store variables for this `makeCounter` run. + +So we have two nested Lexical Environments, just like in the example above: + +![](closure-makecounter.svg) + +What's different is that, during the execution of `makeCounter()`, a tiny nested function is created of only one line: `return count++`. We don't run it yet, only create. + +All functions remember the Lexical Environment in which they were made. Technically, there's no magic here: all functions have the hidden property named `[[Environment]]`, that keeps the reference to the Lexical Environment where the function was created: + +![](closure-makecounter-environment.svg) + +So, `counter.[[Environment]]` has the reference to `{count: 0}` Lexical Environment. That's how the function remembers where it was created, no matter where it's called. The `[[Environment]]` reference is set once and forever at function creation time. + +Later, when `counter()` is called, a new Lexical Environment is created for the call, and its outer Lexical Environment reference is taken from `counter.[[Environment]]`: + +![](closure-makecounter-nested-call.svg) + +Now when the code inside `counter()` looks for `count` variable, it first searches its own Lexical Environment (empty, as there are no local variables there), then the Lexical Environment of the outer `makeCounter()` call, where it finds and changes it. + +**A variable is updated in the Lexical Environment where it lives.** + +Here's the state after the execution: + +![](closure-makecounter-nested-call-2.svg) + +If we call `counter()` multiple times, the `count` variable will be increased to `2`, `3` and so on, at the same place. + +```smart header="Closure" +There is a general programming term "closure", that developers generally should know. + +A [closure](https://en.wikipedia.org/wiki/Closure_(computer_programming)) is a function that remembers its outer variables and can access them. In some languages, that's not possible, or a function should be written in a special way to make it happen. But as explained above, in JavaScript, all functions are naturally closures (there is only one exception, to be covered in ). + +That is: they automatically remember where they were created using a hidden `[[Environment]]` property, and then their code can access outer variables. + +When on an interview, a frontend developer gets a question about "what's a closure?", a valid answer would be a definition of the closure and an explanation that all functions in JavaScript are closures, and maybe a few more words about technical details: the `[[Environment]]` property and how Lexical Environments work. +``` + +## Garbage collection + +Usually, a Lexical Environment is removed from memory with all the variables after the function call finishes. That's because there are no references to it. As any JavaScript object, it's only kept in memory while it's reachable. + +However, if there's a nested function that is still reachable after the end of a function, then it has `[[Environment]]` property that references the lexical environment. + +In that case the Lexical Environment is still reachable even after the completion of the function, so it stays alive. + +For example: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js function f() { @@ -320,11 +600,19 @@ function f() { } } +<<<<<<< HEAD let g = f(); // g.[[Environment]] хранит ссылку на лексическое окружение // из соответствующего вызова f() ``` Обратите внимание, что если `f()` вызывается много раз и результирующие функции сохраняются, то все соответствующие объекты лексического окружения также будут сохранены в памяти. В приведенном ниже коде -- все три: +======= +let g = f(); // g.[[Environment]] stores a reference to the Lexical Environment +// of the corresponding f() call +``` + +Please note that if `f()` is called many times, and resulting functions are saved, then all corresponding Lexical Environment objects will also be retained in memory. In the code below, all 3 of them: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js function f() { @@ -340,7 +628,11 @@ let arr = [f(), f(), f()]; Объект лексического окружения исчезает, когда становится недоступным (как и любой другой объект). Другими словами, он существует только до тех пор, пока на него ссылается хотя бы одна вложенная функция. +<<<<<<< HEAD В приведенном ниже коде после удаления вложенной функции ее окружающее лексическое окружение (а значит, и `value`) очищается из памяти: +======= +In the code below, after the nested function is removed, its enclosing Lexical Environment (and hence the `value`) is cleaned from memory: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js function f() { @@ -351,7 +643,11 @@ function f() { } } +<<<<<<< HEAD let g = f(); // пока существует функция g, value остается в памяти +======= +let g = f(); // while g function exists, the value stays in memory +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 g = null; // ...и теперь память очищена. ``` @@ -362,7 +658,11 @@ g = null; // ...и теперь память очищена. Но на практике движки JavaScript пытаются это оптимизировать. Они анализируют использование переменных и, если легко по коду понять, что внешняя переменная не используется – она удаляется. +<<<<<<< HEAD **Одним из важных побочных эффектов в V8 (Chrome, Edge, Opera) является то, что такая переменная становится недоступной при отладке.** +======= +**An important side effect in V8 (Chrome, Edge, Opera) is that such variable will become unavailable in debugging.** +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Попробуйте запустить следующий пример в Chrome с открытой Developer Tools. @@ -404,6 +704,12 @@ let g = f(); g(); ``` +<<<<<<< HEAD Эту особенность V8 полезно знать. Если вы занимаетесь отладкой в Chrome/Edge/Opera, рано или поздно вы с ней столкнётесь. Это не баг в отладчике, а скорее особенность V8. Возможно со временем это изменится. Вы всегда можете проверить это, запустив примеры на этой странице. +======= +This feature of V8 is good to know. If you are debugging with Chrome/Edge/Opera, sooner or later you will meet it. + +That is not a bug in the debugger, but rather a special feature of V8. Perhaps it will be changed sometime. You can always check for it by running the examples on this page. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/1-js/06-advanced-functions/03-closure/closure-makecounter-environment.svg b/1-js/06-advanced-functions/03-closure/closure-makecounter-environment.svg new file mode 100644 index 0000000000..f78441712f --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/closure-makecounter-environment.svg @@ -0,0 +1 @@ +null[[Environment]]makeCounter: function counter: undefinedcount: 0outerouter \ No newline at end of file diff --git a/1-js/06-advanced-functions/03-closure/closure-makecounter.svg b/1-js/06-advanced-functions/03-closure/closure-makecounter.svg new file mode 100644 index 0000000000..2ca06455a5 --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/closure-makecounter.svg @@ -0,0 +1 @@ +makeCounter: function counter: undefinedcount: 0nullglobal LexicalEnvironmentLexicalEnvironment of makeCounter() callouterouter \ No newline at end of file diff --git a/1-js/06-advanced-functions/03-closure/variable-scope-lookup.svg b/1-js/06-advanced-functions/03-closure/variable-scope-lookup.svg new file mode 100644 index 0000000000..f1f1d3b1df --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/variable-scope-lookup.svg @@ -0,0 +1 @@ +functionUser(name){this.sayHi=function(){alert(name);};}letuser=newUser("John");user.sayHi(); \ No newline at end of file diff --git a/1-js/06-advanced-functions/04-var/article.md b/1-js/06-advanced-functions/04-var/article.md index b0bf9bbd81..f49a8322ce 100644 --- a/1-js/06-advanced-functions/04-var/article.md +++ b/1-js/06-advanced-functions/04-var/article.md @@ -1,6 +1,7 @@ # Устаревшее ключевое слово "var" +<<<<<<< HEAD ```smart header="Эта статья предназначена для понимания старых скриптов" Информация, приведенная в этой статье, полезна для понимания старых скриптов. @@ -8,11 +9,21 @@ ``` В самой первой главе про [переменные](info:variables) мы ознакомились с тремя способами объявления переменных: +======= +```smart header="This article is for understanding old scripts" +The information in this article is useful for understanding old scripts. + +That's not how we write new code. +``` + +In the very first chapter about [variables](info:variables), we mentioned three ways of variable declaration: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 1. `let` 2. `const` 3. `var` +<<<<<<< HEAD `let` и `const` ведут себя одинаково по отношению к лексическому окружению, области видимости. Но `var` - это совершенно другой зверь, берущий своё начало с давних времён. Обычно `var` не используется в современных скриптах, но всё ещё может скрываться в старых. @@ -34,10 +45,28 @@ alert(phrase); // Ошибка: phrase не определена ``` ...Однако, отличия всё же есть. +======= +The `var` declaration is similar to `let`. Most of the time we can replace `let` by `var` or vice-versa and expect things to work: + +```js run +var message = "Hi"; +alert(message); // Hi +``` + +But internally `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. + +If you don't plan on meeting such scripts you may even skip this chapter or postpone it. + +On the other hand, it's important to understand differences when migrating old scripts from `var` to `let`, to avoid odd errors. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## Для "var" не существует блочной области видимости +<<<<<<< HEAD Область видимости переменных `var` ограничивается либо функцией, либо, если переменная глобальная, то скриптом. Такие переменные доступны за пределами блока. +======= +Variables, declared with `var`, are either function-scoped or global-scoped. They are visible through blocks. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Например: @@ -61,19 +90,25 @@ if (true) { } *!* -alert(test); // Error: test is not defined +alert(test); // ReferenceError: test is not defined */!* ``` Аналогично для циклов: `var` не может быть блочной или локальной внутри цикла: -```js +```js run for (var i = 0; i < 10; i++) { + var one = 1; // ... } *!* +<<<<<<< HEAD alert(i); // 10, переменная i доступна вне цикла, т.к. является глобальной переменной +======= +alert(i); // 10, "i" is visible after loop, it's a global variable +alert(one); // 1, "one" is visible after loop, it's a global variable +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 */!* ``` @@ -89,12 +124,41 @@ function sayHi() { } sayHi(); +<<<<<<< HEAD alert(phrase); // Ошибка: phrase не определена (видна в консоли разработчика) ``` Как мы видим, `var` выходит за пределы блоков `if`, `for` и подобных. Это происходит потому, что на заре развития JavaScript блоки кода не имели лексического окружения. Поэтому можно сказать, что `var` - это пережиток прошлого. ## "var" допускает повторное объявление +======= +alert(phrase); // ReferenceError: phrase is not defined +``` + +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" tolerates redeclarations + +If we declare the same variable with `let` twice in the same scope, that's an error: + +```js run +let user; +let user; // SyntaxError: 'user' has already been declared +``` + +With `var`, we can redeclare a variable any number of times. If we use `var` with an already-declared variable, it's just ignored: + +```js run +var user = "Pete"; + +var user = "John"; // this "var" does nothing (already declared) +// ...it doesn't trigger an error + +alert(user); // John +``` + +## "var" variables can be declared below their use +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Если в блоке кода дважды объявить одну и ту же переменную `let`, будет ошибка: @@ -182,11 +246,15 @@ sayHi(); **Объявления переменных "всплывают", но присваивания значений - нет.** +<<<<<<< HEAD Это проще всего продемонстрировать на примере: +======= +That's best demonstrated with an example: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run function sayHi() { - alert(phrase); + alert(phrase); *!* var phrase = "Привет"; @@ -221,15 +289,94 @@ sayHi(); Поскольку все объявления переменных `var` обрабатываются в начале функции, мы можем ссылаться на них в любом месте. Однако, переменные имеют значение `undefined` до строки с присвоением значения. +<<<<<<< HEAD В обоих примерах выше вызов `alert` происходил без ошибки, потому что переменная `phrase` уже существовала. Но её значение ещё не было присвоено, поэтому мы получали `undefined`. +======= +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`. + +## IIFE + +In the past, as there was only `var`, and it has no block-level visibility, programmers invented a way to emulate it. What they did was called "immediately-invoked function expressions" (abbreviated as IIFE). + +That's not something we should use nowadays, but you can find them in old scripts. + +An IIFE looks like this: + +```js run +(function() { + + var message = "Hello"; + + alert(message); // Hello + +})(); +``` + +Here, a Function Expression is created and immediately called. So the code executes right away and has its own private variables. + +The Function Expression is wrapped with parenthesis `(function {...})`, because when JavaScript engine encounters `"function"` in the main code, it understands it as the start of a Function Declaration. But a Function Declaration must have a name, so this kind of code will give an error: + +```js run +// Tries to declare and immediately call a function +function() { // <-- SyntaxError: Function statements require a function name + + var message = "Hello"; + + alert(message); // Hello + +}(); +``` + +Even if we say: "okay, let's add a name", that won't work, as JavaScript does not allow Function Declarations to be called immediately: + +```js run +// syntax error because of parentheses below +function go() { + +}(); // <-- can't call Function Declaration immediately +``` + +So, the parentheses around the function is a trick to show JavaScript that the function is created in the context of another expression, and hence it's a Function Expression: it needs no name and can be called immediately. + +There exist other ways besides parentheses to tell JavaScript that we mean a Function Expression: + +```js run +// Ways to create IIFE + +*!*(*/!*function() { + alert("Parentheses around the function"); +}*!*)*/!*(); + +*!*(*/!*function() { + alert("Parentheses around the whole thing"); +}()*!*)*/!*; + +*!*!*/!*function() { + alert("Bitwise NOT operator starts the expression"); +}(); + +*!*+*/!*function() { + alert("Unary plus starts the expression"); +}(); +``` + +In all the above cases we declare a Function Expression and run it immediately. Let's note again: nowadays there's no reason to write such code. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## IIFE В прошлом, поскольку существовал только `var`, а он не имел блочной области видимости, программисты придумали способ её эмулировать. Этот способ получил название "Immediately-invoked function expressions" (сокращенно IIFE). +<<<<<<< HEAD Это не то, что мы должны использовать сегодня, но, так как вы можете встретить это в старых скриптах, полезно понимать принцип работы. IIFE выглядит следующим образом: +======= +1. `var` variables have no block scope, their visibility is scoped to current function, or global, if declared outside function. +2. `var` declarations are processed at function start (script start for globals). + +There's one more very minor difference related to the global object, that we'll cover in the next chapter. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run (function() { 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 b1ff9361f1..937907ad17 100644 --- a/1-js/06-advanced-functions/05-global-object/article.md +++ b/1-js/06-advanced-functions/05-global-object/article.md @@ -5,7 +5,11 @@ В браузере он называется `window`, в Node.js — `global`, в другой среде исполнения может называться иначе. +<<<<<<< HEAD Недавно `globalThis` был добавлен в язык как стандартизированное имя для глобального объекта, которое должно поддерживаться в любом окружении. Он поддерживается во всех основных браузерах. +======= +Recently, `globalThis` was added to the language, as a standardized name for a global object, that should be supported across all environments. It's supported in all major browsers. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Далее мы будем использовать `window`, полагая, что наша среда - браузер. Если скрипт может выполняться и в другом окружении, лучше будет `globalThis`. @@ -25,7 +29,13 @@ var gVar = 5; alert(window.gVar); // 5 (становится свойством глобального объекта) ``` +<<<<<<< HEAD То же самое касается функций, объявленных с помощью синтаксиса Function Declaration (выражения с ключевым словом `function` в основном потоке кода, не Function Expression) +======= +Function declarations have the same effect (statements with `function` keyword in the main code flow, not function expressions). + +Please don't rely on that! This behavior exists for compatibility reasons. Modern scripts use [JavaScript modules](info:modules) where such a thing doesn't happen. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Пожалуйста, не полагайтесь на это. Такое поведение поддерживается для совместимости. В современных проектах, использующих [JavaScript-модули](info:modules), такого не происходит. @@ -79,6 +89,7 @@ if (!window.Promise) { ## Итого +<<<<<<< HEAD - Глобальный объект хранит переменные, которые должны быть доступны в любом месте программы. Это включает в себя как встроенные объекты, например, `Array`, так и характерные для окружения свойства, например, `window.innerHeight` -- высота окна браузера. @@ -89,3 +100,9 @@ if (!window.Promise) { - Следует хранить значения в глобальном объекте, только если они действительно глобальны для нашего проекта. И стараться свести их количество к минимуму. - В браузерах, если только мы не используем [модули](info:modules), глобальные функции и переменные, объявленные с помощью `var`, становятся свойствами глобального объекта. - Для того, чтобы код был проще и в будущем его легче было поддерживать, следует обращаться к свойствам глобального объекта напрямую, как `window.x`. +======= + ...But more often is referred by "old-school" environment-specific names, such as `window` (browser) and `global` (Node.js). +- We should store values in the global object only if they're truly global for our project. And keep their number at minimum. +- In-browser, unless we're using [modules](info:modules), global functions and variables declared with `var` become a property of the global object. +- To make our code future-proof and easier to understand, we should access properties of the global object directly, as `window.x`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/1-js/06-advanced-functions/06-function-object/5-sum-many-brackets/_js.view/solution.js b/1-js/06-advanced-functions/06-function-object/5-sum-many-brackets/_js.view/solution.js new file mode 100644 index 0000000000..c7d7d734ea --- /dev/null +++ b/1-js/06-advanced-functions/06-function-object/5-sum-many-brackets/_js.view/solution.js @@ -0,0 +1,15 @@ +function sum(a) { + + let currentSum = a; + + function f(b) { + currentSum += b; + return f; + } + + f.toString = function() { + return currentSum; + }; + + return f; +} diff --git a/1-js/06-advanced-functions/06-function-object/5-sum-many-brackets/_js.view/source.js b/1-js/06-advanced-functions/06-function-object/5-sum-many-brackets/_js.view/source.js new file mode 100644 index 0000000000..f10dca5dc8 --- /dev/null +++ b/1-js/06-advanced-functions/06-function-object/5-sum-many-brackets/_js.view/source.js @@ -0,0 +1,12 @@ +function sum(a){ + // Your code goes here. + +} + +/* +sum(1)(2) == 3; // 1 + 2 +sum(1)(2)(3) == 6; // 1 + 2 + 3 +sum(5)(-1)(2) == 6 +sum(6)(-1)(-2)(-3) == 0 +sum(0)(1)(2)(3)(4)(5) == 15 +*/ diff --git a/1-js/06-advanced-functions/06-function-object/5-sum-many-brackets/_js.view/test.js b/1-js/06-advanced-functions/06-function-object/5-sum-many-brackets/_js.view/test.js new file mode 100644 index 0000000000..ed567d3306 --- /dev/null +++ b/1-js/06-advanced-functions/06-function-object/5-sum-many-brackets/_js.view/test.js @@ -0,0 +1,19 @@ +describe("sum", function(){ + + it("sum(1)(2) == 3", function(){ + assert.equal(3, sum(1)(2)); + }); + + it("sum(5)(-1)(2) == 6", function(){ + assert.equal(6, sum(5)(-1)(2)); + }); + + it("sum(6)(-1)(-2)(-3) == 0", function(){ + assert.equal(0, sum(6)(-1)(-2)(-3)); + }); + + it("sum(0)(1)(2)(3)(4)(5) == 15", function(){ + assert.equal(15, sum(0)(1)(2)(3)(4)(5)); + }); +}); + diff --git a/1-js/06-advanced-functions/06-function-object/5-sum-many-brackets/solution.md b/1-js/06-advanced-functions/06-function-object/5-sum-many-brackets/solution.md index 2aba71663b..648651333c 100644 --- a/1-js/06-advanced-functions/06-function-object/5-sum-many-brackets/solution.md +++ b/1-js/06-advanced-functions/06-function-object/5-sum-many-brackets/solution.md @@ -5,7 +5,7 @@ Код: -```js run +```js demo run function sum(a) { let currentSum = a; @@ -52,4 +52,8 @@ function f(b) { } ``` +<<<<<<< HEAD Функция `f` будет использоваться в последующем вызове и снова возвращать себя столько раз, сколько будет необходимо. Затем, при использовании в качестве числа или строки, метод `toString` возвращает `currentSum` -- число. Также здесь мы можем использовать `Symbol.toPrimitive` или `valueOf` для преобразования. +======= +This `f` will be used in the next call, again return itself, as many times as needed. Then, when used as a number or a string -- the `toString` returns the `currentSum`. We could also use `Symbol.toPrimitive` or `valueOf` here for the conversion. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/1-js/06-advanced-functions/06-function-object/article.md b/1-js/06-advanced-functions/06-function-object/article.md index 59b009bf66..89f739e4d9 100644 --- a/1-js/06-advanced-functions/06-function-object/article.md +++ b/1-js/06-advanced-functions/06-function-object/article.md @@ -68,7 +68,11 @@ alert(user.sayBye.name); // sayBye В этом нет никакой магии. Бывает, что корректное имя определить невозможно. В таких случаях свойство name имеет пустое значение. Например: ```js run +<<<<<<< HEAD // функция объявлена внутри массива +======= +// function created inside array +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 let arr = [function() {}]; alert( arr[0].name ); // <пустая строка> @@ -325,7 +329,11 @@ welcome(); // Hello, Guest (вложенный вызов работает) Теперь всё работает, потому что имя `"func"` локальное и находится внутри функции. Теперь оно взято не снаружи (и недоступно оттуда). Спецификация гарантирует, что оно всегда будет ссылаться на текущую функцию. +<<<<<<< HEAD Внешний код все ещё содержит переменные `sayHi` и `welcome`, но теперь `func` -- это "внутреннее имя функции", таким образом она может вызвать себя изнутри. +======= +The outer code still has its variable `sayHi` or `welcome`. And `func` is an "internal function name", the way for the function to can call itself reliably. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```smart header="Это не работает с Function Declaration" Трюк с "внутренним" именем, описанный выше, работает только для Function Expression и *не* работает для Function Declaration. Для Function Declaration синтаксис не предусматривает возможность объявить дополнительное "внутреннее" имя. @@ -346,6 +354,10 @@ welcome(); // Hello, Guest (вложенный вызов работает) Также функции могут содержать дополнительные свойства. Многие известные JavaScript-библиотеки искусно используют эту возможность. +<<<<<<< HEAD Они создают "основную" функцию и добавляют множество "вспомогательных" функций внутрь первой. Например, библиотека [jQuery](https://jquery.com) создаёт функцию с именем `$`. Библиотека [lodash](https://lodash.com) создаёт функцию `_`, а потом добавляет в неё `_.clone`, `_.keyBy` и другие свойства (чтобы узнать о ней побольше см. [документацию](https://lodash.com/docs)). Они делают это, чтобы уменьшить засорение глобального пространства имён посредством того, что одна библиотека предоставляет только одну глобальную переменную, уменьшая вероятность конфликта имён. +======= +They create a "main" function and attach many other "helper" functions to it. For instance, the [jQuery](https://jquery.com) library creates a function named `$`. The [lodash](https://lodash.com) library creates a function `_`, and then adds `_.clone`, `_.keyBy` and other properties to it (see the [docs](https://lodash.com/docs) when you want to learn more about them). Actually, they do it to lessen their pollution of the global space, so that a single library gives only one global variable. That reduces the possibility of naming conflicts. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Таким образом, функция может не только делать что-то сама по себе, но также и предоставлять полезную функциональность через свои свойства. diff --git a/1-js/06-advanced-functions/07-new-function/article.md b/1-js/06-advanced-functions/07-new-function/article.md index 47df9eee1a..63b78e6104 100644 --- a/1-js/06-advanced-functions/07-new-function/article.md +++ b/1-js/06-advanced-functions/07-new-function/article.md @@ -90,7 +90,11 @@ getFunc()(); // *!*"test"*/!*, из лексического окружения Проблема в том, что перед отправкой JavaScript-кода на реальные работающие проекты код сжимается с помощью *минификатора* - специальной программы, которая уменьшает размер кода, удаляя комментарии, лишние пробелы, и, что самое главное, локальным переменным даются укороченные имена. +<<<<<<< HEAD Например, если в функции объявляется переменная `let userName`, то минификатор изменяет её на `let a` (или другую букву, если она не занята) и изменяет её везде. Обычно так делать безопасно, потому что переменная является локальной, и никто снаружи не имеет к ней доступ. И внутри функции минификатор заменяет каждое её упоминание. Минификаторы достаточно умные. Они не просто осуществляют "тупой" поиск-замену, они анализируют структуру кода, и поэтому ничего не ломается. +======= +For instance, if a function has `let userName`, minifier replaces it with `let a` (or another letter if this one is occupied), and does it everywhere. That's usually a safe thing to do, because the variable is local, nothing outside the function can access it. And inside the function, minifier replaces every mention of it. Minifiers are smart, they analyze the code structure, so they don't break anything. They're not just a dumb find-and-replace. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Так что если бы даже `new Function` и имела доступ к внешним переменным, она не смогла бы найти переименованную `userName`. diff --git a/1-js/06-advanced-functions/08-settimeout-setinterval/article.md b/1-js/06-advanced-functions/08-settimeout-setinterval/article.md index 3bf5997b0f..9ef8c0c747 100644 --- a/1-js/06-advanced-functions/08-settimeout-setinterval/article.md +++ b/1-js/06-advanced-functions/08-settimeout-setinterval/article.md @@ -27,7 +27,11 @@ let timerId = setTimeout(func|code, [delay], [arg1], [arg2], ...); : Задержка перед запуском в миллисекундах (1000 мс = 1 с). Значение по умолчанию - 0. `arg1`, `arg2`... +<<<<<<< HEAD : Аргументы, передаваемые в функцию +======= +: Arguments for the function +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Например, данный код вызывает `sayHi()` спустя одну секунду: @@ -61,7 +65,11 @@ setTimeout(sayHi, 1000, "Привет", "Джон"); // Привет, Джон setTimeout("alert('Привет')", 1000); ``` +<<<<<<< HEAD Но использование строк не рекомендуется. Вместо этого используйте функции. Например, так: +======= +But using strings is not recommended, use arrow functions instead of them, like this: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run no-beautify setTimeout(() => alert('Привет'), 1000); @@ -102,7 +110,11 @@ alert(timerId); // тот же идентификатор (не принимае Повторюсь, что нет единой спецификации на эти методы, поэтому такое поведение является нормальным. +<<<<<<< HEAD Для браузеров таймеры описаны в [разделе таймеров](https://www.w3.org/TR/html5/webappapis.html#timers) стандарта HTML5. +======= +For browsers, timers are described in the [timers section](https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#timers) of HTML Living Standard. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## setInterval @@ -129,7 +141,11 @@ setTimeout(() => { clearInterval(timerId); alert('stop'); }, 5000); ```smart header="Во время показа `alert` время тоже идёт" В большинстве браузеров, включая Chrome и Firefox, внутренний счётчик продолжает тикать во время показа `alert/confirm/prompt`. +<<<<<<< HEAD Так что если вы запустите код выше и подождёте с закрытием `alert` несколько секунд, то следующий `alert` будет показан сразу, как только вы закроете предыдущий. Интервал времени между сообщениями `alert` будет короче, чем 2 секунды. +======= +So if you run the code above and don't dismiss the `alert` window for some time, then the next `alert` will be shown immediately as you do it. The actual interval between alerts will be shorter than 2 seconds. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` ## Вложенный setTimeout @@ -183,7 +199,7 @@ let timerId = setTimeout(function request() { ```js let i = 1; setInterval(function() { - func(i); + func(i++); }, 100); ``` @@ -192,12 +208,16 @@ setInterval(function() { ```js let i = 1; setTimeout(function run() { - func(i); + func(i++); setTimeout(run, 100); }, 100); ``` +<<<<<<< HEAD Для `setInterval` внутренний планировщик будет выполнять `func(i)` каждые 100 мс: +======= +For `setInterval` the internal scheduler will run `func(i++)` every 100ms: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ![](setinterval-interval.svg) @@ -231,7 +251,11 @@ setTimeout(function() {...}, 100); Для `setInterval` функция остаётся в памяти до тех пор, пока не будет вызван `clearInterval`. +<<<<<<< HEAD Есть и побочный эффект. Функция ссылается на внешнее лексическое окружение, поэтому пока она существует, внешние переменные существуют тоже. Они могут занимать больше памяти, чем сама функция. Поэтому, если регулярный вызов функции больше не нужен, то лучше отменить его, даже если функция очень маленькая. +======= +There's a side effect. A function references the outer lexical environment, so, while it lives, outer variables live too. They may take much more memory than the function itself. So when we don't need the scheduled function anymore, it's better to cancel it, even if it's very small. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```` ## setTimeout с нулевой задержкой @@ -254,6 +278,11 @@ alert("Привет"); Есть и более продвинутые случаи использования нулевой задержки в браузерах, которые мы рассмотрим в главе . +<<<<<<< HEAD +======= +````smart header="Zero delay is in fact not zero (in a browser)" +In the browser, there's a limitation of how often nested timers can run. The [HTML Living Standard](https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#timers) says: "after five nested timers, the interval is forced to be at least 4 milliseconds.". +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ````smart header="Минимальная задержка вложенных таймеров в браузере" В браузере есть ограничение на то, как часто внутренние счётчики могут выполняться. В [стандарте HTML5](https://www.w3.org/TR/html5/webappapis.html#timers) говорится: "после пяти вложенных таймеров интервал должен составлять не менее четырёх миллисекунд.". @@ -281,22 +310,41 @@ setTimeout(function run() { Это ограничение существует давно, многие скрипты полагаются на него, поэтому оно сохраняется по историческим причинам. +<<<<<<< HEAD Этого ограничения нет в серверном JavaScript. Там есть и другие способы планирования асинхронных задач. Например, [setImmediate](https://nodejs.org/api/timers.html) для Node.js. Так что это ограничение относится только к браузерам. +======= +For server-side JavaScript, that limitation does not exist, and there exist other ways to schedule an immediate asynchronous job, like [setImmediate](https://nodejs.org/api/timers.html#timers_setimmediate_callback_args) for Node.js. So this note is browser-specific. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```` ## Итого +<<<<<<< HEAD - Методы `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-го вызова. +======= +- Methods `setTimeout(func, delay, ...args)` and `setInterval(func, delay, ...args)` allow us to run the `func` once/regularly after `delay` milliseconds. +- To cancel the execution, we should call `clearTimeout/clearInterval` with the value returned by `setTimeout/setInterval`. +- Nested `setTimeout` calls are a more flexible alternative to `setInterval`, allowing us to set the time *between* executions more precisely. +- Zero delay scheduling with `setTimeout(func, 0)` (the same as `setTimeout(func)`) is used to schedule the call "as soon as possible, but after the current script is complete". +- The browser limits the minimal delay for five or more nested calls of `setTimeout` or for `setInterval` (after 5th call) to 4ms. That's for historical reasons. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Обратим внимание, что все методы планирования *не гарантируют* точную задержку. +<<<<<<< HEAD Например, таймер в браузере может замедляться по многим причинам: - Перегружен процессор. - Вкладка браузера в фоновом режиме. - Работа ноутбука от аккумулятора. +======= +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 saving mode. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Всё это может увеличивать минимальный интервал срабатывания таймера (и минимальную задержку) до 300 или даже 1000 мс в зависимости от браузера и настроек производительности ОС. 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 3999156b7c..93b260eba7 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 @@ -4,4 +4,8 @@ function debounce(func, ms) { clearTimeout(timeout); timeout = setTimeout(() => func.apply(this, arguments), ms); }; -} \ No newline at end of file +<<<<<<< HEAD +} +======= +} +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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 bceaefee24..d021da6413 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 @@ -7,21 +7,35 @@ describe('debounce', function () { this.clock.restore(); }); +<<<<<<< HEAD it('для одного вызова - запускается через "ms" миллисекунд', function () { +======= + it('for one call - runs it after given ms', function () { +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 const f = sinon.spy(); const debounced = debounce(f, 1000); debounced('test'); +<<<<<<< HEAD assert(f.notCalled, 'не вызывается сразу'); this.clock.tick(1000); assert(f.calledOnceWith('test'), 'вызывается после 1000ms'); }); it('для 3 вызовов - вызывает последний через "ms" миллисекунд', function () { +======= + assert(f.notCalled, 'not called immediately'); + this.clock.tick(1000); + assert(f.calledOnceWith('test'), 'called after 1000ms'); + }); + + it('for 3 calls - runs the last one after given ms', function () { +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 const f = sinon.spy(); const debounced = debounce(f, 1000); debounced('a'); +<<<<<<< HEAD setTimeout(() => debounced('b'), 200); // проигнорирована setTimeout(() => debounced('c'), 500); // вызвана this.clock.tick(1000); @@ -34,6 +48,20 @@ describe('debounce', function () { }); it('сохраняет контекст вызова', function () { +======= + setTimeout(() => debounced('b'), 200); // ignored (too early) + setTimeout(() => debounced('c'), 500); // runs (1000 ms passed) + this.clock.tick(1000); + + assert(f.notCalled, 'not called after 1000ms'); + + this.clock.tick(500); + + assert(f.calledOnceWith('c'), 'called after 1500ms'); + }); + + it('keeps the context of the call', function () { +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 let obj = { f() { assert.equal(this, obj); @@ -44,4 +72,9 @@ describe('debounce', function () { obj.f('test'); this.clock.tick(5000); }); -}); \ No newline at end of file +<<<<<<< HEAD +}); +======= + +}); +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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 index aab1a471bb..5956e482c3 100644 --- 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 @@ -1,6 +1,7 @@ +<<<<<<< HEAD Функция handler вызывается на этом поле для ввода:
@@ -13,6 +14,20 @@

+======= +Function handler is called on this input: +
+ + +

+ +Debounced function debounce(handler, 1000) is called on this input: +
+ + +

+ +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` +<<<<<<< HEAD Если нам нужно сделать глобальную переменную уровня всей страницы, можно явно присвоить её объекту `window`, тогда получить значение переменной можно обратившись к `window.user`. Но это должно быть исключением, требующим веской причины. +======= +```smart +In the browser, we can make a variable window-level global by explicitly assigning it to a `window` property, e.g. `window.user = "John"`. + +Then all scripts will see it, both with `type="module"` and without it. + +That said, making such global variables is frowned upon. Please try to avoid them. +``` +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ### Код в модуле выполняется только один раз при импорте +<<<<<<< HEAD Если один и тот же модуль используется в нескольких местах, то его код выполнится только один раз, после чего экспортируемая функциональность передаётся всем импортёрам. Это очень важно для понимания работы модулей. Давайте посмотрим примеры. +======= +If the same module is imported into multiple other modules, its code is executed only once, upon the first import. Then its exports are given to all further importers. + +The one-time evaluation has important consequences, that we should be aware of. + +Let's see a couple of examples. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Во-первых, если при запуске модуля возникают побочные эффекты, например выдаётся сообщение, то импорт модуля в нескольких местах покажет его только один раз - при первом импорте: @@ -133,9 +217,17 @@ import `./alert.js`; // Модуль выполнен! import `./alert.js`; // (ничего не покажет) ``` +<<<<<<< HEAD На практике, задача кода модуля - это обычно инициализация, создание внутренних структур данных, а если мы хотим, чтобы что-то можно было использовать много раз, то экспортируем это. Теперь более продвинутый пример. +======= +The second import shows nothing, because the module has already been evaluated. + +There's a rule: top-level module code should be used for initialization, creation of module-specific internal data structures. If we need to make something callable multiple times - we should export it as a function, like we did with `sayHi` above. + +Now, let's consider a deeper example. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Давайте представим, что модуль экспортирует объект: @@ -160,6 +252,7 @@ import {admin} from './admin.js'; alert(admin.name); // Pete *!* +<<<<<<< HEAD // Оба файла, 1.js и 2.js, импортируют один и тот же объект // Изменения, сделанные в 1.js, будут видны в 2.js */!* @@ -170,44 +263,85 @@ alert(admin.name); // Pete Такое поведение позволяет *конфигурировать* модули при первом импорте. Мы можем установить его свойства один раз, и в дальнейших импортах он будет уже настроенным. Например, модуль `admin.js` предоставляет определённую функциональность, но ожидает передачи учётных данных в объект `admin` извне: +======= +// Both 1.js and 2.js reference the same admin object +// Changes made in 1.js are visible in 2.js +*/!* +``` + +As you can see, when `1.js` changes the `name` property in the imported `admin`, then `2.js` can see the new `admin.name`. + +That's exactly because the module is executed only once. Exports are generated, and then they are shared between importers, so if something changes the `admin` object, other importers will see that. + +**Such behavior is actually very convenient, because it allows us to *configure* modules.** + +In other words, a module can provide a generic functionality that needs a setup. E.g. authentication needs credentials. Then it can export a configuration object expecting the outer code to assign to it. + +Here's the classical pattern: +1. A module exports some means of configuration, e.g. a configuration object. +2. On the first import we initialize it, write to its properties. The top-level application script may do that. +3. Further imports use the module. + +For instance, the `admin.js` module may provide certain functionality (e.g. authentication), but expect the credentials to come into the `config` object from outside: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js // 📁 admin.js -export let admin = { }; +export let config = { }; export function sayHi() { - alert(`Ready to serve, ${admin.name}!`); + alert(`Ready to serve, ${config.user}!`); } ``` +<<<<<<< HEAD В `init.js`, первом скрипте нашего приложения, мы установим `admin.name`. Тогда все это увидят, включая вызовы, сделанные из самого `admin.js`: +======= +Here, `admin.js` exports the `config` object (initially empty, but may have default properties too). + +Then in `init.js`, the first script of our app, we import `config` from it and set `config.user`: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js // 📁 init.js -import {admin} from './admin.js'; -admin.name = "Pete"; +import {config} from './admin.js'; +config.user = "Pete"; ``` +<<<<<<< HEAD Другой модуль тоже увидит `admin.name`: +======= +...Now the module `admin.js` is configured. -```js -// 📁 other.js -import {admin, sayHi} from './admin.js'; +Further importers can call it, and it correctly shows the current user: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 -alert(admin.name); // *!*Pete*/!* +```js +// 📁 another.js +import {sayHi} from './admin.js'; sayHi(); // Ready to serve, *!*Pete*/!*! ``` + ### import.meta Объект `import.meta` содержит информацию о текущем модуле. +<<<<<<< HEAD Содержимое зависит от окружения. В браузере он содержит ссылку на скрипт или ссылку на текущую веб-страницу, если модуль встроен в HTML: ```html run height=0 ``` @@ -233,18 +367,31 @@ sayHi(); // Ready to serve, *!*Pete*/!*! Есть и несколько других, именно браузерных особенностей скриптов с `type="module"` по сравнению с обычными скриптами. +<<<<<<< HEAD Если вы читаете материал в первый раз или, если не собираетесь использовать модули в браузерах, то сейчас можете пропустить эту секцию. +======= +You may want to skip this section for now if you're reading for the first time, or if you don't use JavaScript in a browser. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ### Модули являются отложенными (deferred) Модули *всегда* выполняются в отложенном (deferred) режиме, точно так же, как скрипты с атрибутом `defer` (описан в главе [](info:script-async-defer)). Это верно и для внешних и встроенных скриптов-модулей. +<<<<<<< HEAD Другими словами: - загрузка внешних модулей, таких как ` @@ -268,14 +419,23 @@ sayHi(); // Ready to serve, *!*Pete*/!*! ``` +<<<<<<< HEAD Пожалуйста, обратите внимание: второй скрипт выполнится раньше, чем первый! Поэтому мы увидим сначала `undefined`, а потом `object`. Это потому, что модули начинают выполняться после полной загрузки страницы. Обычные скрипты запускаются сразу же, поэтому сообщение из обычного скрипта мы видим первым. При использовании модулей нам стоит иметь в виду, что HTML-страница будет показана браузером до того, как выполнятся модули и JavaScript-приложение будет готово к работе. Некоторые функции могут ещё не работать. Нам следует разместить "индикатор загрузки" или что-то ещё, чтобы не смутить этим посетителя. +======= +Please note: the second script actually runs before the first! So we'll see `undefined` first, and then `object`. + +That's because modules are deferred, so we wait for the document to be processed. The regular script runs immediately, so we see its output first. + +When using modules, we should be aware that the HTML page shows up as it loads, and JavaScript modules run after that, so the user may see the page before the JavaScript application is ready. Some functionality may not work yet. We should put "loading indicators", or otherwise ensure that the visitor won't be confused by that. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ### Атрибут async работает во встроенных скриптах +<<<<<<< HEAD Для не-модульных скриптов атрибут `async` работает только на внешних скриптах. Скрипты с ним запускаются сразу по готовности, они не ждут другие скрипты или HTML-документ. Для модулей атрибут `async` работает на любых скриптах. @@ -283,6 +443,15 @@ sayHi(); // Ready to serve, *!*Pete*/!*! Например, в скрипте ниже есть `async`, поэтому он выполнится сразу после загрузки, не ожидая других скриптов. Скрипт выполнит импорт (загрузит `./analytics.js`) и сразу запустится, когда будет готов, даже если HTML документ ещё не будет загружен, или если другие скрипты ещё загружаются. +======= +For non-module scripts, the `async` attribute only works on external scripts. Async scripts run immediately when ready, independently of other scripts or the HTML document. + +For module scripts, it works on inline scripts as well. + +For example, the inline script below has `async`, so it doesn't wait for anything. + +It performs the import (fetches `./analytics.js`) and runs when ready, even if the HTML document is not finished yet, or if other scripts are still pending. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Это очень полезно, когда модуль ни с чем не связан, например для счётчиков, рекламы, обработчиков событий. @@ -300,7 +469,11 @@ sayHi(); // Ready to serve, *!*Pete*/!*! Внешние скрипты с атрибутом `type="module"` имеют два отличия: +<<<<<<< HEAD 1. Внешние скрипты с одинаковым атрибутом `src` запускаются только один раз: +======= +1. External scripts with the same `src` run only once: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```html @@ -326,11 +499,19 @@ import {sayHi} from 'sayHi'; // Ошибка, "голый" модуль // путь должен быть, например './sayHi.js' или абсолютный ``` +<<<<<<< HEAD Другие окружения, например Node.js, допускают использование "голых" модулей, без путей, так как в них есть свои правила, как работать с такими модулями и где их искать. Но браузеры пока не поддерживают "голые" модули. +======= +Certain environments, like Node.js or bundle tools allow bare modules, without any path, as they have their own ways for finding modules and hooks to fine-tune them. But browsers do not support bare modules yet. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ### Совместимость, "nomodule" +<<<<<<< HEAD Старые браузеры не понимают атрибут `type="module"`. Скрипты с неизвестным атрибутом `type` просто игнорируются. Мы можем сделать для них "резервный" скрипт при помощи атрибута `nomodule`: +======= +Old browsers do not understand `type="module"`. Scripts of an unknown type are just ignored. For them, it's possible to provide a fallback using the `nomodule` attribute: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```html run + + diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-dom.view/index.js b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-dom.view/index.js new file mode 100644 index 0000000000..ea55b44781 --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-dom.view/index.js @@ -0,0 +1,24 @@ +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 = `Message: System status OK: ${new Date().toLocaleTimeString()}`; + element.deref().append(payload); + } else { // (8) + alert("The element has been deleted."); // (9) + clearInterval(timerId); + } + }, 1000); +}; \ No newline at end of file diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-01.svg b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-01.svg new file mode 100644 index 0000000000..2a507dbcdb --- /dev/null +++ b/1-js/99-js-misc/07-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/07-weakref-finalizationregistry/weakref-finalizationregistry-02.svg b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-02.svg new file mode 100644 index 0000000000..6cc199a128 --- /dev/null +++ b/1-js/99-js-misc/07-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/07-weakref-finalizationregistry/weakref-finalizationregistry-03.svg b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-03.svg new file mode 100644 index 0000000000..949a14f9f3 --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-03.svg @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + key + value + image-01.jpg + image-02.jpg + image-03.jpg + + + + + + + + + + + + + + WeakRef object + + + + + + + + + + + + + + + + WeakRef object + + + + + + + + + + + + + + + + + + + WeakRef object + + + + + + + \ No newline at end of file diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-04.svg b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-04.svg new file mode 100644 index 0000000000..1177d6580d --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-04.svg @@ -0,0 +1,77 @@ + + + + + + + name: "John" + Object + + admin + + + + + + + + + key + value + image-01.jpg + image-02.jpg + image-03.jpg + + + + + + + + + + + + + + WeakRef object + + + + + + + + + + + + + + + + WeakRef object + + + + + undefined + undefined + + + + + + + + + + + + + + + WeakRef object + + + \ No newline at end of file diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-05.svg b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-05.svg new file mode 100644 index 0000000000..e738f8e7ed --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-05.svg @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + image-02.jpg + image-03.jpg + + key + value + image-01.jpg + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + WeakRef object + + + + + + + + + + + + + + + + WeakRef object + + + + + undefined + undefined + Deleted by FinalizationRegistry cleanup callback + + + + + + + + + + + + + + + WeakRef object + + + + \ No newline at end of file diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-01.png b/1-js/99-js-misc/07-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/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-01.png differ diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-02.png b/1-js/99-js-misc/07-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/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-02.png differ diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-03.gif b/1-js/99-js-misc/07-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/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-03.gif differ diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-04.jpg b/1-js/99-js-misc/07-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/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-04.jpg differ diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-05.gif b/1-js/99-js-misc/07-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/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-05.gif differ diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-06.jpg b/1-js/99-js-misc/07-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/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-06.jpg differ diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-07.gif b/1-js/99-js-misc/07-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/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-07.gif differ diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-08.jpg b/1-js/99-js-misc/07-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/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-08.jpg differ diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/index.css b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/index.css new file mode 100644 index 0000000000..e6c9e39606 --- /dev/null +++ b/1-js/99-js-misc/07-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/07-weakref-finalizationregistry/weakref-finalizationregistry.view/index.html b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/index.html new file mode 100644 index 0000000000..7ce52f927f --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/index.html @@ -0,0 +1,49 @@ + + + + + + + Photo Library Collage + + + + +

+
+

+ Photo Library Collage +

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

+
+ +
+
+

Logger:

+
+
+
+
+
+ + + + + diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/index.js b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/index.js new file mode 100644 index 0000000000..983b34d9ab --- /dev/null +++ b/1-js/99-js-misc/07-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/07-weakref-finalizationregistry/weakref-finalizationregistry.view/utils.js b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/utils.js new file mode 100644 index 0000000000..f0140c116a --- /dev/null +++ b/1-js/99-js-misc/07-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/2-ui/1-document/01-browser-environment/article.md b/2-ui/1-document/01-browser-environment/article.md index a4a1144587..61f05d72a9 100644 --- a/2-ui/1-document/01-browser-environment/article.md +++ b/2-ui/1-document/01-browser-environment/article.md @@ -1,5 +1,6 @@ # Браузерное окружение, спецификации +<<<<<<< HEAD Язык JavaScript изначально был создан для веб-браузеров. Но с тех пор он значительно эволюционировал и превратился в кроссплатформенный язык программирования для решения широкого круга задач. Сегодня JavaScript может использоваться в браузере, на веб-сервере или в какой-то другой среде, даже в кофеварке. Каждая среда предоставляет свою функциональность, которую спецификация JavaScript называет *окружением*. @@ -7,6 +8,15 @@ Окружение предоставляет свои объекты и дополнительные функции, в дополнение базовым языковым. Браузеры, например, дают средства для управления веб-страницами. Node.js делает доступными какие-то серверные возможности и так далее. На картинке ниже в общих чертах показано, что доступно для JavaScript в браузерном окружении: +======= +The JavaScript language was initially created for web browsers. Since then, it has evolved into a language with many uses and platforms. + +A platform may be a browser, or a web-server or another *host*, or even a "smart" coffee machine if it can run JavaScript. Each of these provides platform-specific functionality. The JavaScript specification calls that a *host environment*. + +A host environment provides its own objects and functions in addition to the language core. Web browsers give a means to control web pages. Node.js provides server-side features, and so on. + +Here's a bird's-eye view of what we have when JavaScript runs in a web browser: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ![](windowObjects.svg) @@ -15,7 +25,11 @@ 1. Во-первых, это глобальный объект для JavaScript-кода, об этом более подробно говорится в главе . 2. Во-вторых, он также представляет собой окно браузера и располагает методами для управления им. +<<<<<<< HEAD Например, здесь мы используем `window` как глобальный объект: +======= +For instance, we can use it as a global object: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run global function sayHi() { @@ -26,17 +40,29 @@ function sayHi() { window.sayHi(); ``` +<<<<<<< HEAD А здесь мы используем `window` как объект окна браузера, чтобы узнать его высоту: +======= +And we can use it as a browser window, to show the window height: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run alert(window.innerHeight); // внутренняя высота окна браузера ``` +<<<<<<< HEAD Существует гораздо больше свойств и методов для управления окном браузера. Мы рассмотрим их позднее. ## DOM (Document Object Model) Document Object Model, сокращённо DOM - объектная модель документа, которая представляет все содержимое страницы в виде объектов, которые можно менять. +======= +There are more window-specific methods and properties, which we'll cover later. + +## DOM (Document Object Model) + +The Document Object Model, or DOM for short, represents all page content as objects that can be modified. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Объект `document` - основная "входная точка". С его помощью мы можем что-то создавать или менять на странице. @@ -49,13 +75,18 @@ document.body.style.background = "red"; setTimeout(() => document.body.style.background = "", 1000); ``` +<<<<<<< HEAD Мы использовали в примере только `document.body.style`, но на самом деле возможности по управлению страницей намного шире. Различные свойства и методы описаны в спецификации: - **DOM Living Standard** на +======= +Here, we used `document.body.style`, but there's much, much more. Properties and methods are described in the specification: [DOM Living Standard](https://dom.spec.whatwg.org). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```smart header="DOM - не только для браузеров" Спецификация DOM описывает структуру документа и предоставляет объекты для манипуляций со страницей. Существуют и другие, отличные от браузеров, инструменты, использующие DOM. +<<<<<<< HEAD Например, серверные скрипты, которые загружают и обрабатывают HTML-страницы, также могут использовать DOM. При этом они могут поддерживать спецификацию не полностью. ``` @@ -63,16 +94,34 @@ setTimeout(() => document.body.style.background = "", 1000); Правила стилей CSS структурированы иначе чем HTML. Для них есть отдельная спецификация [CSSOM](https://www.w3.org/TR/cssom-1/), которая объясняет, как стили должны представляться в виде объектов, как их читать и писать. CSSOM используется вместе с DOM при изменении стилей документа. В реальности CSSOM требуется редко, обычно правила CSS статичны. Мы редко добавляем/удаляем стили из JavaScript, но и это возможно. +======= +For instance, server-side scripts that download HTML pages and process them can also use the DOM. They may support only a part of the specification though. +``` + +```smart header="CSSOM for styling" +There's also a separate specification, [CSS Object Model (CSSOM)](https://www.w3.org/TR/cssom-1/) for CSS rules and stylesheets, that explains how they are represented as objects, and how to read and write them. + +The CSSOM is used together with the DOM when we modify style rules for the document. In practice though, the CSSOM is rarely required, because we rarely need to modify CSS rules from JavaScript (usually we just add/remove CSS classes, not modify their CSS rules), but that's also possible. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` ## BOM (Browser Object Model) +<<<<<<< HEAD Объектная модель браузера (Browser Object Model, BOM) - это дополнительные объекты, предоставляемые браузером (окружением), чтобы работать со всем, кроме документа. +======= +The Browser Object Model (BOM) represents additional objects provided by the browser (host environment) for working with everything except the document. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Например: +<<<<<<< HEAD - Объект [navigator](mdn:api/Window/navigator) даёт информацию о самом браузере и операционной системе. Среди множества его свойств самыми известными являются: `navigator.userAgent` -- информация о текущем браузере, и `navigator.platform` -- информация о платформе (может помочь в понимании того, в какой ОС открыт браузер -- Windows/Linux/Mac и так далее). - Объект [location](mdn:api/Window/location) позволяет получить текущий URL и перенаправить браузер по новому адресу. +======= +- 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 differentiate 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Вот как мы можем использовать объект `location`: @@ -83,33 +132,60 @@ if (confirm("Перейти на Wikipedia?")) { } ``` +<<<<<<< HEAD Функции `alert/confirm/prompt` тоже являются частью BOM: они не относятся непосредственно к странице, но представляют собой методы объекта окна браузера для коммуникации с пользователем. ```smart header="Спецификации" BOM является частью общей [спецификации HTML](https://html.spec.whatwg.org). Да, вы всё верно услышали. Спецификация HTML по адресу не только про "язык HTML" (теги, атрибуты), она также покрывает целое множество объектов, методов и специфичных для каждого браузера расширений DOM. Это всё "HTML в широком смысле". Для некоторых вещей есть отдельные спецификации, перечисленные на . +======= +The functions `alert/confirm/prompt` are also a part of the BOM: they are not directly related to the document, but represent pure browser methods for communicating with the user. + +```smart header="Specifications" +The BOM is a part of the general [HTML specification](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". Also, some parts have additional specs listed at . +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` ## Итого Говоря о стандартах, у нас есть: +<<<<<<< HEAD Спецификация DOM : описывает структуру документа, манипуляции с контентом и события, подробнее на . Спецификация CSSOM : Описывает файлы стилей, правила написания стилей и манипуляций с ними, а также то, как это всё связано со страницей, подробнее на . +======= +DOM specification +: Describes the document structure, manipulations, and events, see . + +CSSOM specification +: Describes stylesheets and style rules, manipulations with them, and their binding to documents, see . +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Спецификация HTML : Описывает язык HTML (например, теги) и BOM (объектную модель браузера) -- разные функции браузера: `setTimeout`, `alert`, `location` и так далее, подробнее на . Тут берётся за основу спецификация DOM и расширяется дополнительными свойствами и методами. Кроме того, некоторые классы описаны отдельно на . +<<<<<<< HEAD Пожалуйста, заметьте для себя эти ссылки, так как по ним содержится очень много информации, которую невозможно изучить полностью и держать в уме. Когда вам нужно будет прочитать о каком-то свойстве или методе, справочник на сайте Mozilla тоже очень хороший ресурс, хотя ничто не сравнится с чтением спецификации: она сложная и объёмная, но сделает ваши знания максимально полными. +======= +Please note these links, as there's so much to learn that it's impossible to cover everything and remember it all. + +When you'd like to read about a property or a method, the Mozilla manual at is also a nice resource, but the corresponding spec may be better: it's more complex and longer to read, but will make your fundamental knowledge sound and complete. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Для поиска чего-либо обычно удобно использовать интернет-поиск со словами "WHATWG [термин]" или "MDN [термин]", например , . +<<<<<<< HEAD А теперь давайте перейдём к изучению DOM, так как страница - это основа всего. +======= +Now, we'll get down to learning the DOM, because the document plays the central role in the UI. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/2-ui/1-document/02-dom-nodes/article.md b/2-ui/1-document/02-dom-nodes/article.md index 0856d16e75..cd05a48fc0 100644 --- a/2-ui/1-document/02-dom-nodes/article.md +++ b/2-ui/1-document/02-dom-nodes/article.md @@ -6,6 +6,7 @@ libs: # DOM-дерево +<<<<<<< HEAD Основой HTML-документа являются теги. В соответствии с объектной моделью документа ("Document Object Model", коротко DOM), каждый HTML-тег является объектом. Вложенные теги являются "детьми" родительского элемента. Текст, который находится внутри тега, также является объектом. @@ -13,6 +14,15 @@ libs: Все эти объекты доступны при помощи JavaScript, мы можем использовать их для изменения страницы. Например, `document.body` - объект для тега ``. +======= +The backbone of an HTML document is tags. + +According to the Document Object Model (DOM), every HTML tag is an object. Nested tags are "children" of the enclosing one. The text inside a tag is an object as well. + +All these objects are accessible using JavaScript, and we can use them to modify the page. + +For example, `document.body` is the object representing the `` tag. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Если запустить этот код, то `` станет красным на 3 секунды: @@ -22,20 +32,41 @@ document.body.style.background = 'red'; // сделать фон красным setTimeout(() => document.body.style.background = '', 3000); // вернуть назад ``` +<<<<<<< HEAD Это был лишь небольшой пример того, что может DOM. Скоро мы изучим много способов работать с DOM, но сначала нужно познакомиться с его структурой. ## Пример DOM Начнём с такого, простого, документа: +======= +Here we used `style.background` to change the background color of `document.body`, but there are many other properties, such as: + +- `innerHTML` -- HTML contents of the node. +- `offsetWidth` -- the node width (in pixels) +- ...and so on. + +Soon we'll learn more ways to manipulate the DOM, but first we need to know about its structure. + +## An example of the DOM + +Let's start with the following simple document: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```html run no-beautify +<<<<<<< HEAD О лосях Правда о лосях. +======= + About elk + + + The truth about elk. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` @@ -45,7 +76,11 @@ DOM -- это представление HTML-документа в виде д
@@ -56,17 +91,26 @@ drawHtmlTree(node1, 'div.domtree', 690, 320); Каждый узел этого дерева - это объект. +<<<<<<< HEAD Теги являются *узлами-элементами* (или просто элементами). Они образуют структуру дерева: `` -- это корневой узел, `` и `` его дочерние узлы и т.д. +======= +Tags are *element nodes* (or just elements) and form the tree structure: `` is at the root, then `` and `` are its children, etc. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Текст внутри элементов образует *текстовые узлы*, обозначенные как `#text`. Текстовый узел содержит в себе только строку текста. У него не может быть потомков, т.е. он находится всегда на самом нижнем уровне. +<<<<<<< HEAD Например, в теге `` есть текстовый узел `"О лосях"`. +======= +For instance, the `<title>` tag has the text `"About elk"`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Обратите внимание на специальные символы в текстовых узлах: - перевод строки: `↵` (в JavaScript он обозначается как `\n`) - пробел: `␣` +<<<<<<< HEAD Пробелы и переводы строки -- это полноправные символы, как буквы и цифры. Они образуют текстовые узлы и становятся частью дерева DOM. Так, в примере выше в теге `<head>` есть несколько пробелов перед `<title>`, которые образуют текстовый узел `#text` (он содержит в себе только перенос строки и несколько пробелов). Существует всего два исключения из этого правила: @@ -74,18 +118,35 @@ drawHtmlTree(node1, 'div.domtree', 690, 320); 2. Если мы записываем что-либо после закрывающего тега `</body>`, браузер автоматически перемещает эту запись в конец `body`, поскольку спецификация HTML требует, чтобы всё содержимое было внутри `<body>`. Поэтому после закрывающего тега `</body>` не может быть никаких пробелов. В остальных случаях всё просто -- если в документе есть пробелы (или любые другие символы), они становятся текстовыми узлами дерева DOM, и если мы их удалим, то в DOM их тоже не будет. +======= +Spaces and newlines are totally valid characters, like letters and digits. They form text nodes and become a part of the DOM. So, for instance, in the example above the `<head>` tag contains some spaces before `<title>`, and that text becomes a `#text` node (it contains a newline and some spaces only). + +There are only two top-level exclusions: +1. Spaces and newlines before `<head>` are ignored for historical reasons. +2. If we put something after `</body>`, then that is automatically moved inside the `body`, at the end, as the HTML spec requires that all content must be inside `<body>`. So there can't be any spaces after `</body>`. + +In other cases everything's straightforward -- if there are spaces (just like any character) in the document, then they become text nodes in the DOM, and if we remove them, then there won't be any. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Здесь пробельных текстовых узлов нет: ```html no-beautify <!DOCTYPE HTML> +<<<<<<< HEAD <html><head><title>О лосяхПравда о лосях. +======= +About elkThe truth about elk. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```
@@ -100,11 +161,19 @@ drawHtmlTree(node2, 'div.domtree', 690, 210); ## Автоисправление +<<<<<<< HEAD Если браузер сталкивается с некорректно написанным HTML-кодом, он автоматически корректирует его при построении DOM. Например, в начале документа всегда должен быть тег ``. Даже если его нет в документе -- он будет в дереве DOM, браузер его создаст. То же самое касается и тега ``. Например, если HTML-файл состоит из единственного слова `"Привет"`, браузер обернёт его в теги `` и ``, добавит необходимый тег ``, и DOM будет выглядеть так: +======= +If the browser encounters malformed HTML, it automatically corrects it when making the DOM. + +For instance, the top tag is always ``. Even if it doesn't exist in the document, it will exist in the DOM, because the browser will create it. The same goes for ``. + +As an example, if the HTML file is the single word `"Hello"`, the browser will wrap it into `` and ``, and add the required ``, and the DOM will be: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9
@@ -117,7 +186,11 @@ drawHtmlTree(node3, 'div.domtree', 690, 150); При генерации DOM браузер самостоятельно обрабатывает ошибки в документе, закрывает теги и так далее. +<<<<<<< HEAD Есть такой документ с незакрытыми тегами: +======= +A document with unclosed tags: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```html no-beautify

Привет @@ -126,7 +199,11 @@ drawHtmlTree(node3, 'div.domtree', 690, 150);

  • Папа ``` +<<<<<<< HEAD ...Но DOM будет нормальным, потому что браузер сам закроет теги и восстановит отсутствующие детали: +======= +...will become a normal DOM as the browser reads tags and restores the missing parts: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9
    @@ -136,8 +213,13 @@ let node4 = {"name":"HTML","nodeType":1,"children":[{"name":"HEAD","nodeType":1, drawHtmlTree(node4, 'div.domtree', 690, 360); +<<<<<<< HEAD ````warn header="Таблицы всегда содержат ``" Важный "особый случай" -- работа с таблицами. По стандарту DOM у них должен быть ``, но в HTML их можно написать (официально) без него. В этом случае браузер добавляет `` в DOM самостоятельно. +======= +````warn header="Tables always have ``" +An interesting "special case" is tables. By DOM specification they must have `` tag, but HTML text may omit it. Then the browser creates `` in the DOM automatically. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Для такого HTML: @@ -154,7 +236,11 @@ let node5 = {"name":"TABLE","nodeType":1,"children":[{"name":"TBODY","nodeType": drawHtmlTree(node5, 'div.domtree', 600, 200); +<<<<<<< HEAD Видите? Из пустоты появился ``, как будто документ и был таким. Важно знать об этом, иначе при работе с таблицами возможны сюрпризы. +======= +You see? The `` appeared out of nowhere. We should keep this in mind while working with tables to avoid surprises. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```` ## Другие типы узлов @@ -167,7 +253,11 @@ drawHtmlTree(node5, 'div.domtree', 600, 200); +<<<<<<< HEAD Правда о лосях. +======= + The truth about elk. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9
    1. Лось -- животное хитрое
    2. *!* @@ -182,7 +272,11 @@ drawHtmlTree(node5, 'div.domtree', 600, 200);
      @@ -193,38 +287,61 @@ drawHtmlTree(node6, 'div.domtree', 690, 500); **Все, что есть в HTML, даже комментарии, является частью DOM.** +<<<<<<< HEAD Даже директива ``, которую мы ставим в начале HTML, тоже является DOM-узлом. Она находится в дереве DOM прямо перед ``. Мы не будем рассматривать этот узел, мы даже не рисуем его на наших диаграммах, но он существует. +======= +Even the `` directive at the very beginning of HTML is also a DOM node. It's in the DOM tree right before ``. Few people know about that. We are not going to touch that node, we even don't draw it on diagrams, but it's there. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Даже объект `document`, представляющий весь документ, формально является DOM-узлом. Существует [12 типов узлов](https://dom.spec.whatwg.org/#node). Но на практике мы в основном работаем с 4 из них: +<<<<<<< HEAD 1. `document` -- "входная точка" в DOM. 2. узлы-элементы -- HTML-теги, основные строительные блоки. 3. текстовые узлы -- содержат текст. 4. комментарии -- иногда в них можно включить информацию, которая не будет показана, но доступна в DOM для чтения JS. +======= +1. `document` -- the "entry point" into DOM. +2. element nodes -- HTML-tags, the tree building blocks. +3. text nodes -- contain text. +4. comments -- sometimes we can put information there, it won't be shown, but JS can read it from the DOM. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## Поэкспериментируйте сами +<<<<<<< HEAD Чтобы посмотреть структуру DOM в реальном времени, попробуйте [Live DOM Viewer](https://software.hixie.ch/utilities/js/live-dom-viewer/). Просто введите что-нибудь в поле, и ниже вы увидите, как меняется DOM. +======= +To see the DOM structure in real-time, try [Live DOM Viewer](https://software.hixie.ch/utilities/js/live-dom-viewer/). Just type in the document, and it will show up as a DOM at an instant. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Другой способ исследовать DOM - это использовать инструменты разработчика браузера. Это то, что мы каждый день делаем при разработке. +<<<<<<< HEAD Для этого откройте страницу [elks.html](elks.html), включите инструменты разработчика и перейдите на вкладку Elements. +======= +To do so, open the web page [elk.html](elk.html), turn on the browser developer tools and switch to the Elements tab. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Выглядит примерно так: -![](elks.png) +![](elk.svg) Вы можете увидеть DOM, понажимать на элементы, детально рассмотреть их и так далее. Обратите внимание, что структура DOM в инструментах разработчика отображается в упрощённом виде. Текстовые узлы показаны как простой текст. И кроме пробелов нет никаких "пустых" текстовых узлов. Ну и отлично, потому что большую часть времени нас будут интересовать узлы-элементы. +<<<<<<< HEAD Клик по этой кнопке в левом верхнем углу инспектора позволяет при помощи мыши (или другого устройства ввода) выбрать элемент на веб-странице и "проинспектировать" его (браузер сам найдёт и отметит его во вкладке Elements). Этот способ отлично подходит, когда у нас огромная HTML-страница (и соответствующий ей огромный DOM), и мы хотим увидеть, где находится интересующий нас элемент. +======= +Clicking the button in the left-upper corner allows us to choose a node from the webpage using a mouse (or other pointer devices) and "inspect" it (scroll to it in the Elements tab). This works great when we have a huge HTML page (and corresponding huge DOM) and would like to see the place of a particular element in it. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Есть и другой способ сделать это: можно кликнуть на странице по элементу правой кнопкой мыши и в контекстном меню выбрать "Inspect". -![](inspect.png) +![](inspect.svg) В правой части инструментов разработчика находятся следующие подразделы: - **Styles** -- здесь мы видим CSS, применённый к текущему элементу: правило за правилом, включая встроенные стили (выделены серым). Почти всё можно отредактировать на месте, включая размеры, внешние и внутренние отступы. @@ -247,15 +364,19 @@ drawHtmlTree(node6, 'div.domtree', 690, 500); Теперь мы можем запускать на них команды. Например `$0.style.background = 'red'` сделает выбранный элемент красным, как здесь: -![](domconsole0.png) +![](domconsole0.svg) Это мы посмотрели как получить узел из Elements в Console. Есть и обратный путь: если есть переменная `node`, ссылающаяся на DOM-узел, можно использовать в консоли команду `inspect(node)`, чтобы увидеть этот элемент во вкладке Elements. +<<<<<<< HEAD Или мы можем просто вывести DOM-узел в консоль и исследовать "на месте", как `document.body` ниже: +======= +Or we can just output the DOM node in the console and explore "in-place", like `document.body` below: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 -![](domconsole1.png) +![](domconsole1.svg) Это может быть полезно для отладки. В следующей главе мы рассмотрим доступ и изменение DOM при помощи JavaScript. @@ -273,4 +394,8 @@ HTML/XML документы представлены в браузере в ви Здесь мы рассмотрели основы, наиболее часто используемые и важные действия для начала разработки. Подробную документацию по инструментам разработки Chrome Developer Tools можно найти на странице . Лучший способ изучить инструменты -- походить по разным вкладкам, почитать меню: большинство действий очевидны для пользователя. Позже, когда вы немного их изучите, прочитайте документацию и узнайте то, что осталось. +<<<<<<< HEAD У DOM-узлов есть свойства и методы, которые позволяют выбирать любой из элементов, изменять, перемещать их на странице и многое другое. Мы вернёмся к ним в последующих разделах. +======= +DOM nodes have properties and methods that allow us to travel between them, modify them, move around the page, and more. We'll get down to them in the next chapters. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/2-ui/1-document/02-dom-nodes/domconsole0.svg b/2-ui/1-document/02-dom-nodes/domconsole0.svg new file mode 100644 index 0000000000..eb99f193fe --- /dev/null +++ b/2-ui/1-document/02-dom-nodes/domconsole0.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/2-ui/1-document/02-dom-nodes/domconsole1.svg b/2-ui/1-document/02-dom-nodes/domconsole1.svg new file mode 100644 index 0000000000..02ef5f0a65 --- /dev/null +++ b/2-ui/1-document/02-dom-nodes/domconsole1.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/2-ui/1-document/02-dom-nodes/elks.html b/2-ui/1-document/02-dom-nodes/elk.html similarity index 54% rename from 2-ui/1-document/02-dom-nodes/elks.html rename to 2-ui/1-document/02-dom-nodes/elk.html index 07bb33f7a4..0659b6a256 100644 --- a/2-ui/1-document/02-dom-nodes/elks.html +++ b/2-ui/1-document/02-dom-nodes/elk.html @@ -1,7 +1,11 @@ +<<<<<<< HEAD:2-ui/1-document/02-dom-nodes/elks.html Правда о лосях. +======= + The truth about elk. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:2-ui/1-document/02-dom-nodes/elk.html
      1. Лось -- животное хитрое
      2. diff --git a/2-ui/1-document/02-dom-nodes/elk.svg b/2-ui/1-document/02-dom-nodes/elk.svg new file mode 100644 index 0000000000..448eea9d13 --- /dev/null +++ b/2-ui/1-document/02-dom-nodes/elk.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/2-ui/1-document/02-dom-nodes/inspect.svg b/2-ui/1-document/02-dom-nodes/inspect.svg new file mode 100644 index 0000000000..60696ec0d5 --- /dev/null +++ b/2-ui/1-document/02-dom-nodes/inspect.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/2-ui/1-document/03-dom-navigation/article.md b/2-ui/1-document/03-dom-navigation/article.md index d8ef379328..2a76fa845d 100644 --- a/2-ui/1-document/03-dom-navigation/article.md +++ b/2-ui/1-document/03-dom-navigation/article.md @@ -149,7 +149,11 @@ elem.childNodes[elem.childNodes.length - 1] === elem.lastChild Первый пункт - это хорошо для нас. Второй - бывает неудобен, но можно пережить. Если нам хочется использовать именно методы массива, то мы можем создать настоящий массив из коллекции, используя `Array.from`: ```js run +<<<<<<< HEAD alert( Array.from(document.body.childNodes).filter ); // сделали массив +======= + alert( Array.from(document.body.childNodes).filter ); // function +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` ```warn header="DOM-коллекции -- только для чтения" @@ -202,8 +206,13 @@ DOM-коллекции, и даже более -- *все* навигацион Например: ```js run +<<<<<<< HEAD // родителем является alert( document.body.parentNode === document.documentElement ); // выведет true +======= +// parent of is +alert( document.body.parentNode === document.documentElement ); // true +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 // после идёт alert( document.head.nextSibling ); // HTMLBodyElement @@ -214,7 +223,11 @@ alert( document.body.previousSibling ); // HTMLHeadElement ## Навигация только по элементам +<<<<<<< HEAD Навигационные свойства, описанные выше, относятся ко *всем* узлам в документе. В частности, в `childNodes` находятся и текстовые узлы и узлы-элементы и узлы-комментарии, если они есть. +======= +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 they exist. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Но для большинства задач текстовые узлы и узлы-комментарии нам не нужны. Мы хотим манипулировать узлами-элементами, которые представляют собой теги и формируют структуру страницы. @@ -224,10 +237,17 @@ alert( document.body.previousSibling ); // HTMLHeadElement Эти ссылки похожи на те, что раньше, только в ряде мест стоит слово `Element`: +<<<<<<< HEAD - `children` -- коллекция детей, которые являются элементами. - `firstElementChild`, `lastElementChild` -- первый и последний дочерний элемент. - `previousElementSibling`, `nextElementSibling` -- соседи-элементы. - `parentElement` -- родитель-элемент. +======= +- `children` -- only those children that are element nodes. +- `firstElementChild`, `lastElementChild` -- first and last element children. +- `previousElementSibling`, `nextElementSibling` -- neighbor elements. +- `parentElement` -- parent element. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ````smart header="Зачем нужен `parentElement`? Разве может родитель быть *не* элементом?" Свойство `parentElement` возвращает родитель-элемент, а `parentNode` возвращает "любого родителя". Обычно эти свойства одинаковы: они оба получают родителя. @@ -280,12 +300,21 @@ while(elem = elem.parentElement) { // идти наверх до Некоторые типы DOM-элементов предоставляют для удобства дополнительные свойства, специфичные для их типа. +<<<<<<< HEAD Таблицы -- отличный пример таких элементов. **Элемент ``**, в дополнение к свойствам, о которых речь шла выше, поддерживает следующие: - `table.rows` -- коллекция строк `` таблицы. - `table.caption/tHead/tFoot` -- ссылки на элементы таблицы ``, ``. - `table.tBodies` -- коллекция элементов таблицы `` (по спецификации их может быть больше одного). +======= +Tables are a great example of that, and represent a particularly important case: + +**The `
        `, `
        `** element supports (in addition to the given above) these properties: +- `table.rows` -- the collection of `` elements of the table. +- `table.caption/tHead/tFoot` -- references to elements ``, ``. +- `table.tBodies` -- the collection of `` elements (can be many according to the standard, but there will always be at least one -- even if it is not in the source HTML, the browser will put it in the DOM). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 **``, ``, ``** предоставляют свойство `rows`: - `tbody.rows` -- коллекция строк `` секции. @@ -311,8 +340,14 @@ while(elem = elem.parentElement) { // идти наверх до
        `, `
        ``` @@ -320,9 +355,15 @@ while(elem = elem.parentElement) { // идти наверх до Существуют также дополнительные навигационные ссылки для HTML-форм. Мы рассмотрим их позже, когда начнём работать с формами. +<<<<<<< HEAD ## Итого Получив DOM-узел, мы можем перейти к его ближайшим соседям используя навигационные ссылки. +======= +## Summary + +Given a DOM node, we can go to its immediate neighbors using navigation properties. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Есть два основных набора ссылок: 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 bb4dafaf02..56abe4a401 100644 --- a/2-ui/1-document/04-searching-elements-dom/article.md +++ b/2-ui/1-document/04-searching-elements-dom/article.md @@ -55,8 +55,13 @@ ``` +<<<<<<< HEAD ```warn header="Пожалуйста, не используйте такие глобальные переменные для доступа к элементам" Это поведение соответствует [стандарту](https://html.spec.whatwg.org/#dom-window-nameditem), но поддерживается в основном для совместимости, как осколок далёкого прошлого. +======= +```warn header="Please don't use id-named global variables to access elements" +This behavior is described [in the specification](https://html.spec.whatwg.org/multipage/window-object.html#named-access-on-the-window-object), but it is supported mainly for compatibility. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Браузер пытается помочь нам, смешивая пространства имён JS и DOM. Это удобно для простых скриптов, которые находятся прямо в HTML, но, вообще говоря, не очень хорошо. Возможны конфликты имён. Кроме того, при чтении JS-кода, не видя HTML, непонятно, откуда берётся переменная. @@ -71,8 +76,13 @@ Если в документе есть несколько элементов с одинаковым значением `id`, то поведение методов поиска непредсказуемо. Браузер может вернуть любой из них случайным образом. Поэтому, пожалуйста, придерживайтесь правила сохранения уникальности `id`. ``` +<<<<<<< HEAD ```warn header="Только `document.getElementById`, а не `anyElem.getElementById`" Метод `getElementById` можно вызвать только для объекта `document`. Он осуществляет поиск по `id` по всему документу. +======= +```warn header="Only `document.getElementById`, not `anyElem.getElementById`" +The method `getElementById` can be called only on `document` object. It looks for the given `id` in the whole document. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` ## querySelectorAll [#querySelectorAll] @@ -103,8 +113,13 @@ Этот метод действительно мощный, потому что можно использовать любой CSS-селектор. +<<<<<<< HEAD ```smart header="Псевдоклассы тоже работают" Псевдоклассы в CSS-селекторе, в частности `:hover` и `:active`, также поддерживаются. Например, `document.querySelectorAll(':hover')` вернёт коллекцию (в порядке вложенности: от внешнего к внутреннему) из текущих элементов под курсором мыши. +======= +```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 `` to the most nested one). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` ## querySelector [#querySelector] @@ -117,9 +132,15 @@ Предыдущие методы искали по DOM. +<<<<<<< HEAD Метод [elem.matches(css)](https://dom.spec.whatwg.org/#dom-element-matches) ничего не ищет, а проверяет, удовлетворяет ли `elem` CSS-селектору, и возвращает `true` или `false`. Этот метод удобен, когда мы перебираем элементы (например, в массиве или в чём-то подобном) и пытаемся выбрать те из них, которые нас интересуют. +======= +The [elem.matches(css)](https://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`. + +The method comes in handy when we are iterating over elements (like in an array or something) and trying to filter out those that interest us. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Например: @@ -143,7 +164,11 @@ *Предки* элемента - родитель, родитель родителя, его родитель и так далее. Вместе они образуют цепочку иерархии от элемента до вершины. +<<<<<<< HEAD Метод `elem.closest(css)` ищет ближайшего предка, который соответствует CSS-селектору. Сам элемент также включается в поиск. +======= +The method `elem.closest(css)` looks for the nearest ancestor that matches the CSS-selector. The `elem` itself is also included in the search. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Другими словами, метод `closest` поднимается вверх от элемента и проверяет каждого из родителей. Если он соответствует селектору, поиск прекращается. Метод возвращает либо предка, либо `null`, если такой элемент не найден. @@ -154,8 +179,13 @@
          +<<<<<<< HEAD
        • Глава 1
        • Глава 2
        • +======= +
        • Chapter 1
        • +
        • Chapter 2
        • +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9
        @@ -177,9 +207,15 @@ Здесь мы рассмотрим их для полноты картины, также вы можете встретить их в старом коде. +<<<<<<< HEAD - `elem.getElementsByTagName(tag)` ищет элементы с данным тегом и возвращает их коллекцию. Передав `"*"` вместо тега, можно получить всех потомков. - `elem.getElementsByClassName(className)` возвращает элементы, которые имеют данный CSS-класс. - `document.getElementsByName(name)` возвращает элементы с заданным атрибутом `name`. Очень редко используется. +======= +- `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. +- `document.getElementsByName(name)` returns elements with the given `name` attribute, document-wide. Very rarely used. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Например: ```js @@ -364,7 +400,11 @@ document.getElementsByTagName('input')[0].value = 5; +<<<<<<< HEAD Безусловно, наиболее часто используемыми в настоящее время являются методы `querySelector` и `querySelectorAll`, но и методы `getElement(s)By*` могут быть полезны в отдельных случаях, а также встречаются в старом коде. +======= +By far the most used are `querySelector` and `querySelectorAll`, but `getElement(s)By*` can be sporadically helpful or found in the old scripts. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Кроме того: 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 3d36432531..fd49a05b1f 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 @@ -9,7 +9,11 @@ У разных DOM-узлов могут быть разные свойства. Например, у узла, соответствующего тегу ``, есть свойства, связанные со ссылками, а у соответствующего тегу `` -- свойства, связанные с полем ввода и т.д. Текстовые узлы отличаются от узлов-элементов. Но у них есть общие свойства и методы, потому что все классы DOM-узлов образуют единую иерархию. +<<<<<<< HEAD Каждый DOM-узел принадлежит соответствующему встроенному классу. +======= +The root of the hierarchy is [EventTarget](https://dom.spec.whatwg.org/#eventtarget), that is inherited by [Node](https://dom.spec.whatwg.org/#interface-node), and other DOM nodes inherit from it. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Корнем иерархии является [EventTarget](https://dom.spec.whatwg.org/#eventtarget), от него наследует [Node](https://dom.spec.whatwg.org/#interface-node) и остальные DOM-узлы. @@ -17,15 +21,60 @@ ![](_dom-class-hierarchy.svg) +<<<<<<< HEAD Существуют следующие классы: - [EventTarget](https://dom.spec.whatwg.org/#eventtarget) -- это корневой "абстрактный" класс для всего. +======= +- [EventTarget](https://dom.spec.whatwg.org/#eventtarget) -- is the root "abstract" class for everything. + + 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](https://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 other classes that inherit from it (and so inherit the `Node` functionality). + +- [Document](https://dom.spec.whatwg.org/#interface-document), for historical reasons often inherited by `HTMLDocument` (though the latest spec doesn't dictate it) -- is a document as a whole. + + The `document` global object belongs exactly to this class. It serves as an entry point to the DOM. + +- [CharacterData](https://dom.spec.whatwg.org/#interface-characterdata) -- an "abstract" class, inherited by: + - [Text](https://dom.spec.whatwg.org/#interface-text) -- the class corresponding to a text inside elements, e.g. `Hello` in `

        Hello

        `. + - [Comment](https://dom.spec.whatwg.org/#interface-comment) -- the class for comments. They are not shown, but each comment becomes a member of DOM. + +- [Element](https://dom.spec.whatwg.org/#interface-element) -- is the base class for DOM elements. + + It provides element-level navigation like `nextElementSibling`, `children` and searching methods like `getElementsByTagName`, `querySelector`. + + A browser supports not only HTML, but also XML and SVG. So the `Element` class serves as a base for more specific classes: `SVGElement`, `XMLElement` (we don't need them here) and `HTMLElement`. + +- Finally, [HTMLElement](https://html.spec.whatwg.org/multipage/dom.html#htmlelement) is the basic class for all HTML elements. We'll work with it most of the time. + + It is inherited by concrete HTML elements: + - [HTMLInputElement](https://html.spec.whatwg.org/multipage/forms.html#htmlinputelement) -- the class for `` elements, + - [HTMLBodyElement](https://html.spec.whatwg.org/multipage/semantics.html#htmlbodyelement) -- the class for `` elements, + - [HTMLAnchorElement](https://html.spec.whatwg.org/multipage/semantics.html#htmlanchorelement) -- the class for `
        ` elements, + - ...and so on. + +There are many other tags with their own classes that may have specific properties and methods, while some elements, such as ``, `
        `, `
        ` do not have any specific properties, so they are instances of `HTMLElement` class. + +So, the full set of properties and methods of a given node comes as the result of the chain of inheritance. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Объекты этого класса никогда не создаются. Он служит основой, благодаря которой все DOM-узлы поддерживают так называемые "события", о которых мы поговорим позже. - [Node](https://dom.spec.whatwg.org/#interface-node) -- также является "абстрактным" классом, и служит основой для DOM-узлов. +<<<<<<< HEAD Он обеспечивает базовую функциональность: `parentNode`, `nextSibling`, `childNodes` и т.д. (это геттеры). Объекты класса `Node` никогда не создаются. Но есть определённые классы узлов, которые наследуются от него (и следовательно наследуют функционал `Node`). +======= +- `HTMLInputElement` -- this class provides input-specific properties, +- `HTMLElement` -- it provides common HTML element methods (and getters/setters), +- `Element` -- provides generic element methods, +- `Node` -- provides common DOM node properties, +- `EventTarget` -- gives the support for events (to be covered), +- ...and finally it inherits from `Object`, so "plain object" methods like `hasOwnProperty` are also available. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 - [Document](https://dom.spec.whatwg.org/#interface-document), по историческим причинам часто наследуется `HTMLDocument` (хотя последняя спецификация этого не навязывает) -- это документ в целом. @@ -154,14 +203,22 @@ interface HTMLInputElement: HTMLElement { ```html run - ``` +<<<<<<< HEAD **Будьте осторожны: в отличие от `innerHTML`, запись в `outerHTML` не изменяет элемент. Вместо этого элемент заменяется целиком во внешнем контексте.** +======= +**Beware: unlike `innerHTML`, writing to `outerHTML` does not change the element. Instead, it replaces it in the DOM.** +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Да, звучит странно, и это действительно необычно, поэтому здесь мы и отмечаем это особо. @@ -329,7 +399,11 @@ elem.innerHTML = elem.innerHTML + "..." div.outerHTML = '

        Новый элемент

        '; // (*) *!* +<<<<<<< HEAD // Содержимое div осталось тем же! +======= + // Wow! 'div' is still the same! +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 */!* alert(div.outerHTML); //
        Привет, мир!
        (**) @@ -337,6 +411,7 @@ elem.innerHTML = elem.innerHTML + "..." Какая-то магия, да? +<<<<<<< HEAD В строке `(*)` мы заменили `div` на `

        Новый элемент

        `. Во внешнем документе мы видим новое содержимое вместо `
        `. Но, как видно в строке (`**`), старая переменная `div` осталась прежней! Это потому, что использование `outerHTML` не изменяет DOM-элемент, а удаляет его из внешнего контекста и вставляет вместо него новый HTML-код. @@ -345,10 +420,24 @@ elem.innerHTML = elem.innerHTML + "..." - `div` был удалён из документа. - Вместо него был вставлен другой HTML `

        Новый элемент

        `. - В `div` осталось старое значение. Новый HTML не сохранён ни в какой переменной. +======= +In the line `(*)` we replaced `div` with `

        A new element

        `. In the outer document (the DOM) we can see the new content instead of the `
        `. But, as we can see in line `(**)`, the value of the old `div` variable hasn't changed! + +The `outerHTML` assignment does not modify the DOM element (the object referenced by, in this case, the variable 'div'), but removes it from the DOM and inserts the new HTML in its place. + +So what happened in `div.outerHTML=...` is: +- `div` was removed from the document. +- Another piece of HTML `

        A new element

        ` was inserted in its place. +- `div` still has its old value. The new HTML wasn't saved to any variable. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Здесь легко сделать ошибку: заменить `div.outerHTML`, а потом продолжить работать с `div`, как будто там новое содержимое. Но это не так. Подобное верно для `innerHTML`, но не для `outerHTML`. +<<<<<<< HEAD Мы можем писать в `elem.outerHTML`, но надо иметь в виду, что это не меняет элемент, в который мы пишем. Вместо этого создаётся новый HTML на его месте. Мы можем получить ссылки на новые элементы, обратившись к DOM. +======= +We can write to `elem.outerHTML`, but should keep in mind that it doesn't change the element we're writing to ('elem'). It puts the new HTML in its place instead. We can get references to the new elements by querying the DOM. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## nodeValue/data: содержимое текстового узла @@ -424,15 +513,24 @@ elem.innerHTML = elem.innerHTML + "..."
        ``` +<<<<<<< HEAD 1. В первый `
        ` имя приходит "как HTML": все теги стали именно тегами, поэтому мы видим имя, выделенное жирным шрифтом. 2. Во второй `
        ` имя приходит "как текст", поэтому мы видим `Винни-пух!`. +======= +1. The first `
        ` gets the name "as HTML": all tags become tags, so we see the bold name. +2. The second `
        ` gets the name "as text", so we literally see `Winnie-the-Pooh!`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 В большинстве случаев мы рассчитываем получить от пользователя текст и хотим, чтобы он интерпретировался как текст. Мы не хотим, чтобы на сайте появлялся произвольный HTML-код. Присваивание через `textContent` -- один из способов от этого защититься. @@ -440,7 +538,11 @@ elem.innerHTML = elem.innerHTML + "..." Атрибут и DOM-свойство "hidden" указывает на то, видим ли мы элемент или нет. +<<<<<<< HEAD Мы можем использовать его в HTML или назначать при помощи JavaScript, как в примере ниже: +======= +We can use it in HTML or assign it using JavaScript, like this: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```html run height="80"
        Оба тега DIV внизу невидимы
        @@ -501,7 +603,11 @@ elem.innerHTML = elem.innerHTML + "..." Главные свойства DOM-узла: `nodeType` +<<<<<<< HEAD : Свойство `nodeType` позволяет узнать тип DOM-узла. Его значение - числовое: `1` для элементов,`3` для текстовых узлов, и т.д. Только для чтения. +======= +: We can use it to see if a node is a text or an element node. It has a numeric value: `1` for elements,`3` for text nodes, and a few others for other node types. Read-only. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 `nodeName/tagName` : Для элементов это свойство возвращает название тега (записывается в верхнем регистре, за исключением XML-режима). Для узлов-неэлементов `nodeName` описывает, что это за узел. Только для чтения. @@ -523,4 +629,8 @@ elem.innerHTML = elem.innerHTML + "..." В зависимости от своего класса DOM-узлы имеют и другие свойства. Например у элементов `` (`HTMLInputElement`) есть свойства `value`, `type`, у элементов `` (`HTMLAnchorElement`) есть `href` и т.д. Большинство стандартных HTML-атрибутов имеют соответствующие свойства DOM. +<<<<<<< HEAD Впрочем, HTML-атрибуты и свойства DOM не всегда одинаковы, мы увидим это в следующей главе. +======= +However, HTML attributes and DOM properties are not always the same, as we'll see in the next chapter. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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..ccd93d5000 --- /dev/null +++ b/2-ui/1-document/05-basic-dom-node-properties/dom-class-hierarchy.svg @@ -0,0 +1 @@ +EventTargetNode Element HTMLElement HTMLBodyElement HTMLInputElement HTMLAnchorElement Document HTMLDocument CharacterData Document as a whole<input type="…"><body><a href="…">< div > ... </ div >Comment <!--comment-->Text "Hello" \ No newline at end of file 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 98f32b23f3..4e15d85863 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 @@ -8,7 +8,11 @@ ## DOM-свойства +<<<<<<< HEAD Ранее мы уже видели встроенные DOM-свойства. Их много. Но технически нас никто не ограничивает, и если этого мало -- мы можем добавить своё собственное свойство. +======= +We've already seen built-in DOM properties. There are a lot. But technically no one limits us, and if there aren't enough, we can add our own. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 DOM-узлы -- это обычные объекты JavaScript. Мы можем их изменять. @@ -162,7 +166,11 @@ document.body.sayHi(); // Hello, I'm BODY ``` +<<<<<<< HEAD Но есть и исключения, например, `input.value` синхронизируется только в одну сторону -- атрибут -> значение, но не в обратную: +======= +But there are exclusions, for instance `input.value` synchronizes only from attribute -> property, but not back: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```html run @@ -297,16 +305,26 @@ DOM-свойства не всегда являются строками. Нап
        ``` +<<<<<<< HEAD Почему атрибут может быть предпочтительнее таких классов, как `.order-state-new`, `.order-state-pending`, `order-state-canceled`? Это потому, что атрибутом удобнее управлять. Состояние может быть изменено достаточно просто: +======= +Why would using an attribute be preferable to having classes like `.order-state-new`, `.order-state-pending`, `.order-state-canceled`? + +Because an attribute is more convenient to manage. The state can be changed as easy as: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js // немного проще, чем удаление старого/добавление нового класса div.setAttribute('order-state', 'canceled'); ``` +<<<<<<< HEAD Но с пользовательскими атрибутами могут возникнуть проблемы. Что если мы используем нестандартный атрибут для наших целей, а позже он появится в стандарте и будет выполнять какую-то функцию? Язык HTML живой, он растёт, появляется больше атрибутов, чтобы удовлетворить потребности разработчиков. В этом случае могут возникнуть неожиданные эффекты. +======= +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, and more attributes appear to suit the needs of developers. There may be unexpected effects in such case. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Чтобы избежать конфликтов, существуют атрибуты вида [data-*](https://html.spec.whatwg.org/#embedding-custom-non-visible-data-with-the-data-*-attributes). 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 5b79fd609b..43552d85c4 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 @@ -6,7 +6,11 @@ importance: 5 У нас есть пустой DOM-элемент `elem` и строка `text`. +<<<<<<< HEAD Какие из этих 3-х команд работают одинаково? +======= +Which of these 3 commands will do exactly the same? +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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 953d1bf1a5..c999aa46d4 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 @@ -39,15 +39,28 @@ function update() { ```js let timerId; +<<<<<<< HEAD function clockStart() { // запустить часы timerId = setInterval(update, 1000); +======= +function clockStart() { // run the clock + if (!timerId) { // only set a new interval if the clock is not running + timerId = setInterval(update, 1000); + } +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 update(); // (*) } function clockStop() { clearInterval(timerId); - timerId = null; + timerId = null; // (**) } ``` +<<<<<<< HEAD Обратите внимание, что вызов `update()` не только запланирован, но и тут же производится в строке `(*)`. Иначе посетителю пришлось бы ждать до первого выполнения `setInterval`, то есть целую секунду. +======= +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. + +Also it is important to set a new interval in `clockStart()` only when the clock is not running. Otherways clicking the start button several times would set multiple concurrent intervals. Even worse - we would only keep the `timerID` of the last interval, losing references to all others. Then we wouldn't be able to stop the clock ever again! Note that we need to clear the `timerID` when the clock is stopped in the line `(**)`, so that it can be started again by running `clockStart()`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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 8a1bd9cacf..a09fa9395c 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,3 +1,4 @@ +<<<<<<< HEAD @@ -66,3 +67,73 @@ +======= + + + + + + + + +
        + hh:mm:ss +
        + + + + + + + + + + + +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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 bfe51303d6..04c7254d37 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,14 +1,13 @@ Решение короткое, но может показаться немного сложным, поэтому здесь я предоставлю подробные комментарии: - ```js -let sortedRows = Array.from(table.rows) - .slice(1) - .sort((rowA, rowB) => rowA.cells[0].innerHTML > rowB.cells[0].innerHTML ? 1 : -1); +let sortedRows = Array.from(table.tBodies[0].rows) // 1 + .sort((rowA, rowB) => rowA.cells[0].innerHTML.localeCompare(rowB.cells[0].innerHTML)); -table.tBodies[0].append(...sortedRows); +table.tBodies[0].append(...sortedRows); // (3) ``` +<<<<<<< HEAD 1. Получим все ``, как `table.querySelectorAll('tr')`, затем сделаем массив из них, потому что нам понадобятся методы массива. 2. Первый TR (`table.rows[0]`) -- это заголовок таблицы, поэтому мы берём `.slice(1)`. 3. Затем отсортируем их по содержимому в первом `` (по имени). @@ -17,3 +16,14 @@ table.tBodies[0].append(...sortedRows); Таблицы всегда имеют неявный элемент ``, поэтому нам нужно получить его и вставить в него: простой `table.append(...)` потерпит неудачу. Обратите внимание: нам не нужно их удалять, просто "вставляем их заново", они автоматически покинут старое место. +======= +The step-by-step algorthm: + +1. Get all ``, from ``. +2. Then sort them comparing by the content of the first `` (the name field). +3. Now insert nodes in the right order by `.append(...sortedRows)`. + +We don't have to remove row elements, just "re-insert", they leave the old place automatically. + +P.S. In our case, there's an explicit `` in the table, but even if HTML table doesn't have ``, the DOM structure always has it. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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 b47cf85582..2a3514a6fe 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 @@ -1,7 +1,6 @@ - - +<<<<<<< HEAD @@ -24,14 +23,33 @@
        Имя5
        +======= + + + + + + + + + + + + + + + + + + + + +
        NameSurnameAge
        JohnSmith10
        PeteBrown15
        AnnLee5
        .........
        +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 - - - - + table.tBodies[0].append(...sortedRows); + 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 37f06d90a5..aa8271e693 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 @@ -1,7 +1,6 @@ - - +<<<<<<< HEAD @@ -31,3 +30,30 @@ +======= +
        Имя
        + + + + + + + + + + + + + + + + + + + +
        NameSurnameAge
        JohnSmith10
        PeteBrown15
        AnnLee5
        .........
        + + +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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 18f3f3768b..ee467b7314 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,17 @@ HTML в задаче некорректен. В этом всё дело. +<<<<<<< HEAD Браузер исправил ошибку автоматически. Но внутри `` не может быть текста: в соответствии со спецификацией допускаются только табличные теги. Поэтому браузер показывает `"aaa"` *до* `
        `. +======= +The browser has to fix it automatically. But there may be no text inside the `
        `: according to the spec only table-specific tags are allowed. So the browser shows `"aaa"` *before* the `
        `. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Теперь очевидно, что когда мы удаляем таблицу, текст остаётся. +<<<<<<< HEAD На этот вопрос можно легко ответить, изучив DOM, используя инструменты браузера. Вы увидите `"aaa"` до элемента `
        `. +======= +The question can be easily answered by exploring the DOM using the browser tools. You'll see `"aaa"` before the `
        `. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Вообще, в стандарте 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 494e09c802..dc1c3e2aeb 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 @@ -10,6 +10,14 @@ importance: 1 Почему так происходит? +<<<<<<< HEAD +======= +In the example below, the call `table.remove()` removes the table from the document. + +But if you run it, you can see that the text `"aaa"` is still visible. + +Why does that happen? +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```html height=100 run
        @@ -23,6 +31,10 @@ importance: 1 alert(table); // таблица, как и должно быть table.remove(); +<<<<<<< HEAD // почему в документе остался текст "ааа"? +======= + // why there's still "aaa" in the document? +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` 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 4fee71695d..43f51ffc3e 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 @@ -8,9 +8,15 @@ importance: 4 Для каждого пункта: +<<<<<<< HEAD 1. Запрашивайте содержимое пункта у пользователя с помощью `prompt`. 2. Создавайте элемент `
      3. ` и добавляйте его к `
          `. 3. Продолжайте до тех пор, пока пользователь не отменит ввод (нажатием клавиши `key:Esc` или введя пустую строку). +======= +1. Ask a user about its content using `prompt`. +2. Create the `
        • ` with it and add it to `
            `. +3. Continue until the user cancels the input (by pressing `key:Esc` or via an empty entry). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Все элементы должны создаваться динамически. 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 c0e15c7b2a..1c4eaa6667 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 @@ -2,8 +2,16 @@ Алгоритм: +<<<<<<< HEAD 1. Создать заголовок таблицы с `
      4. `. 4. Увеличить день в `d`: `d.setDate(d.getDate()+1)`. Если `d.getMonth()` ещё не в следующем месяце, то добавим новую ячейку ``. +4. Increase the day in `d`: `d.setDate(d.getDate()+1)`. If `d.getMonth()` is not yet the next month, then add the new cell `
        ` и именами дней недели. 2. Создать объект даты `d = new Date(year, month-1)`. Это первый день месяца `month` (с учётом того, что месяцы в JS начинаются от 0, а не от 1). 3. Ячейки первого ряда пустые от начала и до дня недели `d.getDay()`, с которого начинается месяц. Заполним `` в календарь. Если это воскресенье, то добавим новую строку "</tr><tr>". 5. Если месяц закончился, но строка таблицы ещё не заполнена, добавим в неё пустые ``, чтобы сделать в календаре красивые пустые квадратики. +======= +1. Create the table header with `` and weekday names. +2. 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`). +3. First few cells till the first day of the month `d.getDay()` may be empty. Let's fill them in with `` to the calendar. If that's a Sunday, then add a newline "</tr><tr>". +5. If the month has finished, but the table row is not yet full, add empty `` into it, to make it square. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/2-ui/1-document/07-modifying-document/article.md b/2-ui/1-document/07-modifying-document/article.md index b19c56a9f0..cc87554ac0 100644 --- a/2-ui/1-document/07-modifying-document/article.md +++ b/2-ui/1-document/07-modifying-document/article.md @@ -1,12 +1,20 @@ # Изменение документа +<<<<<<< HEAD Модификации DOM - это ключ к созданию "живых" страниц. +======= +DOM modification is the key to creating "live" pages. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Здесь мы увидим, как создавать новые элементы "на лету" и изменять уже существующие. ## Пример: показать сообщение +<<<<<<< HEAD Рассмотрим методы на примере - а именно, добавим на страницу сообщение, которое будет выглядеть получше, чем `alert`. +======= +Let's demonstrate using an example. We'll add a message on the page that looks nicer than `alert`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Вот такое: @@ -28,7 +36,11 @@ */!* ``` +<<<<<<< HEAD Это был пример HTML. Теперь давайте создадим такой же `div`, используя JavaScript (предполагаем, что стили в HTML или во внешнем CSS-файле). +======= +That was the HTML example. Now let's create the same `div` with JavaScript (assuming that the styles are in the HTML/CSS already). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## Создание элемента @@ -50,21 +62,45 @@ DOM-узел можно создать двумя методами: Большую часть времени нам нужно создавать узлы элементов, такие как `div` для сообщения. +<<<<<<< HEAD ### Создание сообщения В нашем случае сообщение - это `div` с классом `alert` и HTML в нём: +======= +Most of the time we need to create element nodes, such as the `div` for the message. + +### Creating the message + +Creating the message div takes 3 steps: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js +// 1. Create
        element let div = document.createElement('div'); + +// 2. Set its class to "alert" div.className = "alert"; +<<<<<<< HEAD div.innerHTML = "Всем привет! Вы прочитали важное сообщение."; ``` Мы создали элемент, но пока он только в переменной. Мы не можем видеть его на странице, поскольку он не является частью документа. +======= + +// 3. Fill it with the content +div.innerHTML = "Hi there! You've read an important message."; +``` + +We've created the element. But as of now it's only in a variable named `div`, not in the page yet. So we can't see it. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## Методы вставки +<<<<<<< HEAD Чтобы наш `div` появился, нам нужно вставить его где-нибудь в `document`. Например, в `document.body`. +======= +To make the `div` show up, we need to insert it somewhere into `document`. For instance, into `` element, referenced by `document.body`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Для этого есть метод `append`, в нашем случае: `document.body.append(div)`. @@ -92,6 +128,7 @@ div.innerHTML = "Всем привет! Вы прочитали ``` +<<<<<<< HEAD Вот методы для различных вариантов вставки: - `node.append(...nodes or strings)` -- добавляет узлы или строки в конец `node`, @@ -101,6 +138,23 @@ div.innerHTML = "Всем привет! Вы прочитали - `node.replaceWith(...nodes or strings)` –- заменяет `node` заданными узлами или строками. Вот пример использования этих методов, чтобы добавить новые элементы в список и текст до/после него: +======= +Here we called `append` on `document.body`, but we can call `append` method on any other element, to put another element into it. For instance, we can append something to `
        ` by calling `div.append(anotherElement)`. + +Here are more insertion methods, they specify different places where to insert: + +- `node.append(...nodes or strings)` -- append nodes or strings *at the end* of `node`, +- `node.prepend(...nodes or strings)` -- insert nodes or strings *at the beginning* of `node`, +- `node.before(...nodes or strings)` –- insert nodes or strings *before* `node`, +- `node.after(...nodes or strings)` –- insert nodes or strings *after* `node`, +- `node.replaceWith(...nodes or strings)` –- replaces `node` with the given nodes or strings. + +Arguments of these methods are an arbitrary list of DOM nodes to insert, or text strings (that become text nodes automatically). + +Let's see them in action. + +Here's an example of using these methods to add items to a list and the text before/after it: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```html autorun
          @@ -123,7 +177,11 @@ div.innerHTML = "Всем привет! Вы прочитали ``` +<<<<<<< HEAD Наглядная иллюстрация того, куда эти методы вставляют: +======= +Here's a visual picture of what the methods do: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ![](before-prepend-append-after.svg) @@ -141,7 +199,11 @@ before after ``` +<<<<<<< HEAD Эти методы могут вставлять несколько узлов и текстовых фрагментов за один вызов. +======= +As said, these methods can insert multiple nodes and text pieces in a single call. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Например, здесь вставляется строка и элемент: @@ -152,7 +214,11 @@ after ``` +<<<<<<< HEAD Весь текст вставляется *как текст*. +======= +Please note: the text is inserted "as text", not "as HTML", with proper escaping of characters such as `<`, `>`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Поэтому финальный HTML будет: @@ -168,7 +234,11 @@ after Поэтому эти методы могут использоваться только для вставки DOM-узлов или текстовых фрагментов. +<<<<<<< HEAD А что, если мы хотим вставить HTML именно "как html", со всеми тегами и прочим, как делает это `elem.innerHTML`? +======= +But what if we'd like to insert an HTML string "as html", with all tags and stuff working, in the same manner as `elem.innerHTML` does it? +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## insertAdjacentHTML/Text/Element @@ -201,7 +271,11 @@ after

          Пока

          ``` +<<<<<<< HEAD Так мы можем добавлять произвольный HTML на страницу. +======= +That's how we can append arbitrary HTML to the page. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Варианты вставки: @@ -534,6 +608,7 @@ ul.append(...getListContent()); // append + оператор "..." = друзь Все эти методы возвращают `node`. +<<<<<<< HEAD - Если нужно вставить фрагмент HTML, то `elem.insertAdjacentHTML(where, html)` вставляет в зависимости от `where`: - `"beforebegin"` -- вставляет `html` прямо перед `elem`, - `"afterbegin"` -- вставляет `html` в `elem` в начало, @@ -541,6 +616,15 @@ ul.append(...getListContent()); // append + оператор "..." = друзь - `"afterend"` -- вставляет `html` сразу после `elem`. Также существуют похожие методы `elem.insertAdjacentText` и `elem.insertAdjacentElement`, они вставляют текстовые строки и элементы, но они редко используются. +======= +- Given some HTML in `html`, `elem.insertAdjacentHTML(where, html)` inserts it depending on the value of `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`. + + Also there are similar methods, `elem.insertAdjacentText` and `elem.insertAdjacentElement`, that insert text strings and elements, but they are rarely used. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 - Чтобы добавить HTML на страницу до завершения её загрузки: - `document.write(html)` 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 d81ef38a00..2b75c9f4e2 100644 --- a/2-ui/1-document/08-styles-and-classes/article.md +++ b/2-ui/1-document/08-styles-and-classes/article.md @@ -158,7 +158,20 @@ setTimeout(() => document.body.style.display = "", 1000); // возврат к Если мы установим в `style.display` пустую строку, то браузер применит CSS-классы и встроенные стили, как если бы такого свойства `style.display` вообще не было. +<<<<<<< HEAD ## Следите за единицами измерения +======= +Also there is a special method for that, `elem.style.removeProperty('style property')`. So, We can remove a property like this: + +```js run +document.body.style.background = 'red'; //set background to red + +setTimeout(() => document.body.style.removeProperty('background'), 1000); // remove background after 1 second +``` + +````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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Не забудьте добавить к значениям единицы измерения. @@ -254,8 +267,13 @@ pseudo ```smart header="Вычисленное (computed) и окончательное (resolved) значения" Есть две концепции в [CSS](https://drafts.csswg.org/cssom/#resolved-values): +<<<<<<< HEAD 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`. +======= +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`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Давным-давно `getComputedStyle` был создан для получения вычисленных значений, но оказалось, что окончательные значения гораздо удобнее, и стандарт изменился. @@ -265,7 +283,11 @@ pseudo ````warn header="`getComputedStyle` требует полное свойство!" Для правильного получения значения нужно указать точное свойство. Например: `paddingLeft`, `marginTop`, `borderTopWidth`. При обращении к сокращённому: `padding`, `margin`, `border` – правильный результат не гарантируется. +<<<<<<< HEAD Например, если есть свойства `paddingLeft/paddingTop`, то что мы получим вызывая `getComputedStyle(elem).padding`? Ничего, или, может быть, "сгенерированное" значение из известных внутренних отступов? Стандарта для этого нет. +======= +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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```` ```smart header="Стили, применяемые к посещённым `:visited` ссылкам, скрываются!" @@ -273,7 +295,11 @@ pseudo Но `getComputedStyle` не даёт доступ к этой информации, чтобы произвольная страница не могла определить, посещал ли пользователь ту или иную ссылку, проверив стили. +<<<<<<< HEAD JavaScript не видит стили, применяемые с помощью `:visited`. Кроме того, в CSS есть ограничение, которое запрещает в целях безопасности применять к `:visited` CSS-стили, изменяющие геометрию элемента. Это гарантирует, что нет обходного пути для "злой" страницы проверить, была ли ссылка посещена и, следовательно, нарушить конфиденциальность. +======= +JavaScript may not see the styles applied by `:visited`. And also, there's a limitation in CSS that forbids applying geometry-changing styles in `:visited`. That's to guarantee that there's no side way for an evil page to test if a link was visited and hence to break the privacy. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` ## Итого 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 9f806ecf6d..9301953c54 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 @@ -4,7 +4,11 @@ importance: 5 # Найти размер прокрутки снизу +<<<<<<< HEAD Свойство `elem.scrollTop` содержит размер прокрученной области при отсчёте сверху. А как подсчитать размер прокрутки снизу (назовём его `scrollBottom`)? +======= +The `elem.scrollTop` property is the size of the scrolled out part from the top. How to get the size of the bottom scroll (let's call it `scrollBottom`)? +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Напишите соответствующее выражение для произвольного элемента `elem`. 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 79d0b1f845..61a4e9e290 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 @@ -20,7 +20,7 @@
          - . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
          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 12cf07d69e..2624db59b1 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 @@ -24,17 +24,30 @@ ball.style.left = Math.round(field.clientWidth / 2 - ball.offsetWidth / 2) + 'px ball.style.top = Math.round(field.clientHeight / 2 - ball.offsetHeight / 2) + 'px'; ``` +<<<<<<< HEAD **Внимание, подводный камень!** +======= +Now the ball is finally centered. + +````warn header="Attention: the pitfall!" +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Код выше стабильно работать не будет, потому что `` идёт без ширины/высоты: ```html ``` +```` Если браузеру неизвестны ширина и высота изображения (из атрибута HTML-тега или CSS-свойств), он считает их равными `0` до тех пор, пока изображение не загрузится. +<<<<<<< HEAD При первой загрузке браузер обычно кеширует изображения, так что при последующей загрузке оно будет доступно тут же, вместе с размерами. Но при первой загрузке значение ширины мяча `ball.offsetWidth` равно `0`. Это приводит к вычислению неверных координат. +======= +So the value of `ball.offsetWidth` will be `0` until the image loads. That leads to wrong coordinates in the code above. + +After the first load, the browser usually caches the image, and on reloads it will have the size immediately. But on the first load the value of `ball.offsetWidth` is `0`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Мы можем исправить это, добавив атрибуты `width/height` тегу ``: 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 c7712b25f8..42bb7306ab 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,13 @@ 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 685edda8a3..cca4e745ca 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 @@ -10,7 +10,11 @@ importance: 5 Каковы координаты центра поля? +<<<<<<< HEAD Вычислите их и используйте, чтобы поместить мяч в центр поля: +======= +Calculate them and use to place the ball into the center of the green field: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 [iframe src="solution" height=180] 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 211e54b708..33bf079e2c 100644 --- a/2-ui/1-document/09-size-and-scroll/article.md +++ b/2-ui/1-document/09-size-and-scroll/article.md @@ -55,6 +55,7 @@ Эти свойства редко используются, но так как они являются "самыми внешними" метриками, мы начнём с них. +<<<<<<< HEAD В свойстве `offsetParent` находится предок элемента, который используется внутри браузера для вычисления координат при рендеринге. То есть, ближайший предок, который удовлетворяет следующим условиям: @@ -62,6 +63,15 @@ 1. Является CSS-позиционированным (CSS-свойство `position` равно `absolute`, `relative`, `fixed` или `sticky`), 2. или `
        `, ``, ``, 3. или ``. +======= +The `offsetParent` is the nearest ancestor that the browser uses for calculating coordinates during rendering. + +That's the nearest ancestor that is one of the following: + +1. CSS-positioned (`position` is `absolute`, `relative`, `fixed` or `sticky`), or +2. `
        `, ``, or ``, or +3. ``. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Свойства `offsetLeft/offsetTop` содержат координаты x/y относительно верхнего левого угла `offsetParent`. @@ -104,9 +114,15 @@ ````smart header="Метрики для не показываемых элементов равны нулю." Координаты и размеры в JavaScript устанавливаются только для видимых элементов. +<<<<<<< HEAD Если элемент (или любой его родитель) имеет `display:none` или отсутствует в документе, то все его метрики равны нулю (или `null`, если это `offsetParent`). Например, свойство `offsetParent` равно `null`, а `offsetWidth` и `offsetHeight` равны `0`, когда мы создали элемент, но ещё не вставили его в документ, или если у элемента (или у его родителя) `display:none`. +======= +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` for `offsetParent`). + +For example, `offsetParent` is `null`, and `offsetWidth`, `offsetHeight` are `0` when we created an element, but haven't inserted it into the document yet, or it (or its ancestor) has `display:none`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Мы можем использовать это, чтобы делать проверку на видимость: @@ -116,7 +132,11 @@ function isHidden(elem) { } ``` +<<<<<<< HEAD Заметим, что функция `isHidden` также вернёт `true` для элементов, которые в принципе показываются, но их размеры равны нулю. +======= +Please note that such `isHidden` returns `true` for elements that are on-screen, but have zero sizes. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```` ## clientTop/Left @@ -211,7 +231,11 @@ element.style.height = `${element.scrollHeight}px`;
        Кликни
        Меня
        1
        2
        3
        4
        5
        6
        7
        8
        9
        ``` +<<<<<<< HEAD Установка значения `scrollTop` на `0` или на большое значение, такое как `1e9`, прокрутит элемент в самый верх/низ соответственно. +======= +Setting `scrollTop` to `0` or a big value, such as `1e9` will make the element scroll to the very top/bottom respectively. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```` ## Не стоит брать width/height из CSS @@ -243,9 +267,15 @@ alert( getComputedStyle(elem).width ); // показывает CSS-ширину ``` +<<<<<<< HEAD Конечно, с точки зрения CSS `width:auto` – совершенно нормально, но нам-то в JavaScript нужен конкретный размер в `px`, который мы могли бы использовать для вычислений. Получается, что в данном случае ширина из CSS вообще бесполезна. Есть и ещё одна причина: полоса прокрутки. Бывает, без полосы прокрутки код работает прекрасно, но стоит ей появиться, как начинают проявляться баги. Так происходит потому, что полоса прокрутки "отъедает" место от области внутреннего содержимого в некоторых браузерах. Таким образом, реальная ширина содержимого *меньше* CSS-ширины. Как раз это и учитывают свойства `clientWidth/clientHeight`. +======= + 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. + +And there's one more reason: a scrollbar. Sometimes the code that works fine without a scrollbar becomes buggy 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ...Но с `getComputedStyle(elem).width` ситуация иная. Некоторые браузеры (например, Chrome) возвращают реальную внутреннюю ширину с вычетом ширины полосы прокрутки, а некоторые (например, Firefox) -- именно CSS-свойство (игнорируя полосу прокрутки). Эти кроссбраузерные отличия – ещё один повод не использовать `getComputedStyle`, а использовать свойства-метрики. @@ -265,6 +295,7 @@ alert( getComputedStyle(elem).width ); // показывает CSS-ширину У элементов есть следующие геометрические свойства (метрики): +<<<<<<< HEAD - `offsetParent` -- ближайший CSS-позиционированный родитель или ближайший `td`, `th`, `table`, `body`. - `offsetLeft/offsetTop` -- позиция в пикселях верхнего левого угла относительно `offsetParent`. - `offsetWidth/offsetHeight` -- "внешняя" ширина/высота элемента, включая рамки. @@ -272,5 +303,14 @@ alert( getComputedStyle(elem).width ); // показывает CSS-ширину - `clientWidth/clientHeight` -- ширина/высота содержимого вместе с внутренними отступами `padding`, но без полосы прокрутки. - `scrollWidth/scrollHeight` -- ширина/высота содержимого, аналогично `clientWidth/Height`, но учитывают прокрученную, невидимую область элемента. - `scrollLeft/scrollTop` -- ширина/высота прокрученной сверху части элемента, считается от верхнего левого угла. +======= +- `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 distances from the upper-left outer corner to the upper-left inner (content + padding) 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, just like `clientWidth/clientHeight`, but also include scrolled-out, invisible part of the element. +- `scrollLeft/scrollTop` -- width/height of the scrolled out upper part of the element, starting from its upper-left corner. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Все свойства доступны только для чтения, кроме `scrollLeft/scrollTop`, изменение которых заставляет браузер прокручивать элемент. 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 09335e0239..d6a9e25e36 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,12 +1,22 @@ # Размеры и прокрутка окна +<<<<<<< HEAD Как узнать ширину и высоту окна браузера? Как получить полную ширину и высоту документа, включая прокрученную часть? Как прокрутить страницу с помощью JavaScript? Для большинства таких запросов мы можем использовать корневой элемент документа `document.documentElement`, который соответствует тегу ``. Однако есть дополнительные методы и особенности, которые необходимо учитывать. +======= +How do we find the width and height of the browser window? How do we get the full width and height of the document, including the scrolled out part? How do we scroll the page using JavaScript? + +For this type of information, we can use the root document element `document.documentElement`, that corresponds to the `` tag. But there are additional methods and peculiarities to consider. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## Ширина/высота окна +<<<<<<< HEAD Чтобы получить ширину/высоту окна, можно взять свойства `clientWidth/clientHeight` из `document.documentElement`: +======= +To get window width and height, we can use the `clientWidth/clientHeight` of `document.documentElement`: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ![](document-client-width-height.svg) @@ -16,12 +26,21 @@ ``` +<<<<<<< HEAD ````warn header="Не `window.innerWidth/Height`" Браузеры также поддерживают свойства `window.innerWidth/innerHeight`. Вроде бы, похоже на то, что нам нужно. Почему же не использовать их? Если есть полоса прокрутки, и она занимает какое-то место, то свойства `clientWidth/clientHeight` указывают на ширину/высоту документа без неё (за её вычетом). Иными словами, они возвращают высоту/ширину видимой части документа, доступной для содержимого. А `window.innerWidth/innerHeight` включают в себя полосу прокрутки. +======= +````warn header="Not `window.innerWidth/innerHeight`" +Browsers also support properties like `window.innerWidth/innerHeight`. They look like what we want, so why not to use them instead? + +If there exists a scrollbar, and it occupies some space, `clientWidth/clientHeight` provide the width/height without it (subtract it). In other words, they return the width/height of the visible part of the document, available for the content. + +`window.innerWidth/innerHeight` includes the scrollbar. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Если полоса прокрутки занимает некоторое место, то эти две строки выведут разные значения: ```js run @@ -29,7 +48,11 @@ alert( window.innerWidth ); // полная ширина окна alert( document.documentElement.clientWidth ); // ширина окна за вычетом полосы прокрутки ``` +<<<<<<< HEAD В большинстве случаев нам нужна *доступная* ширина окна: для рисования или позиционирования. Полоса прокрутки "отъедает" её часть. Поэтому следует использовать `documentElement.clientHeight/Width`. +======= +In most cases, we need the *available* window width in order to draw or position something within scrollbars (if there are any), so we should use `documentElement.clientHeight/clientWidth`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```` ```warn header="`DOCTYPE` -- это важно" @@ -40,9 +63,15 @@ alert( document.documentElement.clientWidth ); // ширина окна за в ## Ширина/высота документа +<<<<<<< HEAD Теоретически, т.к. корневым элементом документа является `documentElement`, и он включает в себя всё содержимое, мы можем получить полный размер документа как `documentElement.scrollWidth/scrollHeight`. Но именно на этом элементе, для страницы в целом, эти свойства работают не так, как предполагается. В Chrome/Safari/Opera, если нет прокрутки, то `documentElement.scrollHeight` может быть даже меньше, чем `documentElement.clientHeight`! С точки зрения элемента это невозможная ситуация. +======= +Theoretically, as the root document element is `document.documentElement`, and it encloses all the content, we could measure the document's full size as `document.documentElement.scrollWidth/scrollHeight`. + +But on that element, 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`! Weird, right? +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Чтобы надёжно получить полную высоту документа, нам следует взять максимальное из этих свойств: @@ -61,12 +90,20 @@ alert('Полная высота документа с прокручиваем ## Получение текущей прокрутки [#page-scroll] +<<<<<<< HEAD Обычные элементы хранят текущее состояние прокрутки в `elem.scrollLeft/scrollTop`. Что же со страницей? В большинстве браузеров мы можем обратиться к `documentElement.scrollLeft/Top`, за исключением основанных на старом WebKit (Safari), где есть ошибка ([5991](https://bugs.webkit.org/show_bug.cgi?id=5991)), и там нужно использовать `document.body` вместо `document.documentElement`. К счастью, нам совсем не обязательно запоминать эти особенности, потому что текущую прокрутку можно прочитать из свойств `window.pageXOffset/pageYOffset`: +======= +DOM elements have their current scroll state in their `scrollLeft/scrollTop` properties. + +For document scroll, `document.documentElement.scrollLeft/scrollTop` works in most browsers, except older WebKit-based ones, like Safari (bug [5991](https://bugs.webkit.org/show_bug.cgi?id=5991)), where we should use `document.body` instead of `document.documentElement`. + +Luckily, we don't have to remember these peculiarities at all, because the scroll is available in the special properties, `window.pageXOffset/pageYOffset`: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run alert('Текущая прокрутка сверху: ' + window.pageYOffset); @@ -75,6 +112,7 @@ alert('Текущая прокрутка слева: ' + window.pageXOffset); Эти свойства доступны только для чтения. +<<<<<<< HEAD ```smart header="В качестве свойств объекта `window` также доступны `scrollX` и `scrollY`" По историческим причинам существует два аналога `window.pageXOffset` и `window.pageYOffset`: - `window.pageXOffset` -- то же самое, что и `window.scrollX`. @@ -87,13 +125,33 @@ alert('Текущая прокрутка слева: ' + window.pageXOffset); Для прокрутки страницы из JavaScript её DOM должен быть полностью построен. Например, если мы попытаемся прокрутить страницу из скрипта, подключенного в ``, это не сработает. +======= +```smart header="Also available as `window` properties `scrollX` and `scrollY`" +For historical reasons, both properties exist, but they are the same: +- `window.pageXOffset` is an alias of `window.scrollX`. +- `window.pageYOffset` is an alias of `window.scrollY`. +``` + +## Scrolling: scrollTo, scrollBy, scrollIntoView [#window-scroll] + +```warn +To scroll the page with JavaScript, its DOM must be fully built. + +For instance, if we try to scroll the page with a script in ``, it won't work. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` Обычные элементы можно прокручивать, изменяя `scrollTop/scrollLeft`. +<<<<<<< HEAD Мы можем сделать то же самое для страницы в целом, используя `document.documentElement.scrollTop/Left` (кроме основанных на старом WebKit (Safari), где, как сказано выше, `document.body.scrollTop/Left`). Есть и другие способы, в которых подобных несовместимостей нет: специальные методы [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). +======= +We can do the same for the page using `document.documentElement.scrollTop/scrollLeft` (except Safari, where `document.body.scrollTop/Left` should be used instead). + +Alternatively, there's a simpler, universal solution: special methods [window.scrollBy(x,y)](mdn:api/Window/scrollBy) and [window.scrollTo(pageX,pageY)](mdn:api/Window/scrollTo). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 - Метод `scrollBy(x,y)` прокручивает страницу *относительно её текущего положения*. Например, `scrollBy(0,10)` прокручивает страницу на `10px` вниз. @@ -142,11 +200,16 @@ window.scrollTo({ ## scrollIntoView +<<<<<<< HEAD Для полноты картины давайте рассмотрим ещё один метод: [elem.scrollIntoView(top)](mdn:api/Element/scrollIntoView). +======= +For completeness, let's cover one more method: [elem.scrollIntoView(top)](mdn:api/Element/scrollIntoView). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Вызов `elem.scrollIntoView(top)` прокручивает страницу, чтобы `elem` оказался вверху. У него есть один аргумент: +<<<<<<< HEAD - если `top=true` (по умолчанию), то страница будет прокручена, чтобы `elem` появился в верхней части окна. Верхний край элемента совмещён с верхней частью окна. - если `top=false`, то страница будет прокручена, чтобы `elem` появился внизу. Нижний край элемента будет совмещён с нижним краем окна. @@ -156,12 +219,24 @@ window.scrollTo({ А следующая кнопка прокрутит страницу так, что она сама окажется внизу +======= +- 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 will be 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 will be aligned with the window bottom. + +```online +The button below scrolls the page to position itself at the window top: + + + +And this button scrolls the page to position itself at the bottom: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` Как и `scrollTo`/`scrollBy`, `scrollIntoView` также принимает объект `options` как аргумент (он немного отличается): +<<<<<<< HEAD ```js this.scrollIntoView(options). ``` @@ -185,28 +260,52 @@ this.scrollIntoView({ Иногда нам нужно сделать документ "непрокручиваемым". Например, при показе большого диалогового окна над документом – чтобы посетитель мог прокручивать это окно, но не документ. Чтобы запретить прокрутку страницы, достаточно установить `document.body.style.overflow = "hidden"`. +======= +Sometimes we need to make the document "unscrollable". For instance, when we need to cover the page with a large message requiring immediate attention, and we want the visitor to interact with that message, not with the document. + +To make the document unscrollable, it's enough to set `document.body.style.overflow = "hidden"`. The page will "freeze" at its current scroll position. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```online Попробуйте сами: - + - + +<<<<<<< HEAD Первая кнопка останавливает прокрутку, вторая возобновляет её. +======= +The first button freezes the scroll, while the second one releases it. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` Аналогичным образом мы можем "заморозить" прокрутку для других элементов, а не только для `document.body`. +<<<<<<< HEAD Недостатком этого способа является то, что сама полоса прокрутки исчезает. Если она занимала некоторую ширину, то теперь эта ширина освободится, и содержимое страницы расширится, текст «прыгнет», заняв освободившееся место. Это выглядит немного странно, но это можно обойти, если сравнить `clientWidth` до и после остановки, и если `clientWidth` увеличится (значит полоса прокрутки исчезла), то добавить `padding` в `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. If it increased (the scrollbar disappeared), then add `padding` to `document.body` in place of the scrollbar to keep the content width the same. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Размеры: +<<<<<<< HEAD - Ширина/высота видимой части документа (ширина/высота области содержимого): `document.documentElement.clientWidth/Height` - Ширина/высота всего документа со всей прокручиваемой областью страницы: +======= +Geometry: + +- Width/height of the visible part of the document (content area width/height): `document.documentElement.clientWidth/clientHeight` +- Width/height of the whole document, with the scrolled out part: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js let scrollHeight = Math.max( 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 ac013283ce..3765d19f00 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,6 +1,10 @@ # Внешние углы +<<<<<<< HEAD Координаты внешних углов -- это как раз то, что возвращает функция [elem.getBoundingClientRect()](https://developer.mozilla.org/ru/docs/DOM/element.getBoundingClientRect). +======= +Outer corners are basically what we get from [elem.getBoundingClientRect()](https://developer.mozilla.org/en-US/docs/DOM/element.getBoundingClientRect). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Координаты верхнего левого внешнего угла будут в переменной `answer1` и нижнего правого -- в `answer2`: 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 abb2f0b884..8ca186e110 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 @@ -28,8 +28,8 @@ let box = elem.getBoundingClientRect(); return { - top: box.top + pageYOffset, - left: box.left + pageXOffset + top: box.top + window.pageYOffset, + left: box.left + window.pageXOffset }; } diff --git a/2-ui/1-document/11-coordinates/4-position-inside-absolute/solution.view/index.html b/2-ui/1-document/11-coordinates/4-position-inside-absolute/solution.view/index.html index 7e841397ba..b89db37900 100644 --- a/2-ui/1-document/11-coordinates/4-position-inside-absolute/solution.view/index.html +++ b/2-ui/1-document/11-coordinates/4-position-inside-absolute/solution.view/index.html @@ -26,8 +26,8 @@ let box = elem.getBoundingClientRect(); return { - top: box.top + pageYOffset, - left: box.left + pageXOffset + top: box.top + window.pageYOffset, + left: box.left + window.pageXOffset }; } diff --git a/2-ui/1-document/11-coordinates/article.md b/2-ui/1-document/11-coordinates/article.md index 24bde095e1..003fc22ffa 100644 --- a/2-ui/1-document/11-coordinates/article.md +++ b/2-ui/1-document/11-coordinates/article.md @@ -36,7 +36,11 @@ ```online Кликните на кнопку, чтобы увидеть её координаты относительно окна: +<<<<<<< HEAD

        +======= +

        +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` +<<<<<<< HEAD **Так как у элемента DOM может быть только одно свойство с именем `onclick`, то назначить более одного обработчика так нельзя.** +======= +In the first example, the HTML attribute is used to initialize the `button.onclick`, while in the second example -- the script, that's all the difference. + +**As there's only one `onclick` property, we can't assign more than one event handler.** +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 В примере ниже назначение через JavaScript перезапишет обработчик из атрибута: @@ -124,6 +150,7 @@ ``` +<<<<<<< HEAD Кстати, обработчиком можно назначить и уже существующую функцию: ```js @@ -135,6 +162,9 @@ elem.onclick = sayThanks; ``` Убрать обработчик можно назначением `elem.onclick = null`. +======= +To remove a handler -- assign `elem.onclick = null`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## Доступ к элементу через this @@ -148,9 +178,25 @@ elem.onclick = sayThanks; ## Частые ошибки +<<<<<<< HEAD Если вы только начинаете работать с событиями, обратите внимание на следующие моменты. **Функция должна быть присвоена как `sayThanks`, а не `sayThanks()`.** +======= +If you're starting to work with events -- please note some subtleties. + +We can set an existing function as a handler: + +```js +function sayThanks() { + alert('Thanks!'); +} + +elem.onclick = sayThanks; +``` + +But be careful: the function should be assigned as `sayThanks`, not `sayThanks()`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js // правильно @@ -160,7 +206,11 @@ button.onclick = sayThanks; button.onclick = sayThanks(); ``` +<<<<<<< HEAD Если добавить скобки, то `sayThanks()` -- это уже вызов функции, результат которого (равный `undefined`, так как функция ничего не возвращает) будет присвоен `onclick`. Так что это не будет работать. +======= +If we add parentheses, then `sayThanks()` becomes a function call. So the last line actually takes the *result* of the function execution, that is `undefined` (as the function returns nothing), and assigns it to `onclick`. That doesn't work. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ...А вот в разметке, в отличие от свойства, скобки нужны: @@ -168,22 +218,34 @@ button.onclick = sayThanks(); ``` +<<<<<<< HEAD Это различие просто объяснить. При создании обработчика браузером из атрибута, он автоматически создаёт функцию с *телом из значения атрибута*: `sayThanks()`. +======= +The difference is easy to explain. When the browser reads the attribute, it creates a handler function with body from the attribute content. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Так что разметка генерирует такое свойство: ```js button.onclick = function() { *!* +<<<<<<< HEAD sayThanks(); // содержимое атрибута +======= + sayThanks(); // <-- the attribute content goes here +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 */!* }; ``` +<<<<<<< HEAD **Используйте именно функции, а не строки.** Назначение обработчика строкой `elem.onclick = "alert(1)"` также сработает. Это сделано из соображений совместимости, но делать так не рекомендуется. **Не используйте `setAttribute` для обработчиков.** +======= +**Don't use `setAttribute` for handlers.** +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Такой вызов работать не будет: @@ -199,9 +261,15 @@ document.body.setAttribute('onclick', function() { alert(1) }); ## addEventListener +<<<<<<< HEAD Фундаментальный недостаток описанных выше способов назначения обработчика – невозможность повесить несколько обработчиков на одно событие. Например, одна часть кода хочет при клике на кнопку делать её подсвеченной, а другая – выдавать сообщение. +======= +The fundamental problem of the aforementioned ways to assign handlers is that we *can't assign multiple handlers to one event*. + +Let's say, one part of our code wants to highlight a button on click, and another one wants to show a message on the same click. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Мы хотим назначить два обработчика для этого. Но новое DOM-свойство перезапишет предыдущее: @@ -211,7 +279,11 @@ input.onclick = function() { alert(1); } input.onclick = function() { alert(2); } // заменит предыдущий обработчик ``` +<<<<<<< HEAD Разработчики стандартов достаточно давно это поняли и предложили альтернативный способ назначения обработчиков при помощи специальных методов `addEventListener` и `removeEventListener`. Они свободны от указанного недостатка. +======= +Developers of web standards understood that long ago and suggested an alternative way of managing handlers using the special methods `addEventListener` and `removeEventListener` which aren't bound by such constraint. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Синтаксис добавления обработчика: @@ -226,11 +298,18 @@ element.addEventListener(event, handler, [options]); : Ссылка на функцию-обработчик. `options` +<<<<<<< HEAD : Дополнительный объект со свойствами: - `once`: если `true`, тогда обработчик будет автоматически удалён после выполнения. - `capture`: фаза, на которой должен сработать обработчик, подробнее об этом будет рассказано в главе . Так исторически сложилось, что `options` может быть `false/true`, это то же самое, что `{capture: false/true}`. - `passive`: если `true`, то указывает, что обработчик никогда не вызовет `preventDefault()`, подробнее об этом будет рассказано в главе . +======= +: An additional optional object with properties: + - `once`: if `true`, then the listener is automatically removed after it triggers. + - `capture`: the phase where to handle the event, to be covered later in the chapter . For historical reasons, `options` can also be `false/true`, that's the same as `{capture: false/true}`. + - `passive`: if `true`, then the handler will not call `preventDefault()`, we'll explain that later in . +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Для удаления обработчика следует использовать `removeEventListener`: @@ -241,7 +320,11 @@ element.removeEventListener(event, handler, [options]); ````warn header="Удаление требует именно ту же функцию" Для удаления нужно передать именно ту функцию-обработчик которая была назначена. +<<<<<<< HEAD Вот так не сработает: +======= +This doesn't work: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js no-beautify elem.addEventListener( "click" , () => alert('Спасибо!')); @@ -249,7 +332,11 @@ elem.addEventListener( "click" , () => alert('Спасибо!')); elem.removeEventListener( "click", () => alert('Спасибо!')); ``` +<<<<<<< HEAD Обработчик не будет удалён, т.к. в `removeEventListener` передана не та же функция, а другая, с одинаковым кодом, но это не важно. +======= +The handler won't be removed, because `removeEventListener` gets another function -- with the same code, but that doesn't matter, as it's a different function object. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Вот так правильно: @@ -266,7 +353,11 @@ input.removeEventListener("click", handler); Обратим внимание -- если функцию обработчик не сохранить где-либо, мы не сможем её удалить. Нет метода, который позволяет получить из элемента обработчики событий, назначенные через `addEventListener`. ```` +<<<<<<< HEAD Метод `addEventListener` позволяет добавлять несколько обработчиков на одно событие одного элемента, например: +======= +Multiple calls to `addEventListener` allow it to add multiple handlers, like this: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```html run no-beautify @@ -290,6 +381,7 @@ input.removeEventListener("click", handler); Как видно из примера выше, можно одновременно назначать обработчики и через DOM-свойство и через `addEventListener`. Однако, во избежание путаницы, рекомендуется выбрать один способ. +<<<<<<< HEAD ````warn header="Обработчики некоторых событий можно назначать только через `addEventListener`" Существуют события, которые нельзя назначить через DOM-свойство, но можно через `addEventListener`. @@ -298,24 +390,52 @@ input.removeEventListener("click", handler); ```js document.onDOMContentLoaded = function() { alert("DOM построен"); // не будет работать +======= +````warn header="For some events, handlers only work with `addEventListener`" +There exist events that can't be assigned via a DOM-property. Only with `addEventListener`. + +For instance, the `DOMContentLoaded` event, that triggers when the document is loaded and the DOM has been built. + +```js +// will never run +document.onDOMContentLoaded = function() { + alert("DOM built"); +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 }; ``` ```js +<<<<<<< HEAD document.addEventListener("DOMContentLoaded", function() { alert("DOM построен"); // а вот так сработает }); ``` Так что `addEventListener` более универсален. Хотя заметим, что таких событий меньшинство, это скорее исключение, чем правило. +======= +// this way it works +document.addEventListener("DOMContentLoaded", function() { + alert("DOM built"); +}); +``` +So `addEventListener` is more universal. Although, such events are an exception rather than the rule. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```` ## Объект события +<<<<<<< HEAD Чтобы хорошо обработать событие, могут понадобиться детали того, что произошло. Не просто "клик" или "нажатие клавиши", а также -- какие координаты указателя мыши, какая клавиша нажата и так далее. +======= +To properly handle an event we'd want to know more about what's happened. Not just a "click" or a "keydown", but what were the pointer coordinates? Which key was pressed? And so on. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Когда происходит событие, браузер создаёт *объект события*, записывает в него детали и передаёт его в качестве аргумента функции-обработчику. +<<<<<<< HEAD Пример ниже демонстрирует получение координат мыши из объекта события: +======= +Here's an example of getting pointer coordinates from the event object: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```html run @@ -337,6 +457,7 @@ document.addEventListener("DOMContentLoaded", function() { `event.currentTarget` : Элемент, на котором сработал обработчик. Значение -- обычно такое же, как и у `this`, но если обработчик является функцией-стрелкой или при помощи `bind` привязан другой объект в качестве `this`, то мы можем получить элемент из `event.currentTarget`. +<<<<<<< HEAD `event.clientX / event.clientY` : Координаты курсора в момент клика относительно окна, для событий мыши. @@ -344,6 +465,15 @@ document.addEventListener("DOMContentLoaded", function() { ````smart header="Объект события доступен и в HTML" При назначении обработчика в HTML, тоже можно использовать объект `event`, вот так: +======= +`event.clientX` / `event.clientY` +: Window-relative coordinates of the cursor, for pointer events. + +There are more properties. Many of them depend on the event type: keyboard events have one set of properties, pointer events - another one, we'll study them later when as we move on to the details of different events. + +````smart header="The event object is also available in HTML handlers" +If we assign a handler in HTML, we can also use the `event` object, like this: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```html autorun height=60 @@ -364,17 +494,25 @@ document.addEventListener("DOMContentLoaded", function() { ``` +<<<<<<< HEAD Как видим, если `addEventListener` получает объект в качестве обработчика, он вызывает `object.handleEvent(event)`, когда происходит событие. Мы также можем использовать класс для этого: +======= +As we can see, when `addEventListener` receives an object as the handler, it calls `obj.handleEvent(event)` in case of an event. + +We could also use objects of a custom class, like this: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```html run @@ -396,6 +534,7 @@ document.addEventListener("DOMContentLoaded", function() { *!* let menu = new Menu(); + elem.addEventListener('mousedown', menu); elem.addEventListener('mouseup', menu); */!* @@ -446,7 +585,11 @@ HTML-атрибуты используются редко потому, что J DOM-свойства вполне можно использовать, но мы не можем назначить больше одного обработчика на один тип события. Во многих случаях с этим ограничением можно мириться. +<<<<<<< HEAD Последний способ самый гибкий, однако нужно писать больше всего кода. Есть несколько типов событий, которые работают только через него, например, `DOMContentLoaded`. Также `addEventListener` поддерживает объекты в качестве обработчиков событий. В этом случае вызывается метод объекта `handleEvent`. +======= +The last way is the most flexible, but it is also the longest to write. There are few events that only work with it, for instance `transitionend` and `DOMContentLoaded` (to be covered). Also `addEventListener` supports objects as event handlers. In that case the method `handleEvent` is called in case of the event. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Не важно, как вы назначаете обработчик -- он получает объект события первым аргументом. Этот объект содержит подробности о том, что произошло. 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 f4328b8409..9d9d89a88a 100644 --- a/2-ui/2-events/02-bubbling-and-capturing/article.md +++ b/2-ui/2-events/02-bubbling-and-capturing/article.md @@ -68,8 +68,13 @@ При этом внутри обработчика `form.onclick`: +<<<<<<< HEAD - `this` (=`event.currentTarget`) всегда будет элемент `
        `, так как обработчик сработал на ней. - `event.target` будет содержать ссылку на конкретный элемент внутри формы, на котором произошёл клик. +======= +- `this` (=`event.currentTarget`) is the `` element, because the handler runs on it. +- `event.target` is the actual element inside the form that was clicked. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Попробуйте сами: @@ -101,8 +106,13 @@ Для того, чтобы полностью остановить обработку, существует метод `event.stopImmediatePropagation()`. Он не только предотвращает всплытие, но и останавливает обработку событий на текущем элементе. ``` +<<<<<<< HEAD ```warn header="Не прекращайте всплытие без необходимости!" Всплытие -- это удобно. Не прекращайте его без явной нужды, очевидной и архитектурно прозрачной. +======= +```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 out. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Зачастую прекращение всплытия через `event.stopPropagation()` имеет свои подводные камни, которые со временем могут стать проблемами. @@ -120,27 +130,46 @@ Существует ещё одна фаза из жизненного цикла события -- "погружение" (иногда её называют "перехват"). Она очень редко используется в реальном коде, однако тоже может быть полезной. +<<<<<<< HEAD Стандарт [DOM Events](https://www.w3.org/TR/DOM-Level-3-Events/) описывает 3 фазы прохода события: +======= +The standard [DOM Events](https://www.w3.org/TR/DOM-Level-3-Events/) describes 3 phases of event propagation: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 1. Фаза погружения (capturing phase) -- событие сначала идёт сверху вниз. 2. Фаза цели (target phase) -- событие достигло целевого(исходного) элемента. 3. Фаза всплытия (bubbling stage) -- событие начинает всплывать. +<<<<<<< HEAD Картинка из спецификации демонстрирует, как это работает при клике по ячейке `
        +<<<<<<< HEAD +======= + + + +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ...ещё 2 строки такого же вида......ещё 2 строки такого же вида... @@ -99,11 +113,19 @@ table.onclick = function(event) { }; ``` +<<<<<<< HEAD Разберём пример: 1. Метод `elem.closest(selector)` возвращает ближайшего предка, соответствующего селектору. В данном случае нам нужен `
        `, расположенной внутри таблицы: +======= +Here's the picture, taken from the specification, of the capturing `(1)`, target `(2)` and bubbling `(3)` phases for a click event on a `` inside a table: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ![](eventflow.svg) То есть при клике на `` событие путешествует по цепочке родителей сначала вниз к элементу (погружается), затем оно достигает целевой элемент (фаза цели), а потом идёт наверх (всплытие), вызывая по пути обработчики. +<<<<<<< HEAD **Ранее мы говорили только о всплытии, потому что другие стадии, как правило, не используются и проходят незаметно для нас.** Обработчики, добавленные через `on`-свойство или через HTML-атрибуты, или через `addEventListener(event, handler)` с двумя аргументами, ничего не знают о фазе погружения, а работают только на 2-ой и 3-ей фазах. +======= +Until now, we only talked about bubbling, because the capturing phase is rarely used. + +In fact, the capturing phase was invisible for us, because handlers added using `on`-property or using HTML attributes or using two-argument `addEventListener(event, handler)` don't know anything about capturing, they only run on the 2nd and 3rd phases. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Чтобы поймать событие на стадии погружения, нужно использовать третий аргумент `capture` вот так: ```js elem.addEventListener(..., {capture: true}) +<<<<<<< HEAD // или просто "true", как сокращение для {capture: true} +======= + +// or, just "true" is an alias to {capture: true} +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 elem.addEventListener(..., true) ``` @@ -179,9 +208,16 @@ elem.addEventListener(..., true) Если вы кликните по `

        `, то последовательность следующая: +<<<<<<< HEAD 1. `HTML` -> `BODY` -> `FORM` -> `DIV` (фаза погружения, первый обработчик) 2. `P` (фаза цели, срабатывают обработчики, установленные и на погружение и на всплытие, так что выведется два раза) 3. `DIV` -> `FORM` -> `BODY` -> `HTML` (фаза всплытия, второй обработчик) +======= +1. `HTML` -> `BODY` -> `FORM` -> `DIV -> P` (capturing phase, the first listener): +2. `P` -> `DIV` -> `FORM` -> `BODY` -> `HTML` (bubbling phase, the second listener). + +Please note, the `P` shows up twice, because we've set two listeners: capturing and bubbling. The target triggers at the end of the first and at the beginning of the second phase. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Существует свойство `event.eventPhase`, содержащее номер фазы, на которой событие было поймано. Но оно используется редко, мы обычно и так знаем об этом в обработчике. @@ -189,8 +225,13 @@ elem.addEventListener(..., true) Если мы добавили обработчик вот так `addEventListener(..., true)`, то мы должны передать то же значение аргумента `capture` в `removeEventListener(..., true)`, когда снимаем обработчик. ``` +<<<<<<< HEAD ````smart header="На каждой фазе разные обработчики на одном элементе срабатывают в порядке назначения" Если у нас несколько обработчиков одного события, назначенных `addEventListener` на один элемент, в рамках одной фазы, то их порядок срабатывания - тот же, в котором они установлены: +======= +````smart header="Listeners on the same element and same phase run in their set order" +If we have multiple event handlers on the same phase, assigned to the same element with `addEventListener`, they run in the same order as they are created: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js elem.addEventListener("click", e => alert(1)); // всегда сработает перед следующим @@ -198,7 +239,16 @@ elem.addEventListener("click", e => alert(2)); ``` ```` +<<<<<<< HEAD ## Итого +======= +```smart header="The `event.stopPropagation()` during the capturing also prevents the bubbling" +The `event.stopPropagation()` method and its sibling `event.stopImmediatePropagation()` can also be called on the capturing phase. Then not only the futher capturing is stopped, but the bubbling as well. + +In other words, normally the event goes first down ("capturing") and then up ("bubbling"). But if `event.stopPropagation()` is called during the capturing phase, then the event travel stops, no bubbling will occur. +``` + +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 При наступлении события - самый глубоко вложенный элемент, на котором оно произошло, помечается как "целевой" (`event.target`). @@ -206,7 +256,13 @@ elem.addEventListener("click", e => alert(2)); - Далее обработчики вызываются на целевом элементе. - Далее событие двигается от `event.target` вверх к корню документа, по пути вызывая обработчики, поставленные через `on` и `addEventListener` без третьего аргумента или с третьим аргументом равным `false`. +<<<<<<< HEAD Каждый обработчик имеет доступ к свойствам события `event`: +======= +- Then the event moves down from the document root to `event.target`, calling handlers assigned with `addEventListener(..., true)` on the way (`true` is a shorthand for `{capture: true}`). +- Then handlers are called on the target element itself. +- Then the event bubbles up from `event.target` to the root, calling handlers assigned using `on`, HTML attributes and `addEventListener` without the 3rd argument or with the 3rd argument `false/{capture:false}`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 - `event.target` -- самый глубокий элемент, на котором произошло событие. - `event.currentTarget` (=`this`) -- элемент, на котором в данный момент сработал обработчик (тот, на котором "висит" конкретный обработчик) @@ -216,8 +272,18 @@ elem.addEventListener("click", e => alert(2)); В современной разработке стадия погружения используется очень редко, обычно события обрабатываются во время всплытия. И в этом есть логика. +<<<<<<< HEAD В реальном мире, когда происходит чрезвычайная ситуация, местные службы реагируют первыми. Они знают лучше всех местность, в которой это произошло, и другие детали. Вышестоящие инстанции подключаются уже после этого и при необходимости. +======= +The capturing phase is used very rarely, usually we handle events on bubbling. And there's a logical explanation for that. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Тоже самое справедливо для обработчиков событий. Код, который "навесил" обработчик на конкретный элемент, знает максимум деталей об элементе и его предназначении. Например, обработчик на определённом `

        ` скорее всего подходит только для этого конкретного ``, он знает все о нём, поэтому он должен отработать первым. Далее имеет смысл передать обработку события родителю -- он тоже понимает, что происходит, но уже менее детально, далее – выше, и так далее, до самого объекта `document`, обработчик на котором реализовывает самую общую функциональность уровня документа. +<<<<<<< HEAD Всплытие и погружение являются основой для "делегирования событий" -- очень мощного приёма обработки событий. Его мы изучим в следующей главе. +======= +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 `` may be suited for that exactly ``, 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 one. + +Bubbling and capturing lay the foundation for "event delegation" -- an extremely powerful event handling pattern that we study in the next chapter. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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 692cffa8bd..b95168e09c 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 @@ -22,10 +22,17 @@ importance: 5 Детали оформления: +<<<<<<< HEAD - Отступ от подсказки до элемента с `data-tooltip` должен быть `5px` по высоте. - Подсказка должна быть, по возможности, посередине элемента. - Подсказка не должна вылезать за границы экрана, в том числе если страница частично прокручена, если нельзя показать сверху -- показывать снизу элемента. - Текст подсказки брать из значения атрибута `data-tooltip`. Это может быть произвольный HTML. +======= +- The distance between the element and the tooltip should be `5px`. +- The tooltip should be centered relative to the element, if possible. +- 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 content is given in the `data-tooltip` attribute. It can be arbitrary HTML. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Для решения вам понадобятся два события: - `mouseover` срабатывает, когда указатель мыши заходит на элемент. diff --git a/2-ui/2-events/03-event-delegation/article.md b/2-ui/2-events/03-event-delegation/article.md index ef3fe512d4..55b8c959a4 100644 --- a/2-ui/2-events/03-event-delegation/article.md +++ b/2-ui/2-events/03-event-delegation/article.md @@ -1,11 +1,19 @@ # Делегирование событий +<<<<<<< HEAD Всплытие и перехват событий позволяет реализовать один из самых важных приёмов разработки -- *делегирование*. +======= +Capturing and bubbling allow us to implement one of the most powerful event handling patterns called *event delegation*. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Идея в том, что если у нас есть много элементов, события на которых нужно обрабатывать похожим образом, то вместо того, чтобы назначать обработчик каждому, мы ставим один обработчик на их общего предка. +<<<<<<< HEAD Из него можно получить целевой элемент `event.target`, понять на каком именно потомке произошло событие и обработать его. +======= +In the handler we get `event.target` to see where the event actually happened and handle it. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Рассмотрим пример -- [диаграмму Ба-Гуа](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). Это таблица, отражающая древнюю китайскую философию. @@ -21,9 +29,15 @@ Квадрат Bagua: Направление, Элемент, Цвет, Значение
        ...Северо-Запад... ... ...Northwest
        Metal
        Silver
        Elders
        ......
        `, находящийся выше по дереву от исходного элемента. 2. Если `event.target` не содержится внутри элемента ``, то вызов вернёт `null`, и ничего не произойдёт. 3. Если таблицы вложенные, `event.target` может содержать элемент ``, находящийся вне текущей таблицы. В таких случаях мы должны проверить, действительно ли это `` *нашей таблицы*. 4. И если это так, то подсвечиваем его. +======= +Explanations: +1. The method `elem.closest(selector)` returns the nearest ancestor that matches the selector. In our case we look for `` on the way up from the source element. +2. If `event.target` is not inside any ``, then the call returns immediately, as there's nothing to do. +3. In case of nested tables, `event.target` may be a ``, but lying outside of the current table. So we check if that's actually *our table's* ``. +4. And, if it's so, then highlight it. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 В итоге мы получили короткий код подсветки, быстрый и эффективный, которому совершенно не важно, сколько всего в таблице ``. @@ -162,7 +184,11 @@ table.onclick = function(event) { Обратите внимание, что метод `this.onClick` в строке, отмеченной звёздочкой `(*)`, привязывается к контексту текущего объекта `this`. Это важно, т.к. иначе `this` внутри него будет ссылаться на DOM-элемент (`elem`), а не на объект `Menu`, и `this[action]` будет не тем, что нам нужно. +<<<<<<< HEAD Так что же даёт нам здесь делегирование? +======= +So, what advantages does delegation give us here? +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```compare + Не нужно писать код, чтобы присвоить обработчик каждой кнопке. Достаточно просто создать один метод и поместить его в разметку. @@ -241,13 +267,21 @@ table.onclick = function(event) { Мы можем комбинировать несколько вариантов поведения на одном элементе. +<<<<<<< HEAD Шаблон "поведение" может служить альтернативой для фрагментов JS-кода в вёрстке. +======= +The "behavior" pattern can be an alternative to mini-fragments of JavaScript. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## Итого Делегирование событий -- это здорово! Пожалуй, это один из самых полезных приёмов для работы с DOM. +<<<<<<< HEAD Он часто используется, если есть много элементов, обработка которых очень схожа, но не только для этого. +======= +It's often used to add the same handling for many similar elements, but not only for that. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Алгоритм: @@ -258,14 +292,25 @@ table.onclick = function(event) { Зачем использовать: ```compare +<<<<<<< HEAD + Упрощает процесс инициализации и экономит память: не нужно вешать много обработчиков. + Меньше кода: при добавлении и удалении элементов не нужно ставить или снимать обработчики. + Удобство изменений DOM: можно массово добавлять или удалять элементы путём изменения `innerHTML` и ему подобных. +======= ++ 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 the like. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` Конечно, у делегирования событий есть свои ограничения: ```compare +<<<<<<< HEAD - Во-первых, событие должно всплывать. Некоторые события этого не делают. Также, низкоуровневые обработчики не должны вызывать `event.stopPropagation()`. - Во-вторых, делегирование создаёт дополнительную нагрузку на браузер, ведь обработчик запускается, когда событие происходит в любом месте контейнера, не обязательно на элементах, которые нам интересны. Но обычно эта нагрузка настолько пустяковая, что её даже не стоит принимать во внимание. +======= +- 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 whether they interest us or not. But usually the load is negligible, so we don't take it into account. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` 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 606ae793b4..b8c945c9e1 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 @@ -14,7 +14,11 @@ importance: 3 } +<<<<<<< HEAD браузер откроет w3.org +======= +the browser will go to w3.org +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` Браузер переходит по указанной ссылке, но нам этого не нужно. 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 083cfac0d1..c124645e40 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,11 @@
        #contents

        +<<<<<<< HEAD Как насчёт того, чтобы прочитать Википедию или посетить W3.org и узнать о современных стандартах? +======= + How about to read Wikipedia or visit W3.org and learn about modern standards? +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9

        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 b8e6509f6d..94764a6de6 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,11 @@
        #contents

        +<<<<<<< HEAD Как насчёт того, чтобы прочитать Википедию или посетить W3.org и узнать о современных стандартах? +======= + How about to read Wikipedia or visit W3.org and learn about modern standards? +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9

        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 4670b17ba4..faa44990b3 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 @@ -12,5 +12,10 @@ importance: 5 Детали: +<<<<<<< HEAD - Содержимое `#contents` может быть загружено динамически и присвоено при помощи `innerHTML`. Так что найти все ссылки и поставить на них обработчики нельзя. Используйте делегирование. - Содержимое может иметь вложенные теги, *в том числе внутри ссылок*, например, `...`. +======= +- 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 event delegation. +- The content may have nested tags. Inside links too, like `...`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/2-ui/2-events/04-default-browser-action/article.md b/2-ui/2-events/04-default-browser-action/article.md index f58a6561e9..8a2df3ef17 100644 --- a/2-ui/2-events/04-default-browser-action/article.md +++ b/2-ui/2-events/04-default-browser-action/article.md @@ -1,23 +1,42 @@ # Действия браузера по умолчанию +<<<<<<< HEAD Многие события автоматически влекут за собой действие браузера. +======= +Many events automatically lead to certain actions performed by the browser. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Например: +<<<<<<< HEAD - Клик по ссылке инициирует переход на новый URL. - Нажатие на кнопку "отправить" в форме – отсылку её на сервер. - Зажатие кнопки мыши над текстом и её движение в таком состоянии – инициирует его выделение. Если мы обрабатываем событие в JavaScript, то зачастую такое действие браузера нам не нужно. К счастью, его можно отменить. +======= +- A click on a link - initiates navigation to its URL. +- A click on a form submit button - initiates its submission to the server. +- Pressing a mouse button over a text and moving it - selects the text. + +If we handle an event in JavaScript, we may not want the corresponding browser action to happen, and want to implement another behavior instead. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## Отмена действия браузера Есть два способа отменить действие браузера: +<<<<<<< HEAD - Основной способ – это воспользоваться объектом `event`. Для отмены действия браузера существует стандартный метод `event.preventDefault()`. - Если же обработчик назначен через `on<событие>` (не через `addEventListener`), то также можно вернуть `false` из обработчика. В следующем примере при клике по ссылке переход не произойдёт: +======= +- The main way is to use the `event` object. There's a method `event.preventDefault()`. +- If the handler is assigned using `on` (not by `addEventListener`), then returning `false` also works the same. + +In this HTML, a click on a link doesn't lead to navigation; the browser doesn't do anything: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```html autorun height=60 no-beautify Нажми здесь @@ -25,12 +44,23 @@ здесь ``` +<<<<<<< HEAD ```warn header="Возврат `false` из обработчика - это исключение" Обычно значение, которое возвращает обработчик события, игнорируется. Единственное исключение – это `return false` из обработчика, назначенного через `on<событие>`. В других случаях `return` не нужен, он никак не обрабатывается. +======= +In the next example we'll use this technique to create a JavaScript-powered menu. + +```warn header="Returning `false` from a handler is an exception" +The value returned by an event handler is usually ignored. + +The only exception is `return false` from a handler assigned using `on`. + +In all other cases, `return` value is ignored. In particular, there's no sense in returning `true`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` ### Пример: меню @@ -49,7 +79,11 @@ [iframe height=70 src="menu" link edit] +<<<<<<< HEAD В HTML-разметке все элементы меню являются не кнопками, а ссылками, то есть тегами ``. В этом подходе есть некоторые преимущества, например: +======= +Menu items are implemented as HTML-links ``, not buttons ` ``` +<<<<<<< HEAD Обратите внимание: событие должно содержать флаг `cancelable: true`. Иначе, вызов `event.preventDefault()` будет проигнорирован. +======= +Please note: the event must have the flag `cancelable: true`, otherwise the call `event.preventDefault()` is ignored. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## Вложенные события обрабатываются синхронно +<<<<<<< HEAD Обычно события обрабатываются асинхронно. То есть, если браузер обрабатывает `onclick` и в процессе этого произойдёт новое событие, то оно ждёт, пока закончится обработка `onclick`. Исключением является ситуация, когда событие инициировано из обработчика другого события. @@ -218,6 +242,16 @@ alert(event.clientX); // undefined, неизвестное свойство пр Тогда управление сначала переходит в обработчик вложенного события и уже после этого возвращается назад. В примере ниже событие `menu-open` обрабатывается синхронно во время обработки `onclick`: +======= +Usually events are processed in a queue. That is: if the browser is processing `onclick` and a new event occurs, e.g. mouse moved, then its handling is queued up, corresponding `mousemove` handlers will be called after `onclick` processing is finished. + +The notable exception is when one event is initiated from within another one, e.g. using `dispatchEvent`. Such events are processed immediately: the new event handlers are called, and then the current event handling is resumed. + +For instance, in the code below the `menu-open` event is triggered during the `onclick`. + +It's processed immediately, without waiting for `onclick` handler to end: + +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```html run autorun @@ -226,7 +260,10 @@ alert(event.clientX); // undefined, неизвестное свойство пр menu.onclick = function() { alert(1); +<<<<<<< HEAD // alert("вложенное событие") +======= +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 menu.dispatchEvent(new CustomEvent("menu-open", { bubbles: true })); @@ -234,17 +271,32 @@ alert(event.clientX); // undefined, неизвестное свойство пр alert(2); }; +<<<<<<< HEAD document.addEventListener('menu-open', () => alert('вложенное событие')) +======= + // triggers between 1 and 2 + document.addEventListener('menu-open', () => alert('nested')); +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 -``` +``` Порядок вывода: 1 -> вложенное событие -> 2. +<<<<<<< HEAD Обратите внимание, что вложенное событие `menu-open` успевает всплыть и запустить обработчик на `document`. Обработка вложенного события полностью завершается до того, как управление возвращается во внешний код (`onclick`). Это справедливо не только для `dispatchEvent`, но и для других ситуаций. JavaScript в обработчике события может вызвать другие методы, которые приведут к другим событиям -- они тоже обрабатываются синхронно. Если нам это не подходит, то мы можем либо поместить `dispatchEvent` (или любой другой код, инициирующий события) в конец обработчика `onclick`, либо, если это неудобно, можно обернуть генерацию события в `setTimeout` с нулевой задержкой: +======= +Please note that the nested event `menu-open` is caught on the `document`. The propagation and handling of the nested event is finished before the processing gets back to the outer code (`onclick`). + +That's not only about `dispatchEvent`, there are other cases. If an event handler calls methods that trigger other events -- they are processed synchronously too, in a nested fashion. + +Let's say we don't like it. We'd want `onclick` to be fully processed first, independently from `menu-open` or any other nested events. + +Then we can either put the `dispatchEvent` (or another event-triggering call) at the end of `onclick` or, maybe better, wrap it in the zero-delay `setTimeout`: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```html run @@ -253,7 +305,6 @@ alert(event.clientX); // undefined, неизвестное свойство пр menu.onclick = function() { alert(1); - // alert(2) setTimeout(() => menu.dispatchEvent(new CustomEvent("menu-open", { bubbles: true }))); @@ -263,9 +314,13 @@ alert(event.clientX); // undefined, неизвестное свойство пр document.addEventListener('menu-open', () => alert('вложенное событие')) -``` +``` +<<<<<<< HEAD Теперь `dispatchEvent` запускается асинхронно после исполнения текущего кода, включая `menu.onclick`, поэтому обработчики полностью независимы. +======= +Now `dispatchEvent` runs asynchronously after the current code execution is finished, including `menu.onclick`, so event handlers are totally separate. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Новый порядок вывода: 1 -> 2 -> вложенное событие. @@ -281,9 +336,15 @@ alert(event.clientX); // undefined, неизвестное свойство пр Для пользовательских событий стоит применять конструктор `CustomEvent`. У него есть дополнительная опция `detail`, с помощью которой можно передавать информацию в объекте события. После чего все обработчики смогут получить к ней доступ через `event.detail`. +<<<<<<< HEAD Несмотря на техническую возможность генерировать встроенные браузерные события типа `click` или `keydown`, пользоваться ей стоит с большой осторожностью. Весьма часто, когда разработчик хочет сгенерировать встроенное событие – это вызвано "кривой" архитектурой кода. +======= +Despite the technical possibility of generating browser events like `click` or `keydown`, we should use them with great care. + +We shouldn't generate browser events as it's a hacky way to run handlers. That's bad architecture most of the time. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Как правило, генерация встроенных событий полезна в следующих случаях: diff --git a/2-ui/3-event-details/1-mouse-events-basics/article.md b/2-ui/3-event-details/1-mouse-events-basics/article.md index 1d330f1184..8d0ac5f6e5 100644 --- a/2-ui/3-event-details/1-mouse-events-basics/article.md +++ b/2-ui/3-event-details/1-mouse-events-basics/article.md @@ -1,3 +1,8 @@ +<<<<<<< HEAD +======= + +# Mouse events +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 # Основы событий мыши @@ -5,9 +10,13 @@ Сразу заметим: эти события бывают не только из-за мыши, но и эмулируются на других устройствах, в частности, на мобильных, для совместимости. +<<<<<<< HEAD ## Типы событий мыши Мы уже видели некоторые из этих событий: +======= +We've already seen some of these events: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 `mousedown/mouseup` : Кнопка мыши нажата/отпущена над элементом. @@ -16,6 +25,7 @@ : Курсор мыши появляется над элементом и уходит с него. `mousemove` +<<<<<<< HEAD : Каждое движение мыши над элементом генерирует это событие. `click` @@ -32,6 +42,26 @@ ## Порядок событий Как вы можете видеть из приведённого выше списка, действие пользователя может вызвать несколько событий. +======= +: Every mouse move over an element triggers that event. + +`click` +: Triggers after `mousedown` and then `mouseup` over the same element if the left mouse button was used. + +`dblclick` +: Triggers after two clicks on the same element within a short timeframe. Rarely used nowadays. + +`contextmenu` +: Triggers when the right mouse button is pressed. There are other ways to open a context menu, e.g. using a special keyboard key, it triggers in that case also, so it's not exactly the mouse event. + +...There are several other events too, we'll cover them later. + +## Events order + +As you can see from the list above, a user action may trigger multiple events. + +For instance, a left-button click first triggers `mousedown`, when the button is pressed, then `mouseup` and `click` when it's released. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Например, клик мышью вначале вызывает `mousedown`, когда кнопка нажата, затем `mouseup` и `click`, когда она отпущена. @@ -40,13 +70,20 @@ ```online Кликните на кнопку ниже, и вы увидите события. Также попробуйте двойной клик. +<<<<<<< HEAD В окне теста ниже все события мыши записываются, и если задержка между ними более 1 секунды, то они разделяются горизонтальной чертой. Кроме того, мы можем видеть свойство `button`, которое позволяет нам определять кнопку мыши; это объясняется ниже. +======= +On the teststand below, all mouse events are logged, and if there is more than a 1 second delay between them, they are separated by a horizontal rule. + +Also, we can see the `button` property that allows us to detect the mouse button; it's explained below. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` +<<<<<<< HEAD ## Кнопки мыши События, связанные с кликом, всегда имеют свойство `button`, которое позволяет получить конкретную кнопку мыши. @@ -66,6 +103,39 @@ | Кнопка X2 (вперёд) | 4 | Большинство мышек имеют только левую и правую кнопку, поэтому возможные значения это 0 или 2. Сенсорные устройства также генерируют аналогичные события, когда кто-то нажимает на них. +======= +## Mouse button + +Click-related events always have the `button` property, which allows to get the exact mouse button. + +We usually don't use it for `click` and `contextmenu` events, because the former happens only on left-click, and the latter -- only on right-click. + +On the other hand, `mousedown` and `mouseup` handlers may need `event.button`, because these events trigger on any button, so `button` allows to distinguish between "right-mousedown" and "left-mousedown". + +The possible values of `event.button` are: + +| Button state | `event.button` | +|--------------|----------------| +| Left button (primary) | 0 | +| Middle button (auxiliary) | 1 | +| Right button (secondary) | 2 | +| X1 button (back) | 3 | +| X2 button (forward) | 4 | + +Most mouse devices only have the left and right buttons, so possible values are `0` or `2`. Touch devices also generate similar events when one taps on them. + +Also there's `event.buttons` property that has all currently pressed buttons as an integer, one bit per button. In practice this property is very rarely used, you can find details at [MDN](mdn:/api/MouseEvent/buttons) if you ever need it. + +```warn header="The outdated `event.which`" +Old code may use `event.which` property that's an old non-standard way of getting a button, with possible values: + +- `event.which == 1` – left button, +- `event.which == 2` – middle button, +- `event.which == 3` – right button. + +As of now, `event.which` is deprecated, we shouldn't use it. +``` +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Также есть свойство `event.buttons`, в котором все нажатые в данный момент кнопки представлены в виде целого числа, по одному биту на кнопку. На практике это свойство используется очень редко, вы можете найти подробную информацию по адресу [MDN](mdn:/api/MouseEvent/buttons), если вам это когда-нибудь понадобится. @@ -121,24 +191,40 @@ Даже если мы и хотели бы заставить людей на Mac использовать именно `key:Ctrl`+клик, это довольно сложно. Проблема в том, что левый клик в сочетании с `key:Ctrl` интерпретируется как *правый клик* на MacOS и генерирует событие `contextmenu`, а не `click` как на Windows/Linux. +<<<<<<< HEAD Поэтому, если мы хотим, чтобы пользователям всех операционных систем было удобно, то вместе с `ctrlKey` нам нужно проверять `metaKey`. +======= +So if we want users of all operating systems to feel comfortable, then together with `ctrlKey` we should check `metaKey`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Для JS-кода это означает, что мы должны проверить `if (event.ctrlKey || event.metaKey)`. ``` +<<<<<<< HEAD ```warn header="Не забывайте про мобильные устройства" Комбинации клавиш хороши в качестве дополнения к рабочему процессу. Так что, если посетитель использует клавиатуру – они работают. Но если на их устройстве его нет – тогда должен быть способ жить без клавиш-модификаторов. +======= +```warn header="There are also mobile devices" +Keyboard combinations are good as an addition to the workflow. So that if the visitor uses a keyboard -- they work. + +But if their device doesn't have it -- then there should be a way to live without modifier keys. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` ## Координаты: clientX/Y, pageX/Y +<<<<<<< HEAD Все события мыши имеют координаты двух видов: +======= +All mouse events provide coordinates in two flavours: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 1. Относительно окна: `clientX` и `clientY`. 2. Относительно документа: `pageX` и `pageY`. +<<<<<<< HEAD Мы уже рассмотрели разницу между ними в главе . Если в кратце, то относительные координаты документа `pageX/Y` отсчитываются от левого верхнего угла документа и не меняются при прокрутке страницы, в то время как `clientX/Y` отсчитываются от левого верхнего угла текущего окна. Когда страница прокручивается, они меняются. @@ -146,6 +232,15 @@ Например, если у нас есть окно размером 500x500, и курсор мыши находится в левом верхнем углу, то значения `clientX` и `clientY` равны `0`, независимо от того, как прокручивается страница. А если мышь находится в центре окна, то значения `clientX` и `clientY` равны `250` независимо от того, в каком месте документа она находится и до какого места документ прокручен. В этом они похожи на `position:fixed`. +======= +We already covered the difference between them in the chapter . + +In short, document-relative coordinates `pageX/Y` are counted from the left-upper corner of the document, and do not change when the page is scrolled, while `clientX/Y` are counted from the current window left-upper corner. When the page is scrolled, they change. + +For instance, if we have a window of the size 500x500, and the mouse is in the left-upper corner, then `clientX` and `clientY` are `0`, no matter how the page is scrolled. + +And if the mouse is in the center, then `clientX` and `clientY` are `250`, no matter what place in the document it is. They are similar to `position:fixed` in that aspect. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ````online Наведите курсор мыши на поле ввода, чтобы увидеть `clientX/clientY` (пример находится в `iframe`, поэтому координаты определяются относительно этого `iframe`): @@ -155,6 +250,7 @@ ``` ```` +<<<<<<< HEAD Координаты относительно документа `pageX`, `pageY` отсчитываются не от окна, а от левого верхнего угла документа. Подробнее о координатах вы можете узнать в главе . ## Отключаем выделение @@ -162,6 +258,13 @@ Двойной клик мыши имеет побочный эффект, который может быть неудобен в некоторых интерфейсах: он выделяет текст. Например, двойной клик на текст ниже выделяет его в дополнение к нашему обработчику: +======= +## Preventing selection on mousedown + +Double mouse click has a side effect that may be disturbing in some interfaces: it selects text. + +For instance, double-clicking on the text below selects it in addition to our handler: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```html autorun height=50 Сделайте двойной клик на мне @@ -204,9 +307,15 @@ События мыши имеют следующие свойства: +<<<<<<< HEAD - Кнопка: `button`. - Клавиши-модификаторы (`true` если нажаты): `altKey`, `ctrlKey`, `shiftKey` и `metaKey` (Mac). - Если вы планируете обработать `key:Ctrl`, то не забудьте, что пользователи Mac обычно используют `key:Cmd`, поэтому лучше проверить `if (e.metaKey || e.ctrlKey)`. +======= +- Button: `button`. +- Modifier keys (`true` if pressed): `altKey`, `ctrlKey`, `shiftKey` and `metaKey` (Mac). + - If you want to handle `key:Ctrl`, then don't forget Mac users, they usually use `key:Cmd`, so it's better to check `if (e.metaKey || e.ctrlKey)`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 - Координаты относительно окна: `clientX/clientY`. - Координаты относительно документа: `pageX/pageY`. diff --git a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/1-behavior-nested-tooltip/solution.view/index.html b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/1-behavior-nested-tooltip/solution.view/index.html index 557174a92e..998c751212 100644 --- a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/1-behavior-nested-tooltip/solution.view/index.html +++ b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/1-behavior-nested-tooltip/solution.view/index.html @@ -54,7 +54,11 @@

        Жили-были на свете три поросенка. Три брата. Все одинакового роста, кругленькие, розовые, с одинаковыми веселыми хвостиками.

        +<<<<<<< HEAD

        Даже имена у них были похожи. Звали поросят: Ниф-Ниф, Нуф-Нуф и Наф-Наф. Все лето они кувыркались в зеленой траве, грелись на солнышке, нежились в лужах.

        +======= +

        The three little pigs grew so big that their mother said to them, "You are too big to live here any longer. You must go and build houses for yourselves. But take care that the wolf does not catch you."

        +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9

        Но вот наступила осень. Солнце уже не так сильно припекало, серые облака тянулись над пожелтевшим лесом.

        diff --git a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/1-behavior-nested-tooltip/source.view/index.html b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/1-behavior-nested-tooltip/source.view/index.html index f1221ed9cc..be3970799c 100644 --- a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/1-behavior-nested-tooltip/source.view/index.html +++ b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/1-behavior-nested-tooltip/source.view/index.html @@ -54,7 +54,11 @@

        Жили-были на свете три поросенка. Три брата. Все одинакового роста, кругленькие, розовые, с одинаковыми веселыми хвостиками.

        +<<<<<<< HEAD

        Даже имена у них были похожи. Звали поросят: Ниф-Ниф, Нуф-Нуф и Наф-Наф. Все лето они кувыркались в зеленой траве, грелись на солнышке, нежились в лужах.

        +======= +

        The three little pigs grew so big that their mother said to them, "You are too big to live here any longer. You must go and build houses for yourselves. But take care that the wolf does not catch you."

        +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9

        Но вот наступила осень. Солнце уже не так сильно припекало, серые облака тянулись над пожелтевшим лесом.

        diff --git a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/2-hoverintent/solution.md b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/2-hoverintent/solution.md index 1b9e770578..308cf57d8a 100644 --- a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/2-hoverintent/solution.md +++ b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/2-hoverintent/solution.md @@ -11,7 +11,11 @@ К сожалению, в JavaScript нет возможности получать текущие координаты мыши. Не существует функции типа `получитьТекущиеКоординатыМыши()`. +<<<<<<< HEAD Единственный путь - это слушать события мыши, например `mousemove`, и координаты брать из объекта события. +======= +The only way to get coordinates is to listen for mouse events, like `mousemove`, and take coordinates from the event object. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Так что поставим обработчик на `mousemove`, чтобы отслеживать координаты и запоминать их. И будем сравнивать результаты каждые `100ms`. diff --git a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/2-hoverintent/solution.view/index.html b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/2-hoverintent/solution.view/index.html index d3a5342317..033d9c6b16 100644 --- a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/2-hoverintent/solution.view/index.html +++ b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/2-hoverintent/solution.view/index.html @@ -20,6 +20,7 @@ diff --git a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/2-hoverintent/solution.view/test.js b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/2-hoverintent/solution.view/test.js index 7c7f6d23df..f5d4aaffb3 100644 --- a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/2-hoverintent/solution.view/test.js +++ b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/2-hoverintent/solution.view/test.js @@ -49,18 +49,18 @@ describe("hoverIntent", function() { } }) - it("mouseover -> immediately no tooltip", function() { + it("mouseover -> when the pointer just arrived, no tooltip", function() { mouse('mouseover', 10, 10); assert.isFalse(isOver); }); - it("mouseover -> pause shows tooltip", function() { + it("mouseover -> after a delay, the tooltip shows up", function() { mouse('mouseover', 10, 10); this.clock.tick(100); assert.isTrue(isOver); }); - it("mouseover -> fast mouseout no tooltip", function() { + it("mouseover -> followed by fast mouseout leads doesn't show tooltip", function() { mouse('mouseover', 10, 10); setTimeout( () => mouse('mouseout', 300, 300, { relatedTarget: document.body}), diff --git a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/2-hoverintent/source.view/hoverIntent.js b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/2-hoverintent/source.view/hoverIntent.js index 49de775b36..4b3abab0f7 100644 --- a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/2-hoverintent/source.view/hoverIntent.js +++ b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/2-hoverintent/source.view/hoverIntent.js @@ -44,7 +44,12 @@ class HoverIntent { destroy() { +<<<<<<< HEAD /* ваш код для отключения функциональности и снятия всех обработчиков */ +======= + /* your code to "disable" the functionality, remove all handlers */ + /* it's needed for the tests to work */ +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 } } diff --git a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/2-hoverintent/source.view/index.html b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/2-hoverintent/source.view/index.html index 97cd11d787..f86cefe708 100644 --- a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/2-hoverintent/source.view/index.html +++ b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/2-hoverintent/source.view/index.html @@ -3,10 +3,13 @@ +<<<<<<< HEAD Документ +======= +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 - + @@ -21,6 +24,7 @@ diff --git a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/2-hoverintent/source.view/style.css b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/2-hoverintent/source.view/style.css index 980e9457e7..fa2f09eba8 100644 --- a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/2-hoverintent/source.view/style.css +++ b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/2-hoverintent/source.view/style.css @@ -2,6 +2,10 @@ color: red; } +body { + margin: 0; +} + .minutes { color: green; } @@ -20,9 +24,15 @@ top: 0; } -.tooltip { +#tooltip { position: absolute; - background: #eee; - border: 1px brown solid; - padding: 3px; + padding: 10px 20px; + border: 1px solid #b3c9ce; + border-radius: 4px; + text-align: center; + font: italic 14px/1.3 sans-serif; + color: #333; + background: #fff; + z-index: 100000; + box-shadow: 3px 3px 3px rgba(0, 0, 0, .3); } diff --git a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/2-hoverintent/source.view/test.js b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/2-hoverintent/source.view/test.js index 44369d8c3f..f5d4aaffb3 100644 --- a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/2-hoverintent/source.view/test.js +++ b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/2-hoverintent/source.view/test.js @@ -3,7 +3,7 @@ describe("hoverIntent", function() { function mouse(eventType, x, y, options) { - let eventOptions = Object.assign({ + let eventOptions = Object.assign({ bubbles: true, clientX: x, clientY: y, @@ -11,15 +11,15 @@ describe("hoverIntent", function() { pageY: y, target: elem }, options || {}); - + elem.dispatchEvent(new MouseEvent(eventType, eventOptions)); } let isOver; let hoverIntent; - - + + before(function() { this.clock = sinon.useFakeTimers(); }); @@ -27,11 +27,11 @@ describe("hoverIntent", function() { after(function() { this.clock.restore(); }); - - + + beforeEach(function() { isOver = false; - + hoverIntent = new HoverIntent({ elem: elem, over: function() { @@ -49,18 +49,18 @@ describe("hoverIntent", function() { } }) - it("mouseover -> immediately no tooltip", function() { + it("mouseover -> when the pointer just arrived, no tooltip", function() { mouse('mouseover', 10, 10); assert.isFalse(isOver); }); - - it("mouseover -> pause shows tooltip", function() { + + it("mouseover -> after a delay, the tooltip shows up", function() { mouse('mouseover', 10, 10); this.clock.tick(100); assert.isTrue(isOver); }); - it("mouseover -> fast mouseout no tooltip", function() { + it("mouseover -> followed by fast mouseout leads doesn't show tooltip", function() { mouse('mouseover', 10, 10); setTimeout( () => mouse('mouseout', 300, 300, { relatedTarget: document.body}), @@ -94,5 +94,5 @@ describe("hoverIntent", function() { this.clock.tick(200); assert.isFalse(isOver); }); - + }); diff --git a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/article.md b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/article.md index d9b09b6b0c..74b6c2a815 100644 --- a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/article.md +++ b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/article.md @@ -76,7 +76,17 @@ Важная особенность события `mouseout` - оно генерируется в том числе, когда указатель переходит с элемента на его потомка. +<<<<<<< HEAD То есть, визуально указатель всё ещё на элементе, но мы получим `mouseout`! +======= +```html +
        +
        ...
        +
        +``` + +If we're on `#parent` and then move the pointer deeper into `#child`, we get `mouseout` on `#parent`! +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ![](mouseover-to-child.svg) @@ -119,7 +129,11 @@ parent.onmouseover = function(event) { Или же можно использовать другие события: `mouseenter` и `mouseleave`, которые мы сейчас изучим, с ними такая проблема не возникает. +<<<<<<< HEAD ## События mouseenter и mouseleave +======= +Alternatively we can use other events: `mouseenter` and `mouseleave`, that we'll be covering now, as they don't have such problems. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 События `mouseenter/mouseleave` похожи на `mouseover/mouseout`. Они тоже генерируются, когда курсор мыши переходит на элемент или покидает его. @@ -137,7 +151,11 @@ parent.onmouseover = function(event) { ```online Вот тот же пример, что и выше, но на этот раз на верхнем элементе стоят обработчики `mouseenter/mouseleave` вместо `mouseover/mouseout`. +<<<<<<< HEAD Как вы сами можете увидеть, генерируются только события, связанные с движением курсора относительно верхнего `
        `. Ничего не произойдёт при переходе на внутренний `
        ` и обратно. Переходы на потомки игнорируются. +======= +As you can see, the only generated events are the ones related to moving the pointer in and out of the top element. Nothing happens when the pointer goes to the child and back. Transitions between descendants are ignored +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 [codetabs height=340 src="mouseleave"] ``` @@ -187,10 +205,14 @@ table.onmouseout = function(event) { [js src="mouseenter-mouseleave-delegation-2/script.js"] +Once again, the important features are: +1. It uses event delegation to handle entering/leaving of any `
        ` inside the table. So it relies on `mouseover/out` instead of `mouseenter/leave` that don't bubble and hence allow no delegation. +2. Extra events, such as moving between descendants of `` are filtered out, so that `onEnter/Leave` runs only if the pointer leaves or enters `` as a whole. + ```online Полный пример со всеми деталями: -[codetabs height=380 src="mouseenter-mouseleave-delegation-2"] +[codetabs height=460 src="mouseenter-mouseleave-delegation-2"] Попробуйте подвигать курсор между ячейками и внутри них. Быстро или медленно - без разницы. В отличие от предыдущего примера выделяется только сама ячейка ``. ``` diff --git a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseenter-mouseleave-delegation-2.view/index.html b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseenter-mouseleave-delegation-2.view/index.html index d0960ca52b..ea331d3fc4 100755 --- a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseenter-mouseleave-delegation-2.view/index.html +++ b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseenter-mouseleave-delegation-2.view/index.html @@ -67,6 +67,10 @@
        + + + + diff --git a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseenter-mouseleave-delegation-2.view/script.js b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseenter-mouseleave-delegation-2.view/script.js index d783d5e20e..dc72c30d5a 100755 --- a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseenter-mouseleave-delegation-2.view/script.js +++ b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseenter-mouseleave-delegation-2.view/script.js @@ -18,7 +18,7 @@ table.onmouseover = function(event) { // ура, мы зашли на новый
        currentElem = target; - target.style.background = 'pink'; + onEnter(currentElem); }; @@ -39,7 +39,29 @@ table.onmouseout = function(event) { relatedTarget = relatedTarget.parentNode; } +<<<<<<< HEAD // мы действительно покинули элемент currentElem.style.background = ''; +======= + // we left the . really. + onLeave(currentElem); +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 currentElem = null; }; + +// any functions to handle entering/leaving an element +function onEnter(elem) { + elem.style.background = 'pink'; + + // show that in textarea + text.value += `over -> ${currentElem.tagName}.${currentElem.className}\n`; + text.scrollTop = 1e6; +} + +function onLeave(elem) { + elem.style.background = ''; + + // show that in textarea + text.value += `out <- ${elem.tagName}.${elem.className}\n`; + text.scrollTop = 1e6; +} diff --git a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseenter-mouseleave-delegation.view/script.js b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseenter-mouseleave-delegation.view/script.js index 4700d686e2..ae633ad67d 100755 --- a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseenter-mouseleave-delegation.view/script.js +++ b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseenter-mouseleave-delegation.view/script.js @@ -1,13 +1,15 @@ table.onmouseover = function(event) { let target = event.target; target.style.background = 'pink'; - text.value += "mouseover " + target.tagName + "\n"; + + text.value += `over -> ${target.tagName}\n`; text.scrollTop = text.scrollHeight; }; table.onmouseout = function(event) { let target = event.target; target.style.background = ''; - text.value += "mouseout " + target.tagName + "\n"; + + text.value += `out <- ${target.tagName}\n`; text.scrollTop = text.scrollHeight; -}; \ No newline at end of file +}; diff --git a/2-ui/3-event-details/4-mouse-drag-and-drop/2-drag-heroes/solution.view/soccer.js b/2-ui/3-event-details/4-mouse-drag-and-drop/2-drag-heroes/solution.view/soccer.js index 9caa863a79..964695b7e4 100644 --- a/2-ui/3-event-details/4-mouse-drag-and-drop/2-drag-heroes/solution.view/soccer.js +++ b/2-ui/3-event-details/4-mouse-drag-and-drop/2-drag-heroes/solution.view/soccer.js @@ -7,7 +7,7 @@ document.addEventListener('mousedown', function(event) { if (!dragElement) return; event.preventDefault(); - + dragElement.ondragstart = function() { return false; }; @@ -19,7 +19,7 @@ document.addEventListener('mousedown', function(event) { function onMouseUp(event) { finishDrag(); }; - + function onMouseMove(event) { moveAt(event.clientX, event.clientY); } @@ -31,9 +31,9 @@ document.addEventListener('mousedown', function(event) { if(isDragging) { return; } - + isDragging = true; - + document.addEventListener('mousemove', onMouseMove); element.addEventListener('mouseup', onMouseUp); @@ -51,10 +51,10 @@ document.addEventListener('mousedown', function(event) { if(!isDragging) { return; } - + isDragging = false; - dragElement.style.top = parseInt(dragElement.style.top) + pageYOffset + 'px'; + dragElement.style.top = parseInt(dragElement.style.top) + window.pageYOffset + 'px'; dragElement.style.position = 'absolute'; document.removeEventListener('mousemove', onMouseMove); diff --git a/2-ui/3-event-details/4-mouse-drag-and-drop/article.md b/2-ui/3-event-details/4-mouse-drag-and-drop/article.md index 8b383ef8a8..44724572a4 100644 --- a/2-ui/3-event-details/4-mouse-drag-and-drop/article.md +++ b/2-ui/3-event-details/4-mouse-drag-and-drop/article.md @@ -1,5 +1,6 @@ # Drag'n'Drop с событиями мыши +<<<<<<< HEAD Drag'n'Drop - отличный способ улучшить интерфейс. Захват элемента мышкой и его перенос визуально упростят что угодно: от копирования и перемещения документов (как в файловых менеджерах) до оформления заказа ("положить в корзину"). В современном стандарте HTML5 есть [раздел о Drag and Drop](https://html.spec.whatwg.org/multipage/interaction.html#dnd) - и там есть специальные события именно для Drag'n'Drop переноса, такие как `dragstart`, `dragend` и так далее. @@ -9,11 +10,23 @@ Drag'n'Drop - отличный способ улучшить интерфейс. Но у них есть и ограничения. Например, нельзя организовать перенос "только по горизонтали" или "только по вертикали". Также нельзя ограничить перенос внутри заданной зоны. Есть и другие интерфейсные задачи, которые такими встроенными событиями не реализуемы. Кроме того, мобильные устройства плохо их поддерживают. Здесь мы будем рассматривать Drag'n'Drop при помощи событий мыши. +======= +Drag'n'Drop is a great interface solution. Taking something and dragging and dropping it is a clear and simple way to do many things, from copying and moving documents (as in file managers) to ordering (dropping items into a cart). + +In the modern HTML standard there's a [section about Drag and Drop](https://html.spec.whatwg.org/multipage/interaction.html#dnd) with special events such as `dragstart`, `dragend`, and so on. + +These events allow us to support special kinds of drag'n'drop, such as handling dragging a file from OS file-manager and dropping it into the browser window. Then JavaScript can access the contents of such files. + +But native Drag Events also have limitations. For instance, we can't prevent dragging from a certain area. Also we can't make the dragging "horizontal" or "vertical" only. And there are many other drag'n'drop tasks that can't be done using them. Also, mobile device support for such events is very weak. + +So here we'll see how to implement Drag'n'Drop using mouse events. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## Алгоритм Drag'n'Drop Базовый алгоритм Drag'n'Drop выглядит так: +<<<<<<< HEAD 1. При `mousedown` - готовим элемент к перемещению, если необходимо (например, создаём его копию). 2. Затем при `mousemove` передвигаем элемент на новые координаты путём смены `left/top` и `position:absolute`. 3. При `mouseup` - остановить перенос элемента и произвести все действия, связанные с окончанием Drag'n'Drop. @@ -34,6 +47,25 @@ ball.onmousedown = function(event) { // (1) отследить нажатие // и установим абсолютно спозиционированный мяч под курсор moveAt(event.pageX, event.pageY); +======= +1. On `mousedown` - prepare the element for moving, if needed (maybe create a clone of it, add a class to it or whatever). +2. Then on `mousemove` move it by changing `left/top` with `position:absolute`. +3. On `mouseup` - perform all actions related to finishing the drag'n'drop. + +These are the basics. Later we'll see how to add other features, such as highlighting current underlying elements while we drag over them. + +Here's the implementation of dragging a ball: + +```js +ball.onmousedown = function(event) { + // (1) prepare to moving: make absolute and on top by z-index + ball.style.position = 'absolute'; + ball.style.zIndex = 1000; + + // move it out of any current parents directly into body + // to make it positioned relative to the body + document.body.append(ball); +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 // передвинуть мяч под координаты курсора // и сдвинуть на половину ширины/высоты для центрирования @@ -42,14 +74,24 @@ ball.onmousedown = function(event) { // (1) отследить нажатие ball.style.top = pageY - ball.offsetHeight / 2 + 'px'; } + // move our absolutely positioned ball under the pointer + moveAt(event.pageX, event.pageY); + function onMouseMove(event) { moveAt(event.pageX, event.pageY); } +<<<<<<< HEAD // (3) перемещать по экрану document.addEventListener('mousemove', onMouseMove); // (4) положить мяч, удалить более ненужные обработчики событий +======= + // (2) move the ball on mousemove + document.addEventListener('mousemove', onMouseMove); + + // (3) drop the ball, remove unneeded handlers +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ball.onmouseup = function() { document.removeEventListener('mousemove', onMouseMove); ball.onmouseup = null; @@ -65,10 +107,17 @@ ball.onmousedown = function(event) { // (1) отследить нажатие [iframe src="ball" height=230] +<<<<<<< HEAD Попробуйте перенести мяч мышкой и вы увидите описанное поведение. ``` Всё потому, что браузер имеет свой собственный Drag'n'Drop, который автоматически запускается и вступает в конфликт с нашим. Это происходит именно для картинок и некоторых других элементов. +======= +Try to drag'n'drop with the mouse and you'll see such behavior. +``` + +That's because the browser has its own drag'n'drop support for images and some other elements. It runs automatically and conflicts with ours. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Его нужно отключить: @@ -94,14 +143,22 @@ ball.ondragstart = function() { ## Правильное позиционирование +<<<<<<< HEAD В примерах выше мяч позиционируется так, что его центр оказывается под указателем мыши: +======= +In the examples above the ball is always moved so that its center is under the pointer: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js ball.style.left = pageX - ball.offsetWidth / 2 + 'px'; ball.style.top = pageY - ball.offsetHeight / 2 + 'px'; ``` +<<<<<<< HEAD Неплохо, но есть побочные эффекты. Мы, для начала переноса, можем нажать мышью на любом месте мяча. Если мячик "взят" за самый край – то в начале переноса он резко "прыгает", центрируясь под указателем мыши. +======= +Not bad, but there's a side effect. To initiate the drag'n'drop, we can `mousedown` anywhere on the ball. But if "take" it from its edge, then the ball suddenly "jumps" to become centered under the mouse pointer. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Было бы лучше, если бы изначальный сдвиг курсора относительно элемента сохранялся. @@ -125,7 +182,11 @@ ball.style.top = pageY - ball.offsetHeight / 2 + 'px'; ```js // onmousemove +<<<<<<< HEAD // ball has position:absoute +======= + // ball has position:absolute +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ball.style.left = event.pageX - *!*shiftX*/!* + 'px'; ball.style.top = event.pageY - *!*shiftY*/!* + 'px'; ``` @@ -220,7 +281,11 @@ ball.ondragstart = function() { Так что же делать? +<<<<<<< HEAD Существует метод `document.elementFromPoint(clientX, clientY)`. Он возвращает наиболее глубоко вложенный элемент по заданным координатам окна (или `null`, если указанные координаты находятся за пределами окна). +======= +There's a method called `document.elementFromPoint(clientX, clientY)`. It returns the most nested element on given window-relative coordinates (or `null` if given coordinates are out of the window). If there are multiple overlapping elements on the same coordinates, then the topmost one is returned. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Мы можем использовать его, чтобы из любого обработчика событий мыши выяснить, над какой мы потенциальной целью переноса, вот так: @@ -281,7 +346,11 @@ function onMouseMove(event) { } ``` +<<<<<<< HEAD В приведённом ниже примере, когда мяч перетаскивается через футбольные ворота, ворота подсвечиваются. +======= +In the example below when the ball is dragged over the soccer goal, the goal is highlighted. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 [codetabs height=250 src="ball4"] @@ -305,4 +374,8 @@ function onMouseMove(event) { - Можно использовать делегирование событий для `mousedown/up`. Один обработчик событий на большой зоне, который проверяет `event.target`, может управлять Drag'n'Drop для сотен элементов. - И так далее. +<<<<<<< HEAD Существуют фреймворки, которые строят архитектуру поверх этого алгоритма, создавая такие классы, как `DragZone`, `Droppable`, `Draggable`. Большинство из них делают вещи, аналогичные описанным выше. Вы можете и сами создать вашу собственную реализацию переноса, как видите, это достаточно просто, возможно, проще, чем адаптация чего-то готового. +======= +There are frameworks that build architecture over it: `DragZone`, `Droppable`, `Draggable` and other classes. Most of them do the similar stuff to what's described above, so it should be easy to understand them now. Or roll your own, as you can see that that's easy enough to do, sometimes easier than adapting a third-party solution. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/2-ui/3-event-details/4-mouse-drag-and-drop/ball.view/index.html b/2-ui/3-event-details/4-mouse-drag-and-drop/ball.view/index.html index acb990bec0..34c1f11103 100644 --- a/2-ui/3-event-details/4-mouse-drag-and-drop/ball.view/index.html +++ b/2-ui/3-event-details/4-mouse-drag-and-drop/ball.view/index.html @@ -13,16 +13,13 @@ diff --git a/2-ui/3-event-details/6-pointer-events/slider.view/style.css b/2-ui/3-event-details/6-pointer-events/slider.view/style.css new file mode 100644 index 0000000000..a84cd5e7e8 --- /dev/null +++ b/2-ui/3-event-details/6-pointer-events/slider.view/style.css @@ -0,0 +1,20 @@ +.slider { + border-radius: 5px; + background: #E0E0E0; + background: linear-gradient(left top, #E0E0E0, #EEEEEE); + width: 310px; + height: 15px; + margin: 5px; +} + +.thumb { + touch-action: none; + width: 10px; + height: 25px; + border-radius: 3px; + position: relative; + left: 10px; + top: -5px; + background: blue; + cursor: pointer; +} diff --git a/2-ui/3-event-details/5-keyboard-events/2-check-sync-keydown/solution.md b/2-ui/3-event-details/7-keyboard-events/2-check-sync-keydown/solution.md similarity index 100% rename from 2-ui/3-event-details/5-keyboard-events/2-check-sync-keydown/solution.md rename to 2-ui/3-event-details/7-keyboard-events/2-check-sync-keydown/solution.md diff --git a/2-ui/3-event-details/5-keyboard-events/2-check-sync-keydown/solution.view/index.html b/2-ui/3-event-details/7-keyboard-events/2-check-sync-keydown/solution.view/index.html similarity index 100% rename from 2-ui/3-event-details/5-keyboard-events/2-check-sync-keydown/solution.view/index.html rename to 2-ui/3-event-details/7-keyboard-events/2-check-sync-keydown/solution.view/index.html diff --git a/2-ui/3-event-details/5-keyboard-events/2-check-sync-keydown/task.md b/2-ui/3-event-details/7-keyboard-events/2-check-sync-keydown/task.md similarity index 100% rename from 2-ui/3-event-details/5-keyboard-events/2-check-sync-keydown/task.md rename to 2-ui/3-event-details/7-keyboard-events/2-check-sync-keydown/task.md diff --git a/2-ui/3-event-details/5-keyboard-events/article.md b/2-ui/3-event-details/7-keyboard-events/article.md similarity index 86% rename from 2-ui/3-event-details/5-keyboard-events/article.md rename to 2-ui/3-event-details/7-keyboard-events/article.md index 29660c0b9c..9dd7ba2d8d 100644 --- a/2-ui/3-event-details/5-keyboard-events/article.md +++ b/2-ui/3-event-details/7-keyboard-events/article.md @@ -107,7 +107,11 @@ document.addEventListener('keydown', function(event) { Чтобы отслеживать символы, зависящие от раскладки, `event.key` надёжнее. +<<<<<<< HEAD:2-ui/3-event-details/5-keyboard-events/article.md С другой стороны, преимущество `event.code` заключается в том, что его значение всегда остаётся неизменным, будучи привязанным к физическому местоположению клавиши, даже если пользователь меняет язык. Так что горячие клавиши, использующие это свойство, будут работать даже в случае переключения языка. +======= +On the other hand, `event.code` has the benefit of staying always the same, bound to the physical key location. So hotkeys that rely on it work well even in case of a language switch. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:2-ui/3-event-details/7-keyboard-events/article.md Хотим поддерживать клавиши, меняющиеся при раскладке? Тогда `event.key` - верный выбор. @@ -139,22 +143,31 @@ document.addEventListener('keydown', function(event) { ```html autorun height=60 run ``` +<<<<<<< HEAD:2-ui/3-event-details/5-keyboard-events/article.md Заметьте, что специальные клавиши, такие как `key:Backspace`, `key:Left`, `key:Right`, `key:Ctrl+V`, в этом поле для ввода не работают. Это побочный эффект чересчур жёсткого фильтра `checkPhoneKey`. Добавим ему немного больше свободы: +======= +The `onkeydown` handler here uses `checkPhoneKey` to check for the key pressed. If it's valid (from `0..9` or one of `+-()`), then it returns `true`, otherwise `false`. +As we know, the `false` value returned from the event handler, assigned using a DOM property or an attribute, such as above, prevents the default action, so nothing appears in the `` for keys that don't pass the test. (The `true` value returned doesn't affect anything, only returning `false` matters) +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:2-ui/3-event-details/7-keyboard-events/article.md + +Please note that special keys, such as `key:Backspace`, `key:Left`, `key:Right`, do not work in the input. That's a side effect of the strict filter `checkPhoneKey`. These keys make it return `false`. + +Let's relax the filter a little bit by allowing arrow keys `key:Left`, `key:Right` and `key:Delete`, `key:Backspace`: ```html autorun height=60 run @@ -162,7 +175,13 @@ function checkPhoneKey(key) { Теперь стрелочки и удаление прекрасно работают. +<<<<<<< HEAD:2-ui/3-event-details/5-keyboard-events/article.md ...Впрочем, мы всё равно можем ввести в `` что угодно с помощью правого клика мыши и пункта "Вставить" контекстного меню. Так что такой фильтр не обладает 100% надёжностью. Мы можем просто оставить всё как есть, потому что в большинстве случаев это работает. Альтернатива -- отслеживать событие `input`, оно генерируется после любых изменений в поле ``, и мы можем проверять новое значение и подчёркивать/изменять его, если оно не подходит. +======= +Even though we have the key filter, one still can enter anything using a mouse and right-click + Paste. Mobile devices provide other means to enter values. So the filter is not 100% reliable. + +The alternative approach would be to track the `oninput` event -- it triggers *after* any modification. There we can check the new `input.value` and modify it/highlight the `` when it's invalid. Or we can use both event handlers together. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:2-ui/3-event-details/7-keyboard-events/article.md ## "Дела минувших дней" @@ -170,7 +189,17 @@ function checkPhoneKey(key) { Но количество браузерных несовместимостей при работе с ними было столь велико, что у разработчиков спецификации не было другого выхода, кроме как объявить их устаревшими и создать новые, современные события (которые и описываются в этой главе). Старый код ещё работает, так как браузеры продолжают поддерживать и `keypress`, и `keyCode` с `charCode`, и `which`, но более нет никакой необходимости в их использовании. +<<<<<<< HEAD:2-ui/3-event-details/5-keyboard-events/article.md ## Итого +======= +## Mobile Keyboards + +When using virtual/mobile keyboards, formally known as IME (Input-Method Editor), the W3C standard states that a KeyboardEvent's [`e.keyCode` should be `229`](https://www.w3.org/TR/uievents/#determine-keydown-keyup-keyCode) and [`e.key` should be `"Unidentified"`](https://www.w3.org/TR/uievents-key/#key-attr-values). + +While some of these keyboards might still use the right values for `e.key`, `e.code`, `e.keyCode`... when pressing certain keys such as arrows or backspace, there's no guarantee, so your keyboard logic might not always work on mobile devices. + +## Summary +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:2-ui/3-event-details/7-keyboard-events/article.md Нажатие клавиши всегда генерирует клавиатурное событие, будь то буквенно-цифровая клавиша или специальная типа `key:Shift` или `key:Ctrl` и т.д. Единственным исключением является клавиша `Fn`, которая присутствует на клавиатуре некоторых ноутбуков. События на клавиатуре для неё нет, потому что она обычно работает на уровне более низком, чем даже ОС. diff --git a/2-ui/3-event-details/7-keyboard-events/german-layout.svg b/2-ui/3-event-details/7-keyboard-events/german-layout.svg new file mode 100644 index 0000000000..7ac9a4008b --- /dev/null +++ b/2-ui/3-event-details/7-keyboard-events/german-layout.svg @@ -0,0 +1 @@ +StrgStrgAl tAlt GrWinWinMenu \ No newline at end of file diff --git a/2-ui/3-event-details/5-keyboard-events/keyboard-dump.view/index.html b/2-ui/3-event-details/7-keyboard-events/keyboard-dump.view/index.html similarity index 76% rename from 2-ui/3-event-details/5-keyboard-events/keyboard-dump.view/index.html rename to 2-ui/3-event-details/7-keyboard-events/keyboard-dump.view/index.html index f565d52707..c40249e7cc 100644 --- a/2-ui/3-event-details/5-keyboard-events/keyboard-dump.view/index.html +++ b/2-ui/3-event-details/7-keyboard-events/keyboard-dump.view/index.html @@ -28,8 +28,13 @@ +<<<<<<< HEAD:2-ui/3-event-details/5-keyboard-events/keyboard-dump.view/index.html +======= + + +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:2-ui/3-event-details/7-keyboard-events/keyboard-dump.view/index.html diff --git a/2-ui/3-event-details/5-keyboard-events/keyboard-dump.view/script.js b/2-ui/3-event-details/7-keyboard-events/keyboard-dump.view/script.js similarity index 96% rename from 2-ui/3-event-details/5-keyboard-events/keyboard-dump.view/script.js rename to 2-ui/3-event-details/7-keyboard-events/keyboard-dump.view/script.js index 5eba24c7ad..d97f7a7b50 100644 --- a/2-ui/3-event-details/5-keyboard-events/keyboard-dump.view/script.js +++ b/2-ui/3-event-details/7-keyboard-events/keyboard-dump.view/script.js @@ -5,6 +5,8 @@ let lastTime = Date.now(); function handle(e) { if (form.elements[e.type + 'Ignore'].checked) return; + area.scrollTop = 1e6; + let text = e.type + ' key=' + e.key + ' code=' + e.code + diff --git a/2-ui/3-event-details/5-keyboard-events/keyboard-dump.view/style.css b/2-ui/3-event-details/7-keyboard-events/keyboard-dump.view/style.css similarity index 100% rename from 2-ui/3-event-details/5-keyboard-events/keyboard-dump.view/style.css rename to 2-ui/3-event-details/7-keyboard-events/keyboard-dump.view/style.css diff --git a/2-ui/3-event-details/7-keyboard-events/us-layout.svg b/2-ui/3-event-details/7-keyboard-events/us-layout.svg new file mode 100644 index 0000000000..353f225f1b --- /dev/null +++ b/2-ui/3-event-details/7-keyboard-events/us-layout.svg @@ -0,0 +1 @@ +Caps LockShiftShift \ No newline at end of file diff --git a/2-ui/3-event-details/8-onscroll/1-endless-page/solution.md b/2-ui/3-event-details/8-onscroll/1-endless-page/solution.md index b58a18e06f..e63c6a7c0f 100644 --- a/2-ui/3-event-details/8-onscroll/1-endless-page/solution.md +++ b/2-ui/3-event-details/8-onscroll/1-endless-page/solution.md @@ -55,12 +55,19 @@ function populate() { // нижняя граница документа let windowRelativeBottom = document.documentElement.getBoundingClientRect().bottom; +<<<<<<< HEAD // если пользователь не прокрутил достаточно далеко (>100px до конца страницы) — прерываем цикл if (windowRelativeBottom > document.documentElement.clientHeight + 100) { break; } // добавим больше данных +======= + // if the user hasn't scrolled far enough (>100px to the end) + if (windowRelativeBottom > document.documentElement.clientHeight + 100) break; + + // let's add more data +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 document.body.insertAdjacentHTML("beforeend", `

        Date: ${new Date()}

        `); } } diff --git a/2-ui/3-event-details/8-onscroll/3-load-visible-img/source.view/index.html b/2-ui/3-event-details/8-onscroll/3-load-visible-img/source.view/index.html index 23bf8a0b8a..0a571fe3e4 100644 --- a/2-ui/3-event-details/8-onscroll/3-load-visible-img/source.view/index.html +++ b/2-ui/3-event-details/8-onscroll/3-load-visible-img/source.view/index.html @@ -169,7 +169,11 @@

        Нептун

        * Достаточно, чтобы верхний или нижний край элемента был виден */ function isVisible(elem) { +<<<<<<< HEAD // ...ваш код... +======= + // todo: your code +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 } function showVisible() { diff --git a/2-ui/3-event-details/8-onscroll/article.md b/2-ui/3-event-details/8-onscroll/article.md index 59ff73df7e..fb3592dc79 100644 --- a/2-ui/3-event-details/8-onscroll/article.md +++ b/2-ui/3-event-details/8-onscroll/article.md @@ -1,6 +1,10 @@ # Прокрутка +<<<<<<< HEAD Событие прокрутки `scroll` позволяет реагировать на прокрутку страницы или элемента. Есть много хороших вещей, которые при этом можно сделать. +======= +The `scroll` event allows reacting to a page or element scrolling. There are quite a few good things we can do here. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Например: - Показать/скрыть дополнительные элементы управления или информацию, основываясь на том, в какой части документа находится пользователь. @@ -10,7 +14,7 @@ ```js autorun window.addEventListener('scroll', function() { - document.getElementById('showScroll').innerHTML = pageYOffset + 'px'; + document.getElementById('showScroll').innerHTML = window.pageYOffset + 'px'; }); ``` @@ -34,4 +38,8 @@ window.addEventListener('scroll', function() { Способов инициировать прокрутку много, поэтому более надёжный способ -- использовать CSS, свойство `overflow`. +<<<<<<< HEAD Вот несколько задач, которые вы можете решить или просмотреть, чтобы увидеть применение `onscroll`. +======= +Here are few tasks that you can solve or look through to see applications of `onscroll`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/2-ui/4-forms-controls/1-form-elements/1-add-select-option/task.md b/2-ui/4-forms-controls/1-form-elements/1-add-select-option/task.md index ab5d03393f..73bccfc6d7 100644 --- a/2-ui/4-forms-controls/1-form-elements/1-add-select-option/task.md +++ b/2-ui/4-forms-controls/1-form-elements/1-add-select-option/task.md @@ -15,6 +15,14 @@ importance: 5 Используя JavaScript: +<<<<<<< HEAD 1. Выведите значение и текст выбранного пункта. 2. Добавьте пункт: ``. 3. Сделайте его выбранным. +======= +1. Show the value and the text of the selected option. +2. Add an option: ``. +3. Make it selected. + +Note, if you've done everything right, your alert should show `blues`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/2-ui/4-forms-controls/1-form-elements/article.md b/2-ui/4-forms-controls/1-form-elements/article.md index afbfa59300..08d2ff8072 100644 --- a/2-ui/4-forms-controls/1-form-elements/article.md +++ b/2-ui/4-forms-controls/1-form-elements/article.md @@ -8,11 +8,19 @@ Формы в документе входят в специальную коллекцию `document.forms`. +<<<<<<< HEAD Это так называемая "именованная" коллекция: мы можем использовать для получения формы как её имя, так и порядковый номер в документе. ```js no-beautify document.forms.my - форма с именем "my" (name="my") document.forms[0] - первая форма в документе +======= +That's a so-called *"named collection"*: it's both named and ordered. We can use both the name or the number in the document to get the form. + +```js no-beautify +document.forms.my; // the form with name="my" +document.forms[0]; // the first form in the document +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` Когда мы уже получили форму, любой элемент доступен в именованной коллекции `form.elements`. @@ -36,9 +44,15 @@ document.forms[0] - первая форма в документе ``` +<<<<<<< HEAD Может быть несколько элементов с одним и тем же именем, это часто бывает с кнопками-переключателями `radio`. В этом случае `form.elements[name]` является коллекцией, например: +======= +There may be multiple elements with the same name. This is typical with radio buttons and checkboxes. + +In that case, `form.elements[name]` is a *collection*. For instance: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```html run height=40
        @@ -118,13 +132,21 @@ alert(ageElems[0]); // [object HTMLInputElement] ``` +<<<<<<< HEAD Обычно это не вызывает проблем, так как мы редко меняем имена у элементов формы. +======= +That's usually not a problem, however, because we rarely change names of form elements. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```` ## Обратная ссылка: element.form +<<<<<<< HEAD Для любого элемента форма доступна через `element.form`. Так что форма ссылается на все элементы, а эти элементы ссылаются на форму. +======= +For any element, the form is available as `element.form`. So a form references all elements, and elements reference the form. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Вот иллюстрация: @@ -154,7 +176,11 @@ alert(ageElems[0]); // [object HTMLInputElement] ### input и textarea +<<<<<<< HEAD К их значению можно получить доступ через свойство `input.value` (строка) или `input.checked` (булево значение) для чекбоксов. +======= +We can access their value as `input.value` (string) or `input.checked` (boolean) for checkboxes and radio buttons. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Вот так: @@ -175,12 +201,19 @@ input.checked = true; // для чекбоксов и переключателе Элемент ``: +<<<<<<< HEAD 1. Найти соответствующий элемент `
        ` в "режиме редактирования", клики по другим ячейкам игнорируются. - Таблица может иметь множество ячеек. Используйте делегирование событий. +======= +- On click -- the cell should become "editable" (textarea appears inside), we can change HTML. There should be no resize, all geometry should remain the same. +- Buttons OK and CANCEL appear below the cell to finish/cancel the editing. +- Only one cell may be editable at a moment. While a `` is in "edit mode", clicks on other cells are ignored. +- The table may have many cells. Use event delegation. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Демо: diff --git a/2-ui/4-forms-controls/2-focus-blur/5-keyboard-mouse/task.md b/2-ui/4-forms-controls/2-focus-blur/5-keyboard-mouse/task.md index c5911d5849..37d095814d 100644 --- a/2-ui/4-forms-controls/2-focus-blur/5-keyboard-mouse/task.md +++ b/2-ui/4-forms-controls/2-focus-blur/5-keyboard-mouse/task.md @@ -8,6 +8,12 @@ importance: 4 [demo src="solution"] +<<<<<<< HEAD P.S. Не добавляйте обработчики никуда, кроме элемента `#mouse`. P.P.S. Не изменяйте HTML/CSS, подход должен быть общим и работать с любым элементом. +======= +P.S. Don't put event handlers anywhere except the `#mouse` element. + +P.P.S. Don't modify HTML/CSS, the approach should be generic and work with any element. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/2-ui/4-forms-controls/2-focus-blur/article.md b/2-ui/4-forms-controls/2-focus-blur/article.md index d024b53b53..1bc2587557 100644 --- a/2-ui/4-forms-controls/2-focus-blur/article.md +++ b/2-ui/4-forms-controls/2-focus-blur/article.md @@ -1,5 +1,9 @@ +<<<<<<< HEAD # Фокусировка: focus/blur +======= +An element receives the focus when the user either clicks on it or uses the `key:Tab` key on the keyboard. There's also an `autofocus` HTML attribute that puts the focus onto an element by default when a page loads and other means of getting the focus. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Элемент получает фокус, когда пользователь кликает по нему или использует клавишу `key:Tab`. Также существует HTML-атрибут `autofocus`, который устанавливает фокус на элемент, когда страница загружается. Есть и другие способы получения фокуса, о них - далее. @@ -17,10 +21,15 @@ Используем их для валидации(проверки) введённых данных. +<<<<<<< HEAD В примере ниже: - Обработчик `blur` проверяет, введён ли email, и если нет -- показывает ошибку. - Обработчик `focus` скрывает это сообщение об ошибке (в момент потери фокуса проверка повторится): +======= +- The `blur` handler checks if the field has an email entered, and if not -- shows an error. +- The `focus` handler hides the error message (on `blur` it will be checked again): +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```html run autorun height=60 <p>Hello</p>p.firstChild \ No newline at end of file diff --git a/2-ui/99-ui-misc/03-event-loop/2-micro-macro-queue/solution.md b/2-ui/99-ui-misc/03-event-loop/2-micro-macro-queue/solution.md index 1a9b022079..e447c361a5 100644 --- a/2-ui/99-ui-misc/03-event-loop/2-micro-macro-queue/solution.md +++ b/2-ui/99-ui-misc/03-event-loop/2-micro-macro-queue/solution.md @@ -1,3 +1,4 @@ +<<<<<<< HEAD Вывод в консоли: 1 7 3 5 2 6 4. Задача довольно простая, нужно лишь понимать, как работают очереди микрозадач и макрозадач. @@ -47,4 +48,56 @@ console.log(7); - В очереди макрозадач получается теперь: `console.log(2); console.log(6); console.log(4)`. 3. Очередь микрозадач полностью выполнена, срабатывает очередь макрозадач. Она выведет `2`, `6`, `4`. -Получается вывод `1 7 3 5 2 6 4`. \ No newline at end of file +Получается вывод `1 7 3 5 2 6 4`. +======= +The console output is: 1 7 3 5 2 6 4. + +The task is quite simple, we just need to know how microtask and macrotask queues work. + +Let's see what's going on, step by step. + +```js +console.log(1); +// The first line executes immediately, it outputs `1`. +// Macrotask and microtask queues are empty, as of now. + +setTimeout(() => console.log(2)); +// `setTimeout` appends the callback to the macrotask queue. +// - macrotask queue content: +// `console.log(2)` + +Promise.resolve().then(() => console.log(3)); +// The callback is appended to the microtask queue. +// - microtask queue content: +// `console.log(3)` + +Promise.resolve().then(() => setTimeout(() => console.log(4))); +// The callback with `setTimeout(...4)` is appended to microtasks +// - microtask queue content: +// `console.log(3); setTimeout(...4)` + +Promise.resolve().then(() => console.log(5)); +// The callback is appended to the microtask queue +// - microtask queue content: +// `console.log(3); setTimeout(...4); console.log(5)` + +setTimeout(() => console.log(6)); +// `setTimeout` appends the callback to macrotasks +// - macrotask queue content: +// `console.log(2); console.log(6)` + +console.log(7); +// Outputs 7 immediately. +``` + +To summarize, + +1. Numbers `1` and `7` show up immediately, because simple `console.log` calls don't use any queues. +2. Then, after the main code flow is finished, the microtask queue runs. + - It has commands: `console.log(3); setTimeout(...4); console.log(5)`. + - Numbers `3` and `5` show up, while `setTimeout(() => console.log(4))` adds the `console.log(4)` call to the end of the macrotask queue. + - The macrotask queue is now: `console.log(2); console.log(6); console.log(4)`. +3. After the microtask queue becomes empty, the macrotask queue executes. It outputs `2`, `6`, `4`. + +Finally, we have the output: `1 7 3 5 2 6 4`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/2-ui/99-ui-misc/03-event-loop/2-micro-macro-queue/task.md b/2-ui/99-ui-misc/03-event-loop/2-micro-macro-queue/task.md index ef61141f81..c0334724cd 100644 --- a/2-ui/99-ui-misc/03-event-loop/2-micro-macro-queue/task.md +++ b/2-ui/99-ui-misc/03-event-loop/2-micro-macro-queue/task.md @@ -2,7 +2,11 @@ importance: 5 --- +<<<<<<< HEAD # Что код выведет в консоли? +======= +# What will be the output of this code? +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js console.log(1); @@ -19,4 +23,7 @@ setTimeout(() => console.log(6)); console.log(7); ``` +<<<<<<< HEAD +======= +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/2-ui/99-ui-misc/03-event-loop/article.md b/2-ui/99-ui-misc/03-event-loop/article.md index d5ff51efc8..21350d07cc 100644 --- a/2-ui/99-ui-misc/03-event-loop/article.md +++ b/2-ui/99-ui-misc/03-event-loop/article.md @@ -9,7 +9,11 @@ ## Событийный цикл +<<<<<<< HEAD Идея *событийного цикла* очень проста. Есть бесконечный цикл, в котором движок JavaScript ожидает задачи, исполняет их и снова ожидает появления новых. +======= +The *event loop* concept is very simple. There's an endless loop, where the JavaScript engine waits for tasks, executes them and then sleeps, waiting for more tasks. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Общий алгоритм движка: @@ -17,7 +21,11 @@ - выполнить их, начиная с самой старой 2. Бездействовать до появления новой задачи, а затем перейти к пункту 1 +<<<<<<< HEAD Это формализация того, что мы наблюдаем, просматривая веб-страницу. Движок JavaScript большую часть времени ничего не делает и работает, только если требуется исполнить скрипт/обработчик или обработать событие. +======= +That's a formalization of what we see when browsing a page. The JavaScript engine does nothing most of the time, it only runs if a script/handler/event activates. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Примеры задач: @@ -30,6 +38,7 @@ Может так случиться, что задача поступает, когда движок занят чем-то другим, тогда она ставится в очередь. +<<<<<<< HEAD Очередь, которую формируют такие задачи, называют "очередью макрозадач" (macrotask queue, термин [V8](https://v8.dev/)). ![](eventLoop.svg) @@ -37,26 +46,53 @@ Например, когда движок занят выполнением скрипта, пользователь может передвинуть мышь, тем самым вызвав появление события `mousemove`, или может истечь таймер, установленный `setTimeout`, и т.п. Эти задачи формируют очередь, как показано на иллюстрации выше. Задачи из очереди исполняются по правилу "первым пришёл - первым ушёл". Когда браузер заканчивает выполнение скрипта, он обрабатывает событие `mousemove`, затем выполняет обработчик, заданный `setTimeout`, и так далее. +======= +The tasks form a queue, the so-called "macrotask queue" ([v8](https://v8.dev/) term): + +![](eventLoop.svg) + +For instance, while the engine is busy executing a `script`, a user may move their mouse causing `mousemove`, and `setTimeout` may be due and so on, these tasks form a queue, as illustrated in the picture above. + +Tasks from the queue are processed on a "first come – first served" basis. When the engine browser is done with the `script`, it handles `mousemove` event, then `setTimeout` handler, and so on. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Пока что всё просто, не правда ли? +<<<<<<< HEAD Отметим две детали: 1. Рендеринг (отрисовка страницы) никогда не происходит во время выполнения задачи движком. Не имеет значения, сколь долго выполняется задача. Изменения в DOM отрисовываются только после того, как задача выполнена. 2. Если задача выполняется очень долго, то браузер не может выполнять другие задачи, обрабатывать пользовательские события, поэтому спустя некоторое время браузер предлагает "убить" долго выполняющуюся задачу. Такое возможно, когда в скрипте много сложных вычислений или ошибка, ведущая к бесконечному циклу. Это была теория. Теперь давайте взглянем, как можно применить эти знания. +======= +Two more details: +1. Rendering never happens while the engine executes a task. It doesn't matter if the task takes a long time. Changes to the DOM are painted only after the task is complete. +2. If a task takes too long, the browser can't do other tasks, such as processing user events. So after some time, it raises an alert like "Page Unresponsive", suggesting killing the task with the whole page. That happens when there are a lot of complex calculations or a programming error leading to an infinite loop. + +That was the theory. Now let's see how we can apply that knowledge. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## Пример 1: разбиение "тяжёлой" задачи. Допустим, у нас есть задача, требующая значительных ресурсов процессора. +<<<<<<< HEAD Например, подсветка синтаксиса (используется для выделения цветом участков кода на этой странице) -- довольно процессороёмкая задача. Для подсветки кода надо выполнить синтаксический анализ, создать много элементов для цветового выделения, добавить их в документ -- для большого текста это требует значительных ресурсов. +======= +For example, syntax-highlighting (used to colorize code examples on this page) is quite CPU-heavy. To highlight the code, it performs the analysis, creates many colored elements, adds them to the document -- for a large amount of text that takes a lot of time. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Пока движок занят подсветкой синтаксиса, он не может делать ничего, связанного с DOM, не может обрабатывать пользовательские события и т.д. Возможно даже "подвисание" браузера, что совершенно неприемлемо. +<<<<<<< HEAD Мы можем избежать этого, разбив задачу на части. Сделать подсветку для первых 100 строк, затем запланировать `setTimeout` (с нулевой задержкой) для разметки следующих 100 строк и т.д. Чтобы продемонстрировать такой подход, давайте будем использовать для простоты функцию, которая считает от `1` до `1000000000`. +======= +We can avoid problems by splitting the big task into pieces. Highlight the first 100 lines, then schedule `setTimeout` (with zero-delay) for the next 100 lines, and so on. + +To demonstrate this approach, for the sake of simplicity, instead of text-highlighting, let's take a function that counts from `1` to `1000000000`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Если вы запустите код ниже, движок "зависнет" на некоторое время. Для серверного JS это будет явно заметно, а если вы будете выполнять этот код в браузере, то попробуйте понажимать другие кнопки на странице -- вы заметите, что никакие другие события не обрабатываются до завершения функции счёта. @@ -78,9 +114,15 @@ function count() { count(); ``` +<<<<<<< HEAD Браузер может даже показать сообщение "скрипт выполняется слишком долго". Давайте разобьём задачу на части, воспользовавшись вложенным `setTimeout`: +======= +The browser may even show a "the script takes too long" warning. + +Let's split the job using nested `setTimeout` calls: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let i = 0; @@ -113,13 +155,23 @@ count(); 2. Второе выполнение производит счёт: i=1000001...2000000. 3. ...и так далее. +<<<<<<< HEAD Теперь если новая сторонняя задача (например, событие `onclick`) появляется, пока движок занят выполнением 1-й части, то она становится в очередь, и затем выполняется, когда 1-я часть завершена, перед следующей частью. Периодические возвраты в событийный цикл между запусками `count` дают движку достаточно "воздуха", чтобы сделать что-то ещё, отреагировать на действия пользователя. Отметим, что оба варианта -- с разбиением задачи с помощью `setTimeout` и без -- сопоставимы по скорости выполнения. Нет большой разницы в общем времени счёта. +======= +Now, if a new side task (e.g. `onclick` event) appears while the engine is busy executing part 1, it gets queued and then executes when part 1 finished, before the next part. Periodic returns to the event loop between `count` executions provide just enough "air" for the JavaScript engine to do something else, to react to other user actions. + +The notable thing is that both variants -- with and without splitting the job by `setTimeout` -- are comparable in speed. There's not much difference in the overall counting time. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Чтобы сократить разницу ещё сильнее, давайте немного улучшим наш код. +<<<<<<< HEAD Мы перенесём планирование очередного вызова в начало `count()`: +======= +We'll move the scheduling to the beginning of the `count()`: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let i = 0; @@ -128,7 +180,11 @@ let start = Date.now(); function count() { +<<<<<<< HEAD // перенесём планирование очередного вызова в начало +======= + // move the scheduling to the beginning +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 if (i < 1e9 - 1e6) { setTimeout(count); // запланировать новый вызов } @@ -160,9 +216,15 @@ count(); Ещё одно преимущество разделения на части крупной задачи в браузерных скриптах - это возможность показывать индикатор выполнения. +<<<<<<< HEAD Обычно браузер отрисовывает содержимое страницы после того, как заканчивается выполнение текущего кода. Не имеет значения, насколько долго выполняется задача. Изменения в DOM отображаются только после её завершения. С одной стороны, это хорошо, потому что наша функция может создавать много элементов, добавлять их по одному в документ и изменять их стили -- пользователь не увидит "промежуточного", незаконченного состояния. Это важно, верно? +======= +As mentioned earlier, changes to DOM are painted only after the currently running task is completed, irrespective of how long it takes. + +On one hand, that's great, because our function may create many elements, add them one-by-one to the document and change their styles -- the visitor won't see any "intermediate", unfinished state. An important thing, right? +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 В примере ниже изменения `i` не будут заметны, пока функция не завершится, поэтому мы увидим только последнее значение `i`: @@ -238,7 +300,11 @@ menu.onclick = function() { ## Макрозадачи и Микрозадачи +<<<<<<< HEAD Помимо *макрозадач*, описанных в этой части, существуют *микрозадачи*, упомянутые в главе . +======= +Along with *macrotasks*, described in this chapter, there are *microtasks*, mentioned in the chapter . +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Микрозадачи приходят только из кода. Обычно они создаются промисами: выполнение обработчика `.then/catch/finally` становится микрозадачей. Микрозадачи также используются "под капотом" `await`, т.к. это форма обработки промиса. @@ -263,11 +329,19 @@ alert("code"); 2. `promise` появляется вторым, потому что `.then` проходит через очередь микрозадач и выполняется после текущего синхронного кода. 3. `timeout` появляется последним, потому что это макрозадача. +<<<<<<< HEAD Более подробное изображение событийного цикла выглядит так: ![](eventLoop-full.svg) **Все микрозадачи завершаются до обработки каких-либо событий или рендеринга, или перехода к другой макрозадаче.** +======= +The richer event loop picture looks like this (order is from top to bottom, that is: the script first, then microtasks, rendering and so on): + +![](eventLoop-full.svg) + +All microtasks are completed before any other event handling or rendering or any other macrotask takes place. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Это важно, так как гарантирует, что общее окружение остаётся одним и тем же между микрозадачами - не изменены координаты мыши, не получены новые данные по сети и т.п. @@ -303,7 +377,11 @@ alert("code"); ## Итого +<<<<<<< HEAD Более подробный алгоритм событийного цикла (хоть и упрощённый в сравнении со [спецификацией](https://html.spec.whatwg.org/multipage/webappapis.html#event-loop-processing-model)): +======= +A more detailed event loop algorithm (though still simplified compared to the [specification](https://html.spec.whatwg.org/multipage/webappapis.html#event-loop-processing-model)): +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 1. Выбрать и исполнить старейшую задачу из очереди *макрозадач* (например, "script"). 2. Исполнить все *микрозадачи*: @@ -316,7 +394,11 @@ alert("code"); Чтобы добавить в очередь новую *макрозадачу*: - Используйте `setTimeout(f)` с нулевой задержкой. +<<<<<<< HEAD Этот способ можно использовать для разбиения больших вычислительных задач на части, чтобы браузер мог реагировать на пользовательские события и показывать прогресс выполнения этих частей. +======= +That may be used to split a big calculation-heavy task into pieces, for the browser to be able to react to user events and show progress between them. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Также это используется в обработчиках событий для отложенного выполнения действия после того, как событие полностью обработано (всплытие завершено). @@ -335,5 +417,9 @@ alert("code"); Web Workers могут обмениваться сообщениями с основным процессом, но они имеют свои переменные и свой событийный цикл. +<<<<<<< HEAD Web Workers не имеют доступа к DOM, поэтому основное их применение - вычисления. Они позволяют задействовать несколько ядер процессора одновременно. +======= +Web Workers do not have access to DOM, so they are useful, mainly, for calculations, to use multiple CPU cores simultaneously. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` diff --git a/3-frames-and-windows/01-popup-windows/article.md b/3-frames-and-windows/01-popup-windows/article.md index de116459a0..2feab98fcc 100644 --- a/3-frames-and-windows/01-popup-windows/article.md +++ b/3-frames-and-windows/01-popup-windows/article.md @@ -7,7 +7,11 @@ window.open('https://javascript.info/') ``` +<<<<<<< HEAD ... и откроется новое окно с указанным URL. Большинство современных браузеров по умолчанию будут открывать новую вкладку вместо отдельного окна. +======= +...And it will open a new window with given URL. Most modern browsers are configured to open url in new tabs instead of separate windows. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Попапы существуют с доисторических времён. Они были придуманы для отображения нового контента поверх открытого главного окна. Но с тех пор появились другие способы сделать это: JavaScript может загрузить содержимое вызовом [fetch](info:fetch) и показать его в тут же созданном `
        `, так что попапы используются не каждый день. @@ -15,9 +19,15 @@ window.open('https://javascript.info/') Однако, для некоторых задач попапы ещё используются, например для OAuth-авторизации (вход через Google/Facebook/...), так как: +<<<<<<< HEAD 1. Попап - это отдельное окно со своим JavaScript-окружением. Так что открытие попапа со стороннего, не доверенного сайта вполне безопасно 2. Открыть попап очень просто. 3. Попап может производить навигацию (менять URL) и отсылать сообщения в основное окно. +======= +1. A popup is a separate window which has its own independent JavaScript environment. So opening a popup from a third-party, non-trusted site is safe. +2. It's very easy to open a popup. +3. A popup can navigate (change URL) and send messages to the opener window. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## Блокировка попапов @@ -38,9 +48,15 @@ button.onclick = () => { Таким образом браузеры могут защитить пользователя от появления нежелательных попапов, при этом не отключая попапы полностью. +<<<<<<< HEAD ## Полный синтаксис window.open Синтаксис открытия нового окна: `window.open(url, name, params)`: +======= +## window.open + +The syntax to open a popup is: `window.open(url, name, params)`: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 url : URL для загрузки в новом окне. @@ -49,7 +65,11 @@ name : Имя нового окна. У каждого окна есть свойство `window.name`, в котором можно задавать, какое окно использовать для попапа. Таким образом, если уже существует окно с заданным именем – указанный в параметрах URL откроется в нем, в противном случае откроется новое окно. params +<<<<<<< HEAD : Строка параметров для нового окна. Содержит настройки, разделённые запятыми. Важно помнить, что в данной строке не должно быть пробелов. Например `width=200,height=100`. +======= +: The configuration string for the new window. It contains settings, delimited by a comma. There must be no spaces in params, for instance: `width=200,height=100`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Параметры в строке `params`: @@ -68,7 +88,13 @@ params ## Пример: минималистичное окно +<<<<<<< HEAD Давайте откроем окно с минимальным набором настроек, просто чтобы посмотреть, какие из них браузер позволит отключить: +======= +## Example: a minimalistic window + +Let's open a window with minimal set of features, just to see which of them browser allows to disable: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let params = `scrollbars=no,resizable=no,status=no,location=no,toolbar=no,menubar=no, @@ -99,7 +125,11 @@ open('/', 'test', params); ## Доступ к попапу из основного окна +<<<<<<< HEAD Вызов `open` возвращает ссылку на новое окно. Эта ссылка может быть использована для управления свойствами окна, например, изменения положения и др. +======= +The `open` call returns a reference to the new window. It can be used to manipulate its properties, change location and even more. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Например, здесь мы генерируем содержимое попапа из JavaScript: @@ -133,7 +163,11 @@ newWindow.onload = function() { Иначе, например, если основное окно с `site.com`, а попап с `gmail.com`, это невозможно по соображениям пользовательской безопасности. Детали см. в главе . ``` +<<<<<<< HEAD ## Доступ к открывшему окну из попапа +======= +## Accessing window from popup +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Попап также может обратиться к открывшему его окну по ссылке `window.opener`. Она равна `null` для всех окон, кроме попапов. @@ -172,7 +206,13 @@ newWindow.onload = function() { ## Прокрутка и изменение размеров +<<<<<<< HEAD Методы для передвижения и изменения размеров окна: +======= +## Moving and resizing + +There are methods to move/resize a window: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 `win.moveBy(x,y)` : Переместить окно относительно текущей позиции на `x` пикселей вправо и `y` пикселей вниз. Допустимы отрицательные значения (для перемещения окна влево и вверх). @@ -215,14 +255,23 @@ newWindow.onload = function() { ## Установка и потеря фокуса +<<<<<<< HEAD Теоретически, установить попап в фокус можно с помощью метода `window.focus()`, а убрать из фокуса – с помощью `window.blur()`. Также существуют события `focus/blur`, которые позволяют отследить, когда фокус переводится на какое-то другое окно. Раньше на "плохих" сайтах эти методы могли становиться средством манипуляции. Например: +======= +Theoretically, there are `window.focus()` and `window.blur()` methods to focus/unfocus on a window. And there are also `focus/blur` events that allow to catch the moment when the visitor focuses on a window and switches elsewhere. + +Although, in practice they are severely limited, because in the past evil pages abused them. + +For instance, look at this code: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run window.onblur = () => window.focus(); ``` +<<<<<<< HEAD Когда пользователь пытается перевести фокус на другое окно, этот код возвращает фокус назад. Таким образом, фокус как бы "блокируется" в попапе, который не нужен пользователю. Из-за этого в браузерах и появились ограничения, которые препятствуют такого рода поведению фокуса. Эти ограничения нужны для защиты пользователя от назойливой рекламы и "плохих" страниц, и их работа различается в зависимости от конкретного браузера. @@ -230,13 +279,29 @@ window.onblur = () => window.focus(); Например, мобильный браузер обычно полностью игнорирует такие вызовы метода `window.focus()`. Также фокусировка не работает, когда попап открыт в отдельной вкладке (в отличие от открытия в отдельном окне). Но всё-таки иногда методы фокусировки бывают полезны. Например: +======= +When a user attempts to switch out of the window (`window.onblur`), it brings the window back into focus. The intention is to "lock" the user within the `window`. + +So browsers had to introduce many limitations to forbid the code like that and protect the user from ads and evils pages. They depend on the browser. + +For instance, a mobile browser usually ignores `window.focus()` completely. Also focusing doesn't work when a popup opens in a separate tab rather than a new window. + +Still, there are some use cases when such calls do work and can be useful. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 - Когда мы открываем попап, может быть хорошей идеей запустить для него `newWindow.focus()`. Для некоторых комбинаций браузера и операционной системы это устранит неоднозначность – заметит ли пользователь это новое окно. - Если нужно отследить, когда посетитель использует веб-приложение, можно отслеживать `window.onfocus/onblur`. Это позволит ставить на паузу и продолжать выполнение анимаций и других интерактивных действий на странице. При этом важно помнить, что `blur` означает, что окно больше не в фокусе, но пользователь может по-прежнему видеть его. +<<<<<<< HEAD ## Итого Всплывающие окна используются нечасто. Ведь загрузить новую информацию можно динамически, а показать -- в элементе `
        `, расположенным над страницей (`z-index`). Ещё одна альтернатива -- тег ` diff --git a/3-frames-and-windows/06-clickjacking/article.md b/3-frames-and-windows/06-clickjacking/article.md index 9eeb9a9323..eb649171aa 100644 --- a/3-frames-and-windows/06-clickjacking/article.md +++ b/3-frames-and-windows/06-clickjacking/article.md @@ -152,7 +152,11 @@ window.onbeforeunload = function() { ## Отображение с ограниченными возможностями +<<<<<<< HEAD У заголовка `X-Frame-Options` есть побочный эффект. Другие сайты не смогут отобразить нашу страницу во фрейме, даже если у них будут на то веские причины. +======= +The `X-Frame-Options` header has a side effect. Other sites won't be able to show our page in a frame, even if they have good reasons to do so. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Так что есть другие решения... Например, мы можем "накрыть" страницу блоком `
        ` со стилями `height: 100%; width: 100%;`, чтобы он перехватывал все клики. Этот `
        ` будем убирать, если `window == top` или если мы поймём, что защита нам не нужна. diff --git a/4-binary/01-arraybuffer-binary-arrays/article.md b/4-binary/01-arraybuffer-binary-arrays/article.md index 43d97173c7..b5ab1025fc 100644 --- a/4-binary/01-arraybuffer-binary-arrays/article.md +++ b/4-binary/01-arraybuffer-binary-arrays/article.md @@ -30,14 +30,25 @@ alert(buffer.byteLength); // 16 **Для работы с `ArrayBuffer` нам нужен специальный объект, реализующий "представление" данных.** +<<<<<<< HEAD Такие объекты не хранят какое-то собственное содержимое. Они интерпретируют бинарные данные, хранящиеся в `ArrayBuffer`. +======= +A view object does not store anything on its own. It's the "eyeglasses" that give an interpretation of the bytes stored in the `ArrayBuffer`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Например: +<<<<<<< HEAD - **`Uint8Array`** -- представляет каждый байт в `ArrayBuffer` как отдельное число; возможные значения находятся в промежутке от 0 до 255 (в байте 8 бит, отсюда такой набор). Такое значение называется "8-битное целое без знака". - **`Uint16Array`** -- представляет каждые 2 байта в `ArrayBuffer` как целое число; возможные значения находятся в промежутке от 0 до 65535. Такое значение называется "16-битное целое без знака". - **`Uint32Array`** -- представляет каждые 4 байта в `ArrayBuffer` как целое число; возможные значения находятся в промежутке от 0 до 4294967295. Такое значение называется "32-битное целое без знака". - **`Float64Array`** -- представляет каждые 8 байт в `ArrayBuffer` как число с плавающей точкой; возможные значения находятся в промежутке между 5.0x10-324 и 1.8x10308. +======= +- **`Uint8Array`** -- treats each byte in `ArrayBuffer` as a separate number, with possible values from 0 to 255 (a byte is 8-bit, so it can hold only that much). Such value is called a "8-bit unsigned integer". +- **`Uint16Array`** -- treats every 2 bytes as an integer, with possible values from 0 to 65535. That's called a "16-bit unsigned integer". +- **`Uint32Array`** -- treats every 4 bytes as an integer, with possible values from 0 to 4294967295. That's called a "32-bit unsigned integer". +- **`Float64Array`** -- treats every 8 bytes as a floating point number with possible values from 5.0x10-324 to 1.8x10308. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Таким образом, бинарные данные из `ArrayBuffer` размером 16 байт могут быть представлены как 16 чисел маленькой разрядности или как 8 чисел большей разрядности (по 2 байта каждое), или как 4 числа ещё большей разрядности (по 4 байта каждое), или как 2 числа с плавающей точкой высокой точности (по 8 байт каждое). @@ -71,10 +82,19 @@ for(let num of view) { ## TypedArray +<<<<<<< HEAD Общий термин для всех таких представлений (`Uint8Array`, `Uint32Array` и т.д.) -- это [TypedArray](https://tc39.github.io/ecma262/#sec-typedarray-objects), типизированный массив. У них имеется набор одинаковых свойств и методов. Они уже намного больше напоминают обычные массивы: элементы проиндексированы, и возможно осуществить обход содержимого. +======= +The common term for all these views (`Uint8Array`, `Uint32Array`, etc) is [TypedArray](https://tc39.github.io/ecma262/#sec-typedarray-objects). They share the same set of methods and properties. +Please note, there's no constructor called `TypedArray`, it's just a common "umbrella" term to represent one of views over `ArrayBuffer`: `Int8Array`, `Uint8Array` and so on, the full list will soon follow. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 + +When you see something like `new TypedArray`, it means any of `new Int8Array`, `new Uint8Array`, etc. + +Typed arrays behave like regular arrays: have indexes and are iterable. Конструкторы типизированных массивов (будь то `Int8Array` или `Float64Array`, без разницы) ведут себя по-разному в зависимости от типа передаваемого им аргумента. @@ -123,9 +143,15 @@ new TypedArray(); Как видим, можно создавать типизированные массивы `TypedArray` напрямую, не передавая в конструктор объект типа `ArrayBuffer`. Но представления не могут существовать сами по себе без двоичных данных, так что на самом деле объект `ArrayBuffer` создаётся автоматически во всех случаях, кроме первого, когда он явно передан в конструктор представления. +<<<<<<< HEAD Для доступа к `ArrayBuffer` в `TypedArray` есть следующие свойства: - `buffer` -- ссылка на объект `ArrayBuffer`. - `byteLength` -- размер содержимого `ArrayBuffer` в байтах. +======= +To access the underlying `ArrayBuffer`, there are following properties in `TypedArray`: +- `buffer` -- references the `ArrayBuffer`. +- `byteLength` -- the length of the `ArrayBuffer`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Таким образом, мы всегда можем перейти от одного представления к другому: ```js @@ -205,7 +231,11 @@ alert(uint8array[1]); // 1 ## DataView +<<<<<<< HEAD [DataView](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView) -- это специальное супергибкое нетипизированное представление данных из `ArrayBuffer`. Оно позволяет обращаться к данным на любой позиции и в любом формате. +======= +[DataView](mdn:/JavaScript/Reference/Global_Objects/DataView) is a special super-flexible "untyped" view over `ArrayBuffer`. It allows to access the data on any offset in any format. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 - В случае типизированных массивов конструктор строго задаёт формат данных. Весь массив состоит из однотипных значений. Доступ к i-ому элементу можно получить как `arr[i]`. - В случае `DataView` доступ к данным осуществляется посредством методов типа `.getUint8(i)` или `.getUint16(i)`. Мы выбираем формат данных в момент обращения к ним, а не в момент их создания. @@ -231,8 +261,13 @@ let dataView = new DataView(buffer); // получим 8-битное число на позиции 0 alert( dataView.getUint8(0) ); // 255 +<<<<<<< HEAD // а сейчас мы получим 16-битное число на той же позиции 0, оно состоит из 2-х байт, вместе составляющих число 65535 alert( dataView.getUint16(0) ); // 65535 (максимальное 16-битное беззнаковое целое) +======= +// now get 16-bit number at offset 0, it consists of 2 bytes, together interpreted as 65535 +alert( dataView.getUint16(0) ); // 65535 (biggest 16-bit unsigned int) +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 // получим 32-битное число на позиции 0 alert( dataView.getUint32(0) ); // 4294967295 (максимальное 32-битное беззнаковое целое) @@ -240,7 +275,11 @@ alert( dataView.getUint32(0) ); // 4294967295 (максимальное 32-би dataView.setUint32(0, 0); // при установке 4-байтового числа в 0, во все его 4 байта будут записаны нули ``` +<<<<<<< HEAD Представление `DataView` отлично подходит, когда мы храним данные разного формата в одном буфере. Например, мы храним последовательность пар, первое значение пары 16-битное целое, а второе -- 32-битное с плавающей точкой. `DataView` позволяет легко получить доступ к обоим. +======= +`DataView` is great when we store mixed-format data in the same buffer. For example, when we store a sequence of pairs (16-bit integer, 32-bit float), `DataView` allows to access them easily. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## Итого @@ -255,7 +294,11 @@ dataView.setUint32(0, 0); // при установке 4-байтового чи - `Float32Array`, `Float64Array` -- для 32- и 64-битных знаковых чисел с плавающей точкой. - Или `DataView` -- представление, использующее отдельные методы, чтобы уточнить формат данных при обращении, например, `getUint8(offset)`. +<<<<<<< HEAD Обычно мы создаём и работаем с типизированными массивами, оставляя `ArrayBuffer` "под капотом". Но мы можем в любой момент получить к нему доступ с помощью `.buffer` и при необходимости создать другое представление. +======= +In most cases we create and operate directly on typed arrays, leaving `ArrayBuffer` under cover, as a "common denominator". We can access it as `.buffer` and make another view if needed. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Существуют ещё 2 дополнительных термина, которые используются в описаниях методов, работающих с бинарными данными: - `ArrayBufferView` -- это общее название для представлений всех типов. diff --git a/4-binary/02-text-decoder/article.md b/4-binary/02-text-decoder/article.md index e537804dda..cf71be4d2b 100644 --- a/4-binary/02-text-decoder/article.md +++ b/4-binary/02-text-decoder/article.md @@ -2,17 +2,28 @@ Что если бинарные данные фактически являются строкой? Например, мы получили файл с текстовыми данными. +<<<<<<< HEAD Встроенный объект [TextDecoder](https://encoding.spec.whatwg.org/#interface-textdecoder) позволяет декодировать данные из бинарного буфера в обычную строку. +======= +The built-in [TextDecoder](https://encoding.spec.whatwg.org/#interface-textdecoder) object allows one to read the value into an actual JavaScript string, given the buffer and the encoding. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Для этого прежде всего нам нужно создать сам декодер: ```js let decoder = new TextDecoder([label], [options]); ``` +<<<<<<< HEAD - **`label`** -- тип кодировки, `utf-8` используется по умолчанию, но также поддерживаются `big5`, `windows-1251` и многие другие. - **`options`** -- объект с дополнительными настройками: - **`fatal`** -- boolean, если значение `true`, тогда генерируется ошибка для невалидных (не декодируемых) символов, в ином случае (по умолчанию) они заменяются символом `\uFFFD`. - **`ignoreBOM`** -- boolean, если значение `true`, тогда игнорируется BOM (дополнительный признак, определяющий порядок следования байтов), что необходимо крайне редко. +======= +- **`label`** -- the encoding, `utf-8` by default, but `big5`, `windows-1251` and many other are also supported. +- **`options`** -- optional object: + - **`fatal`** -- boolean, if `true` then throw an exception for invalid (non-decodable) characters, otherwise (default) replace them with character `\uFFFD`. + - **`ignoreBOM`** -- boolean, if `true` then ignore BOM (an optional byte-order Unicode mark), rarely needed. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ...и после использовать его метод decode: @@ -59,7 +70,7 @@ alert( new TextDecoder().decode(binaryString) ); // Hello Имеет следующий синтаксис: -```js run +```js let encoder = new TextEncoder(); ``` diff --git a/4-binary/03-blob/article.md b/4-binary/03-blob/article.md index eaf65164cf..3ac95c6fda 100644 --- a/4-binary/03-blob/article.md +++ b/4-binary/03-blob/article.md @@ -57,7 +57,11 @@ blob.slice([byteStart], [byteEnd], [contentType]); ## Blob как URL +<<<<<<< HEAD Blob может быть использован как URL для ``, `` или других тегов, для показа содержимого. +======= +A Blob can be easily used as a URL for ``, `` or other tags, to show its contents. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Давайте начнём с простого примера. При клике на ссылку мы загружаем динамически генерируемый Blob с `hello world` содержимым как файл: @@ -74,7 +78,11 @@ link.href = URL.createObjectURL(blob); Мы также можем создать ссылку динамически, используя только JavaScript, и эмулировать на ней клик, используя `link.click()`, тогда загрузка начнётся автоматически. +<<<<<<< HEAD Далее простой пример создания "на лету" и загрузки Blob-объекта, без использования HTML: +======= +Here's the similar code that causes user to download the dynamically created `Blob`, without any HTML: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let link = document.createElement('a'); @@ -97,11 +105,19 @@ URL.revokeObjectURL(link.href); blob:https://javascript.info/1e67e00e-860d-40a5-89ae-6ab0cbee6273 ``` +<<<<<<< HEAD Браузер для каждого URL, сгенерированного через `URL.createObjectURL`, сохраняет внутреннее соответствие URL -> `Blob`. Таким образом, такие URL короткие, но дают доступ к большому объекту `Blob`. Сгенерированный url действителен, только пока текущий документ открыт. Это позволяет ссылаться на сгенерированный в нём Blob в ``, `` или в любом другом объекте, где ожидается url в качестве одного из параметров. В данном случае возможен побочный эффект. Пока в карте соответствия существует ссылка на Blob, он находится в памяти. Браузер не может освободить память, занятую Blob-объектом. +======= +For each URL generated by `URL.createObjectURL` the browser stores a URL -> `Blob` mapping internally. So such URLs are short, but allow to access the `Blob`. + +A generated URL (and hence the link with it) is only valid within the current document, while it's open. And it allows to reference the `Blob` in ``, ``, basically any other object that expects a URL. + +There's a side effect though. While there's a mapping for a `Blob`, the `Blob` itself resides in the memory. The browser can't free it. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Ссылка в карте соответствия автоматически удаляется при выгрузке документа, после этого также освобождается память. Но если приложение имеет длительный жизненный цикл, это может произойти не скоро. Таким образом, если мы создадим URL для Blob, он будет висеть в памяти, даже если в нём нет больше необходимости. @@ -117,7 +133,11 @@ blob:https://javascript.info/1e67e00e-860d-40a5-89ae-6ab0cbee6273 Эта кодировка представляет двоичные данные в виде строки с безопасными для чтения символами в ASCII-кодах от 0 до 64. И что более важно -- мы можем использовать эту кодировку для "data-urls". +<<<<<<< HEAD [data url](https://developer.mozilla.org/ru/docs/Web/HTTP/Basics_of_HTTP/Data_URIs) имеет форму `data:[][;base64],`. Мы можем использовать такой url где угодно наряду с "обычным" url. +======= +A [data url](mdn:/http/Data_URIs) has the form `data:[][;base64],`. We can use such urls everywhere, on par with "regular" urls. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Например, смайлик: @@ -149,7 +169,11 @@ reader.onload = function() { }; ``` +<<<<<<< HEAD Оба варианта могут быть использованы для создания URL с Blob. Но обычно `URL.createObjectURL(blob)` является более быстрым и безопасным. +======= +Both ways of making a URL of a `Blob` are usable. But usually `URL.createObjectURL(blob)` is simpler and faster. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```compare title-plus="URL.createObjectURL(blob)" title-minus="Blob to data url" + Нужно отзывать объект для освобождения памяти. @@ -164,8 +188,13 @@ reader.onload = function() { Операции с изображениями выполняются через элемент ``: +<<<<<<< HEAD 1. Для отрисовки изображения (или его части) на холсте (canvas) используется [canvas.drawImage](https://developer.mozilla.org/ru/docs/Web/API/CanvasRenderingContext2D/drawImage). 2. Вызов canvas-метода [.toBlob(callback, format, quality)](https://developer.mozilla.org/ru/docs/Web/API/HTMLCanvasElement/toBlob) создаёт Blob и вызывает функцию `callback` при завершении. +======= +1. Draw an image (or its part) on canvas using [canvas.drawImage](mdn:/api/CanvasRenderingContext2D/drawImage). +2. Call canvas method [.toBlob(callback, format, quality)](mdn:/api/HTMLCanvasElement/toBlob) that creates a `Blob` and runs `callback` with it when done. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 В примере ниже изображение просто копируется, но мы можем взять его часть или трансформировать его на canvas перед созданием Blob: @@ -184,7 +213,11 @@ let context = canvas.getContext('2d'); context.drawImage(img, 0, 0); // мы можем вращать изображение при помощи context.rotate() и делать множество других преобразований +<<<<<<< HEAD // toBlob является асинхронной операцией, для которой callback-функция вызывается при завершении +======= +// toBlob is async operation, callback is called when done +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 canvas.toBlob(function(blob) { // после того, как Blob создан, загружаем его let link = document.createElement('a'); @@ -209,21 +242,52 @@ let blob = await new Promise(resolve => canvasElem.toBlob(resolve, 'image/png')) Конструктор `Blob` позволяет создать Blob-объект практически из чего угодно, включая `BufferSource`. +<<<<<<< HEAD Но если нам нужна производительная низкоуровневая обработка, мы можем использовать `ArrayBuffer` из `FileReader`: ```js // получаем arrayBuffer из Blob let fileReader = new FileReader(); +======= +But if we need to perform low-level processing, we can get the lowest-level `ArrayBuffer` from `blob.arrayBuffer()`: -*!* -fileReader.readAsArrayBuffer(blob); -*/!* +```js +// get arrayBuffer from blob +const bufferPromise = await blob.arrayBuffer(); +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 -fileReader.onload = function(event) { - let arrayBuffer = fileReader.result; -}; +// or +blob.arrayBuffer().then(buffer => /* process the ArrayBuffer */); ``` +## From Blob to stream + +When we read and write to a blob of more than `2 GB`, the use of `arrayBuffer` becomes more memory intensive for us. At this point, we can directly convert the blob to a stream. + +A stream is a special object that allows to read from it (or write into it) portion by portion. It's outside of our scope here, but here's an example, and you can read more at . Streams are convenient for data that is suitable for processing piece-by-piece. + +The `Blob` interface's `stream()` method returns a `ReadableStream` which upon reading returns the data contained within the `Blob`. + +Then we can read from it, like this: + +```js +// get readableStream from blob +const readableStream = blob.stream(); +const stream = readableStream.getReader(); + +while (true) { + // for each iteration: value is the next blob fragment + let { done, value } = await stream.read(); + if (done) { + // no more data in the stream + console.log('all blob processed.'); + break; + } + + // do something with the data portion we've just read from the blob + console.log(value); +} +``` ## Итого @@ -233,8 +297,17 @@ fileReader.onload = function(event) { Методы, которые выполняют сетевые запросы, такие как [XMLHttpRequest](info:xmlhttprequest), [fetch](info:fetch) и подобные, могут изначально работать с `Blob` так же, как и с другими объектами, представляющими двоичные данные. +<<<<<<< HEAD Мы можем легко конвертировать `Blob` в низкоуровневые бинарные типы данных и обратно: - Мы можем создать Blob из типизированного массива, используя конструктор `new Blob(...)`. - Мы можем обратно создать `ArrayBuffer` из Blob, используя `FileReader`, а затем создать его представление для низкоуровневых операций. +======= +We can easily convert between `Blob` and low-level binary data types: + +- We can make a `Blob` from a typed array using `new Blob(...)` constructor. +- We can get back `ArrayBuffer` from a Blob using `blob.arrayBuffer()`, and then create a view over it for low-level binary processing. + +Conversion streams are very useful when we need to handle large blob. You can easily create a `ReadableStream` from a blob. The `Blob` interface's `stream()` method returns a `ReadableStream` which upon reading returns the data contained within the blob. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/4-binary/04-file/article.md b/4-binary/04-file/article.md index f88d9b2e6e..8b638d5723 100644 --- a/4-binary/04-file/article.md +++ b/4-binary/04-file/article.md @@ -65,6 +65,7 @@ let reader = new FileReader(); // без аргументов - `readAsText` - для текстовых файлов, когда мы хотим получить строку. - `readAsDataURL` -- когда мы хотим использовать данные в `src` для `img` или другого тега. Есть альтернатива - можно не читать файл, а вызвать `URL.createObjectURL(file)`, детали в главе . +<<<<<<< HEAD В процессе чтения происходят следующие события: - `loadstart` -- чтение начато. - `progress` -- срабатывает во время чтения данных. @@ -72,6 +73,11 @@ let reader = new FileReader(); // без аргументов - `abort` -- вызван `abort()`. - `error` -- произошла ошибка. - `loadend` -- чтение завершено (успешно или нет). +======= +- `readAsArrayBuffer` -- for binary files, to do low-level binary operations. For high-level operations, like slicing, `File` inherits from `Blob`, so we can call them directly, without reading. +- `readAsText` -- for text files, when we'd like to get a string. +- `readAsDataURL` -- when we'd like to use this data in `src` for `img` or another tag. There's an alternative to reading a file for that, as discussed in chapter : `URL.createObjectURL(file)`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Когда чтение закончено, мы сможем получить доступ к его результату следующим образом: - `reader.result` результат чтения (если оно успешно) diff --git a/5-network/01-fetch/01-fetch-users/solution.md b/5-network/01-fetch/01-fetch-users/solution.md index 20f4081249..0bb492e953 100644 --- a/5-network/01-fetch/01-fetch-users/solution.md +++ b/5-network/01-fetch/01-fetch-users/solution.md @@ -3,7 +3,11 @@ Если ответ приходит cо статусом `200`, то вызываем метод `.json()`, чтобы прочитать JS-объект. +<<<<<<< HEAD А если запрос завершается ошибкой или код статуса в ответе отличен от 200, то мы просто возвращаем `null` в массиве результатов. +======= +Otherwise, if a `fetch` fails, or the response has non-200 status, we just return `null` in the resulting array. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Вот код: diff --git a/5-network/01-fetch/article.md b/5-network/01-fetch/article.md index 87ff820dcc..fbccf41b45 100644 --- a/5-network/01-fetch/article.md +++ b/5-network/01-fetch/article.md @@ -1,7 +1,11 @@ # Fetch +<<<<<<< HEAD JavaScript может отправлять сетевые запросы на сервер и подгружать новую информацию по мере необходимости. +======= +JavaScript can send network requests to the server and load new information whenever it's needed. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Например, мы можем использовать сетевой запрос, чтобы: - Отправить заказ, @@ -24,7 +28,11 @@ let promise = fetch(url, [options]) - **`url`** -- URL для отправки запроса. - **`options`** -- дополнительные параметры: метод, заголовки и так далее. +<<<<<<< HEAD Без `options` это простой GET-запрос, скачивающий содержимое по адресу `url`. +======= +Without `options`, this is a simple GET request, downloading the contents of the `url`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Браузер сразу же начинает запрос и возвращает промис, который внешний код использует для получения результата. @@ -58,12 +66,21 @@ if (response.ok) { // если HTTP-статус в диапазоне 200-299 `Response` предоставляет несколько методов, основанных на промисах, для доступа к телу ответа в различных форматах: +<<<<<<< HEAD - **`response.text()`** -- читает ответ и возвращает как обычный текст, - **`response.json()`** -- декодирует ответ в формате JSON, - **`response.formData()`** -- возвращает ответ как объект `FormData` (разберём его в [следующей главе](info:formdata)), - **`response.blob()`** -- возвращает объект как [Blob](info:blob) (бинарные данные с типом), - **`response.arrayBuffer()`** -- возвращает ответ как [ArrayBuffer](info:arraybuffer-binary-arrays) (низкоуровневое представление бинарных данных), - помимо этого, `response.body` - это объект [ReadableStream](https://streams.spec.whatwg.org/#rs-class), с помощью которого можно считывать тело запроса по частям. Мы рассмотрим и такой пример несколько позже. +======= +- **`response.text()`** -- read the response and return as text, +- **`response.json()`** -- parse the response as JSON, +- **`response.formData()`** -- return the response as `FormData` object (explained in the [next chapter](info:formdata)), +- **`response.blob()`** -- return the response as [Blob](info:blob) (binary data with type), +- **`response.arrayBuffer()`** -- return the response as [ArrayBuffer](info:arraybuffer-binary-arrays) (low-level representation of binary data), +- additionally, `response.body` is a [ReadableStream](https://streams.spec.whatwg.org/#rs-class) object, it allows you to read the body chunk-by-chunk, we'll see an example later. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Например, получим JSON-объект с последними коммитами из репозитория на GitHub: @@ -86,7 +103,11 @@ fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commi .then(commits => alert(commits[0].author.login)); ``` +<<<<<<< HEAD Для получения ответа в виде текста используем `await response.text()` вместо `.json()`: +======= +To get the response text, `await response.text()` instead of `.json()`: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run async let response = await fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits'); @@ -125,8 +146,14 @@ setTimeout(() => { // прячем через три секунды Если мы уже получили ответ с `response.text()`, тогда `response.json()` не сработает, так как данные уже были обработаны. ```js +<<<<<<< HEAD let text = await response.text(); // тело ответа обработано let parsed = await response.json(); // ошибка (данные уже были обработаны) +======= +let text = await response.text(); // response body consumed +let parsed = await response.json(); // fails (already consumed) +``` +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```` ## Заголовки ответа @@ -188,12 +215,21 @@ let response = fetch(protectedUrl, { Для отправки `POST`-запроса или запроса с другим методом, нам необходимо использовать `fetch` параметры: +<<<<<<< HEAD - **`method`** -- HTTP метод, например `POST`, - **`body`** -- тело запроса, одно из списка: - строка (например, в формате JSON), - объект `FormData` для отправки данных как `form/multipart`, - `Blob`/`BufferSource` для отправки бинарных данных, - [URLSearchParams](info:url) для отправки данных в кодировке `x-www-form-urlencoded`, используется редко. +======= +- **`method`** -- HTTP-method, e.g. `POST`, +- **`body`** -- the request body, one of: + - a string (e.g. JSON-encoded), + - `FormData` object, to submit the data as `multipart/form-data`, + - `Blob`/`BufferSource` to send binary data, + - [URLSearchParams](info:url), to submit the data in `x-www-form-urlencoded` encoding, rarely used. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Чаще всего используется JSON. @@ -227,7 +263,11 @@ alert(result.message); Мы можем отправить бинарные данные при помощи `fetch`, используя объекты `Blob` или `BufferSource`. +<<<<<<< HEAD В этом примере есть элемент ``, на котором мы можем рисовать движением мыши. При нажатии на кнопку "Отправить" изображение отправляется на сервер: +======= +In this example, there's a `` where we can draw by moving a mouse over it. A click on the "submit" button sends the image to the server: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```html run autorun height="90" @@ -292,6 +332,7 @@ fetch(url, options) .then(result => /* обрабатываем результат */) ``` +<<<<<<< HEAD Параметры ответа: - `response.status` -- HTTP-код ответа, - `response.ok` -- `true`, если статус ответа в диапазоне 200-299. @@ -303,6 +344,19 @@ fetch(url, options) - **`response.formData()`** -- возвращает ответ как объект FormData (кодировка form/multipart, см. следующую главу), - **`response.blob()`** -- возвращает объект как [Blob](info:blob) (бинарные данные с типом), - **`response.arrayBuffer()`** -- возвращает ответ как [ArrayBuffer](info:arraybuffer-binary-arrays) (низкоуровневые бинарные данные), +======= +Response properties: +- `response.status` -- HTTP code of the response, +- `response.ok` -- `true` if the status is 200-299. +- `response.headers` -- Map-like object with HTTP headers. + +Methods to get response body: +- **`response.text()`** -- return the response as text, +- **`response.json()`** -- parse the response as JSON object, +- **`response.formData()`** -- return the response as `FormData` object (`multipart/form-data` encoding, see the next chapter), +- **`response.blob()`** -- return the response as [Blob](info:blob) (binary data with type), +- **`response.arrayBuffer()`** -- return the response as [ArrayBuffer](info:arraybuffer-binary-arrays) (low-level binary data), +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Опции `fetch`, которые мы изучили на данный момент: - `method` -- HTTP-метод, diff --git a/5-network/02-formdata/article.md b/5-network/02-formdata/article.md index e7e2f3c18c..80612e5819 100644 --- a/5-network/02-formdata/article.md +++ b/5-network/02-formdata/article.md @@ -10,7 +10,11 @@ let formData = new FormData([form]); Если передать в конструктор элемент HTML-формы `form`, то создаваемый объект автоматически прочитает из неё поля. +<<<<<<< HEAD Его особенность заключается в том, что методы для работы с сетью, например `fetch`, позволяют указать объект `FormData` в свойстве тела запроса `body`. +======= +The special thing about `FormData` is that network methods, such as `fetch`, can accept a `FormData` object as a body. It's encoded and sent out with `Content-Type: multipart/form-data`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Он будет соответствующим образом закодирован и отправлен с заголовком `Content-Type: multipart/form-data`. @@ -47,21 +51,37 @@ let formData = new FormData([form]); ``` +<<<<<<< HEAD В этом примере серверный код не представлен, он за рамками этой статьи, он принимает POST-запрос с данными формы и отвечает сообщением "Пользователь сохранён". +======= +In this example, the server code is not presented, as it's beyond our scope. The server accepts the POST request and replies "User saved". +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## Методы объекта FormData С помощью указанных ниже методов мы можем изменять поля в объекте `FormData`: +<<<<<<< HEAD - `formData.append(name, value)` - добавляет к объекту поле с именем `name` и значением `value`, - `formData.append(name, blob, fileName)` - добавляет поле, как будто в форме имеется элемент ``, третий аргумент `fileName` устанавливает имя файла (не имя поля формы), как будто это имя из файловой системы пользователя, - `formData.delete(name)` - удаляет поле с заданным именем `name`, - `formData.get(name)` - получает значение поля с именем `name`, - `formData.has(name)` - если существует поле с именем `name`, то возвращает `true`, иначе `false` +======= +- `formData.append(name, value)` - add a form field with the given `name` and `value`, +- `formData.append(name, blob, fileName)` - add a field as if it were ``, the third argument `fileName` sets file name (not form field name), as it were a name of the file in user's filesystem, +- `formData.delete(name)` - remove the field with the given `name`, +- `formData.get(name)` - get the value of the field with the given `name`, +- `formData.has(name)` - if there exists a field with the given `name`, returns `true`, otherwise `false` +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Технически форма может иметь много полей с одним и тем же именем `name`, поэтому несколько вызовов `append` добавят несколько полей с одинаковыми именами. +<<<<<<< HEAD Ещё существует метод `set`, его синтаксис такой же, как у `append`. Разница в том, что `.set` удаляет все уже имеющиеся поля с именем `name` и только затем добавляет новое. То есть этот метод гарантирует, что будет существовать только одно поле с именем `name`, в остальном он аналогичен `.append`: +======= +There's also method `set`, with the same syntax as `append`. The difference is that `.set` removes all fields with the given `name`, and then appends a new field. So it makes sure there's only one field with such `name`, the rest is just like `append`: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 - `formData.set(name, value)`, - `formData.set(name, blob, fileName)`. @@ -75,13 +95,21 @@ formData.append('key2', 'value2'); // Список пар ключ/значение for(let [name, value] of formData) { +<<<<<<< HEAD alert(`${name} = ${value}`); // key1=value1, потом key2=value2 +======= + alert(`${name} = ${value}`); // key1 = value1, then key2 = value2 +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 } ``` ## Отправка формы с файлом +<<<<<<< HEAD Объекты `FormData` всегда отсылаются с заголовком `Content-Type: multipart/form-data`, этот способ кодировки позволяет отсылать файлы. Таким образом, поля `` тоже отправляются, как это и происходит в случае обычной формы. +======= +The form is always sent as `Content-Type: multipart/form-data`, this encoding allows to send files. So, `` fields are sent also, similar to a usual form submission. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Пример такой формы: @@ -168,7 +196,11 @@ formData.append("image", imageBlob, "image.png"); Объекты [FormData](https://xhr.spec.whatwg.org/#interface-formdata) используются, чтобы взять данные из HTML-формы и отправить их с помощью `fetch` или другого метода для работы с сетью. +<<<<<<< HEAD Мы можем создать такой объект уже с данными, передав в конструктор HTML-форму -- `new FormData(form)`, или же можно создать объект вообще без формы и затем добавить к нему поля с помощью методов: +======= +We can either create `new FormData(form)` from an HTML form, or create an object without a form at all, and then append fields with methods: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 - `formData.append(name, value)` - `formData.append(name, blob, fileName)` diff --git a/5-network/03-fetch-progress/article.md b/5-network/03-fetch-progress/article.md index 6a7f4f3620..b47486c587 100644 --- a/5-network/03-fetch-progress/article.md +++ b/5-network/03-fetch-progress/article.md @@ -5,11 +5,19 @@ Заметим, на данный момент в `fetch` нет способа отслеживать процесс *отправки*. Для этого используйте [XMLHttpRequest](info:xmlhttprequest), позже мы его рассмотрим. +<<<<<<< HEAD Чтобы отслеживать ход загрузки данных с сервера, можно использовать свойство `response.body`. Это `ReadableStream` ("поток для чтения") -- особый объект, который предоставляет тело ответа по частям, по мере поступления. Потоки для чтения описаны в спецификации [Streams API](https://streams.spec.whatwg.org/#rs-class). +======= +To track download progress, we can use `response.body` property. It's a `ReadableStream` -- a special object that provides body chunk-by-chunk, as it comes. Readable streams are described in the [Streams API](https://streams.spec.whatwg.org/#rs-class) specification. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 В отличие от `response.text()`, `response.json()` и других методов, `response.body` даёт полный контроль над процессом чтения, и мы можем подсчитать, сколько данных получено на каждый момент. +<<<<<<< HEAD Вот примерный код, который читает ответ из `response.body`: +======= +Here's the sketch of code that reads the response from `response.body`: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js // вместо response.json() и других методов @@ -107,6 +115,14 @@ alert(commits[0].author.login); let blob = new Blob(chunks); ``` +<<<<<<< HEAD В итоге у нас есть результат (строки или `Blob`, смотря что удобно) и отслеживание прогресса получения. На всякий случай повторимся, что здесь мы рассмотрели, как отслеживать процесс получения данных с сервера, а не их отправки на сервер. Для отслеживания отправки у `fetch` пока нет способа. +======= +At the end we have the result (as a string or a blob, whatever is convenient), and progress-tracking in the process. + +Once again, please note, that's not for *upload* progress (no way now with `fetch`), only for *download* progress. + +Also, if the size is unknown, we should check `receivedLength` in the loop and break it once it reaches a certain limit. So that the `chunks` won't overflow the memory. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/5-network/04-fetch-abort/article.md b/5-network/04-fetch-abort/article.md index f5b2b82a69..30330901ac 100644 --- a/5-network/04-fetch-abort/article.md +++ b/5-network/04-fetch-abort/article.md @@ -1,6 +1,7 @@ # Fetch: прерывание запроса +<<<<<<< HEAD Как мы знаем, метод `fetch` возвращает промис. А в JavaScript в целом нет понятия "отмены" промиса. Как же прервать запрос `fetch`? Для таких целей существует специальный встроенный объект: `AbortController`, который можно использовать для отмены не только `fetch`, но и других асинхронных задач. @@ -8,11 +9,19 @@ Использовать его достаточно просто: - Шаг 1: создаём контроллер: +======= +As we know, `fetch` returns a promise. And JavaScript generally has no concept of "aborting" a promise. So how can we cancel an ongoing `fetch`? E.g. if the user actions on our site indicate that the `fetch` isn't needed any more. - ```js - let controller = new AbortController(); - ``` +There's a special built-in object for such purposes: `AbortController`. It can be used to abort not only `fetch`, but other asynchronous tasks as well. +The usage is very straightforward: + +## The AbortController object +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 + +Create a controller: + +<<<<<<< HEAD Контроллер `controller` - чрезвычайно простой объект. - Он имеет единственный метод `abort()` и единственное свойство `signal`. @@ -23,39 +32,96 @@ Все, кто хочет узнать о вызове `abort()`, ставят обработчики на `controller.signal`, чтобы отслеживать его. Вот так (пока без `fetch`): +======= +```js +let controller = new AbortController(); +``` + +A controller is an extremely simple object. + +- It has a single method `abort()`, +- And a single property `signal` that allows to set event listeners on it. + +When `abort()` is called: +- `controller.signal` emits the `"abort"` event. +- `controller.signal.aborted` property becomes `true`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 - ```js run - let controller = new AbortController(); - let signal = controller.signal; +Generally, we have two parties in the process: +1. The one that performs a cancelable operation, it sets a listener on `controller.signal`. +2. The one that cancels: it calls `controller.abort()` when needed. +<<<<<<< HEAD // срабатывает при вызове controller.abort() signal.addEventListener('abort', () => alert("отмена!")); controller.abort(); // отмена! +======= +Here's the full example (without `fetch` yet): - alert(signal.aborted); // true - ``` +```js run +let controller = new AbortController(); +let signal = controller.signal; +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 + +// The party that performs a cancelable operation +// gets the "signal" object +// and sets the listener to trigger when controller.abort() is called +signal.addEventListener('abort', () => alert("abort!")); +<<<<<<< HEAD - Шаг 2: передайте свойство `signal` опцией в метод `fetch`: +======= +// The other party, that cancels (at any point later): +controller.abort(); // abort! +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 - ```js - let controller = new AbortController(); - fetch(url, { - signal: controller.signal - }); - ``` +// The event triggers and signal.aborted becomes true +alert(signal.aborted); // true +``` +<<<<<<< HEAD Метод `fetch` умеет работать с `AbortController`, он слушает событие `abort` на `signal`. - Шаг 3: чтобы прервать выполнение `fetch`, вызовите `controller.abort()`: +======= +As we can see, `AbortController` is just a mean to pass `abort` events when `abort()` is called on it. - ```js - controller.abort(); - ``` +We could implement the same kind of event listening in our code on our own, without the `AbortController` object. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 +But what's valuable is that `fetch` knows how to work with the `AbortController` object. It's integrated in it. + +<<<<<<< HEAD Вот и всё: `fetch` получает событие из `signal` и прерывает запрос. Когда `fetch` отменяется, его промис завершается с ошибкой `AbortError`, поэтому мы должны обработать её, например, в `try..catch`: +======= +## Using with fetch + +To be able to cancel `fetch`, pass the `signal` property of an `AbortController` as a `fetch` option: + +```js +let controller = new AbortController(); +fetch(url, { + signal: controller.signal +}); +``` + +The `fetch` method knows how to work with `AbortController`. It will listen to `abort` events on `signal`. + +Now, to abort, call `controller.abort()`: + +```js +controller.abort(); +``` + +We're done: `fetch` gets the event from `signal` and aborts the request. + +When a fetch is aborted, its promise rejects with an error `AbortError`, so we should handle it, e.g. in `try..catch`. + +Here's the full example with `fetch` aborted after 1 second: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run async // прервать через 1 секунду @@ -75,21 +141,31 @@ try { } ``` +<<<<<<< HEAD **`AbortController` - масштабируемый, он позволяет отменить несколько вызовов `fetch` одновременно.** Например, здесь мы запрашиваем много URL параллельно, и контроллер прерывает их все: +======= +## AbortController is scalable + +`AbortController` is scalable. It allows to cancel multiple fetches at once. + +Here's a sketch of code that fetches many `urls` in parallel, and uses a single controller to abort them all: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js let urls = [...]; // список URL для параллельных fetch let controller = new AbortController(); +// an array of fetch promises let fetchJobs = urls.map(url => fetch(url, { signal: controller.signal })); let results = await Promise.all(fetchJobs); +<<<<<<< HEAD // если откуда-то вызвать controller.abort(), // то это прервёт все вызовы fetch ``` @@ -97,6 +173,15 @@ let results = await Promise.all(fetchJobs); Если у нас есть собственные асинхронные задачи, отличные от `fetch`, мы можем использовать один `AbortController` для их остановки вместе с `fetch`. Нужно лишь слушать его событие `abort`: +======= +// if controller.abort() is called from anywhere, +// it aborts all fetches +``` + +If we have our own asynchronous tasks, different from `fetch`, we can use a single `AbortController` to stop those, together with fetches. + +We just need to listen to its `abort` event in our tasks: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js let urls = [...]; @@ -114,8 +199,20 @@ let fetchJobs = urls.map(url => fetch(url, { // запросы fetch // ожидать выполнения нашей задачи и всех запросов let results = await Promise.all([...fetchJobs, ourJob]); +<<<<<<< HEAD // вызов откуда-нибудь ещё: // controller.abort() прервёт все вызовы fetch и наши задачи ``` Так что `AbortController` существует не только для `fetch`, это универсальный объект для отмены асинхронных задач, в `fetch` встроена интеграция с ним. +======= +// if controller.abort() is called from anywhere, +// it aborts all fetches and ourJob +``` + +## Summary + +- `AbortController` is a simple object that generates an `abort` event on its `signal` property when the `abort()` method is called (and also sets `signal.aborted` to `true`). +- `fetch` integrates with it: we pass the `signal` property as the option, and then `fetch` listens to it, so it's possible to abort the `fetch`. +- We can use `AbortController` in our code. The "call `abort()`" -> "listen to `abort` event" interaction is simple and universal. We can use it even without `fetch`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/5-network/05-fetch-crossorigin/article.md b/5-network/05-fetch-crossorigin/article.md index 81c7a1d06a..67458fee9b 100644 --- a/5-network/05-fetch-crossorigin/article.md +++ b/5-network/05-fetch-crossorigin/article.md @@ -20,15 +20,25 @@ try { Эта политика называется "CORS": Cross-Origin Resource Sharing ("совместное использование ресурсов между разными источниками"). +<<<<<<< HEAD ## Зачем нужен CORS? Экскурс в историю CORS существует для защиты интернета от злых хакеров. +======= +## Why is CORS needed? A brief history + +CORS exists to protect the internet from evil hackers. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Серьёзно. Давайте сделаем краткое историческое отступление. **Многие годы скрипт с одного сайта не мог получить доступ к содержимому другого сайта.** +<<<<<<< HEAD Это простое, но могучее правило было основой интернет-безопасности. Например, хакерский скрипт с сайта `hacker.com` не мог получить доступ к почтовому ящику пользователя на сайте `gmail.com`. И люди чувствовали себя спокойно. +======= +That simple, yet powerful rule was a foundation of the internet security. E.g. an evil script from website `hacker.com` could not access the user's mailbox at website `gmail.com`. People felt safe. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 В то время в JavaScript не было методов для сетевых запросов. Это был "игрушечный" язык для украшения веб-страниц. @@ -44,7 +54,11 @@ CORS существует для защиты интернета от злых */!* +<<<<<<< HEAD +======= + +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 *!* */!* @@ -95,12 +109,19 @@ CORS существует для защиты интернета от злых Спустя некоторое время в браузерном JavaScript появились методы для сетевых запросов. +<<<<<<< HEAD Вначале запросы на другой источник были запрещены. Но в результате долгих дискуссий было решено разрешить их делать, но для использования новых возможностей требовать разрешение сервера, выраженное в специальных заголовках. ## Простые запросы +======= +At first, cross-origin requests were forbidden. But as a result of long discussions, cross-origin requests were allowed, but with any new capabilities requiring an explicit allowance by the server, expressed in special headers. + +## Safe requests +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Есть два вида запросов на другой источник: +<<<<<<< HEAD 1. Простые. 2. Все остальные. @@ -110,30 +131,63 @@ CORS существует для защиты интернета от злых 1. [Простой метод](https://www.w3.org/TR/cors/#simple-method): GET, POST или HEAD 2. [Простые заголовки](https://www.w3.org/TR/cors/#simple-header) -- разрешены только: +======= +1. Safe requests. +2. All the others. + +Safe Requests are simpler to make, so let's start with them. + +A request is safe if it satisfies two conditions: + +1. [Safe method](https://fetch.spec.whatwg.org/#cors-safelisted-method): GET, POST or HEAD +2. [Safe headers](https://fetch.spec.whatwg.org/#cors-safelisted-request-header) -- the only allowed custom headers are: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 - `Accept`, - `Accept-Language`, - `Content-Language`, - `Content-Type` со значением `application/x-www-form-urlencoded`, `multipart/form-data` или `text/plain`. +<<<<<<< HEAD Любой другой запрос считается "непростым". Например, запрос с методом `PUT` или с HTTP-заголовком `API-Key` не соответствует условиям. **Принципиальное отличие между ними состоит в том, что "простой запрос" может быть сделан через `` или ` + + + + +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/7-animation/2-css-animations/4-animate-circle-callback/task.md b/7-animation/2-css-animations/4-animate-circle-callback/task.md index 807b2b8b88..d5753f1f9b 100644 --- a/7-animation/2-css-animations/4-animate-circle-callback/task.md +++ b/7-animation/2-css-animations/4-animate-circle-callback/task.md @@ -1,3 +1,4 @@ +<<<<<<< HEAD # Анимация круга с помощью колбэка @@ -23,3 +24,30 @@ showCircle(150, 150, 100, div => { [iframe src="solution" height=260] За основу возьмите решение задачи . +======= + +# 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/7-animation/2-css-animations/article.md b/7-animation/2-css-animations/article.md index c49a42e704..c77ae702db 100644 --- a/7-animation/2-css-animations/article.md +++ b/7-animation/2-css-animations/article.md @@ -1,13 +1,23 @@ +<<<<<<< HEAD # CSS-анимации CSS позволяет создавать простые анимации без использования JavaScript. +======= +CSS animations make it possible to do simple animations without JavaScript at all. + +JavaScript can be used to control CSS animations and make them even better, with little code. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 JavaScript может быть использован для управления такими CSS-анимациями. Это позволяет делать более сложные анимации, используя небольшие кусочки кода. ## CSS-переходы [#css-transition] +<<<<<<< HEAD Идея CSS-переходов проста: мы указываем, что некоторое свойство должно быть анимировано, и как оно должно быть анимировано. А когда свойство меняется, браузер сам обработает это изменение и отрисует анимацию. +======= +That is, all we need is to change the property, and the fluid transition will be done by the browser. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Всё что нам нужно, чтобы начать анимацию - это изменить свойство, а дальше браузер сделает плавный переход сам. @@ -48,7 +58,11 @@ JavaScript может быть использован для управлени - `transition-timing-function` -- временная функция перехода - `transition-delay` -- задержка начала перехода +<<<<<<< HEAD Далее мы рассмотрим их все, а сейчас ещё заметим, что есть также общее свойство `transition`, которое позволяет задать их одновременно в последовательности: `property duration timing-function delay`, а также анимировать несколько свойств одновременно. +======= +We'll cover them in a moment, for now let's note that the common `transition` property allows declaring them together in the order: `property duration timing-function delay`, as well as animating multiple properties at once. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Например, у этой кнопки анимируются два свойства `color` и `font-size` одновременно: @@ -71,6 +85,7 @@ growing.onclick = function() { ``` +<<<<<<< HEAD Теперь рассмотрим каждое свойство анимации по отдельности. ## transition-property @@ -90,6 +105,27 @@ growing.onclick = function() { Отрицательные значения также допустимы. В таком случае анимация начнётся с середины. Например, если `transition-duration` равно `2s`, а `transition-delay` -- `-1s`, тогда анимация займёт одну секунду и начнётся с середины. Здесь приведён пример анимации, сдвигающей цифры от `0` до `9` с использованием CSS-свойства `transform` со значением `translate`: +======= +Now, let's cover animation properties one by one. + +## transition-property + +In `transition-property`, we write a list of properties to animate, for instance: `left`, `margin-left`, `height`, `color`. Or we could write `all`, which means "animate all properties". + +Do note that, there are properties which can not be animated. However, [most of the generally used properties are animatable](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_animated_properties). + +## transition-duration + +In `transition-duration` we can specify how long the animation should take. The time should be in [CSS time format](https://www.w3.org/TR/css3-values/#time): in seconds `s` or milliseconds `ms`. + +## transition-delay + +In `transition-delay` we can specify the delay *before* the animation. For instance, if `transition-delay` is `1s` and `transition-duration` is `2s`, then the animation starts 1 second after the property change and the total duration will be 2 seconds. + +Negative values are also possible. Then the animation is shown immediately, but the starting point of the animation will be after given value (time). For example, if `transition-delay` is `-1s` and `transition-duration` is `2s`, then animation starts from the halfway point and total duration will be 1 second. + +Here the animation shifts numbers from `0` to `9` using CSS `translate` property: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 [codetabs src="digits"] @@ -109,13 +145,21 @@ growing.onclick = function() { stripe.classList.add('animate'); ``` +<<<<<<< HEAD Можно начать анимацию "с середины", с определённого числа, например, используя отрицательное значение `transition-delay`, соответствующие необходимому числу. +======= +We could also start it from somewhere in the middle of the transition, from an exact number, e.g. corresponding to the current second, using a negative `transition-delay`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Если вы нажмёте на цифру ниже, то анимация начнётся с последней секунды: [codetabs src="digits-negative-delay"] +<<<<<<< HEAD JavaScript делает это с помощью нескольких строк кода: +======= +JavaScript does it with an extra line: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js stripe.onclick = function() { @@ -130,26 +174,49 @@ stripe.onclick = function() { ## transition-timing-function +<<<<<<< HEAD Временная функция описывает, как процесс анимации будет распределён во времени. Будет ли она начата медленно и затем ускорится или наоборот. На первый взгляд это очень сложное свойство, но оно становится понятным, если уделить ему немного времени. Это свойство может принимать два вида значений: кривую Безье или количество шагов. Давайте начнём с кривой Безье, как с наиболее часто используемой. +======= +The timing function describes how the animation process is distributed along its timeline. Will it start slowly and then go fast, or vice versa. + +It appears to be the most complicated property at first. But it becomes very simple if we devote a bit time to it. + +That property accepts two kinds of values: a Bezier curve or steps. Let's start with the curve, as it's used more often. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ### Кривая Безье +<<<<<<< HEAD Временная функция может быть задана, как [кривая Безье](/bezier-curve) с 4 контрольными точками, удовлетворяющими условиям: 1. Первая контрольная точка: `(0,0)`. 2. Последняя контрольная точка: `(1,1)`. 3. Для промежуточных точек значение `x` должно быть `0..1`, значение `y` может принимать любое значение. +======= +The timing function can be set as a [Bezier curve](/bezier-curve) with 4 control points that satisfy the conditions: + +1. First control point: `(0,0)`. +2. Last control point: `(1,1)`. +3. For intermediate points, the values of `x` must be in the interval `0..1`, `y` can be anything. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Синтаксис для кривых Безье в CSS: `cubic-bezier(x2, y2, x3, y3)`. Нам необходимо задать только вторую и третью контрольные точки, потому что первая зафиксирована со значением `(0,0)` и четвёртая - `(1,1)`. +<<<<<<< HEAD Временная функция описывает то, насколько быстро происходит анимации во времени. - Ось `x` - это время: `0` - начальный момент, `1` - последний момент `transition-duration`. - Ось `y` указывает на завершение процесса: `0` - начальное значение свойства, `1` - конечное значение. +======= +The timing function describes how fast the animation process goes. + +- The `x` axis is the time: `0` -- the start, `1` -- the end of `transition-duration`. +- The `y` axis specifies the completion of the process: `0` -- the starting value of the property, `1` -- the final value. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Самым простым примером анимации является равномерная анимация с линейной скоростью. Она может быть задана с помощью кривой `cubic-bezier(0, 0, 1, 1)`. @@ -169,7 +236,11 @@ stripe.onclick = function() { .train { left: 0; transition: left 5s cubic-bezier(0, 0, 1, 1); +<<<<<<< HEAD /* JavaScript устанавливает свойство left равным 450px */ +======= + /* click on a train sets left to 450px, thus triggering the animation */ +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 } ``` @@ -193,13 +264,21 @@ CSS: .train { left: 0; transition: left 5s cubic-bezier(0, .5, .5, 1); +<<<<<<< HEAD /* JavaScript устанавливает свойство left равным 450px */ +======= + /* click on a train sets left to 450px, thus triggering the animation */ +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 } ``` Есть несколько встроенных обозначений кривых Безье: `linear`, `ease`, `ease-in`, `ease-out` и `ease-in-out`. +<<<<<<< HEAD `linear` это короткая запись для `cubic-bezier(0, 0, 1, 1)` -- прямой линии, которую мы видели раньше. +======= +The `linear` is a shorthand for `cubic-bezier(0, 0, 1, 1)` -- a straight line, which we described above. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Другие названия -- это также сокращения для других `cubic-bezier`: @@ -210,29 +289,41 @@ CSS: `*` -- используется по умолчанию, если не задана другая временная функция. +<<<<<<< HEAD Для того, чтобы замедлить поезд, мы можем использовать `ease-out`: +======= +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```css .train { left: 0; transition: left 5s ease-out; - /* transition: left 5s cubic-bezier(0, .5, .5, 1); */ + /* same as transition: left 5s cubic-bezier(0, .5, .5, 1); */ } ``` Но получившийся результат немного отличается. +<<<<<<< HEAD **Кривая Безье может заставить анимацию "выпрыгивать" за пределы диапазона.** Контрольные точки могут иметь любые значения по оси `y`: отрицательные или сколь угодно большие. В таком случае кривая Безье будет скакать очень высоко или очень низко, заставляя анимацию выходить за её нормальные пределы. В приведённом ниже примере код анимации: +======= +**A Bezier curve can make the animation exceed its range.** + +The control points on the curve can have any `y` coordinates: even negative or huge ones. Then the Bezier curve would also extend very low or high, making the animation go beyond its normal range. + +In the example below the animation code is: + +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```css .train { left: 100px; transition: left 5s cubic-bezier(.5, -1, .5, 2); - /* JavaScript sets left to 400px */ + /* click on a train sets left to 450px */ } ``` @@ -246,6 +337,7 @@ CSS: [codetabs src="train-over"] +<<<<<<< HEAD Если мы взглянем на кривую Безье из примера, становится понятно поведение поезда. ![](bezier-train-over.svg) @@ -253,10 +345,31 @@ CSS: Мы вынесли координату `y` для первой опорной точки ниже нуля и выше единицы для третьей опорной точки, поэтому кривая вышла за пределы "обычного" квадрата. Значения `y` вышли из "стандартного" диапазона `0..1`. Как мы знаем, ось `y` измеряет "завершённость процесса анимации". Значение `y = 0` соответствует начальному значению анимируемого свойства и `y = 1` -- конечному значению. Таким образом, `y<0` делает значение свойства `left` меньше начального значения и `y>1` -- больше конечного. +======= +Why it happens is pretty obvious if we look at the graph of the given Bezier curve: + +![](bezier-train-over.svg) + +We moved the `y` coordinate of the 2nd point below zero, and for the 3rd point we made it over `1`, so the curve goes out of the "regular" quadrant. The `y` is out of the "standard" range `0..1`. + +As we know, `y` measures "the completion of the animation process". The value `y = 0` corresponds to the starting property value and `y = 1` -- the ending value. So values `y<0` move the property beyond the starting `left` and `y>1` -- past the final `left`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Это, конечно, "мягкий" вариант. Если значение `y` будут `-99` и `99`, то поезд будет гораздо сильнее "выпрыгивать" за пределы. +<<<<<<< HEAD Как сделать кривую Безье необходимую для конкретной задачи? Существует множество инструментов. +======= +But how do we make a Bezier curve for a specific task? There are many tools. + +- For instance, we can do it on the site . +- Browser developer tools also have special support for Bezier curves in CSS: + 1. Open the developer tools with `key:F12` (Mac: `key:Cmd+Opt+I`). + 2. Select the `Elements` tab, then pay attention to the `Styles` sub-panel at the right side. + 3. CSS properties with a word `cubic-bezier` will have an icon before this word. + 4. Click this icon to edit the curve. + +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 - К примеру, мы можем сделать это на сайте . - Браузернные инструменты разработчика также имеют специальную поддержку для создания кривых Безье в CSS: @@ -265,7 +378,11 @@ CSS: 3. Свойства CSS со словом `cubic-bezier` будут иметь иконку перед этим словом. 4. Кликните по иконке, чтобы отредактировать кривую. +<<<<<<< HEAD ### Шаги +======= +The timing function `steps(number of steps[, start/end])` allows splitting an transition into multiple steps. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Временная функция `steps(количество шагов[, start/end])` позволяет разделить анимацию на шаги. @@ -275,7 +392,23 @@ CSS: [codetabs src="step-list"] +<<<<<<< HEAD В HTML, вереница цифр заключена в `
        ` фиксированной длины: +======= +In the HTML, a stripe of digits is enclosed into a fixed-length `
        `: + +```html +
        +
        0123456789
        +
        +``` + +The `#digit` div has a fixed width and a border, so it looks like a red window. + +We'll make a timer: the digits will appear one by one, in a discrete way. + +To achieve that, we'll hide the `#stripe` outside of `#digit` using `overflow: hidden`, and then shift the `#stripe` to the left step-by-step. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```html
        @@ -298,6 +431,7 @@ Div-элемент `#digit` имеет фиксированную ширину } ``` +<<<<<<< HEAD Первый аргумент временной функции `steps(9, start)` -- количество шагов. Трансформация будет разделена на 9 частей (10% каждая). Временной интервал также будет разделён на 9 частей, таким образом свойство `transition: 9s` обеспечивает нам 9 секунд анимации, что даёт по одной секунде на цифру. Вторым аргументом является одно из ключевых слов: `start` или `end`. @@ -309,9 +443,13 @@ Div-элемент `#digit` имеет фиксированную ширину [codetabs src="step"] Щелчок по цифре немедленно изменяет её на `1` (первый шаг), а затем изменяется в начале следующей секунды. +======= +The first argument of `steps(9, start)` is the number of steps. The transform will be split into 9 parts (10% each). The time interval is automatically divided into 9 parts as well, so `transition: 9s` gives us 9 seconds for the whole animation – 1 second per digit. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Анимация будет происходить так: +<<<<<<< HEAD - `0s` -- `-10%` (первое изменение в начале первой секунды, сразу после нажатия) - `1s` -- `-20%` - ... @@ -326,32 +464,82 @@ Div-элемент `#digit` имеет фиксированную ширину - `0s` -- `0` - `1s` -- `-10%` (первое изменение произойдёт в конце первой секунды) +======= +The `start` means that in the beginning of animation we need to make the first step immediately. + +In action: + +[codetabs src="step"] + +A click on the digit changes it to `1` (the first step) immediately, and then changes in the beginning of the next second. + +The process is progressing like this: + +- `0s` -- `-10%` (first change in the beginning of the 1st second, immediately) +- `1s` -- `-20%` +- ... +- `8s` -- `-90%` +- (the last second shows the final value). + +Here, the first change was immediate because of `start` in the `steps`. + +The alternative value `end` would mean that the change should be applied not in the beginning, but at the end of each second. + +So the process for `steps(9, end)` would go like this: + +- `0s` -- `0` (during the first second nothing changes) +- `1s` -- `-10%` (first change at the end of the 1st second) +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 - `2s` -- `-20%` - ... - `9s` -- `-90%` +<<<<<<< HEAD Пример `step(9, end)` в действии (обратите внимание на паузу перед первым изменением цифры): [codetabs src="step-end"] Существуют также некоторые заранее определённые сокращения для `steps(...)`: +======= +Here's `steps(9, end)` in action (note the pause before the first digit change): + +[codetabs src="step-end"] + +There are also some pre-defined shorthands for `steps(...)`: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 - `step-start` -- то же самое, что `steps(1, start)`. Оно означает, что анимация начнётся сразу и произойдёт в один шаг. Таким образом она начнётся и завершится сразу, как будто и нет никакой анимации. - `step-end` -- то же самое, что `steps(1, end)`: выполнит анимацию за один шаг в конце `transition-duration`. +<<<<<<< HEAD Такие значения используются редко, потому что это не совсем анимация, а точнее будет сказать одношаговые изменения. Мы упоминаем их здесь для полноты картины. ## Событие: "transitionend" Когда завершается анимация, срабатывает событие `transitionend`. +======= +These values are rarely used, as they represent not a real animation, but rather a single-step change. We mention them here for completeness. + +## Event: "transitionend" + +When the CSS animation finishes, the `transitionend` event triggers. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Оно широко используется для выполнения действий после завершения анимации, а также для создания последовательности анимаций. +<<<<<<< HEAD Например, корабль в приведённом ниже примере начинает плавать туда и обратно по клику, каждый раз все дальше и дальше вправо: [iframe src="boat" height=300 edit link] Анимация начинается с помощью функции `go`, которая вызывается каждый раз снова, когда переход заканчивается и меняется направление: +======= +For instance, the ship in the example below starts to sail there and back when clicked, each time farther and farther to the right: + +[iframe src="boat" height=300 edit link] + +The animation is initiated by the function `go` that re-runs each time the transition finishes, and flips the direction: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js boat.onclick = function() { @@ -360,11 +548,19 @@ boat.onclick = function() { function go() { if (times % 2) { +<<<<<<< HEAD // плыть вправо boat.classList.remove('back'); boat.style.marginLeft = 100 * times + 200 + 'px'; } else { // плыть влево +======= + // sail to the right + boat.classList.remove('back'); + boat.style.marginLeft = 100 * times + 200 + 'px'; + } else { + // sail to the left +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 boat.classList.add('back'); boat.style.marginLeft = 100 * times - 200 + 'px'; } @@ -380,7 +576,11 @@ boat.onclick = function() { }; ``` +<<<<<<< HEAD Объект события `transitionend` содержит ряд полезных свойств: +======= +The event object for `transitionend` has a few specific properties: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 `event.propertyName` : Имя свойства, анимация которого завершилась. Может быть полезным, если мы анимируем несколько свойств. @@ -392,7 +592,11 @@ boat.onclick = function() { Мы можем объединить несколько простых анимаций вместе, используя CSS-правило `@keyframes`. +<<<<<<< HEAD Оно определяет "имя" анимации и правила: что, когда и где анимировать. После этого можно использовать свойство `animation`, чтобы назначить анимацию на элемент и определить её дополнительные параметры. +======= +It specifies the "name" of the animation and rules - what, when and where to animate. Then using the `animation` property, we can attach the animation to the element and specify additional parameters for it. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Ниже приведён пример с пояснениями: @@ -428,16 +632,106 @@ boat.onclick = function() { Существует множество статей про `@keyframes`, а также [детальная спецификация](https://drafts.csswg.org/css-animations/). +<<<<<<< HEAD Скорее всего, вам нечасто понадобится `@keyframes`, разве что на вашем сайте все постоянно в движении. +======= +You probably won't need `@keyframes` often, unless everything is in constant motion on your sites. + +## Performance + +Most CSS properties can be animated, because most of them are numeric values. For instance, `width`, `color`, `font-size` are all numbers. When you animate them, the browser gradually changes these numbers frame by frame, creating a smooth effect. + +However, not all animations will look as smooth as you'd like, because different CSS properties cost differently to change. + +In more technical details, when there's a style change, the browser goes through 3 steps to render the new look: + +1. **Layout**: re-compute the geometry and position of each element, then +2. **Paint**: re-compute how everything should look like at their places, including background, colors, +3. **Composite**: render the final results into pixels on screen, apply CSS transforms if they exist. + +During a CSS animation, this process repeats every frame. However, CSS properties that never affect geometry or position, such as `color`, may skip the Layout step. If a `color` changes, the browser doesn't calculate any new geometry, it goes to Paint -> Composite. And there are few properties that directly go to Composite. You can find a longer list of CSS properties and which stages they trigger at . + +The calculations may take time, especially on pages with many elements and a complex layout. And the delays are actually visible on most devices, leading to "jittery", less fluid animations. + +Animations of properties that skip the Layout step are faster. It's even better if Paint is skipped too. + +The `transform` property is a great choice, because: +- CSS transforms affect the target element box as a whole (rotate, flip, stretch, shift it). +- CSS transforms never affect neighbour elements. + +...So browsers apply `transform` "on top" of existing Layout and Paint calculations, in the Composite stage. + +In other words, the browser calculates the Layout (sizes, positions), paints it with colors, backgrounds, etc at the Paint stage, and then applies `transform` to element boxes that need it. + +Changes (animations) of the `transform` property never trigger Layout and Paint steps. More than that, the browser leverages the graphics accelerator (a special chip on the CPU or graphics card) for CSS transforms, thus making them very efficient. + +Luckily, the `transform` property is very powerful. By using `transform` on an element, you could rotate and flip it, stretch and shrink it, move it around, and [much more](https://developer.mozilla.org/docs/Web/CSS/transform#syntax). So instead of `left/margin-left` properties we can use `transform: translateX(…)`, use `transform: scale` for increasing element size, etc. + +The `opacity` property also never triggers Layout (also skips Paint in Mozilla Gecko). We can use it for show/hide or fade-in/fade-out effects. + +Paring `transform` with `opacity` can usually solve most of our needs, providing fluid, good-looking animations. + +For example, here clicking on the `#boat` element adds the class with `transform: translateX(300px)` and `opacity: 0`, thus making it move `300px` to the right and disappear: + +```html run height=260 autorun no-beautify + + + + +``` + +Here's a more complex example, with `@keyframes`: + +```html run height=80 autorun no-beautify +

        click me to start / stop

        + +``` +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## Итого +<<<<<<< HEAD CSS-анимации позволяют плавно, или не очень, менять одно или несколько свойств. +======= +CSS animations allow smoothly (or step-by-step) animated changes of one or multiple CSS properties. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Они хорошо решают большинство задач по анимации. Также мы можем реализовать анимации через JavaScript, более подробно об этом - в следующей главе. Ограничения CSS-анимаций в сравнении с JavaScript-анимациями: +<<<<<<< HEAD ```compare plus="CSS-анимации" minus="JavaScript-анимации" + Простые анимации делаются просто. + Быстрые и не создают нагрузку на CPU. @@ -446,5 +740,17 @@ CSS-анимации позволяют плавно, или не очень, м ``` Большинство анимаций может быть реализовано с использованием CSS, как описано в этой главе. А событие `transitionend` позволяет запускать JavaScript после анимации, поэтому CSS-анимации прекрасно интегрируются с кодом. +======= +```compare plus="CSS animations" minus="JavaScript animations" ++ Simple things done simply. ++ Fast and lightweight for CPU. +- JavaScript animations are flexible. They can implement any animation logic, like an "explosion" of an element. +- Not just property changes. We can create new elements in JavaScript as part of the animation. +``` + +In early examples in this chapter, we animate `font-size`, `left`, `width`, `height`, etc. In real life projects, we should use `transform: scale()` and `transform: translate()` for better performance. + +The majority of animations can be implemented using CSS as described in this chapter. And the `transitionend` event allows JavaScript to be run after the animation, so it integrates fine with the code. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Но в следующей главе мы рассмотрим некоторые JavaScript-анимации, которые позволяют решать более сложные задачи. diff --git a/7-animation/3-js-animation/1-animate-ball/solution.md b/7-animation/3-js-animation/1-animate-ball/solution.md index 4336a41bd4..bbfb2d65b8 100644 --- a/7-animation/3-js-animation/1-animate-ball/solution.md +++ b/7-animation/3-js-animation/1-animate-ball/solution.md @@ -2,7 +2,11 @@ Нижняя координата поля -- `field.clientHeight`. CSS-свойство `top` относится к верхней границе мяча, которая должна идти от 0 до `field.clientHeight - ball.clientHeight`. +<<<<<<< HEAD А чтобы получить эффект "скачущего" мяча, мы можем использовать функцию расчёта времени `bounce` в режиме `easeOut`. +======= +To get the "bouncing" effect we can use the timing function `bounce` in `easeOut` mode. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Вот конечный код для анимации: diff --git a/7-animation/3-js-animation/article.md b/7-animation/3-js-animation/article.md index a8afe2c108..091afecf16 100644 --- a/7-animation/3-js-animation/article.md +++ b/7-animation/3-js-animation/article.md @@ -77,9 +77,15 @@ setInterval(animate3, 20); Эти независимые перерисовки лучше сгруппировать вместе, тогда они будут легче для браузера, а значит - не грузить процессор и более плавно выглядеть. +<<<<<<< HEAD Существует ещё одна вещь, про которую надо помнить: когда CPU перегружен или есть другие причины делать перерисовку реже (например, когда вкладка браузера скрыта), нам не следует делать её каждые `20ms`. Но как нам узнать об этом в JavaScript? Спецификация [Animation timing](https://www.w3.org/TR/animation-timing/) описывает функцию `requestAnimationFrame`, которая решает все описанные проблемы и делает даже больше. +======= +There's one more thing to keep in mind. Sometimes CPU is overloaded, or there are other reasons to redraw less often (like when the browser tab is hidden), so we really shouldn't run it every `20ms`. + +But how do we know about that in JavaScript? There's a specification [Animation timing](https://www.w3.org/TR/animation-timing/) that provides the function `requestAnimationFrame`. It addresses all these issues and even more. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Синтаксис: ```js @@ -96,7 +102,11 @@ let requestId = requestAnimationFrame(callback) cancelAnimationFrame(requestId); ``` +<<<<<<< HEAD Функция `callback` имеет один аргумент -- время прошедшее с момента начала загрузки страницы в миллисекундах. Это значение может быть получено с помощью вызова [performance.now()](mdn:api/Performance/now). +======= +The `callback` gets one argument -- the time passed from the beginning of the page load in milliseconds. This time can also be obtained by calling [performance.now()](mdn:api/Performance/now). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Как правило, `callback` запускается очень скоро, если только не перегружен CPU или не разряжена батарея ноутбука, или у браузера нет какой-то ещё причины замедлиться. @@ -159,7 +169,11 @@ function animate({timing, draw, duration}) { } ``` +<<<<<<< HEAD График функции: +======= + Its graph: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ![](linear.svg) Это как если бы в `transition-timing-function` передать значение `linear`. Ниже будут представлены более интересные примеры. @@ -227,7 +241,11 @@ function quad(timeFraction) { [iframe height=40 src="quad" link] +<<<<<<< HEAD ...Или кубическая кривая, или любой другой множитель `n`. Повышение степени увеличивает скорость анимации. +======= +...Or the cubic curve or even greater `n`. Increasing the power makes it speed up faster. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Вот график для функции `progress` в степени `5`: @@ -396,15 +414,25 @@ bounceEaseInOut = makeEaseInOut(bounce); ![](circ-ease.svg) +<<<<<<< HEAD - Красный обычный вариант `circ` (`easeIn`). - Зелёный -- `easeOut`. - Синий -- `easeInOut`. +======= +- Red is the regular variant of `circ` (`easeIn`). +- Green -- `easeOut`. +- Blue -- `easeInOut`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Как видно, график первой половины анимации представляет собой уменьшенный `easeIn`, а второй – уменьшенный `easeOut`. В результате, анимация начинается и заканчивается одинаковым эффектом. ## Более интересная функция "draw" +<<<<<<< HEAD Вместо передвижения элемента мы можем делать что-нибудь ещё. Всё, что нам нужно -- это правильно написать функцию `draw`. +======= +Instead of moving the element we can do something else. All we need is to write the proper `draw`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Вот пример "скачущей" анимации набирающегося текста: @@ -451,4 +479,8 @@ function animate({timing, draw, duration}) { JavaScript-анимации могут использовать любые функции расчёта времени. Мы рассмотрели множество примеров и их вариаций, чтобы сделать их ещё более универсальными. В отличие от CSS, мы здесь не ограничены только кривой Безье. +<<<<<<< HEAD То же самое и с `draw`: мы можем анимировать всё что угодно, не только CSS-свойства. +======= +The same is true about `draw`: we can animate anything, not just CSS properties. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/8-web-components/1-webcomponents-intro/article.md b/8-web-components/1-webcomponents-intro/article.md index d7869f4862..754ec78aa6 100644 --- a/8-web-components/1-webcomponents-intro/article.md +++ b/8-web-components/1-webcomponents-intro/article.md @@ -26,9 +26,15 @@ ...И эта штука летает, поддерживая жизни людей в космосе! +<<<<<<< HEAD Как создаются столь сложные устройства? Какие принципы мы могли бы позаимствовать, чтобы сделать нашу разработку такой же надёжной и масштабируемой? Или, по крайней мере, приблизиться к такому уровню. +======= +How are such complex devices created? + +Which principles could we borrow to make our development same-level reliable and scalable? Or, at least, close to it? +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## Компонентная архитектура @@ -56,15 +62,27 @@ Как мы определяем, что является компонентом? Это приходит из соображений здравого смысла, а также с интуицией и опытом. Обычно это объект, отделимый визуально, который мы можем описать с точки зрения того, что он делает и как он взаимодействует со страницей. В примере выше, страница содержит блоки, каждый из которых играет свою роль, и логично выделить их в компоненты. +<<<<<<< HEAD Компонент имеет: - свой собственный JavaScript-класс. - DOM-структура управляется исключительно своим классом, и внешний код не имеет к ней доступа (принцип "инкапсуляции"). - CSS-стили, применённые к компоненту. - API: события, методы класса и т.п., для взаимодействия с другими компонентами. +======= +A component has: +- Its own JavaScript class. +- DOM structure, managed solely by its class, outside code doesn't access it ("encapsulation" principle). +- CSS styles, applied to the component. +- API: events, class methods etc, to interact with other components. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Ещё раз заметим, в компонентном подходе как таковом нет ничего особенного. +<<<<<<< HEAD Существует множество фреймворков и методов разработки для их создания, каждый из которых со своими плюсами и минусами. Обычно особые CSS классы и соглашения используются для эмуляции компонентов -- области видимости CSS и инкапсуляция DOM. +======= +There exist many frameworks and development methodologies to build them, each with its own bells and whistles. Usually, special CSS classes and conventions are used to provide "component feel" -- CSS scoping and DOM encapsulation. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 "Веб-компоненты" предоставляют встроенные возможности браузера для этого, поэтому нам больше не нужно эмулировать их. diff --git a/8-web-components/2-custom-elements/article.md b/8-web-components/2-custom-elements/article.md index d625ba8ae4..ac47d0b55c 100644 --- a/8-web-components/2-custom-elements/article.md +++ b/8-web-components/2-custom-elements/article.md @@ -113,9 +113,15 @@ customElements.define("time-formatted", TimeFormatted); // (2) > ``` +<<<<<<< HEAD 1. Класс имеет только один метод `connectedCallback()` -- браузер вызывает его, когда элемент `` добавляется на страницу (или когда HTML-парсер обнаруживает его), и он использует встроенный форматировщик данных [Intl.DateTimeFormat](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat), хорошо поддерживаемый в браузерах, чтобы показать красиво отформатированное время. 2. Нам нужно зарегистрировать наш новый элемент, используя `customElements.define(tag, class)`. 3. И тогда мы сможем использовать его везде. +======= +1. The class has only one method `connectedCallback()` -- the browser calls it when `` element is added to page (or when HTML parser detects it), and it uses the built-in [Intl.DateTimeFormat](mdn:/JavaScript/Reference/Global_Objects/DateTimeFormat) data formatter, well-supported across the browsers, to show a nicely formatted time. +2. We need to register our new element by `customElements.define(tag, class)`. +3. And then we can use it everywhere. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```smart header="Обновление пользовательских элементов" @@ -146,7 +152,11 @@ customElements.define("time-formatted", TimeFormatted); // (2) В текущей реализации `` после того, как элемент отрендерился, дальнейшие изменения атрибутов не дают никакого эффекта. Это странно для HTML-элемента. Обычно, когда мы изменяем атрибут, например `a.href`, мы ожидаем, что изменение будет видно сразу. Так что давайте исправим это. +<<<<<<< HEAD Мы можем наблюдать за атрибутами, поместив их список в статический геттер `observedAttributes()`. При изменении таких атрибутов вызывается `attributeChangedCallback`. Он срабатывает не для любого атрибута по соображениям производительности. +======= +We can observe attributes by providing their list in `observedAttributes()` static getter. For such attributes, `attributeChangedCallback` is called when they are modified. It doesn't trigger for other, unlisted attributes (that's for performance reasons). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Вот новый ``, который автоматически обновляется при изменении атрибутов: @@ -317,7 +327,11 @@ customElements.define('user-info', class extends HTMLElement { class HelloButton extends HTMLButtonElement { /* методы пользовательского элемента */ } ``` +<<<<<<< HEAD 2. Предоставим третий аргумент в `customElements.define`, указывающий тег: +======= +2. Provide the third argument to `customElements.define`, that specifies the tag: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js customElements.define('hello-button', HelloButton, *!*{extends: 'button'}*/!*); ``` @@ -361,7 +375,11 @@ customElements.define('hello-button', HelloButton, {extends: 'button'}); ## Ссылки - HTML Living Standard: . +<<<<<<< HEAD - Совместимость: . +======= +- Compatiblity: . +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## Итого @@ -393,4 +411,8 @@ customElements.define('hello-button', HelloButton, {extends: 'button'}); /*