diff --git a/1-js/1-getting-started/1-intro/article.md b/1-js/1-getting-started/1-intro/article.md index 75db80f1..9480a342 100644 --- a/1-js/1-getting-started/1-intro/article.md +++ b/1-js/1-getting-started/1-intro/article.md @@ -2,94 +2,83 @@ Давайте посмотрим, что такого особенного в JavaScript, почему именно он, и какие еще технологии существуют, кроме JavaScript. -## Что такое JavaScript? +## Что такое JavaScript? -*JavaScript* изначально создавался для того, чтобы сделать web-странички "живыми". +*JavaScript* изначально создавался для того, чтобы сделать web-странички "живыми". Программы на этом языке называются *скриптами*. В браузере они подключаются напрямую к HTML и, как только загружается страничка -- тут же выполняются. **Программы на JavaScript -- обычный текст**. Они не требуют какой-то специальной подготовки. В этом плане JavaScript сильно отличается от другого языка, который называется [Java](http://ru.wikipedia.org/wiki/Java). -[smart header="Почему JavaScript?"] +```smart header="Почему JavaScript?" Когда создавался язык JavaScript, у него изначально было другое название: "LiveScript". Но тогда был очень популярен язык Java, и маркетологи решили, что схожее название сделает новый язык более популярным. Планировалось, что JavaScript будет эдаким "младшим братом" Java. Однако, история распорядилась по-своему, JavaScript сильно вырос, и сейчас это совершенно независимый язык, со своей спецификацией, которая называется [ECMAScript](https://ru.wikipedia.org/wiki/ECMAScript), и к Java не имеет никакого отношения. У него много особенностей, которые усложняют освоение, но по ходу учебника мы с ними разберёмся. -[/smart] +``` JavaScript может выполняться не только в браузере, а где угодно, нужна лишь специальная программа -- [интерпретатор](http://ru.wikipedia.org/wiki/%D0%98%D0%BD%D1%82%D0%B5%D1%80%D0%BF%D1%80%D0%B5%D1%82%D0%B0%D1%82%D0%BE%D1%80). Процесс выполнения скрипта называют "интерпретацией". -[smart header="Компиляция и интерпретация, для программистов"] -Для выполнения программ, не важно на каком языке, существуют два способа: "компиляция" и "интерпретация". +```smart header="Компиляция и интерпретация, для программистов" +Для выполнения программ, не важно на каком языке, существуют два способа: "компиляция" и "интерпретация". - +- *Компиляция* -- это когда исходный код программы, при помощи специального инструмента, другой программы, которая называется "компилятор", преобразуется в другой язык, как правило -- в машинный код. Этот машинный код затем распространяется и запускается. При этом исходный код программы остаётся у разработчика. +- *Интерпретация* -- это когда исходный код программы получает другой инструмент, который называют "интерпретатор", и выполняет его "как есть". При этом распространяется именно сам исходный код (скрипт). Этот подход применяется в браузерах для JavaScript. Современные интерпретаторы перед выполнением преобразуют JavaScript в машинный код или близко к нему, оптимизируют, а уже затем выполняют. И даже во время выполнения стараются оптимизировать. Поэтому JavaScript работает очень быстро. -[/smart] +``` Во все основные браузеры встроен интерпретатор JavaScript, именно поэтому они могут выполнять скрипты на странице. Но, разумеется, JavaScript можно использовать не только в браузере. Это полноценный язык, программы на котором можно запускать и на сервере, и даже в стиральной машинке, если в ней установлен соответствующий интерпретатор. -[warn header="Поговорим о браузерах"] - +```warn header="Поговорим о браузерах" Далее в этой главе мы говорим о возможностях и ограничениях JavaScript именно в контексте браузера. +``` -[/warn] - -## Что умеет JavaScript? +## Что умеет JavaScript? Современный JavaScript -- это "безопасный" язык программирования общего назначения. Он не предоставляет низкоуровневых средств работы с памятью, процессором, так как изначально был ориентирован на браузеры, в которых это не требуется. -Что же касается остальных возможностей -- они зависят от окружения, в котором запущен JavaScript. В браузере JavaScript умеет делать всё, что относится к манипуляции со страницей, взаимодействию с посетителем и, в какой-то мере, с сервером: +Что же касается остальных возможностей -- они зависят от окружения, в котором запущен JavaScript. В браузере JavaScript умеет делать всё, что относится к манипуляции со страницей, взаимодействию с посетителем и, в какой-то мере, с сервером: - +- Создавать новые HTML-теги, удалять существующие, менять стили элементов, прятать, показывать элементы и т.п. +- Реагировать на действия посетителя, обрабатывать клики мыши, перемещения курсора, нажатия на клавиатуру и т.п. +- Посылать запросы на сервер и загружать данные без перезагрузки страницы (эта технология называется "AJAX"). +- Получать и устанавливать cookie, запрашивать данные, выводить сообщения... +- ...и многое, многое другое! -## Что НЕ умеет JavaScript? +## Что НЕ умеет JavaScript? -JavaScript -- быстрый и мощный язык, но браузер накладывает на его исполнение некоторые ограничения.. +JavaScript -- быстрый и мощный язык, но браузер накладывает на его исполнение некоторые ограничения.. -Это сделано для безопасности пользователей, чтобы злоумышленник не мог с помощью JavaScript получить личные данные или как-то навредить компьютеру пользователя. +Это сделано для безопасности пользователей, чтобы злоумышленник не мог с помощью JavaScript получить личные данные или как-то навредить компьютеру пользователя. Этих ограничений нет там, где JavaScript используется вне браузера, например на сервере. Кроме того, современные браузеры предоставляют свои механизмы по установке плагинов и расширений, которые обладают расширенными возможностями, но требуют специальных действий по установке от пользователя **Большинство возможностей JavaScript в браузере ограничено текущим окном и страницей.** - +![|width="530" height="400"](limitations.png) - + Есть способы это обойти, и они раскрыты в учебнике, но они требуют специального кода на оба документа, которые находятся в разных вкладках или окнах. Без него, из соображений безопасности, залезть из одной вкладки в другую при помощи JavaScript нельзя. +- Из JavaScript можно легко посылать запросы на сервер, с которого пришла страница. Запрос на другой домен тоже возможен, но менее удобен, т. к. и здесь есть ограничения безопасности. -## В чём уникальность JavaScript? +## В чём уникальность JavaScript? Есть как минимум *три* замечательных особенности JavaScript: -[compare] -+Полная интеграция с HTML/CSS. -+Простые вещи делаются просто. -+Поддерживается всеми распространёнными браузерами и включён по умолчанию. -[/compare] +```compare ++ Полная интеграция с HTML/CSS. ++ Простые вещи делаются просто. ++ Поддерживается всеми распространёнными браузерами и включён по умолчанию. +``` -**Этих трёх вещей одновременно нет больше ни в одной браузерной технологии.** +**Этих трёх вещей одновременно нет больше ни в одной браузерной технологии.** Поэтому JavaScript и является самым распространённым средством создания браузерных интерфейсов. @@ -102,49 +91,52 @@ JavaScript -- быстрый и мощный язык, но браузер на *HTML 5* -- эволюция стандарта HTML, добавляющая новые теги и, что более важно, ряд новых возможностей браузерам. Вот несколько примеров: - + +- Чтение/запись файлов на диск (в специальной "песочнице", то есть не любые). +- Встроенная в браузер база данных, которая позволяет хранить данные на компьютере пользователя. +- Многозадачность с одновременным использованием нескольких ядер процессора. +- Проигрывание видео/аудио, без Flash. +- 2D и 3D-рисование с аппаратной поддержкой, как в современных играх. Многие возможности HTML5 всё ещё в разработке, но браузеры постепенно начинают их поддерживать. -[summary]Тенденция: JavaScript становится всё более и более мощным и возможности браузера растут в сторону десктопных приложений.[/summary] +```summary +Тенденция: JavaScript становится всё более и более мощным и возможности браузера растут в сторону десктопных приложений. +``` -### EcmaScript 6 +### ECMAScript 6 -Сам язык JavaScript улучшается. Современный стандарт EcmaScript 5 включает в себя новые возможности для разработки, EcmaScript 6 будет шагом вперёд в улучшении синтаксиса языка. +Сам язык JavaScript улучшается. Современный стандарт ECMAScript 5 включает в себя новые возможности для разработки, ECMAScript 6 будет шагом вперёд в улучшении синтаксиса языка. Современные браузеры улучшают свои движки, чтобы увеличить скорость исполнения JavaScript, исправляют баги и стараются следовать стандартам. -[summary]Тенденция: JavaScript становится всё быстрее и стабильнее, в язык добавляются новые возможности.[/summary] +```summary +Тенденция: JavaScript становится всё быстрее и стабильнее, в язык добавляются новые возможности. +``` Очень важно то, что новые стандарты HTML5 и ECMAScript сохраняют максимальную совместимость с предыдущими версиями. Это позволяет избежать неприятностей с уже существующими приложениями. -Впрочем, небольшая проблема с "супер-современными штучками" всё же есть. Иногда браузеры стараются включить новые возможности, которые ещё не полностью описаны в стандарте, но настолько интересны, что разработчики просто не могут ждать. +Впрочем, небольшая проблема с "супер-современными штучками" всё же есть. Иногда браузеры стараются включить новые возможности, которые ещё не полностью описаны в стандарте, но настолько интересны, что разработчики просто не могут ждать. ...Однако, со временем стандарт меняется и браузерам приходится подстраиваться к нему, что может привести к ошибкам в уже написанном, основанном на старой реализации, JavaScript-коде. Поэтому следует дважды подумать перед тем, как применять на практике такие "супер-новые" решения. При этом все браузеры сходятся к стандарту, и различий между ними уже гораздо меньше, чем всего лишь несколько лет назад. -[summary]Тенденция: всё идет к полной совместимости со стандартом.[/summary] - +```summary +Тенденция: всё идет к полной совместимости со стандартом. +``` ## Альтернативные браузерные технологии -Вместе с JavaScript на страницах используются и другие технологии. Связка с ними может помочь достигнуть более интересных результатов в тех местах, где браузерный JavaScript пока не столь хорош, как хотелось бы. +Вместе с JavaScript на страницах используются и другие технологии. Связка с ними может помочь достигнуть более интересных результатов в тех местах, где браузерный JavaScript пока не столь хорош, как хотелось бы. -### Java +### Java Java -- язык общего назначения, на нём можно писать самые разные программы. Для интернет-страниц есть особая возможность - написание *апплетов*. *Апплет* -- это программа на языке Java, которую можно подключить к HTML при помощи тега `applet`, выглядит это примерно так: -```html - +```html run @@ -159,13 +151,12 @@ Java -- язык общего назначения, на нём можно пи В первую очередь тем, что подписанный Java-апплет может всё то же, что и обычная программа, установленная на компьютере посетителя. Конечно, для этого понадобится согласие пользователя при открытии такого апплета. -[compare] -+Java может делать *всё* от имени посетителя, совсем как установленная программа. Потенциально опасные действия требуют подписанного апплета и согласия пользователя. --Java требует больше времени для загрузки. --Среда выполнения Java, включая браузерный плагин, должна быть установлена на компьютере посетителя и включена. --Java-апплет не интегрирован с HTML-страницей, а выполняется отдельно. Но он может вызывать функции JavaScript. -[/compare] - +```compare ++ Java может делать *всё* от имени посетителя, совсем как установленная программа. Потенциально опасные действия требуют подписанного апплета и согласия пользователя. +- Java требует больше времени для загрузки. +- Среда выполнения Java, включая браузерный плагин, должна быть установлена на компьютере посетителя и включена. +- Java-апплет не интегрирован с HTML-страницей, а выполняется отдельно. Но он может вызывать функции JavaScript. +``` ### Плагины и расширения для браузера @@ -175,31 +166,29 @@ Java -- язык общего назначения, на нём можно пи Как и в ситуации с Java-апплетом, у них широкие возможности, но посетитель поставит их в том случае, если вам доверяет. -### Adobe Flash +### Adobe Flash -Adobe Flash -- кросс-браузерная платформа для мультимедиа-приложений, анимаций, аудио и видео. +Adobe Flash -- кросс-браузерная платформа для мультимедиа-приложений, анимаций, аудио и видео. *Flash-ролик* -- это скомпилированная программа, написанная на языке ActionScript. Её можно подключить к HTML-странице и запустить в прямоугольном контейнере. -В первую очередь Flash полезен тем, что позволяет **кросс-браузерно** работать с микрофоном, камерой, с буфером обмена, а также поддерживает продвинутые возможности по работе с сетевыми соединениями. +В первую очередь Flash полезен тем, что позволяет **кросс-браузерно** работать с микрофоном, камерой, с буфером обмена, а также поддерживает продвинутые возможности по работе с сетевыми соединениями. -[compare] -+Сокеты, UDP для P2P и другие продвинутые возможности по работе с сетевыми соединениями -+Поддержка мультимедиа: изображения, аудио, видео. Работа с веб-камерой и микрофоном. --Flash должен быть установлен и включён. А на некоторых устройствах он вообще не поддерживается. --Flash не интегрирован с HTML-страницей, а выполняется отдельно. --Существуют ограничения безопасности, однако они немного другие, чем в JavaScript. -[/compare] +```compare ++ Сокеты, UDP для P2P и другие продвинутые возможности по работе с сетевыми соединениями ++ Поддержка мультимедиа: изображения, аудио, видео. Работа с веб-камерой и микрофоном. +- Flash должен быть установлен и включён. А на некоторых устройствах он вообще не поддерживается. +- Flash не интегрирован с HTML-страницей, а выполняется отдельно. +- Существуют ограничения безопасности, однако они немного другие, чем в JavaScript. +``` Из Flash можно вызывать JavaScript и наоборот, поэтому обычно сайты используют JavaScript, а там, где он не справляется -- можно подумать о Flash. - ## Языки поверх JavaScript - Синтаксис JavaScript устраивает не всех: одним он кажется слишком свободным, другим -- наоборот, слишком ограниченным, третьи хотят добавить в язык дополнительные возможности, которых нет в стандарте... -Это нормально, ведь требования и проекты у всех разные. +Это нормально, ведь требования и проекты у всех разные. В последние годы появилось много языков, которые добавляют различные возможности "поверх" JavaScript, а для запуска в браузере -- при помощи специальных инструментов "трансляторов" превращаются в обычный JavaScript-код. @@ -207,20 +196,17 @@ Adobe Flash -- кросс-браузерная платформа для мул При этом разные языки выглядят по-разному и добавляют совершенно разные вещи: - +- Язык [CoffeeScript](http://coffeescript.org/) -- это "синтаксический сахар" поверх JavaScript. Он сосредоточен на большей ясности и краткости кода. Как правило, его особенно любят программисты на Ruby. +- Язык [TypeScript](http://www.typescriptlang.org/) сосредоточен на добавлении строгой типизации данных. Он предназначен для упрощения разработки и поддержки больших систем. Его разрабатывает Microsoft. +- Язык [Dart](https://www.dartlang.org/) интересен тем, что он не только транслируется в JavaScript, как и другие языки, но и имеет свою независимую среду выполнения, которая даёт ему ряд возможностей и доступна для встраивания в приложения (вне браузера). Он разрабатывается компанией Google. -[smart header="ES6 и ES7 прямо сейчас"] +```smart header="ES6 и ES7 прямо сейчас" Существуют также трансляторы, которые берут код, использующий возможности будущих стандартов JavaScript, и преобразуют его в более старый вариант, который понимают все браузеры. Например, [babeljs](https://babeljs.io/). Благодаря этому, мы можем использовать многие возможности будущего уже сегодня. -[/smart] - +``` ## Итого diff --git a/1-js/1-getting-started/1-intro/limitations.png b/1-js/1-getting-started/1-intro/limitations.png index 0f58b9d6..60b44d73 100644 Binary files a/1-js/1-getting-started/1-intro/limitations.png and b/1-js/1-getting-started/1-intro/limitations.png differ diff --git a/1-js/1-getting-started/1-intro/limitations@2x.png b/1-js/1-getting-started/1-intro/limitations@2x.png index e790016b..9847f6a8 100644 Binary files a/1-js/1-getting-started/1-intro/limitations@2x.png and b/1-js/1-getting-started/1-intro/limitations@2x.png differ diff --git a/1-js/1-getting-started/2-pre-coding/article.md b/1-js/1-getting-started/2-pre-coding/article.md index 05833be2..d95fe5f9 100644 --- a/1-js/1-getting-started/2-pre-coding/article.md +++ b/1-js/1-getting-started/2-pre-coding/article.md @@ -2,7 +2,7 @@ В этом разделе мы познакомимся со справочниками и спецификациями. -Если вы только начинаете изучение, то вряд ли они будут нужны прямо сейчас. Тем не менее, эта глава находится в начале, так как предсказать точный момент, когда вы захотите заглянуть в справочник -- невозможно, но точно известно, что этот момент настанет. +Если вы только начинаете изучение, то вряд ли они будут нужны прямо сейчас. Тем не менее, эта глава находится в начале, так как предсказать точный момент, когда вы захотите заглянуть в справочник -- невозможно, но точно известно, что этот момент настанет. Поэтому рекомендуется кратко взглянуть на них и взять на заметку, чтобы при необходимости вернуться к ним в будущем. @@ -16,29 +16,23 @@ Тем не менее, жить вполне можно если знать, куда смотреть. -**Есть три основных справочника по JavaScript на английском языке**: +**Есть три основных справочника по JavaScript на английском языке**: -
    -
  1. [Mozilla Developer Network](https://developer.mozilla.org/) -- содержит информацию, верную для основных браузеров. Также там присутствуют расширения только для Firefox (они помечены). +1. [Mozilla Developer Network](https://developer.mozilla.org/) -- содержит информацию, верную для основных браузеров. Также там присутствуют расширения только для Firefox (они помечены). -Когда мне нужно быстро найти "стандартную" информацию по `RegExp` - ввожу в Google **"RegExp MDN"**, и ключевое слово "MDN" (Mozilla Developer Network) приводит к информации из этого справочника. -
  2. -
  3. [MSDN](http://msdn.microsoft.com) -- справочник от Microsoft. Там много информации, в том числе и по JavaScript (они называют его "JScript"). Если нужно что-то, специфичное для IE -- лучше лезть сразу туда. + Когда мне нужно быстро найти "стандартную" информацию по `RegExp` - ввожу в Google **"RegExp MDN"**, и ключевое слово "MDN" (Mozilla Developer Network) приводит к информации из этого справочника. +2. [MSDN](http://msdn.microsoft.com) -- справочник от Microsoft. Там много информации, в том числе и по JavaScript (они называют его "JScript"). Если нужно что-то, специфичное для IE -- лучше лезть сразу туда. -Например, для информации об особенностях `RegExp` в IE -- полезное сочетание: **"RegExp msdn"**. Иногда к поисковой фразе лучше добавить термин "JScript": **"RegExp msdn jscript"**.
  4. -
  5. [Safari Developer Library](https://developer.apple.com/library/safari/navigation/index.html) -- менее известен и используется реже, но в нём тоже можно найти ценную информацию.
  6. -
+ Например, для информации об особенностях `RegExp` в IE -- полезное сочетание: **"RegExp msdn"**. Иногда к поисковой фразе лучше добавить термин "JScript": **"RegExp msdn jscript"**. +3. [Safari Developer Library](https://developer.apple.com/library/safari/navigation/index.html) -- менее известен и используется реже, но в нём тоже можно найти ценную информацию. Есть ещё справочники, не от разработчиков браузеров, но тоже хорошие: -
    -
  1. [http://help.dottoro.com]() -- содержит подробную информацию по HTML/CSS/JavaScript.
  2. -
  3. [http://javascript.ru/manual]() -- справочник по JavaScript на русском языке, он содержит основную информацию по языку, без функций для работы с документом. К нему можно обращаться и по адресу, если знаете, что искать. Например, так: [http://javascript.ru/RegExp](). -
  4. -
  5. [http://www.quirksmode.org]() -- информация о браузерных несовместимостях. Этот ресурс сам по себе довольно старый и, в первую очередь, полезен для поддержки устаревших браузеров. Для поиска можно пользоваться комбинацией **"quirksmode onkeypress"** в Google.
  6. -
  7. [http://caniuse.com]() -- ресурс о поддержке браузерами новейших возможностей HTML/CSS/JavaScript. Например, для поддержки функций криптографии: [http://caniuse.com/#feat=cryptography](). -
  8. -
+1. -- содержит подробную информацию по HTML/CSS/JavaScript. +2. -- справочник по JavaScript на русском языке, он содержит основную информацию по языку, без функций для работы с документом. К нему можно обращаться и по адресу, если знаете, что искать. Например, так: . +3. -- информация о браузерных несовместимостях. Этот ресурс сам по себе довольно старый и, в первую очередь, полезен для поддержки устаревших браузеров. Для поиска можно пользоваться комбинацией **"quirksmode onkeypress"** в Google. +4. -- ресурс о поддержке браузерами новейших возможностей HTML/CSS/JavaScript. Например, для поддержки функций криптографии: . +5. -- таблица с обзором поддержки спецификации ECMAScript различными платформами. ## Спецификации @@ -52,67 +46,60 @@ Её перевод есть на сайте в разделе [стандарт языка](http://es5.javascript.ru/). -[smart header="Почему не просто "JavaScript" ?"] +```smart header="Почему не просто "JavaScript" ?" Вы можете спросить: "Почему спецификация для JavaScript не называется просто *"JavaScript"*, зачем существует какое-то отдельное название?" -Всё потому, что JavaScript™ -- зарегистрированная торговая марка, принадлежащая корпорации Oracle. +Всё потому, что JavaScript™ -- зарегистрированная торговая марка, принадлежащая корпорации Oracle. Название "ECMAScript" было выбрано, чтобы сохранить спецификацию независимой от владельцев торговой марки. -[/smart] +``` Спецификация может рассказать многое о том, как работает язык, и она является самым фундаментальным, доверенным источником информации. -### Спецификации HTML/DOM/CSS +### Спецификации HTML/DOM/CSS -JavaScript -- язык общего назначения, поэтому в спецификации ECMAScript нет ни слова о браузерах. +JavaScript -- язык общего назначения, поэтому в спецификации ECMAScript нет ни слова о браузерах. -Главная организация, которая занимается HTML, CSS, XML и множеством других стандартов -- [Консорциум Всемирной паутины](https://ru.wikipedia.org/wiki/%D0%9A%D0%BE%D0%BD%D1%81%D0%BE%D1%80%D1%86%D0%B8%D1%83%D0%BC_%D0%92%D1%81%D0%B5%D0%BC%D0%B8%D1%80%D0%BD%D0%BE%D0%B9_%D0%BF%D0%B0%D1%83%D1%82%D0%B8%D0%BD%D1%8B) (World Wide Consortium, сокращённо W3C). +Главная организация, которая занимается HTML, CSS, XML и множеством других стандартов -- [Консорциум Всемирной паутины](https://ru.wikipedia.org/wiki/%D0%9A%D0%BE%D0%BD%D1%81%D0%BE%D1%80%D1%86%D0%B8%D1%83%D0%BC_%D0%92%D1%81%D0%B5%D0%BC%D0%B8%D1%80%D0%BD%D0%BE%D0%B9_%D0%BF%D0%B0%D1%83%D1%82%D0%B8%D0%BD%D1%8B) (World Wide Web Consortium, сокращённо W3C). -Информацию о них можно найти на сайте [w3.org](http://w3.org). К сожалению, найти в этой куче то, что нужно, может быть нелегко, особенно когда неизвестно в каком именно стандарте искать. Самый лучший способ -- попросить Google с указанием сайта. +Информацию о них можно найти на сайте [w3.org](http://w3.org). К сожалению, найти в этой куче то, что нужно, может быть нелегко, особенно когда неизвестно в каком именно стандарте искать. Самый лучший способ -- попросить Google с указанием сайта. -Например, для поиска `document.cookie` набрать [document.cookie site:w3.org](https://www.google.com/search?q=document.cookie+site%3Aw3.org). +Например, для поиска `document.cookie` набрать [document.cookie site:w3.org](https://www.google.com/search?q=document.cookie+site%3Aw3.org). Последние версии стандартов расположены на домене [dev.w3.org](http://dev.w3.org). -Кроме того, в том, что касается HTML5 и DOM/CSS, W3C активно использует наработки другой организации -- [WhatWG](https://whatwg.org/). Поэтому самые актуальные версии спецификаций по этим темам обычно находятся на [https://whatwg.org/specs/](). +Кроме того, в том, что касается HTML5 и DOM/CSS, W3C активно использует наработки другой организации -- [WhatWG](https://whatwg.org/). Поэтому самые актуальные версии спецификаций по этим темам обычно находятся на . -Иногда бывает так, что информация на сайте [http://dev.w3.org]() отличается от [http://whatwg.org](). В этом случае, как правило, следует руководствоваться [http://whatwg.org](). +Иногда бывает так, что информация на сайте отличается от . В этом случае, как правило, следует руководствоваться . ## Итого Итак, посмотрим какие у нас есть источники информации. Справочники: -
    -
  • Mozilla Developer Network -- информация для Firefox и большинства браузеров. -Google-комбо: `"RegExp MDN"`, ключевое слово "MDN".
  • -
  • MSDN -- информация по IE. -Google-комбо: `"RegExp msdn"`. Иногда лучше добавить термин "JScript": `"RegExp msdn jscript"`.
  • -
  • [Safari Developer Library](https://developer.apple.com/library/safari/navigation/index.html) -- информация по Safari.
  • -
  • http://help.dottoro.com -- подробная информация по HTML/CSS/JavaScript с учётом браузерной совместимости. -Google-комбо: `"RegExp dottoro"`.
  • -
  • [](http://javascript.ru/manual) -- справочник по JavaScript на русском языке. К нему можно обращаться и по адресу, если знаете, что искать. Например, так: [](http://javascript.ru/RegExp). + +- Mozilla Developer Network -- информация для Firefox и большинства браузеров. +Google-комбо: `"RegExp MDN"`, ключевое слово "MDN". +- MSDN -- информация по IE. +Google-комбо: `"RegExp msdn"`. Иногда лучше добавить термин "JScript": `"RegExp msdn jscript"`. +- [Safari Developer Library](https://developer.apple.com/library/safari/navigation/index.html) -- информация по Safari. +- http://help.dottoro.com -- подробная информация по HTML/CSS/JavaScript с учётом браузерной совместимости. +Google-комбо: `"RegExp dottoro"`. +- -- справочник по JavaScript на русском языке. К нему можно обращаться и по адресу, если знаете, что искать. Например, так: . Google-комбо: `"RegExp site:javascript.ru"`. -
  • -
Спецификации содержат важнейшую информацию о том, как оно "должно работать": -
    -
  • JavaScript, современный стандарт [ES5 (англ)](http://www.ecma-international.org/publications/standards/Ecma-262.htm), и предыдущий [ES3 (рус)](http://javascript.ru/ecma).
  • -
  • HTML/DOM/CSS -- на сайте [http://w3.org](http://www.w3.org). -Google-комбо: `"document.cookie site:w3.org"`.
  • -
  • ...А самые последние версии стандартов -- на [http://dev.w3.org]() и на [http://whatwg.org/specs/](https://whatwg.org/specs/).
  • -
+- JavaScript, современный стандарт [ES5 (англ)](http://www.ecma-international.org/publications/standards/Ecma-262.htm), и предыдущий [ES3 (рус)](http://javascript.ru/ecma). +- HTML/DOM/CSS -- на сайте [http://w3.org](http://www.w3.org). +Google-комбо: `"document.cookie site:w3.org"`. +- ...А самые последние версии стандартов -- на и на [http://whatwg.org/specs/](https://whatwg.org/specs/). То, как оно на самом деле работает и несовместимости: -
    -
  • [http://quirksmode.org/](). Google-комбо: `"innerHeight quirksmode"`.
  • -
+- . Google-комбо: `"innerHeight quirksmode"`. Поддержка современных и новейших возможностей браузерами: -
    -
  • [http://caniuse.com](). Google-комбо: `"caniuse geolocation"`.
  • -
+- . Google-комбо: `"caniuse geolocation"`. + diff --git a/1-js/1-getting-started/3-editor/article.md b/1-js/1-getting-started/3-editor/article.md index 9523c6c8..983112df 100644 --- a/1-js/1-getting-started/3-editor/article.md +++ b/1-js/1-getting-started/3-editor/article.md @@ -1,31 +1,28 @@ # Редакторы для кода -Для разработки обязательно нужен хороший редактор. +Для разработки обязательно нужен хороший редактор. Выбранный вами редактор должен иметь в своем арсенале: -
    -
  1. Подсветку синтаксиса.
  2. -
  3. Автодополнение.
  4. -
  5. "Фолдинг" (от англ. folding) -- возможность скрыть-раскрыть блок кода.
  6. -
+1. Подсветку синтаксиса. +2. Автодополнение. +3. "Фолдинг" (от англ. folding) -- возможность скрыть-раскрыть блок кода. [cut] + ## IDE Термин IDE (Integrated Development Environment) -- "интегрированная среда разработки", означает редактор, который расширен большим количеством "наворотов", умеет работать со вспомогательными системами, такими как багтрекер, контроль версий, и много чего ещё. Как правило, IDE загружает весь проект целиком, поэтому может предоставлять автодополнение по функциям всего проекта, удобную навигацию по его файлам и т.п. -Если вы ещё не задумывались над выбором IDE, присмотритесь к следующим вариантам. +Если вы ещё не задумывались над выбором IDE, присмотритесь к следующим вариантам. -
    -
  • Продукты IntelliJ: [WebStorm](http://www.jetbrains.com/webstorm/), а также в зависимости от дополнительного языка программирования [PHPStorm (PHP)](http://www.jetbrains.com/phpstorm/), [IDEA (Java)](http://www.jetbrains.com/idea/), [RubyMine (Ruby)](http://www.jetbrains.com/ruby/) и другие.
  • -
  • Visual Studio, в сочетании с разработкой под .NET (Win)
  • -
  • Продукты на основе Eclipse, в частности [Aptana](http://www.aptana.com/) и Zend Studio
  • -
  • [Komodo IDE](http://www.activestate.com/komodo-ide) и его облегчённая версия [Komodo Edit](http://www.activestate.com/komodo-edit).
  • -
  • [Netbeans](http://netbeans.org/)
  • -
+- Продукты IntelliJ: [WebStorm](http://www.jetbrains.com/webstorm/), а также в зависимости от дополнительного языка программирования [PHPStorm (PHP)](http://www.jetbrains.com/phpstorm/), [IDEA (Java)](http://www.jetbrains.com/idea/), [RubyMine (Ruby)](http://www.jetbrains.com/ruby/) и другие. +- Visual Studio, в сочетании с разработкой под .NET (Win) +- Продукты на основе Eclipse, в частности [Aptana](http://www.aptana.com/) и Zend Studio +- [Komodo IDE](http://www.activestate.com/komodo-ide) и его облегчённая версия [Komodo Edit](http://www.activestate.com/komodo-edit). +- [Netbeans](http://netbeans.org/) Почти все они, за исключением Visual Studio, кросс-платформенные. @@ -35,31 +32,27 @@ ## Лёгкие редакторы -Лёгкие редакторы -- не такие мощные, как IDE, но они быстрые и простые, мгновенно стартуют. +Лёгкие редакторы -- не такие мощные, как IDE, но они быстрые и простые, мгновенно стартуют. Основная сфера применения лёгкого редактора -- мгновенно открыть нужный файл, чтобы что-то в нём поправить. -На практике "лёгкие" редакторы могут обладать большим количеством плагинов, так что граница между IDE и "лёгким" редактором размыта, спорить что именно редактор, а что IDE -- не имеет смысла. +На практике "лёгкие" редакторы могут обладать большим количеством плагинов, так что граница между IDE и "лёгким" редактором размыта, спорить что именно редактор, а что IDE -- не имеет смысла. Достойны внимания: -
    -
  • Sublime Text (кросс-платформенный, shareware).
  • -
  • Atom (кросс-платформенный, free).
  • -
  • SciTe простой, легкий и очень быстрый (Windows, бесплатный).
  • -
  • Notepad++ (Windows, бесплатный).
  • -
  • Vim, Emacs. Если умеете их готовить.
  • -
+- Sublime Text (кросс-платформенный, shareware). +- Atom (кросс-платформенный, free). +- SciTe простой, легкий и очень быстрый (Windows, бесплатный). +- Notepad++ (Windows, бесплатный). +- Vim, Emacs. Если умеете их готовить. ## Мои редакторы Лично мои любимые редакторы: -
    -
  • Как IDE -- редакторы от Jetbrains: для чистого JavaScript [WebStorm](http://www.jetbrains.com/webstorm/), если ещё какой-то язык, то в зависимости от языка: [PHPStorm (PHP)](http://www.jetbrains.com/phpstorm/), [IDEA (Java)](http://www.jetbrains.com/idea/), [RubyMine (Ruby)](http://www.jetbrains.com/ruby/). У них есть и другие редакторы под разные языки, но я ими не пользовался.
  • -
  • Как быстрый редактор -- Sublime Text.
  • -
  • Иногда Visual Studio, если разработка идёт под платформу .NET (Win).
  • -
+- Как IDE -- редакторы от Jetbrains: для чистого JavaScript [WebStorm](http://www.jetbrains.com/webstorm/), если ещё какой-то язык, то в зависимости от языка: [PHPStorm (PHP)](http://www.jetbrains.com/phpstorm/), [IDEA (Java)](http://www.jetbrains.com/idea/), [RubyMine (Ruby)](http://www.jetbrains.com/ruby/). У них есть и другие редакторы под разные языки, но я ими не пользовался. +- Как быстрый редактор -- Sublime Text. +- Иногда Visual Studio, если разработка идёт под платформу .NET (Win). Если не знаете, что выбрать -- можно посмотреть на них ;) diff --git a/1-js/1-getting-started/4-devtools/article.md b/1-js/1-getting-started/4-devtools/article.md index 7a26dfce..63f54cb5 100644 --- a/1-js/1-getting-started/4-devtools/article.md +++ b/1-js/1-getting-started/4-devtools/article.md @@ -1,8 +1,8 @@ # Консоль разработчика -При разработке скриптов всегда возможны ошибки... Впрочем, что я говорю? У вас абсолютно точно будут ошибки, если конечно вы -- человек, а не робот. +При разработке скриптов всегда возможны ошибки... Впрочем, что я говорю? У вас абсолютно точно будут ошибки, если конечно вы -- человек, а не [робот](http://ru.wikipedia.org/wiki/%D0%91%D0%B5%D0%BD%D0%B4%D0%B5%D1%80_(%D0%A4%D1%83%D1%82%D1%83%D1%80%D0%B0%D0%BC%D0%B0)). -Чтобы читать их в удобном виде, а также получать массу полезной информации о выполнении скриптов, в браузерах есть *инструменты разработки*. +Чтобы читать их в удобном виде, а также получать массу полезной информации о выполнении скриптов, в браузерах есть *инструменты разработки*. **Для разработки рекомендуется использовать Chrome или Firefox.** @@ -12,100 +12,87 @@ [cut] -## Google Chrome +## Google Chrome -Откройте страницу [bug.html](bug.html). +Откройте страницу [bug.html](bug.html). В её JavaScript-коде есть ошибка. Конечно, обычному посетителю она не видна, нужно открыть инструменты разработчика. -Для этого используйте сочетание клавиш [key Ctrl+Shift+J], а если у вас Mac, то [key Cmd+Shift+J]. +Для этого используйте клавишу `key:F12` под Windows, а если у вас Mac, то `key:Cmd+Opt+J`. При этом откроются инструменты разработчика и вкладка Console, в которой будет ошибка. Выглядеть будет примерно так: - +![](chrome.png) +- При клике на `bug.html` вы перейдёте во вкладку с кодом к месту ошибки, там будет и краткое описание ошибки. +В данном случае ошибка вызвана строкой `lalala`, которая интерпретатору непонятна. +- В этом же окошке вы можете набирать команды на JavaScript. Например, наберите `alert("Hello")` -- команду вывода сообщения и запустите её нажатием `key:Enter`. Мы познакомимся с этой и другими командами далее. +- Для перевода курсора на следующую строку (если команда состоит из нескольких строк) -- используется сочетание `key:Shift+Enter`. -
    -
  • При клике на `bug.html` вы перейдёте во вкладку с кодом к месту ошибки, там будет и краткое описание ошибки. -В данном случае ошибка вызвана строкой `lalala`, которая интерпретатору непонятна.
  • -
  • В этом же окошке вы можете набирать команды на JavaScript. Например, наберите `alert("Hello")` -- команду вывода сообщения и запустите её нажатием [key Enter]. Мы познакомимся с этой и другими командами далее.
  • -
  • Для перевода курсора на следующую строку (если команда состоит из нескольких строк) -- используется сочетание [key Shift+Enter].
  • -
+Далее в учебнике мы подробнее рассмотрим отладку в Chrome в главе . -Далее в учебнике мы подробнее рассмотрим отладку в Chrome в главе [](/debugging-chrome). +## Firefox -## Firefox +Для разработки в Firefox используется расширение Firebug. -Для разработки в Firefox используется расширение Firebug. +1. Первым делом его надо установить. -
    -
  1. Первым делом его надо установить. + Это можно сделать со страницы https://addons.mozilla.org/ru/firefox/addon/firebug/. -Это можно сделать со страницы https://addons.mozilla.org/ru/firefox/addon/firebug/. + Перезапустите браузер. Firebug появится в правом-нижнем углу окна: -Перезапустите браузер. Firebug появится в правом-нижнем углу окна: + ![](firebug-gray.png) - + Если иконки не видно -- возможно, у вас выключена панель расширений. Нажмите `key:Ctrl+\` для её отображения. -Если иконки не видно -- возможно, у вас выключена панель расширений. Нажмите [key Ctrl+\] для её отображения. + Ну а если её нет и там, то нажмите `key:F12` -- это горячая клавиша для запуска Firebug, расширение появится, если установлено. +2. Далее, для того чтобы консоль заработала, её надо включить. -Ну а если её нет и там, то нажмите [key F12] -- это горячая клавиша для запуска Firebug, расширение появится, если установлено. -
  2. -
  3. Далее, для того чтобы консоль заработала, её надо включить. + Если консоль уже была включена ранее, то этот шаг не нужен, но если она серая -- выберите в меню `Консоль` и включите её: -Если консоль уже была включена ранее, то этот шаг не нужен, но если она серая -- выберите в меню `Консоль` и включите её: + ![](firefox_console_enable.png) +3. Для того, чтобы Firebug работал без глюков, желательно сначала открыть Firebug, а уже потом -- зайти на страницу. - + С открытым Firebug зайдите на страницу с ошибкой: [bug.html](/devtools/bug.html). -
  4. -
  5. Для того, чтобы Firebug работал без глюков, желательно сначала открыть Firebug, а уже потом -- зайти на страницу. + Консоль покажет ошибку: -С открытым Firebug зайдите на страницу с ошибкой: [bug.html](/devtools/bug.html). + ![](firefox.png) -Консоль покажет ошибку: + Кликните на строчке с ошибкой и браузер покажет исходный код. При необходимости включайте дополнительные панели. - +Как и в Chrome, можно набирать и запускать команды. Область для команд на рисунке находится справа, запуск команд осуществляется нажатием `key:Ctrl+Enter` (для Mac -- `key:Cmd+Enter`). -Кликните на строчке с ошибкой и браузер покажет исходный код. При необходимости включайте дополнительные панели. -
  6. -
- -Как и в Chrome, можно набирать и запускать команды. Область для команд на рисунке находится справа, запуск команд осуществляется нажатием [key Ctrl+Enter] (для Mac -- [key Cmd+Enter]). - -Можно перенести её вниз, нажав на кнопочку (на рисунке её не видно, но она присутствует в правом нижнем углу панели разработки). +Можно перенести её вниз, нажав на кнопочку ![](firefox_console_down.png) (на рисунке её не видно, но она присутствует в правом нижнем углу панели разработки). Об основных возможностях можно прочитать на сайте firebug.ru. -## Internet Explorer +## Internet Explorer -Панель разработчика запускается нажатием [key F12]. +Панель разработчика запускается нажатием `key:F12`. Откройте её и зайдите на страницу с ошибкой: [bug.html](/devtools/bug.html). Если вы разобрались с Chrome/Firefox, то дальнейшее будет вам более-менее понятно, так как инструменты IE построены позже и по аналогии с Chrome/Firefox. -## Safari +## Safari -Горячие клавиши: [key Ctrl+Shift+I], [key Ctrl+Alt+C] для Mac -- [key Cmd] вместо [key Ctrl]. +Горячие клавиши: `key:Ctrl+Shift+I`, `key:Ctrl+Alt+C` для Mac -- `key:Cmd` вместо `key:Ctrl`. Для доступа к функционалу разработки через меню: -
    -
  1. -В Safari первым делом нужно активировать меню разработки. +1. В Safari первым делом нужно активировать меню разработки. -Откройте меню, нажав на колесико справа-сверху и выберите `Настройки`. + Откройте меню, нажав на колесико справа-сверху и выберите `Настройки`. -Затем вкладка `Дополнительно`: + Затем вкладка `Дополнительно`: - + ![](safari.png) -Отметьте `Показывать меню "Разработка" в строке меню`. Закройте настройки. -
  2. -
  3. Нажмите на колесико и выберите `Показать строку меню`. + Отметьте `Показывать меню "Разработка" в строке меню`. Закройте настройки. +2. Нажмите на колесико и выберите `Показать строку меню`. -Инструменты будут доступны в появившейся строке меню, в пункте `Разработка`.
  4. -
+ Инструменты будут доступны в появившейся строке меню, в пункте `Разработка`. ## Итого diff --git a/1-js/10-es-modern/1-es-modern-usage/article.md b/1-js/10-es-modern/1-es-modern-usage/article.md index 86b8b7ff..388244ee 100644 --- a/1-js/10-es-modern/1-es-modern-usage/article.md +++ b/1-js/10-es-modern/1-es-modern-usage/article.md @@ -1,6 +1,6 @@ # ES-2015 сейчас -[Стандарт ES-2015](http://www.ecma-international.org/publications/standards/Ecma-262.htm) был принят в июне 2015. Пока что большинство браузеров реализуют его частично, текущее состояние реализации различных возможностей можно посмотреть здесь: [](https://kangax.github.io/compat-table/es6/). +[Стандарт ES-2015](http://www.ecma-international.org/publications/standards/Ecma-262.htm) был принят в июне 2015. Пока что большинство браузеров реализуют его частично, текущее состояние реализации различных возможностей можно посмотреть здесь: . Когда стандарт будет более-менее поддерживаться во всех браузерах, то весь учебник будет обновлён в соответствии с ним. Пока же, как центральное место для "сбора" современных фич JavaScript, создан этот раздел. @@ -20,12 +20,10 @@ Он состоит из двух частей: -
    -
  1. Собственно транспайлер, который переписывает код.
  2. -
  3. Полифилл, который добавляет методы `Array.from`, `String.prototype.repeat` и другие.
  4. -
+1. Собственно транспайлер, который переписывает код. +2. [Полифилл](https://learn.javascript.ru/dom-polyfill), который добавляет методы `Array.from`, `String.prototype.repeat` и другие. -На странице [](https://babeljs.io/repl/) можно поэкспериментировать с транспайлером: слева вводится код в ES-2015, а справа появляется результат его преобразования в ES5. +На странице можно поэкспериментировать с транспайлером: слева вводится код в ES-2015, а справа появляется результат его преобразования в ES5. Обычно Babel.JS работает на сервере в составе системы сборки JS-кода (например [webpack](http://webpack.github.io/) или [brunch](http://brunch.io/)) и автоматически переписывает весь код в ES5. @@ -35,8 +33,7 @@ Это выглядит так: -```html - +```html run *!* @@ -57,19 +54,19 @@ # Примеры на этом сайте -[warn header="Только при поддержке браузера"] +```warn header="Только при поддержке браузера" Запускаемые примеры с ES-2015 будут работать только если ваш браузер поддерживает соответствующую возможность стандарта. -[/warn] +``` Это означает, что при запуске примеров в браузере, который их не поддерживает, будет ошибка. Это не означает, что пример неправильный! Просто пока нет поддержки... -Рекомендуется [Chrome Canary](https://www.google.com/chrome/browser/canary.html) большинство примеров в нём работает. [Firefox Developer Edition](https://www.mozilla.org/en-US/firefox/channel/#developer) тоже неплох в поддержке современного стандарта, но на момент написания этого текста переменные [let](/let-const) работают только при указании `version=1.7` в типе скрипта: ` -[/head] - - - - - +- Генераторы создаются при помощи функций-генераторов `function*(…) {…}`. +- Внутри генераторов и только внутри них разрешён оператор `yield`. Это иногда создаёт неудобства, поскольку в коллбэках `.map/.forEach` сделать `yield` нельзя. Впрочем, можно сделать `yield` массива (при использовании `co`). +- Внешний код и генератор обмениваются промежуточными результатами посредством вызовов `next/yield`. +- Генераторы позволяют писать плоский асинхронный код, при помощи библиотеки `co`. +Что касается кросс-браузерной поддержки -- она стремительно приближается. Пока же можно использовать генераторы вместе с [Babel](https://babeljs.io). diff --git a/1-js/10-es-modern/12-generator/genYield2-2.png b/1-js/10-es-modern/12-generator/genYield2-2.png index 67484418..cef1d7b2 100644 Binary files a/1-js/10-es-modern/12-generator/genYield2-2.png and b/1-js/10-es-modern/12-generator/genYield2-2.png differ diff --git a/1-js/10-es-modern/12-generator/genYield2-2@2x.png b/1-js/10-es-modern/12-generator/genYield2-2@2x.png index d6bcf78e..f377c210 100644 Binary files a/1-js/10-es-modern/12-generator/genYield2-2@2x.png and b/1-js/10-es-modern/12-generator/genYield2-2@2x.png differ diff --git a/1-js/10-es-modern/12-generator/genYield2-3.png b/1-js/10-es-modern/12-generator/genYield2-3.png index ccc92309..22cc2a9d 100644 Binary files a/1-js/10-es-modern/12-generator/genYield2-3.png and b/1-js/10-es-modern/12-generator/genYield2-3.png differ diff --git a/1-js/10-es-modern/12-generator/genYield2-3@2x.png b/1-js/10-es-modern/12-generator/genYield2-3@2x.png index e4064bb0..c288a4b7 100644 Binary files a/1-js/10-es-modern/12-generator/genYield2-3@2x.png and b/1-js/10-es-modern/12-generator/genYield2-3@2x.png differ diff --git a/1-js/10-es-modern/12-generator/genYield2.png b/1-js/10-es-modern/12-generator/genYield2.png index dd29c353..1e29a1f8 100644 Binary files a/1-js/10-es-modern/12-generator/genYield2.png and b/1-js/10-es-modern/12-generator/genYield2.png differ diff --git a/1-js/10-es-modern/12-generator/genYield2@2x.png b/1-js/10-es-modern/12-generator/genYield2@2x.png index 3600cb13..1cf8b1fa 100644 Binary files a/1-js/10-es-modern/12-generator/genYield2@2x.png and b/1-js/10-es-modern/12-generator/genYield2@2x.png differ diff --git a/1-js/10-es-modern/12-generator/generateSequence-1.png b/1-js/10-es-modern/12-generator/generateSequence-1.png index e5182d51..76e84112 100644 Binary files a/1-js/10-es-modern/12-generator/generateSequence-1.png and b/1-js/10-es-modern/12-generator/generateSequence-1.png differ diff --git a/1-js/10-es-modern/12-generator/generateSequence-1@2x.png b/1-js/10-es-modern/12-generator/generateSequence-1@2x.png index 311a4f69..cd2a416d 100644 Binary files a/1-js/10-es-modern/12-generator/generateSequence-1@2x.png and b/1-js/10-es-modern/12-generator/generateSequence-1@2x.png differ diff --git a/1-js/10-es-modern/12-generator/generateSequence-2.png b/1-js/10-es-modern/12-generator/generateSequence-2.png index 622f03a6..8c7db83f 100644 Binary files a/1-js/10-es-modern/12-generator/generateSequence-2.png and b/1-js/10-es-modern/12-generator/generateSequence-2.png differ diff --git a/1-js/10-es-modern/12-generator/generateSequence-2@2x.png b/1-js/10-es-modern/12-generator/generateSequence-2@2x.png index e761223e..e270fdcb 100644 Binary files a/1-js/10-es-modern/12-generator/generateSequence-2@2x.png and b/1-js/10-es-modern/12-generator/generateSequence-2@2x.png differ diff --git a/1-js/10-es-modern/12-generator/generateSequence-3.png b/1-js/10-es-modern/12-generator/generateSequence-3.png index 81ebadea..678d3b5e 100644 Binary files a/1-js/10-es-modern/12-generator/generateSequence-3.png and b/1-js/10-es-modern/12-generator/generateSequence-3.png differ diff --git a/1-js/10-es-modern/12-generator/generateSequence-3@2x.png b/1-js/10-es-modern/12-generator/generateSequence-3@2x.png index 440fd364..99b23031 100644 Binary files a/1-js/10-es-modern/12-generator/generateSequence-3@2x.png and b/1-js/10-es-modern/12-generator/generateSequence-3@2x.png differ diff --git a/1-js/10-es-modern/12-generator/generateSequence-4.png b/1-js/10-es-modern/12-generator/generateSequence-4.png index a9c7d7a5..ce3cc2a2 100644 Binary files a/1-js/10-es-modern/12-generator/generateSequence-4.png and b/1-js/10-es-modern/12-generator/generateSequence-4.png differ diff --git a/1-js/10-es-modern/12-generator/generateSequence-4@2x.png b/1-js/10-es-modern/12-generator/generateSequence-4@2x.png index f76061df..81c234a6 100644 Binary files a/1-js/10-es-modern/12-generator/generateSequence-4@2x.png and b/1-js/10-es-modern/12-generator/generateSequence-4@2x.png differ diff --git a/1-js/10-es-modern/12-generator/head.html b/1-js/10-es-modern/12-generator/head.html new file mode 100644 index 00000000..759a5c0a --- /dev/null +++ b/1-js/10-es-modern/12-generator/head.html @@ -0,0 +1,9 @@ + + \ No newline at end of file diff --git a/1-js/10-es-modern/13-modules/article.md b/1-js/10-es-modern/13-modules/article.md index fc9be8f5..e226c25f 100644 --- a/1-js/10-es-modern/13-modules/article.md +++ b/1-js/10-es-modern/13-modules/article.md @@ -1,23 +1,21 @@ # Модули -Концепция модулей как способа организации JavaScript-кода существовала давно. +Концепция модулей как способа организации JavaScript-кода существовала давно. Когда приложение сложное и кода много -- мы пытаемся разбить его на файлы. В каждом файле описываем какую-то часть, а в дальнейшем -- собираем эти части воедино. -Модули в стандарте EcmaScript предоставляют удобные средства для этого. +Модули в стандарте ECMAScript предоставляют удобные средства для этого. Такие средства предлагались сообществом и ранее, например: -
    -
  • [AMD](https://en.wikipedia.org/wiki/Asynchronous_module_definition) -- одна из самых древних систем организации модулей, требует лишь наличия клиентской библиотеки, к примеру, [require.js](http://requirejs.org/), но поддерживается и серверными средствами.
  • -
  • [CommonJS](http://wiki.commonjs.org/wiki/Modules/1.1) -- система модулей, встроенная в сервер Node.JS. Требует поддержки на клиентской и серверной стороне.
  • -
  • [UMD](https://github.com/umdjs/umd) -- система модулей, которая предложена в качестве универсальной. UMD-модули будут работать и в системе AMD и в CommonJS.
  • -
+- [AMD](https://en.wikipedia.org/wiki/Asynchronous_module_definition) -- одна из самых древних систем организации модулей, требует лишь наличия клиентской библиотеки, к примеру, [require.js](http://requirejs.org/), но поддерживается и серверными средствами. +- [CommonJS](http://wiki.commonjs.org/wiki/Modules/1.1) -- система модулей, встроенная в сервер Node.JS. Требует поддержки на клиентской и серверной стороне. +- [UMD](https://github.com/umdjs/umd) -- система модулей, которая предложена в качестве универсальной. UMD-модули будут работать и в системе AMD и в CommonJS. Все перечисленные выше системы требуют различных библиотек или систем сборки для использования. -Новый стандарт отличается от них прежде всего тем, что это -- стандарт. А значит, со временем, будет поддерживаться браузерами без дополнительных утилит. +Новый стандарт отличается от них прежде всего тем, что это -- стандарт. А значит, со временем, будет поддерживаться браузерами без дополнительных утилит. Однако, сейчас браузерной поддержки почти нет. Поэтому ES-модули используются в сочетании с системами сборки, такими как [webpack](http://webpack.github.io/), [brunch](http://brunch.io/) и другими, при подключённом [Babel.JS](https://babeljs.io). Мы рассмотрим это далее. @@ -33,30 +31,32 @@ Ключевое слово `export` можно ставить: -
    -
  • перед объявлением переменных, функций и классов.
  • -
  • отдельно, при этом в фигурных скобках указывается, что именно экспортируется.
  • -
+- перед объявлением переменных, функций и классов. +- отдельно, при этом в фигурных скобках указывается, что именно экспортируется. -Например: +Например, так экспортируется переменная `one`: ```js // экспорт прямо перед объявлением export let one = 1; +``` + +Можно написать `export` и отдельно от объявления: -// можно и раздельно, в две строки вместо одной +```js let two = 2; + export {two}; ``` -Заметим, что в фигурных скобках указывается не блок, не произвольное выражение, а только имя. +При этом в фигурных скобках указываются одна или несколько экспортируемых переменных. -Можно указать несколько: +Для двух переменных будет так: ```js export {one, two}; ``` -Также можно указать, что переменная `one` будет доступна снаружи под именем `once`, а `two` -- под именем `twice`: +При помощи ключевого слова `as` можно указать, что переменная `one` будет доступна снаружи (экспортирована) под именем `once`, а `two` -- под именем `twice`: ```js export {one as once, two as twice}; @@ -70,13 +70,15 @@ export class User { } }; -export function sayHi() { +export function sayHi() { alert("Hello!"); }; -// или export {User, sayHi} +// отдельно от объявлений было бы так: +// export {User, sayHi} ``` +````smart header="Для экспорта обязательно нужно имя" Заметим, что и у функции и у класса при таком экспорте должно быть имя. Так будет ошибка: @@ -85,21 +87,23 @@ export function sayHi() { export function() { alert("Error"); }; ``` +В экспорте указываются именно имена, а не произвольные выражения. +```` + ## import Другие модули могут подключать экспортированные значения при помощи ключевого слова `import`. -Синтаксис: +Синтаксис: ```js import {one, two} from "./nums"; ``` Здесь: -
    -
  • `"./nums"` -- модуль, как правило это путь к файлу модуля.
  • -
  • `one, two` -- импортируемые переменные, которые должны быть обозначены в `nums` словом `export`.
  • -
+ +- `"./nums"` -- модуль, как правило это путь к файлу модуля. +- `one, two` -- импортируемые переменные, которые должны быть обозначены в `nums` словом `export`. В результате импорта появятся локальные переменные `one`, `two`, которые будут содержать значения соответствующих экспортов. @@ -115,7 +119,7 @@ export let two = 2; ```js import {one, two} from "./nums"; -alert( `${one} and ${two}` ); // 1 and 2 +alert( `${one} and ${two}` ); // 1 and 2 ``` Импортировать можно и под другим именем, указав его в "as": @@ -124,11 +128,10 @@ alert( `${one} and ${two}` ); // 1 and 2 // импорт one под именем item1, а two – под именем item2 import {one as item1, two as item2} from "./nums"; -alert( `${item1} and ${item2}` ); // 1 and 2 +alert( `${item1} and ${item2}` ); // 1 and 2 ``` -[smart header="Импорт всех значений в виде объекта"] - +````smart header="Импорт всех значений в виде объекта" Можно импортировать все значения сразу в виде объекта вызовом `import * as obj`, например: ```js @@ -136,19 +139,18 @@ alert( `${item1} and ${item2}` ); // 1 and 2 import * as numbers from "./nums"; */!* -alert( `${numbers.one} and ${numbers.two}` ); // 1 and 2 +// теперь экспортированные переменные - свойства numbers +alert( `${numbers.one} and ${numbers.two}` ); // 1 and 2 ``` -[/smart] +```` ## export default -Выше мы видели, что модуль может экспортировать произвольное количество значений при помощи `export`. - -Однако, как правило, код стараются организовать так, чтобы каждый модуль делал одну вещь. Иначе говоря, "один файл -- одна сущность, которую он описывает". +Выше мы видели, что модуль может экспортировать выбранные переменные при помощи `export`. -Например, файл `user.js` содержит `class User`, файл `login.js` -- функцию `login()` для авторизации, и т.п. +Однако, как правило, код стараются организовать так, чтобы каждый модуль делал одну вещь. Иначе говоря, "один файл -- одна сущность, которую он описывает". Например, файл `user.js` содержит `class User`, файл `login.js` -- функцию `login()` для авторизации, и т.п. -При этом модули, разумеется, будут взаимосвязаны. Например, `login.js`, скорее всего, будет импортировать класс `User` из модуля `user.js`. +При этом модули, разумеется, будут использовать друг друга. Например, `login.js`, скорее всего, будет импортировать класс `User` из модуля `user.js`. Для такой ситуации, когда один модуль экспортирует одно значение, предусмотрено особое ключевое сочетание `export default`. @@ -174,25 +176,67 @@ import User from './user'; new User("Вася"); ``` -Если бы в `user.js` не было `default`, то в `login.js` необходимо было бы указать фигурные скобки: +"Экспорт по умолчанию" -- своего рода "синтаксический сахар". Можно было бы и без него, импортировать значение обычным образом через фигурные скобки `{…}`. Если бы в `user.js` не было `default`, то в `login.js` необходимо было бы указать фигурные скобки: ```js -// если в бы user.js не было default: +// если бы user.js содержал // export class User { ... } -// …то при импорте понадобились бы фигурные скобки: +// …то при импорте User понадобились бы фигурные скобки: import {User} from './user'; new User("Вася"); ``` -Как видно, "экспорт по умолчанию" -- лишь небольшой синтаксический сахар. Можно было бы и без него, импортировать значение обычным образом через фигурные скобки `{…}`. +На практике этот "сахар" весьма приятен, так как позволяет легко видеть, какое именно значение экспортирует модуль, а также обойтись без лишних символов при импорте. + +## CommonJS + +Если вы раньше работали с Node.JS или использовали систему сборки в синтаксисе CommonJS, то вот соответствия. + +Для экспорта по умолчанию вместо: +```js +module.exports = VARIABLE; +``` + +Пишем: +```js +export default VARIABLE; +``` -Но на практике этот "сахар" весьма приятен, так как позволяет легко видеть, какое именно значение экспортирует модуль, а также обойтись без лишних символов при импорте. +А при импорте из такого модуля вместо: + +```js +const VARIABLE = require('./file'); +``` + +Пишем: + +```js +import VARIABLE from './file'; +``` + +Для экспорта нескольких значений из модуля, вместо: + +```js +exports.NAME = VARIABLE; +``` + +Пишем в фигурных скобках, что надо экспортировать и под каким именем (без `as`, если имя совпадает): + +```js +export {VARIABLE as NAME}; +``` + +При импорте -- также фигурные скобки: + +```js +import {NAME} from './file'; +``` ## Использование -Современный стандарт EcmaScript описывает, как импортировать и экспортировать значения из модулей, но он ничего не говорит о том, как эти модули искать, загружать и т.п. +Современный стандарт ECMAScript описывает, как импортировать и экспортировать значения из модулей, но он ничего не говорит о том, как эти модули искать, загружать и т.п. Такие механизмы предлагались в процессе создания стандарта, но были убраны по причине недостаточной проработанности. Возможно, они появятся в будущем. @@ -203,16 +247,14 @@ new User("Вася"); Ниже вы можете увидеть полный пример использования модулей с системой сборки [webpack](http://webpack.github.io). В нём есть: -
    -
  • `nums.js` -- модуль, экспортирующий `one` и `two`, как описано выше.
  • -
  • `main.js` -- модуль, который импортирует `one`, `two` из `nums` и выводит их сумму.
  • -
  • `webpack.config.js` -- конфигурация для системы сборки.
  • -
  • `bundle.js` -- файл, который создала система сборки из `main.js` и `nums.js`.
  • -
  • `index.html` -- простой HTML-файл для демонстрации.
  • -
-[codetabs src="http://github.com/nums"] +- `nums.js` -- модуль, экспортирующий `one` и `two`, как описано выше. +- `main.js` -- модуль, который импортирует `one`, `two` из `nums` и выводит их сумму. +- `webpack.config.js` -- конфигурация для системы сборки. +- `bundle.js` -- файл, который создала система сборки из `main.js` и `nums.js`. +- `index.html` -- простой HTML-файл для демонстрации. +[codetabs src="http://github.com/nums"] ## Итого @@ -220,23 +262,15 @@ new User("Вася"); Экспорт: -
    -
  • `export` можно поставить прямо перед объявлением функции, класса, переменной.
  • -
  • Если `export` стоит отдельно от объявления, то значения в нём указываются в фигурных скобках: `export {…}`.
  • -
  • Также можно экспортировать "значение по умолчанию" при помощи `export default`.
  • -
+- `export` можно поставить прямо перед объявлением функции, класса, переменной. +- Если `export` стоит отдельно от объявления, то значения в нём указываются в фигурных скобках: `export {…}`. +- Также можно экспортировать "значение по умолчанию" при помощи `export default`. Импорт: -
    -
  • В фигурных скобках указываются значения, а затем -- модуль, откуда их брать: `import {a, b, c as d} from "module"`.
  • -
  • Можно импортировать все значения в виде объекта при помощи `import * as obj from "module"`.
  • -
  • Без фигурных скобок будет импортировано "значение по умолчанию": `import User from "user"`.
  • -
- -На текущий момент модули требуют системы сборки на сервере. Автор этого текста преимущественно использует webpack, но есть и другие варианты. - - - +- В фигурных скобках указываются значения, а затем -- модуль, откуда их брать: `import {a, b, c as d} from "module"`. +- Можно импортировать все значения в виде объекта при помощи `import * as obj from "module"`. +- Без фигурных скобок будет импортировано "значение по умолчанию": `import User from "user"`. +На текущий момент модули требуют системы сборки на сервере. Автор этого текста преимущественно использует webpack, но есть и другие варианты. diff --git a/1-js/10-es-modern/13-modules/nums.view/webpack.config.js b/1-js/10-es-modern/13-modules/nums.view/webpack.config.js index 6899addf..0d1553f3 100644 --- a/1-js/10-es-modern/13-modules/nums.view/webpack.config.js +++ b/1-js/10-es-modern/13-modules/nums.view/webpack.config.js @@ -1,5 +1,5 @@ // Для использования нужен Node.JS -// Поставьте его: +// Поставьте webpack: // npm i -g webpack // Поставьте babel-loader: // npm i babel-loader diff --git a/1-js/10-es-modern/14-proxy/article.md b/1-js/10-es-modern/14-proxy/article.md index b64a3a5d..b2589224 100644 --- a/1-js/10-es-modern/14-proxy/article.md +++ b/1-js/10-es-modern/14-proxy/article.md @@ -11,54 +11,46 @@ let proxy = new Proxy(target, handler) Здесь: -
    -
  • `target` -- объект, обращения к которому надо перехватывать.
  • -
  • `handler` -- объект с "ловушками": функциями-перехватчиками для операций к `target`.
  • -
+- `target` -- объект, обращения к которому надо перехватывать. +- `handler` -- объект с "ловушками": функциями-перехватчиками для операций к `target`. Почти любая операция может быть перехвачена и обработана прокси до или даже вместо доступа к объекту `target`, например: чтение и запись свойств, получение списка свойств, вызов функции (если `target` -- функция) и т.п. Различных типов ловушек довольно много. -Сначала мы подробно рассмотрим самые важные "ловушки", а затем посмотрим и на их полный список. +Сначала мы подробно рассмотрим самые важные "ловушки", а затем посмотрим и на их полный список. -[smart header="Если ловушки нет -- операция идёт над `target`"] +```smart header="Если ловушки нет -- операция идёт над `target`" Если для операции нет ловушки, то она выполняется напрямую над `target`. -[/smart] +``` ## get/set Самыми частыми являются ловушки для чтения и записи в объект: -
-
`get(target, property, receiver)`
-
Срабатывает при чтении свойства из прокси. +`get(target, property, receiver)` +: Срабатывает при чтении свойства из прокси. Аргументы: -
    -
  • `target` -- целевой объект, тот же который был передан первым аргументом в `new Proxy`.
  • -
  • `property` -- имя свойства.
  • -
  • `receiver` -- объект, к которому было применено присваивание. Обычно сам прокси, либо прототипно наследующий от него. Этот аргумент используется редко.
  • -
-
-
`set(target, property, value, receiver)`
-
Срабатывает при записи свойства в прокси. +- `target` -- целевой объект, тот же который был передан первым аргументом в `new Proxy`. +- `property` -- имя свойства. +- `receiver` -- объект, к которому было применено присваивание. Обычно сам прокси, либо прототипно наследующий от него. Этот аргумент используется редко. -Аргументы: -
    -
  • `target` -- целевой объект, тот же который был передан первым аргументом в `new Proxy`.
  • -
  • `property` -- имя свойства.
  • -
  • `value` -- значение свойства.
  • -
  • `receiver` -- объект, к которому было применено присваивание, обычно сам прокси, либо прототипно наследующий от него.
  • -
-Метод `set` должен вернуть `true`, если присвоение успешно обработано и `false` в случае ошибки (приведёт к генерации `TypeError`). -
-
+`set(target, property, value, receiver)` +: Срабатывает при записи свойства в прокси. + + Аргументы: + +- `target` -- целевой объект, тот же который был передан первым аргументом в `new Proxy`. +- `property` -- имя свойства. +- `value` -- значение свойства. +- `receiver` -- объект, к которому было применено присваивание, обычно сам прокси, либо прототипно наследующий от него. + + Метод `set` должен вернуть `true`, если присвоение успешно обработано и `false` в случае ошибки (приведёт к генерации `TypeError`). Пример с выводом всех операций чтения и записи: -```js -//+ run +```js run 'use strict'; let user = {}; @@ -67,13 +59,13 @@ let proxy = new Proxy(user, { get(target, prop) { *!* alert(`Чтение ${prop}`); -*/!* +*/!* return target[prop]; }, set(target, prop, value) { *!* alert(`Запись ${prop} ${value}`); -*/!* +*/!* target[prop] = value; return true; } @@ -88,14 +80,13 @@ alert(user.firstName); // Ilya При каждой операции чтения и записи свойств `proxy` в коде выше срабатывают методы `get/set`. Через них значение в конечном счёте попадает в объект (или считывается из него). -Можно сделать и позаковырестее. +Можно сделать и позаковыристее. Методы `get/set` позволяют реализовать доступ к произвольным свойствам, которых в объекте нет. Например, в коде ниже словарь `dictionary` содержит различные фразы: -```js -//+ run +```js run 'use strict'; let dictionary = { @@ -108,9 +99,7 @@ alert( dictionary['Hello'] ); // Привет А что, если фразы нет? В этом случае будем возвращать фразу без перевода и, на всякий случай, писать об этом в консоль: - -```js -//+ run +```js run 'use strict'; let dictionary = { @@ -155,8 +144,7 @@ alert( 'Welcome' in dictionary ); // false, нет такого свойства Вот так `dictionary` будет всегда возвращать `true` для любой `in`-проверки: -```js -//+ run +```js run 'use strict'; let dictionary = { @@ -176,14 +164,13 @@ alert("BlaBlaBla" in dictionary); // true # deleteProperty -Ловушка `deleteProperty` по синтаксису аналогична `get/has`. +Ловушка `deleteProperty` по синтаксису аналогична `get/has`. Срабатывает при операции `delete`, должна вернуть `true`, если удаление было успешным. В примере ниже `delete` не повлияет на исходный объект, так как все операции перехватываются и "аннигилируются" прокси: -```js -//+ run +```js run 'use strict'; let dictionary = { @@ -208,15 +195,13 @@ alert("Hello" in dictionary); // true alert("Hello" in proxy); // true ``` - # enumerate Ловушка `enumerate` перехватывает операции `for..in` и `for..of` по объекту. -Как и до ранее, если ловушки нет, то эти операторы работают с исходным объектом: +Как и ранее, если ловушки нет, то эти операторы работают с исходным объектом: -```js -//+ run +```js run 'use strict'; let obj = {a: 1, b: 1}; @@ -235,8 +220,7 @@ for(let prop in proxy) { В примере ниже прокси делает так, что итерация идёт по всем свойствам, кроме начинающихся с подчёркивания `_`: -```js -//+ run +```js run 'use strict'; let user = { @@ -265,11 +249,9 @@ for(let prop in proxy) { ``` Посмотрим внимательнее, что происходит внутри `enumerate`: -
    -
  1. Сначала получаем список интересующих нас свойств в виде массива.
  2. -
  3. Метод должен возвратить [итератор](/iterator) по массиву. Встроенный итератор для массива получаем через вызов `props[Symbol.iterator]()`.
  4. -
+1. Сначала получаем список интересующих нас свойств в виде массива. +2. Метод должен возвратить [итератор](/iterator) по массиву. Встроенный итератор для массива получаем через вызов `props[Symbol.iterator]()`. # apply @@ -279,17 +261,13 @@ for(let prop in proxy) { Метод `apply(target, thisArgument, argumentsList)` получает: -
    -
  • `target` -- исходный объект.
  • -
  • `thisArgument` -- контекст `this` вызова.
  • -
  • `argumentsList` -- аргументы вызова в виде массива.
  • -
- +- `target` -- исходный объект. +- `thisArgument` -- контекст `this` вызова. +- `argumentsList` -- аргументы вызова в виде массива. Она может обработать вызов сама и/или передать его функции. -```js -//+ run +```js run 'use strict'; function sum(a, b) { @@ -321,8 +299,7 @@ alert( proxy(1, 2) ); Пример ниже передаёт операцию создания исходному классу или функции-конструктору, выводя сообщение об этом: -```js -//+ run +```js run 'use strict'; function User(name, surname) { @@ -349,22 +326,20 @@ alert( user.name ); // Ilya Полный список возможных функций-перехватчиков, которые может задавать `handler`: -
    -
  • [getPrototypeOf](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler/has) -- перехватывает обращение к методу `getPrototypeOf`.
  • -
  • [setPrototypeOf](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler/setPrototypeOf) -- перехватывает обращение к методу `setPrototypeOf`.
  • -
  • [isExtensible](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler/isExtensible) -- перехватывает обращение к методу `isExtensible`.
  • -
  • [preventExtensions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler/preventExtensions) -- перехватывает обращение к методу `preventExtensions`.
  • -
  • [getOwnPropertyDescriptor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler/getOwnPropertyDescriptor) -- перехватывает обращение к методу `getOwnPropertyDescriptor`.
  • -
  • [defineProperty](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler/defineProperty) -- перехватывает обращение к методу `defineProperty`.
  • -
  • [has](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler/has) -- перехватывает проверку существования свойства, которая используется в операторе `in` и в некоторых других методах встроенных объектов.
  • -
  • [get](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler/get) -- перехватывает чтение свойства.
  • -
  • [set](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler/set) -- перехватывает запись свойства.
  • -
  • [deleteProperty](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler/deleteProperty) -- перехватывает удаление свойства оператором `delete`.
  • -
  • [enumerate](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler/enumerate) -- срабатывает при вызове `for..in` или `for..of`, возвращает итератор для свойств объекта.
  • -
  • [ownKeys](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler/ownKeys) -- перехватывает обращения к методу `getOwnPropertyNames`.
  • -
  • [apply](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler/apply) -- перехватывает вызовы `target()`.
  • -
  • [construct](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler/construct) -- перехватывает вызовы `new target()`.
  • -
+- [getPrototypeOf](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler/has) -- перехватывает обращение к методу `getPrototypeOf`. +- [setPrototypeOf](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler/setPrototypeOf) -- перехватывает обращение к методу `setPrototypeOf`. +- [isExtensible](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler/isExtensible) -- перехватывает обращение к методу `isExtensible`. +- [preventExtensions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler/preventExtensions) -- перехватывает обращение к методу `preventExtensions`. +- [getOwnPropertyDescriptor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler/getOwnPropertyDescriptor) -- перехватывает обращение к методу `getOwnPropertyDescriptor`. +- [defineProperty](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler/defineProperty) -- перехватывает обращение к методу `defineProperty`. +- [has](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler/has) -- перехватывает проверку существования свойства, которая используется в операторе `in` и в некоторых других методах встроенных объектов. +- [get](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler/get) -- перехватывает чтение свойства. +- [set](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler/set) -- перехватывает запись свойства. +- [deleteProperty](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler/deleteProperty) -- перехватывает удаление свойства оператором `delete`. +- [enumerate](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler/enumerate) -- срабатывает при вызове `for..in` или `for..of`, возвращает итератор для свойств объекта. +- [ownKeys](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler/ownKeys) -- перехватывает обращения к методу `getOwnPropertyNames`. +- [apply](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler/apply) -- перехватывает вызовы `target()`. +- [construct](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler/construct) -- перехватывает вызовы `new target()`. Каждый перехватчик запускается с `handler` в качестве `this`. Это означает, что `handler` кроме ловушек может содержать и другие полезные свойства и методы. @@ -376,13 +351,9 @@ alert( user.name ); // Ilya `Proxy` позволяет модифицировать поведение объекта как угодно, перехватывать любые обращения к его свойствам и методам, включая вызовы для функций. -Особенно приятна возможность перехватывать обращения к отсутствующим свойствам, разработчики ожидали её уже давно. +Особенно приятна возможность перехватывать обращения к отсутствующим свойствам, разработчики ожидали её уже давно. Что касается поддержки, то возможности полифиллов здесь ограничены. "Переписать" прокси на старом JavaScript сложновато, учитывая низкоуровневые возможности, которые он даёт. Поэтому нужна именно браузерная поддержка. [Постепенно](https://kangax.github.io/compat-table/es6/) она реализуется. - - - - diff --git a/1-js/10-es-modern/2-let-const/article.md b/1-js/10-es-modern/2-let-const/article.md index 1b5f4f17..e5566a61 100644 --- a/1-js/10-es-modern/2-let-const/article.md +++ b/1-js/10-es-modern/2-let-const/article.md @@ -11,205 +11,178 @@ let a = 5; У объявлений переменной через `let` есть три основных отличия от `var`: -
    -
  1. **Область видимости переменной `let` -- блок `{...}`.** +1. **Область видимости переменной `let` -- блок `{...}`.** -Как мы помним, переменная, объявленная через `var`, видна везде в функции. + Как мы помним, переменная, объявленная через `var`, видна везде в функции. -Переменная, объявленная через `let`, видна только в рамках блока `{...}`, в котором объявлена. + Переменная, объявленная через `let`, видна только в рамках блока `{...}`, в котором объявлена. -Это, в частности, влияет на объявления внутри `if`, `while` или `for`. + Это, в частности, влияет на объявления внутри `if`, `while` или `for`. -Например, переменная через `var`: + Например, переменная через `var`: -```js -//+ run -'use strict'; - -var apples = 5; - -if (true) { - var apples = 10; - - alert(apples); // 10 (внутри блока) -} - -alert(apples); // 10 (снаружи блока то же самое) -``` - -В примере выше `apples` -- одна переменная на весь код, которая модифицируется в `if`. - -То же самое с `let` будет работать по-другому: - -```js -//+ run -'use strict'; + ```js run + var apples = 5; -let apples = 5; // (*) + if (true) { + var apples = 10; -if (true) { - let apples = 10; + alert(apples); // 10 (внутри блока) + } - alert(apples); // 10 (внутри блока) -} + alert(apples); // 10 (снаружи блока то же самое) + ``` -*!* -alert(apples); // 5 (снаружи блока значение не изменилось) -*/!* -``` - -Здесь, фактически, две независимые переменные `apples`, одна -- глобальная, вторая -- в блоке `if`. - -Заметим, что если объявление `let apples` в первой строке `(*)` удалить, то в последнем `alert` будет ошибка: переменная неопределена: - -```js -//+ run -'use strict'; + В примере выше `apples` -- одна переменная на весь код, которая модифицируется в `if`. -if (true) { - let apples = 10; + То же самое с `let` будет работать по-другому: - alert(apples); // 10 (внутри блока) -} - -*!* -alert(apples); // ошибка! -*/!* -``` + ```js run + let apples = 5; // (*) + if (true) { + let apples = 10; -Это потому что переменная `let` всегда видна именно в том блоке, где объявлена, и не более. + alert(apples); // 10 (внутри блока) + } -
  2. -
  3. **Переменная `let` видна только после объявления.** + *!* + alert(apples); // 5 (снаружи блока значение не изменилось) + */!* + ``` -Как мы помним, переменные `var` существуют и до объявления. Они равны `undefined`: + Здесь, фактически, две независимые переменные `apples`, одна -- глобальная, вторая -- в блоке `if`. -```js -//+ run -'use strict'; + Заметим, что если объявление `let apples` в первой строке `(*)` удалить, то в последнем `alert` будет ошибка: переменная не определена: -alert(a); // undefined - -var a = 5; -``` + ```js run + if (true) { + let apples = 10; -С переменными `let` всё проще. До объявления их вообще нет. + alert(apples); // 10 (внутри блока) + } -Такой доступ приведёт к ошибке: -```js -//+ run -'use strict'; + *!* + alert(apples); // ошибка! + */!* + ``` -*!* -alert(a); // ошибка, нет такой переменной -*/!* + Это потому что переменная `let` всегда видна именно в том блоке, где объявлена, и не более. +2. **Переменная `let` видна только после объявления.** -let a = 5; -``` + Как мы помним, переменные `var` существуют и до объявления. Они равны `undefined`: -Заметим также, что переменные `let` нельзя повторно объявлять. То есть, такой код выведет ошибку: + ```js run + alert(a); // undefined -```js -//+ run -'use strict'; + var a = 5; + ``` -let x; -let x; // ошибка: переменная x уже объявлена -``` + С переменными `let` всё проще. До объявления их вообще нет. -Это -- хоть и выглядит ограничением по сравнению с `var`, но на самом деле проблем не создаёт. Например, два таких цикла совсем не конфликтуют: -```js -//+ run -'use strict'; + Такой доступ приведёт к ошибке: + ```js run + *!* + alert(a); // ошибка, нет такой переменной + */!* -// каждый цикл имеет свою переменную i -for(let i = 0; i<10; i++) { /* … */ } -for(let i = 0; i<10; i++) { /* … */ } + let a = 5; + ``` -alert( i ); // ошибка: глобальной i нет -``` + Заметим также, что переменные `let` нельзя повторно объявлять. То есть, такой код выведет ошибку: -При объявлении внутри цикла переменная `i` будет видна только в блоке цикла. Она не видна снаружи, поэтому будет ошибка в последнем `alert`. + ```js run + let x; + let x; // ошибка: переменная x уже объявлена + ``` + Это -- хоть и выглядит ограничением по сравнению с `var`, но на самом деле проблем не создаёт. Например, два таких цикла совсем не конфликтуют: + ```js run + // каждый цикл имеет свою переменную i + for(let i = 0; i<10; i++) { /* … */ } + for(let i = 0; i<10; i++) { /* … */ } -
  4. -
  5. **При использовании в цикле, для каждой итерации создаётся своя переменная.** + alert( i ); // ошибка: глобальной i нет + ``` -Переменная `var` -- одна на все итерации цикла и видна даже после цикла: + При объявлении внутри цикла переменная `i` будет видна только в блоке цикла. Она не видна снаружи, поэтому будет ошибка в последнем `alert`. +3. **При использовании в цикле, для каждой итерации создаётся своя переменная.** -```js -//+ run -for(var i=0; i<10; i++) { /* … */ } + Переменная `var` -- одна на все итерации цикла и видна даже после цикла: -alert(i); // 10 -``` + ```js run + for(var i=0; i<10; i++) { /* … */ } -С переменной `let` -- всё по-другому. + alert(i); // 10 + ``` -Каждому повторению цикла соответствует своя независимая переменная `let`. Если внутри цикла есть вложенные объявления функций, то в замыкании каждой будет та переменная, которая была при соответствующей итерации. + С переменной `let` -- всё по-другому. -Это позволяет легко решить классическую проблему с замыканиями, описанную в задаче [](/task/make-army). + Каждому повторению цикла соответствует своя независимая переменная `let`. Если внутри цикла есть вложенные объявления функций, то в замыкании каждой будет та переменная, которая была при соответствующей итерации. -```js -//+ run -'use strict'; + Это позволяет легко решить классическую проблему с замыканиями, описанную в задаче . -function makeArmy() { + ```js run + function makeArmy() { - let shooters = []; + let shooters = []; - for (*!*let*/!* i = 0; i < 10; i++) { - shooters.push(function() { - alert( i ); // выводит свой номер - }); - } + for (*!*let*/!* i = 0; i < 10; i++) { + shooters.push(function() { + alert( i ); // выводит свой номер + }); + } - return shooters; -} + return shooters; + } -var army = makeArmy(); + var army = makeArmy(); -army[0](); // 0 -army[5](); // 5 -``` + army[0](); // 0 + army[5](); // 5 + ``` -Если бы объявление было `var i`, то была бы одна переменная `i` на всю функцию, и вызовы в последних строках выводили бы `10` (подробнее -- см. задачу [](/task/make-army)). + Если бы объявление было `var i`, то была бы одна переменная `i` на всю функцию, и вызовы в последних строках выводили бы `10` (подробнее -- см. задачу ). -А выше объявление `let i` создаёт для каждого повторения блока в цикле свою переменную, которую функция и получает из замыкания в последних строках. -
  6. -
+ А выше объявление `let i` создаёт для каждого повторения блока в цикле свою переменную, которую функция и получает из замыкания в последних строках. ## const Объявление `const` задаёт константу, то есть переменную, которую нельзя менять: -```js -//+ run -'use strict'; - +```js run const apple = 5; apple = 10; // ошибка ``` В остальном объявление `const` полностью аналогично `let`. -[smart header="константы и КОНСТАНТЫ"] -Константы, которые жёстко заданы всегда, во время всей программы, обычно пишутся в верхнем регистра. Например: const ORANGE = "#ffa500". +Заметим, что если в константу присвоен объект, то от изменения защищена сама константа, но не свойства внутри неё: + +```js +const user = { + name: "Вася" +}; + +user.name = "Петя"; // допустимо +user = 5; // нельзя, будет ошибка +``` + +То же самое верно, если константе присвоен массив или другое объектное значение. + +```smart header="константы и КОНСТАНТЫ" +Константы, которые жёстко заданы всегда, во время всей программы, обычно пишутся в верхнем регистре. Например: `const ORANGE = "#ffa500"`. Большинство переменных -- константы в другом смысле: они не меняются после присвоения. Но при разных запусках функции это значение может быть разным. Для таких переменных можно использовать `const` и обычные строчные буквы в имени. -[/smart] +``` ## Итого - Переменные `let`: -
    -
  • Видны только после объявления и только в текущем блоке.
  • -
  • Нельзя переобъявлять (в том же блоке).
  • -
  • При объявлении переменной в цикле `for(let …)` -- она видна только в этом цикле. Причём каждой итерации соответствует своя переменная `let`.
  • -
+- Видны только после объявления и только в текущем блоке. +- Нельзя переобъявлять (в том же блоке). +- При объявлении переменной в цикле `for(let …)` -- она видна только в этом цикле. Причём каждой итерации соответствует своя переменная `let`. Переменная `const` -- это константа, в остальном -- как `let`. diff --git a/1-js/10-es-modern/3-destructuring/article.md b/1-js/10-es-modern/3-destructuring/article.md index 1c095ad9..1fda9d8a 100644 --- a/1-js/10-es-modern/3-destructuring/article.md +++ b/1-js/10-es-modern/3-destructuring/article.md @@ -20,8 +20,7 @@ alert(lastName); // Кантор Ненужные элементы массива также можно отбросить, поставив лишнюю запятую: -```js -//+ run +```js run 'use strict'; *!* @@ -38,8 +37,7 @@ alert(title); // Император Если мы хотим получить и последующие значения массива, но не уверены в их числе -- можно добавить ещё один параметр, который получит "всё остальное", при помощи оператора `"..."` ("spread", троеточие): -```js -//+ run +```js run 'use strict'; *!* @@ -57,9 +55,7 @@ alert(rest); // Император,Рима (массив из 2х элем Если значений в массиве меньше, чем переменных -- ошибки не будет, просто присвоится `undefined`: - -```js -//+ run +```js run 'use strict'; *!* @@ -71,8 +67,7 @@ alert(firstName); // undefined Впрочем, как правило, в таких случаях задают значение по умолчанию. Для этого нужно после переменной использовать символ `=` со значением, например: -```js -//+ run +```js run 'use strict'; *!* @@ -86,8 +81,7 @@ alert(lastName); // Анонимный В качестве значений по умолчанию можно использовать не только примитивы, но и выражения, даже включающие в себя вызовы функций: -```js -//+ run +```js run 'use strict'; function defaultLastName() { @@ -103,7 +97,7 @@ alert(firstName); // Вася alert(lastName); // 1436...-visitor ``` -Заметим, что вызов функции `defaultLastName()` для генерации значения по умолчанию будет осуществлён только при необходимости, то есть если значения нет в массиве. +Заметим, что вызов функции `defaultLastName()` для генерации значения по умолчанию будет осуществлён только при необходимости, то есть если значения нет в массиве. ## Деструктуризация объекта @@ -118,8 +112,7 @@ let {var1, var2} = {var1:…, var2…} Например: -```js -//+ run +```js run 'use strict'; let options = { @@ -141,8 +134,7 @@ alert(height); // 200 Если хочется присвоить свойство объекта в переменную с другим именем, например, чтобы свойство `options.width` пошло в переменную `w`, то можно указать соответствие через двоеточие, вот так: -```js -//+ run +```js run 'use strict'; let options = { @@ -160,12 +152,11 @@ alert(w); // 100 alert(h); // 200 ``` -В примере выше свойство `width` отправилось в переменную `w`, свойство `height` -- в переменную `h`, а `title` -- в переменную с тем же названием. +В примере выше свойство `width` отправилось в переменную `w`, свойство `height` -- в переменную `h`, а `title` -- в переменную с тем же названием. Если каких-то свойств в объекте нет, можно указать значение по умолчанию через знак равенства `=`, вот так; -```js -//+ run +```js run 'use strict'; let options = { @@ -183,9 +174,7 @@ alert(height); // 200 Можно и сочетать одновременно двоеточие и равенство: - -```js -//+ run +```js run 'use strict'; let options = { @@ -205,8 +194,7 @@ alert(h); // 200 Такой возможности в текущем стандарте нет. Она планируется в будущем стандарте, и выглядеть она будет примерно так: -```js -//+ run +```js run 'use strict'; let options = { @@ -225,15 +213,13 @@ let {title, ...size} = options; Этот код будет работать, например, при использовании Babel со включёнными экспериментальными возможностями, но ещё раз заметим, что в текущий стандарт такая возможность не вошла. -[smart header="Деструктуризация без объявления"] - +````smart header="Деструктуризация без объявления" В примерах выше переменные объявлялись прямо перед присваиванием: `let {…} = {…}`. Конечно, можно и без `let`, использовать уже существующие переменные. Однако, здесь есть небольшой "подвох". В JavaScript, если в основном потоке кода (не внутри другого выражения) встречается `{...}`, то это воспринимается как блок. Например, можно использовать такой блок для ограничения видимости переменных: -```js -//+ run +```js run 'use strict'; { // вспомогательные переменные, локальные для блока @@ -258,7 +244,7 @@ let a, b; let a, b; ({a, b} = {a:5, b:6}); // внутри выражения это уже не блок ``` -[/smart] +```` ## Вложенные деструктуризации @@ -268,8 +254,7 @@ let a, b; В коде ниже `options` содержит подобъект и подмассив. В деструктуризации ниже сохраняется та же структура: -```js -//+ run +```js run 'use strict'; let options = { @@ -292,33 +277,24 @@ alert(item2); // Пирожное Как видно, весь объект `options` корректно разбит на переменные. - ## Итого -
    -
  • Деструктуризация позволяет разбивать объект или массив на переменные при присвоении.
  • -
  • Синтаксис: -```js -let {prop : varName = default, ...} = object -``` - -Здесь двоеточие `:` задаёт отображение свойства `prop` в переменную `varName`, а равенство `=default` задаёт выражение, которое будет использовано, если значение отсутствует (не указано или `undefined`). - -Для массивов имеет значение порядок, поэтому нельзя использовать `:`, но значение по умолчанию -- можно: - -```js -let [var1 = default, var2, ...rest] = array -``` - -Объявление переменной в начале конструкции не обязательно. Можно использовать и существующие переменные. Однако при деструктуризации объекта может потребоваться обернуть выражение в скобки. -
  • -
  • Вложенные объекты и массивы тоже работают, при деструктуризации нужно лишь сохранить ту же структуру, что и исходный объект/массив.
  • -
- -Как мы увидим далее, деструктуризации особенно удобны при чтении объектных параметров функций. +- Деструктуризация позволяет разбивать объект или массив на переменные при присвоении. +- Синтаксис: + ```js + let {prop : varName = default, ...} = object + ``` + Здесь двоеточие `:` задаёт отображение свойства `prop` в переменную `varName`, а равенство `=default` задаёт выражение, которое будет использовано, если значение отсутствует (не указано или `undefined`). + Для массивов имеет значение порядок, поэтому нельзя использовать `:`, но значение по умолчанию -- можно: + ```js + let [var1 = default, var2, ...rest] = array + ``` + Объявление переменной в начале конструкции не обязательно. Можно использовать и существующие переменные. Однако при деструктуризации объекта может потребоваться обернуть выражение в скобки. +- Вложенные объекты и массивы тоже работают, при деструктуризации нужно лишь сохранить ту же структуру, что и исходный объект/массив. +Как мы увидим далее, деструктуризации особенно удобны при чтении объектных параметров функций. diff --git a/1-js/10-es-modern/4-es-function/article.md b/1-js/10-es-modern/4-es-function/article.md index ab0d40e3..fc5ac186 100644 --- a/1-js/10-es-modern/4-es-function/article.md +++ b/1-js/10-es-modern/4-es-function/article.md @@ -7,10 +7,9 @@ Можно указывать параметры по умолчанию через равенство `=`, например: -```js -//+ run +```js run function showMenu(title = "Без заголовка", width = 100, height = 200) { - alert(`${title} ${width} ${height}`); + alert(title + ' ' + width + ' ' + height); } showMenu("Меню"); // Меню 100 200 @@ -18,76 +17,69 @@ showMenu("Меню"); // Меню 100 200 Параметр по умолчанию используется при отсутствующем аргументе или равном `undefined`, например: -```js -//+ run +```js run function showMenu(title = "Заголовок", width = 100, height = 200) { - alert(`title=${title} width=${width} height=${height}`); + alert('title=' + title + ' width=' + width + ' height=' + height); } // По умолчанию будут взяты 1 и 3 параметры // title=Заголовок width=null height=200 -showMenu(undefined, null); +showMenu(undefined, null); ``` -При передаче любого значения, кроме `undefined`, включая пустую строку, ноль или `null`, параметр считается переданным, и значение по умолчание не используется. +При передаче любого значения, кроме `undefined`, включая пустую строку, ноль или `null`, параметр считается переданным, и значение по умолчанию не используется. **Параметры по умолчанию могут быть не только значениями, но и выражениями.** Например: -```js -//+ run +```js run function sayHi(who = getCurrentUser().toUpperCase()) { - alert(`Привет, ${who}!`); + alert('Привет, ' + who); } function getCurrentUser() { return 'Вася'; } -sayHi(); // Привет, ВАСЯ! +sayHi(); // Привет, ВАСЯ ``` Заметим, что значение выражения `getCurrentUser().toUpperCase()` будет вычислено, и соответствующие функции вызваны -- лишь в том случае, если это необходимо, то есть когда функция вызвана без параметра. В частности, выражение по умолчанию не вычисляется при объявлении функции. В примере выше функция `getCurrentUser()` будет вызвана именно в последней строке, так как не передан параметр. - ## Оператор spread вместо arguments Чтобы получить массив аргументов, можно использовать оператор `…`, например: -```js -//+ run - +```js run function showName(firstName, lastName, *!*...rest*/!*) { - alert(`${firstName} ${lastName} - ${rest}`); + alert(firstName + ' ' + lastName + ' - ' + rest); } // выведет: Юлий Цезарь - Император,Рима showName("Юлий", "Цезарь", "Император", "Рима"); ``` -В `rest` попадёт массив всех аргументов, начиная со второго. +В `rest` попадёт массив всех аргументов, начиная с третьего. Заметим, что `rest` -- настоящий массив, с методами `map`, `forEach` и другими, в отличие от `arguments`. -[warn header="Оператор … должен быть в конце"] - +````warn header="Оператор … должен быть в конце" Оператор `…` собирает "все оставшиеся" аргументы, поэтому такое объявление не имеет смысла: ```js function f(arg1, ...rest, arg2) { // arg2 после ...rest ?! // будет ошибка } ``` -Параметр `...rest` должен быть в конце функции. -[/warn] +Параметр `...rest` должен быть в конце функции. +```` Выше мы увидели использование `...` для чтения параметров в объявлении функции. Но этот же оператор можно использовать и при вызове функции, для передачи массива параметров как списка, например: -```js -//+ run +```js run 'use strict'; let numbers = [2, 3, 15]; @@ -112,8 +104,7 @@ Math.max.apply(Math, numbers); Если функция получает объект, то она может его тут же разбить в переменные: -```js -//+ run +```js run 'use strict'; let options = { @@ -125,7 +116,7 @@ let options = { *!* function showMenu({title, width, height}) { */!* - alert(`${title} ${width} ${height}`); // Меню 100 200 + alert(title + ' ' + width + ' ' + height); // Меню 100 200 } showMenu(options); @@ -133,8 +124,7 @@ showMenu(options); Можно использовать и более сложную деструктуризацию, с соответствиями и значениями по умолчанию: -```js -//+ run +```js run 'use strict'; let options = { @@ -144,7 +134,7 @@ let options = { *!* function showMenu({title="Заголовок", width:w=100, height:h=200}) { */!* - alert(`${title} ${w} ${h}`); + alert(title + ' ' + w + ' ' + h); } // объект options будет разбит на переменные @@ -155,12 +145,11 @@ showMenu(options); // Меню 100 200 Если хочется, чтобы функция могла быть вызвана вообще без аргументов -- нужно добавить ей параметр по умолчанию -- уже не внутрь деструктуризации, а в самом списке аргументов: -```js -//+ run +```js run 'use strict'; function showMenu({title="Заголовок", width:w=100, height:h=200} *!*= {}*/!*) { - alert(`${title} ${w} ${h}`); + alert(title + ' ' + w + ' ' + h); } showMenu(); // Заголовок 100 200 @@ -174,15 +163,14 @@ showMenu(); // Заголовок 100 200 Например: -```js -//+ run +```js run 'use strict'; function f() {} // f.name == "f" let g = function g() {}; // g.name == "g" -alert(`${f.name} ${g.name}`) // f g +alert(f.name + ' ' + g.name) // f g ``` В примере выше показаны Function Declaration и Named Function Expression. В синтаксисе выше довольно очевидно, что у этих функций есть имя `name`. В конце концов, оно указано в объявлении. @@ -197,12 +185,12 @@ alert(`${f.name} ${g.name}`) // f g 'use strict'; // свойство g.name = "g" -let g = function() {}; +let g = function() {}; let user = { // свойство user.sayHi.name == "sayHi" - sayHi: function() { }; -} + sayHi: function() {} +}; ``` ## Функции в блоке @@ -211,15 +199,14 @@ let user = { Например: -```js -//+ run +```js run 'use strict'; if (true) { sayHi(); // работает - function sayHi() { + function sayHi() { alert("Привет!"); } @@ -234,8 +221,7 @@ sayHi(); // ошибка, функции не существует Появился новый синтаксис для задания функций через "стрелку" `=>`. Его простейший вариант выглядит так: -```js -//+ run +```js run 'use strict'; *!* @@ -257,8 +243,7 @@ let inc = function(x) { return x + 1; }; Если аргументов несколько, то нужно обернуть их в скобки, вот так: -```js -//+ run +```js run 'use strict'; *!* @@ -266,20 +251,19 @@ let sum = (a,b) => a + b; */!* // аналог с function -// let inc = function(a, b) { return a + b; }; +// let sum = function(a, b) { return a + b; }; alert( sum(1, 2) ); // 3 ``` Если нужно задать функцию без аргументов, то также используются скобки, в этом случае -- пустые: -```js -//+ run +```js run 'use strict'; *!* // вызов getTime() будет возвращать текущее время -let getTime = () => `${new Date().getHours()} : ${new Date().getMinutes()}`; +let getTime = () => new Date().getHours() + ':' + new Date().getMinutes(); */!* alert( getTime() ); // текущее время @@ -287,9 +271,7 @@ alert( getTime() ); // текущее время Когда тело функции достаточно большое, то можно его обернуть в фигурные скобки `{…}`: - -```js -//+ run +```js run 'use strict'; *!* @@ -297,7 +279,7 @@ let getTime = () => { let date = new Date(); let hours = date.getHours(); let minutes = date.getMinutes(); - return `${hours}:${minutes}`; + return hours + ':' + minutes; }; */!* @@ -308,14 +290,13 @@ alert( getTime() ); // текущее время Функции-стрелки очень удобны в качестве коллбеков, например: -```js -//+ run +```js run `use strict`; let arr = [5, 8, 3]; *!* -let sorted = arr.sort( (a,b) => a - b ); +let sorted = arr.sort( (a,b) => a - b ); */!* alert(sorted); // 3, 5, 8 @@ -325,12 +306,11 @@ alert(sorted); // 3, 5, 8 ## Функции-стрелки не имеют своего this -Внутри функций-стрелок -- тот же `this`, что и снаружи. +Внутри функций-стрелок -- тот же `this`, что и снаружи. Это очень удобно в обработчиках событий и коллбэках, например: -```js -//+ run +```js run 'use strict'; let group = { @@ -339,8 +319,8 @@ let group = { showList: function() { *!* - this.students.forEach( - (student) => alert(`${this.title}: ${student}`) + this.students.forEach( + student => alert(this.title + ': ' + student) ) */!* } @@ -356,8 +336,7 @@ group.showList(); Если бы в `forEach` вместо функции-стрелки была обычная функция, то была бы ошибка: -```js -//+ run +```js run 'use strict'; let group = { @@ -367,7 +346,7 @@ let group = { showList: function() { *!* this.students.forEach(function(student) { - alert(`${this.title}: ${student}`); // будет ошибка + alert(this.title + ': ' + student); // будет ошибка }) */!* } @@ -378,9 +357,16 @@ group.showList(); При запуске будет "попытка прочитать свойство `title` у `undefined`", так как `.forEach(f)` при запуске `f` не ставит `this`. То есть, `this` внутри `forEach` будет `undefined`. -[warn header="Функции стрелки нельзя запускать с `new`"] +```warn header="Функции стрелки нельзя запускать с `new`" Отсутствие у функции-стрелки "своего `this`" влечёт за собой естественное ограничение: такие функции нельзя использовать в качестве конструктора, то есть нельзя вызывать через `new`. -[/warn] +``` + +```smart header="=> это не то же самое, что `.bind(this)`" +Есть тонкое различие между функцией стрелкой `=>` и обычной функцией, у которой вызван `.bind(this)`: + +- Вызовом `.bind(this)` мы передаём текущий `this`, привязывая его к функции. +- При `=>` привязки не происходит, так как функция стрелка вообще не имеет контекста `this`. Поиск `this` в ней осуществляется так же, как и поиск обычной переменной, то есть, выше в замыкании. До появления стандарта ES-2015 такое было невозможно. +``` ## Функции-стрелки не имеют своего arguments @@ -388,8 +374,7 @@ group.showList(); Например: -```js -//+ run +```js run 'use strict'; function f() { @@ -406,8 +391,7 @@ f(1); // 1 Например, декоратор `defer(f, ms)` ниже получает функцию `f` и возвращает обёртку вокруг неё, откладывающую вызов на `ms` миллисекунд: -```js -//+ run +```js run 'use strict'; *!* @@ -418,12 +402,12 @@ function defer(f, ms) { } */!* -function sayHi(who) { - alert(`Привет, ${who}!`); +function sayHi(who) { + alert('Привет, ' + who); } let sayHiDeferred = defer(sayHi, 2000); -sayHiDeferred("Вася"); // Привет, Вася! через 2 секунды +sayHiDeferred("Вася"); // Привет, Вася через 2 секунды ``` Аналогичная реализация без функции-стрелки выглядела бы так: @@ -433,7 +417,7 @@ function defer(f, ms) { return function() { *!* let args = arguments; - let ctx = this; + let ctx = this; */!* setTimeout(function() { return f.apply(ctx, args); @@ -444,26 +428,18 @@ function defer(f, ms) { В этом коде пришлось создавать дополнительные переменные `args` и `ctx` для передачи внешних аргументов и контекста через замыкание. - ## Итого Основные улучшения в функциях: -
    -
  • Можно задавать параметры по умолчанию, а также использовать деструктуризацию для чтения приходящего объекта.
  • -
  • Оператор spread (троеточие) в объявлении позволяет функции получать оставшиеся аргументы в массив: `function f(arg1, arg2, ...rest)`.
  • -
  • Тот же оператор spread в вызове функции позволяет передать её массив как список аргументов (вместо `apply`).
  • -
  • У функции есть свойство `name`, оно содержит имя, указанное при объявлении функции, либо, если его нет, то имя свойства или переменную, в которую она записана. Есть и некоторые другие ситуации, в которых интерпретатор подставляет "самое подходящее" имя.
  • -
  • Объявление Function Declaration в блоке `{...}` видно только в этом блоке.
  • -
  • Появились функции-стрелки: -
      -
    • Без фигурных скобок возвращают выражение `expr`: `(args) => expr`.
    • -
    • С фигурными скобками требуют явного `return`.
    • -
    • Сохраняют `this` и `arguments` окружающего контекста.
    • -
    • Не могут быть использованы как конструкторы, с `new`.
    • -
    -
  • -
- - +- Можно задавать параметры по умолчанию, а также использовать деструктуризацию для чтения приходящего объекта. +- Оператор spread (троеточие) в объявлении позволяет функции получать оставшиеся аргументы в массив: `function f(arg1, arg2, ...rest)`. +- Тот же оператор spread в вызове функции позволяет передать её массив как список аргументов (вместо `apply`). +- У функции есть свойство `name`, оно содержит имя, указанное при объявлении функции, либо, если его нет, то имя свойства или переменную, в которую она записана. Есть и некоторые другие ситуации, в которых интерпретатор подставляет "самое подходящее" имя. +- Объявление Function Declaration в блоке `{...}` видно только в этом блоке. +- Появились функции-стрелки: + - Без фигурных скобок возвращают выражение `expr`: `(args) => expr`. + - С фигурными скобками требуют явного `return`. + - Не имеют своих `this` и `arguments`, при обращении получают их из окружающего контекста. + - Не могут быть использованы как конструкторы, с `new`. diff --git a/1-js/10-es-modern/5-es-string/article.md b/1-js/10-es-modern/5-es-string/article.md index f28c4b8a..14f31a96 100644 --- a/1-js/10-es-modern/5-es-string/article.md +++ b/1-js/10-es-modern/5-es-string/article.md @@ -1,5 +1,5 @@ -# Строки +# Строки Есть ряд улучшений и новых методов для строк. @@ -7,40 +7,35 @@ ## Строки-шаблоны -Добавлен новый вид кавычек для строк: +Добавлен новый вид кавычек для строк: ```js let str = `обратные кавычки`; ``` Основные отличия от двойных `"…"` и одинарных `'…'` кавычек: -
    -
  • **В них разрешён перевод строки.** +- **В них разрешён перевод строки.** -Например: -```js -//+ run -alert(`моя - многострочная - строка`); -``` -Заметим, что пробелы и, собственно, перевод строки также входят в строку, и будут выведены. -
  • -
  • **Можно вставлять выражения при помощи `${…}`.** + Например: + ```js run + alert(`моя + многострочная + строка`); + ``` -Например: -```js -//+ run -'use strict'; -let apples = 2; -let oranges = 3; + Заметим, что пробелы и, собственно, перевод строки также входят в строку, и будут выведены. +- **Можно вставлять выражения при помощи `${…}`.** -alert(`${apples} + ${oranges} = ${apples + oranges}`); // 2 + 3 = 5 -``` + Например: + ```js run + 'use strict'; + let apples = 2; + let oranges = 3; -Как видно, при помощи `${…}` можно вставлять как и значение переменной `${apples}`, так и более сложные выражения, которые могут включать в себя операторы, вызовы функций и т.п. Такую вставку называют "интерполяцией". -
  • -
+ alert(`${apples} + ${oranges} = ${apples + oranges}`); // 2 + 3 = 5 + ``` + + Как видно, при помощи `${…}` можно вставлять как и значение переменной `${apples}`, так и более сложные выражения, которые могут включать в себя операторы, вызовы функций и т.п. Такую вставку называют "интерполяцией". ## Функции шаблонизации @@ -55,8 +50,7 @@ let str = func`моя строка`; Например: -```js -//+ run +```js run 'use strict'; function f(strings, ...values) { @@ -68,34 +62,31 @@ function f(strings, ...values) { let apples = 3; let oranges = 5; -// | s[0] | v[0] |s[1]| v[1] |s[2] | v[2] |s[3] +// | s[0] | v[0] |s[1]| v[1] |s[2] | v[2] |s[3] let str = f`Sum of ${apples} + ${oranges} =\n ${apples + oranges}!`; ``` -В примере выше видно, что строка разбивается по очереди на части: "кусок строки" -- "параметр" -- "кусок строки" -- "параметр". +В примере выше видно, что строка разбивается по очереди на части: "кусок строки" -- "параметр" -- "кусок строки" -- "параметр". -
    -
  • Участки строки идут в первый аргумент-массив `strings`.
  • -
  • У этого массива есть дополнительное свойство `strings.raw`. В нём находятся строки в точности как в оригинале. Это влияет на спец-символы, например в `strings` символ `\n` -- это перевод строки, а в `strings.raw` -- это именно два символа `\n`.
  • -
  • Дальнейший список аргументов функции шаблонизации -- это значения выражений в `${...}`, в данном случае их три.
  • -
+- Участки строки идут в первый аргумент-массив `strings`. +- У этого массива есть дополнительное свойство `strings.raw`. В нём находятся строки в точности как в оригинале. Это влияет на спец-символы, например в `strings` символ `\n` -- это перевод строки, а в `strings.raw` -- это именно два символа `\n`. +- Дальнейший список аргументов функции шаблонизации -- это значения выражений в `${...}`, в данном случае их три. -[smart header="Зачем `strings.raw`?"] +```smart header="Зачем `strings.raw`?" В отличие от `strings`, в `strings.raw` содержатся участки строки в "изначально введённом" виде. То есть, если в строке находится `\n` или `\u1234` или другое особое сочетание символов, то оно таким и останется. Это нужно в тех случаях, когда функция шаблонизации хочет произвести обработку полностью самостоятельно (свои спец. символы?). Или же когда обработка спец. символов не нужна -- например, строка содержит "обычный текст", набранный непрограммистом без учёта спец. символов. -[/smart] +``` Как видно, функция имеет доступ ко всему: к выражениям, к участкам текста и даже, через `strings.raw` -- к оригинально введённому тексту без учёта стандартных спец. символов. -Функция шаблонизации может как-то преобразовать строку и вернуть новый результат. +Функция шаблонизации может как-то преобразовать строку и вернуть новый результат. В простейшем случае можно просто "склеить" полученные фрагменты в строку: -```js -//+ run +```js run 'use strict'; // str восстанавливает строку @@ -106,7 +97,7 @@ function str(strings, ...values) { str += values[i]; } - // последний кусок строки + // последний кусок строки str += strings[strings.length-1]; return str; } @@ -118,14 +109,13 @@ let oranges = 5; alert( str`Sum of ${apples} + ${oranges} = ${apples + oranges}!`); ``` -Функция `str` в примере выше делает то же самое, что обычные обратные кавычки. Но, конечно, можно пойти намного дальше. Например, генерировать из HTML-строки DOM-узлы (функции шаблонизации не обязательно возвращать именно строку). +Функция `str` в примере выше делает то же самое, что обычные обратные кавычки. Но, конечно, можно пойти намного дальше. Например, генерировать из HTML-строки DOM-узлы (функции шаблонизации не обязательно возвращать именно строку). -Или можно реализовать интернационализацию. В примере ниже функция `i18n` осуществляет перевод строки. +Или можно реализовать интернационализацию. В примере ниже функция `i18n` осуществляет перевод строки. Она подбирает по строке вида `"Hello, ${name}!"` шаблон перевода `"Привет, {0}!"` (где `{0}` -- место для вставки параметра) и возвращает переведённый результат со вставленным именем `name`: -```js -//+ run +```js run 'use strict'; let messages = { @@ -173,8 +163,7 @@ alert( i18n`Hello, ${name}!` ); // Привет, Вася! Например: -```js -//+ run +```js run alert( '我'.length ); // 1 alert( '𩷶'.length ); // 2 ``` @@ -185,8 +174,7 @@ alert( '𩷶'.length ); // 2 Ими представлены редкие математические символы, а также некоторые символы для эмоций, к примеру: -```js -//+ run +```js run alert( '𝒳'.length ); // 2, MATHEMATICAL SCRIPT CAPITAL X alert( '😂'.length ); // 2, FACE WITH TEARS OF JOY ``` @@ -195,26 +183,23 @@ alert( '😂'.length ); // 2, FACE WITH TEARS OF JOY Например, `charCodeAt` считает суррогатную пару двумя разными символами и возвращает код каждой: -```js -//+ run +```js run // как будто в строке два разных символа (на самом деле один) alert( '𝒳'.charCodeAt(0) + ' ' + '𝒳'.charCodeAt(1) ); // 55349 56499 ``` ...В то время как `codePointAt` возвращает его Unicode-код суррогатной пары правильно: -```js -//+ run +```js run // один символ с "длинным" (более 2 байт) unicode-кодом alert( '𝒳'.codePointAt(0) ); // 119987 ``` -Метод `String.fromCodePoint(code)` корректно создаёт строку из "длинного кода", в отличие от старого `String.fromCharCode(code)`. +Метод `String.fromCodePoint(code)` корректно создаёт строку из "длинного кода", в отличие от старого `String.fromCharCode(code)`. Например: -```js -//+ run +```js run // Правильно alert( String.fromCodePoint(119987) ); // 𝒳 // Неверно! @@ -223,24 +208,21 @@ alert( String.fromCharCode(119987) ); // 풳 Более старый метод `fromCharCode` в последней строке дал неверный результат, так как он берёт только первые два байта от числа `119987` и создаёт символ из них, а остальные отбрасывает. - ### \u{длинный код} -Есть и ещё синтаксическое улучшение для больших Unicode-кодов. +Есть и ещё синтаксическое улучшение для больших Unicode-кодов. В JavaScript-строках давно можно вставлять символы по Unicode-коду, вот так: -```js -//+ run +```js run alert( "\u2033" ); // ″, символ двойного штриха ``` -Синтаксис: `\uNNNN`, где `NNNN` -- четырёхзначный шестнадцатиричный код, причём он должен быть ровно четырёхзначным. +Синтаксис: `\uNNNN`, где `NNNN` -- четырёхзначный шестнадцатиричный код, причём он должен быть ровно четырёхзначным. "Лишние" цифры уже не войдут в код, например: -```js -//+ run +```js run alert( "\u20331" ); // Два символа: символ двойного штриха ″, а затем 1 ``` @@ -248,8 +230,7 @@ alert( "\u20331" ); // Два символа: символ двойного шт Например: -```js -//+ run +```js run alert( "\u{20331}" ); // 𠌱, китайский иероглиф с этим кодом ``` @@ -261,7 +242,7 @@ alert( "\u{20331}" ); // 𠌱, китайский иероглиф с этим Для генерации произвольных сочетаний используются несколько юникодных символов: основа и один или несколько значков. -Например, если после символа `S` идёт символ "точка сверху" (код `\u0307`), то показано это будет как "S с точкой сверху" `Ṡ`. +Например, если после символа `S` идёт символ "точка сверху" (код `\u0307`), то показано это будет как "S с точкой сверху" `Ṡ`. Если нужен ещё значок над той же буквой (или под ней) -- без проблем. Просто добавляем соответствующий символ. @@ -269,16 +250,14 @@ alert( "\u{20331}" ); // 𠌱, китайский иероглиф с этим Пример этого символа в JavaScript-строке: -```js -//+ run +```js run alert("S\u0307\u0323"); // Ṩ ``` Такая возможность добавить произвольной букве нужные значки, с одной стороны, необходима, а с другой стороны -- возникает проблемка: можно представить одинаковый с точки зрения визуального отображения и интерпретации символ -- разными сочетаниями Unicode-кодов. Вот пример: -```js -//+ run +```js run alert("S\u0307\u0323"); // Ṩ alert("S\u0323\u0307"); // Ṩ @@ -287,46 +266,40 @@ alert( "S\u0307\u0323" == "S\u0323\u0307" ); // false В первой строке после основы `S` идёт сначала значок "верхняя точка", а потом -- нижняя, во второй -- наоборот. По кодам строки не равны друг другу. Но символ задают один и тот же. - С целью разрешить эту ситуацию, существует *юникодная нормализация*, при которой строки приводятся к единому, "нормальному", виду. В современном JavaScript это делает метод [str.normalize()](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/String/normalize). -```js -//+ run +```js run alert( "S\u0307\u0323".normalize() == "S\u0323\u0307".normalize() ); // true ``` Забавно, что в данной конкретной ситуации `normalize()` приведёт последовательность из трёх символов к одному: [\u1e68 (S с двумя точками)](http://www.fileformat.info/info/unicode/char/1e68/index.htm). -```js -//+ run +```js run alert( "S\u0307\u0323".normalize().length ); // 1, нормализовало в один символ alert( "S\u0307\u0323".normalize() == "\u1e68" ); // true ``` Это, конечно, не всегда так, просто в данном случае оказалось, что именно такой символ в юникоде уже есть. Если добавить значков, то нормализация уже даст несколько символов. -Для большинства практических задач информации, данной выше, должно быть вполне достаточно, но если хочется более подробно ознакомиться с вариантами и правилами нормализации -- они описаны в приложении к стандарту юникод [Unicode Normalization Forms](http://www.unicode.org/reports/tr15/). +Для большинства практических задач информации, данной выше, должно быть вполне достаточно, но если хочется более подробно ознакомиться с вариантами и правилами нормализации -- они описаны в приложении к стандарту юникод [Unicode Normalization Forms](http://www.unicode.org/reports/tr15/). ## Полезные методы Добавлены ряд полезных методов общего назначения: -
    -
  • [str.includes(s)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/includes) -- проверяет, включает ли одна строка в себя другую, возвращает `true/false`.
  • -
  • [str.endsWith(s)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/endsWith) -- возвращает `true`, если строка `str` заканчивается подстрокой `s`.
  • -
  • [str.startsWith(s)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith) -- возвращает `true`, если строка `str` начинается со строки `s`.
  • -
  • [str.repeat(times)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/repeat) -- повторяет строку `str` `times` раз.
  • -
+- [str.includes(s)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/includes) -- проверяет, включает ли одна строка в себя другую, возвращает `true/false`. +- [str.endsWith(s)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/endsWith) -- возвращает `true`, если строка `str` заканчивается подстрокой `s`. +- [str.startsWith(s)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith) -- возвращает `true`, если строка `str` начинается со строки `s`. +- [str.repeat(times)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/repeat) -- повторяет строку `str` `times` раз. Конечно, всё это можно было сделать при помощи других встроенных методов, но новые методы более удобны. ## Итого Улучшения: -
    -
  • Строки-шаблоны -- для удобного задания строк (многострочных, с переменными), плюс возможность использовать функцию шаблонизации для самостоятельного форматирования.
  • -
  • Юникод -- улучшена работа с суррогатными парами.
  • -
  • Полезные методы для проверок вхождения одной строки в другую.
  • -
\ No newline at end of file + +- Строки-шаблоны -- для удобного задания строк (многострочных, с переменными), плюс возможность использовать функцию шаблонизации для самостоятельного форматирования. +- Юникод -- улучшена работа с суррогатными парами. +- Полезные методы для проверок вхождения одной строки в другую. diff --git a/1-js/10-es-modern/6-es-object/article.md b/1-js/10-es-modern/6-es-object/article.md index d298c03b..2e56ffa5 100644 --- a/1-js/10-es-modern/6-es-object/article.md +++ b/1-js/10-es-modern/6-es-object/article.md @@ -9,12 +9,11 @@ Зачастую у нас есть переменные, например, `name` и `isAdmin`, и мы хотим использовать их в объекте. -При объявлении объекта в этом случае достаточно указать только имя свойства, а значение будет взято из переменной с таким именем. +При объявлении объекта в этом случае достаточно указать только имя свойства, а значение будет взято из переменной с аналогичным именем. Например: -```js -//+ run +```js run 'use strict'; let name = "Вася"; @@ -22,20 +21,18 @@ let isAdmin = true; *!* let user = { - name, + name, isAdmin }; */!* alert( JSON.stringify(user) ); // {"name": "Вася", "isAdmin": true} ``` - ## Вычисляемые свойства В качестве имени свойства можно использовать выражение, например: -```js -//+ run +```js run 'use strict'; let propName = "firstName"; @@ -51,8 +48,7 @@ alert( user.firstName ); // Вася Или даже так: -```js -//+ run +```js run 'use strict'; let a = "Мой "; @@ -61,27 +57,24 @@ let c = "Крокодил"; let user = { *!* - [(a + b + c).toLowerCase()]: "Вася" + [(a + b + c).toLowerCase()]: "Гена" */!* }; -alert( user["мой зелёный крокодил"] ); // Вася +alert( user["мой зелёный крокодил"] ); // Гена ``` - ## Геттер-сеттер для прототипа В ES5 для прототипа был метод-геттер: -
    -
  • `Object.getPrototypeOf(obj)`
  • -
+ +- `Object.getPrototypeOf(obj)` В ES-2015 также добавился сеттер: -
    -
  • `Object.setPrototypeOf(obj, newProto)`
  • -
-...А также "узаконено" свойство `__proto__`, которое даёт прямой доступ к прототипу. Его, в качестве "нестандартного", но удобного способа работы с прототипом реализовали почти все браузеры (кроме IE10-), так что было принято решение добавить его в стандарт. +- `Object.setPrototypeOf(obj, newProto)` + +...А также "узаконено" свойство `__proto__`, которое даёт прямой доступ к прототипу. Его, в качестве "нестандартного", но удобного способа работы с прототипом, реализовали почти все браузеры (кроме IE10-), так что было принято решение добавить его в стандарт. ## Object.assign @@ -96,8 +89,7 @@ Object.assign(target, src1, src2...) Например: -```js -//+ run +```js run 'use strict'; let user = { name: "Вася" }; @@ -107,7 +99,7 @@ let admin = { isAdmin: true }; Object.assign(user, visitor, admin); // user <- visitor <- admin -alert( JSON.stringify(user) ); // user: Вася, visits: true, isAdmin: true +alert( JSON.stringify(user) ); // name: Вася, visits: true, isAdmin: true ``` Его также можно использовать для 1-уровневого клонирования объекта: @@ -123,7 +115,6 @@ let clone = Object.assign({}, user); */!* ``` - ## Object.is(value1, value2) Новая функция для проверки равенства значений. @@ -132,9 +123,7 @@ let clone = Object.assign({}, user); Она похожа на обычное строгое равенство `===`, но есть отличия: -```js -//+ run - +```js run // Сравнение +0 и -0 alert( Object.is(+0, -0)); // false alert( +0 === -0 ); // true @@ -146,34 +135,30 @@ alert( NaN === NaN ); // false Отличия эти в большинстве ситуаций некритичны, так что непохоже, чтобы эта функция вытеснила обычную проверку `===`. Что интересно -- этот алгоритм сравнения, который называется [SameValue](http://www.ecma-international.org/ecma-262/6.0/index.html#sec-samevalue), применяется во внутренних реализациях различных методов современного стандарта. - ## Методы объекта Долгое время в JavaScript термин "метод объекта" был просто альтернативным названием для свойства-функции. -Теперь это уже не так, добавлены именно "методы объекта", которые, по сути, являются свойствами-функциями, привязанными к объекту. +Теперь это уже не так. Добавлены именно "методы объекта", которые, по сути, являются свойствами-функциями, привязанными к объекту. Их особенности: -
    -
  1. Более короткий синтаксис объявления.
  2. -
  3. Наличие в методах специального внутреннего свойства `[[HomeObject]]` ("домашний объект"), ссылающегося на объект, которому метод принадлежит. Мы посмотрим его использование чуть дальше.
  4. -
+1. Более короткий синтаксис объявления. +2. Наличие в методах специального внутреннего свойства `[[HomeObject]]` ("домашний объект"), ссылающегося на объект, которому метод принадлежит. Мы посмотрим его использование чуть дальше. Для объявления метода вместо записи `"prop: function() {…}"` нужно написать просто `"prop() { … }"`. Например: -```js -//+ run +```js run 'use strict'; let name = "Вася"; let user = { name, *!* - // вместо "sayHi: function() {" пишем "sayHi() {" - sayHi() { + // вместо "sayHi: function() {" пишем "sayHi() {" + sayHi() { alert(this.name); } */!* @@ -186,8 +171,7 @@ user.sayHi(); // Вася Также методами станут объявления геттеров `get prop()` и сеттеров `set prop()`: -```js -//+ run +```js run 'use strict'; let name = "Вася", surname="Петров"; @@ -204,14 +188,13 @@ alert( user.fullName ); // Вася Петров Можно задать и метод с вычисляемым названием: -```js -//+ run +```js run 'use strict'; let methodName = "getFirstName"; let user = { - // в квадратных скобках может быть любое выражение, + // в квадратных скобках может быть любое выражение, // которое должно вернуть название метода [methodName]() { // вместо [methodName]: function() { return "Вася"; @@ -229,21 +212,20 @@ alert( user.getFirstName() ); // Вася Вызов `super.parentProperty` позволяет из метода объекта получить свойство его прототипа. -Например, в коде ниже `rabbit` наследует от `animal`. +Например, в коде ниже `rabbit` наследует от `animal`. Вызов `super.walk()` из метода объекта `rabbit` обращается к `animal.walk()`: -```js -//+ run +```js run 'use strict'; -let animal = { - walk() { +let animal = { + walk() { alert("I'm walking"); } }; -let rabbit = { +let rabbit = { __proto__: animal, walk() { *!* @@ -256,23 +238,22 @@ let rabbit = { rabbit.walk(); ``` -Как правило, это используется в [классах](/class), которые мы рассмотрим в следующем разделе, но важно понимать, что "классы" здесь на самом деле не при чём. Свойство `super` работает через прототип, на уровне методов объекта. +Как правило, это используется в [классах](/es-class), которые мы рассмотрим в следующем разделе, но важно понимать, что "классы" здесь на самом деле ни при чём. Свойство `super` работает через прототип, на уровне методов объекта. При обращении через `super` используется `[[HomeObject]]` текущего метода, и от него берётся `__proto__`. Поэтому `super` работает только внутри методов. В частности, если переписать этот код, оформив `rabbit.walk` как обычное свойство-функцию, то будет ошибка: -```js -//+ run +```js run 'use strict'; -let animal = { - walk() { +let animal = { + walk() { alert("I'm walking"); } }; -let rabbit = { +let rabbit = { __proto__: animal, *!* walk: function() { // Надо: walk() { @@ -284,22 +265,20 @@ let rabbit = { rabbit.walk(); ``` -Ошибка возникнет, так как `rabbit.walk` теперь обычная функция, и не имеет `[[HomeObject]]`. В ней не работает `super`. +Ошибка возникнет, так как `rabbit.walk` теперь обычная функция и не имеет `[[HomeObject]]`. Поэтому в ней не работает `super`. Исключением из этого правила являются функции-стрелки. В них используется `super` внешней функции. Например, здесь функция-стрелка в `setTimeout` берёт внешний `super`: - -```js -//+ run +```js run 'use strict'; -let animal = { - walk() { +let animal = { + walk() { alert("I'm walking"); } }; -let rabbit = { +let rabbit = { __proto__: animal, walk() { *!* @@ -313,19 +292,18 @@ rabbit.walk(); Ранее мы говорили о том, что у функций-стрелок нет своего `this`, `arguments`: они используют те, которые во внешней функции. Теперь к этому списку добавился ещё и `super`. -[smart header="Свойство `[[HomeObject]]` -- не изменяемое"] +````smart header="Свойство `[[HomeObject]]` -- не изменяемое" При создании метода -- он привязан к своему объекту навсегда. Технически можно даже скопировать его и запустить отдельно, и `super` продолжит работать: -```js -//+ run +```js run 'use strict'; -let animal = { +let animal = { walk() { alert("I'm walking"); } }; -let rabbit = { +let rabbit = { __proto__: animal, walk() { super.walk(); @@ -339,30 +317,28 @@ walk(); // вызовет animal.walk() */!* ``` -В примере выше метод `walk()` запускается отдельно от объекта, но всё равно сохраняется через `super` доступ к его прототипу, благодаря `[[HomeObject]]`. +В примере выше метод `walk()` запускается отдельно от объекта, но всё равно, благодаря `[[HomeObject]]`, сохраняется доступ к его прототипу через `super`. -Это -- скорее технический момент, так как методы объекта, всё же, предназначены для вызова в контексте этого объекта. В частности, правила для `this` в методах -- те же, что и для обычных функций. В примере выше при вызове `walk()` без объекта `this` будет `undefined`. -[/smart] +Это -- скорее технический момент, так как методы объекта, всё же, предназначены для вызова в контексте этого объекта. В частности, правила для `this` в методах -- те же, что и для обычных функций. В примере выше при вызове `walk()` без объекта `this` будет `undefined`. +```` -## Итого +## Итого Улучшения в описании свойств: -
    -
  • Запись `name: name` можно заменить на просто `name`
  • -
  • Если имя свойства находится в переменной или задано выражением `expr`, то его можно указать в квадратных скобках `[expr]`.
  • -
  • Свойства-функции можно оформить как методы: `"prop: function() {"` -> `"prop() {"`.
  • -
+ +- Запись `name: name` можно заменить на просто `name` +- Если имя свойства находится в переменной или задано выражением `expr`, то его можно указать в квадратных скобках `[expr]`. +- Свойства-функции можно оформить как методы: `"prop: function() {}"` -> `"prop() {}"`. В методах работает обращение к свойствам прототипа через `super.parentProperty`. Для работы с прототипом: -
    -
  • `Object.setPrototypeOf(obj, proto)` -- метод для установки прототипа.
  • -
  • `obj.__proto__` -- ссылка на прототип.
  • -
+ +- `Object.setPrototypeOf(obj, proto)` -- метод для установки прототипа. +- `obj.__proto__` -- ссылка на прототип. Дополнительно: -
    -
  • Метод `Object.assign(target, src1, src2...)` -- копирует свойства из всех аргументов в первый объект.
  • -
  • Метод `Object.is(value1, value2)` проверяет два значения на равенство. В отличие от `===` считает `+0` и `-0` разными числами. А также считает, что `NaN` равно самому себе.
  • -
+ +- Метод `Object.assign(target, src1, src2...)` -- копирует свойства из всех аргументов в первый объект. +- Метод `Object.is(value1, value2)` проверяет два значения на равенство. В отличие от `===` считает `+0` и `-0` разными числами. А также считает, что `NaN` равно самому себе. + diff --git a/1-js/10-es-modern/7-es-class/article.md b/1-js/10-es-modern/7-es-class/article.md index 8ce0f71b..cefbcfa5 100644 --- a/1-js/10-es-modern/7-es-class/article.md +++ b/1-js/10-es-modern/7-es-class/article.md @@ -1,7 +1,7 @@ -# Классы +# Классы -В современном JavaScript появился новый, "более красивый" синтаксис для классов. +В современном JavaScript появился новый, "более красивый" синтаксис для классов. Новая конструкция `class` -- удобный "синтаксический сахар" для задания конструктора вместе с прототипом. @@ -18,12 +18,11 @@ class Название [extends Родитель] { Например: -```js -//+ run +```js run 'use strict'; class User { - + constructor(name) { this.name = name; } @@ -38,7 +37,7 @@ let user = new User("Вася"); user.sayHi(); // Вася ``` -Функция `constructor` запускается при создании `new User`, остальные методы -- записываются в `User.prototype`. +Функция `constructor` запускается при создании `new User`, остальные методы записываются в `User.prototype`. Это объявление примерно аналогично такому: @@ -52,31 +51,26 @@ User.prototype.sayHi = function() { }; ``` -В обоих случаях `new User` будет создавать объекты. Метод `sayHi` -- также в обоих случаях находится в прототипе. +В обоих случаях `new User` будет создавать объекты. Метод `sayHi` также в обоих случаях находится в прототипе. Но при объявлении через `class` есть и ряд отличий: -
    -
  • `User` нельзя вызывать без `new`, будет ошибка.
  • -
  • Объявление класса с точки зрения области видимости ведёт себя как `let`. В частности, оно видно только текущем в блоке и только в коде, который находится ниже объявления (Function Declaration видно и до объявления).
  • -
+- `User` нельзя вызывать без `new`, будет ошибка. +- Объявление класса с точки зрения области видимости ведёт себя как `let`. В частности, оно видно только в текущем блоке и только в коде, который находится ниже объявления (Function Declaration видно и до объявления). Методы, объявленные внутри `class`, также имеют ряд особенностей: -
    -
  • Метод `sayHi` является именно методом, то есть имеет доступ к `super`.
  • -
  • Все методы класса работают в режиме `use strict`, даже если он не указан.
  • -
  • Все методы класса не перечислимы. То есть в цикле `for..in` по объекту их не будет.
  • -
+- Метод `sayHi` является именно методом, то есть имеет доступ к `super`. +- Все методы класса работают в строгом режиме `use strict`, даже если он не указан. +- Все методы класса не перечислимы. То есть в цикле `for..in` по объекту их не будет. ## Class Expression -Так же, как и Function Expression, классы можно задавать "инлайн", в любом выражении и внутри вызова функции. +Также, как и Function Expression, классы можно задавать "инлайн", в любом выражении и внутри вызова функции. Это называется Class Expression: -```js -//+ run +```js run 'use strict'; let User = class { @@ -88,9 +82,7 @@ new User().sayHi(); В примере выше у класса нет имени, что один-в-один соответствует синтаксису функций. Но имя можно дать. Тогда оно, как и в Named Function Expression, будет доступно только внутри класса: - -```js -//+ run +```js run 'use strict'; let SiteGuest = class User { @@ -103,14 +95,13 @@ new User(); // ошибка */!* ``` -В примере выше имя `User` будет доступно только внутри класса и может быть использовано, например для создания новых объектов данного типа. +В примере выше имя `User` будет доступно только внутри класса и может быть использовано, например, для создания новых объектов данного типа. -Наиболее очевидная область применения этой возможности -- создание вспомогательного класса прямо при вызове функции. +Наиболее очевидная область применения этой возможности -- создание вспомогательного класса прямо при вызове функции. Например, функция `createModel` в примере ниже создаёт объект по классу и данным, добавляет ему `_id` и пишет в "реестр" `allModels`: -```js -//+ run +```js run 'use strict'; let allModels = {}; @@ -142,8 +133,7 @@ alert( allModels[user._id].name ); // Вася В классах, как и в обычных объектах, можно объявлять геттеры и сеттеры через `get/set`, а также использовать `[…]` для свойств с вычисляемыми именами: -```js -//+ run +```js run 'use strict'; class User { @@ -168,8 +158,10 @@ class User { *!* // вычисляемое название метода -*/!* - ["test".toUpperCase()]: true +*/!* + ["test".toUpperCase()]() { + alert("PASSED!"); + } }; @@ -177,22 +169,20 @@ let user = new User("Вася", "Пупков"); alert( user.fullName ); // Вася Пупков user.fullName = "Иван Петров"; alert( user.fullName ); // Иван Петров -alert( user.TEST ); // true +user.TEST(); // PASSED! ``` При чтении `fullName` будет вызван метод `get fullName()`, при присвоении -- метод `set fullName` с новым значением. -[warn header="`class` не позволяет задавать свойства-значения"] - +```warn header="`class` не позволяет задавать свойства-значения" В синтаксисе классов, как мы видели выше, можно создавать методы. Они будут записаны в прототип, как например `User.prototype.sayHi`. Однако, нет возможности задать в прототипе обычное значение (не функцию), такое как `User.prototype.key = "value"`. Конечно, никто не мешает после объявления класса в прототип дописать подобные свойства, однако предполагается, что в прототипе должны быть только методы. -Если свойство-значение, всё же, необходимо, то, можно создать геттер, который будет нужное значение возвращать. -[/warn] - +Если свойство-значение, всё же, необходимо, то можно создать геттер, который будет нужное значение возвращать. +``` ## Статические свойства @@ -202,8 +192,7 @@ alert( user.TEST ); // true Например: -```js -//+ run +```js run 'use strict'; class User { @@ -230,8 +219,7 @@ alert( User.createGuest ); // createGuest ... (функция) Также статическими удобно делать константы: -```js -//+ run +```js run 'use strict'; class Menu { @@ -247,15 +235,14 @@ alert( Menu.elemClass ); // menu Синтаксис: ```js -class Child extends Parent { +class Child extends Parent { ... } ``` -Посмотрим, как это выглядит на практике. В примере ниже объявлено два класса: `Animal` и наследующий от него `Rabbit`: +Посмотрим как это выглядит на практике. В примере ниже объявлено два класса: `Animal` и наследующий от него `Rabbit`: -```js -//+ run +```js run 'use strict'; class Animal { @@ -277,7 +264,7 @@ class Rabbit extends Animal { } } -new Rabbit("Вася").walk(); +new Rabbit("Вася").walk(); // I walk: Вася // and jump! ``` @@ -286,8 +273,7 @@ new Rabbit("Вася").walk(); Это потому, что при наследовании через `extends` формируется стандартная цепочка прототипов: методы `Rabbit` находятся в `Rabbit.prototype`, методы `Animal` -- в `Animal.prototype`, и они связаны через `__proto__`: -```js -//+ run +```js run 'use strict'; class Animal { } @@ -298,16 +284,15 @@ alert( Rabbit.prototype.__proto__ == Animal.prototype ); // true Как видно из примера выше, методы родителя (`walk`) можно переопределить в наследнике. При этом для обращения к родительскому методу используют `super.walk()`. -Немного особая история -- с конструктором. +С конструктором -- немного особая история. Конструктор `constructor` родителя наследуется автоматически. То есть, если в потомке не указан свой `constructor`, то используется родительский. В примере выше `Rabbit`, таким образом, использует `constructor` от `Animal`. -Если же у потомка свой `constructor`, то чтобы в нём вызвать конструктор родителя -- используется синтаксис `super()` с аргументами для родителя. +Если же у потомка свой `constructor`, то, чтобы в нём вызвать конструктор родителя -- используется синтаксис `super()` с аргументами для родителя. Например, вызовем конструктор `Animal` в `Rabbit`: -```js -//+ run +```js run 'use strict'; class Animal { @@ -333,15 +318,13 @@ new Rabbit().walk(); // I walk: Кроль ``` Для такого вызова есть небольшие ограничения: -
    -
  • Вызвать конструктор родителя можно только изнутри конструктора потомка. В частности, `super()` нельзя вызвать из произвольного метода.
  • -
  • В конструкторе потомка мы обязаны вызвать `super()` до обращения к `this`. До вызова `super` не существует `this`, так как по спецификации в этом случае именно `super` инициализует `this`.
  • -
+ +- Вызвать конструктор родителя можно только изнутри конструктора потомка. В частности, `super()` нельзя вызвать из произвольного метода. +- В конструкторе потомка мы обязаны вызвать `super()` до обращения к `this`. До вызова `super` не существует `this`, так как по спецификации в этом случае именно `super` инициализует `this`. Второе ограничение выглядит несколько странно, поэтому проиллюстрируем его примером: -```js -//+ run +```js run 'use strict'; class Animal { @@ -364,19 +347,13 @@ class Rabbit extends Animal { new Rabbit(); ``` - ## Итого -
    -
  • Классы можно объявлять как в основном потоке кода, так и "инлайн", по аналогии с Function Declaration и Expression.
  • -
  • В объявлении классов можно использовать методы, геттеры/сеттеры и вычислимые названия методов.
  • -
  • При наследовании вызов конструктора родителя осуществлятся через `super(...args)`, вызов родительских методов -- через `super.method(...args)`.
  • -
+- Классы можно объявлять как в основном потоке кода, так и "инлайн", по аналогии с Function Declaration и Expression. +- В объявлении классов можно использовать методы, геттеры/сеттеры и вычислимые названия методов. +- При наследовании вызов конструктора родителя осуществляется через `super(...args)`, вызов родительских методов -- через `super.method(...args)`. -Концепция классов, которая после долгих обсуждений получилась в стандарте EcmaScript, носит название "максимально минимальной". То есть, в неё вошли только те возможности, которые уж точно необходимы. +Концепция классов, которая после долгих обсуждений получилась в стандарте ECMAScript, носит название "максимально минимальной". То есть, в неё вошли только те возможности, которые уж точно необходимы. В частности, не вошли "приватные" и "защищённые" свойства. То есть, все свойства и методы класса технически доступны снаружи. Возможно, они появятся в будущих редакциях стандарта. - - - diff --git a/1-js/10-es-modern/8-symbol/article.md b/1-js/10-es-modern/8-symbol/article.md index 0e89bb9b..22517005 100644 --- a/1-js/10-es-modern/8-symbol/article.md +++ b/1-js/10-es-modern/8-symbol/article.md @@ -16,29 +16,25 @@ let sym = Symbol(); У символов есть и соответствующий `typeof`: -```js -//+ run +```js run 'use strict'; let sym = Symbol(); alert( typeof sym ); // symbol ``` +Каждый символ -- уникален. У функции `Symbol` есть необязательный аргумент "имя символа". Его можно использовать для описания символа, в целях отладки: -Каждый символ -- уникален. У функции `Symbol` есть необязательный аргумент "имя символа". Можно его использовать для описания символа, в целях отладки: - -```js -//+ run +```js run 'use strict'; let sym = Symbol("name"); alert( sym.toString() ); // Symbol(name) ``` -...Но при этом если у двух символов одинаковое имя, то это не значит, что они равны: +...Но при этом, если у двух символов одинаковое имя, то это не значит, что они равны: -```js -//+ run +```js run alert( Symbol("name") == Symbol("name") ); // false ``` @@ -46,14 +42,13 @@ alert( Symbol("name") == Symbol("name") ); // false ## Глобальные символы -Существует "глобальный реестр" символов, который позволяет, при необходимости, разделять символы между частями программы. +Существует "глобальный реестр" символов, который позволяет, при необходимости, иметь общие "глобальные" символы, которые можно получить из реестра по имени. Для чтения (или создания, при отсутствии) "глобального" символа служит вызов `Symbol.for(имя)`. Например: -```js -//+ run +```js run 'use strict'; // создание символа в реестре @@ -63,11 +58,15 @@ let name = Symbol.for("name"); alert( Symbol.for("name") == name ); // true ``` -Вызов `Symbol.for` возвращает символ по имени. Обратным для него является вызов `Symbol.keyFor(sym)` позволяет получить по глобальному символу его имя: +Таким образом, можно из разных частей программы, обратившись к реестру, получить единый глобальный символ с именем `"name"`. +```smart +В некоторых языках программирования, например Ruby, имя однозначно идентифицирует символ. В JavaScript, как мы видим, это верно для глобальных символов. +``` -```js -//+ run +У вызова `Symbol.for`, который возвращает символ по имени, есть обратный вызов -- `Symbol.keyFor(sym)`. Он позволяет получить по глобальному символу его имя: + +```js run 'use strict'; // создание символа в реестре @@ -76,11 +75,11 @@ let name = Symbol.for("name"); // получение имени символа alert( Symbol.keyFor(name) ); // name ``` -[warn header="`Symbol.keyFor` возвращает `undefined`, если символ не глобальный"] + +````warn header="`Symbol.keyFor` возвращает `undefined`, если символ не глобальный" Заметим, что `Symbol.keyFor` работает *только для глобальных символов*, для остальных будет возвращено `undefined`: -```js -//+ run +```js run 'use strict'; alert( Symbol.keyFor(Symbol.for("name")) ); // name, глобальный @@ -88,14 +87,13 @@ alert( Symbol.keyFor(Symbol("name2")) ); // undefined, обычный симво ``` Таким образом, имя символа, если этот символ не глобальный, не имеет особого применения, оно полезно лишь в целях вывода и отладки. -[/warn] +```` ## Использование символов -Символы можно использовать в качестве имён для свойств объекта, вот так: +Символы можно использовать в качестве имён для свойств объекта следующим образом: -```js -//+ run +```js run 'use strict'; let isAdmin = Symbol("isAdmin"); @@ -108,10 +106,9 @@ let user = { alert(user[isAdmin]); // true ``` -Особенность символов -- в том, что если в объект записать свойство-символ, то оно не участвует в итерации: +Особенность символов в том, что если в объект записать свойство-символ, то оно не участвует в итерации: -```js -//+ run +```js run 'use strict'; let user = { @@ -129,18 +126,17 @@ alert( user[Symbol.for("isAdmin")] ); Кроме того, свойство-символ недоступно, если обратиться к его названию: `user.isAdmin` не существует. -Зачем всё это, почему не просто использовать строки? +Зачем всё это, почему просто не использовать строки? Резонный вопрос. На ум могут прийти соображения производительности, так как символы -- это по сути специальные идентификаторы, они компактнее, чем строка. Но при современных оптимизациях объектов это редко имеет значение. -Самое широкое применение символов предусмотрено внутри самого стандарта JavaScript. В современном стандарте есть много системных символов. Их список есть в спецификации, в таблице [Well-known Symbols](http://www.ecma-international.org/ecma-262/6.0/index.html#table-1). В спецификации принято символы для краткости обозначать их как '@@имя', например `@@iterator`, но доступны они как свойства `Symbol`. +Самое широкое применение символов предусмотрено внутри самого стандарта JavaScript. В современном стандарте есть много системных символов. Их список есть в спецификации, в таблице [Well-known Symbols](http://www.ecma-international.org/ecma-262/6.0/index.html#table-1). В спецификации для краткости символы принято обозначать как '@@имя', например `@@iterator`, но доступны они как свойства `Symbol`. Например: -
    -
  • `Symbol.toPrimitive` -- идентификатор для свойства, задающего функцию преобразования объекта в примитив.
  • -
  • `Symbol.iterator` -- идентификатор для свойства, задающего функцию итерации по объекту.
  • -
  • ...и т.п.
  • -
+ +- `Symbol.toPrimitive` -- идентификатор для свойства, задающего функцию преобразования объекта в примитив. +- `Symbol.iterator` -- идентификатор для свойства, задающего функцию итерации по объекту. +- ...и т.п. **Мы легко поймём смысл введения нового типа "символ", если поставим себя на место создателей языка JavaScript.** @@ -151,20 +147,18 @@ alert( user[Symbol.for("isAdmin")] ); Нельзя просто взять и зарезервировать какие-то свойства существующих объектов для нового функционала. Поэтому ввели целый тип "символы". Их можно использовать для задания таких свойств, так как они: -
    -
  • а) уникальны,
  • -
  • б) не участвуют в циклах,
  • -
  • в) заведомо не сломают старый код, который о них слыхом не слыхивал.
  • -
+ +- а) уникальны, +- б) не участвуют в циклах, +- в) заведомо не сломают старый код, который о них слыхом не слыхивал. Продемонстрируем отсутствие конфликта для нового системного свойства `Symbol.iterator`: -```js -//+ run +```js run 'use strict'; -let obj = { - iterator: 1, +let obj = { + iterator: 1, [Symbol.iterator]() {} } @@ -178,17 +172,16 @@ alert(obj[Symbol.iterator]) // function, символ не конфликтуе Эта функция возвращает все символы в объекте (и только их). Заметим, что старая функция `getOwnPropertyNames` символы не возвращает, что опять же гарантирует отсутствие конфликтов со старым кодом. -```js -//+ run +```js run 'use strict'; -let obj = { - iterator: 1, +let obj = { + iterator: 1, [Symbol.iterator]: function() {} } // один символ в объекте -alert( Object.getOwnPropertySymbols(obj) ); // Symbol(Symbol.iterator) +alert( Object.getOwnPropertySymbols(obj)[0].toString() ); // Symbol(Symbol.iterator) // и одно обычное свойство alert( Object.getOwnPropertyNames(obj) ); // iterator @@ -196,15 +189,12 @@ alert( Object.getOwnPropertyNames(obj) ); // iterator ## Итого -
    -
  • Символы -- новый примитивный тип, предназначенный для уникальных идентификаторов.
  • -
  • Все символы уникальны, символы с одинаковым именем не равны друг другу.
  • -
  • Существует глобальный реестр символов, доступных через метод `Symbol.for(name)`. Для глобального символа можно получить имя вызовом и `Symbol.keyFor(sym)`.
  • -
+- Символы -- новый примитивный тип, предназначенный для уникальных идентификаторов. +- Все символы уникальны. Символы с одинаковым именем не равны друг другу. +- Существует глобальный реестр символов, доступных через метод `Symbol.for(name)`. Для глобального символа можно получить имя вызовом и `Symbol.keyFor(sym)`. Основная область использования символов -- это системные свойства объектов, которые задают разные аспекты их поведения. Поддержка у них пока небольшая, но она растёт. Системные символы позволяют разработчикам стандарта добавлять новые "особые" свойства объектов, при этом не резервируя соответствующие строковые значения. Системные символы доступны как свойства функции `Symbol`, например `Symbol.iterator`. -Мы можем создавать и свои символы, использовать их в объектах. Записывать их как свойства `Symbol`, разумеется, нельзя, если нужен глобально доступный символ, то используется `Symbol.for(имя)`. - +Мы можем создавать и свои символы, использовать их в объектах. Записывать их как свойства `Symbol`, разумеется, нельзя. Если нужен глобально доступный символ, то используется `Symbol.for(имя)`. diff --git a/1-js/10-es-modern/9-iterator/article.md b/1-js/10-es-modern/9-iterator/article.md index 4d7e50db..2787f6b6 100644 --- a/1-js/10-es-modern/9-iterator/article.md +++ b/1-js/10-es-modern/9-iterator/article.md @@ -1,39 +1,36 @@ - + # Итераторы В современный JavaScript добавлена новая концепция "итерируемых" (iterable) объектов. Итерируемые или, иными словами, "перебираемые" объекты -- это те, содержимое которых можно перебрать в цикле. -Например, перебираемым объектом является массив. Но не только он. В браузере существует множество объектов, которые не являются массивами, но содержимое которых можно перебрать (к примеру, список DOM-узлов). +Например, перебираемым объектом является массив. Но не только он. В браузере существует множество объектов, которые не являются массивами, но содержимое которых можно перебрать (к примеру, список DOM-узлов). Для перебора таких объектов добавлен новый синтаксис цикла: `for..of`. Например: -```js -//+ run +```js run 'use strict'; let arr = [1, 2, 3]; // массив — пример итерируемого объекта -for(let value of arr) { +for (let value of arr) { alert(value); // 1, затем 2, затем 3 } ``` Также итерируемой является строка: -```js -//+ run +```js run 'use strict'; -for(let char of "Привет") { +for (let char of "Привет") { alert(char); // Выведет по одной букве: П, р, и, в, е, т } ``` - Итераторы -- расширяющая понятие "массив" концепция, которая пронизывает современный стандарт JavaScript сверху донизу. Практически везде, где нужен перебор, он осуществляется через итераторы. Это включает в себя не только строки, массивы, но и вызов функции с оператором spread `f(...args)`, и многое другое. @@ -44,7 +41,7 @@ for(let char of "Привет") { Допустим, у нас есть некий объект, который надо "умным способом" перебрать. -Например, `range` -- диапазон чисел от `from` до `to`, и мы хотим, чтобы `for(let num of range)` "перебирал", этот объект. При этом под перебором мы подразумеваем перечисление чисел от `from` до `to`. +Например, `range` -- диапазон чисел от `from` до `to`, и мы хотим, чтобы `for (let num of range)` "перебирал" этот объект. При этом под перебором мы подразумеваем перечисление чисел от `from` до `to`. Объект `range` без итератора: @@ -55,19 +52,18 @@ let range = { }; // хотим сделать перебор -// for(let num of range) ... +// for (let num of range) ... ``` -Для возможности использовать объект в `for..of` нужно создать в нём свойство с названием `Symbol.iterator` (системный символ). +Для возможности использовать объект в `for..of` нужно создать в нём свойство с названием `Symbol.iterator` (системный символ). При вызове метода `Symbol.iterator` перебираемый объект должен возвращать другой объект ("итератор"), который умеет осуществлять перебор. -По стандарту у такого объекта должен быть метод `next()`, который при каждом вызове возвращает очередное значение и окончен ли перебор. +По стандарту у такого объекта должен быть метод `next()`, который при каждом вызове возвращает очередное значение и проверяет, окончен ли перебор. -Так это выглядит в коде: +В коде это выглядит следующим образом: -```js -//+ run +```js run 'use strict'; let range = { @@ -81,7 +77,7 @@ range[Symbol.iterator] = function() { let current = this.from; let last = this.to; - // метод должен вернуть объект с next() + // метод должен вернуть объект с методом next() return { next() { if (current <= last) { @@ -93,7 +89,7 @@ range[Symbol.iterator] = function() { return { done: true }; - } + } } } @@ -106,26 +102,19 @@ for (let num of range) { Как видно из кода выше, здесь имеет место разделение сущностей: -
    -
  • Перебираемый объект `range` сам не реализует методы для своего перебора.
  • -
  • Для этого создаётся другой объект, который хранит текущее состояние перебора и возвращает значение. Этот объект называется итератором и возвращается при вызове метода `range[Symbol.iterator]`.
  • -
  • У итератора должен быть метод `next()`, который при каждом вызове возвращает объект со свойствами: -
      -
    • `value` -- очередное значение, -
    • `done` -- равно `false`, если есть ещё значения, и `true` -- в конце.
    • -
    -
  • -
+- Перебираемый объект `range` сам не реализует методы для своего перебора. +- Для этого создаётся другой объект, который хранит текущее состояние перебора и возвращает значение. Этот объект называется итератором и возвращается при вызове метода `range[Symbol.iterator]`. +- У итератора должен быть метод `next()`, который при каждом вызове возвращает объект со свойствами: + - `value` -- очередное значение, + - `done` -- равно `false` если есть ещё значения, и `true` -- в конце. Конструкция `for..of` в начале своего выполнения автоматически вызывает `Symbol.iterator()`, получает итератор и далее вызывает метод `next()` до получения `done: true`. Такова внутренняя механика. Внешний код при переборе через `for..of` видит только значения. -Такое отделение функционала перебора от самого объекта даёт дополнительную гибкость, например, объект может возвращать разные итераторы в зависимости от своего настроения и времени суток. Однако, бывают ситуации, когда оно не нужно. +Такое отделение функционала перебора от самого объекта даёт дополнительную гибкость. Например, объект может возвращать разные итераторы в зависимости от своего настроения и времени суток. Однако, бывают ситуации когда оно не нужно. Если функционал по перебору (метод `next`) предоставляется самим объектом, то можно вернуть `this` в качестве итератора: - -```js -//+ run +```js run 'use strict'; let range = { @@ -155,7 +144,7 @@ let range = { return { done: true }; - } + } } }; @@ -166,26 +155,25 @@ for (let num of range) { // Произойдёт вызов Math.max(1,2,3,4,5); alert( Math.max(...range) ); // 5 (*) - ``` -При таком подходе сам объект и хранит состояние итерации (текущий перебираемый элемент). +При таком подходе сам объект и хранит состояние итерации (текущий перебираемый элемент). В данном случае это работает, но для большей гибкости и понятности кода рекомендуется, всё же, выделять итератор в отдельный объект со своим состоянием и кодом. -[smart header="Оператор spread `...` и итераторы"] -В последней строке `(*)` примера выше можно видеть, что итерируемый объект передаётся через spread для `Math.max`. +```smart header="Оператор spread `...` и итераторы" +В последней строке `(*)` примера выше можно видеть, что итерируемый объект передаётся через spread для `Math.max`. При этом `...range` автоматически превращает итерируемый объект в массив. То есть произойдёт цикл `for..of` по `range`, и его результаты будут использованы в качестве списка аргументов. -[/smart] +``` -[smart header="Бесконечные итераторы"] +```smart header="Бесконечные итераторы" Возможны и бесконечные итераторы. Например, пример выше при `range.to = Infinity` будет таковым. Или можно сделать итератор, генерирующий бесконечную последовательность псевдослучайных чисел. Тоже полезно. Нет никаких ограничений на `next`, он может возвращать всё новые и новые значения, и это нормально. Разумеется, цикл `for..of` по такому итератору тоже будет бесконечным, нужно его прерывать, например, через `break`. -[/smart] +``` ## Встроенные итераторы @@ -193,14 +181,13 @@ alert( Math.max(...range) ); // 5 (*) Например, этот код получает итератор для строки и вызывает его полностью "вручную": -```js -//+ run +```js run 'use strict'; let str = "Hello"; // Делает то же, что и -// for(var letter of str) alert(letter); +// for (var letter of str) alert(letter); let iterator = str[Symbol.iterator](); @@ -211,20 +198,13 @@ while(true) { } ``` -То же самое будет работать и для массивов. +То же самое будет работать и для массивов. ## Итого -
    -
  • *Итератор* -- объект, предназначенный для перебора другого объекта.
  • -
  • У итератора должен быть метод `next()`, возвращающий `{done: Boolean, value: any}`, где `value` -- очередное значение, а `done: true` в конце.
  • -
  • Метод `Symbol.iterator` предназначен для получения итератора из объекта. Цикл `for..of` делает это автоматически, но можно и вызвать его напрямую.
  • -
  • В современном стандарте есть много мест, где вместо массива используются более абстрактные "итерируемые" (со свойством `Symbol.iterator`) объекты, например оператор spread `...`.
  • -
  • Встроенные объекты, такие как массивы и строки, являются итерируемыми, в соответствии с описанным выше.
  • -
- - - - - +- *Итератор* -- объект, предназначенный для перебора другого объекта. +- У итератора должен быть метод `next()`, возвращающий объект `{done: Boolean, value: any}`, где `value` -- очередное значение, а `done: true` в конце. +- Метод `Symbol.iterator` предназначен для получения итератора из объекта. Цикл `for..of` делает это автоматически, но можно и вызвать его напрямую. +- В современном стандарте есть много мест, где вместо массива используются более абстрактные "итерируемые" (со свойством `Symbol.iterator`) объекты, например оператор spread `...`. +- Встроенные объекты, такие как массивы и строки, являются итерируемыми, в соответствии с описанным выше. diff --git a/1-js/2-first-steps/1-hello-world/1-hello-alert/solution.md b/1-js/2-first-steps/1-hello-world/1-hello-alert/solution.md index b27e40d9..b54cabc1 100644 --- a/1-js/2-first-steps/1-hello-world/1-hello-alert/solution.md +++ b/1-js/2-first-steps/1-hello-world/1-hello-alert/solution.md @@ -1,7 +1,6 @@ Код страницы: -```html - +```html run diff --git a/1-js/2-first-steps/1-hello-world/1-hello-alert/task.md b/1-js/2-first-steps/1-hello-world/1-hello-alert/task.md index 0f7f9375..83603396 100644 --- a/1-js/2-first-steps/1-hello-world/1-hello-alert/task.md +++ b/1-js/2-first-steps/1-hello-world/1-hello-alert/task.md @@ -1,9 +1,12 @@ -# Выведите alert +importance: 5 + +--- -[importance 5] +# Выведите alert Сделайте страницу, которая выводит "Я - JavaScript!". Создайте её на диске, откройте в браузере, убедитесь, что всё работает. [demo src="http://github.com/solution"] + diff --git a/1-js/2-first-steps/1-hello-world/article.md b/1-js/2-first-steps/1-hello-world/article.md index 739b103b..6d81273c 100644 --- a/1-js/2-first-steps/1-hello-world/article.md +++ b/1-js/2-first-steps/1-hello-world/article.md @@ -1,19 +1,20 @@ # Привет, мир! В этой статье мы создадим простой скрипт и посмотрим, как он работает. + [cut] + ## Тег SCRIPT -[smart header="А побыстрее?"] -В том (и только в том!) случае, если читатель нетерпелив и уже разрабатывал на JavaScript или имеет достаточно опыта в другом языке программировании, он может не читать каждую статью этого раздела, а перепрыгнуть сразу к главе [](/javascript-specials). Там будет кратко самое основное. +```smart header="А побыстрее?" +В том (и только в том!) случае, если читатель нетерпелив и уже разрабатывал на JavaScript или имеет достаточно опыта в другом языке программировании, он может не читать каждую статью этого раздела, а перепрыгнуть сразу к главе . Там будет кратко самое основное. Если же у вас есть достаточно времени и желание начать с азов, то читайте дальше :) -[/smart] +``` Программы на языке JavaScript можно вставить в любое место HTML при помощи тега `SCRIPT`. Например: -```html - +```html run height=100 @@ -39,66 +40,57 @@ ``` Этот пример использует следующие элементы: - -
-
<script> ... </script>
-
Тег `script` содержит исполняемый код. Предыдущие стандарты HTML требовали обязательного указания атрибута `type`, но сейчас он уже не нужен. Достаточно просто `` +: Тег `script` содержит исполняемый код. Предыдущие стандарты HTML требовали обязательного указания атрибута `type`, но сейчас он уже не нужен. Достаточно просто ` -``` + Выглядит это примерно так: + + ```html no-beautify + + ``` -Браузер, для которого предназначались такие трюки, очень старый Netscape, давно умер. Поэтому в этих комментариях нет нужды. -
-
+ Браузер, для которого предназначались такие трюки, очень старый Netscape, давно умер. Поэтому в этих комментариях нет нужды. Итак, для вставки скрипта мы просто пишем ` -[/head] \ No newline at end of file +- Округления числа: `(12.34^0) = 12`. +- Проверки на равенство `-1`: `if (~n) { n не -1 }`. +- Упаковки нескольких битововых значений ("флагов") в одно значение. Это экономит память и позволяет проверять наличие комбинации флагов одним оператором `&`. +- Других ситуаций, когда нужны битовые маски. + diff --git a/1-js/2-first-steps/10-bitwise-operators/head.html b/1-js/2-first-steps/10-bitwise-operators/head.html new file mode 100644 index 00000000..6480a159 --- /dev/null +++ b/1-js/2-first-steps/10-bitwise-operators/head.html @@ -0,0 +1,8 @@ + \ No newline at end of file diff --git a/1-js/2-first-steps/11-uibasic/1-simple-page/solution.md b/1-js/2-first-steps/11-uibasic/1-simple-page/solution.md index a70b07e0..0a93edbb 100644 --- a/1-js/2-first-steps/11-uibasic/1-simple-page/solution.md +++ b/1-js/2-first-steps/11-uibasic/1-simple-page/solution.md @@ -1,7 +1,6 @@ JS-код: -```js -//+ demo run +```js demo run var name = prompt("Ваше имя?", ""); alert( name ); ``` diff --git a/1-js/2-first-steps/11-uibasic/1-simple-page/task.md b/1-js/2-first-steps/11-uibasic/1-simple-page/task.md index 6d2ca2e1..90651af7 100644 --- a/1-js/2-first-steps/11-uibasic/1-simple-page/task.md +++ b/1-js/2-first-steps/11-uibasic/1-simple-page/task.md @@ -1,8 +1,10 @@ -# Простая страница +importance: 4 + +--- -[importance 4] +# Простая страница Создайте страницу, которая спрашивает имя и выводит его. -[demo /] +[demo] diff --git a/1-js/2-first-steps/11-uibasic/article.md b/1-js/2-first-steps/11-uibasic/article.md index e5c745ce..242b83d7 100644 --- a/1-js/2-first-steps/11-uibasic/article.md +++ b/1-js/2-first-steps/11-uibasic/article.md @@ -1,9 +1,10 @@ # Взаимодействие с пользователем: alert, prompt, confirm - В этом разделе мы рассмотрим базовые UI операции: `alert`, `prompt` и `confirm`, которые позволяют работать с данными, полученными от пользователя. + [cut] -## alert + +## alert Синтаксис: @@ -13,64 +14,57 @@ alert(сообщение) `alert` выводит на экран окно с сообщением и приостанавливает выполнение скрипта, пока пользователь не нажмёт "ОК". -```js -//+ run +```js run alert( "Привет" ); ``` Окно сообщения, которое выводится, является *модальным окном*. Слово "модальное" означает, что посетитель не может взаимодействовать со страницей, нажимать другие кнопки и т.п., пока не разберётся с окном. В данном случае - пока не нажмёт на "OK". -## prompt +## prompt Функция prompt принимает два аргумента: -```js -//+ no-beautify +```js no-beautify result = prompt(title, default); ``` -Она выводит модальное окно с заголовком `title`, полем для ввода текста, заполненным строкой по умолчанию `default` и кнопками OK/CANCEL. +Она выводит модальное окно с заголовком `title`, полем для ввода текста, заполненным строкой по умолчанию `default` и кнопками OK/CANCEL. -Пользователь должен либо что-то ввести и нажать OK, либо отменить ввод кликом на CANCEL или нажатием [key Esc] на клавиатуре. +Пользователь должен либо что-то ввести и нажать OK, либо отменить ввод кликом на CANCEL или нажатием `key:Esc` на клавиатуре. **Вызов `prompt` возвращает то, что ввёл посетитель -- строку или специальное значение `null`, если ввод отменён.** -[warn header="Safari 5.1+ не возвращает `null`"] +```warn header="Safari 5.1+ не возвращает `null`" Единственный браузер, который не возвращает `null` при отмене ввода -- это Safari. При отсутствии ввода он возвращает пустую строку. Предположительно, это ошибка в браузере. Если нам важен этот браузер, то пустую строку нужно обрабатывать точно так же, как и `null`, т.е. считать отменой ввода. -[/warn] +``` Как и в случае с `alert`, окно `prompt` модальное. -```js -//+ run +```js run var years = prompt('Сколько вам лет?', 100); alert('Вам ' + years + ' лет!') ``` -[warn header="Всегда указывайте `default`"] +````warn header="Всегда указывайте `default`" Второй параметр может отсутствовать. Однако при этом IE вставит в диалог значение по умолчанию `"undefined"`. Запустите этот код в IE, чтобы понять о чём речь: -```js -//+ run +```js run var test = prompt("Тест"); ``` Поэтому рекомендуется *всегда* указывать второй аргумент: -```js -//+ run +```js run var test = prompt("Тест", ''); // <-- так лучше ``` +```` -[/warn] - - -## confirm +## confirm Синтаксис: @@ -78,14 +72,13 @@ var test = prompt("Тест", ''); // <-- так лучше result = confirm(question); ``` -`confirm` выводит окно с вопросом `question` с двумя кнопками: OK и CANCEL. +`confirm` выводит окно с вопросом `question` с двумя кнопками: OK и CANCEL. -**Результатом будет `true` при нажатии OK и `false` - при CANCEL([key Esc]).** +**Результатом будет `true` при нажатии OK и `false` - при CANCEL(`key:Esc`).** Например: -```js -//+ run +```js run var isAdmin = confirm("Вы - администратор?"); alert( isAdmin ); @@ -95,17 +88,15 @@ alert( isAdmin ); Конкретное место, где выводится модальное окно с вопросом -- обычно это центр браузера, и внешний вид окна выбирает браузер. Разработчик не может на это влиять. -С одной стороны -- это недостаток, так как нельзя вывести окно в своем, особо красивом, дизайне. +С одной стороны -- это недостаток, так как нельзя вывести окно в своем, особо красивом, дизайне. С другой стороны, преимущество этих функций по сравнению с другими, более сложными методами взаимодействия, которые мы изучим в дальнейшем -- как раз в том, что они очень просты. Это самый простой способ вывести сообщение или получить информацию от посетителя. Поэтому их используют в тех случаях, когда простота важна, а всякие "красивости" особой роли не играют. - ## Резюме -
    -
  • `alert` выводит сообщение.
  • -
  • `prompt` выводит сообщение и ждёт, пока пользователь введёт текст, а затем возвращает введённое значение или `null`, если ввод отменён (CANCEL/[key Esc]).
  • -
  • `confirm` выводит сообщение и ждёт, пока пользователь нажмёт "OK" или "CANCEL" и возвращает `true/false`.
  • -
+- `alert` выводит сообщение. +- `prompt` выводит сообщение и ждёт, пока пользователь введёт текст, а затем возвращает введённое значение или `null`, если ввод отменён (CANCEL/`key:Esc`). +- `confirm` выводит сообщение и ждёт, пока пользователь нажмёт "OK" или "CANCEL" и возвращает `true/false`. + diff --git a/1-js/2-first-steps/12-ifelse/1-if-zero-string/solution.md b/1-js/2-first-steps/12-ifelse/1-if-zero-string/solution.md index 92098c3e..e5ebb8f3 100644 --- a/1-js/2-first-steps/12-ifelse/1-if-zero-string/solution.md +++ b/1-js/2-first-steps/12-ifelse/1-if-zero-string/solution.md @@ -1,11 +1,10 @@ -**Да, выведется,** т.к. внутри `if` стоит строка `"0"`. +**Да, выведется,** т.к. внутри `if` стоит строка `"0"`. Любая строка, кроме пустой (а здесь она не пустая), в логическом контексте является `true`. Можно запустить и проверить: -```js -//+ run +```js run if ("0") { alert( 'Привет' ); } diff --git a/1-js/2-first-steps/12-ifelse/1-if-zero-string/task.md b/1-js/2-first-steps/12-ifelse/1-if-zero-string/task.md index 27ce5aee..d68e63aa 100644 --- a/1-js/2-first-steps/12-ifelse/1-if-zero-string/task.md +++ b/1-js/2-first-steps/12-ifelse/1-if-zero-string/task.md @@ -1,6 +1,8 @@ -# if (строка с нулём) +importance: 5 + +--- -[importance 5] +# if (строка с нулём) Выведется ли `alert`? diff --git a/1-js/2-first-steps/12-ifelse/2-check-standard/ifelse_task2.png b/1-js/2-first-steps/12-ifelse/2-check-standard/ifelse_task2.png index a0925072..ee1021f2 100644 Binary files a/1-js/2-first-steps/12-ifelse/2-check-standard/ifelse_task2.png and b/1-js/2-first-steps/12-ifelse/2-check-standard/ifelse_task2.png differ diff --git a/1-js/2-first-steps/12-ifelse/2-check-standard/ifelse_task2/index.html b/1-js/2-first-steps/12-ifelse/2-check-standard/ifelse_task2/index.html index 392ead19..eed852e7 100644 --- a/1-js/2-first-steps/12-ifelse/2-check-standard/ifelse_task2/index.html +++ b/1-js/2-first-steps/12-ifelse/2-check-standard/ifelse_task2/index.html @@ -5,14 +5,14 @@ - \ No newline at end of file + diff --git a/1-js/2-first-steps/12-ifelse/2-check-standard/ifelse_task2@2x.png b/1-js/2-first-steps/12-ifelse/2-check-standard/ifelse_task2@2x.png new file mode 100644 index 00000000..3d428e05 Binary files /dev/null and b/1-js/2-first-steps/12-ifelse/2-check-standard/ifelse_task2@2x.png differ diff --git a/1-js/2-first-steps/12-ifelse/2-check-standard/solution.md b/1-js/2-first-steps/12-ifelse/2-check-standard/solution.md index aacb5966..99dea945 100644 --- a/1-js/2-first-steps/12-ifelse/2-check-standard/solution.md +++ b/1-js/2-first-steps/12-ifelse/2-check-standard/solution.md @@ -1,6 +1,4 @@ -```html - -``` +[html run src="http://github.com/ifelse_task2/index.html"] diff --git a/1-js/2-first-steps/12-ifelse/2-check-standard/task.md b/1-js/2-first-steps/12-ifelse/2-check-standard/task.md index f5aa5eb6..8fadd98b 100644 --- a/1-js/2-first-steps/12-ifelse/2-check-standard/task.md +++ b/1-js/2-first-steps/12-ifelse/2-check-standard/task.md @@ -1,13 +1,15 @@ -# Проверка стандарта +importance: 2 + +--- -[importance 2] +# Проверка стандарта Используя конструкцию `if..else`, напишите код, который будет спрашивать: "Каково "официальное" название JavaScript?". -Если посетитель вводит "EcmaScript", то выводить "Верно!", если что-то другое -- выводить "Не знаете? "EcmaScript"!". +Если посетитель вводит "ECMAScript", то выводить "Верно!", если что-то другое -- выводить "Не знаете? "ECMAScript"!". Блок-схема: - +![](ifelse_task2.png) -[demo src="http://github.com/ifelse_task2"] \ No newline at end of file +[demo src="http://github.com/ifelse_task2"] diff --git a/1-js/2-first-steps/12-ifelse/2-check-standardifelse_task2/index.html b/1-js/2-first-steps/12-ifelse/2-check-standardifelse_task2/index.html index 392ead19..eed852e7 100644 --- a/1-js/2-first-steps/12-ifelse/2-check-standardifelse_task2/index.html +++ b/1-js/2-first-steps/12-ifelse/2-check-standardifelse_task2/index.html @@ -5,14 +5,14 @@ - \ No newline at end of file + diff --git a/1-js/2-first-steps/12-ifelse/3-sign/solution.md b/1-js/2-first-steps/12-ifelse/3-sign/solution.md index df54e347..7be97834 100644 --- a/1-js/2-first-steps/12-ifelse/3-sign/solution.md +++ b/1-js/2-first-steps/12-ifelse/3-sign/solution.md @@ -1,7 +1,6 @@ -```js -//+ run +```js run var value = prompt('Введите число', 0); if (value > 0) { diff --git a/1-js/2-first-steps/12-ifelse/3-sign/task.md b/1-js/2-first-steps/12-ifelse/3-sign/task.md index 9d24ede9..a9322b1d 100644 --- a/1-js/2-first-steps/12-ifelse/3-sign/task.md +++ b/1-js/2-first-steps/12-ifelse/3-sign/task.md @@ -1,12 +1,13 @@ -# Получить знак числа +importance: 2 + +--- -[importance 2] +# Получить знак числа Используя конструкцию `if..else`, напишите код, который получает значение `prompt`, а затем выводит `alert`: -
    -
  • `1`, если значение больше нуля,
  • -
  • `-1`, если значение меньше нуля,
  • -
  • `0`, если значение равно нулю.
  • -
-[demo src="http://github.com/if_sign"] \ No newline at end of file +- `1`, если значение больше нуля, +- `-1`, если значение меньше нуля, +- `0`, если значение равно нулю. + +[demo src="http://github.com/if_sign"] diff --git a/1-js/2-first-steps/12-ifelse/4-check-login/ifelse_task.png b/1-js/2-first-steps/12-ifelse/4-check-login/ifelse_task.png index 56ed3c7c..f130d3bd 100644 Binary files a/1-js/2-first-steps/12-ifelse/4-check-login/ifelse_task.png and b/1-js/2-first-steps/12-ifelse/4-check-login/ifelse_task.png differ diff --git a/1-js/2-first-steps/12-ifelse/4-check-login/ifelse_task@2x.png b/1-js/2-first-steps/12-ifelse/4-check-login/ifelse_task@2x.png new file mode 100644 index 00000000..8b7b519e Binary files /dev/null and b/1-js/2-first-steps/12-ifelse/4-check-login/ifelse_task@2x.png differ diff --git a/1-js/2-first-steps/12-ifelse/4-check-login/solution.md b/1-js/2-first-steps/12-ifelse/4-check-login/solution.md index edcdbb50..eb4906b0 100644 --- a/1-js/2-first-steps/12-ifelse/4-check-login/solution.md +++ b/1-js/2-first-steps/12-ifelse/4-check-login/solution.md @@ -1,7 +1,6 @@ -```js -//+ run demo +```js run demo var userName = prompt('Кто пришёл?', ''); if (userName == 'Админ') { diff --git a/1-js/2-first-steps/12-ifelse/4-check-login/task.md b/1-js/2-first-steps/12-ifelse/4-check-login/task.md index a158cb4b..30739ac4 100644 --- a/1-js/2-first-steps/12-ifelse/4-check-login/task.md +++ b/1-js/2-first-steps/12-ifelse/4-check-login/task.md @@ -1,6 +1,8 @@ -# Проверка логина +importance: 3 + +--- -[importance 3] +# Проверка логина Напишите код, который будет спрашивать логин (`prompt`). @@ -10,8 +12,8 @@ Блок-схема: - +![](ifelse_task.png) Для решения используйте вложенные блоки `if`. Обращайте внимание на стиль и читаемость кода. -[demo /] \ No newline at end of file +[demo] diff --git a/1-js/2-first-steps/12-ifelse/5-rewrite-if-question/task.md b/1-js/2-first-steps/12-ifelse/5-rewrite-if-question/task.md index dc75b1fb..1cc4f4b2 100644 --- a/1-js/2-first-steps/12-ifelse/5-rewrite-if-question/task.md +++ b/1-js/2-first-steps/12-ifelse/5-rewrite-if-question/task.md @@ -1,6 +1,8 @@ -# Перепишите 'if' в '?' +importance: 5 + +--- -[importance 5] +# Перепишите 'if' в '?' Перепишите `if` с использованием оператора `'?'`: diff --git a/1-js/2-first-steps/12-ifelse/6-rewrite-if-else-question/task.md b/1-js/2-first-steps/12-ifelse/6-rewrite-if-else-question/task.md index b3babf08..4952611a 100644 --- a/1-js/2-first-steps/12-ifelse/6-rewrite-if-else-question/task.md +++ b/1-js/2-first-steps/12-ifelse/6-rewrite-if-else-question/task.md @@ -1,8 +1,10 @@ -# Перепишите 'if..else' в '?' +importance: 5 + +--- -[importance 5] +# Перепишите 'if..else' в '?' -Перепишите `if..else` с использованием нескольких операторов `'?'`. +Перепишите `if..else` с использованием нескольких операторов `'?'`. Для читаемости -- оформляйте код в несколько строк. diff --git a/1-js/2-first-steps/12-ifelse/article.md b/1-js/2-first-steps/12-ifelse/article.md index 4b33eb99..19da23b9 100644 --- a/1-js/2-first-steps/12-ifelse/article.md +++ b/1-js/2-first-steps/12-ifelse/article.md @@ -1,11 +1,12 @@ # Условные операторы: if, '?' Иногда, в зависимости от условия, нужно выполнить различные действия. Для этого используется оператор `if`. + [cut] + Например: -```js -//+ run +```js run var year = prompt('В каком году появилась спецификация ECMA-262 5.1?', ''); if (year != 2011) alert( 'А вот и неправильно!' ); @@ -24,20 +25,18 @@ if (year != 2011) { } ``` -**Рекомендуется использовать фигурные скобки всегда, даже когда команда одна.** +**Рекомендуется использовать фигурные скобки всегда, даже когда команда одна.** Это улучшает читаемость кода. - ## Преобразование к логическому типу -Оператор `if (...)` вычисляет и преобразует выражение в скобках к логическому типу. +Оператор `if (...)` вычисляет и преобразует выражение в скобках к логическому типу. В логическом контексте: -
    -
  • Число `0`, пустая строка `""`, `null` и `undefined`, а также `NaN` являются `false`,
  • -
  • Остальные значения -- `true`.
  • -
+ +- Число `0`, пустая строка `""`, `null` и `undefined`, а также `NaN` являются `false`, +- Остальные значения -- `true`. Например, такое условие никогда не выполнится: @@ -69,8 +68,7 @@ if (cond) { Необязательный блок `else` ("иначе") выполняется, если условие неверно: -```js -//+ run +```js run var year = prompt('Введите год появления стандарта ECMA-262 5.1', ''); if (year == 2011) { @@ -84,8 +82,7 @@ if (year == 2011) { Бывает нужно проверить несколько вариантов условия. Для этого используется блок `else if ...`. Например: -```js -//+ run +```js run var year = prompt('В каком году появилась спецификация ECMA-262 5.1?', ''); if (year < 2011) { @@ -99,12 +96,10 @@ if (year < 2011) { В примере выше JavaScript сначала проверит первое условие, если оно ложно -- перейдет ко второму -- и так далее, до последнего `else`. - ## Оператор вопросительный знак '?' Иногда нужно в зависимости от условия присвоить переменную. Например: -```js -//+ run no-beautify +```js run no-beautify var access; var age = prompt('Сколько вам лет?', ''); @@ -141,27 +136,25 @@ access = age > 14 ? true : false; ...Но когда скобки есть -- код лучше читается. Так что рекомендуется их писать. -[smart] +````smart В данном случае можно было бы обойтись и без оператора `'?'`, т.к. сравнение само по себе уже возвращает `true/false`: ```js access = age > 14; ``` -[/smart] +```` -[smart header="\"Тернарный оператор\""] -Вопросительный знак -- единственный оператор, у которого есть аж три аргумента, в то время как у обычных операторов их один-два. +```smart header="\"Тернарный оператор\"" +Вопросительный знак -- единственный оператор, у которого есть аж три аргумента, в то время как у обычных операторов их один-два. Поэтому его называют *"тернарный оператор"*. -[/smart] - +``` ## Несколько операторов '?' Последовательность операторов `'?'` позволяет вернуть значение в зависимости не от одного условия, а от нескольких. Например: -```js -//+ run +```js run var age = prompt('возраст?', 18); var message = (age < 3) ? 'Здравствуй, малыш!' : @@ -181,7 +174,7 @@ alert( message ); ```js if (age < 3) { message = 'Здравствуй, малыш!'; -} else if (a < 18) { +} else if (age < 18) { message = 'Привет!'; } else if (age < 100) { message = 'Здравствуйте!'; @@ -194,8 +187,7 @@ if (age < 3) { Иногда оператор вопросительный знак `'?'` используют как замену `if`: -```js -//+ run no-beautify +```js run no-beautify var company = prompt('Какая компания создала JavaScript?', ''); *!* @@ -204,18 +196,17 @@ var company = prompt('Какая компания создала JavaScript?', ' */!* ``` -Работает это так: в зависимости от условия, будет выполнена либо первая, либо вторая часть после `'?'`. +Работает это так: в зависимости от условия, будет выполнена либо первая, либо вторая часть после `'?'`. Результат выполнения не присваивается в переменную, так что пропадёт (впрочем, `alert` ничего не возвращает). **Рекомендуется не использовать вопросительный знак таким образом.** -Несмотря на то, что с виду такая запись короче `if`, она является существенно менее читаемой. +Несмотря на то, что с виду такая запись короче `if`, она является существенно менее читаемой. Вот, для сравнения, то же самое с `if`: -```js -//+ run no-beautify +```js run no-beautify var company = prompt('Какая компания создала JavaScript?', ''); *!* diff --git a/1-js/2-first-steps/13-logical-ops/1-alert-null-2-undefined/solution.md b/1-js/2-first-steps/13-logical-ops/1-alert-null-2-undefined/solution.md index 4756a6ce..3f22d9a8 100644 --- a/1-js/2-first-steps/13-logical-ops/1-alert-null-2-undefined/solution.md +++ b/1-js/2-first-steps/13-logical-ops/1-alert-null-2-undefined/solution.md @@ -1,7 +1,6 @@ Ответ: `2`, это первое значение, которое в логическом контексте даст `true`. -```js -//+ run +```js run alert( null || 2 || undefined ); ``` diff --git a/1-js/2-first-steps/13-logical-ops/1-alert-null-2-undefined/task.md b/1-js/2-first-steps/13-logical-ops/1-alert-null-2-undefined/task.md index f6e93650..8edfc7d7 100644 --- a/1-js/2-first-steps/13-logical-ops/1-alert-null-2-undefined/task.md +++ b/1-js/2-first-steps/13-logical-ops/1-alert-null-2-undefined/task.md @@ -1,6 +1,8 @@ -# Что выведет alert (ИЛИ)? +importance: 5 + +--- -[importance 5] +# Что выведет alert (ИЛИ)? Что выведет код ниже? diff --git a/1-js/2-first-steps/13-logical-ops/2-alert-or/solution.md b/1-js/2-first-steps/13-logical-ops/2-alert-or/solution.md index 30fce045..40de4042 100644 --- a/1-js/2-first-steps/13-logical-ops/2-alert-or/solution.md +++ b/1-js/2-first-steps/13-logical-ops/2-alert-or/solution.md @@ -1,15 +1,12 @@ Ответ: сначала `1`, затем `2`. -```js -//+ run +```js run alert( alert(1) || 2 || alert(3) ); ``` Вызов `alert` не возвращает значения, или, иначе говоря, возвращает `undefined`. -
    -
  1. Первый оператор ИЛИ `||` выполнит первый `alert(1)`, получит `undefined` и пойдёт дальше, ко второму операнду.
  2. -
  3. Так как второй операнд `2` является истинным, то вычисления завершатся, результатом `undefined || 2` будет `2`, которое будет выведено внешним `alert( .... )`.
  4. -
+1. Первый оператор ИЛИ `||` выполнит первый `alert(1)`, получит `undefined` и пойдёт дальше, ко второму операнду. +2. Так как второй операнд `2` является истинным, то вычисления завершатся, результатом `undefined || 2` будет `2`, которое будет выведено внешним `alert( .... )`. Второй оператор `||` не будет выполнен, выполнение до `alert(3)` не дойдёт, поэтому `3` выведено не будет. \ No newline at end of file diff --git a/1-js/2-first-steps/13-logical-ops/2-alert-or/task.md b/1-js/2-first-steps/13-logical-ops/2-alert-or/task.md index 1d4ed593..6cd09bda 100644 --- a/1-js/2-first-steps/13-logical-ops/2-alert-or/task.md +++ b/1-js/2-first-steps/13-logical-ops/2-alert-or/task.md @@ -1,6 +1,8 @@ -# Что выведет alert (ИЛИ)? +importance: 3 + +--- -[importance 3] +# Что выведет alert (ИЛИ)? Что выведет код ниже? diff --git a/1-js/2-first-steps/13-logical-ops/3-alert-1-null-2/solution.md b/1-js/2-first-steps/13-logical-ops/3-alert-1-null-2/solution.md index c91e674a..6cef884d 100644 --- a/1-js/2-first-steps/13-logical-ops/3-alert-1-null-2/solution.md +++ b/1-js/2-first-steps/13-logical-ops/3-alert-1-null-2/solution.md @@ -1,7 +1,6 @@ Ответ: `null`, это первое ложное значение из списка. -```js -//+ run +```js run alert( 1 && null && 2 ); ``` diff --git a/1-js/2-first-steps/13-logical-ops/3-alert-1-null-2/task.md b/1-js/2-first-steps/13-logical-ops/3-alert-1-null-2/task.md index 38fee457..79813818 100644 --- a/1-js/2-first-steps/13-logical-ops/3-alert-1-null-2/task.md +++ b/1-js/2-first-steps/13-logical-ops/3-alert-1-null-2/task.md @@ -1,6 +1,8 @@ -# Что выведет alert (И)? +importance: 5 + +--- -[importance 5] +# Что выведет alert (И)? Что выведет код ниже? diff --git a/1-js/2-first-steps/13-logical-ops/4-alert-and/solution.md b/1-js/2-first-steps/13-logical-ops/4-alert-and/solution.md index 83a88c73..5e0a6d50 100644 --- a/1-js/2-first-steps/13-logical-ops/4-alert-and/solution.md +++ b/1-js/2-first-steps/13-logical-ops/4-alert-and/solution.md @@ -1,7 +1,6 @@ Ответ: `1`, а затем `undefined`. -```js -//+ run +```js run alert( alert(1) && alert(2) ); ``` diff --git a/1-js/2-first-steps/13-logical-ops/4-alert-and/task.md b/1-js/2-first-steps/13-logical-ops/4-alert-and/task.md index 2d1594d9..0d84377a 100644 --- a/1-js/2-first-steps/13-logical-ops/4-alert-and/task.md +++ b/1-js/2-first-steps/13-logical-ops/4-alert-and/task.md @@ -1,6 +1,8 @@ -# Что выведет alert (И)? +importance: 3 + +--- -[importance 3] +# Что выведет alert (И)? Что выведет код ниже? diff --git a/1-js/2-first-steps/13-logical-ops/5-alert-and-or/solution.md b/1-js/2-first-steps/13-logical-ops/5-alert-and-or/solution.md index e02d8e49..6580e8ed 100644 --- a/1-js/2-first-steps/13-logical-ops/5-alert-and-or/solution.md +++ b/1-js/2-first-steps/13-logical-ops/5-alert-and-or/solution.md @@ -1,7 +1,6 @@ Ответ: `3`. -```js -//+ run +```js run alert( null || 2 && 3 || 4 ); ``` diff --git a/1-js/2-first-steps/13-logical-ops/5-alert-and-or/task.md b/1-js/2-first-steps/13-logical-ops/5-alert-and-or/task.md index 9e367c3b..48736169 100644 --- a/1-js/2-first-steps/13-logical-ops/5-alert-and-or/task.md +++ b/1-js/2-first-steps/13-logical-ops/5-alert-and-or/task.md @@ -1,6 +1,8 @@ -# Что выведет этот код? +importance: 5 + +--- -[importance 5] +# Что выведет этот код? Что выведет код ниже? diff --git a/1-js/2-first-steps/13-logical-ops/6-check-if-in-range/task.md b/1-js/2-first-steps/13-logical-ops/6-check-if-in-range/task.md index df16ad99..9205a805 100644 --- a/1-js/2-first-steps/13-logical-ops/6-check-if-in-range/task.md +++ b/1-js/2-first-steps/13-logical-ops/6-check-if-in-range/task.md @@ -1,7 +1,9 @@ -# Проверка if внутри диапазона +importance: 3 + +--- -[importance 3] +# Проверка if внутри диапазона Напишите условие `if` для проверки того факта, что переменная `age` находится между `14` и `90` включительно. -"Включительно" означает, что концы промежутка включены, то есть `age` может быть равна `14` или `90`. \ No newline at end of file +"Включительно" означает, что концы промежутка включены, то есть `age` может быть равна `14` или `90`. \ No newline at end of file diff --git a/1-js/2-first-steps/13-logical-ops/7-check-if-out-range/task.md b/1-js/2-first-steps/13-logical-ops/7-check-if-out-range/task.md index 1d833664..c1dd709a 100644 --- a/1-js/2-first-steps/13-logical-ops/7-check-if-out-range/task.md +++ b/1-js/2-first-steps/13-logical-ops/7-check-if-out-range/task.md @@ -1,6 +1,8 @@ -# Проверка if вне диапазона +importance: 3 + +--- -[importance 3] +# Проверка if вне диапазона Напишите условие `if` для проверки того факта, что `age` НЕ находится между 14 и 90 включительно. diff --git a/1-js/2-first-steps/13-logical-ops/8-if-question/solution.md b/1-js/2-first-steps/13-logical-ops/8-if-question/solution.md index f1b83dab..169af744 100644 --- a/1-js/2-first-steps/13-logical-ops/8-if-question/solution.md +++ b/1-js/2-first-steps/13-logical-ops/8-if-question/solution.md @@ -1,9 +1,8 @@ -Ответ: первое и третье выполнятся. +Ответ: первое и третье выполнятся. Детали: -```js -//+ run +```js run // Выполнится // Результат -1 || 0 = -1, в логическом контексте true if (-1 || 0) alert( 'первое' ); diff --git a/1-js/2-first-steps/13-logical-ops/8-if-question/task.md b/1-js/2-first-steps/13-logical-ops/8-if-question/task.md index ee7018b6..fbff4e7a 100644 --- a/1-js/2-first-steps/13-logical-ops/8-if-question/task.md +++ b/1-js/2-first-steps/13-logical-ops/8-if-question/task.md @@ -1,8 +1,10 @@ -# Вопрос про "if" +importance: 5 + +--- -[importance 5] +# Вопрос про "if" -Какие из этих `if` верны, т.е. выполнятся? +Какие из этих `if` верны, т.е. выполнятся? Какие конкретно значения будут результатами выражений в условиях `if(...)`? diff --git a/1-js/2-first-steps/13-logical-ops/article.md b/1-js/2-first-steps/13-logical-ops/article.md index c81990db..f65935e0 100644 --- a/1-js/2-first-steps/13-logical-ops/article.md +++ b/1-js/2-first-steps/13-logical-ops/article.md @@ -1,8 +1,9 @@ # Логические операторы -Для операций над логическими значениями в JavaScript есть `||` (ИЛИ), `&&` (И) и `!` (НЕ). +Для операций над логическими значениями в JavaScript есть `||` (ИЛИ), `&&` (И) и `!` (НЕ). Хоть они и называются *"логическими"*, но в JavaScript могут применяться к значениям любого типа и возвращают также значения любого типа. + [cut] ## || (ИЛИ) @@ -17,8 +18,7 @@ result = a || b; Получается следующая "таблица результатов": -```js -//+ run +```js run alert( true || true ); // true alert( false || true ); // true alert( true || false ); // true @@ -27,8 +27,7 @@ alert( false || false ); // false Если значение не логического типа -- то оно к нему приводится в целях вычислений. Например, число `1` будет воспринято как `true`, а `0` -- как `false`: -```js -//+ run +```js run if (1 || 0) { // сработает как if( true || false ) alert( 'верно' ); } @@ -36,8 +35,7 @@ if (1 || 0) { // сработает как if( true || false ) Обычно оператор ИЛИ используется в `if`, чтобы проверить, выполняется ли хотя бы одно из условий, например: -```js -//+ run +```js run var hour = 9; *!* @@ -49,8 +47,7 @@ if (hour < 10 || hour > 18) { Можно передать и больше условий: -```js -//+ run +```js run var hour = 12, isWeekend = true; @@ -61,28 +58,25 @@ if (hour < 10 || hour > 18 || isWeekend) { ## Короткий цикл вычислений - -JavaScript вычисляет несколько ИЛИ слева направо. При этом, чтобы экономить ресурсы, используется так называемый *"короткий цикл вычисления"*. +JavaScript вычисляет несколько ИЛИ слева направо. При этом, чтобы экономить ресурсы, используется так называемый *"короткий цикл вычисления"*. Допустим, вычисляются несколько ИЛИ подряд: `a || b || c || ...`. Если первый аргумент -- `true`, то результат заведомо будет `true` (хотя бы одно из значений -- `true`), и остальные значения игнорируются. -Это особенно заметно, когда выражение, переданное в качестве второго аргумента, имеет *сторонний эффект* -- например, присваивает переменную. +Это особенно заметно, когда выражение, переданное в качестве второго аргумента, имеет *сторонний эффект* -- например, присваивает переменную. При запуске примера ниже присвоение `x` не произойдёт: -```js -//+ run no-beautify +```js run no-beautify var x; -*!*true*/!* || (x = 1); +*!*true*/!* || (x = 1); alert(x); // undefined, x не присвоен ``` ...А в примере ниже первый аргумент -- `false`, так что ИЛИ попытается вычислить второй, запустив тем самым присваивание: -```js -//+ run no-beautify +```js run no-beautify var x; *!*false*/!* || (x = 1); @@ -92,10 +86,10 @@ alert(x); // 1 ## Значение ИЛИ -[quote author="Илья Канатов, участник курса JavaScript"] +```quote author="Илья Канатов, участник курса JavaScript" `||` запинается на "правде",
`&&` запинается на "лжи". -[/quote] +``` Итак, как мы видим, оператор ИЛИ вычисляет ровно столько значений, сколько необходимо -- до первого `true`. @@ -103,9 +97,8 @@ alert(x); // 1 Например: -```js -//+ run -alert( 1 || 0 ); // 1 +```js run +alert( 1 || 0 ); // 1 alert( true || 'неважно что' ); // true alert( null || 1 ); // 1 @@ -114,8 +107,7 @@ alert( undefined || 0 ); // 0 Это используют, в частности, чтобы выбрать первое "истинное" значение из списка: -```js -//+ run +```js run var undef; // переменная не присвоена, т.е. равна undefined var zero = 0; var emptyStr = ""; @@ -130,8 +122,7 @@ alert( result ); // выведет "Привет!" - первое значени Если все значения "ложные", то `||` возвратит последнее из них: -```js -//+ run +```js run alert( undefined || '' || false || 0 ); // 0 ``` @@ -141,7 +132,6 @@ alert( undefined || '' || false || 0 ); // 0 ## && (И) - Оператор И пишется как два амперсанда `&&`: ```js @@ -150,18 +140,16 @@ result = a && b; В классическом программировании И возвращает `true`, если оба аргумента истинны, а иначе -- `false`: -```js -//+ run +```js run alert( true && true ); // true alert( false && true ); // false alert( true && false ); // false alert( false && false ); // false ``` -Пример c `if`: +Пример с `if`: -```js -//+ run +```js run var hour = 12, minute = 30; @@ -172,8 +160,7 @@ if (hour == 12 && minute == 30) { Как и в ИЛИ, в И допустимы любые значения: -```js -//+ run +```js run if (1 && 0) { // вычислится как true && false alert( 'не сработает, т.к. условие ложно' ); } @@ -185,9 +172,8 @@ if (1 && 0) { // вычислится как true && false Например: -```js -//+ run -// Первый аргумент - true, +```js run +// Первый аргумент - true, // Поэтому возвращается второй аргумент alert( 1 && 0 ); // 0 alert( 1 && 5 ); // 5 @@ -200,8 +186,7 @@ alert( 0 && "не важно" ); // 0 Можно передать и несколько значений подряд, при этом возвратится первое "ложное" (на котором остановились вычисления), а если его нет -- то последнее: -```js -//+ run +```js run alert( 1 && 2 && null && 3 ); // null alert( 1 && 2 && 3 ); // 3 @@ -211,23 +196,20 @@ alert( 1 && 2 && 3 ); // 3 Иначе можно сказать, что "`&&` запинается на лжи". -[smart header="Приоритет у `&&` больше, чем у `||`"] +````smart header="Приоритет у `&&` больше, чем у `||`" Приоритет оператора И `&&` больше, чем ИЛИ `||`, так что он выполняется раньше. Поэтому в следующем коде сначала будет вычислено правое И: `1 && 0 = 0`, а уже потом -- ИЛИ. -```js -//+ run +```js run alert( 5 || 1 && 0 ); // 5 ``` -[/smart] - -[warn header="Не используйте `&&` вместо `if`"] +```` +````warn header="Не используйте `&&` вместо `if`" Оператор `&&` в простых случаях можно использовать вместо `if`, например: -```js -//+ run +```js run var x = 1; (x > 0) && alert( 'Больше' ); @@ -237,8 +219,7 @@ var x = 1; Получился аналог: -```js -//+ run +```js run var x = 1; if (x > 0) { @@ -247,7 +228,7 @@ if (x > 0) { ``` Однако, как правило, вариант с `if` лучше читается и воспринимается. Он более очевиден, поэтому лучше использовать его. Это, впрочем, относится и к другим неочевидным применениям возможностей языка. -[/warn] +```` ## ! (НЕ) @@ -259,23 +240,19 @@ var result = !value; Действия `!`: -
    -
  1. Сначала приводит аргумент к логическому типу `true/false`.
  2. -
  3. Затем возвращает противоположное значение.
  4. -
+1. Сначала приводит аргумент к логическому типу `true/false`. +2. Затем возвращает противоположное значение. Например: -```js -//+ run +```js run alert( !true ); // false alert( !0 ); // true ``` **В частности, двойное НЕ используют для преобразования значений к логическому типу:** -```js -//+ run +```js run alert( !!"строка" ); // true alert( !!null ); // false ``` diff --git a/1-js/2-first-steps/14-types-conversion/1-primitive-conversions-questions/solution.md b/1-js/2-first-steps/14-types-conversion/1-primitive-conversions-questions/solution.md index 7a0cbf25..8cc2d254 100644 --- a/1-js/2-first-steps/14-types-conversion/1-primitive-conversions-questions/solution.md +++ b/1-js/2-first-steps/14-types-conversion/1-primitive-conversions-questions/solution.md @@ -1,17 +1,16 @@ -```js -//+ no-beautify +```js no-beautify "" + 1 + 0 = "10" // (1) "" - 1 + 0 = -1 // (2) true + false = 1 -6 / "3" = 2 +6 / "3" = 2 "2" * "3" = 6 4 + 5 + "px" = "9px" "$" + 4 + 5
 = "$45" -"4" - 2
 = 2 +"4" - 2
 = 2 "4px" - 2
 = NaN -7 / 0
 = Infinity +7 / 0
 = Infinity " -9\n" + 5 = " -9\n5" " -9\n" - 5 = -14 5 && 2
 = 2 @@ -24,11 +23,9 @@ null == "\n0\n" = false // (5) +null == +"\n0\n" = true // (6) ``` -
    -
  1. Оператор `"+"` в данном случае прибавляет `1` как строку, и затем `0`.
  2. -
  3. Оператор `"-"` работает только с числами, так что он сразу приводит `""` к `0`.
  4. -
  5. `null` при численном преобразовании становится `0`
  6. -
  7. `undefined` при численном преобразовании становится `NaN`
  8. -
  9. При сравнении `==` с `null` преобразования не происходит, есть жёсткое правило: `null == undefined` и только.
  10. -
  11. И левая и правая часть `==` преобразуются к числу `0`.
  12. -
\ No newline at end of file +1. Оператор `"+"` в данном случае прибавляет `1` как строку, и затем `0`. +2. Оператор `"-"` работает только с числами, так что он сразу приводит `""` к `0`. +3. `null` при численном преобразовании становится `0` +4. `undefined` при численном преобразовании становится `NaN` +5. При сравнении `==` с `null` преобразования не происходит, есть жёсткое правило: `null == undefined` и только. +6. И левая и правая часть `==` преобразуются к числу `0`. diff --git a/1-js/2-first-steps/14-types-conversion/1-primitive-conversions-questions/task.md b/1-js/2-first-steps/14-types-conversion/1-primitive-conversions-questions/task.md index 1c2c953f..281a8d60 100644 --- a/1-js/2-first-steps/14-types-conversion/1-primitive-conversions-questions/task.md +++ b/1-js/2-first-steps/14-types-conversion/1-primitive-conversions-questions/task.md @@ -1,22 +1,23 @@ -# Вопросник по преобразованиям, для примитивов +importance: 5 + +--- -[importance 5] +# Вопросник по преобразованиям, для примитивов Подумайте, какой результат будет у выражений ниже. Тут не только преобразования типов. Когда закончите -- сверьтесь с решением. -```js -//+ no-beautify -"" + 1 + 0 +```js no-beautify +"" + 1 + 0 "" - 1 + 0 true + false 6 / "3" "2" * "3" 4 + 5 + "px" -"$" + 4 + 5
 -"4" - 2
 +"$" + 4 + 5
 +"4" - 2
 "4px" - 2
 -7 / 0
 -" -9\n" + 5 +7 / 0
 +" -9\n" + 5 " -9\n" - 5 5 && 2
 2 && 5
 @@ -24,7 +25,7 @@ true + false 0 || 5 null + 1 undefined + 1 -null == "\n0\n" -+null == +"\n0\n" +null == "\n0\n" ++null == +"\n0\n" ``` diff --git a/1-js/2-first-steps/14-types-conversion/article.md b/1-js/2-first-steps/14-types-conversion/article.md index 3c7d44ab..602109c4 100644 --- a/1-js/2-first-steps/14-types-conversion/article.md +++ b/1-js/2-first-steps/14-types-conversion/article.md @@ -1,23 +1,22 @@ # Преобразование типов для примитивов Система преобразования типов в JavaScript очень проста, но отличается от других языков. Поэтому она часто служит "камнем преткновения" для приходящих из других языков программистов. + [cut] + Всего есть три преобразования: -
    -
  1. Cтроковое преобразование.
  2. -
  3. Числовое преобразование.
  4. -
  5. Преобразование к логическому значению.
  6. -
-**Эта глава описывает преобразование только примитивных значений, объекты разбираются далее.** +1. Строковое преобразование. +2. Числовое преобразование. +3. Преобразование к логическому значению. +**Эта глава описывает преобразование только примитивных значений, объекты разбираются далее.** -## Строковое преобразование +## Строковое преобразование Строковое преобразование происходит, когда требуется представление чего-либо в виде строки. Например, его производит функция `alert`. -```js -//+ run +```js run var a = true; alert( a ); // "true" @@ -25,8 +24,7 @@ alert( a ); // "true" Можно также осуществить преобразование явным вызовом `String(val)`: -```js -//+ run +```js run alert( String(null) === "null" ); // true ``` @@ -34,13 +32,12 @@ alert( String(null) === "null" ); // true Также для явного преобразования применяется оператор `"+"`, у которого один из аргументов строка. В этом случае он приводит к строке и другой аргумент, например: -```js -//+ run +```js run alert( true + "test" ); // "truetest" alert( "123" + undefined ); // "123undefined" ``` -## Численное преобразование +## Численное преобразование Численное преобразование происходит в математических функциях и выражениях, а также при сравнении данных различных типов (кроме сравнений `===`, `!==`). @@ -56,52 +53,44 @@ var a = Number("123"); // 123, тот же эффект ЗначениеПреобразуется в... -`undefined``NaN` -`null``0` -`true / false``1 / 0` -СтрокаПробельные символы по краям обрезаются.
Далее, если остаётся пустая строка, то `0`, иначе из непустой строки "считывается" число, при ошибке результат `NaN`. +undefinedNaN +null0 +true / false1 / 0 +СтрокаПробельные символы по краям обрезаются.
Далее, если остаётся пустая строка, то 0, иначе из непустой строки "считывается" число, при ошибке результат NaN. Например: -```js -//+ run +```js run // после обрезания пробельных символов останется "123" alert( +" \n 123 \n \n" ); // 123 ``` Ещё примеры: -
    -
  • Логические значения: -```js -//+ run -alert( +true ); // 1 -alert( +false ); // 0 -``` +- Логические значения: -
  • -
  • Сравнение разных типов -- значит численное преобразование: + ```js run + alert( +true ); // 1 + alert( +false ); // 0 + ``` +- Сравнение разных типов -- значит численное преобразование: -```js -//+ run -alert( "\n0 " == 0 ); // true -``` + ```js run + alert( "\n0 " == 0 ); // true + ``` -При этом строка `"\n0"` преобразуется к числу, как указано выше: начальные и конечные пробелы обрезаются, получается строка `"0"`, которая равна `0`.
  • - -
  • С логическими значениями: + При этом строка `"\n0"` преобразуется к числу, как указано выше: начальные и конечные пробелы обрезаются, получается строка `"0"`, которая равна `0`. -```js -//+ run -alert( "\n" == false ); -alert( "1" == true ); -``` +- С логическими значениями: + + ```js run + alert( "\n" == false ); + alert( "1" == true ); + ``` -Здесь сравнение `"=="` снова приводит обе части к числу. В первой строке слева и справа получается `0`, во второй `1`. -
  • -
+ Здесь сравнение `"=="` снова приводит обе части к числу. В первой строке слева и справа получается `0`, во второй `1`. ### Специальные значения @@ -112,18 +101,17 @@ alert( "1" == true ); Специальные значения преобразуются к числу так: - - + +
ЗначениеПреобразуется в...
`undefined``NaN`
`null``0`
undefinedNaN
null0
Это преобразование осуществляется при арифметических операциях и сравнениях `> >= < <=`, но не при проверке равенства `==`. Алгоритм проверки равенства для этих значений в спецификации прописан отдельно (пункт [11.9.3](http://es5.github.com/x11.html#x11.9.3)). В нём считается, что `null` и `undefined` равны `"=="` между собой, но эти значения не равны никакому другому значению. -Это ведёт к забавным последствиям. +Это ведёт к забавным последствиям. Например, `null` не подчиняется законам математики -- он "больше либо равен нулю": `null>=0`, но не больше и не равен: -```js -//+ run +```js run alert( null >= 0 ); // true, т.к. null преобразуется к 0 alert( null > 0 ); // false (не больше), т.к. null преобразуется к 0 alert( null == 0 ); // false (и не равен!), т.к. == рассматривает null особо. @@ -131,22 +119,21 @@ alert( null == 0 ); // false (и не равен!), т.к. == рассматри Значение `undefined` вообще "несравнимо": -```js -//+ run +```js run alert( undefined > 0 ); // false, т.к. undefined -> NaN alert( undefined == 0 ); // false, т.к. это undefined (без преобразования) alert( undefined < 0 ); // false, т.к. undefined -> NaN ``` -**Для более очевидной работы кода и во избежание ошибок лучше не давать специальным значениям участвовать в сравнениях `> >= < <=`.** +**Для более очевидной работы кода и во избежание ошибок лучше не давать специальным значениям участвовать в сравнениях `> >= < <=`.** Используйте в таких случаях переменные-числа или приводите к числу явно. - + ## Логическое преобразование Преобразование к `true/false` происходит в логическом контексте, таком как `if(value)`, и при применении логических операторов. -Все значения, которые интуитивно "пусты", становятся `false`. Их несколько: `0`, пустая строка, `null`, `undefined` и `NaN`. +Все значения, которые интуитивно "пусты", становятся `false`. Их несколько: `0`, пустая строка, `null`, `undefined` и `NaN`. Остальное, в том числе и любые объекты -- `true`. @@ -154,25 +141,22 @@ alert( undefined < 0 ); // false, т.к. undefined -> NaN - - - - + + + +
ЗначениеПреобразуется в...
`undefined`, `null``false`
ЧислаВсе `true`, кроме `0`, `NaN` -- `false`.
СтрокиВсе `true`, кроме пустой строки `""` -- `false`
ОбъектыВсегда `true`
undefined, nullfalse
ЧислаВсе true, кроме 0, NaN -- false.
СтрокиВсе true, кроме пустой строки "" -- false
ОбъектыВсегда true
**Для явного преобразования используется двойное логическое отрицание `!!value` или вызов `Boolean(value)`.** -[warn header="Обратите внимание: строка `\"0\"` становится `true`"] +````warn header="Обратите внимание: строка `\"0\"` становится `true`" В отличие от многих языков программирования (например PHP), `"0"` в JavaScript является `true`, как и строка из пробелов: -```js -//+ run +```js run alert( !!"0" ); // true alert( !!" " ); // любые непустые строки, даже из пробелов - true! ``` - -[/warn] - +```` Логическое преобразование интересно тем, как оно сочетается с численным. @@ -180,33 +164,29 @@ alert( !!" " ); // любые непустые строки, даже из пр Например, равенство в следующем примере верно, так как происходит численное преобразование: -```js -//+ run +```js run alert( 0 == "\n0\n" ); // true ``` -...А в логическом контексте левая часть даст `false`, правая -- `true`: +... А в логическом контексте левая часть (0) даст `false`, правая ("\n0\n") -- `true`, так как любая не пустая строка в логическом контексте равна `true`: -```js -//+ run +```js run if ("\n0\n") { alert( "true, совсем не как 0!" ); } ``` -С точки зрения преобразования типов в JavaScript это совершенно нормально. При равенстве -- численное преобразование, а в `if` -- логическое, только и всего. +С точки зрения преобразования типов в JavaScript это совершенно нормально. При сравнении с помощью "==" -- численное преобразование, а в `if` -- логическое, только и всего. ## Итого В JavaScript есть три преобразования: -
    -
  1. Строковое: `String(value)` -- в строковом контексте или при сложении со строкой. Работает очевидным образом.
  2. -
  3. Численное: `Number(value)` -- в численном контексте, включая унарный плюс `+value`. Происходит при сравнении разных типов, кроме строгого равенства.
  4. -
  5. Логическое: `Boolean(value)` -- в логическом контексте, можно также сделать двойным НЕ: `!!value`.
  6. -
+1. Строковое: `String(value)` -- в строковом контексте или при сложении со строкой. Работает очевидным образом. +2. Численное: `Number(value)` -- в численном контексте, включая унарный плюс `+value`. Происходит при сравнении разных типов, кроме строгого равенства. +3. Логическое: `Boolean(value)` -- в логическом контексте, можно также сделать двойным НЕ: `!!value`. Точные таблицы преобразований даны выше в этой главе. -Особым случаем является проверка равенства с `null` и `undefined`. Они равны друг другу, но не равны чему бы то ни было ещё, этот случай прописан особо в спецификации. +Особым случаем является проверка равенства с `null` и `undefined`. Они равны друг другу, но не равны чему бы то ни было ещё, этот случай прописан особо в спецификации. diff --git a/1-js/2-first-steps/15-while-for/1-loop-last-value/solution.md b/1-js/2-first-steps/15-while-for/1-loop-last-value/solution.md index 15c634c9..b702b632 100644 --- a/1-js/2-first-steps/15-while-for/1-loop-last-value/solution.md +++ b/1-js/2-first-steps/15-while-for/1-loop-last-value/solution.md @@ -1,7 +1,6 @@ Ответ: `1`. -```js -//+ run +```js run var i = 3; while (i) { @@ -9,7 +8,7 @@ while (i) { } ``` -Каждое выполнение цикла уменьшает `i`. Проверка `while(i)` даст сигнал "стоп" при `i = 0`. +Каждое выполнение цикла уменьшает `i`. Проверка `while(i)` даст сигнал "стоп" при `i = 0`. Соответственно, шаги цикла: diff --git a/1-js/2-first-steps/15-while-for/1-loop-last-value/task.md b/1-js/2-first-steps/15-while-for/1-loop-last-value/task.md index 25359633..7eeacc85 100644 --- a/1-js/2-first-steps/15-while-for/1-loop-last-value/task.md +++ b/1-js/2-first-steps/15-while-for/1-loop-last-value/task.md @@ -1,6 +1,8 @@ -# Последнее значение цикла +importance: 3 + +--- -[importance 3] +# Последнее значение цикла Какое последнее значение выведет этот код? Почему? diff --git a/1-js/2-first-steps/15-while-for/2-which-value-while/solution.md b/1-js/2-first-steps/15-while-for/2-which-value-while/solution.md index f7e77582..76c8bd8b 100644 --- a/1-js/2-first-steps/15-while-for/2-which-value-while/solution.md +++ b/1-js/2-first-steps/15-while-for/2-which-value-while/solution.md @@ -1,31 +1,27 @@ -
    -
  1. **От 1 до 4** -```js -//+ run -var i = 0; -while (++i < 5) alert( i ); -``` +1. **От 1 до 4** -Первое значение: `i=1`, так как операция `++i` сначала увеличит `i`, а потом уже произойдёт сравнение и выполнение `alert`. + ```js run + var i = 0; + while (++i < 5) alert( i ); + ``` -Далее `2,3,4..` Значения выводятся одно за другим. Для каждого значения сначала происходит увеличение, а потом -- сравнение, так как `++` стоит перед переменной. + Первое значение: `i=1`, так как операция `++i` сначала увеличит `i`, а потом уже произойдёт сравнение и выполнение `alert`. -При `i=4` произойдет увеличение `i` до `5`, а потом сравнение `while(5 < 5)` -- это неверно. Поэтому на этом цикл остановится, и значение `5` выведено не будет. -
  2. -
  3. **От 1 до 5** + Далее `2,3,4..` Значения выводятся одно за другим. Для каждого значения сначала происходит увеличение, а потом -- сравнение, так как `++` стоит перед переменной. -```js -//+ run -var i = 0; -while (i++ < 5) alert( i ); -``` + При `i=4` произойдет увеличение `i` до `5`, а потом сравнение `while(5 < 5)` -- это неверно. Поэтому на этом цикл остановится, и значение `5` выведено не будет. +2. **От 1 до 5** -Первое значение: `i=1`. Остановимся на нём подробнее. Оператор `i++` увеличивает `i`, возвращая старое значение, так что в сравнении `i++ < 5` будет участвовать старое `i=0`. + ```js run + var i = 0; + while (i++ < 5) alert( i ); + ``` -Но последующий вызов `alert` уже не относится к этому выражению, так что получит новый `i=1`. + Первое значение: `i=1`. Остановимся на нём подробнее. Оператор `i++` увеличивает `i`, возвращая старое значение, так что в сравнении `i++ < 5` будет участвовать старое `i=0`. -Далее `2,3,4..` Для каждого значения сначала происходит сравнение, а потом -- увеличение, и затем срабатывание `alert`. + Но последующий вызов `alert` уже не относится к этому выражению, так что получит новый `i=1`. -Окончание цикла: при `i=4` произойдет сравнение `while(4 < 5)` -- верно, после этого сработает `i++`, увеличив `i` до `5`, так что значение `5` будет выведено. Оно станет последним.
  4. -
\ No newline at end of file + Далее `2,3,4..` Для каждого значения сначала происходит сравнение, а потом -- увеличение, и затем срабатывание `alert`. + + Окончание цикла: при `i=4` произойдет сравнение `while(4 < 5)` -- верно, после этого сработает `i++`, увеличив `i` до `5`, так что значение `5` будет выведено. Оно станет последним. diff --git a/1-js/2-first-steps/15-while-for/2-which-value-while/task.md b/1-js/2-first-steps/15-while-for/2-which-value-while/task.md index 674f005b..7053db33 100644 --- a/1-js/2-first-steps/15-while-for/2-which-value-while/task.md +++ b/1-js/2-first-steps/15-while-for/2-which-value-while/task.md @@ -1,23 +1,20 @@ -# Какие значения i выведет цикл while? +importance: 4 -[importance 4] +--- -Для каждого цикла запишите, какие значения он выведет. Потом сравните с ответом. -
    -
  1. Префиксный вариант +# Какие значения i выведет цикл while? -```js -var i = 0; -while (++i < 5) alert( i ); -``` +Для каждого цикла запишите, какие значения он выведет. Потом сравните с ответом. -
  2. -
  3. Постфиксный вариант +1. Префиксный вариант -```js -var i = 0; -while (i++ < 5) alert( i ); -``` + ```js + var i = 0; + while (++i < 5) alert( i ); + ``` +2. Постфиксный вариант -
  4. -
\ No newline at end of file + ```js + var i = 0; + while (i++ < 5) alert( i ); + ``` diff --git a/1-js/2-first-steps/15-while-for/3-which-value-for/solution.md b/1-js/2-first-steps/15-while-for/3-which-value-for/solution.md index 941a07be..c0078fe0 100644 --- a/1-js/2-first-steps/15-while-for/3-which-value-for/solution.md +++ b/1-js/2-first-steps/15-while-for/3-which-value-for/solution.md @@ -1,17 +1,15 @@ **Ответ: от 0 до 4 в обоих случаях.** -```js -//+ run +```js run for (var i = 0; i < 5; ++i) alert( i ); for (var i = 0; i < 5; i++) alert( i ); ``` Такой результат обусловлен алгоритмом работы `for`: -
    -
  1. Выполнить присвоение `i=0`
  2. -
  3. Проверить `i<5`
  4. -
  5. Если верно - выполнить тело цикла `alert(i)`, затем выполнить `i++`
  6. -
+ +1. Выполнить присвоение `i=0` +2. Проверить `i<5` +3. Если верно - выполнить тело цикла `alert(i)`, затем выполнить `i++` Увеличение `i++` выполняется отдельно от проверки условия (2), значение `i` при этом не используется, поэтому нет никакой разницы между `i++` и `++i`. \ No newline at end of file diff --git a/1-js/2-first-steps/15-while-for/3-which-value-for/task.md b/1-js/2-first-steps/15-while-for/3-which-value-for/task.md index e918c89c..d5cd2093 100644 --- a/1-js/2-first-steps/15-while-for/3-which-value-for/task.md +++ b/1-js/2-first-steps/15-while-for/3-which-value-for/task.md @@ -1,21 +1,18 @@ -# Какие значения i выведет цикл for? +importance: 4 -[importance 4] +--- -Для каждого цикла запишите, какие значения он выведет. Потом сравните с ответом. -
    -
  1. Постфиксная форма: +# Какие значения i выведет цикл for? -```js -for (var i = 0; i < 5; i++) alert( i ); -``` +Для каждого цикла запишите, какие значения он выведет. Потом сравните с ответом. -
  2. -
  3. Префиксная форма: +1. Постфиксная форма: -```js -for (var i = 0; i < 5; ++i) alert( i ); -``` + ```js + for (var i = 0; i < 5; i++) alert( i ); + ``` +2. Префиксная форма: -
  4. -
\ No newline at end of file + ```js + for (var i = 0; i < 5; ++i) alert( i ); + ``` diff --git a/1-js/2-first-steps/15-while-for/4-for-even/solution.md b/1-js/2-first-steps/15-while-for/4-for-even/solution.md index 62ca05c7..eaf7641e 100644 --- a/1-js/2-first-steps/15-while-for/4-for-even/solution.md +++ b/1-js/2-first-steps/15-while-for/4-for-even/solution.md @@ -1,7 +1,6 @@ -```js -//+ run demo +```js run demo for (var i = 2; i <= 10; i++) { if (i % 2 == 0) { alert( i ); diff --git a/1-js/2-first-steps/15-while-for/4-for-even/task.md b/1-js/2-first-steps/15-while-for/4-for-even/task.md index d20029d0..5674088a 100644 --- a/1-js/2-first-steps/15-while-for/4-for-even/task.md +++ b/1-js/2-first-steps/15-while-for/4-for-even/task.md @@ -1,7 +1,9 @@ -# Выведите чётные числа +importance: 5 + +--- -[importance 5] +# Выведите чётные числа При помощи цикла `for` выведите чётные числа от `2` до `10`. -[demo /] \ No newline at end of file +[demo] diff --git a/1-js/2-first-steps/15-while-for/5-replace-for-while/solution.md b/1-js/2-first-steps/15-while-for/5-replace-for-while/solution.md index 84915ab4..05059918 100644 --- a/1-js/2-first-steps/15-while-for/5-replace-for-while/solution.md +++ b/1-js/2-first-steps/15-while-for/5-replace-for-while/solution.md @@ -1,7 +1,6 @@ -```js -//+ run +```js run var i = 0; while (i < 3) { alert( "номер " + i + "!" ); diff --git a/1-js/2-first-steps/15-while-for/5-replace-for-while/task.md b/1-js/2-first-steps/15-while-for/5-replace-for-while/task.md index 2da6d6f2..9b5cca54 100644 --- a/1-js/2-first-steps/15-while-for/5-replace-for-while/task.md +++ b/1-js/2-first-steps/15-while-for/5-replace-for-while/task.md @@ -1,11 +1,12 @@ -# Замените for на while +importance: 5 + +--- -[importance 5] +# Замените for на while Перепишите код, заменив цикл `for` на `while`, без изменения поведения цикла. -```js -//+ run +```js run for (var i = 0; i < 3; i++) { alert( "номер " + i + "!" ); } diff --git a/1-js/2-first-steps/15-while-for/6-repeat-until-correct/solution.md b/1-js/2-first-steps/15-while-for/6-repeat-until-correct/solution.md index 49d5396e..4cc84703 100644 --- a/1-js/2-first-steps/15-while-for/6-repeat-until-correct/solution.md +++ b/1-js/2-first-steps/15-while-for/6-repeat-until-correct/solution.md @@ -1,7 +1,6 @@ -```js -//+ run demo +```js run demo var num; do { @@ -10,9 +9,8 @@ do { ``` Цикл `do..while` повторяется, пока верны две проверки: -
    -
  1. Проверка `num <= 100` -- то есть, введённое число всё еще меньше `100`.
  2. -
  3. Проверка `num != null` -- значение `null` означает, что посетитель нажал "Отмена", в этом случае цикл тоже нужно прекратить.
  4. -
+ +1. Проверка `num <= 100` -- то есть, введённое число всё еще меньше `100`. +2. Проверка `num != null` -- значение `null` означает, что посетитель нажал "Отмена", в этом случае цикл тоже нужно прекратить. Кстати, сравнение `num <= 100` при вводе `null` даст `true`, так что вторая проверка необходима. \ No newline at end of file diff --git a/1-js/2-first-steps/15-while-for/6-repeat-until-correct/task.md b/1-js/2-first-steps/15-while-for/6-repeat-until-correct/task.md index 4c2fbedc..732848f0 100644 --- a/1-js/2-first-steps/15-while-for/6-repeat-until-correct/task.md +++ b/1-js/2-first-steps/15-while-for/6-repeat-until-correct/task.md @@ -1,11 +1,14 @@ -# Повторять цикл, пока ввод неверен +importance: 5 + +--- -[importance 5] +# Повторять цикл, пока ввод неверен -Напишите цикл, который предлагает `prompt` ввести число, большее `100`. Если посетитель ввёл другое число -- попросить ввести ещё раз, и так далее. +Напишите цикл, который предлагает `prompt` ввести число, большее `100`. Если посетитель ввёл другое число -- попросить ввести ещё раз, и так далее. Цикл должен спрашивать число пока либо посетитель не введёт число, большее `100`, либо не нажмёт кнопку Cancel (ESC). Предполагается, что посетитель вводит только числа. Предусматривать обработку нечисловых строк в этой задаче необязательно. -[demo /] +[demo] + diff --git a/1-js/2-first-steps/15-while-for/7-list-primes/solution.md b/1-js/2-first-steps/15-while-for/7-list-primes/solution.md index eead33a3..02eea8bc 100644 --- a/1-js/2-first-steps/15-while-for/7-list-primes/solution.md +++ b/1-js/2-first-steps/15-while-for/7-list-primes/solution.md @@ -12,10 +12,9 @@ Решение с использованием метки: -```js -//+ run +```js run nextPrime: - for (var i = 2; i < 10; i++) { + for (var i = 2; i <= 10; i++) { for (var j = 2; j < i; j++) { if (i % j == 0) continue nextPrime; @@ -25,4 +24,4 @@ nextPrime: } ``` -Конечно же, его можно оптимизировать с точки зрения производительности. Например, проверять все `j` не от `2` до `i`, а от `2` до квадратного корня из `i`. А для очень больших чисел -- существуют более эффективные специализированные алгоритмы проверки простоты числа, например [квадратичное решето](http://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) и [решето числового поля](http://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). \ No newline at end of file +Конечно же, его можно оптимизировать с точки зрения производительности. Например, проверять все `j` не от `2` до `i`, а от `2` до квадратного корня из `i`. А для очень больших чисел -- существуют более эффективные специализированные алгоритмы проверки простоты числа, например [квадратичное решето](http://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) и [решето числового поля](http://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). diff --git a/1-js/2-first-steps/15-while-for/7-list-primes/task.md b/1-js/2-first-steps/15-while-for/7-list-primes/task.md index 51f8c29f..6867714f 100644 --- a/1-js/2-first-steps/15-while-for/7-list-primes/task.md +++ b/1-js/2-first-steps/15-while-for/7-list-primes/task.md @@ -1,6 +1,8 @@ -# Вывести простые числа +importance: 3 + +--- -[importance 3] +# Вывести простые числа Натуральное число, большее 1, называется *простым*, если оно ни на что не делится, кроме себя и `1`. diff --git a/1-js/2-first-steps/15-while-for/article.md b/1-js/2-first-steps/15-while-for/article.md index 71991707..eba33741 100644 --- a/1-js/2-first-steps/15-while-for/article.md +++ b/1-js/2-first-steps/15-while-for/article.md @@ -2,10 +2,12 @@ При написании скриптов зачастую встает задача сделать однотипное действие много раз. -Например, вывести товары из списка один за другим. Или просто перебрать все числа от 1 до 10 и для каждого выполнить одинаковый код. +Например, вывести товары из списка один за другим. Или просто перебрать все числа от 1 до 10 и для каждого выполнить одинаковый код. Для многократного повторения одного участка кода - предусмотрены *циклы*. + [cut] + ## Цикл while Цикл `while` имеет вид: @@ -20,8 +22,7 @@ while (условие) { Например, цикл ниже выводит `i` пока `i < 3`: -```js -//+ run +```js run var i = 0; while (i < 3) { alert( i ); @@ -43,8 +44,7 @@ while (true) { **Условие в скобках интерпретируется как логическое значение, поэтому вместо `while (i!=0)` обычно пишут `while (i)`**: -```js -//+ run +```js run var i = 3; *!* while (i) { // при i, равном 0, значение в скобках будет false и цикл остановится @@ -54,7 +54,7 @@ while (i) { // при i, равном 0, значение в скобках бу } ``` -## Цикл do..while +## Цикл do..while Проверку условия можно поставить *под* телом цикла, используя специальный синтаксис `do..while`: @@ -68,8 +68,7 @@ do { Например: -```js -//+ run +```js run var i = 0; do { alert( i ); @@ -79,8 +78,7 @@ do { Синтаксис `do..while` редко используется, т.к. обычный `while` нагляднее -- в нём не приходится искать глазами условие и ломать голову, почему оно проверяется именно в конце. - -## Цикл for +## Цикл for Чаще всего применяется цикл `for`. Выглядит он так: @@ -92,8 +90,7 @@ for (начало; условие; шаг) { Пример цикла, который выполняет `alert(i)` для `i` от `0` до `2` включительно (до `3`): -```js -//+ run +```js run var i; for (i = 0; i < 3; i++) { @@ -102,37 +99,33 @@ for (i = 0; i < 3; i++) { ``` Здесь: -
    -
  • **Начало:** `i=0`.
  • -
  • **Условие:** `i<3`.
  • -
  • **Шаг:** `i++`.
  • -
  • **Тело:** `alert(i)`, т.е. код внутри фигурных скобок (они не обязательны, если только одна операция)
  • -
+ +- **Начало:** `i=0`. +- **Условие:** `i<3`. +- **Шаг:** `i++`. +- **Тело:** `alert(i)`, т.е. код внутри фигурных скобок (они не обязательны, если только одна операция) Цикл выполняется так: -
    -
  1. Начало: `i=0` выполняется один-единственный раз, при заходе в цикл.
  2. -
  3. Условие: `i<3` проверяется перед каждой итерацией и при входе в цикл, если оно нарушено, то происходит выход.
  4. -
  5. Тело: `alert(i)`.
  6. -
  7. Шаг: `i++` выполняется после *тела* на каждой итерации, но перед проверкой условия.
  8. -
  9. Идти на шаг 2.
  10. -
+1. Начало: `i=0` выполняется один-единственный раз, при заходе в цикл. +2. Условие: `i<3` проверяется перед каждой итерацией и при входе в цикл, если оно нарушено, то происходит выход. +3. Тело: `alert(i)`. +4. Шаг: `i++` выполняется после *тела* на каждой итерации, но перед проверкой условия. +5. Идти на шаг 2. Иными словами, поток выполнения: `начало` -> (если `условие` -> `тело` -> `шаг`) -> (если `условие` -> `тело` -> `шаг`) -> ... и так далее, пока верно `условие`. -[smart] +````smart В цикле также можно определить переменную: -```js -//+ run no-beautify +```js run no-beautify for (*!*var*/!* i = 0; i < 3; i++) { alert(i); // 0, 1, 2 } ``` Эта переменная будет видна и за границами цикла, в частности, после окончания цикла `i` станет равно `3`. -[/smart] +```` ## Пропуск частей for @@ -140,8 +133,7 @@ for (*!*var*/!* i = 0; i < 3; i++) { Например, можно убрать `начало`. Цикл в примере ниже полностью идентичен приведённому выше: -```js -//+ run +```js run var i = 0; for (; i < 3; i++) { @@ -152,7 +144,6 @@ for (; i < 3; i++) { Можно убрать и `шаг`: ```js -//+ run var i = 0; for (; i < 3;) { @@ -165,18 +156,17 @@ for (; i < 3;) { ```js for (;;) { - // будет выполняться вечно + // будет выполняться вечно } ``` При этом сами точки с запятой `;` обязательно должны присутствовать, иначе будет ошибка синтаксиса. -[smart header="`for..in`"] -Существует также специальная конструкция `for..in` для перебора свойств объекта. - -Мы познакомимся с ней позже, когда будем [говорить об объектах](#for..in). -[/smart] +```smart header="`for..in`" +Существует также специальная конструкция `for..in` для перебора свойств объекта. +Мы познакомимся с ней позже, когда будем [говорить об объектах](info:object-for-in#for..in). +``` ## Прерывание цикла: break @@ -215,10 +205,9 @@ alert( 'Сумма: ' + sum ); Например, цикл ниже использует `continue`, чтобы не выводить чётные значения: -```js -//+ run no-beautify +```js run no-beautify for (var i = 0; i < 10; i++) { - + *!*if (i % 2 == 0) continue;*/!* alert(i); @@ -227,8 +216,7 @@ for (var i = 0; i < 10; i++) { Для чётных `i` срабатывает `continue`, выполнение тела прекращается и управление передаётся на следующий проход `for`. -[smart header="Директива `continue` позволяет обойтись без скобок"] - +````smart header="Директива `continue` позволяет обойтись без скобок" Цикл, который обрабатывает только нечётные значения, мог бы выглядеть так: ```js @@ -242,9 +230,9 @@ for (var i = 0; i < 10; i++) { ``` С технической точки зрения он полностью идентичен. Действительно, вместо `continue` можно просто завернуть действия в блок `if`. Однако, мы получили дополнительный уровень вложенности фигурных скобок. Если код внутри `if` более длинный, то это ухудшает читаемость, в отличие от варианта с `continue`. -[/smart] +```` -[warn header="Нельзя использовать break/continue справа от оператора '?'"] +````warn header="Нельзя использовать break/continue справа от оператора '?'" Обычно мы можем заменить `if` на оператор вопросительный знак `'?'`. То есть, запись: @@ -265,21 +253,20 @@ if (условие) { В обоих случаях в зависимости от условия выполняется либо `a()` либо `b()`. -Но разница состоит в том, что оператор вопросительный знак `'?'`, использованный во второй записи, возвращает значение. +Но разница состоит в том, что оператор вопросительный знак `'?'`, использованный во второй записи, возвращает значение. -**Синтаксические конструкции, которые не возвращают значений, нельзя использовать в операторе `'?'`.** +**Синтаксические конструкции, которые не возвращают значений, нельзя использовать в операторе `'?'`.** К таким относятся большинство конструкций и, в частности, `break/continue`. Поэтому такой код приведёт к ошибке: -```js -//+ no-beautify +```js no-beautify (i > 5) ? alert(i) : *!*continue*/!*; ``` Впрочем, как уже говорилось ранее, оператор вопросительный знак `'?'` не стоит использовать таким образом. Это -- всего лишь ещё одна причина, почему для проверки условия предпочтителен `if`. -[/warn] +```` ## Метки для break/continue @@ -287,15 +274,14 @@ if (условие) { Например, внутри цикла по `i` находится цикл по `j`, и при выполнении некоторого условия мы бы хотели выйти из обоих циклов сразу: -```js -//+ run no-beautify +```js run no-beautify *!*outer:*/!* for (var i = 0; i < 3; i++) { for (var j = 0; j < 3; j++) { - + var input = prompt('Значение в координатах '+i+','+j, ''); - // если отмена ввода или пустая строка - + // если отмена ввода или пустая строка - // завершить оба цикла if (!input) *!*break outer*/!*; // (*) @@ -308,16 +294,14 @@ alert('Готово!'); Метка имеет вид `"имя:"`, имя должно быть уникальным. Она ставится перед циклом, вот так: -```js -//+ no-beautify +```js no-beautify outer: for (var i = 0; i < 3; i++) { ... } ``` Можно также выносить её на отдельную строку: -```js -//+ no-beautify -outer: +```js no-beautify +outer: for (var i = 0; i < 3; i++) { ... } ``` @@ -325,25 +309,21 @@ for (var i = 0; i < 3; i++) { ... } В примере выше это означает, что будет разорван самый внешний цикл и управление перейдёт на `alert`. -Директива `continue` также может быть использована с меткой, в этом случае управление перепрыгнет на следующую итерацию цикла с меткой. +Директива `continue` также может быть использована с меткой, в этом случае управление перепрыгнет на следующую итерацию цикла с меткой. ## Итого JavaScript поддерживает три вида циклов: -
    -
  • `while` -- проверка условия перед каждым выполнением.
  • -
  • `do..while` -- проверка условия после каждого выполнения.
  • -
  • `for` -- проверка условия перед каждым выполнением, а также дополнительные настройки.
  • -
+ +- `while` -- проверка условия перед каждым выполнением. +- `do..while` -- проверка условия после каждого выполнения. +- `for` -- проверка условия перед каждым выполнением, а также дополнительные настройки. Чтобы организовать бесконечный цикл, используют конструкцию `while(true)`. При этом он, как и любой другой цикл, может быть прерван директивой `break`. Если на данной итерации цикла делать больше ничего не надо, но полностью прекращать цикл не следует -- используют директиву `continue`. -Обе этих директивы поддерживают "метки", которые ставятся перед циклом. Метки -- единственный способ для `break/continue` повлиять на выполнение внешнего цикла. - -Заметим, что метки не позволяют прыгнуть в произвольное место кода, в JavaScript нет такой возможности. - - +Обе этих директивы поддерживают "метки", которые ставятся перед циклом. Метки -- единственный способ для `break/continue` повлиять на выполнение внешнего цикла. +Заметим, что метки не позволяют прыгнуть в произвольное место кода, в JavaScript нет такой возможности. diff --git a/1-js/2-first-steps/16-switch/1-rewrite-switch-if-else/solution.md b/1-js/2-first-steps/16-switch/1-rewrite-switch-if-else/solution.md index 1605333f..0d9081ad 100644 --- a/1-js/2-first-steps/16-switch/1-rewrite-switch-if-else/solution.md +++ b/1-js/2-first-steps/16-switch/1-rewrite-switch-if-else/solution.md @@ -1,14 +1,13 @@ -Если совсем точно следовать условию, то сравнение должно быть строгим `'==='`. +Если совсем точно следовать условию, то сравнение должно быть строгим `'==='`. В реальном случае, скорее всего, подойдёт обычное сравнение `'=='`. -```js -//+ no-beautify +```js no-beautify if(browser == 'IE') { alert('О, да у вас IE!'); } else if (browser == 'Chrome' || browser == 'Firefox' - || browser == 'Safari' + || browser == 'Safari' || browser == 'Opera') { alert('Да, и эти браузеры мы поддерживаем'); } else { diff --git a/1-js/2-first-steps/16-switch/1-rewrite-switch-if-else/task.md b/1-js/2-first-steps/16-switch/1-rewrite-switch-if-else/task.md index 15bf197e..256cec56 100644 --- a/1-js/2-first-steps/16-switch/1-rewrite-switch-if-else/task.md +++ b/1-js/2-first-steps/16-switch/1-rewrite-switch-if-else/task.md @@ -1,6 +1,8 @@ -# Напишите "if", аналогичный "switch" +importance: 5 + +--- -[importance 5] +# Напишите "if", аналогичный "switch" Напишите `if..else`, соответствующий следующему `switch`: diff --git a/1-js/2-first-steps/16-switch/2-rewrite-if-switch/solution.md b/1-js/2-first-steps/16-switch/2-rewrite-if-switch/solution.md index ab738f53..0de1a316 100644 --- a/1-js/2-first-steps/16-switch/2-rewrite-if-switch/solution.md +++ b/1-js/2-first-steps/16-switch/2-rewrite-if-switch/solution.md @@ -1,7 +1,6 @@ Первые две проверки -- обычный `case`, третья разделена на два `case`: -```js -//+ run +```js run var a = +prompt('a?', ''); switch (a) { diff --git a/1-js/2-first-steps/16-switch/2-rewrite-if-switch/task.md b/1-js/2-first-steps/16-switch/2-rewrite-if-switch/task.md index a542c993..53a21d17 100644 --- a/1-js/2-first-steps/16-switch/2-rewrite-if-switch/task.md +++ b/1-js/2-first-steps/16-switch/2-rewrite-if-switch/task.md @@ -1,11 +1,12 @@ -# Переписать if'ы в switch +importance: 4 + +--- -[importance 4] +# Переписать if'ы в switch Перепишите код с использованием одной конструкции `switch`: -```js -//+ run +```js run var a = +prompt('a?', ''); if (a == 0) { diff --git a/1-js/2-first-steps/16-switch/article.md b/1-js/2-first-steps/16-switch/article.md index 9a1f8fff..21c9d99c 100644 --- a/1-js/2-first-steps/16-switch/article.md +++ b/1-js/2-first-steps/16-switch/article.md @@ -3,13 +3,14 @@ Конструкция `switch` заменяет собой сразу несколько `if`. Она представляет собой более наглядный способ сравнить выражение сразу с несколькими вариантами. + [cut] + ## Синтаксис Выглядит она так: -```js -//+ no-beautify +```js no-beautify switch(x) { case 'value1': // if (x === 'value1') ... @@ -25,26 +26,17 @@ switch(x) { } ``` -
    -
  • -Переменная `x` проверяется на строгое равенство первому значению `value1`, затем второму `value2` и так далее. -
  • -
  • -Если соответствие установлено -- switch начинает выполняться от соответствующей директивы `case` и далее, *до ближайшего `break`* (или до конца `switch`). -
  • -
  • -Если ни один `case` не совпал -- выполняетcя (если есть) вариант `default`. -
  • -
+- Переменная `x` проверяется на строгое равенство первому значению `value1`, затем второму `value2` и так далее. +- Если соответствие установлено -- switch начинает выполняться от соответствующей директивы `case` и далее, *до ближайшего `break`* (или до конца `switch`). +- Если ни один `case` не совпал -- выполняется (если есть) вариант `default`. -При этом `case` называют *вариантами `switch`*. +При этом `case` называют *вариантами `switch`*. -## Пример работы +## Пример работы Пример использования `switch` (сработавший код выделен): -```js -//+ run +```js run var a = 2 + 2; switch (a) { @@ -72,8 +64,7 @@ switch (a) { Пример без `break`: -```js -//+ run +```js run var a = 2 + 2; switch (a) { @@ -102,8 +93,7 @@ alert( 'Я таких значений не знаю' ); Например: -```js -//+ run +```js run var a = 1; var b = 0; @@ -125,8 +115,7 @@ switch (a) { В примере ниже `case 3` и `case 5` выполняют один и тот же код: -```js -//+ run no-beautify +```js run no-beautify var a = 2+2; switch (a) { @@ -149,12 +138,11 @@ switch (a) { При `case 3` выполнение идёт со строки `(*)`, при `case 5` -- со строки `(**)`. -## Тип имеет значение +## Тип имеет значение Следующий пример принимает значение от посетителя. -```js -//+ run +```js run var arg = prompt("Введите arg?") switch (arg) { case '0': @@ -173,14 +161,12 @@ switch (arg) { } ``` -Что оно выведет при вводе числа 0? Числа 1? 2? 3? +Что оно выведет при вводе числа 0? Числа 1? 2? 3? Подумайте, выпишите свои ответы, исходя из текущего понимания работы `switch` и *потом* читайте дальше... -
    -
  • При вводе `0` выполнится первый `alert`, далее выполнение продолжится вниз до первого `break` и выведет второй `alert('Два')`. Итого, два вывода `alert`.
  • -
  • При вводе `1` произойдёт то же самое.
  • -
  • При вводе `2`, `switch` перейдет к `case '2'`, и сработает единственный `alert('Два')`.
  • -
  • **При вводе `3`, `switch` перейдет на `default`.** Это потому, что `prompt` возвращает строку `'3'`, а не число. Типы разные. Оператор `switch` предполагает строгое равенство `===`, так что совпадения не будет.
  • -
+- При вводе `0` выполнится первый `alert`, далее выполнение продолжится вниз до первого `break` и выведет второй `alert('Два')`. Итого, два вывода `alert`. +- При вводе `1` произойдёт то же самое. +- При вводе `2`, `switch` перейдет к `case '2'`, и сработает единственный `alert('Два')`. +- **При вводе `3`, `switch` перейдет на `default`.** Это потому, что `prompt` возвращает строку `'3'`, а не число. Типы разные. Оператор `switch` предполагает строгое равенство `===`, так что совпадения не будет. diff --git a/1-js/2-first-steps/17-function-basics/1-if-else-required/task.md b/1-js/2-first-steps/17-function-basics/1-if-else-required/task.md index f97c3b92..ffeab7c2 100644 --- a/1-js/2-first-steps/17-function-basics/1-if-else-required/task.md +++ b/1-js/2-first-steps/17-function-basics/1-if-else-required/task.md @@ -1,6 +1,8 @@ -# Обязателен ли "else"? +importance: 4 + +--- -[importance 4] +# Обязателен ли "else"? Следующая функция возвращает `true`, если параметр `age` больше `18`. В ином случае она задаёт вопрос посредством вызова `confirm` и возвращает его результат. diff --git a/1-js/2-first-steps/17-function-basics/2-rewrite-function-question-or/task.md b/1-js/2-first-steps/17-function-basics/2-rewrite-function-question-or/task.md index 5d30718a..0eccd64e 100644 --- a/1-js/2-first-steps/17-function-basics/2-rewrite-function-question-or/task.md +++ b/1-js/2-first-steps/17-function-basics/2-rewrite-function-question-or/task.md @@ -1,6 +1,8 @@ -# Перепишите функцию, используя оператор '?' или '||' +importance: 4 + +--- -[importance 4] +# Перепишите функцию, используя оператор '?' или '||' Следующая функция возвращает `true`, если параметр `age` больше `18`. В ином случае она задаёт вопрос `confirm` и возвращает его результат. @@ -15,9 +17,9 @@ function checkAge(age) { } ``` -Перепишите функцию, чтобы она делала то же самое, но без `if`, в одну строку. +Перепишите функцию, чтобы она делала то же самое, но без `if`, в одну строку. Сделайте два варианта функции `checkAge`: -
    -
  1. Используя оператор `'?'`
  2. -
  3. Используя оператор `||`
  4. -
+ +1. Используя оператор `'?'` +2. Используя оператор `||` + diff --git a/1-js/2-first-steps/17-function-basics/3-min/task.md b/1-js/2-first-steps/17-function-basics/3-min/task.md index 8d042382..09ea866c 100644 --- a/1-js/2-first-steps/17-function-basics/3-min/task.md +++ b/1-js/2-first-steps/17-function-basics/3-min/task.md @@ -1,6 +1,8 @@ -# Функция min +importance: 1 + +--- -[importance 1] +# Функция min Задача "Hello World" для функций :) diff --git a/1-js/2-first-steps/17-function-basics/4-pow/solution.md b/1-js/2-first-steps/17-function-basics/4-pow/solution.md index 30ec3ce4..91be32b8 100644 --- a/1-js/2-first-steps/17-function-basics/4-pow/solution.md +++ b/1-js/2-first-steps/17-function-basics/4-pow/solution.md @@ -1,7 +1,6 @@ -```js -//+ run demo +```js run demo /** * Возводит x в степень n (комментарий JSDoc) * diff --git a/1-js/2-first-steps/17-function-basics/4-pow/task.md b/1-js/2-first-steps/17-function-basics/4-pow/task.md index 0160bfcf..0714b1ad 100644 --- a/1-js/2-first-steps/17-function-basics/4-pow/task.md +++ b/1-js/2-first-steps/17-function-basics/4-pow/task.md @@ -1,6 +1,8 @@ -# Функция pow(x,n) +importance: 4 + +--- -[importance 4] +# Функция pow(x,n) Напишите функцию `pow(x,n)`, которая возвращает `x` в степени `n`. Иначе говоря, умножает `x` на себя `n` раз и возвращает результат. @@ -12,6 +14,6 @@ pow(1, 100) = 1 * 1 * ...*1 = 1 Создайте страницу, которая запрашивает `x` и `n`, а затем выводит результат `pow(x,n)`. -[demo /] +[demo] P.S. В этой задаче функция обязана поддерживать только натуральные значения `n`, т.е. целые от `1` и выше. \ No newline at end of file diff --git a/1-js/2-first-steps/17-function-basics/article.md b/1-js/2-first-steps/17-function-basics/article.md index a9fa6f16..e1825e7d 100644 --- a/1-js/2-first-steps/17-function-basics/article.md +++ b/1-js/2-first-steps/17-function-basics/article.md @@ -1,11 +1,13 @@ # Функции -Зачастую нам надо повторять одно и то же действие во многих частях программы. +Зачастую нам надо повторять одно и то же действие во многих частях программы. Например, красиво вывести сообщение необходимо при приветствии посетителя, при выходе посетителя с сайта, ещё где-нибудь. Чтобы не повторять один и тот же код во многих местах, придуманы функции. Функции являются основными "строительными блоками" программы. + [cut] + Примеры встроенных функций вы уже видели -- это `alert(message)`, `prompt(message, default)` и `confirm(question)`. Но можно создавать и свои. ## Объявление @@ -18,12 +20,11 @@ function showMessage() { } ``` -Вначале идет ключевое слово `function`, после него *имя функции*, затем *список параметров* в скобках (в примере выше он пустой) и *тело функции* -- код, который выполняется при её вызове. +Вначале идет ключевое слово `function`, после него *имя функции*, затем *список параметров* в скобках (в примере выше он пустой) и *тело функции* -- код, который выполняется при её вызове. Объявленная функция доступна по имени, например: -```js -//+ run +```js run function showMessage() { alert( 'Привет всем присутствующим!' ); } @@ -34,16 +35,15 @@ showMessage(); */!* ``` -Этот код выведет сообщение два раза. Уже здесь видна **главная цель создания функций: избавление от дублирования кода**. +Этот код выведет сообщение два раза. Уже здесь видна **главная цель создания функций: избавление от дублирования кода**. -Если понадобится поменять сообщение или способ его вывода -- достаточно изменить его в одном месте: в функции, которая его выводит. +Если понадобится поменять сообщение или способ его вывода -- достаточно изменить его в одном месте: в функции, которая его выводит. -## Локальные переменные +## Локальные переменные Функция может содержать *локальные* переменные, объявленные через `var`. Такие переменные видны только внутри функции: -```js -//+ run +```js run function showMessage() { *!* var message = 'Привет, я - Вася!'; // локальная переменная @@ -63,8 +63,7 @@ alert( message ); // <-- будет ошибка, т.к. переменная в Например: -```js -//+ no-beautify +```js no-beautify function count() { // переменные i,j не будут уничтожены по окончании цикла for (*!*var*/!* i = 0; i < 3; i++) { @@ -96,12 +95,11 @@ function count() { } ``` -## Внешние переменные +## Внешние переменные Функция может обратиться ко внешней переменной, например: -```js -//+ run no-beautify +```js run no-beautify var *!*userName*/!* = 'Вася'; function showMessage() { @@ -114,8 +112,7 @@ showMessage(); // Привет, я Вася Доступ возможен не только на чтение, но и на запись. При этом, так как переменная внешняя, то изменения будут видны и снаружи функции: -```js -//+ run +```js run var userName = 'Вася'; function showMessage() { @@ -140,15 +137,12 @@ alert( userName ); // Петя, значение внешней переменн Делайте глобальными только те переменные, которые действительно имеют общее значение для вашего проекта, а нужные для решения конкретной задачи -- пусть будут локальными в соответствующей функции. - -[warn header="Внимание: неявное объявление глобальных переменных!"] - +````warn header="Внимание: неявное объявление глобальных переменных!" В старом стандарте JavaScript существовала возможность неявного объявления переменных присвоением значения. Например: -```js -//+ run +```js run function showMessage() { message = 'Привет'; // без var! } @@ -167,20 +161,19 @@ alert( message ); // Привет Здесь опасность даже не в автоматическом создании переменной, а в том, что глобальные переменные должны использоваться тогда, когда действительно нужны "общескриптовые" параметры. Забыли `var` в одном месте, потом в другом -- в результате одна функция неожиданно поменяла глобальную переменную, которую использует другая. И поди разберись, кто и когда её поменял, не самая приятная ошибка для отладки. -[/warn] +```` -В будущем, когда мы лучше познакомимся с основами JavaScript, в главе [](/closures), мы более детально рассмотрим внутренние механизмы работы переменных и функций. +В будущем, когда мы лучше познакомимся с основами JavaScript, в главе , мы более детально рассмотрим внутренние механизмы работы переменных и функций. -## Параметры +## Параметры При вызове функции ей можно передать данные, которые та использует по своему усмотрению. Например, этот код выводит два сообщения: -```js -//+ run no-beautify +```js run no-beautify function showMessage(*!*from, text*/!*) { // параметры from, text - + from = "** " + from + " **"; // здесь может быть сложный код оформления alert(from + ': ' + text); @@ -196,11 +189,10 @@ showMessage('Маша', 'Как дела?'); Например, в коде ниже есть внешняя переменная `from`, значение которой при запуске функции копируется в параметр функции с тем же именем. Далее функция работает уже с параметром: -```js -//+ run +```js run function showMessage(from, text) { *!* - from = '**' + from + '**'; // меняем локальную переменную from + from = '**' + from + '**'; // меняем локальную переменную from */!* alert( from + ': ' + text ); } @@ -226,8 +218,7 @@ showMessage("Маша"); При этом можно проверить, и если параметр не передан -- присвоить ему значение "по умолчанию": -```js -//+ run +```js run function showMessage(from, text) { *!* if (text === undefined) { @@ -248,33 +239,28 @@ showMessage("Маша"); // Маша: текст не передан Для указания значения "по умолчанию", то есть, такого, которое используется, если аргумент не указан, используется два способа: -
    -
  1. Можно проверить, равен ли аргумент `undefined`, и если да -- то записать в него значение по умолчанию. Этот способ продемонстрирован в примере выше.
  2. -
  3. Использовать оператор `||`: +1. Можно проверить, равен ли аргумент `undefined`, и если да -- то записать в него значение по умолчанию. Этот способ продемонстрирован в примере выше. +2. Использовать оператор `||`: -```js -//+ run -function showMessage(from, text) { - text = text || 'текст не передан'; + ```js run + function showMessage(from, text) { + text = text || 'текст не передан'; - ... -} -``` + ... + } + ``` -Второй способ считает, что аргумент отсутствует, если передана пустая строка, `0`, или вообще любое значение, которое в логическом контексте является `false`. -
  4. -
+ Второй способ считает, что аргумент отсутствует, если передана пустая строка, `0`, или вообще любое значение, которое в логическом контексте является `false`. -Если аргументов передано больше, чем надо, например `showMessage("Маша", "привет", 1, 2, 3)`, то ошибки не будет. Но, чтобы получить такие "лишние" аргументы, нужно будет прочитать их из специального объекта `arguments`, который мы рассмотрим в главе [](/arguments-pseudoarray). +Если аргументов передано больше, чем надо, например `showMessage("Маша", "привет", 1, 2, 3)`, то ошибки не будет. Но, чтобы получить такие "лишние" аргументы, нужно будет прочитать их из специального объекта `arguments`, который мы рассмотрим в главе . -## Возврат значения +## Возврат значения Функция может возвратить результат, который будет передан в вызвавший её код. Например, создадим функцию `calcD`, которая будет возвращать дискриминант квадратного уравнения по формуле b2 - 4ac: -```js -//+ run no-beautify +```js run no-beautify function calcD(a, b, c) { *!*return*/!* b*b - 4*a*c; } @@ -283,14 +269,13 @@ var test = calcD(-4, 2, 1); alert(test); // 20 ``` -**Для возврата значения используется директива `return`.** +**Для возврата значения используется директива `return`.** Она может находиться в любом месте функции. Как только до неё доходит управление -- функция завершается и значение передается обратно. Вызовов `return` может быть и несколько, например: -```js -//+ run +```js run function checkAge(age) { if (age > 18) { return true; @@ -320,18 +305,17 @@ function showMovie(age) { */!* } - alert( "Фильм не для всех" ); // (*) + alert( "Фильм не для всех" ); // (*) // ... } ``` -В коде выше, если сработал `if`, то строка `(*)` и весь код под ней никогда не выполнится, так как `return` завершает выполнение функции. +В коде выше, если сработал `if`, то строка `(*)` и весь код под ней никогда не выполнится, так как `return` завершает выполнение функции. -[smart header="Значение функции без `return` и с пустым `return`"] +````smart header="Значение функции без `return` и с пустым `return`" В случае, когда функция не вернула значение или `return` был без аргументов, считается что она вернула `undefined`: -```js -//+ run +```js run function doNothing() { /* пусто */ } alert( doNothing() ); // undefined @@ -341,16 +325,14 @@ alert( doNothing() ); // undefined Ещё пример, на этот раз с `return` без аргумента: -```js -//+ run +```js run function doNothing() { return; } alert( doNothing() === undefined ); // true ``` - -[/smart] +```` ## Выбор имени функции [#function-naming] @@ -360,40 +342,36 @@ alert( doNothing() === undefined ); // true Функции, которые начинаются с `"show"` -- что-то показывают: -```js -//+ no-beautify +```js no-beautify showMessage(..) // префикс show, "показать" сообщение ``` Функции, начинающиеся с `"get"` -- получают, и т.п.: -```js -//+ no-beautify +```js no-beautify getAge(..) // get, "получает" возраст calcD(..) // calc, "вычисляет" дискриминант createForm(..) // create, "создает" форму checkPermission(..) // check, "проверяет" разрешение, возвращает true/false ``` -Это очень удобно, поскольку взглянув на функцию -- мы уже примерно представляем, что она делает, даже если функцию написал совсем другой человек, а в отдельных случаях -- и какого вида значение она возвращает. +Это очень удобно, поскольку взглянув на функцию -- мы уже примерно представляем, что она делает, даже если функцию написал совсем другой человек, а в отдельных случаях -- и какого вида значение она возвращает. -[smart header="Одна функция -- одно действие"] - -Функция должна делать только то, что явно подразумевается её названием. И это должно быть одно действие. +```smart header="Одна функция -- одно действие" +Функция должна делать только то, что явно подразумевается её названием. И это должно быть одно действие. Если оно сложное и подразумевает поддействия -- может быть имеет смысл выделить их в отдельные функции? Зачастую это имеет смысл, чтобы лучше структурировать код. **...Но самое главное -- в функции не должно быть ничего, кроме самого действия и поддействий, неразрывно связанных с ним.** Например, функция проверки данных (скажем, `"validate"`) не должна показывать сообщение об ошибке. Её действие -- проверить. -[/smart] - +``` -[smart header="Сверхкороткие имена функций"] -Имена функций, которые используются *очень часто*, иногда делают сверхкороткими. +```smart header="Сверхкороткие имена функций" +Имена функций, которые используются *очень часто*, иногда делают сверхкороткими. -Например, во фреймворке [jQuery](http://jquery.com) есть функция `$`, во фреймворке [Prototype](http://prototypejs.com) -- функция `$$`, а в библиотеке [LoDash](http://lodash.com/) очень активно используется функция с названием из одного символа подчеркивания `_`. -[/smart] +Например, во фреймворке [jQuery](http://jquery.com) есть функция `$`, во фреймворке [Prototype](http://prototypejs.org/) -- функция `$$`, а в библиотеке [LoDash](http://lodash.com/) очень активно используется функция с названием из одного символа подчеркивания `_`. +``` ## Итого @@ -405,31 +383,22 @@ function имя(параметры, через, запятую) { } ``` -
    -
  • Передаваемые значения копируются в параметры функции и становятся локальными переменными.
  • -
  • Параметры функции копируются в её локальные переменные.
  • -
  • Можно объявить новые локальные переменые при помощи `var`.
  • -
  • Значение возвращается оператором `return ...`.
  • -
  • Вызов `return` тут же прекращает функцию.
  • -
  • Если `return;` вызван без значения, или функция завершилась без `return`, то её результат равен `undefined`.
  • -
+- Передаваемые значения копируются в параметры функции и становятся локальными переменными. +- Параметры функции копируются в её локальные переменные. +- Можно объявить новые локальные переменные при помощи `var`. +- Значение возвращается оператором `return ...`. +- Вызов `return` тут же прекращает функцию. +- Если `return;` вызван без значения, или функция завершилась без `return`, то её результат равен `undefined`. При обращении к необъявленной переменной функция будет искать внешнюю переменную с таким именем, но лучше, если функция использует только локальные переменные: -
    -
  • Это делает очевидным общий поток выполнения -- что передаётся в функцию и какой получаем результат.
  • -
  • Это предотвращает возможные конфликты доступа, когда две функции, возможно написанные в разное время или разными людьми, неожиданно друг для друга меняют одну и ту же внешнюю переменную.
  • -
- - +- Это делает очевидным общий поток выполнения -- что передаётся в функцию и какой получаем результат. +- Это предотвращает возможные конфликты доступа, когда две функции, возможно написанные в разное время или разными людьми, неожиданно друг для друга меняют одну и ту же внешнюю переменную. Именование функций: -
    -
  • Имя функции должно понятно и чётко отражать, что она делает. Увидев её вызов в коде, вы должны тут же понимать, что она делает.
  • -
  • Функция -- это действие, поэтому для имён функций, как правило, используются глаголы.
  • -
+- Имя функции должно понятно и чётко отражать, что она делает. Увидев её вызов в коде, вы должны тут же понимать, что она делает. +- Функция -- это действие, поэтому для имён функций, как правило, используются глаголы. Функции являются основными строительными блоками скриптов. Мы будем неоднократно возвращаться к ним и изучать все более и более глубоко. - diff --git a/1-js/2-first-steps/18-function-declaration-expression/article.md b/1-js/2-first-steps/18-function-declaration-expression/article.md index 6449b93a..4ccdcb9a 100644 --- a/1-js/2-first-steps/18-function-declaration-expression/article.md +++ b/1-js/2-first-steps/18-function-declaration-expression/article.md @@ -1,11 +1,10 @@ # Функциональные выражения -В JavaScript функция является значением, таким же как строка или число. +В JavaScript функция является значением, таким же как строка или число. Как и любое значение, объявленную функцию можно вывести, вот так: -```js -//+ run +```js run function sayHi() { alert( "Привет" ); } @@ -19,10 +18,9 @@ alert( sayHi ); // выведет код функции **Функцию можно скопировать в другую переменную:** -```js -//+ run no-beautify +```js run no-beautify function sayHi() { // (1) - alert( "Привет" ); + alert( "Привет" ); } var func = sayHi; // (2) @@ -32,26 +30,22 @@ sayHi = null; sayHi(); // ошибка (4) ``` -
    -
  1. Объявление `(1)` как бы говорит интерпретатору "создай функцию и помести её в переменную `sayHi`
  2. -
  3. В строке `(2)` мы копируем функцию в новую переменную `func`. Ещё раз обратите внимание: после `sayHi` нет скобок. Если бы они были, то вызов `var func = sayHi()` записал бы в `func` *результат* работы `sayHi()` (кстати, чему он равен? правильно, `undefined`, ведь внутри `sayHi` нет `return`).
  4. -
  5. На момент `(3)` функцию можно вызывать и как `sayHi()` и как `func()`
  6. -
  7. ...Однако, в любой момент значение переменной можно поменять. При этом, если оно не функция, то вызов `(4)` выдаст ошибку.
  8. -
+1. Объявление `(1)` как бы говорит интерпретатору "создай функцию и помести её в переменную `sayHi` +2. В строке `(2)` мы копируем функцию в новую переменную `func`. Ещё раз обратите внимание: после `sayHi` нет скобок. Если бы они были, то вызов `var func = sayHi()` записал бы в `func` *результат* работы `sayHi()` (кстати, чему он равен? правильно, `undefined`, ведь внутри `sayHi` нет `return`). +3. На момент `(3)` функцию можно вызывать и как `sayHi()` и как `func()` +4. ...Однако, в любой момент значение переменной можно поменять. При этом, если оно не функция, то вызов `(4)` выдаст ошибку. -Обычные значения, такие как числа или строки, представляют собой *данные*. А функцию можно воспринимать как *действие*. +Обычные значения, такие как числа или строки, представляют собой *данные*. А функцию можно воспринимать как *действие*. Это действие можно запустить через скобки `()`, а можно и скопировать в другую переменную, как было продемонстрировано выше. - ## Объявление Function Expression [#function-expression] Существует альтернативный синтаксис для объявления функции, который ещё более наглядно показывает, что функция -- это всего лишь разновидность значения переменной. Он называется "Function Expression" (функциональное выражение) и выглядит так: -```js -//+ run +```js run var f = function(параметры) { // тело функции }; @@ -59,8 +53,7 @@ var f = function(параметры) { Например: -```js -//+ run +```js run var sayHi = function(person) { alert( "Привет, " + person ); }; @@ -70,12 +63,10 @@ sayHi('Вася'); ## Сравнение с Function Declaration -"Классическое" объявление функции, о котором мы говорили до этого, вида `function имя(параметры) {...}`, называется в спецификации языка "Function Declaration". +"Классическое" объявление функции, о котором мы говорили до этого, вида `function имя(параметры) {...}`, называется в спецификации языка "Function Declaration". -
    -
  • *Function Declaration* -- функция, объявленная в основном потоке кода.
  • -
  • *Function Expression* -- объявление функции в контексте какого-либо выражения, например присваивания.
  • -
+- *Function Declaration* -- функция, объявленная в основном потоке кода. +- *Function Expression* -- объявление функции в контексте какого-либо выражения, например присваивания. Несмотря на немного разный вид, по сути две эти записи делают одно и то же: @@ -97,8 +88,7 @@ var sum = function(a, b) { Поэтому их можно вызвать *до* объявления, например: -```js -//+ run refresh untrusted +```js run refresh untrusted *!* sayHi("Вася"); // Привет, Вася */!* @@ -110,8 +100,7 @@ function sayHi(name) { А если бы это было объявление Function Expression, то такой вызов бы не сработал: -```js -//+ run refresh untrusted +```js run refresh untrusted *!* sayHi("Вася"); // ошибка! */!* @@ -121,9 +110,9 @@ var sayHi = function(name) { } ``` -Это из-за того, что JavaScript перед запуском кода ищет в нём Function Declaration (их легко найти: они не являются частью выражений и начинаются со слова `function`) и обрабатывает их. +Это из-за того, что JavaScript перед запуском кода ищет в нём Function Declaration (их легко найти: они не являются частью выражений и начинаются со слова `function`) и обрабатывает их. -А Function Expression создаются в процессе выполнении выражения, в котором созданы, в данном случае -- функция будет создана при операции присваивания `sayHi = function...` +А Function Expression создаются в процессе выполнения выражения, в котором созданы, в данном случае -- функция будет создана при операции присваивания `sayHi = function...` Как правило, возможность Function Declaration вызвать функцию до объявления -- это удобно, так как даёт больше свободы в том, как организовать свой код. @@ -135,8 +124,7 @@ var sayHi = function(name) { Например, попробуем, в зависимости от условия, объявить функцию `sayHi` по-разному: -```js -//+ run +```js run var age = +prompt("Сколько вам лет?", 20); if (age >= 18) { @@ -152,49 +140,11 @@ if (age >= 18) { sayHi(); ``` -При вводе `20` в примере выше в любом браузере, кроме Firefox, мы увидим, что условное объявление не работает. Срабатывает `"До 18 нельзя"`, несмотря на то, что `age = 20`. - -В чём дело? Чтобы ответить на этот вопрос -- вспомним, как работают функции. - -
    -
  1. Function Declaration обрабатываются перед запуском кода. Интерпретатор сканирует код и создает из таких объявлений функции. При этом второе объявление перезапишет первое. -
  2. -
  3. Дальше, во время выполнения, объявления Function Declaration игнорируются (они уже были обработаны). Это как если бы код был таким: - -```js -function sayHi() { - alert( 'Прошу вас!' ); -} - -function sayHi() { - alert( 'До 18 нельзя' ); -} - -var age = 20; - -if (age >= 18) { - /* объявление было обработано ранее */ -} else { - /* объявление было обработано ранее */ -} - -*!* -sayHi(); // "До 18 нельзя", сработает всегда вторая функция -*/!* -``` - -...То есть, от `if` здесь уже ничего не зависит. По-разному объявить функцию, в зависимости от условия, не получилось. -
  4. -
- -Такое поведение соответствует современному стандарту. На момент написания этого раздела ему следуют все браузеры, кроме, как ни странно, Firefox. - -**Вывод: для условного объявления функций Function Declaration не годится.** +Function Declaration при `use strict` видны только внутри блока, в котором объявлены. Так как код в учебнике выполняется в режиме `use strict`, то будет ошибка. А что, если использовать Function Expression? -```js -//+ run +```js run var age = prompt('Сколько вам лет?'); var sayHi; @@ -214,12 +164,11 @@ sayHi(); Или даже так: -```js -//+ run no-beautify +```js run no-beautify var age = prompt('Сколько вам лет?'); var sayHi = (age >= 18) ? - function() { alert('Прошу Вас!'); } : + function() { alert('Прошу Вас!'); } : function() { alert('До 18 нельзя'); }; sayHi(); @@ -229,14 +178,20 @@ sayHi(); ### Анонимные функции -Взглянем ещё на один пример. +Взглянем ещё на один пример -- функцию `ask(question, yes, no)` с тремя параметрами: -Функция `ask(question, yes, no)` предназначена для выбора действия в зависимости от результата `f`. +`question` +: Строка-вопрос -Она выводит вопрос на подтверждение `question` и, в зависимости от согласия пользователя, вызывает `yes` или `no`: +`yes` +: Функция -```js -//+ run +`no` +: Функция + +Она выводит вопрос на подтверждение `question` и, в зависимости от согласия пользователя, вызывает функцию `yes()` или `no()`: + +```js run *!* function ask(question, yes, no) { if (confirm(question)) yes() @@ -256,14 +211,13 @@ function showCancel() { ask("Вы согласны?", showOk, showCancel); ``` -Какой-то очень простой код, не правда ли? Зачем, вообще, может понадобиться такая `ask`? +Какой-то очень простой код, не правда ли? Зачем, вообще, может понадобиться такая `ask`? -...Но при работе со страницей такие функции как раз очень востребованы, только вот спрашивают они не простым `confirm`, а выводят более красивое окно с вопросом и могут интеллектуально обработать ввод посетителя. Но это всё в своё время. +...Оказывается, при работе со страницей такие функции как раз очень востребованы, только вот спрашивают они не простым `confirm`, а выводят более красивое окно с вопросом и могут интеллектуально обработать ввод посетителя. Но это всё потом, когда перейдём к работе с интерфейсом. -Здесь обратим внимание на то, что то же самое можно написать более коротко: +Здесь же обратим внимание на то, что то же самое можно написать более коротко: -```js -//+ run no-beautify +```js run no-beautify function ask(question, yes, no) { if (confirm(question)) yes() else no(); @@ -271,14 +225,14 @@ function ask(question, yes, no) { *!* ask( - "Вы согласны?", + "Вы согласны?", function() { alert("Вы согласились."); }, function() { alert("Вы отменили выполнение."); } ); */!* ``` -Здесь функции объявлены прямо внутри вызова `ask(...)`, даже без присвоения им имени. +Здесь функции объявлены прямо внутри вызова `ask(...)`, даже без присвоения им имени. **Функциональное выражение, которое не записывается в переменную, называют [анонимной функцией](http://ru.wikipedia.org/wiki/%D0%90%D0%BD%D0%BE%D0%BD%D0%B8%D0%BC%D0%BD%D0%B0%D1%8F_%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D1%8F).** @@ -292,8 +246,7 @@ ask( Он позволяет создавать функцию полностью "на лету" из строки, вот так: -```js -//+ run +```js run var sum = new Function('a,b', ' return a+b; '); var result = sum(1, 2); @@ -301,14 +254,14 @@ alert( result ); // 3 ``` То есть, функция создаётся вызовом `new Function(params, code)`: -
-
`params`
-
Параметры функции через запятую в виде строки.
-
`code`
-
Код функции в виде строки.
-
-Таким образом можно конструировать функцию, код которой неизвестен на момент написания программы, но строка с ним генерируется или подгружается динамически во время её выполнения. +`params` +: Параметры функции через запятую в виде строки. + +`code` +: Код функции в виде строки. + +Таким образом можно конструировать функцию, код которой неизвестен на момент написания программы, но строка с ним генерируется или подгружается динамически во время её выполнения. Пример использования -- динамическая компиляция шаблонов на JavaScript, мы встретимся с ней позже, при работе с интерфейсами. @@ -316,10 +269,8 @@ alert( result ); // 3 Функции в JavaScript являются значениями. Их можно присваивать, передавать, создавать в любом месте кода. -
    -
  • Если функция объявлена в *основном потоке кода*, то это Function Declaration.
  • -
  • Если функция создана как *часть выражения*, то это Function Expression.
  • -
+- Если функция объявлена в *основном потоке кода*, то это Function Declaration. +- Если функция создана как *часть выражения*, то это Function Expression. Между этими двумя основными способами создания функций есть следующие различия: @@ -336,13 +287,13 @@ alert( result ); // 3 Можно вызвать до объявления -`Да` (т.к. создаётся заранее) -`Нет` +Да (т.к. создаётся заранее) +Нет -Условное объявление в `if` -`Не работает` -`Работает` +Условное объявление в if +Не работает +Работает @@ -352,15 +303,14 @@ alert( result ); // 3 Сравните по читаемости: -```js -//+ no-beautify -// Function Expression +```js no-beautify +// Function Expression var f = function() { ... } -// Function Declaration +// Function Declaration function f() { ... } ``` Function Declaration короче и лучше читается. Дополнительный бонус -- такие функции можно вызывать до того, как они объявлены. -Используйте Function Expression только там, где это действительно нужно и удобно. +Используйте Function Expression только там, где это действительно нужно и удобно. diff --git a/1-js/2-first-steps/19-recursion/1-sum-to/solution.md b/1-js/2-first-steps/19-recursion/1-sum-to/solution.md index 791c4b3d..0d9e1e39 100644 --- a/1-js/2-first-steps/19-recursion/1-sum-to/solution.md +++ b/1-js/2-first-steps/19-recursion/1-sum-to/solution.md @@ -1,7 +1,6 @@ Решение **с использованием цикла**: -```js -//+ run +```js run function sumTo(n) { var sum = 0; for (var i = 1; i <= n; i++) { @@ -15,8 +14,7 @@ alert( sumTo(100) ); Решение через **рекурсию**: -```js -//+ run +```js run function sumTo(n) { if (n == 1) return 1; return n + sumTo(n - 1); @@ -27,8 +25,7 @@ alert( sumTo(100) ); Решение **по формуле**: `sumTo(n) = n*(n+1)/2`: -```js -//+ run +```js run function sumTo(n) { return n * (n + 1) / 2; } diff --git a/1-js/2-first-steps/19-recursion/1-sum-to/task.md b/1-js/2-first-steps/19-recursion/1-sum-to/task.md index 9e7e8e8f..91b9d08c 100644 --- a/1-js/2-first-steps/19-recursion/1-sum-to/task.md +++ b/1-js/2-first-steps/19-recursion/1-sum-to/task.md @@ -1,11 +1,12 @@ -# Вычислить сумму чисел до данного +importance: 5 + +--- -[importance 5] +# Вычислить сумму чисел до данного Напишите функцию `sumTo(n)`, которая для данного `n` вычисляет сумму чисел от 1 до `n`, например: -```js -//+ no-beautify +```js no-beautify sumTo(1) = 1 sumTo(2) = 2 + 1 = 3 sumTo(3) = 3 + 2 + 1 = 6 @@ -15,11 +16,10 @@ sumTo(100) = 100 + 99 + ... + 2 + 1 = 5050 ``` Сделайте три варианта решения: -
    -
  1. С использованием цикла.
  2. -
  3. Через рекурсию, т.к. `sumTo(n) = n + sumTo(n-1)` для `n > 1`.
  4. -
  5. С использованием формулы для суммы [арифметической прогрессии](http://ru.wikipedia.org/wiki/%D0%90%D1%80%D0%B8%D1%84%D0%BC%D0%B5%D1%82%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B0%D1%8F_%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B5%D1%81%D1%81%D0%B8%D1%8F).
  6. -
+ +1. С использованием цикла. +2. Через рекурсию, т.к. `sumTo(n) = n + sumTo(n-1)` для `n > 1`. +3. С использованием формулы для суммы [арифметической прогрессии](http://ru.wikipedia.org/wiki/%D0%90%D1%80%D0%B8%D1%84%D0%BC%D0%B5%D1%82%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B0%D1%8F_%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B5%D1%81%D1%81%D0%B8%D1%8F). Пример работы вашей функции: diff --git a/1-js/2-first-steps/19-recursion/2-factorial/solution.md b/1-js/2-first-steps/19-recursion/2-factorial/solution.md index f3811e2c..1cbd981f 100644 --- a/1-js/2-first-steps/19-recursion/2-factorial/solution.md +++ b/1-js/2-first-steps/19-recursion/2-factorial/solution.md @@ -2,8 +2,7 @@ То есть, результат функции для `n` можно получить как `n`, умноженное на результат функции для `n-1`, и так далее до `1!`: -```js -//+ run +```js run function factorial(n) { return (n != 1) ? n * factorial(n - 1) : 1; } @@ -13,8 +12,7 @@ alert( factorial(5) ); // 120 Базисом рекурсии является значение `1`. А можно было бы сделать базисом и `0`. Тогда код станет чуть короче: -```js -//+ run +```js run function factorial(n) { return n ? n * factorial(n - 1) : 1; } diff --git a/1-js/2-first-steps/19-recursion/2-factorial/task.md b/1-js/2-first-steps/19-recursion/2-factorial/task.md index 59579043..78049ec4 100644 --- a/1-js/2-first-steps/19-recursion/2-factorial/task.md +++ b/1-js/2-first-steps/19-recursion/2-factorial/task.md @@ -1,6 +1,8 @@ -# Вычислить факториал +importance: 4 + +--- -[importance 4] +# Вычислить факториал *Факториа́л числа* -- это число, умноженное на "себя минус один", затем на "себя минус два" и так далее, до единицы. Обозначается `n!` diff --git a/1-js/2-first-steps/19-recursion/3-fibonacci-numbers/solution.md b/1-js/2-first-steps/19-recursion/3-fibonacci-numbers/solution.md index 5c5fbfab..0f20f250 100644 --- a/1-js/2-first-steps/19-recursion/3-fibonacci-numbers/solution.md +++ b/1-js/2-first-steps/19-recursion/3-fibonacci-numbers/solution.md @@ -2,8 +2,7 @@ Решение по формуле, используя рекурсию: -```js -//+ run +```js run function fib(n) { return n <= 1 ? n : fib(n - 1) + fib(n - 2); } @@ -17,8 +16,7 @@ alert( fib(7) ); // 13 Это потому, что функция порождает обширное дерево вложенных вызовов. При этом ряд значений вычисляется много раз. Например, посмотрим на отрывок вычислений: -```js -//+ no-beautify +```js no-beautify ... fib(5) = fib(4) + fib(3) fib(4) = fib(3) + fib(2) @@ -35,8 +33,7 @@ fib(4) = fib(3) + fib(2) Будем идти по формуле слева-направо: -```js -//+ no-beautify +```js no-beautify var a = 1, b = 1; // начальные значения var c = a + b; // 2 @@ -48,9 +45,8 @@ a b c Теперь следующий шаг, присвоим `a` и `b` текущие 2 числа и получим новое следующее в `c`: -```js -//+ no-beautify -a = b, b = c; +```js no-beautify +a = b, b = c; c = a + b; /* стало так (ещё число): @@ -61,9 +57,8 @@ c = a + b; Следующий шаг даст нам ещё одно число последовательности: -```js -//+ no-beautify -a = b, b = c; +```js no-beautify +a = b, b = c; c = a + b; /* стало так (ещё число): @@ -78,8 +73,7 @@ P.S. Этот подход к вычислению называется [дин # Код для вычисления в цикле -```js -//+ run +```js run function fib(n) { var a = 1, b = 1; diff --git a/1-js/2-first-steps/19-recursion/3-fibonacci-numbers/task.md b/1-js/2-first-steps/19-recursion/3-fibonacci-numbers/task.md index b05734a0..4ec36b41 100644 --- a/1-js/2-first-steps/19-recursion/3-fibonacci-numbers/task.md +++ b/1-js/2-first-steps/19-recursion/3-fibonacci-numbers/task.md @@ -1,6 +1,8 @@ -# Числа Фибоначчи +importance: 5 + +--- -[importance 5] +# Числа Фибоначчи Последовательность [чисел Фибоначчи](http://ru.wikipedia.org/wiki/%D0%A7%D0%B8%D1%81%D0%BB%D0%B0_%D0%A4%D0%B8%D0%B1%D0%BE%D0%BD%D0%B0%D1%87%D1%87%D0%B8) имеет формулу Fn = Fn-1 + Fn-2. То есть, следующее число получается как сумма двух предыдущих. @@ -10,8 +12,7 @@ Напишите функцию `fib(n)`, которая возвращает `n-е` число Фибоначчи. Пример работы: -```js -//+ no-beautify +```js no-beautify function fib(n) { /* ваш код */ } alert( fib(3) ); // 2 @@ -21,4 +22,3 @@ alert( fib(77)); // 5527939700884757 **Все запуски функций из примера выше должны срабатывать быстро.** - \ No newline at end of file diff --git a/1-js/2-first-steps/19-recursion/article.md b/1-js/2-first-steps/19-recursion/article.md index 144e1cad..4a768f4d 100644 --- a/1-js/2-first-steps/19-recursion/article.md +++ b/1-js/2-first-steps/19-recursion/article.md @@ -1,6 +1,6 @@ # Рекурсия, стек -В коде функции могут вызывать другие функции для выполнения подзадач. +В теле функции могут быть вызываны другие функции для выполнения подзадач. Частный случай подвызова -- когда функция вызывает сама себя. Это называется *рекурсией*. @@ -14,12 +14,11 @@ [cut] - ## Степень pow(x, n) через рекурсию В качестве первого примера использования рекурсивных вызовов -- рассмотрим задачу возведения числа `x` в натуральную степень `n`. -Её можно представить как совокупность более простого действия и более простой задачи того же типа вот так: +Её можно представить как совокупность более простого действия и более простой задачи того же типа вот так: ```js pow(x, n) = x * pow(x, n - 1) @@ -29,12 +28,10 @@ pow(x, n) = x * pow(x, n - 1) Например, вычислим `pow(2, 4)`, последовательно переходя к более простой задаче: -
    -
  1. `pow(2, 4) = 2 * pow(2, 3)`
  2. -
  3. `pow(2, 3) = 2 * pow(2, 2)`
  4. -
  5. `pow(2, 2) = 2 * pow(2, 1)`
  6. -
  7. `pow(2, 1) = 2`
  8. -
+1. `pow(2, 4) = 2 * pow(2, 3)` +2. `pow(2, 3) = 2 * pow(2, 2)` +3. `pow(2, 2) = 2 * pow(2, 1)` +4. `pow(2, 1) = 2` На шаге 1 нам нужно вычислить `pow(2,3)`, поэтому мы делаем шаг 2, дальше нам нужно `pow(2,2)`, мы делаем шаг 3, затем шаг 4, и на нём уже можно остановиться, ведь очевидно, что результат возведения числа в степень 1 -- равен самому числу. @@ -42,8 +39,7 @@ pow(x, n) = x * pow(x, n - 1) Этот алгоритм на JavaScript: -```js -//+ run +```js run function pow(x, n) { if (n != 1) { // пока n != 1, сводить вычисление pow(x,n) к pow(x,n-1) return x * pow(x, n - 1); @@ -55,11 +51,11 @@ function pow(x, n) { alert( pow(2, 3) ); // 8 ``` -Говорят, что "функция `pow` *рекурсивно вызывает сама себя*" до `n == 1`. +Говорят, что "функция `pow` *рекурсивно вызывает сама себя*" до `n == 1`. -Значение, на котором рекурсия заканчивается называют *базисом рекурсии*. В примере выше базисом является `1`. +Значение, на котором рекурсия заканчивается, называют *базисом рекурсии*. В примере выше базисом является `1`. -Общее количество вложенных вызовов называют *глубиной рекурсии*. В случае со степенью, всего будет `n` вызовов. +Общее количество вложенных вызовов называют *глубиной рекурсии*. В случае со степенью, всего будет `n` вызовов. Максимальная глубина рекурсии в браузерах ограничена, точно можно рассчитывать на `10000` вложенных вызовов, но некоторые интерпретаторы допускают и больше. @@ -69,7 +65,7 @@ alert( pow(2, 3) ); // 8 Теперь мы посмотрим, как работают рекурсивные вызовы. Для этого мы рассмотрим, как вообще работают функции, что происходит при вызове. -**У каждого вызова функции есть свой "контекст выполнения" (execution context).** +**У каждого вызова функции есть свой "контекст выполнения" (execution context).** Контекст выполнения -- это служебная информация, которая соответствует текущему запуску функции. Она включает в себя локальные переменные функции и конкретное место в коде, на котором находится интерпретатор. @@ -105,8 +101,7 @@ function pow(x, n) { Разберём происходящее с контекстами более подробно, начиная с вызова `(*)`: -```js -//+ run +```js run function pow(x, n) { if (n != 1) { // пока n!=1 сводить вычисление pow(..n) к pow(..n-1) return x * pow(x, n - 1); @@ -120,70 +115,68 @@ alert( pow(2, 3) ); // (*) */!* ``` -
-
`pow(2, 3)`
-
Запускается функция `pow`, с аргументами `x=2`, `n=3`. Эти переменные хранятся в контексте выполнения, схематично изображённом ниже: - -
    -
  • Контекст: { x: 2, n: 3, строка 1 }
  • -
-Выполнение в этом контексте продолжается, пока не встретит вложенный вызов в строке 3. -
-
`pow(2, 2)`
-
В строке `3` происходит вложенный вызов `pow` с аргументами `x=2`, `n=2`. Текущий контекст сохраняется в стеке, а для вложеннного вызова создаётся новый контекст (выделен жирным ниже): - -
    -
  • Контекст: { x: 2, n: 3, строка 3 }
  • -
  • Контекст: { x: 2, n: 2, строка 1 }
  • -
-Обратим внимание, что контекст включает в себя не только переменные, но и место в коде, так что когда вложенный вызов завершится -- можно будет легко вернуться назад. - -Слово "строка" здесь условно, на самом деле, конечно, запомнено более точное место в цепочке команд. -
-
`pow(2, 1)`
-
Опять вложенный вызов в строке `3`, на этот раз -- с аргументами `x=2`, `n=1`. Создаётся новый текущий контекст, предыдущий добавляется в стек: -
    -
  • Контекст: { x: 2, n: 3, строка 3 }
  • -
  • Контекст: { x: 2, n: 2, строка 3 }
  • -
  • Контекст: { x: 2, n: 1, строка 1 }
  • -
-На текущий момент в стеке уже два старых контекста. -
-
Выход из `pow(2, 1)`.
-
При выполнении `pow(2, 1)`, в отличие от предыдущих запусков, выражение `n != 1` будет равно `false`, поэтому сработает вторая ветка `if..else`: - -```js -function pow(x, n) { - if (n != 1) { - return x * pow(x, n - 1); - } else { -*!* - return x; // первая степень числа равна самому числу -*/!* - } -} -``` - -Здесь вложенных вызовов нет, так что функция заканчивает свою работу, возвращая `2`. Текущий контекст больше не нужен и удаляется из памяти, из стека восстанавливается предыдущий: - -
    -
  • Контекст: { x: 2, n: 3, строка 3 }
  • -
  • Контекст: { x: 2, n: 2, строка 3 }
  • -
-Возобновляется обработка внешнего вызова `pow(2, 2)`. -
-
Выход из `pow(2, 2)`.
-
...И теперь уже `pow(2, 2)` может закончить свою работу, вернув `4`. Восстанавливается контекст предыдущего вызова: -
    -
  • Контекст: { x: 2, n: 3, строка 3 }
  • -
-Возобновляется обработка внешнего вызова `pow(2, 3)`. -
-
Выход из `pow(2, 3)`.
-
Самый внешний вызов заканчивает свою работу, его результат: `pow(2, 3) = 8`.
-
- -Глубина рекурсии в данном случае составила: **3**. +`pow(2, 3)` +: Запускается функция `pow`, с аргументами `x=2`, `n=3`. Эти переменные хранятся в контексте выполнения, схематично изображённом ниже: + +
    +
  • Контекст: { x: 2, n: 3, строка 1 }
  • +
+ Выполнение в этом контексте продолжается, пока не встретит вложенный вызов в строке 3. + +`pow(2, 2)` +: В строке `3` происходит вложенный вызов `pow` с аргументами `x=2`, `n=2`. Текущий контекст сохраняется в стеке, а для вложеннного вызова создаётся новый контекст (выделен жирным ниже): + +
    +
  • Контекст: { x: 2, n: 3, строка 3 }
  • +
  • Контекст: { x: 2, n: 2, строка 1 }
  • +
+ Обратим внимание, что контекст включает в себя не только переменные, но и место в коде, так что когда вложенный вызов завершится -- можно будет легко вернуться назад. + + Слово "строка" здесь условно, на самом деле, конечно, запомнено более точное место в цепочке команд. + +`pow(2, 1)` +: Опять вложенный вызов в строке `3`, на этот раз -- с аргументами `x=2`, `n=1`. Создаётся новый текущий контекст, предыдущий добавляется в стек: +
    +
  • Контекст: { x: 2, n: 3, строка 3 }
  • +
  • Контекст: { x: 2, n: 2, строка 3 }
  • +
  • Контекст: { x: 2, n: 1, строка 1 }
  • +
+ На текущий момент в стеке уже два старых контекста. + +Выход из `pow(2, 1)`. +: При выполнении `pow(2, 1)`, в отличие от предыдущих запусков, выражение `n != 1` будет равно `false`, поэтому сработает вторая ветка `if..else`: + + ```js + function pow(x, n) { + if (n != 1) { + return x * pow(x, n - 1); + } else { + *!* + return x; // первая степень числа равна самому числу + */!* + } + } + ``` + + Здесь вложенных вызовов нет, так что функция заканчивает свою работу, возвращая `2`. Текущий контекст больше не нужен и удаляется из памяти, из стека восстанавливается предыдущий: + +
    +
  • Контекст: { x: 2, n: 3, строка 3 }
  • +
  • Контекст: { x: 2, n: 2, строка 3 }
  • +
+ Возобновляется обработка внешнего вызова `pow(2, 2)`. + +Выход из `pow(2, 2)`. +: ...И теперь уже `pow(2, 2)` может закончить свою работу, вернув `4`. Восстанавливается контекст предыдущего вызова: +
    +
  • Контекст: { x: 2, n: 3, строка 3 }
  • +
+ Возобновляется обработка внешнего вызова `pow(2, 3)`. + +Выход из `pow(2, 3)`. +: Самый внешний вызов заканчивает свою работу, его результат: `pow(2, 3) = 8`. + + Глубина рекурсии в данном случае составила: **3**. Как видно из иллюстраций выше, глубина рекурсии равна максимальному числу контекстов, одновременно хранимых в стеке. @@ -211,33 +204,9 @@ function pow(x, n) { Рекурсия -- это когда функция вызывает сама себя, как правило, с другими аргументами. -Существуют много областей применения рекурсивных вызовов. Здесь мы посмотрели на один из них -- решение задачи путём сведения её к более простой (с меньшими аргументами), но также рекурсия используется для работы с "естественно рекурсивными" структурами данных, такими как HTML-документы, для "глубокого" копирования сложных объектов. +Существуют много областей применения рекурсивных вызовов. Здесь мы посмотрели на один из них -- решение задачи путём сведения её к более простой (с меньшими аргументами), но также рекурсия используется для работы с "естественно рекурсивными" структурами данных, такими как HTML-документы, для "глубокого" копирования сложных объектов. Есть и другие применения, с которыми мы встретимся по мере изучения JavaScript. -Здесь мы постарались рассмотреть происходящее достаточно подробно, однако, если пожелаете, допустимо временно забежать вперёд и открыть главу [](/debugging-chrome), с тем чтобы при помощи отладчика построчно пробежаться по коду и посмотреть стек на каждом шаге. Отладчик даёт к нему доступ. - - - -[head] - -[/head] diff --git a/1-js/2-first-steps/19-recursion/head.html b/1-js/2-first-steps/19-recursion/head.html new file mode 100644 index 00000000..9acd1ada --- /dev/null +++ b/1-js/2-first-steps/19-recursion/head.html @@ -0,0 +1,20 @@ + \ No newline at end of file diff --git a/1-js/2-first-steps/2-external-script/1-hello-alert-ext/solution.md b/1-js/2-first-steps/2-external-script/1-hello-alert-ext/solution.md index bef2fc5b..d253e0ae 100644 --- a/1-js/2-first-steps/2-external-script/1-hello-alert-ext/solution.md +++ b/1-js/2-first-steps/2-external-script/1-hello-alert-ext/solution.md @@ -1,12 +1,8 @@ Код для HTML-файла: -```html - -``` +[html src="http://github.com/index.html"] Для файла `alert.js` из той же директории: -```js -//+ src="http://github.com/alert.js" -``` +[js src="http://github.com/alert.js"] diff --git a/1-js/2-first-steps/2-external-script/1-hello-alert-ext/task.md b/1-js/2-first-steps/2-external-script/1-hello-alert-ext/task.md index 93cf1b4e..62d6f4e1 100644 --- a/1-js/2-first-steps/2-external-script/1-hello-alert-ext/task.md +++ b/1-js/2-first-steps/2-external-script/1-hello-alert-ext/task.md @@ -1,7 +1,9 @@ -# Вывести alert внешним скриптом +importance: 5 + +--- -[importance 5] +# Вывести alert внешним скриптом -Возьмите решение предыдущей задачи [](/task/hello-alert) и вынесите скрипт во внешний файл `alert.js`, который расположите в той же директории. +Возьмите решение предыдущей задачи и вынесите скрипт во внешний файл `alert.js`, который расположите в той же директории. Откройте страницу и проверьте, что вывод сообщения всё ещё работает. \ No newline at end of file diff --git a/1-js/2-first-steps/2-external-script/2-async-defer-first/solution.md b/1-js/2-first-steps/2-external-script/2-async-defer-first/solution.md index 089a87e0..a6e0ebe6 100644 --- a/1-js/2-first-steps/2-external-script/2-async-defer-first/solution.md +++ b/1-js/2-first-steps/2-external-script/2-async-defer-first/solution.md @@ -1,6 +1,5 @@ Ответы: -
    -
  1. Первым выполнится `big.js`, это нормальная последовательность выполнения подряд идущих скриптов.
  2. -
  3. Первым выполнится `small.js`, так как скрипты из-за `async` ведут себя совершенно независимо друг от друга, страница тоже от них не зависит.
  4. -
  5. Первым выполнится `big.js`, так как скрипты, подключённые через `defer`, сохраняют порядок выполнения относительно друг друга.
  6. -
\ No newline at end of file + +1. Первым выполнится `big.js`, это нормальная последовательность выполнения подряд идущих скриптов. +2. Первым выполнится `small.js`, так как скрипты из-за `async` ведут себя совершенно независимо друг от друга, страница тоже от них не зависит. +3. Первым выполнится `big.js`, так как скрипты, подключённые через `defer`, сохраняют порядок выполнения относительно друг друга. diff --git a/1-js/2-first-steps/2-external-script/2-async-defer-first/task.md b/1-js/2-first-steps/2-external-script/2-async-defer-first/task.md index 72405f9a..a8d3afcf 100644 --- a/1-js/2-first-steps/2-external-script/2-async-defer-first/task.md +++ b/1-js/2-first-steps/2-external-script/2-async-defer-first/task.md @@ -1,6 +1,8 @@ -# Какой скрипт выполнится первым? +importance: 4 + +--- -[importance 4] +# Какой скрипт выполнится первым? В примере ниже подключены два скрипта `small.js` и `big.js`. diff --git a/1-js/2-first-steps/2-external-script/article.md b/1-js/2-first-steps/2-external-script/article.md index 41a9a3b7..ca015f9c 100644 --- a/1-js/2-first-steps/2-external-script/article.md +++ b/1-js/2-first-steps/2-external-script/article.md @@ -6,14 +6,14 @@ ``` -Здесь `/path/to/script.js` -- это абсолютный путь к файлу, содержащему скрипт (из корня сайта). +Здесь `/path/to/script.js` -- это абсолютный путь к файлу, содержащему скрипт (из корня сайта). Браузер сам скачает скрипт и выполнит. Можно указать и полный URL, например: ```html - + ``` Вы также можете использовать путь относительно текущей страницы. Например, `src="http://github.com/lodash.js"` обозначает файл из текущей директории. @@ -26,20 +26,18 @@ ... ``` -[smart] +```smart Как правило, в HTML пишут только самые простые скрипты, а сложные выносят в отдельный файл. Браузер скачает его только первый раз и в дальнейшем, при правильной настройке сервера, будет брать из своего [кеша](http://ru.wikipedia.org/wiki/%D0%9A%D1%8D%D1%88). -Благодаря этому один и тот же большой скрипт, содержащий, к примеру, библиотеку функций, может использоваться на разных страницах без полной перезагрузки с сервера. -[/smart] - - -[warn header="Если указан атрибут `src`, то содержимое тега игнорируется."] +Благодаря этому один и тот же большой скрипт, содержащий, к примеру, библиотеку функций, может использоваться на разных страницах без полной перезагрузки с сервера. +``` -В одном теге `SCRIPT` нельзя одновременно подключить внешний скрипт и указать код. +````warn header="Если указан атрибут `src`, то содержимое тега игнорируется." +В одном теге `SCRIPT` нельзя одновременно подключить внешний скрипт и указать код. -Вот так не cработает: +Вот так не сработает: ```html ``` - -[/warn] +```` ## Асинхронные скрипты: defer/async @@ -66,8 +63,7 @@ Например, в примере ниже -- пока все кролики не будут посчитаны -- нижний `

` не будет показан: -```html - +```html run height=100 @@ -113,18 +109,17 @@ ``` -И здесь вопрос -- действительно ли мы этого хотим? То есть, действительно ли оставшуюся часть страницы нельзя показывать до загрузки скрипта? +И здесь вопрос -- действительно ли мы этого хотим? То есть, действительно ли оставшуюся часть страницы нельзя показывать до загрузки скрипта? Есть ситуации, когда мы не только НЕ хотим такой задержки, но она даже опасна. -Например, если мы подключаем внешний скрипт, который показывает рекламу или вставляет счётчик посещений, а затем идёт наша страница. Конечно, неправильно, что пока счётчик или реклама не подгрузятся -- оставшаяся часть страницы не показывается. Счётчик посещений не должен никак задерживать отображение страницы сайта. Реклама тоже не должна тормозить сайт и нарушать его функционал. +Например, если мы подключаем внешний скрипт, который показывает рекламу или вставляет счётчик посещений, а затем идёт наша страница. Конечно, неправильно, что пока счётчик или реклама не подгрузятся -- оставшаяся часть страницы не показывается. Счётчик посещений не должен никак задерживать отображение страницы сайта. Реклама тоже не должна тормозить сайт и нарушать его функциональность. А что, если сервер, с которого загружается внешний скрипт, перегружен? Посетитель в этом случае может ждать очень долго! Вот пример, с подобным скриптом (стоит искусственная задержка загрузки): -```html - +```html run height=100

Важная информация не покажется, пока не загрузится скрипт.

@@ -141,60 +136,58 @@ Поэтому "расположить скрипты внизу" -- не лучший выход. Кардинально решить эту проблему помогут атрибуты `async` или `defer`: -
-
Атрибут `async`
-
Поддерживается всеми браузерами, кроме IE9-. Скрипт выполняется полностью асинхронно. То есть, при обнаружении ` - -``` + Первое -- браузер гарантирует, что относительный порядок скриптов с `defer` будет сохранён. -А в таком коде (с `defer`) первым сработает всегда `1.js`, а скрипт `2.js`, даже если загрузился раньше, будет его ждать. + То есть, в таком коде (с `async`) первым сработает тот скрипт, который раньше загрузится: -```html - - -``` + ```html + + + ``` -Поэтому атрибут `defer` используют в тех случаях, когда второй скрипт `2.js` зависит от первого `1.js`, к примеру -- использует что-то, описанное первым скриптом. + А в таком коде (с `defer`) первым сработает всегда `1.js`, а скрипт `2.js`, даже если загрузился раньше, будет его ждать. -Второе отличие -- скрипт с `defer` сработает, когда весь HTML-документ будет обработан браузером. + ```html + + + ``` -Например, если документ достаточно большой... -```html - - + Поэтому атрибут `defer` используют в тех случаях, когда второй скрипт `2.js` зависит от первого `1.js`, к примеру -- использует что-то, описанное первым скриптом. -Много много много букв -``` + Второе отличие -- скрипт с `defer` сработает, когда весь HTML-документ будет обработан браузером. + + Например, если документ достаточно большой... + ```html + + + + Много много много букв + ``` -...То скрипт `async.js` выполнится, как только загрузится -- возможно, до того, как весь документ готов. А `defer.js` подождёт готовности всего документа. + ...То скрипт `async.js` выполнится, как только загрузится -- возможно, до того, как весь документ готов. А `defer.js` подождёт готовности всего документа. -Это бывает удобно, когда мы в скрипте хотим работать с документом, и должны быть уверены, что он полностью получен. -
-
+ Это бывает удобно, когда мы в скрипте хотим работать с документом, и должны быть уверены, что он полностью получен. -[smart header="`async` вместе с `defer`"] +```smart header="`async` вместе с `defer`" При одновременном указании `async` и `defer` в современных браузерах будет использован только `async`, в IE9- -- только `defer` (не понимает `async`). -[/smart] +``` -[warn header="Атрибуты `async/defer` -- только для внешних скриптов"] -Атрибуты `async/defer` работают только в том случае, если назначены на внешние скрипты, т.е. имеющие `src`. +```warn header="Атрибуты `async/defer` -- только для внешних скриптов" +Атрибуты `async/defer` работают только в том случае, если назначены на внешние скрипты, т.е. имеющие `src`. -При попытке назначить их на обычные скрипты <script>...</script>, они будут проигнороированы. -[/warn] +При попытке назначить их на обычные скрипты <script>...</script>, они будут проигнорированы. +``` Тот же пример с `async`: -```html - +```html run height=100

Важная информация теперь не ждёт, пока загрузится скрипт...

@@ -202,27 +195,26 @@

...Важная информация!

``` -При запуске вы увидите, что вся страница отобразилась тут же, а `alert` из внешнего скрипта появится позже, когда загрузится скрипт. +При запуске вы увидите, что вся страница отобразилась тут же, а `alert` из внешнего скрипта появится позже, когда загрузится скрипт. -[smart header="Эти атрибуты давно \"в ходу\""] +```smart header="Эти атрибуты давно \"в ходу\"" Большинство современных систем рекламы и счётчиков знают про эти атрибуты и используют их. Перед вставкой внешнего тега `` -
  • Специальные атрибуты `async` и `defer` используются для того, чтобы пока грузится внешний скрипт -- браузер показал остальную (следующую за ним) часть страницы. Без них этого не происходит.
  • -
  • Разница между `async` и `defer`: атрибут `defer` сохраняет относительную последовательность скриптов, а `async` -- нет. Кроме того, `defer` всегда ждёт, пока весь HTML-документ будет готов, а `async` -- нет.
  • - +- Скрипты вставляются на страницу как текст в теге `` +- Специальные атрибуты `async` и `defer` используются для того, чтобы пока грузится внешний скрипт -- браузер показал остальную (следующую за ним) часть страницы. Без них этого не происходит. +- Разница между `async` и `defer`: атрибут `defer` сохраняет относительную последовательность скриптов, а `async` -- нет. Кроме того, `defer` всегда ждёт, пока весь HTML-документ будет готов, а `async` -- нет. Очень важно не только читать учебник, но делать что-то самостоятельно. Решите задачки, чтобы удостовериться, что вы всё правильно поняли. - diff --git a/1-js/2-first-steps/20-named-function-expression/1-nfe-check/solution.md b/1-js/2-first-steps/20-named-function-expression/1-nfe-check/solution.md index 5875fae8..354d81ba 100644 --- a/1-js/2-first-steps/20-named-function-expression/1-nfe-check/solution.md +++ b/1-js/2-first-steps/20-named-function-expression/1-nfe-check/solution.md @@ -1,19 +1,17 @@ **Первый код выведет `function ...`, второй -- ошибку во всех браузерах, кроме IE8-.** -```js -//+ run untrusted no-beautify +```js run untrusted no-beautify // обычное объявление функции (Function Declaration) -function g() { return 1; }; +function g() { return 1; }; alert(g); // функция ``` Во втором коде скобки есть, значит функция внутри является не `Function Declaration`, а частью выражения, то есть `Named Function Expression`. Его имя видно только внутри, снаружи переменная `g` не определена. -```js -//+ run untrusted no-beautify +```js run untrusted no-beautify // Named Function Expression! -(function g() { return 1; }); +(function g() { return 1; }); alert(g); // Ошибка! ``` diff --git a/1-js/2-first-steps/20-named-function-expression/1-nfe-check/task.md b/1-js/2-first-steps/20-named-function-expression/1-nfe-check/task.md index 0510ff4d..8b79d17b 100644 --- a/1-js/2-first-steps/20-named-function-expression/1-nfe-check/task.md +++ b/1-js/2-first-steps/20-named-function-expression/1-nfe-check/task.md @@ -1,11 +1,12 @@ -# Проверка на NFE +importance: 5 + +--- -[importance 5] +# Проверка на NFE Каков будет результат выполнения кода? -```js -//+ no-beautify +```js no-beautify function g() { return 1; } alert(g); @@ -13,8 +14,7 @@ alert(g); А такого? Будет ли разница, если да -- почему? -```js -//+ no-beautify +```js no-beautify (function g() { return 1; }); alert(g); diff --git a/1-js/2-first-steps/20-named-function-expression/article.md b/1-js/2-first-steps/20-named-function-expression/article.md index c51478a3..43a01b4c 100644 --- a/1-js/2-first-steps/20-named-function-expression/article.md +++ b/1-js/2-first-steps/20-named-function-expression/article.md @@ -4,7 +4,6 @@ [cut] - ## Named Function Expression [#functions-nfe] Обычное функциональное выражение: @@ -14,8 +13,7 @@ var f = function(...) { /* тело функции */ }; Именованное с именем `sayHi`: -```js -//+ no-beautify +```js no-beautify var f = function *!*sayHi*/!*(...) { /* тело функции */ }; ``` @@ -27,10 +25,9 @@ var f = function *!*sayHi*/!*(...) { /* тело функции */ }; Например: -```js -//+ run +```js run var f = function sayHi(name) { - alert( sayHi ); // изнутри функции - видно (выведет код функции) + alert( sayHi ); // изнутри функции - видно (выведет код функции) }; alert( sayHi ); // снаружи - не видно (ошибка: undefined variable 'sayHi') @@ -38,8 +35,7 @@ alert( sayHi ); // снаружи - не видно (ошибка: undefined var Кроме того, имя NFE нельзя перезаписать: -```js -//+ run +```js run var test = function sayHi(name) { *!* sayHi = "тест"; // попытка перезаписи @@ -54,16 +50,15 @@ test(); Как правило, имя NFE используется для единственной цели -- позволить изнутри функции вызвать саму себя. -## Пример использования +## Пример использования -NFE используется в первую очередь в тех ситуациях, когда функцию нужно передавать в другое место кода или перемещать из одной переменной в другую. +NFE используется в первую очередь в тех ситуациях, когда функцию нужно передавать в другое место кода или перемещать из одной переменной в другую. **Внутреннее имя позволяет функции надёжно обращаться к самой себе, где бы она ни находилась.** -Вспомним, к примеру, функцию-факториал из задачи [](/task/factorial): +Вспомним, к примеру, функцию-факториал из задачи : -```js -//+ run +```js run function f(n) { return n ? n * f(n - 1) : 1; }; @@ -73,8 +68,7 @@ alert( f(5) ); // 120 Попробуем перенести её в другую переменную `g`: -```js -//+ run +```js run function f(n) { return n ? n * f(n - 1) : 1; }; @@ -91,12 +85,11 @@ alert( g(5) ); // запуск функции с новым именем - ош Для того, чтобы функция всегда надёжно работала, объявим её как Named Function Expression: -```js -//+ run no-beautify -var f = function *!*factorial*/!*(n) { +```js run no-beautify +var f = function *!*factorial*/!*(n) { return n ? n**!*factorial*/!*(n-1) : 1; }; - + var g = f; // скопировали ссылку на функцию-факториал в g f = null; @@ -105,16 +98,14 @@ alert( g(5) ); // 120, работает! */!* ``` -[warn header="В браузере IE8- создаются две функции"] - +````warn header="В браузере IE8- создаются две функции" Как мы говорили выше, в браузере IE до 9 версии имя NFE видно везде, что является ошибкой с точки зрения стандарта. ...Но на самом деле ситуация ещё забавнее. Старый IE создаёт в таких случаях целых две функции: одна записывается в переменную `f`, а вторая -- в переменную `factorial`. Например: -```js -//+ run +```js run var f = function factorial(n) { /*...*/ }; // в IE8- false @@ -123,21 +114,19 @@ alert( f === factorial ); ``` Все остальные браузеры полностью поддерживают именованные функциональные выражения. -[/warn] +```` +```smart header="Устаревшее специальное значение `arguments.callee`" +Если вы давно работаете с JavaScript, то, возможно, знаете, что раньше для этой цели также служило специальное значение `arguments.callee`. -[smart header="Устаревшее специальное значение `arguments.callee`"] -Если вы давно работаете с JavaScript, то, возможно, знаете, что раньше для этой цели также служило специальное значение `arguments.callee`. - -Если это название вам ни о чём не говорит -- всё в порядке, читайте дальше, мы обязательно обсудим его [в отдельной главе](#arguments-callee). - -Если же вы в курсе, то стоит иметь в виду, что оно официально исключено из современного стандарта. А NFE -- это наше настоящее. -[/smart] +Если это название вам ни о чём не говорит -- всё в порядке, читайте дальше, мы обязательно обсудим его [в отдельной главе](info:arguments-pseudoarray#arguments-callee). +Если же вы в курсе, то стоит иметь в виду, что оно официально исключено из современного стандарта. А NFE -- это наше настоящее. +``` ## Итого -Если функция задана как Function Expression, ей можно дать имя. +Если функция задана как Function Expression, ей можно дать имя. Оно будет доступно только внутри функции (кроме IE8-). diff --git a/1-js/2-first-steps/21-javascript-specials/article.md b/1-js/2-first-steps/21-javascript-specials/article.md index 3dad3c2e..a05f9cb6 100644 --- a/1-js/2-first-steps/21-javascript-specials/article.md +++ b/1-js/2-first-steps/21-javascript-specials/article.md @@ -12,23 +12,20 @@ Операторы разделяются точкой с запятой: -```js -//+ run no-beautify +```js run no-beautify alert('Привет'); alert('Мир'); ``` Как правило, перевод строки тоже подразумевает точку с запятой. Так тоже будет работать: -```js -//+ run no-beautify -alert('Привет') +```js run no-beautify +alert('Привет') alert('Мир') ``` ...Однако, иногда JavaScript не вставляет точку с запятой. Например: -```js -//+ run no-beautify +```js run no-beautify var a = 2 +3 @@ -37,8 +34,7 @@ alert(a); // 5 Бывают случаи, когда это ведёт к ошибкам, которые достаточно трудно найти и исправить, например: -```js -//+ run +```js run alert("После этого сообщения будет ошибка") [1, 2].forEach(alert) @@ -50,62 +46,53 @@ alert("После этого сообщения будет ошибка") Поддерживаются однострочные комментарии `// ...` и многострочные `/* ... */`: -Подробнее: [](/structure). +Подробнее: . ## Переменные и типы -
      -
    • Объявляются директивой `var`. Могут хранить любое значение: +- Объявляются директивой `var`. Могут хранить любое значение: -```js -var x = 5; -x = "Петя"; -``` + ```js + var x = 5; + x = "Петя"; + ``` +- Есть 5 "примитивных" типов и объекты: -
    • -
    • Есть 5 "примитивных" типов и объекты: + ```js no-beautify + x = 1; // число + x = "Тест"; // строка, кавычки могут быть одинарные или двойные + x = true; // булево значение true/false + x = null; // спец. значение (само себе тип) + x = undefined; // спец. значение (само себе тип) + ``` -```js -//+ no-beautify -x = 1; // число -x = "Тест"; // строка, кавычки могут быть одинарные или двойные -x = true; // булево значение true/false -x = null; // спец. значение (само себе тип) -x = undefined; // спец. значение (само себе тип) -``` + Также есть специальные числовые значения `Infinity` (бесконечность) и `NaN`. -Также есть специальные числовые значения `Infinity` (бесконечность) и `NaN`. + Значение `NaN` обозначает ошибку и является результатом числовой операции, если она некорректна. +- **Значение `null` не является "ссылкой на нулевой адрес/объект" или чем-то подобным. Это просто специальное значение.** -Значение `NaN` обозначает ошибку и является результатом числовой операции, если она некорректна. -
    • -
    • **Значение `null` не является "ссылкой на нулевой адрес/объект" или чем-то подобным. Это просто специальное значение.** + Оно присваивается, если мы хотим указать, что значение переменной неизвестно. -Оно присваивается, если мы хотим указать, что значение переменной неизвестно. + Например: -Например: + ```js + var age = null; // возраст неизвестен + ``` +- **Значение `undefined` означает "переменная не присвоена".** -```js -var age = null; // возраст неизвестен -``` + Например: -
    • -
    • **Значение `undefined` означает "переменная не присвоена".** + ```js + var x; + alert( x ); // undefined + ``` -Например: + Можно присвоить его и явным образом: `x = undefined`, но так делать не рекомендуется. -```js -var x; -alert( x ); // undefined -``` - -Можно присвоить его и явным образом: `x = undefined`, но так делать не рекомендуется. - -Про объекты мы поговорим в главе [](/object), они в JavaScript сильно отличаются от большинства других языков. -
    • -
    • В имени переменной могут быть использованы любые буквы или цифры, но цифра не может быть первой. Символы доллар `$` и подчёркивание `_` допускаются наравне с буквами.
    • -
    + Про объекты мы поговорим в главе , они в JavaScript сильно отличаются от большинства других языков. +- В имени переменной могут быть использованы любые буквы или цифры, но цифра не может быть первой. Символы доллар `$` и подчёркивание `_` допускаются наравне с буквами. -Подробнее: [](/variables), [](/types-intro). +Подробнее: , . ## Строгий режим @@ -121,27 +108,24 @@ alert( x ); // undefined Одно из важных изменений в современном стандарте -- все переменные нужно объявлять через `var`. Есть и другие, которые мы изучим позже, вместе с соответствующими возможностями языка. - - ## Взаимодействие с посетителем Простейшие функции для взаимодействия с посетителем в браузере: -
    -
    ["prompt(вопрос[, по_умолчанию])"](https://developer.mozilla.org/en/DOM/window.prompt)
    -
    Задать `вопрос` и возвратить введённую строку, либо `null`, если посетитель нажал "Отмена".
    -
    ["confirm(вопрос)"](https://developer.mozilla.org/en/DOM/window.confirm)
    -
    Задать `вопрос` и предложить кнопки "Ок", "Отмена". Возвращает, соответственно, `true/false`.
    -
    ["alert(сообщение)"](https://developer.mozilla.org/en/DOM/window.alert)
    -
    Вывести сообщение на экран.
    -
    +["prompt(вопрос[, по_умолчанию])"](https://developer.mozilla.org/en/DOM/window.prompt) +: Задать `вопрос` и возвратить введённую строку, либо `null`, если посетитель нажал "Отмена". + +["confirm(вопрос)"](https://developer.mozilla.org/en/DOM/window.confirm) +: Задать `вопрос` и предложить кнопки "Ок", "Отмена". Возвращает, соответственно, `true/false`. + +["alert(сообщение)"](https://developer.mozilla.org/en/DOM/window.alert) +: Вывести сообщение на экран. Все эти функции являются *модальными*, т.е. не позволяют посетителю взаимодействовать со страницей до ответа. Например: -```js -//+ run +```js run var userName = prompt("Введите имя?", "Василий"); var isTeaWanted = confirm("Вы хотите чаю?"); @@ -149,70 +133,53 @@ alert( "Посетитель: " + userName ); alert( "Чай: " + isTeaWanted ); ``` -Подробнее: [](/uibasic). +Подробнее: . ## Особенности операторов -
      -
    • **Для сложения строк используется оператор `+`.** +- **Для сложения строк используется оператор `+`.** -Если хоть один аргумент -- строка, то другой тоже приводится к строке: + Если хоть один аргумент -- строка, то другой тоже приводится к строке: -```js -//+ run -alert( 1 + 2 ); // 3, число -alert( '1' + 2 ); // '12', строка -alert( 1 + '2' ); // '12', строка -``` + ```js run + alert( 1 + 2 ); // 3, число + alert( '1' + 2 ); // '12', строка + alert( 1 + '2' ); // '12', строка + ``` +- **Сравнение `===` проверяет точное равенство, включая одинаковый тип.** Это самый очевидный и надёжный способ сравнения. -
    • -
    • **Сравнение `===` проверяет точное равенство, включая одинаковый тип.** Это самый очевидный и надёжный способ сравнения. +- **Остальные сравнения `== < <= > >=` осуществляют числовое приведение типа:** -**Остальные сравнения `== < <= > >=` осуществляют числовое приведение типа:** + ```js run + alert( 0 == false ); // true + alert( true > 0 ); // true + ``` -```js -//+ run -alert( 0 == false ); // true -alert( true > 0 ); // true -``` + Исключение -- сравнение двух строк, которое осуществляется лексикографически (см. далее). -Исключение -- сравнение двух строк (см. далее). + Также: значения `null` и `undefined` при `==` равны друг другу и не равны ничему ещё. А при операторах больше/меньше происходит приведение `null` к `0`, а `undefined` к `NaN`. -**Исключение: значения `null` и `undefined` ведут себя в сравнениях не как ноль.** -
        -
      • Они равны `null == undefined` друг другу и не равны ничему ещё. В частности, не равны нулю.
      • -
      • В других сравнениях (кроме `===`) значение `null` преобразуется к нулю, а `undefined` -- становится `NaN` ("ошибка").
      • -
      + Такое поведение может привести к неочевидным результатам, поэтому лучше всего использовать для сравнения с `null/undefined` оператор `===`. Оператор `==` тоже можно, если не хотите отличать `null` от `undefined`. -Такое поведение может привести к неочевидным результатам, поэтому лучше всего использовать для сравнения с ними `===`. Оператор `==` тоже можно, если не хотите отличать `null` от `undefined`. + Например, забавное следствие этих правил для `null`: -Например, забавное следствие этих правил для `null`: + ```js run no-beautify + alert( null > 0 ); // false, т.к. null преобразовано к 0 + alert( null >= 0 ); // true, т.к. null преобразовано к 0 + alert( null == 0 ); // false, в стандарте явно указано, что null равен лишь undefined + ``` -```js -//+ run no-beautify -alert( null > 0 ); // false, т.к. null преобразовано к 0 -alert( null >= 0 ); // true, т.к. null преобразовано к 0 -alert( null == 0 ); // false, в стандарте явно указано, что null равен лишь undefined -``` - -С точки зрения здравого смысла такое невозможно. Значение `null` не равно нулю и не больше, но при этом `null >= 0` возвращает `true`! -
    • - + С точки зрения здравого смысла такое невозможно. Значение `null` не равно нулю и не больше, но при этом `null >= 0` возвращает `true`! +- **Сравнение строк -- лексикографическое, символы сравниваются по своим unicode-кодам.** -
    • **Сравнение строк -- лексикографическое, символы сравниваются по своим unicode-кодам.** - -Поэтому получается, что строчные буквы всегда больше, чем прописные: - -```js -//+ run -alert( 'а' > 'Я' ); // true -``` + Поэтому получается, что строчные буквы всегда больше, чем прописные: -
    • -
    + ```js run + alert( 'а' > 'Я' ); // true + ``` -Подробнее: [](/operators), [](/comparison). + Подробнее: , . ## Логические операторы @@ -228,61 +195,55 @@ alert( 'а' > 'Я' ); // true Например: -```js -//+ run +```js run alert( 0 && 1 ); // 0 alert( 1 && 2 && 3 ); // 3 alert( null || 1 || 2 ); // 1 ``` -Подробнее: [](/logical-ops). - -## Циклы +Подробнее: . -
      -
    • Поддерживаются три вида циклов: +## Циклы -```js -// 1 -while (условие) { - ... -} +- Поддерживаются три вида циклов: -// 2 -do { - ... -} while (условие); + ```js + // 1 + while (условие) { + ... + } -// 3 -for (var i = 0; i < 10; i++) { - ... -} -``` + // 2 + do { + ... + } while (условие); -
    • -
    • Переменную можно объявлять прямо в цикле, но видна она будет и за его пределами.
    • -
    • Поддерживаются директивы `break/continue` для выхода из цикла/перехода на следующую итерацию. + // 3 + for (var i = 0; i < 10; i++) { + ... + } + ``` +- Переменную можно объявлять прямо в цикле, но видна она будет и за его пределами. +- Поддерживаются директивы `break/continue` для выхода из цикла/перехода на следующую итерацию. -Для выхода одновременно из нескольких уровней цикла можно задать метку. + Для выхода одновременно из нескольких уровней цикла можно задать метку. -Синтаксис: "`имя_метки:`", ставится она только перед циклами и блоками, например: + Синтаксис: "`имя_метки:`", ставится она только перед циклами и блоками, например: -```js -*!*outer:*/!* -for(;;) { - ... - for(;;) { - ... - *!*break outer;*/!* - } -} -``` + ```js + *!*outer:*/!* + for(;;) { + ... + for(;;) { + ... + *!*break outer;*/!* + } + } + ``` -Переход на метку возможен только изнутри цикла, и только на внешний блок по отношению к данному циклу. В произвольное место программы перейти нельзя. -
    • -
    + Переход на метку возможен только изнутри цикла, и только на внешний блок по отношению к данному циклу. В произвольное место программы перейти нельзя. -Подробнее: [](/while-for). +Подробнее: . ## Конструкция switch @@ -290,8 +251,7 @@ for(;;) { Например: -```js -//+ run +```js run var age = prompt('Ваш возраст', 18); switch (age) { @@ -307,14 +267,13 @@ switch (age) { } ``` -Подробнее: [](/switch). +Подробнее: . ## Функции Синтаксис функций в JavaScript: -```js -//+ run +```js run // function имя(список параметров) { тело } function sum(a, b) { var result = a + b; @@ -326,32 +285,25 @@ function sum(a, b) { alert( sum(1, 2) ); // 3 ``` -
      -
    • `sum` -- имя функции, ограничения на имя функции -- те же, что и на имя переменной.
    • -
    • Переменные, объявленные через `var` внутри функции, видны везде внутри этой функции, блоки `if`, `for` и т.п. на видимость не влияют.
    • -
    • Параметры копируются в локальные переменные `a`, `b`. -
    • -
    • Функция без `return` считается возвращающей `undefined`. Вызов `return` без значения также возвращает `undefined`: +- `sum` -- имя функции, ограничения на имя функции -- те же, что и на имя переменной. +- Переменные, объявленные через `var` внутри функции, видны везде внутри этой функции, блоки `if`, `for` и т.п. на видимость не влияют. +- Параметры копируются в локальные переменные `a`, `b`. +- Функция без `return` считается возвращающей `undefined`. Вызов `return` без значения также возвращает `undefined`: -```js -//+ run no-beautify -function f() { } -alert( f() ); // undefined -``` - -
    • -
    + ```js run no-beautify + function f() { } + alert( f() ); // undefined + ``` -Подробнее: [](/function-basics). +Подробнее: . ## Function Declaration и Expression -Функция в JavaScript является обычным значением. +Функция в JavaScript является обычным значением. Её можно создать в любом месте кода и присвоить в переменную, вот так: -```js -//+ run +```js run var sum = function(a, b) { var result = a + b; @@ -367,7 +319,7 @@ alert( sum(1, 2) ); // 3 Обычно это удобно, но может быть проблемой, если нужно объявить функцию в зависимости от условия. В этом случае, а также в других ситуациях, когда хочется создать функцию "здесь и сейчас", используют Function Expression. -Детали: [](/function-declaration-expression). +Детали: . ## Named Function Expression @@ -377,8 +329,7 @@ alert( sum(1, 2) ); // 3 Например, создадим функцию для вычисления факториала как Function Expression и дадим ей имя `me`: -```js -//+ run +```js run var factorial = function me(n) { return (n == 1) ? n : n * me(n - 1); } @@ -390,8 +341,8 @@ alert( me ); // ошибка, нет такой переменной ``` Ограничение видимости для имени не работает в IE8-, но вызов с его помощью работает во всех браузерах. - -Более развёрнуто: [](/named-function-expression). + +Более развёрнуто: . ## Итого diff --git a/1-js/2-first-steps/3-structure/article.md b/1-js/2-first-steps/3-structure/article.md index ec28445d..7e10cf43 100644 --- a/1-js/2-first-steps/3-structure/article.md +++ b/1-js/2-first-steps/3-structure/article.md @@ -1,7 +1,9 @@ # Структура кода В этой главе мы рассмотрим общую структуру кода, команды и их разделение. + [cut] + ## Команды Раньше мы уже видели пример команды: `alert('Привет, мир!')` выводит сообщение. @@ -10,39 +12,35 @@ Например, вместо одного вызова `alert` сделаем два: -```js -//+ run no-beautify +```js run no-beautify alert('Привет'); alert('Мир'); ``` Как правило, каждая команда пишется на отдельной строке -- так код лучше читается: -```js -//+ run no-beautify -alert('Привет'); +```js run no-beautify +alert('Привет'); alert('Мир'); ``` ## Точка с запятой [#semicolon] -Точку с запятой *во многих случаях* можно не ставить, если есть переход на новую строку. +Точку с запятой *во многих случаях* можно не ставить, если есть переход на новую строку. Так тоже будет работать: -```js -//+ run no-beautify -alert('Привет') +```js run no-beautify +alert('Привет') alert('Мир') ``` -В этом случае JavaScript интерпретирует переход на новую строчку как разделитель команд и автоматически вставляет "виртуальную" точку с запятой между ними. +В этом случае JavaScript интерпретирует переход на новую строчку как разделитель команд и автоматически вставляет "виртуальную" точку с запятой между ними. **Однако, важно то, что "во многих случаях" не означает "всегда"!** Например, запустите этот код: -```js -//+ run no-beautify +```js run no-beautify alert(3 + 1 + 2); @@ -54,13 +52,12 @@ alert(3 + **Но в некоторых важных ситуациях JavaScript "забывает" вставить точку с запятой там, где она нужна.** -Таких ситуаций не так много, но ошибки, которые при этом появляются, достаточно сложно обнаруживать и исправлять. +Таких ситуаций не так много, но ошибки, которые при этом появляются, достаточно сложно обнаруживать и исправлять. Чтобы не быть голословным, вот небольшой пример. Такой код работает: -```js -//+ run +```js run [1, 2].forEach(alert) ``` @@ -68,8 +65,7 @@ alert(3 + Важно, что вот такой код уже работать не будет: -```js -//+ run no-beautify +```js run no-beautify alert("Сейчас будет ошибка") [1, 2].forEach(alert) ``` @@ -77,8 +73,7 @@ alert("Сейчас будет ошибка") Выведется только первый `alert`, а дальше -- ошибка. Потому что перед квадратной скобкой JavaScript точку с запятой не ставит, а как раз здесь она нужна (упс!). Если её поставить, то всё будет в порядке: -```js -//+ run +```js run alert( "Сейчас будет ошибка" ); [1, 2].forEach(alert) ``` @@ -93,9 +88,8 @@ alert( "Сейчас будет ошибка" ); *Однострочные комментарии* начинаются с двойного слэша `//`. Текст считается комментарием до конца строки: -```js -//+ run -// Команда ниже говорит "Привет" +```js run +// Команда ниже говорит "Привет" alert( 'Привет' ); alert( 'Мир' ); // Второе сообщение выводим отдельно @@ -103,8 +97,7 @@ alert( 'Мир' ); // Второе сообщение выводим отдел *Многострочные комментарии* начинаются слешем-звездочкой "/*" и заканчиваются звездочкой-слэшем "*/", вот так: -```js -//+ run +```js run /* Пример с двумя сообщениями. Это - многострочный комментарий. */ @@ -114,31 +107,27 @@ alert( 'Мир' ); Всё содержимое комментария игнорируется. Если поместить код внутрь /* ... */ или после `//` -- он не выполнится. -```js -//+ run +```js run /* Закомментировали код alert( 'Привет' ); */ alert( 'Мир' ); ``` -[smart header="Используйте горячие клавиши!"] -В большинстве редакторов комментарий можно поставить горячей клавишей, обычно это [key Ctrl+/] для однострочных и что-то вроде [key Ctrl+Shift+/] -- для многострочных комментариев (нужно выделить блок и нажать сочетание клавиш). Детали смотрите в руководстве по редактору. -[/smart] - -[warn header="Вложенные комментарии не поддерживаются!"] +```smart header="Используйте горячие клавиши!" +В большинстве редакторов комментарий можно поставить горячей клавишей, обычно это `key:Ctrl+/` для однострочных и что-то вроде `key:Ctrl+Shift+/` -- для многострочных комментариев (нужно выделить блок и нажать сочетание клавиш). Детали смотрите в руководстве по редактору. +``` + +````warn header="Вложенные комментарии не поддерживаются!" В этом коде будет ошибка: -```js -//+ run no-beautify -/* +```js run no-beautify +/* /* вложенный комментарий ?!? */ */ alert('Мир'); ``` - -[/warn] - +```` Не бойтесь комментариев. Чем больше кода в проекте -- тем они важнее. Что же касается увеличения размера кода -- это не страшно, т.к. существуют инструменты сжатия JavaScript, которые при публикации кода легко их удалят. diff --git a/1-js/2-first-steps/4-strict-mode/article.md b/1-js/2-first-steps/4-strict-mode/article.md index 02670f7b..aab085cc 100644 --- a/1-js/2-first-steps/4-strict-mode/article.md +++ b/1-js/2-first-steps/4-strict-mode/article.md @@ -2,11 +2,11 @@ Очень долго язык JavaScript развивался без потери совместимости. Новые возможности добавлялись в язык, но старые -- никогда не менялись, чтобы не "сломать" уже существующие HTML/JS-страницы с их использованием. -Однако, это привело к тому, что любая ошибка в дизайне языка становилась "вмороженной" в него навсегда. +Однако, это привело к тому, что любая ошибка в дизайне языка становилась "вмороженной" в него навсегда. -Так было до появления стандарта EcmaScript 5 (ES5), который одновременно добавил новые возможности и внёс в язык ряд исправлений, которые могут привести к тому, что старый код, который был написан до его появления, перестанет работать. +Так было до появления стандарта ECMAScript 5 (ES5), который одновременно добавил новые возможности и внёс в язык ряд исправлений, которые могут привести к тому, что старый код, который был написан до его появления, перестанет работать. -Чтобы этого не случилось, решили, что по умолчанию эти опасные изменения будут выключены, и код будет работать по-старому. А для того, чтобы перевести код в режим полного соответствия современному стандарту, нужно указать специальную директиву `use strict`. +Чтобы этого не случилось, решили, что по умолчанию эти опасные изменения будут выключены, и код будет работать по-старому. А для того, чтобы перевести код в режим полного соответствия современному стандарту, нужно указать специальную директиву `use strict`. Эта директива не поддерживается IE9-. @@ -25,15 +25,15 @@ ... ``` -[warn header="Отменить действие `use strict` никак нельзя"] -Не существует директивы `no use strict` или подобной, которая возвращает в старый режим. +```warn header="Отменить действие `use strict` никак нельзя" +Не существует директивы `no use strict` или подобной, которая возвращает в старый режим. Если уж вошли в современный режим, то это дорога в один конец. -[/warn] +``` -[smart header="`use strict` для функций"] +```smart header="`use strict` для функций" Через некоторое время мы будем проходить [функции](/function-basics). На будущее заметим, что `use strict` также можно указывать в начале функций, тогда строгий режим будет действовать только внутри функции. -[/smart] +``` В следующих главах мы будем подробно останавливаться на отличиях в работе языка при `use strict` и без него. @@ -43,22 +43,19 @@ Однако, есть и две проблемы. -
    -
    Поддержка браузеров IE9-, которые игнорируют `"use strict"`.
    -
    Предположим, что мы, используя `"use strict"`, разработали код и протестировали его в браузере Chrome. Всё работает... Однако, вероятность ошибок при этом в IE9- выросла! Он-то всегда работает по старому стандарту, а значит, иногда по-другому. Возникающие ошибки придётся отлаживать уже в IE9-, и это намного менее приятно, нежели в Chrome. +Поддержка браузеров IE9-, которые игнорируют `"use strict"`. +: Предположим, что мы, используя `"use strict"`, разработали код и протестировали его в браузере Chrome. Всё работает... Однако, вероятность ошибок при этом в IE9- выросла! Он-то всегда работает по старому стандарту, а значит, иногда по-другому. Возникающие ошибки придётся отлаживать уже в IE9-, и это намного менее приятно, нежели в Chrome. + + Впрочем, проблема не так страшна. Несовместимостей мало. И если их знать (а в учебнике мы будем останавливаться на них) и писать правильный код, то всё будет в порядке и `"use strict"` станет нашим верным помощником. -Впрочем, проблема не так страшна. Несовместимостей мало. И если их знать (а в учебнике мы будем останавливаться на них) и писать правильный код, то всё будет в порядке и `"use strict"` станет нашим верным помощником. -
    -
    Библиотеки, написанные без учёта `"use strict"`.
    -
    Некоторые библиотеки, которые написаны без `"use strict"`, не всегда корректно работают, если вызывающий код содержит `"use strict"`. +Библиотеки, написанные без учёта `"use strict"`. +: Некоторые библиотеки, которые написаны без `"use strict"`, не всегда корректно работают, если вызывающий код содержит `"use strict"`. -В первую очередь имеются в виду сторонние библиотеки, которые писали не мы, и которые не хотелось бы переписывать или править. + В первую очередь имеются в виду сторонние библиотеки, которые писали не мы, и которые не хотелось бы переписывать или править. -Таких библиотек мало, но при переводе давно существующих проектов на `"use strict"` эта проблема возникает с завидной регулярностью. -
    -
    + Таких библиотек мало, но при переводе давно существующих проектов на `"use strict"` эта проблема возникает с завидной регулярностью. -Вывод? +Вывод? **Писать код с `use strict` следует лишь в том случае, если вы уверены, что описанных выше проблем не будет.** @@ -66,7 +63,7 @@ ## ES5-shim [#es5-shim] -Браузер IE8 поддерживает только совсем старую версию стандарта JavaScript, а именно ES3. +Браузер IE8 поддерживает только совсем старую версию стандарта JavaScript, а именно ES3. К счастью, многие возможности современного стандарта можно добавить в этот браузер, подключив библиотеку [ES5 shim](https://github.com/es-shims/es5-shim), а именно -- скрипты `es5-shim.js` и `es5-sham.js` из неё. diff --git a/1-js/2-first-steps/5-variables/1-hello-variables/solution.md b/1-js/2-first-steps/5-variables/1-hello-variables/solution.md index e81aff84..6fb38307 100644 --- a/1-js/2-first-steps/5-variables/1-hello-variables/solution.md +++ b/1-js/2-first-steps/5-variables/1-hello-variables/solution.md @@ -1,7 +1,6 @@ Каждая строчка решения соответствует одному шагу задачи: -```js -//+ run +```js run var admin, name; // две переменных через запятую name = "Василий"; diff --git a/1-js/2-first-steps/5-variables/1-hello-variables/task.md b/1-js/2-first-steps/5-variables/1-hello-variables/task.md index 42438a4b..bab3606d 100644 --- a/1-js/2-first-steps/5-variables/1-hello-variables/task.md +++ b/1-js/2-first-steps/5-variables/1-hello-variables/task.md @@ -1,10 +1,10 @@ -# Работа с переменными +importance: 2 + +--- -[importance 2] +# Работа с переменными -
      -
    1. Объявите две переменные: `admin` и `name`.
    2. -
    3. Запишите в `name` строку `"Василий"`.
    4. -
    5. Скопируйте значение из `name` в `admin`.
    6. -
    7. Выведите `admin` (должно вывести "Василий").
    8. -
    \ No newline at end of file +1. Объявите две переменные: `admin` и `name`. +2. Запишите в `name` строку `"Василий"`. +3. Скопируйте значение из `name` в `admin`. +4. Выведите `admin` (должно вывести "Василий"). diff --git a/1-js/2-first-steps/5-variables/article.md b/1-js/2-first-steps/5-variables/article.md index 57ce5eff..f0f44c48 100644 --- a/1-js/2-first-steps/5-variables/article.md +++ b/1-js/2-first-steps/5-variables/article.md @@ -5,7 +5,9 @@ Если это электронный магазин -- то это товары, корзина. Если чат -- посетители, сообщения и так далее. Чтобы хранить информацию, используются *переменные*. + [cut] + ## Переменная *Переменная* состоит из имени и выделенной области памяти, которая ему соответствует. @@ -25,8 +27,7 @@ message = 'Hello'; // сохраним в переменной строку Эти данные будут сохранены в соответствующей области памяти и в дальнейшем доступны при обращении по имени: -```js -//+ run +```js run var message; message = 'Hello!'; @@ -41,23 +42,21 @@ var message = 'Hello!'; Можно даже объявить несколько переменных сразу: -```js -//+ no-beautify +```js no-beautify var user = 'John', age = 25, message = 'Hello'; ``` ### Аналогия из жизни -Проще всего понять переменную, если представить её как "коробку" для данных, с уникальным именем. +Проще всего понять переменную, если представить её как "коробку" для данных, с уникальным именем. Например, переменная `message` -- это коробка, в которой хранится значение `"Hello!"`: - +![](variable.png) В коробку можно положить любое значение, а позже - поменять его. Значение в переменной можно изменять сколько угодно раз: -```js -//+ run +```js run var message; message = 'Hello!'; @@ -69,12 +68,11 @@ alert( message ); При изменении значения старое содержимое переменной удаляется. - +![](variable-change.png) Можно объявить две переменные и копировать данные из одной в другую: -```js -//+ run +```js run var hello = 'Hello world!'; var message; @@ -88,21 +86,20 @@ alert( hello ); // Hello world! alert( message ); // Hello world! ``` -[smart] +```smart Существуют [функциональные](http://ru.wikipedia.org/wiki/%D0%AF%D0%B7%D1%8B%D0%BA_%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D0%BE%D0%BD%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B3%D0%BE_%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%D1%8F) языки программирования, в которых значение переменной менять нельзя. Например, [Scala](http://www.scala-lang.org/) или [Erlang](http://www.erlang.org/). В таких языках положил один раз значение в коробку -- и оно хранится там вечно, ни удалить ни изменить. А нужно что-то другое сохранить -- изволь создать новую коробку (объявить новую переменную), повторное использование невозможно. С виду -- не очень удобно, но, как ни странно, и на таких языках вполне можно успешно программировать. Более того, оказывается, что в ряде областей, например в распараллеливании вычислений, они имеют преимущества. Изучение какого-нибудь функционального языка рекомендуется для расширения кругозора. -[/smart] +``` ## Имена переменных [#variable-naming] -На имя переменной в JavaScript наложены всего два ограничения. -
      -
    1. Имя может состоять из: букв, цифр, символов `$` и `_`
    2. -
    3. Первый символ не должен быть цифрой.
    4. -
    +На имя переменной в JavaScript наложены всего два ограничения. + +1. Имя может состоять из: букв, цифр, символов `$` и `_` +2. Первый символ не должен быть цифрой. Например: @@ -113,8 +110,7 @@ var test123; **Что особенно интересно -- доллар `'$'` и знак подчеркивания `'_'` являются такими же обычными символами, как буквы:** -```js -//+ run untrusted +```js run untrusted var $ = 1; // объявили переменную с именем '$' var _ = 2; // переменная с именем '_' @@ -123,44 +119,37 @@ alert( $ + _ ); // 3 А такие переменные были бы неправильными: -```js -//+ no-beautify +```js no-beautify var 1a; // начало не может быть цифрой var my-name; // дефис '-' не является разрешенным символом ``` -[smart header="Регистр букв имеет значение"] -Переменные `apple` и `AppLE` -- две разные переменные. -[/smart] - -[smart header="Русские буквы допустимы, но не рекомендуются"] +```smart header="Регистр букв имеет значение" +Переменные `apple` и `AppLE` -- две разные переменные. +``` +````smart header="Русские буквы допустимы, но не рекомендуются" В названии переменных можно использовать и русские буквы, например: -```js -//+ run +```js run var имя = "Вася"; alert( имя ); // "Вася" ``` Технически, ошибки здесь нет, но на практике сложилась традиция использовать в именах только английские буквы. -[/smart] - -[warn header="Зарезервированные имена"] +```` +````warn header="Зарезервированные имена" Существует список зарезервированных слов, которые нельзя использовать для переменных, так как они используются самим языком, например: `var, class, return, export` и др. Например, такой пример выдаст синтаксическую ошибку: -```js -//+ run no-beautify +```js run no-beautify var return = 5; // ошибка alert(return); ``` - -[/warn] - +```` ## Важность директивы var @@ -174,8 +163,7 @@ num = 5; // переменная num будет создана, если ее н Следующий код выдаст ошибку: -```js -//+ run +```js run "use strict"; *!* @@ -185,8 +173,7 @@ num = 5; // error: num is not defined Обратим внимание, директиву `use strict` нужно ставить до кода, иначе она не сработает: -```js -//+ run +```js run var something; "use strict"; // слишком поздно @@ -196,8 +183,8 @@ num = 5; // ошибки не будет, так как строгий режи */!* ``` -[warn header="Ошибка в IE8- без `var`"] -Если же вы собираетесь поддерживать IE8-, то у меня для вас ещё одна причина всегда использовать `var`. +````warn header="Ошибка в IE8- без `var`" +Если же вы собираетесь поддерживать IE8-, то у меня для вас ещё одна причина всегда использовать `var`. Следущий документ в IE8- ничего не выведет, будет ошибка: @@ -213,19 +200,18 @@ num = 5; // ошибки не будет, так как строгий режи Это потому, что переменная `test` не объявлена через `var` и совпадает с `id` элемента `
    `. Даже не спрашивайте почему -- это ошибка в браузере IE до версии 9. -Самое "забавное" то, что такая ошибка присвоения значений будет только в IE8- и только если на странице присутствует элемент с совпадающим с именем `id`. +Самое "забавное" то, что такая ошибка присвоения значений будет только в IE8- и только если на странице присутствует элемент с совпадающим с именем `id`. Такие ошибки особенно "весело" исправлять и отлаживать. Вывод простой -- всегда объявляем переменные через `var`, и сюрпризов не будет. Даже в старых IE. -[/warn] +```` ## Константы *Константа* -- это переменная, которая никогда не меняется. Как правило, их называют большими буквами, через подчёркивание. Например: -```js -//+ run +```js run var COLOR_RED = "#F00"; var COLOR_GREEN = "#0F0"; var COLOR_BLUE = "#00F"; @@ -239,21 +225,15 @@ alert( color ); // #FF7F00 Зачем нужны константы? Почему бы просто не писать `var color = "#FF7F00"`? -
      -
    1. Во-первых, константа `COLOR_ORANGE` -- это понятное имя. По присвоению `var color="#FF7F00"` непонятно, что цвет -- оранжевый. Иными словами, константа `COLOR_ORANGE` является "понятным псевдонимом" для значения `#FF7F00`.
    2. -
    3. Во-вторых, опечатка в строке, особенно такой сложной как `#FF7F00`, может быть не замечена, а в имени константы её допустить куда сложнее.
    4. -
    +1. Во-первых, константа `COLOR_ORANGE` -- это понятное имя. По присвоению `var color="#FF7F00"` непонятно, что цвет -- оранжевый. Иными словами, константа `COLOR_ORANGE` является "понятным псевдонимом" для значения `#FF7F00`. +2. Во-вторых, опечатка в строке, особенно такой сложной как `#FF7F00`, может быть не замечена, а в имени константы её допустить куда сложнее. **Константы используют вместо строк и цифр, чтобы сделать программу понятнее и избежать ошибок.** ## Итого -
      -
    • В JavaScript можно объявлять переменные для хранения данных. Это делается при помощи `var`.
    • -
    • Технически, можно просто записать значение и без объявления переменной, однако по ряду причин это не рекомендуется.
    • -
    • Вместе с объявлением можно сразу присвоить значение: `var x = 10`.
    • -
    • Переменные, которые названы `БОЛЬШИМИ_БУКВАМИ`, являются константами, то есть никогда не меняются. Как правило, они используются для удобства, чтобы было меньше ошибок.
    • -
    - - +- В JavaScript можно объявлять переменные для хранения данных. Это делается при помощи `var`. +- Технически, можно просто записать значение и без объявления переменной, однако по ряду причин это не рекомендуется. +- Вместе с объявлением можно сразу присвоить значение: `var x = 10`. +- Переменные, которые названы `БОЛЬШИМИ_БУКВАМИ`, являются константами, то есть никогда не меняются. Как правило, они используются для удобства, чтобы было меньше ошибок. diff --git a/1-js/2-first-steps/5-variables/variable-change.png b/1-js/2-first-steps/5-variables/variable-change.png index 19fa6fbf..5d0e9f6e 100644 Binary files a/1-js/2-first-steps/5-variables/variable-change.png and b/1-js/2-first-steps/5-variables/variable-change.png differ diff --git a/1-js/2-first-steps/5-variables/variable-change@2x.png b/1-js/2-first-steps/5-variables/variable-change@2x.png index d00fbe15..adae8984 100644 Binary files a/1-js/2-first-steps/5-variables/variable-change@2x.png and b/1-js/2-first-steps/5-variables/variable-change@2x.png differ diff --git a/1-js/2-first-steps/5-variables/variable.png b/1-js/2-first-steps/5-variables/variable.png index aecd47ad..1bc7cc17 100644 Binary files a/1-js/2-first-steps/5-variables/variable.png and b/1-js/2-first-steps/5-variables/variable.png differ diff --git a/1-js/2-first-steps/5-variables/variable@2x.png b/1-js/2-first-steps/5-variables/variable@2x.png index 97d2b79d..a26e1b21 100644 Binary files a/1-js/2-first-steps/5-variables/variable@2x.png and b/1-js/2-first-steps/5-variables/variable@2x.png differ diff --git a/1-js/2-first-steps/6-variable-names/1-declare-variables/solution.md b/1-js/2-first-steps/6-variable-names/1-declare-variables/solution.md index 692a5bc7..c6fcb247 100644 --- a/1-js/2-first-steps/6-variable-names/1-declare-variables/solution.md +++ b/1-js/2-first-steps/6-variable-names/1-declare-variables/solution.md @@ -1,13 +1,11 @@ Каждая строчка решения соответствует одному шагу задачи: -```js -//+ run +```js run var ourPlanetName = "Земля"; // буквально "название нашей планеты" var userName = "Петя"; // "имя посетителя" ``` -Названия переменных можно бы сократить, например, до `planet` и `name`, но тогда станет менее понятно, о чем речь. +Названия переменных можно бы сократить, например, до `planet` и `name`, но тогда станет менее понятно, о чем речь. Насколько допустимы такие сокращения -- зависит от скрипта, его размера и сложности, от того, есть ли другие планеты и пользователи. В общем, лучше не жалеть букв и называть переменные так, чтобы по имени можно было легко понять, что в ней находится, и нельзя было перепутать переменные. - \ No newline at end of file diff --git a/1-js/2-first-steps/6-variable-names/1-declare-variables/task.md b/1-js/2-first-steps/6-variable-names/1-declare-variables/task.md index 1f0ef92f..05d4b430 100644 --- a/1-js/2-first-steps/6-variable-names/1-declare-variables/task.md +++ b/1-js/2-first-steps/6-variable-names/1-declare-variables/task.md @@ -1,8 +1,8 @@ -# Объявление переменных +importance: 3 + +--- -[importance 3] +# Объявление переменных -
      -
    1. Создайте переменную для названия нашей планеты и присвойте ей значение `"Земля"`. *Правильное* имя выберите сами.
    2. -
    3. Создайте переменную для имени посетителя со значением `"Петя"`. Имя также на ваш вкус.
    4. -
    \ No newline at end of file +1. Создайте переменную для названия нашей планеты и присвойте ей значение `"Земля"`. *Правильное* имя выберите сами. +2. Создайте переменную для имени посетителя со значением `"Петя"`. Имя также на ваш вкус. diff --git a/1-js/2-first-steps/6-variable-names/article.md b/1-js/2-first-steps/6-variable-names/article.md index 202a4f03..498a4a80 100644 --- a/1-js/2-first-steps/6-variable-names/article.md +++ b/1-js/2-first-steps/6-variable-names/article.md @@ -1,120 +1,111 @@ # Правильный выбор имени переменной -Правильный выбор имени переменной -- одна из самых важных и сложных вещей в программировании, которая отличает начинающего от гуру. +Правильный выбор имени переменной -- одна из самых важных и сложных вещей в программировании, которая отличает начинающего от гуру. [cut] -Дело в том, что большинство времени мы тратим не на изначальное написание кода, а на его развитие. +Дело в том, что большую часть времени мы тратим не на изначальное написание кода, а на его развитие. -Возможно, эти слова не очевидны, если вы пока что ничего большого не писали или пишете код "только для чтения" (написал 5 строк, отдал заказчику и забыл). Но чем более серьёзные проекты вы будете делать, тем более актуальны они будут для вас. +Возможно, эти слова не очевидны, если вы пока что ничего большого не писали или пишете код "только для записи" (write-only, написал 5 строк, отдал заказчику и забыл). Но чем более серьёзные проекты вы будете делать, тем более актуальны они будут для вас. -Что такое это "развитие"? Это когда я вчера написал код, а сегодня (или спустя неделю) прихожу и хочу его поменять. Например, вывести сообщение не так, а эдак... Обработать товары по-другому, добавить функционал.. А где у меня там сообщение хранится? А где товар?... +Что такое это "развитие"? Это когда я вчера написал код, а сегодня (или спустя неделю) прихожу и хочу его поменять. Например, вывести сообщение не так, а эдак... Обработать товары по-другому, добавить функциональность.. А где у меня там сообщение хранится? А где товар?... Гораздо проще найти нужные данные, если они правильно помечены, то есть когда переменная названа *правильно*. ## Правила именования -
      -
    • **Правило 1.** +- **Правило 1.** -**Никакого транслита. Только английский.** + **Никакого транслита. Только английский.** -Неприемлемы: + Неприемлемы: -```js -var moiTovari; -var cena; -var ssilka; -``` + ```js + var moiTovari; + var cena; + var ssilka; + ``` -Подойдут: + Подойдут: -```js -var myGoods; -var price; -var link; -``` + ```js + var myGoods; + var price; + var link; + ``` -Чем плох транслит? + Чем плох транслит? -Во-первых, среди разработчиков всего мира принято использовать английский язык для имён переменных. И если ваш код потом попадёт к кому-то другому, например вы будете в команде больше чем из одного человека, то велик шанс, что транслит ему не понравится. + Во-первых, среди разработчиков всего мира принято использовать английский язык для имён переменных. И если ваш код потом попадёт к кому-то другому, например вы будете в команде больше чем из одного человека, то велик шанс, что транслит ему не понравится. -Во-вторых, русский транслит хуже читается и длиннее, чем названия на английском. + Во-вторых, русский транслит хуже читается и длиннее, чем названия на английском. -В-третьих, в проектах вы наверняка будете применять библиотеки, написанные другими людьми. Многое уже готово, в распоряжении современного разработчика есть масса инструментов, все они используют названия переменных и функций на английском языке, и вы, конечно, будете их использовать. А от кода, где транслит перемешан с английским -- волосы могут встать дыбом, и не только на голове. + В-третьих, в проектах вы наверняка будете применять библиотеки, написанные другими людьми. Многое уже готово, в распоряжении современного разработчика есть масса инструментов, все они используют названия переменных и функций на английском языке, и вы, конечно, будете их использовать. А от кода, где транслит перемешан с английским -- волосы могут встать дыбом, и не только на голове. -Если вы вдруг не знаете английский -- самое время выучить. -
    • -
    • **Правило 2.** + Если вы вдруг не знаете английский -- самое время выучить. +- **Правило 2.** -**Использовать короткие имена только для переменных "местного значения".** + **Использовать короткие имена только для переменных "местного значения".** -Называть переменные именами, не несущими смысловой нагрузки, например `a`, `e`, `p`, `mg` -- можно только в том случае, если они используются в небольшом фрагменте кода и их применение очевидно. + Называть переменные именами, не несущими смысловой нагрузки, например `a`, `e`, `p`, `mg` -- можно только в том случае, если они используются в небольшом фрагменте кода и их применение очевидно. -Вообще же, название переменной должно быть понятным. Иногда для этого нужно использовать несколько слов. -
    • -
    • **Правило 3.** + Вообще же, название переменной должно быть понятным. Иногда для этого нужно использовать несколько слов. +- **Правило 3.** -**Переменные из нескольких слов пишутся `вместеВотТак`.** + **Переменные из нескольких слов пишутся `вместеВотТак`.** -Например: + Например: -```js -var borderLeftWidth; -``` + ```js + var borderLeftWidth; + ``` -Этот способ записи называется "верблюжьей нотацией" или, по-английски, "camelCase". + Этот способ записи называется "верблюжьей нотацией" или, по-английски, "camelCase". -Существует альтернативный стандарт, когда несколько слов пишутся через знак подчеркивания `'_'`: + Существует альтернативный стандарт, когда несколько слов пишутся через знак подчеркивания `'_'`: -```js -var border_left_width; -``` + ```js + var border_left_width; + ``` -Преимущественно в JavaScript используется вариант `borderLeftWidth`, в частности во встроенных языковых и браузерных функциях. Поэтому целесообразно остановиться на нём. + Преимущественно в JavaScript используется вариант `borderLeftWidth`, в частности во встроенных языковых и браузерных функциях. Поэтому целесообразно остановиться на нём. -Ещё одна причина выбрать "верблюжью нотацию" -- запись в ней немного короче, чем c подчеркиванием, т.к. не нужно вставлять `'_'`. -
    • -
    • **Правило последнее, главное.** + Ещё одна причина выбрать "верблюжью нотацию" -- запись в ней немного короче, чем с подчеркиванием, т.к. не нужно вставлять `'_'`. +- **Правило последнее, главное.** -**Имя переменной должно максимально чётко соответствовать хранимым в ней данным.** + **Имя переменной должно максимально чётко соответствовать хранимым в ней данным.** -Придумывание таких имен -- одновременно коротких и точных, при которых всегда понятно, что где лежит, приходит с опытом, но только если сознательно стремиться к этому. -
    • -
    + Придумывание таких имен -- одновременно коротких и точных, при которых всегда понятно, что где лежит, приходит с опытом, но только если сознательно стремиться к этому. Позвольте поделиться одним небольшим секретом, который очень прост, но позволит улучшить названия переменных и сэкономит время. -Бывает так, что, написав код, мы через некоторое время к нему возвращаемся, надо что-то поправить. И мы примерно помним, что переменная, в которой хранится нужное вам значение, называется... Ну, скажем, `borderLeftWidth`. Мы ищем в её в коде, не находим, но, разобравшись, обнаруживаем, что на самом деле переменная называлась вот так: `leftBorderWidth`. +Бывает так, что, написав код, мы через некоторое время к нему возвращаемся, надо что-то поправить. И мы примерно помним, что переменная, в которой хранится нужное вам значение, называется... Ну, скажем, `borderLeftWidth`. Мы ищем её в коде, не находим, но, разобравшись, обнаруживаем, что на самом деле переменная называлась вот так: `leftBorderWidth`. **Если мы ищем переменную с одним именем, а находим -- с другим, то зачастую самый лучший ход -- это *переименовать* переменную, чтобы имя было тем, которое вы искали.** То есть, в коде `leftBorderWidth`, а мы её переименуем на ту, которую искали: `borderLeftWidth`. -Зачем? Дело в том, что в следующий раз, когда вы захотите что-то поправить, то вы будете искать по тому же самому имени. Соответственно, это сэкономит вам время. +Зачем? Дело в том, что в следующий раз, когда вы захотите что-то поправить, то вы будете искать по тому же самому имени. Соответственно, это сэкономит вам время. -Есть причина и поважнее. Поскольку именно это имя переменной пришло в голову -- скорее всего, оно больше соответствует хранимым там данным, чем то, которое было мы придумали изначально. Исключения бывают, но в любом случае -- такое несовпадение -- это повод задуматься. +Есть причина и поважнее. Поскольку именно это имя переменной пришло в голову -- скорее всего, оно больше соответствует хранимым там данным, чем то, которое мы придумали изначально. Исключения бывают, но в любом случае -- такое несовпадение -- это повод задуматься. Чтобы удобно переименовывать переменную, нужно использовать [хороший редактор JavaScript](/editor), тогда этот процесс будет очень простым и быстрым. -[smart header="Если коротко..."] -Смысл имени переменной -- это "имя на коробке", по которому мы сможем максимально быстро находить нужные нам данные. +```smart header="Если коротко..." +Смысл имени переменной -- это "имя на коробке", по которому мы сможем максимально быстро находить нужные нам данные. -**Не нужно бояться переименовывать переменные, если вы придумали имя получше.** +**Не нужно бояться переименовывать переменные, если вы придумали имя получше.** Современные редакторы позволяют делать это очень удобно и быстро. Это в конечном счете сэкономит вам время. -[/smart] - +``` -[warn header="Храните в переменной то, что следует"] -Бывают ленивые программисты, которые, вместо того чтобы объявить новую переменную, используют существующую. +```warn header="Храните в переменной то, что следует" +Бывают ленивые программисты, которые, вместо того чтобы объявить новую переменную, используют существующую. -В результате получается, что такая переменная -- как коробка, в которую кидают то одно, то другое, то третье, при этом не меняя название. Что в ней лежит сейчас? А кто его знает... Нужно подойти, проверить. +В результате получается, что такая переменная -- как коробка, в которую кидают то одно, то другое, то третье, при этом не меняя название. Что в ней лежит сейчас? А кто его знает... Нужно подойти, проверить. -Сэкономит такой программист время на объявлении переменной -- потеряет в два раза больше на отладке кода. +Сэкономит такой программист время на объявлении переменной -- потеряет в два раза больше на отладке кода. **"Лишняя" переменная -- добро, а не зло.** -[/warn] - - +``` diff --git a/1-js/2-first-steps/7-types-intro/article.md b/1-js/2-first-steps/7-types-intro/article.md index 11a98f86..8b0dc6be 100644 --- a/1-js/2-first-steps/7-types-intro/article.md +++ b/1-js/2-first-steps/7-types-intro/article.md @@ -19,21 +19,19 @@ n = 12.345; Например, бесконечность `Infinity` получается при делении на ноль: -```js -//+ run +```js run alert( 1 / 0 ); // Infinity ``` Ошибка вычислений `NaN` будет результатом некорректной математической операции, например: -```js -//+ run +```js run alert( "нечисло" * 2 ); // NaN, ошибка ``` Эти значения формально принадлежат типу "число", хотя, конечно, числами в их обычном понимании не являются. -Особенности работы с числами в JavaScript разобраны в главе [](/number). +Особенности работы с числами в JavaScript разобраны в главе . ## Строка "string" @@ -42,22 +40,21 @@ var str = "Мама мыла раму"; str = 'Одинарные кавычки тоже подойдут'; ``` -**В JavaScript одинарные и двойные кавычки равноправны.** Можно использовать или те или другие. +**В JavaScript одинарные и двойные кавычки равноправны.** Можно использовать или те или другие. -[smart header="Тип *символ* не существует, есть только *строка*."] +```smart header="Тип *символ* не существует, есть только *строка*." В некоторых языках программирования есть специальный тип данных для одного символа. Например, в языке С это `char`. В JavaScript есть только тип "строка" `string`. Что, надо сказать, вполне удобно. -[/smart] +``` -Более подробно со строками мы познакомимся в главе [](/string). +Более подробно со строками мы познакомимся в главе . -## Булевый (логический) тип "boolean" +## Булевый (логический) тип "boolean" -У него всего два значения: `true` (истина) и `false` (ложь). +У него всего два значения: `true` (истина) и `false` (ложь). Как правило, такой тип используется для хранения значения типа да/нет, например: -```js -//+ no-beautify +```js no-beautify var checked = true; // поле формы помечено галочкой checked = false; // поле формы не содержит галочки ``` @@ -82,31 +79,29 @@ var age = null; Если переменная объявлена, но в неё ничего не записано, то её значение как раз и есть `undefined`: -```js -//+ run +```js run var x; alert( x ); // выведет "undefined" ``` Можно присвоить `undefined` и в явном виде, хотя это делается редко: -```js -//+ run +```js run var x = 123; x = undefined; alert( x ); // "undefined" ``` -В явном виде `undefined` обычно не присваивают, так как это противоречит его смыслу. Для записи в переменную "пустого" или "неизвестного" значения используется `null`. +В явном виде `undefined` обычно не присваивают, так как это противоречит его смыслу. Для записи в переменную "пустого" или "неизвестного" значения используется `null`. ## Объекты "object" -Первые 5 типов называют *"примитивными"*. +Первые 5 типов называют *"примитивными"*. Особняком стоит шестой тип: *"объекты"*. -Он используется для коллекций данных и для объявления более сложных сущностей. +Он используется для коллекций данных и для объявления более сложных сущностей. Объявляются объекты при помощи фигурных скобок `{...}`, например: @@ -114,32 +109,31 @@ alert( x ); // "undefined" var user = { name: "Вася" }; ``` -Мы подробно разберём способы объявления объектов и, вообще, работу с объектами, позже, в главе [](/object). +Мы подробно разберём способы объявления объектов и, вообще, работу с объектами, позже, в главе . ## Оператор typeof [#type-typeof] -Оператор `typeof` возвращает тип аргумента. +Оператор `typeof` возвращает тип аргумента. У него есть два синтаксиса: со скобками и без: -
      -
    1. Синтаксис оператора: `typeof x`.
    2. -
    3. Синтаксис функции: `typeof(x)`.
    4. -
    + +1. Синтаксис оператора: `typeof x`. +2. Синтаксис функции: `typeof(x)`. Работают они одинаково, но первый синтаксис короче. **Результатом `typeof` является строка, содержащая тип:** ```js -typeof undefined // "undefined" +typeof undefined // "undefined" -typeof 0 // "number" +typeof 0 // "number" -typeof true // "boolean" +typeof true // "boolean" -typeof "foo" // "string" +typeof "foo" // "string" -typeof {} // "object" +typeof {} // "object" *!* typeof null // "object" (1) @@ -152,16 +146,14 @@ typeof function(){} // "function" (2) Последние две строки помечены, потому что `typeof` ведет себя в них по-особому. -
      -
    1. Результат `typeof null == "object"` -- это официально признанная ошибка в языке, которая сохраняется для совместимости. На самом деле `null` -- это не объект, а отдельный тип данных.
    2. -
    3. Функции мы пройдём чуть позже. Пока лишь заметим, что функции не являются отдельным базовым типом в JavaScript, а подвидом объектов. Но `typeof` выделяет функции отдельно, возвращая для них `"function"`. На практике это весьма удобно, так как позволяет легко определить функцию.
    4. -
    +1. Результат `typeof null == "object"` -- это официально признанная ошибка в языке, которая сохраняется для совместимости. На самом деле `null` -- это не объект, а отдельный тип данных. +2. Функции мы пройдём чуть позже. Пока лишь заметим, что функции не являются отдельным базовым типом в JavaScript, а подвидом объектов. Но `typeof` выделяет функции отдельно, возвращая для них `"function"`. На практике это весьма удобно, так как позволяет легко определить функцию. К работе с типами мы также вернёмся более подробно в будущем, после изучения основных структур данных. ## Итого -Есть 5 "примитивных" типов: `number`, `string`, `boolean`, `null`, `undefined` и 6-й тип -- объекты `object`. +Есть 5 "примитивных" типов: `number`, `string`, `boolean`, `null`, `undefined` и 6-й тип -- объекты `object`. Очень скоро мы изучим их во всех деталях. diff --git a/1-js/2-first-steps/8-operators/1-increment-order/solution.md b/1-js/2-first-steps/8-operators/1-increment-order/solution.md index 053e8d8d..150fd122 100644 --- a/1-js/2-first-steps/8-operators/1-increment-order/solution.md +++ b/1-js/2-first-steps/8-operators/1-increment-order/solution.md @@ -1,10 +1,9 @@ # Разъяснения -```js -//+ run no-beautify -var a = 1, b = 1, c, d; +```js run no-beautify +var a = 1, b = 1, c, d; -// префиксная форма сначала увеличивает a до 2, а потом возвращает +// префиксная форма сначала увеличивает a до 2, а потом возвращает c = ++a; alert(c); // 2 // постфиксная форма увеличивает, но возвращает старое значение diff --git a/1-js/2-first-steps/8-operators/1-increment-order/task.md b/1-js/2-first-steps/8-operators/1-increment-order/task.md index 180d28eb..0c0b6fcb 100644 --- a/1-js/2-first-steps/8-operators/1-increment-order/task.md +++ b/1-js/2-first-steps/8-operators/1-increment-order/task.md @@ -1,12 +1,13 @@ -# Инкремент, порядок срабатывания +importance: 5 + +--- -[importance 5] +# Инкремент, порядок срабатывания Посмотрите, понятно ли вам, почему код ниже работает именно так? -```js -//+ run no-beautify -var a = 1, b = 1, c, d; +```js run no-beautify +var a = 1, b = 1, c, d; c = ++a; alert(c); // 2 d = b++; alert(d); // 1 diff --git a/1-js/2-first-steps/8-operators/2-assignment-result/solution.md b/1-js/2-first-steps/8-operators/2-assignment-result/solution.md index c0e69ce4..d29cbb90 100644 --- a/1-js/2-first-steps/8-operators/2-assignment-result/solution.md +++ b/1-js/2-first-steps/8-operators/2-assignment-result/solution.md @@ -2,8 +2,7 @@ Оператор присваивания возвращает значение, которое будет записано в переменную, например: -```js -//+ run +```js run var a = 2; alert( a *= 2 ); // 4 ``` diff --git a/1-js/2-first-steps/8-operators/2-assignment-result/task.md b/1-js/2-first-steps/8-operators/2-assignment-result/task.md index a3c166af..f7c12fd8 100644 --- a/1-js/2-first-steps/8-operators/2-assignment-result/task.md +++ b/1-js/2-first-steps/8-operators/2-assignment-result/task.md @@ -1,6 +1,8 @@ -# Результат присваивания +importance: 3 + +--- -[importance 3] +# Результат присваивания Чему будет равен `x` в примере ниже? diff --git a/1-js/2-first-steps/8-operators/article.md b/1-js/2-first-steps/8-operators/article.md index b8b28f3c..a7e01dd4 100644 --- a/1-js/2-first-steps/8-operators/article.md +++ b/1-js/2-first-steps/8-operators/article.md @@ -5,6 +5,7 @@ Несколько операторов мы знаем со школы -- это обычные сложение `+`, умножение `*`, вычитание и так далее. В этой главе мы сконцентрируемся на операторах, которые в курсе математики не проходят, и на их особенностях в JavaScript. + [cut] ## Термины: "унарный", "бинарный", "операнд" @@ -13,31 +14,23 @@ Прежде, чем мы двинемся дальше -- несколько терминов, чтобы понимать, о чём речь. -
      -
    • *Операнд* -- то, к чему применяется оператор. Например: `5 * 2` -- оператор умножения с левым и правым операндами. Другое название: "аргумент оператора".
    • -
    • *Унарным* называется оператор, который применяется к одному выражению. Например, оператор унарный минус `"-"` меняет знак числа на противоположный: +- *Операнд* -- то, к чему применяется оператор. Например: `5 * 2` -- оператор умножения с левым и правым операндами. Другое название: "аргумент оператора". +- *Унарным* называется оператор, который применяется к одному выражению. Например, оператор унарный минус `"-"` меняет знак числа на противоположный: -```js -//+ run -var x = 1; + ```js run + var x = 1; -*!* -x = -x; -*/!* -alert( x ); // -1, применили унарный минус -``` - -
    • -
    • *Бинарным* называется оператор, который применяется к двум операндам. Тот же минус существует и в бинарной форме: - -```js -//+ run no-beautify -var x = 1, y = 3; -alert( y - x ); // 2, бинарный минус -``` -
    • -
    + *!* + x = -x; + */!* + alert( x ); // -1, применили унарный минус + ``` +- *Бинарным* называется оператор, который применяется к двум операндам. Тот же минус существует и в бинарной форме: + ```js run no-beautify + var x = 1, y = 3; + alert( y - x ); // 2, бинарный минус + ``` ## Сложение строк, бинарный + @@ -56,8 +49,7 @@ alert( a ); // моястрока Причем не важно, справа или слева находится операнд-строка, в любом случае нестроковый аргумент будет преобразован. Например: -```js -//+ run +```js run alert( '1' + 2 ); // "12" alert( 2 + '1' ); // "21" ``` @@ -68,25 +60,21 @@ alert( 2 + '1' ); // "21" Например: -```js -//+ run +```js run alert( 2 - '1' ); // 1 alert( 6 / '2' ); // 3 ``` - - ### Преобразование к числу, унарный плюс + Унарный, то есть применённый к одному значению, плюс ничего не делает с числами: -```js -//+ run +```js run alert( +1 ); // 1 alert( +(1 - 2) ); // -1 ``` -Как видно, плюс ничего не изменил в выражениях. Результат -- такой же, как и без него. +Как видно, плюс ничего не изменил в выражениях. Результат -- такой же, как и без него. Тем не менее, он широко применяется, так как его "побочный эффект" -- преобразование значения в число. @@ -94,8 +82,7 @@ alert( +(1 - 2) ); // -1 А что, если их нужно, к примеру, сложить? Бинарный плюс сложит их как строки: -```js -//+ run +```js run var apples = "2"; var oranges = "3"; @@ -104,8 +91,7 @@ alert( apples + oranges ); // "23", так как бинарный плюс ск Поэтому используем унарный плюс, чтобы преобразовать к числу: -```js -//+ run +```js run var apples = "2"; var oranges = "3"; @@ -118,27 +104,27 @@ alert( +apples + +oranges ); // 5, число, оба операнда пред ## Приоритет - В том случае, если в выражении есть несколько операторов -- порядок их выполнения определяется *приоритетом*. -Из школы мы знаем, что умножение в выражении `2 * 2 + 1` выполнится раньше сложения, т.к. его *приоритет* выше, а скобки явно задают порядок выполнения. Но в JavaScript -- гораздо больше операторов, поэтому существует целая [таблица приоритетов](https://developer.mozilla.org/en/JavaScript/Reference/operators/operator_precedence). +Из школы мы знаем, что умножение в выражении `2 * 2 + 1` выполнится раньше сложения, т.к. его *приоритет* выше, а скобки явно задают порядок выполнения. Но в JavaScript -- гораздо больше операторов, поэтому существует целая [таблица приоритетов](https://developer.mozilla.org/en/JavaScript/Reference/operators/operator_precedence). Она содержит как уже пройденные операторы, так и те, которые мы еще не проходили. В ней каждому оператору задан числовой приоритет. Тот, у кого число больше -- выполнится раньше. Если приоритет одинаковый, то порядок выполнения -- слева направо. Отрывок из таблицы: - - - - - - - - - - - -
    .........
    15унарный плюс`+`
    15унарный минус`-`
    14умножение`*`
    14деление`/`
    13сложение`+`
    13вычитание`-`
    .........
    3присвоение`=`
    .........
    +| Приоритет | Название | Обозначение | +|------------|------|------| +| ... | ... | ... | +| 15 | унарный плюс | `+` | +| 15 | унарный минус | `-` | +| 14 | умножение | `*` | +| 14 | деление | `/` | +| 13 | сложение | `+` | +| 13 | вычитание | `-` | +| ... | ... | ... | +| 3 | присваивание | `=` | +| ... | ... | ... | + Так как "унарный плюс" имеет приоритет `15`, выше, чем `13` у обычного "сложения", то в выражении `+apples + +oranges` сначала сработали плюсы у `apples` и `oranges`, а затем уже обычное сложение. @@ -148,7 +134,7 @@ alert( +apples + +oranges ); // 5, число, оба операнда пред У него -- один из самых низких приоритетов: `3`. -Именно поэтому, когда переменную чему-либо присваивают, например, `x = 2 * 2 + 1` сначала выполнится арифметика, а уже затем -- произойдёт присвоение `=`. +Именно поэтому, когда переменную чему-либо присваивают, например, `x = 2 * 2 + 1` сначала выполнится арифметика, а уже затем -- произойдёт присваивание `=`. ```js var x = 2 * 2 + 1; @@ -158,8 +144,7 @@ alert( x ); // 5 **Возможно присваивание по цепочке:** -```js -//+ run +```js run var a, b, c; *!* @@ -173,13 +158,12 @@ alert( c ); // 4 Такое присваивание работает справа-налево, то есть сначала вычислятся самое правое выражение `2+2`, присвоится в `c`, затем выполнится `b = c` и, наконец, `a = b`. -[smart header="Оператор `\"=\"` возвращает значение"] +````smart header="Оператор `\"=\"` возвращает значение" Все операторы возвращают значение. Вызов `x = выражение` не является исключением. Он записывает выражение в `x`, а затем возвращает его. Благодаря этому присваивание можно использовать как часть более сложного выражения: -```js -//+ run +```js run var a = 1; var b = 2; @@ -193,71 +177,60 @@ alert( c ); // 0 В примере выше результатом `(a = b + 1)` является значение, которое записывается в `a` (т.е. `3`). Оно используется для вычисления `c`. -Забавное применение присваивания, не так ли? +Забавное применение присваивания, не так ли? Знать, как это работает -- стоит обязательно, а вот писать самому -- только если вы уверены, что это сделает код более читаемым и понятным. -[/smart] - +```` ## Взятие остатка % -Оператор взятия остатка `%` интересен тем, что, несмотря на обозначение, никакого отношения к процентам не имеет. +Оператор взятия остатка `%` интересен тем, что, несмотря на обозначение, никакого отношения к процентам не имеет. Его результат `a % b` -- это остаток от деления `a` на `b`. Например: -```js -//+ run -alert( 5 % 2 ); // 1, остаток от деления 5 на 2 +```js run +alert( 5 % 2 ); // 1, остаток от деления 5 на 2 alert( 8 % 3 ); // 2, остаток от деления 8 на 3 alert( 6 % 3 ); // 0, остаток от деления 6 на 3 ``` - -## Инкремент/декремент: ++, -- +## Инкремент/декремент: `++`, `--` Одной из наиболее частых операций в JavaScript, как и во многих других языках программирования, является увеличение или уменьшение переменной на единицу. Для этого существуют даже специальные операторы: -
      -
    • **Инкремент** `++` увеличивает на 1: - -```js -//+ run no-beautify -var i = 2; -i++; // более короткая запись для i = i + 1. -alert(i); // 3 -``` -
    • -
    • **Декремент** `--` уменьшает на 1: +- **Инкремент** `++` увеличивает на 1: -```js -//+ run no-beautify -var i = 2; -i--; // более короткая запись для i = i - 1. -alert(i); // 1 -``` + ```js run no-beautify + var i = 2; + i++; // более короткая запись для i = i + 1. + alert(i); // 3 + ``` +- **Декремент** `--` уменьшает на 1: -
    • -
    + ```js run no-beautify + var i = 2; + i--; // более короткая запись для i = i - 1. + alert(i); // 1 + ``` -[warn] -Инкремент/декремент можно применить только к переменной. +```warn +Инкремент/декремент можно применить только к переменной. Код `5++` даст ошибку. -[/warn] +``` -Вызывать эти операторы можно не только после, но и перед переменной: `i++` (называется "постфиксная форма") или `++i` ("префиксная форма"). +Вызывать эти операторы можно не только после, но и перед переменной: `i++` (называется "постфиксная форма") или `++i` ("префиксная форма"). -Обе эти формы записи делают одно и то же: увеличивают на `1`. +Обе эти формы записи делают одно и то же: увеличивают на `1`. Тем не менее, между ними существует разница. Она видна только в том случае, когда мы хотим не только увеличить/уменьшить переменную, но и использовать результат в том же выражении. Например: -```js -//+ run +```js run var i = 1; var a = ++i; // (*) @@ -266,100 +239,82 @@ alert(a); // *!*2*/!* В строке `(*)` вызов `++i` увеличит переменную, а *затем* вернёт ее значение в `a`. Так что в `a` попадёт значение `i` *после* увеличения. -**Постфиксная форма `i++` отличается от префиксной `++i` тем, что возвращает старое значение, бывшее до увеличения.** +**Постфиксная форма `i++` отличается от префиксной `++i` тем, что возвращает старое значение, бывшее до увеличения.** В примере ниже в `a` попадёт старое значение `i`, равное `1`: -```js -//+ run +```js run var i = 1; var a = i++; // (*) alert(a); // *!*1*/!* ``` -
      -
    • Если результат оператора не используется, а нужно только увеличить/уменьшить переменную -- без разницы, какую форму использовать: - -```js -//+ run -var i = 0; -i++; -++i; -alert( i ); // 2 -``` - -
    • -
    • Если хочется тут же использовать результат, то нужна префиксная форма: - -```js -//+ run -var i = 0; -alert( ++i ); // 1 -``` - -
    • -
    • Если нужно увеличить, но нужно значение переменной *до увеличения* -- постфиксная форма: +- Если результат оператора не используется, а нужно только увеличить/уменьшить переменную -- без разницы, какую форму использовать: -```js -//+ run -var i = 0; -alert( i++ ); // 0 -``` + ```js run + var i = 0; + i++; + ++i; + alert( i ); // 2 + ``` +- Если хочется тут же использовать результат, то нужна префиксная форма: -
    • -
    + ```js run + var i = 0; + alert( ++i ); // 1 + ``` +- Если нужно увеличить, но нужно значение переменной *до увеличения* -- постфиксная форма: -[smart header="Инкремент/декремент можно использовать в любых выражениях"] + ```js run + var i = 0; + alert( i++ ); // 0 + ``` +````smart header="Инкремент/декремент можно использовать в любых выражениях" При этом он имеет более высокий приоритет и выполняется раньше, чем арифметические операции: -```js -//+ run +```js run var i = 1; alert( 2 * ++i ); // 4 ``` - - -```js -//+ run +```js run var i = 1; -alert( 2 * i++ ); // 2, выполнился раньше но значение вернул старое +alert( 2 * i++ ); // 2, выполнился раньше но значение вернул старое +alert( i ); // 2 +alert( 2 * i++ ); // 4 +alert( i ); // 3 ``` -При этом, нужно с осторожностью использовать такую запись, потому что в более длинной строке при быстром "вертикальном" чтении кода легко пропустить такой `i++`, и будет неочевидно, что переменая увеличивается. +При этом, нужно с осторожностью использовать такую запись, потому что в более длинной строке при быстром "вертикальном" чтении кода легко пропустить такой `i++`, и будет неочевидно, что переменая увеличивается. Три строки, по одному действию в каждой -- длиннее, зато нагляднее: -```js -//+ run +```js run var i = 1; alert( 2 * i ); i++; ``` -[/smart] +```` ## Побитовые операторы Побитовые операторы рассматривают аргументы как 32-разрядные целые числа и работают на уровне их внутреннего двоичного представления. -Эти операторы не являются чем-то специфичным для JavaScript, они поддерживаются в большинстве языков программирования. +Эти операторы не являются чем-то специфичным для JavaScript, они поддерживаются в большинстве языков программирования. Поддерживаются следующие побитовые операторы: -
      -
    • AND(и) ( `&` )
    • -
    • OR(или) ( `|` )
    • -
    • XOR(побитовое исключающее или) ( `^` )
    • -
    • NOT(не) ( `~` )
    • -
    • LEFT SHIFT(левый сдвиг) ( `<<` )
    • -
    • RIGHT SHIFT(правый сдвиг) ( `>>` )
    • -
    • ZERO-FILL RIGHT SHIFT(правый сдвиг с заполнением нулями) ( `>>>` )
    • -
    - -Они используются редко, поэтому вынесены в отдельную главу [](/bitwise-operators). +- AND(и) ( `&` ) +- OR(или) ( `|` ) +- XOR(побитовое исключающее или) ( `^` ) +- NOT(не) ( `~` ) +- LEFT SHIFT(левый сдвиг) ( `<<` ) +- RIGHT SHIFT(правый сдвиг) ( `>>` ) +- ZERO-FILL RIGHT SHIFT(правый сдвиг с заполнением нулями) ( `>>>` ) +Они используются редко, поэтому вынесены в отдельную главу . ## Сокращённая арифметика с присваиванием @@ -373,8 +328,7 @@ n = n * 2; Эту запись можно укоротить при помощи совмещённых операторов, вот так: -```js -//+ run +```js run var n = 2; n += 5; // теперь n=7 (работает как n = n + 5) n *= 2; // теперь n=14 (работает как n = n * 2) @@ -382,35 +336,32 @@ n *= 2; // теперь n=14 (работает как n = n * 2) alert( n ); // 14 ``` -Так можно сделать для операторов `+,-,*,/` и бинарных `<<,>>,>>>,&,|,^`. +Так можно сделать для операторов `+,-,*,/,%` и бинарных `<<,>>,>>>,&,|,^`. Вызов с присваиванием имеет в точности такой же приоритет, как обычное присваивание, то есть выполнится после большинства других операций: -```js -//+ run +```js run var n = 2; n *= 3 + 5; alert( n ); // 16 (n = 2 * 8) ``` - ## Оператор запятая Один из самых необычных операторов -- запятая `','`. Его можно вызвать явным образом, например: -```js -//+ run +```js run *!* -a = (5, 6); +var a = (5, 6); */!* alert( a ); ``` -Запятая позволяет перечислять выражения, разделяя их запятой `','`. Каждое из них -- вычисляется и отбрасывается, за исключением последнего, которое возвращается. +Запятая позволяет перечислять выражения, разделяя их запятой `','`. Каждое из них -- вычисляется и отбрасывается, за исключением последнего, которое возвращается. Запятая -- единственный оператор, приоритет которого ниже присваивания. В выражении `a = (5,6)` для явного задания приоритета использованы скобки, иначе оператор `'='` выполнился бы до запятой `','`, получилось бы `(a=5), 6`. diff --git a/1-js/2-first-steps/9-comparison/article.md b/1-js/2-first-steps/9-comparison/article.md index 2440ef6c..f72c9f9c 100644 --- a/1-js/2-first-steps/9-comparison/article.md +++ b/1-js/2-first-steps/9-comparison/article.md @@ -6,28 +6,24 @@ Многие операторы сравнения знакомы нам из математики: -
      -
    • Больше/меньше: a > b, a < b.
    • -
    • Больше/меньше или равно: a >= b, a <= b.
    • -
    • Равно `a == b`. -Для сравнения используется два символа равенства `'='`. Один символ `a = b` означал бы присваивание.
    • -
    • "Не равно". В математике он пишется как , в JavaScript -- знак равенства с восклицательным знаком перед ним !=.
    • -
    +- Больше/меньше: a > b, a < b. +- Больше/меньше или равно: a >= b, a <= b. +- Равно `a == b`. +Для сравнения используется два символа равенства `'='`. Один символ `a = b` означал бы присваивание. +- "Не равно". В математике он пишется как , в JavaScript -- знак равенства с восклицательным знаком перед ним !=. ## Логические значения -Как и другие операторы, сравнение возвращает значение. Это значение имеет *логический* тип. +Как и другие операторы, сравнение возвращает значение. Это значение имеет *логический* тип. Существует всего два логических значения: -
      -
    • `true` -- имеет смысл "да", "верно", "истина".
    • -
    • `false` -- означает "нет", "неверно", "ложь".
    • -
    + +- `true` -- имеет смысл "да", "верно", "истина". +- `false` -- означает "нет", "неверно", "ложь". Например: -```js -//+ run +```js run alert( 2 > 1 ); // true, верно alert( 2 == 1 ); // false, неверно alert( 2 != 1 ); // true @@ -35,8 +31,7 @@ alert( 2 != 1 ); // true Логические значения можно использовать и напрямую, присваивать переменным, работать с ними как с любыми другими: -```js -//+ run +```js run var a = true; // присваивать явно var b = 3 > 4; // или как результат сравнения @@ -49,13 +44,12 @@ alert( a == b ); // (true == false) неверно, выведет false Строки сравниваются побуквенно: -```js -//+ run +```js run alert( 'Б' > 'А' ); // true ``` -[warn header="Осторожно, Unicode!"] -Аналогом "алфавита" во внутреннем представлении строк служит кодировка, у каждого символа -- свой номер (код). JavaScript использует кодировку [Unicode](http://ru.wikipedia.org/wiki/%D0%AE%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4). +````warn header="Осторожно, Unicode!" +Аналогом "алфавита" во внутреннем представлении строк служит кодировка, у каждого символа -- свой номер (код). JavaScript использует кодировку [Unicode](http://ru.wikipedia.org/wiki/%D0%AE%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4). При этом сравниваются *численные коды символов*. В частности, код у символа `Б` больше, чем у `А`, поэтому и результат сравнения такой. @@ -63,68 +57,54 @@ alert( 'Б' > 'А' ); // true Поэтому регистр имеет значение: -```js -//+ run +```js run alert( 'а' > 'Я' ); // true, строчные буквы больше прописных ``` -Для корректного сравнения символы должны быть в одинаковом регистре. -[/warn] +Для корректного сравнения символы должны быть в одинаковом регистре. +```` Если строка состоит из нескольких букв, то сравнение осуществляется как в телефонной книжке или в словаре. Сначала сравниваются первые буквы, потом вторые, и так далее, пока одна не будет больше другой. -Иными словами, больше -- та строка, которая в телефонной книге была бы на большей странице. +Иными словами, больше -- та строка, которая в телефонной книге была бы на большей странице. Например: -
      -
    • Если первая буква первой строки больше -- значит первая строка больше, независимо от остальных символов: - -```js -//+ run -alert( 'Банан' > 'Аят' ); -``` -
    • -
    • Если одинаковы -- сравнение идёт дальше. Здесь оно дойдёт до третьей буквы: +- Если первая буква первой строки больше -- значит первая строка больше, независимо от остальных символов: -```js -//+ run -alert( 'Вася' > 'Ваня' ); // true, т.к. 'с' > 'н' -``` - -
    • -
    • При этом любая буква больше отсутствия буквы: + ```js run + alert( 'Банан' > 'Аят' ); + ``` +- Если одинаковы -- сравнение идёт дальше. Здесь оно дойдёт до третьей буквы: -```js -//+ run -alert( 'Привет' > 'Прив' ); // true, так как 'е' больше чем "ничего". -``` + ```js run + alert( 'Вася' > 'Ваня' ); // true, т.к. 'с' > 'н' + ``` +- При этом любая буква больше отсутствия буквы: -
    • -
    -Такое сравнение называется *лексикографическим*. + ```js run + alert( 'Привет' > 'Прив' ); // true, так как 'е' больше чем "ничего". + ``` +Такое сравнение называется *лексикографическим*. -[warn] -Обычно мы получаем значения от посетителя в виде строк. Например, `prompt` возвращает *строку*, которую ввел посетитель. +````warn +Обычно мы получаем значения от посетителя в виде строк. Например, `prompt` возвращает *строку*, которую ввел посетитель. Числа, полученные таким образом, в виде строк сравнивать нельзя, результат будет неверен. Например: -```js -//+ run +```js run alert( "2" > "14" ); // true, неверно, ведь 2 не больше 14 ``` -В примере выше `2` оказалось больше `14`, потому что строки сравниваются посимвольно, а первый символ `'2'` больше `'1'`. +В примере выше `2` оказалось больше `14`, потому что строки сравниваются посимвольно, а первый символ `'2'` больше `'1'`. Правильно было бы преобразовать их к числу явным образом. Например, поставив перед ними `+`: -```js -//+ run +```js run alert( +"2" > +"14" ); // false, теперь правильно ``` - -[/warn] +```` ## Сравнение разных типов @@ -132,43 +112,38 @@ alert( +"2" > +"14" ); // false, теперь правильно Например: -```js -//+ run +```js run alert( '2' > 1 ); // true, сравнивается как 2 > 1 alert( '01' == 1 ); // true, сравнивается как 1 == 1 alert( false == 0 ); // true, false становится числом 0 alert( true == 1 ); // true, так как true становится числом 1. ``` -Тема преобразований типов будет продолжена далее, в главе [](/types-conversion). +Тема преобразований типов будет продолжена далее, в главе . ## Строгое равенство -В обычном операторе `==` есть "проблема"" -- он не может отличить `0` от `false`: +В обычном операторе `==` есть "проблема" -- он не может отличить `0` от `false`: -```js -//+ run +```js run alert( 0 == false ); // true ``` Та же ситуация с пустой строкой: - -```js -//+ run +```js run alert( '' == false ); // true ``` Это естественное следствие того, что операнды разных типов преобразовались к числу. Пустая строка, как и `false`, при преобразовании к числу дают `0`. -Что же делать, если всё же нужно отличить `0` от `false`? +Что же делать, если всё же нужно отличить `0` от `false`? **Для проверки равенства без преобразования типов используются операторы строгого равенства `===` (тройное равно) и `!==`.** Если тип разный, то они всегда возвращают `false`: -```js -//+ run +```js run alert( 0 === false ); // false, т.к. типы различны ``` @@ -176,33 +151,29 @@ alert( 0 === false ); // false, т.к. типы различны ## Сравнение с null и undefined -Проблемы со специальными значениями возможны, когда к переменной применяется операция сравнения `> < <= >=`, а у неё может быть как численное значение, так и `null/undefined`. +Проблемы со специальными значениями возможны, когда к переменной применяется операция сравнения `> < <= >=`, а у неё может быть как численное значение, так и `null/undefined`. **Интуитивно кажется, что `null/undefined` эквивалентны нулю, но это не так.** Они ведут себя по-другому. - -
      -
    1. Значения `null` и `undefined` равны `==` друг другу и не равны чему бы то ни было ещё. -Это жёсткое правило буквально прописано в спецификации языка.
    2. -
    3. При преобразовании в число `null` становится `0`, а `undefined` становится `NaN`.
    4. -
    + +1. Значения `null` и `undefined` равны `==` друг другу и не равны чему бы то ни было ещё. +Это жёсткое правило буквально прописано в спецификации языка. +2. При преобразовании в число `null` становится `0`, а `undefined` становится `NaN`. Посмотрим забавные следствия. ### Некорректный результат сравнения null с 0 Сравним `null` с нулём: -```js -//+ run +```js run alert( null > 0 ); // false alert( null == 0 ); // false ``` Итак, мы получили, что `null` не больше и не равен нулю. А теперь... -```js -//+ run +```js run alert(null >= 0); // *!*true*/!* ``` @@ -210,39 +181,32 @@ alert(null >= 0); // *!*true*/!* Дело в том, что алгоритмы проверки равенства `==` и сравнения `>= > < <=` работают по-разному. -Сравнение честно приводит к числу, получается ноль. А при проверке равенства значения `null` и `undefined` обрабатываются особым образом: они равны друг другу, но не равны чему-то ещё. +Сравнение честно приводит к числу, получается ноль. А при проверке равенства значения `null` и `undefined` обрабатываются особым образом: они равны друг другу, но не равны чему-то ещё. -В результате получается странная с точки зрения здравого смысла ситуация, которую мы видели в примере выше. +В результате получается странная с точки зрения здравого смысла ситуация, которую мы видели в примере выше. ### Несравнимый undefined Значение `undefined` вообще нельзя сравнивать: -```js -//+ run +```js run alert( undefined > 0 ); // false (1) alert( undefined < 0 ); // false (2) alert( undefined == 0 ); // false (3) ``` -
      -
    • Сравнения `(1)` и `(2)` дают `false` потому, что `undefined` при преобразовании к числу даёт `NaN`. А значение `NaN` по стандарту устроено так, что сравнения `==`, `<`, `>`, `<=`, `>=` и даже `===` с ним возвращают `false`.
    • -
    • Проверка равенства `(3)` даёт `false`, потому что в стандарте явно прописано, что `undefined` равно лишь `null` и ничему другому.
    • -
    - +- Сравнения `(1)` и `(2)` дают `false` потому, что `undefined` при преобразовании к числу даёт `NaN`. А значение `NaN` по стандарту устроено так, что сравнения `==`, `<`, `>`, `<=`, `>=` и даже `===` с ним возвращают `false`. +- Проверка равенства `(3)` даёт `false`, потому что в стандарте явно прописано, что `undefined` равно лишь `null` и ничему другому. **Вывод: любые сравнения с `undefined/null`, кроме точного `===`, следует делать с осторожностью.** Желательно не использовать сравнения `>= > < <=` с ними, во избежание ошибок в коде. - ## Итого -
      -
    • В JavaScript есть логические значения `true` (истина) и `false` (ложь). Операторы сравнения возвращают их.
    • -
    • Строки сравниваются побуквенно.
    • -
    • Значения разных типов приводятся к числу при сравнении, за исключением строгого равенства `===` (`!==`).
    • -
    • Значения `null` и `undefined` равны `==` друг другу и не равны ничему другому. В других сравнениях (с участием `>`,`<`) их лучше не использовать, так как они ведут себя не как `0`.
    • -
    +- В JavaScript есть логические значения `true` (истина) и `false` (ложь). Операторы сравнения возвращают их. +- Строки сравниваются побуквенно. +- Значения разных типов приводятся к числу при сравнении, за исключением строгого равенства `===` (`!==`). +- Значения `null` и `undefined` равны `==` друг другу и не равны ничему другому. В других сравнениях (с участием `>`,`<`) их лучше не использовать, так как они ведут себя не как `0`. Мы ещё вернёмся к теме сравнения позже, когда лучше изучим различные типы данных в JavaScript. diff --git a/1-js/2-first-steps/index.md b/1-js/2-first-steps/index.md index eee61f1c..097960de 100644 --- a/1-js/2-first-steps/index.md +++ b/1-js/2-first-steps/index.md @@ -1,3 +1,3 @@ # Основы JavaScript -Основные кирпичики из которых состоят скрипты. \ No newline at end of file +Основные кирпичики, из которых состоят скрипты. diff --git a/1-js/3-writing-js/1-debugging-chrome/article.md b/1-js/3-writing-js/1-debugging-chrome/article.md index 8092ba0e..2d4948a5 100644 --- a/1-js/3-writing-js/1-debugging-chrome/article.md +++ b/1-js/3-writing-js/1-debugging-chrome/article.md @@ -1,6 +1,6 @@ # Отладка в браузере Chrome -Перед тем, как двигаться дальше, поговорим об отладке скриптов. +Перед тем, как двигаться дальше, поговорим об отладке скриптов. Все современные браузеры поддерживают для этого "инструменты разработчика". Исправление ошибок с их помощью намного проще и быстрее. @@ -14,108 +14,97 @@ Зайдите на [страницу с примером](debugging/index.html) браузером Chrome. -Откройте инструменты разработчика: [key F12] или в меню `Инструменты > Инструменты Разработчика`. +Откройте инструменты разработчика: `key:F12` или в меню `Инструменты > Инструменты Разработчика`. Выберите сверху `Sources`. - +![](chrome_sources.png) Вы видите три зоны: -
      -
    1. **Зона исходных файлов.** В ней находятся все подключённые к странице файлы, включая JS/CSS. Выберите `pow.js`, если он не выбран.
    2. -
    3. **Зона текста.** В ней находится текст файлов.
    4. -
    5. **Зона информации и контроля.** Мы поговорим о ней позже.
    6. -
    +1. **Зона исходных файлов.** В ней находятся все подключённые к странице файлы, включая JS/CSS. Выберите `pow.js`, если он не выбран. +2. **Зона текста.** В ней находится текст файлов. +3. **Зона информации и контроля.** Мы поговорим о ней позже. Обычно зона исходных файлов при отладке не нужна. Скройте её кнопкой . ## Общие кнопки управления - +![](chrome_sources_buttons.png) Три наиболее часто используемые кнопки управления: -
    -
    Формат
    -
    Нажатие форматирует текст текущего файла, расставляет отступы. Нужна, если вы хотите разобраться в чужом коде, плохо отформатированном или сжатом.
    -
    Консоль
    -
    Очень полезная кнопка, открывает тут же консоль для запуска команд. Можно смотреть код и тут же запускать функции. Её нажатие можно заменить на клавишу Esc.
    -
    Окно
    -
    Если код очень большой, то можно вынести инструменты разработки вбок или в отдельное окно, зажав эту кнопку и выбрав соответствующий вариант из списка.
    -
    -## Точки остановки +Формат +: Нажатие форматирует текст текущего файла, расставляет отступы. Нужна, если вы хотите разобраться в чужом коде, плохо отформатированном или сжатом. -Открыли файл `pow.js` во вкладке Sources? Кликните на 6й строке файла `pow.js`, прямо на цифре 6. +Консоль +: Очень полезная кнопка, открывает тут же консоль для запуска команд. Можно смотреть код и тут же запускать функции. Её нажатие можно заменить на клавишу `key:Esc`. -Поздравляю! Вы поставили "точку остановки" или, как чаще говорят, "брейкпойнт". +Окно +: Если код очень большой, то можно вынести инструменты разработки вбок или в отдельное окно, зажав эту кнопку и выбрав соответствующий вариант из списка. -Выглядет это должно примерно так: +## Точки останова - +Открыли файл `pow.js` во вкладке Sources? Кликните на 6-й строке файла `pow.js`, прямо на цифре 6. -Слово *Брейкпойнт* (breakpoint) -- часто используемый английский жаргонизм. Это то место в коде, где отладчик будет *автоматически* останавливать выполнение JavaScript, как только оно до него дойдёт. +Поздравляю! Вы поставили точку останова или, как чаще говорят, "брейкпойнт". + +Выглядеть это должно примерно так: + +![](chrome_sources_breakpoint.png) +Слово *Брейкпойнт* (breakpoint) -- часто используемый английский жаргонизм. Это то место в коде, где отладчик будет *автоматически* останавливать выполнение JavaScript, как только оно до него дойдёт. **В остановленном коде можно посмотреть текущие значения переменных, выполнять команды и т.п., в общем -- отлаживать его.** -Вы можете видеть, что информация о точке остановки появилась справа, в подвкладке Breakpoints. +Вы можете видеть, что информация о точке останова появилась справа, в подвкладке Breakpoints. Вкладка Breakpoints очень удобна, когда код большой, она позволяет: -
      -
    • Быстро перейти на место кода, где стоит брейкпойнт кликом на текст.
    • -
    • Временно выключить брейкпойнт кликом на чекбокс.
    • -
    • Быстро удалить брейкпойнт правым кликом на текст и выбором Remove, и так далее.
    • -
    - -[smart header="Дополнительные возможности"] -
      -
    • Остановку можно инициировать и напрямую из кода скрипта, командой `debugger`: - -```js -function pow(x, n) { - ... - debugger; // <-- отладчик остановится тут - ... -} -``` +- Быстро перейти на место кода, где стоит брейкпойнт кликом на текст. +- Временно выключить брейкпойнт кликом на чекбокс. +- Быстро удалить брейкпойнт правым кликом на текст и выбором Remove, и так далее. -
    • -
    • *Правый клик* на номер строки `pow.js` позволит создать условную точку остановки (conditional breakpoint), т.е. задать условие, при котором точка остановки сработает. +````smart header="Дополнительные возможности" +- Остановку можно инициировать и напрямую из кода скрипта, командой `debugger`: -Это удобно, если остановка нужна только при определённом значении переменной или параметра функции. -
    • -
    -[/smart] + ```js + function pow(x, n) { + ... + debugger; // <-- отладчик остановится тут + ... + } + ``` +- *Правый клик* на номер строки `pow.js` позволит создать условную точку останова (conditional breakpoint), т.е. задать условие, при котором точка останова сработает. + + Это удобно, если останов нужен только при определённом значении переменной или параметра функции. +```` ## Остановиться и осмотреться -Наша функция выполняется сразу при загрузке страницы, так что самый простой способ активировать отладчик JavaScript -- перезагрузить её. Итак, нажимаем [key F5] (Windows, Linux) или [key Cmd+R] (Mac). +Наша функция выполняется сразу при загрузке страницы, так что самый простой способ активировать отладчик JavaScript -- перезагрузить её. Итак, нажимаем `key:F5` (Windows, Linux) или `key:Cmd+R` (Mac). -Если вы сделали всё, как описано выше, то выполнение прервётся как раз на 6й строке. +Если вы сделали всё, как описано выше, то выполнение прервётся как раз на 6-й строке. - +![](chrome_sources_break.png) -Обратите внимание на информационные вкладки справа (отмечены стрелками). +Обратите внимание на информационные вкладки справа (отмечены стрелками). В них мы можем посмотреть текущее состояние: -
      -
    1. **`Watch Expressions` -- показывает текущие значения любых выражений.** -Можно раскрыть эту вкладку, нажать мышью `+` на ней и ввести любое выражение. Отладчик будет отображать его значение на текущий момент, автоматически перевычисляя его при проходе по коду.
    2. -
    3. **`Call Stack` -- стек вызовов, все вложенные вызовы, которые привели к текущему месту кода.** +1. **`Watch Expressions` -- показывает текущие значения любых выражений.** + + Можно раскрыть эту вкладку, нажать мышью `+` на ней и ввести любое выражение. Отладчик будет отображать его значение на текущий момент, автоматически перевычисляя его при проходе по коду. +2. **`Call Stack` -- стек вызовов, все вложенные вызовы, которые привели к текущему месту кода.** -На текущий момент видно, отладчик находится в функции `pow` (pow.js, строка 6), вызванной из анонимного кода (index.html, строка 13).
    4. -
    5. **`Scope Variables` -- переменные.** + На текущий момент видно, отладчик находится в функции `pow` (pow.js, строка 6), вызванной из анонимного кода (index.html, строка 15). +3. **`Scope Variables` -- переменные.** -На текущий момент строка 6 ещё не выполнилась, поэтому `result` равен `undefined`. + На текущий момент строка 6 ещё не выполнилась, поэтому `result` равен `undefined`. -В `Local` показываются переменные функции: объявленные через `var` и параметры. Вы также можете там видеть ключевое слово `this`, если вы не знаете, что это такое -- ничего страшного, мы это обсудим позже, в следующих главах учебника. + В `Local` показываются переменные функции: объявленные через `var` и параметры. Вы также можете там видеть ключевое слово `this`, если вы не знаете, что это такое -- ничего страшного, мы это обсудим позже, в следующих главах учебника. -В `Global` -- глобальные переменные и функции. -
    6. -
    + В `Global` -- глобальные переменные и функции. ## Управление выполнением @@ -123,67 +112,61 @@ function pow(x, n) { Обратим внимание на панель управления справа-сверху, в ней есть 6 кнопок: -
    -
    -- продолжить выполнение, горячая клавиша [key F8].
    -
    Продолжает выполнения скрипта с текущего момента в обычном режиме. Если скрипт не встретит новых точек остановки, то в отладчик управление больше не вернётся. +![|style="vertical-align:middle"](manage1.png) -- продолжить выполнение, горячая клавиша `key:F8`. +: Продолжает выполнения скрипта с текущего момента в обычном режиме. Если скрипт не встретит новых точек останова, то в отладчик управление больше не вернётся. -Нажмите на эту кнопку. + Нажмите на эту кнопку. -Скрипт продолжится, далее, в 6й строке находится рекурсивный вызов функции `pow`, т.е. управление перейдёт в неё опять (с другими аргументами) и сработает точка остановки, вновь включая отладчик. + Скрипт продолжится, далее, в 6-й строке находится рекурсивный вызов функции `pow`, т.е. управление перейдёт в неё опять (с другими аргументами) и сработает точка останова, вновь включая отладчик. -При этом вы увидите, что выполнение стоит на той же строке, но в `Call Stack` появился новый вызов. + При этом вы увидите, что выполнение стоит на той же строке, но в `Call Stack` появился новый вызов. -Походите по стеку вверх-вниз -- вы увидите, что действительно аргументы разные. -
    -
    -- сделать шаг, не заходя внутрь функции, горячая клавиша [key F10].
    -
    Выполняет одну команду скрипта. Если в ней есть вызов функции -- то отладчик обходит его стороной, т.е. не переходит на код внутри. + Походите по стеку вверх-вниз -- вы увидите, что действительно аргументы разные. -Эта кнопка очень удобна, если в текущей строке вызывается функция JS-фреймворка или какая-то другая, которая нас ну совсем не интересует. Тогда выполнение продолжится дальше, без захода в эту функцию, что нам и нужно. +![|style="vertical-align:middle"](manage2.png) -- сделать шаг, не заходя внутрь функции, горячая клавиша `key:F10`. +: Выполняет одну команду скрипта. Если в ней есть вызов функции -- то отладчик обходит его стороной, т.е. не переходит на код внутри. -Обратим внимание, в данном случае эта кнопка при нажатии всё-таки перейдёт внутрь вложенного вызова `pow`, так как внутри `pow` находится брейкпойнт, а на включённых брейкпойнтах отладчик останавливается всегда. -
    -
    -- сделать шаг, горячая клавиша [key F11].
    -
    Выполняет одну команду скрипта и переходит к следующей. Если есть вложенный вызов, то заходит внутрь функции. + Эта кнопка очень удобна, если в текущей строке вызывается функция JS-фреймворка или какая-то другая, которая нас ну совсем не интересует. Тогда выполнение продолжится дальше, без захода в эту функцию, что нам и нужно. -Эта кнопка позволяет подробнейшим образом пройтись по очереди по командам скрипта. -
    -
    -- выполнять до выхода из текущей функции, горячая клавиша [key Shift+F11].
    -
    Выполняет команды до завершения текущей функции. + Обратим внимание, в данном случае эта кнопка при нажатии всё-таки перейдёт внутрь вложенного вызова `pow`, так как внутри `pow` находится брейкпойнт, а на включённых брейкпойнтах отладчик останавливается всегда. -Эта кнопка очень удобна в случае, если мы нечаянно вошли во вложенный вызов, который нам не интересен -- чтобы быстро из него выйти. -
    -
    -- отключить/включить все точки остановки.
    -
    Эта кнопка никак не двигает нас по коду, она позволяет временно отключить все точки остановки в файле. -
    -
    -- включить/отключить автоматическую остановку при ошибке.
    -
    Эта кнопка -- одна из самых важных. +![|style="vertical-align:middle"](manage3.png) -- сделать шаг, горячая клавиша `key:F11`. +: Выполняет одну команду скрипта и переходит к следующей. Если есть вложенный вызов, то заходит внутрь функции. -Нажмите её несколько раз. В старых версиях Chrome у неё три режима -- нужен фиолетовый, в новых -- два, тогда достаточно синего. + Эта кнопка позволяет подробнейшим образом пройтись по очереди по командам скрипта. -Когда она включена, то при ошибке в коде он автоматически остановится и мы сможем посмотреть в отладчике текущие значения переменных, при желании выполнить команды и выяснить, как так получилось. -
    -
    +![|style="vertical-align:middle"](manage4.png) -- выполнять до выхода из текущей функции, горячая клавиша `key:Shift+F11`. +: Выполняет команды до завершения текущей функции. -**Процесс отладки заключается в том, что мы останавливаем скрипт, смотрим, что с переменными, переходим дальше и ищем, где поведение отклоняется от правильного.** + Эта кнопка очень удобна в случае, если мы нечаянно вошли во вложенный вызов, который нам не интересен -- чтобы быстро из него выйти. -[smart header="Continue to here"] -Правый клик на номер строки открывает контекстное меню, в котором можно запустить выполнение кода до неё (Continue to here). Это удобно, когда хочется сразу прыгнуть вперёд и breakpoint неохота ставить. -[/smart] +![|style="vertical-align:middle"](manage5.png) -- отключить/включить все точки останова. +: Эта кнопка никак не двигает нас по коду, она позволяет временно отключить все точки останова в файле. + +![|style="vertical-align:middle"](manage6.png) -- включить/отключить автоматическую остановку при ошибке. +: Эта кнопка -- одна из самых важных. + Нажмите её несколько раз. В старых версиях Chrome у неё три режима -- нужен фиолетовый, в новых -- два, тогда достаточно синего. + Когда она включена, то при ошибке в коде он автоматически остановится и мы сможем посмотреть в отладчике текущие значения переменных, при желании выполнить команды и выяснить, как так получилось. + +**Процесс отладки заключается в том, что мы останавливаем скрипт, смотрим, что с переменными, переходим дальше и ищем, где поведение отклоняется от правильного.** + +```smart header="Continue to here" +Правый клик на номер строки открывает контекстное меню, в котором можно запустить выполнение кода до неё (Continue to here). Это удобно, когда хочется сразу прыгнуть вперёд и breakpoint неохота ставить. +``` ## Консоль При отладке, кроме просмотра переменных и передвижения по скрипту, бывает полезно запускать команды JavaScript. Для этого нужна консоль. -В неё можно перейти, нажав кнопку "Console" вверху-справа, а можно и открыть в дополнение к отладчику, нажав на кнопку или клавишей [key ESC]. +В неё можно перейти, нажав кнопку "Console" вверху-справа, а можно и открыть в дополнение к отладчику, нажав на кнопку или клавишей `key:ESC`. **Самая любимая команда разработчиков: `console.log(...)`.** Она пишет переданные ей аргументы в консоль, например: -```js -//+ run +```js run // результат будет виден в консоли for (var i = 0; i < 5; i++) { console.log("значение", i); @@ -198,12 +181,12 @@ for (var i = 0; i < 5; i++) { Ошибки JavaScript выводятся в консоли. -Например, прервите отладку -- для этого достаточно закрыть инструменты разрабтчика -- и откройте [страницу с ошибкой](error/index.html). +Например, прервите отладку -- для этого достаточно закрыть инструменты разработчика -- и откройте [страницу с ошибкой](error/index.html). -Перейдите во вкладку Console инструментов разработчика ([key Ctrl+Shift+J] / [key Cmd+Shift+J]). +Перейдите во вкладку Console инструментов разработчика (`key:Ctrl+Shift+J` / `key:Cmd+Shift+J`). В консоли вы увидите что-то подобное: - +![](console_error.png) Красная строка -- это сообщение об ошибке. @@ -214,46 +197,33 @@ for (var i = 0; i < 5; i++) { Более подробно прояснить произошедшее нам поможет отладчик. Он может "заморозить" выполнение скрипта на момент ошибки и дать нам возможность посмотреть значения переменных и стека на тот момент. Для этого: -
      -
    1. Перейдите на вкладку Sources.
    2. -
    3. Включите остановку при ошибке, кликнув на кнопку
    4. -
    5. Перезагрузите страницу.
    6. -
    + +1. Перейдите на вкладку Sources. +2. Включите останов при ошибке, кликнув на кнопку ![|style="vertical-align:middle"](manage6.png) +3. Перезагрузите страницу. После перезагрузки страницы JavaScript-код запустится снова и отладчик остановит выполнение на строке с ошибкой: - +![](chrome_break_error.png) Можно посмотреть значения переменных. Открыть консоль и попробовать запустить что-то в ней. Поставить брейкпойнты раньше по коду и посмотреть, что привело к такой печальной картине, и так далее. - ## Итого Отладчик позволяет: -
      -
    • Останавливаться на отмеченном месте (breakpoint) или по команде `debugger`.
    • -
    • Выполнять код -- по одной строке или до определённого места.
    • -
    • Смотреть переменные, выполнять команды в консоли и т.п.
    • -
    -В этой главе кратко описаны возможности отладчика Google Chrome, относящиеся именно к работе с кодом. +- Останавливаться на отмеченном месте (breakpoint) или по команде `debugger`. +- Выполнять код -- по одной строке или до определённого места. +- Смотреть переменные, выполнять команды в консоли и т.п. + +В этой главе кратко описаны возможности отладчика Google Chrome, относящиеся именно к работе с кодом. -Пока что это всё, что нам надо, но, конечно, инструменты разработчика умеют много чего ещё. В частности, вкладка Elements -- позволяет работать со страницей (понадобится позже), Timeline -- смотреть, что именно делает браузер и сколько это у него занимает и т.п. +Пока что это всё, что нам надо, но, конечно, инструменты разработчика умеют много чего ещё. В частности, вкладка Elements -- позволяет работать со страницей (понадобится позже), Timeline -- смотреть, что именно делает браузер и сколько это у него занимает и т.п. Осваивать можно двумя путями: -
      -
    1. [Официальная документация](https://developer.chrome.com/devtools) (на англ.)
    2. -
    3. Кликать в разных местах и смотреть, что получается. Не забывать о клике правой кнопкой мыши.
    4. -
    + +1. [Официальная документация](https://developer.chrome.com/devtools) (на англ.) +2. Кликать в разных местах и смотреть, что получается. Не забывать о клике правой кнопкой мыши. Мы ещё вернёмся к отладчику позже, когда будем работать с HTML. -[head] - -[/head] + diff --git a/1-js/3-writing-js/1-debugging-chrome/head.html b/1-js/3-writing-js/1-debugging-chrome/head.html new file mode 100644 index 00000000..e990ced8 --- /dev/null +++ b/1-js/3-writing-js/1-debugging-chrome/head.html @@ -0,0 +1,8 @@ + \ No newline at end of file diff --git a/1-js/3-writing-js/2-coding-style/1-style-errors/solution.md b/1-js/3-writing-js/2-coding-style/1-style-errors/solution.md index e2473aa4..26d3026a 100644 --- a/1-js/3-writing-js/2-coding-style/1-style-errors/solution.md +++ b/1-js/3-writing-js/2-coding-style/1-style-errors/solution.md @@ -2,8 +2,7 @@ Вы могли заметить следующие недостатки, сверху-вниз: -```js -//+ no-beautify +```js no-beautify function pow(x,n) // <- отсутствует пробел между аргументами { // <- фигурная скобка на отдельной строке var result=1; // <- нет пробелов вокруг знака = @@ -18,10 +17,10 @@ if (n<0) // <- нет пробелов, стоит добавить верти { // <- фигурная скобка на отдельной строке // ниже - слишком длинная строка, нет пробелов alert('Степень '+n+'не поддерживается, введите целую степень, большую 0'); -} -else // <- можно на одной строке } else { +} +else // <- можно на одной строке } else { { - alert(pow(x,n)) // нет точки с запятой + alert(pow(x,n)) // вложенный вызов функции, нет точки с запятой } ``` diff --git a/1-js/3-writing-js/2-coding-style/1-style-errors/task.md b/1-js/3-writing-js/2-coding-style/1-style-errors/task.md index 815fe914..71a54d93 100644 --- a/1-js/3-writing-js/2-coding-style/1-style-errors/task.md +++ b/1-js/3-writing-js/2-coding-style/1-style-errors/task.md @@ -1,25 +1,26 @@ -# Ошибки в стиле +importance: 4 + +--- -[importance 4] +# Ошибки в стиле Какие недостатки вы видите в стиле этого примера? -```js -//+ no-beautify -function pow(x,n) +```js no-beautify +function pow(x,n) { - var result=1; - for(var i=0;i +![](code-style.png) Не всё здесь однозначно, так что разберём эти правила подробнее. @@ -40,24 +42,19 @@ if (n < 0) { Пишутся на той же строке, так называемый "египетский" стиль. Перед скобкой -- пробел. - +![](figure-bracket-style.png) Если у вас уже есть опыт в разработке, и вы привыкли делать скобку на отдельной строке -- это тоже вариант. В конце концов, решать вам. Но в большинстве JavaScript-фреймворков стиль именно такой. @@ -65,71 +62,65 @@ if (n < 0) { ### Длина строки -Максимальную длину строки согласовывают в команде. Как правило, это либо `80`, либо `120` символов, в зависимости от того, какие мониторы у разработчиков. +Максимальную длину строки согласовывают в команде. Как правило, это либо `80`, либо `120` символов, в зависимости от того, какие мониторы у разработчиков. Более длинные строки необходимо разбивать для улучшения читаемости. -### Отступы +### Отступы Отступы нужны двух типов: -
      -
    • **Горизонтальный отступ, при вложенности -- два(или четыре) пробела.** - -Как правило, используются именно пробелы, т.к. они позволяют сделать более гибкие "конфигурации отступов", чем символ "Tab". - -Например, выровнять аргументы относительно открывающей скобки: -```js -//+ no-beautify -show("Строки" + - " выровнены" + - " строго" + - " одна под другой"); -``` -
    • -
    • **Вертикальный отступ, для лучшей разбивки кода -- перевод строки.** - -Используется, чтобы разделить логические блоки внутри одной функции. В примере разделены инициализация переменных, главный цикл и возвращение результата: - -```js -function pow(x, n) { - var result = 1; - // <-- - for (var i = 0; i < n; i++) { - result *= x; - } - // <-- - return result; -} -``` +- **Горизонтальный отступ, при вложенности -- два(или четыре) пробела.** + + Как правило, используются именно пробелы, т.к. они позволяют сделать более гибкие "конфигурации отступов", чем символ "Tab". + + Например, выровнять аргументы относительно открывающей скобки: + ```js no-beautify + show("Строки" + + " выровнены" + + " строго" + + " одна под другой"); + ``` +- **Вертикальный отступ, для лучшей разбивки кода -- перевод строки.** + + Используется, чтобы разделить логические блоки внутри одной функции. В примере разделены инициализация переменных, главный цикл и возвращение результата: + + ```js + function pow(x, n) { + var result = 1; + // <-- + for (var i = 0; i < n; i++) { + result *= x; + } + // <-- + return result; + } + ``` -Вставляйте дополнительный перевод строки туда, где это сделает код более читаемым. Не должно быть более 9 строк кода подряд без вертикального отступа. -
    • -
    + Вставляйте дополнительный перевод строки туда, где это сделает код более читаемым. Не должно быть более 9 строк кода подряд без вертикального отступа. ### Точка с запятой -Точки с запятой нужно ставить, даже если их, казалось бы, можно пропустить. +Точки с запятой нужно ставить, даже если их, казалось бы, можно пропустить. -Есть языки, в которых точка с запятой не обязательна, и её там никто не ставит. В JavaScript перевод строки её заменяет, но лишь частично, поэтому лучше её ставить, как обсуждалось [ранее](#semicolon). +Есть языки, в которых точка с запятой не обязательна, и её там никто не ставит. В JavaScript перевод строки её заменяет, но лишь частично, поэтому лучше её ставить, как обсуждалось [ранее](info:structure#semicolon). ## Именование Общее правило: -
      -
    • Имя переменной -- существительное.
    • -
    • Имя функции -- глагол или начинается с глагола. Бывает, что имена для краткости делают существительными, но глаголы понятнее.
    • -
    + +- Имя переменной -- существительное. +- Имя функции -- глагол или начинается с глагола. Бывает, что имена для краткости делают существительными, но глаголы понятнее. Для имён используется английский язык (не транслит) и верблюжья нотация. -Более подробно -- читайте про [имена функций](#function-naming) и [имена переменных](#variable-naming). +Более подробно -- читайте про [имена функций](info:function-basics#function-naming) и [имена переменных](info:variables#variable-naming). ## Уровни вложенности Уровней вложенности должно быть немного. -Например, [проверки в циклах можно делать через "continue"](#continue), чтобы не было дополнительного уровня `if(..) { ... }`: +Например, [проверки в циклах можно делать через "continue"](info:while-for#continue), чтобы не было дополнительного уровня `if(..) { ... }`: Вместо: @@ -180,7 +171,7 @@ function isEven(n) { // проверка чётности } ``` -Если в блоке `if` идёт `return`, то `else` за ним не нужен. +Если в блоке `if` идёт `return`, то `else` за ним не нужен. **Лучше быстро обработать простые случаи, вернуть результат, а дальше разбираться со сложным, без дополнительного уровня вложенности.** @@ -192,7 +183,7 @@ function isEven(n) { // проверка чётности } ``` -...Однако, если код `!(n % 2)` для вас менее очевиден чем предыдущий вариант, то стоит использовать предыдущий. +...Однако, если код `!(n % 2)` для вас менее очевиден чем предыдущий вариант, то стоит использовать предыдущий. Главное для нас -- не краткость кода, а его простота и читаемость. Совсем не всегда более короткий код проще для понимания, чем более развёрнутый. @@ -200,7 +191,7 @@ function isEven(n) { // проверка чётности Функции должны быть небольшими. Если функция большая -- желательно разбить её на несколько. -Этому правилу бывает сложно следовать, но оно стоит того. При чем же здесь комментарии? +Этому правилу бывает сложно следовать, но оно стоит того. При чем же здесь комментарии? Вызов отдельной небольшой функции не только легче отлаживать и тестировать -- сам факт его наличия является *отличным комментарием*. @@ -225,11 +216,11 @@ function showPrimes(n) { ```js function showPrimes(n) { - + for (var i = 2; i < n; i++) { *!*if (!isPrime(i)) continue;*/!* - - alert(i); // простое + + alert(i); // простое } } @@ -242,78 +233,72 @@ function isPrime(n) { ``` Второй вариант проще и понятнее, не правда ли? Вместо участка кода мы видим описание действия, которое там совершается (проверка `isPrime`). - + ## Функции -- под кодом Есть два способа расположить функции, необходимые для выполнения кода. -
      -
    1. Функции над кодом, который их использует: - -```js -// *!*объявить функции*/!* -function createElement() { - ... -} +1. Функции над кодом, который их использует: -function setHandler(elem) { - ... -} - -function walkAround() { - ... -} + ```js + // *!*объявить функции*/!* + function createElement() { + ... + } -// *!*код, использующий функции*/!* -var elem = createElement(); -setHandler(elem); -walkAround(); -``` + function setHandler(elem) { + ... + } -
    2. -
    3. Сначала код, а функции внизу: + function walkAround() { + ... + } -```js -// *!*код, использующий функции*/!* -var elem = createElement(); -setHandler(elem); -walkAround(); + // *!*код, использующий функции*/!* + var elem = createElement(); + setHandler(elem); + walkAround(); + ``` +2. Сначала код, а функции внизу: -// --- *!*функции*/!* --- + ```js + // *!*код, использующий функции*/!* + var elem = createElement(); + setHandler(elem); + walkAround(); -function createElement() { - ... -} + // --- *!*функции*/!* --- -function setHandler(elem) { - ... -} + function createElement() { + ... + } -function walkAround() { - ... -} -``` + function setHandler(elem) { + ... + } -
    4. -
    + function walkAround() { + ... + } + ``` -...На самом деле существует еще третий "стиль", при котором функции хаотично разбросаны по коду, но это ведь не наш метод, да? +...На самом деле существует еще третий "стиль", при котором функции хаотично разбросаны по коду, но это ведь не наш метод, да? -**Как правило, лучше располагать функции под кодом, который их использует.** +**Как правило, лучше располагать функции под кодом, который их использует.** -То есть, предпочтителен 2й способ. +То есть, предпочтителен 2-й способ. Дело в том, что при чтении такого кода мы хотим знать в первую очередь, *что он делает*, а уже затем *какие функции ему помогают.* Если первым идёт код, то это как раз дает необходимую информацию. Что же касается функций, то вполне возможно нам и не понадобится их читать, особенно если они названы адекватно и то, что они делают, понятно из названия. ## Плохие комментарии -В коде нужны комментарии. +В коде нужны комментарии. -Сразу начну с того, каких комментариев быть почти не должно. +Сразу начну с того, каких комментариев быть почти не должно. **Должен быть минимум комментариев, которые отвечают на вопрос "что происходит в коде?"** -Что интересно, в коде начинающих разработчиков обычно комментариев либо нет, либо они как раз такого типа: "что делается в этих строках". +Что интересно, в коде начинающих разработчиков обычно комментариев либо нет, либо они как раз такого типа: "что делается в этих строках". Серьёзно, хороший код и так понятен. @@ -325,40 +310,35 @@ function walkAround() { ## Хорошие комментарии - А какие комментарии полезны и приветствуются? -
      -
    • **Архитектурный комментарий -- "как оно, вообще, устроено".** +- **Архитектурный комментарий -- "как оно, вообще, устроено".** -Какие компоненты есть, какие технологии использованы, поток взаимодействия. О чём и зачем этот скрипт. Взгляд с высоты птичьего полёта. Эти комментарии особенно нужны, если вы не один, а проект большой. + Какие компоненты есть, какие технологии использованы, поток взаимодействия. О чём и зачем этот скрипт. Взгляд с высоты птичьего полёта. Эти комментарии особенно нужны, если вы не один, а проект большой. -Для описания архитектуры, кстати, создан специальный язык [UML](http://ru.wikipedia.org/wiki/Unified_Modeling_Language), красивые диаграммы, но можно и без этого. Главное -- чтобы понятно. -
    • -
    • **Справочный комментарий перед функцией -- о том, что именно она делает, какие параметры принимает и что возвращает.** + Для описания архитектуры, кстати, создан специальный язык [UML](http://ru.wikipedia.org/wiki/Unified_Modeling_Language), красивые диаграммы, но можно и без этого. Главное -- чтобы понятно. +- **Справочный комментарий перед функцией -- о том, что именно она делает, какие параметры принимает и что возвращает.** -Для таких комментариев существует синтаксис [JSDoc](http://en.wikipedia.org/wiki/JSDoc). + Для таких комментариев существует синтаксис [JSDoc](http://en.wikipedia.org/wiki/JSDoc). -```js -/** - * Возвращает x в степени n, только для натуральных n - * - * @param {number} x Число для возведения в степень. - * @param {number} n Показатель степени, натуральное число. - * @return {number} x в степени n. - */ -function pow(x, n) { - ... -} -``` + ```js + /** + * Возвращает x в степени n, только для натуральных n + * + * @param {number} x Число для возведения в степень. + * @param {number} n Показатель степени, натуральное число. + * @return {number} x в степени n. + */ + function pow(x, n) { + ... + } + ``` -Такие комментарии позволяют сразу понять, что принимает и что делает функция, не вникая в код. + Такие комментарии позволяют сразу понять, что принимает и что делает функция, не вникая в код. -Кстати, они автоматически обрабатываются многими редакторами, например [Aptana](http://aptana.com) и редакторами от [JetBrains](http://www.jetbrains.com/), которые учитывают их при автодополнении, а также выводят их в автоподсказках при наборе кода. + Кстати, они автоматически обрабатываются многими редакторами, например [Aptana](http://aptana.com) и редакторами от [JetBrains](http://www.jetbrains.com/), которые учитывают их при автодополнении, а также выводят их в автоподсказках при наборе кода. -Кроме того, есть инструменты, например [JSDoc 3](https://github.com/jsdoc3/jsdoc), которые умеют генерировать по таким комментариям документацию в формате HTML. Более подробную информацию об этом можно также найти на сайте [](http://usejsdoc.org/). -
    • -
    + Кроме того, есть инструменты, например [JSDoc 3](https://github.com/jsdoc3/jsdoc), которые умеют генерировать по таким комментариям документацию в формате HTML. Более подробную информацию об этом можно также найти на сайте . **...Но куда более важными могут быть комментарии, которые объясняют не *что*, а *почему* в коде происходит именно это!** @@ -368,46 +348,37 @@ function pow(x, n) { Например: -
    -
    Есть несколько способов решения задачи. Почему выбран именно этот?
    -
    -Например, пробовали решить задачу по-другому, но не получилось -- напишите об этом. Почему вы выбрали именно этот способ решения? Особенно это важно в тех случаях, когда используется не первый приходящий в голову способ, а какой-то другой. +Есть несколько способов решения задачи. Почему выбран именно этот? +: Например, пробовали решить задачу по-другому, но не получилось -- напишите об этом. Почему вы выбрали именно этот способ решения? Особенно это важно в тех случаях, когда используется не первый приходящий в голову способ, а какой-то другой. + + Без этого возможна, например, такая ситуация: -Без этого возможна, например, такая ситуация: -
      -
    • Вы открываете код, который был написан какое-то время назад, и видите, что он "неоптимален".
    • -
    • Думаете: "Какой я был дурак", и переписываете под "более очевидный и правильный" вариант.
    • -
    • ...Порыв, конечно, хороший, да только этот вариант вы уже обдумали раньше. И отказались, а почему -- забыли. В процессе переписывания вспомнили, конечно (к счастью), но результат - потеря времени на повторное обдумывание.
    • -
    +- Вы открываете код, который был написан какое-то время назад, и видите, что он "неоптимален". +- Думаете: "Какой я был дурак", и переписываете под "более очевидный и правильный" вариант. +- ...Порыв, конечно, хороший, да только этот вариант вы уже обдумали раньше. И отказались, а почему -- забыли. В процессе переписывания вспомнили, конечно (к счастью), но результат - потеря времени на повторное обдумывание. -Комментарии, которые объясняют выбор решения, очень важны. Они помогают понять происходящее и предпринять правильные шаги при развитии кода. -
    -
    Какие неочевидные возможности обеспечивает этот код? Где ещё они используются?
    -
    -В хорошем коде должно быть минимум неочевидного. Но там, где это есть -- пожалуйста, комментируйте. -
    -
    + Комментарии, которые объясняют выбор решения, очень важны. Они помогают понять происходящее и предпринять правильные шаги при развитии кода. +Какие неочевидные возможности обеспечивает этот код? Где ещё они используются? +: В хорошем коде должно быть минимум неочевидного. Но там, где это есть -- пожалуйста, комментируйте. -[smart header="Комментарии -- это важно"] +```smart header="Комментарии -- это важно" Один из показателей хорошего разработчика -- качество комментариев, которые позволяют эффективно поддерживать код, возвращаться к нему после любой паузы и легко вносить изменения. -[/smart] +``` ## Руководства по стилю -Когда написанием проекта занимается целая команда, то должен существовать один стандарт кода, описывающий где и когда ставить пробелы, запятые, переносы строк и т.п. +Когда написанием проекта занимается целая команда, то должен существовать один стандарт кода, описывающий где и когда ставить пробелы, запятые, переносы строк и т.п. -Сейчас, когда есть столько готовых проектов, нет смысла придумывать целиком своё руководство по стилю. Можно взять уже готовое, и которому, по желанию, всегда можно что-то добавить. +Сейчас, когда есть столько готовых проектов, нет смысла придумывать целиком своё руководство по стилю. Можно взять уже готовое, к которому, по желанию, всегда можно что-то добавить. Большинство есть на английском, сообщите мне, если найдёте хороший перевод: -
      -
    • [Google JavaScript Style Guide](http://google-styleguide.googlecode.com/svn/trunk/javascriptguide.xml)
    • -
    • [JQuery Core Style Guidelines](http://docs.jquery.com/JQuery_Core_Style_Guidelines)
    • -
    • [Airbnb JavaScript Style Guide](https://github.com/airbnb/javascript)
    • -
    • [Idiomatic.JS](https://github.com/rwldrn/idiomatic.js) (есть [перевод](https://github.com/rwldrn/idiomatic.js/tree/master/translations/ru_RU))
    • -
    • [Dojo Style Guide](http://dojotoolkit.org/community/styleGuide)
    • -
    +- [Google JavaScript Style Guide](https://google.github.io/styleguide/javascriptguide.xml) +- [jQuery JavaScript Style Guide](http://contribute.jquery.org/style-guide/js/) +- [Airbnb JavaScript Style Guide](https://github.com/airbnb/javascript) +- [Idiomatic.JS](https://github.com/rwaldron/idiomatic.js) (есть [перевод](https://github.com/rwaldron/idiomatic.js/tree/master/translations/ru_RU)) +- [Dojo Style Guide](https://dojotoolkit.org/reference-guide/1.10/developer/styleguide.html) Для того, чтобы начать разработку, вполне хватит элементов стилей, обозначенных в этой главе. В дальнейшем, посмотрев эти руководства, вы можете выработать и свой стиль, но лучше не делать его особенно "уникальным и неповторимым", себе дороже потом будет с людьми сотрудничать. @@ -417,11 +388,9 @@ function pow(x, n) { Самые известные -- это: -
      -
    • [JSLint](http://www.jslint.com/) -- проверяет код на соответствие [стилю JSLint](http://www.jslint.com/lint.html), в онлайн-интерфейсе вверху можно ввести код, а внизу различные настройки проверки, чтобы сделать её более мягкой.
    • -
    • [JSHint](http://www.jshint.com/) -- вариант JSLint с большим количеством настроек.
    • -
    • [Closure Linter](https://developers.google.com/closure/utilities/) -- проверка на соответствие [Google JavaScript Style Guide](http://google-styleguide.googlecode.com/svn/trunk/javascriptguide.xml).
    • -
    +- [JSLint](http://www.jslint.com/) -- проверяет код на соответствие [стилю JSLint](http://www.jslint.com/lint.html), в онлайн-интерфейсе вверху можно ввести код, а внизу различные настройки проверки, чтобы сделать её более мягкой. +- [JSHint](http://www.jshint.com/) -- вариант JSLint с большим количеством настроек. +- [Closure Linter](https://developers.google.com/closure/utilities/) -- проверка на соответствие [Google JavaScript Style Guide](http://google-styleguide.googlecode.com/svn/trunk/javascriptguide.xml). В частности, JSLint и JSHint интегрированы с большинством редакторов, они гибко настраиваются под нужный стиль и совершенно незаметно улучшают разработку, подсказывая, где и что поправить. diff --git a/1-js/3-writing-js/2-coding-style/code-style.png b/1-js/3-writing-js/2-coding-style/code-style.png index d41a9883..c341a592 100644 Binary files a/1-js/3-writing-js/2-coding-style/code-style.png and b/1-js/3-writing-js/2-coding-style/code-style.png differ diff --git a/1-js/3-writing-js/2-coding-style/code-style@2x.png b/1-js/3-writing-js/2-coding-style/code-style@2x.png index acb8ac1b..a1934ebd 100644 Binary files a/1-js/3-writing-js/2-coding-style/code-style@2x.png and b/1-js/3-writing-js/2-coding-style/code-style@2x.png differ diff --git a/1-js/3-writing-js/2-coding-style/figure-bracket-style.png b/1-js/3-writing-js/2-coding-style/figure-bracket-style.png index 992d126e..7552c1a2 100644 Binary files a/1-js/3-writing-js/2-coding-style/figure-bracket-style.png and b/1-js/3-writing-js/2-coding-style/figure-bracket-style.png differ diff --git a/1-js/3-writing-js/2-coding-style/figure-bracket-style@2x.png b/1-js/3-writing-js/2-coding-style/figure-bracket-style@2x.png index 7b937e88..51c2d566 100644 Binary files a/1-js/3-writing-js/2-coding-style/figure-bracket-style@2x.png and b/1-js/3-writing-js/2-coding-style/figure-bracket-style@2x.png differ diff --git a/1-js/3-writing-js/3-write-unmain-code/article.md b/1-js/3-writing-js/3-write-unmain-code/article.md index dd15be50..9f8748bf 100644 --- a/1-js/3-writing-js/3-write-unmain-code/article.md +++ b/1-js/3-writing-js/3-write-unmain-code/article.md @@ -1,35 +1,33 @@ # Как писать неподдерживаемый код? -[warn header="Познай свой код"] +```warn header="Познай свой код" Эта статья представляет собой мой вольный перевод [How To Write Unmaintainable Code](http://mindprod.com/jgloss/unmain.html) ("как писать неподдерживаемый код") с дополнениями, актуальными для JavaScript. Возможно, в каких-то из этих советов вам даже удастся узнать "этого парня в зеркале". -[/warn] - +``` Предлагаю вашему вниманию советы мастеров древности, следование которым создаст дополнительные рабочие места для JavaScript-разработчиков. - + Если вы будете им следовать, то ваш код будет так сложен в поддержке, что у JavaScript'еров, которые придут после вас, даже простейшее изменение займет годы *оплачиваемого* труда! А сложные задачи оплачиваются хорошо, так что они, определённо, скажут вам "Спасибо". - + Более того, *внимательно* следуя этим правилам, вы сохраните и своё рабочее место, так как все будут бояться вашего кода и бежать от него... - -...Впрочем, всему своя мера. При написании такого кода он не должен *выглядеть* сложным в поддержке, код должен *быть* таковым. + +...Впрочем, всему своя мера. При написании такого кода он не должен *выглядеть* сложным в поддержке, код должен *быть* таковым. Явно кривой код может написать любой дурак. Это заметят, и вас уволят, а код будет переписан с нуля. Вы не можете такого допустить. Эти советы учитывают такую возможность. Да здравствует дзен. - - + [cut] ## Соглашения -- по настроению -[quote author="Сериал \"Симпсоны\", серия Helter Shelter"] +```quote author="Сериал \"Симпсоны\", серия Helter Shelter" Рабочий-чистильщик осматривает дом:
    "...Вот только жук у вас необычный...
    И чтобы с ним справиться, я должен жить как жук, стать жуком, думать как жук."
    (грызёт стол Симпсонов) -[/quote] +``` -Чтобы помешать другому программисту исправить ваш код, вы должны понять путь его мыслей. +Чтобы помешать другому программисту исправить ваш код, вы должны понять путь его мыслей. Представьте, перед ним -- ваш большой скрипт. И ему нужно поправить его. У него нет ни времени ни желания, чтобы читать его целиком, а тем более -- досконально разбирать. Он хотел бы по-быстрому найти нужное место, сделать изменение и убраться восвояси без появления побочных эффектов. @@ -39,15 +37,16 @@ Как затруднить задачу? Можно везде нарушать соглашения -- это помешает ему, но такое могут заметить, и код будет переписан. Как поступил бы ниндзя на вашем месте? -**...Правильно! Следуйте соглашениям "в общем", но иногда -- нарушайте их.** +**...Правильно! Следуйте соглашениям "в общем", но иногда -- нарушайте их.** -Тщательно разбросанные по коду нарушения соглашений с одной стороны не делают код явно плохим при первом взгляде, а с другой -- имеют в точности тот же, и даже лучший эффект, чем явное неследование им! +Тщательно разбросанные по коду нарушения соглашений с одной стороны не делают код явно плохим при первом взгляде, а с другой -- имеют в точности тот же, и даже лучший эффект, чем явное неследование им! ### Пример из jQuery -[warn header="jQuery / DOM"] +```warn header="jQuery / DOM" Этот пример требует знаний jQuery/DOM, если пока их у вас нет -- пропустите его, ничего страшного, но обязательно вернитесь к нему позже. Подобное стоит многих часов отладки. -[/warn] +``` + Во фреймворке jQuery есть метод [wrap](http://api.jquery.com/wrap/), который обёртывает один элемент вокруг другого: ```js @@ -74,11 +73,11 @@ div.append(''); Как правило, методы jQuery работают с теми элементами, которые им переданы. Но не здесь! -Внутри вызова `img.wrap(div)` происходит клонирование `div` и вокруг `img` оборачивается не сам `div`, а его клон. При этом исходная переменная `div` не меняется, в ней как был пустой `div`, так и остался. +Внутри вызова `img.wrap(div)` происходит клонирование `div` и вокруг `img` оборачивается не сам `div`, а его клон. При этом исходная переменная `div` не меняется, в ней как был пустой `div`, так и остался. В итоге, после вызова получается два независимых `div'а`: первый содержит `img` (этот неявный клон никуда не присвоен), а второй -- наш `span`. -Объяснения не очень понятны? Написано что-то странное? Это просто разум, привыкший, что соглашения уважаются, не допускает мысли, что вызов `wrap` -- неявно клонирует элемент. Ведь другие jQuery-методы, кроме `clone` этого не делают. +Объяснения не очень понятны? Написано что-то странное? Это просто разум, привыкший, что соглашения уважаются, не допускает мысли, что вызов `wrap` -- неявно клонирует элемент. Ведь другие jQuery-методы, кроме `clone` этого не делают. Как говорил [Учитель](https://ru.wikipedia.org/wiki/%D0%9A%D0%BE%D0%BD%D1%84%D1%83%D1%86%D0%B8%D0%B9): "В древности люди учились для того, чтобы совершенствовать себя. Нынче учатся для того, чтобы удивить других". @@ -99,13 +98,13 @@ i = i ? i < 0 ? Math.max(0, len + i) : i : 0; ## Именование -Существенную часть науки о создании неподдерживаемого кода занимает искусство выбора имён. +Существенную часть науки о создании неподдерживаемого кода занимает искусство выбора имён. ### Однобуквенные переменные Называйте переменные коротко: `a`, `b` или `c`. -В этом случае никто не сможет найти её, используя фунцию "Поиск" текстового редактора. +В этом случае никто не сможет найти её, используя фунцию "Поиск" текстового редактора. Более того, даже найдя -- никто не сможет "расшифровать" её и догадаться, что она означает. @@ -113,73 +112,73 @@ i = i ? i < 0 ? Math.max(0, len + i) : i : 0; В тех местах, где однобуквенные переменные общеприняты, например, в счетчике цикла -- ни в коем случае не используйте стандартные названия `i`, `j`, `k`. Где угодно, только не здесь! -Остановите свой взыскательный взгляд на чём-нибудь более экзотическом. Например, `x` или `y`. +Остановите свой взыскательный взгляд на чём-нибудь более экзотическом. Например, `x` или `y`. -Эффективность этого подхода особенно заметна, если тело цикла занимает одну-две страницы (чем длиннее -- тем лучше). +Эффективность этого подхода особенно заметна, если тело цикла занимает одну-две страницы (чем длиннее -- тем лучше). -В этом случае заметить, что переменная -- счетчик цикла, без пролистывания вверх, невозможно. +В этом случае заметить, что переменная -- счетчик цикла, без пролистывания вверх, невозможно. ### Русские слова и сокращения Если вам *приходится* использовать длинные, понятные имена переменных -- что поделать.. Но и здесь есть простор для творчества! -**Назовите переменные "калькой" с русского языка или как-то "улучшите" английское слово.** +**Назовите переменные "калькой" с русского языка или как-то "улучшите" английское слово.** -В одном месте напишите `var ssilka`, в другом `var ssylka`, в третьем `var link`, в четвёртом -- `var lnk`... Это действительно великолепно работает и очень креативно! +В одном месте напишите `var ssilka`, в другом `var ssylka`, в третьем `var link`, в четвёртом -- `var lnk`... Это действительно великолепно работает и очень креативно! Количество ошибок при поддержке такого кода увеличивается во много раз. ### Будьте абстрактны при выборе имени -[quote author="Лао-цзы"]Лучший кувшин лепят всю жизнь.
    +```quote author="Лао-цзы" +Лучший кувшин лепят всю жизнь.
    Высокая музыка неподвластна слуху.
    -Великий образ не имеет формы.[/quote] +Великий образ не имеет формы. +``` -При выборе имени старайтесь применить максимально абстрактное слово, например `obj`, `data`, `value`, `item`, `elem` и т.п. +При выборе имени старайтесь применить максимально абстрактное слово, например `obj`, `data`, `value`, `item`, `elem` и т.п. -
      -
    • **Идеальное имя для переменной: `data`.** Используйте это имя везде, где можно. В конце концов, каждая переменная содержит *данные*, не правда ли? +- **Идеальное имя для переменной: `data`.** Используйте это имя везде, где можно. В конце концов, каждая переменная содержит *данные*, не правда ли? -Но что делать, если имя `data` уже занято? Попробуйте `value`, оно не менее универсально. Ведь каждая переменная содержит *значение*. + Но что делать, если имя `data` уже занято? Попробуйте `value`, оно не менее универсально. Ведь каждая переменная содержит *значение*. -Занято и это? Есть и другой вариант. -
    • -
    • **Называйте переменную по типу данных, которые она хранит: `obj`, `num`, `arr`...** + Занято и это? Есть и другой вариант. +- **Называйте переменную по типу данных, которые она хранит: `obj`, `num`, `arr`...** -Насколько это усложнит разработку? Как ни странно, намного! + Насколько это усложнит разработку? Как ни странно, намного! -Казалось бы, название переменной содержит информацию, говорит о том, что в переменной -- число, объект или массив... С другой стороны, **когда непосвящённый будет разбирать этот код -- он с удивлением обнаружит, что информации нет!** + Казалось бы, название переменной содержит информацию, говорит о том, что в переменной -- число, объект или массив... С другой стороны, **когда непосвящённый будет разбирать этот код -- он с удивлением обнаружит, что информации нет!** -Ведь как раз тип легко понять, запустив отладчик и посмотрев, что внутри. Но в чём смысл этой переменной? Что за массив/объект/число в ней хранится? Без долгой медитации над кодом тут не обойтись! -
    • -
    • **Что делать, если и эти имена кончились? Просто добавьте цифру:** `item1, item2, elem5, data1`...
    • -
    + Ведь как раз тип легко понять, запустив отладчик и посмотрев, что внутри. Но в чём смысл этой переменной? Что за массив/объект/число в ней хранится? Без долгой медитации над кодом тут не обойтись! +- **Что делать, если и эти имена кончились? Просто добавьте цифру:** `item1, item2, elem5, data1`... ### Похожие имена -Только истинно внимательный программист достоин понять ваш код. Но как проверить, достоин ли читающий? +Только истинно внимательный программист достоин понять ваш код. Но как проверить, достоин ли читающий? **Один из способов -- использовать похожие имена переменных, например `data` и `date`.** Бегло прочитать такой код почти невозможно. А уж заметить опечатку и поправить её... Ммммм... Мы здесь надолго, время попить чайку. ### А.К.Р.О.Н.И.М -Используйте сокращения, чтобы сделать код короче. +Используйте сокращения, чтобы сделать код короче. Например `ie` (Inner Element), `mc` (Money Counter) и другие. Если вы обнаружите, что путаетесь в них сами -- героически страдайте, но не переписывайте код. Вы знали, на что шли. ### Хитрые синонимы -[quote author="Конфуций"]Очень трудно найти чёрную кошку в тёмной комнате, особенно когда её там нет.[/quote] +```quote author="Конфуций" +Очень трудно найти чёрную кошку в тёмной комнате, особенно когда её там нет. +``` -**Чтобы было не скучно -- используйте *похожие названия* для обозначения *одинаковых действий*.** +**Чтобы было не скучно -- используйте *похожие названия* для обозначения *одинаковых действий*.** -Например, если метод показывает что-то на экране -- начните его название с `display..` (скажем, `displayElement`), а в другом месте объявите аналогичный метод как `show..` (`showFrame`). +Например, если метод показывает что-то на экране -- начните его название с `display..` (скажем, `displayElement`), а в другом месте объявите аналогичный метод как `show..` (`showFrame`). -**Как бы намекните этим, что существует тонкое различие между способами показа в этих методах, хотя на самом деле его нет.** +**Как бы намекните этим, что существует тонкое различие между способами показа в этих методах, хотя на самом деле его нет.** -По возможности, договоритесь с членами своей команды. Если Вася в своих классах использует `display..`, то Валера -- обязательно `render..`, а Петя -- `paint..`. +По возможности, договоритесь с членами своей команды. Если Вася в своих классах использует `display..`, то Валера -- обязательно `render..`, а Петя -- `paint..`. -**...И напротив, если есть две функции с важными отличиями -- используйте одно и то же слово для их описания!** Например, с `print...` можно начать метод печати на принтере `printPage`, а также -- метод добавления текста на страницу `printText`. +**...И напротив, если есть две функции с важными отличиями -- используйте одно и то же слово для их описания!** Например, с `print...` можно начать метод печати на принтере `printPage`, а также -- метод добавления текста на страницу `printText`. А теперь, пусть читающий код думает: "Куда же выводит сообщение `printMessage`?". Особый шик -- добавить элемент неожиданности. Пусть `printMessage` выводит не туда, куда все, а в новое окно! @@ -187,15 +186,15 @@ i = i ? i < 0 ? Math.max(0, len + i) : i : 0; Ни в коем случае не поддавайтесь требованиям написать словарь терминов для проекта. Если же он уже есть -- не следуйте ему, а лучше проглотите и скажите, что так и былО! -Пусть читающий ваш код программист напрасно ищет различия в `helloUser` и `welcomeVisitor` и пытается понять, когда что использовать. Вы-то знаете, что на самом деле различий нет, но искать их можно о-очень долго. +Пусть читающий ваш код программист напрасно ищет различия в `helloUser` и `welcomeVisitor` и пытается понять, когда что использовать. Вы-то знаете, что на самом деле различий нет, но искать их можно о-очень долго. **Для обозначения посетителя в одном месте используйте `user`, а в другом `visitor`, в третьем -- просто `u`. Выбирайте одно имя или другое, в зависимости от функции и настроения.** -Это воплотит сразу два ключевых принципа ниндзя-дизайна -- *сокрытие информации* и *подмена понятий*! +Это воплотит сразу два ключевых принципа ниндзя-дизайна -- *сокрытие информации* и *подмена понятий*! ### Повторно используйте имена -По возможности, повторно используйте имена переменных, функций и свойств. Просто записывайте в них новые значения. +По возможности, повторно используйте имена переменных, функций и свойств. Просто записывайте в них новые значения. Добавляйте новое имя только если это абсолютно необходимо. @@ -217,7 +216,7 @@ function ninjaFunction(elem) { } ``` -Программист, пожелавший добавить действия с `elem` во вторую часть функции, будет удивлён. Лишь во время отладки, посмотрев весь код, он с удивлением обнаружит, что оказывается имел дело с клоном! +Программист, пожелавший добавить действия с `elem` во вторую часть функции, будет удивлён. Лишь во время отладки, посмотрев весь код, он с удивлением обнаружит, что оказывается имел дело с клоном! Регулярные встречи с этим приемом на практике говорят: защититься невозможно. Эффективно даже против опытного ниндзи. @@ -231,16 +230,16 @@ function ninjaFunction(elem) { ### Покажите вашу любовь к разработке -Пусть все видят, какими замечательными сущностями вы оперируете! Имена `superElement`, `megaFrame` и `niceItem` при благоприятном положении звёзд могут привести к просветлению читающего. +Пусть все видят, какими замечательными сущностями вы оперируете! Имена `superElement`, `megaFrame` и `niceItem` при благоприятном положении звёзд могут привести к просветлению читающего. Действительно, с одной стороны, кое-что написано: `super..`, `mega..`, `nice..` С другой -- это не несёт никакой конкретики. Читающий может решить поискать в этом глубинный смысл и замедитировать на часок-другой оплаченного рабочего времени. ### Перекрывайте внешние переменные -[quote author="Гуань Инь-цзы"] +```quote author="Гуань Инь-цзы" Находясь на свету, нельзя ничего увидеть в темноте.
    Пребывая же в темноте, увидишь все, что находится на свету. -[/quote] +``` Почему бы не использовать одинаковые переменные внутри и снаружи функции? Это просто и не требует придумывать новых имён. @@ -261,11 +260,11 @@ function render() { ## Мощные функции! -Не ограничивайте действия функции тем, что написано в её названии. Будьте шире. +Не ограничивайте действия функции тем, что написано в её названии. Будьте шире. Например, функция `validateEmail(email)` может, кроме проверки e-mail на правильность, выводить сообщение об ошибке и просить заново ввести e-mail. -**Выберите хотя бы пару дополнительных действий, кроме основного назначения функции.** +**Выберите хотя бы пару дополнительных действий, кроме основного назначения функции.** Главное -- они должны быть неочевидны из названия функции. Истинный ниндзя-девелопер сделает так, что они будут неочевидны и из кода тоже. @@ -273,12 +272,11 @@ function render() { Представьте, что другому разработчику нужно только проверить адрес, а сообщение -- не выводить. Ваша функция `validateEmail(email)`, которая делает и то и другое, ему не подойдёт. Работодатель будет вынужден оплатить создание новой. - ## Внимание.. Сюр-при-из! -Есть функции, название которых говорит о том, что они ничего не меняют. Например, `isReady`, `checkPermission`, `findTags`... Предполагается, что при вызове они произведут некие вычисления, или найдут и возвратят полезные данные, но при этом их не изменят. В трактатах это называется "отсутствие сторонних эффектов". +Есть функции, название которых говорит о том, что они ничего не меняют. Например, `isReady`, `checkPermission`, `findTags`... Предполагается, что при вызове они произведут некие вычисления, или найдут и возвратят полезные данные, но при этом их не изменят. В трактатах это называется "отсутствие сторонних эффектов". -**По-настоящему красивый приём -- делать в таких функциях что-нибудь полезное, заодно с процессом проверки. Что именно -- совершенно неважно.** +**По-настоящему красивый приём -- делать в таких функциях что-нибудь полезное, заодно с процессом проверки. Что именно -- совершенно неважно.** Удивление и ошеломление, которое возникнет у вашего коллеги, когда он увидит, что функция с названием на `is..`, `check..` или `find...` что-то меняет -- несомненно, расширит его границы разумного! @@ -290,12 +288,10 @@ function render() { ## Заключение -Все советы выше пришли из реального кода... И в том числе от разработчиков с большим опытом. +Все советы выше пришли из реального кода... И в том числе от разработчиков с большим опытом. Возможно, даже больше вашего, так что не судите опрометчиво ;) -
      -
    • Следуйте нескольким из них -- и ваш код станет полон сюрпризов.
    • -
    • Следуйте многим -- и ваш код станет истинно вашим, никто не захочет изменять его.
    • -
    • Следуйте всем -- и ваш код станет ценным уроком для молодых разработчиков, ищущих просветления.
    • -
    \ No newline at end of file +- Следуйте нескольким из них -- и ваш код станет полон сюрпризов. +- Следуйте многим -- и ваш код станет истинно вашим, никто не захочет изменять его. +- Следуйте всем -- и ваш код станет ценным уроком для молодых разработчиков, ищущих просветления. diff --git a/1-js/3-writing-js/4-testing/1-pow-nan-spec/task.md b/1-js/3-writing-js/4-testing/1-pow-nan-spec/task.md index ea90d617..ce9a16b0 100644 --- a/1-js/3-writing-js/4-testing/1-pow-nan-spec/task.md +++ b/1-js/3-writing-js/4-testing/1-pow-nan-spec/task.md @@ -1,12 +1,14 @@ -# Сделать pow по спецификации +importance: 5 + +--- -[importance 5] +# Сделать pow по спецификации Исправьте код функции `pow`, чтобы тесты проходили. Для этого ниже в задаче вы найдёте ссылку на песочницу. -Она содержит HTML с тестами. Обратите внимание, что HTML-страница в ней короче той, что обсуждалась в статье [](/testing). Это потому что библиотеки Chai, Mocha и Sinon объединены в один файл: +Она содержит HTML с тестами. Обратите внимание, что HTML-страница в ней короче той, что обсуждалась в статье . Это потому что библиотеки Chai, Mocha и Sinon объединены в один файл: ```html diff --git a/1-js/3-writing-js/4-testing/2-pow-test-0/solution.md b/1-js/3-writing-js/4-testing/2-pow-test-0/solution.md index 39eae1e7..3392d3d1 100644 --- a/1-js/3-writing-js/4-testing/2-pow-test-0/solution.md +++ b/1-js/3-writing-js/4-testing/2-pow-test-0/solution.md @@ -6,7 +6,7 @@ it("любое число в степени 0 равно 1", function() { }); ``` -Конечно, желательно проверить на нескольких числах. +Конечно, желательно проверить на нескольких числах. Поэтому лучше будет создать блок `describe`, аналогичный тому, что мы делали для произвольных чисел: @@ -28,8 +28,7 @@ describe("любое число, кроме нуля, в степени 0 рав И не забудем добавить отдельный тест для нуля: -```js -//+ no-beautify +```js no-beautify ... it("ноль в нулевой степени даёт NaN", function() { assert( isNaN(pow(0, 0)), "0 в степени 0 не NaN"); diff --git a/1-js/3-writing-js/4-testing/2-pow-test-0/task.md b/1-js/3-writing-js/4-testing/2-pow-test-0/task.md index 28291a3a..ab926b64 100644 --- a/1-js/3-writing-js/4-testing/2-pow-test-0/task.md +++ b/1-js/3-writing-js/4-testing/2-pow-test-0/task.md @@ -1,7 +1,9 @@ -# Добавьте тест к задаче +importance: 5 + +--- -[importance 5] +# Добавьте тест к задаче Добавьте к [предыдущей задаче](/task/pow-nan-spec) тесты, которые будут проверять, что любое число, кроме нуля, в нулевой степени равно `1`, а ноль в нулевой степени даёт `NaN` (это математически корректно, результат 00 не определён). -При необходимости, исправьте реализацию, чтобы тесты проходили без ошибок. \ No newline at end of file +При необходимости, исправьте саму функцию `pow()`, чтобы тесты проходили без ошибок. \ No newline at end of file diff --git a/1-js/3-writing-js/4-testing/3-pow-test-wrong/solution.md b/1-js/3-writing-js/4-testing/3-pow-test-wrong/solution.md index 97b66bbc..31f193ac 100644 --- a/1-js/3-writing-js/4-testing/3-pow-test-wrong/solution.md +++ b/1-js/3-writing-js/4-testing/3-pow-test-wrong/solution.md @@ -1,6 +1,6 @@ Этот тест демонстрирует один из соблазнов, которые ожидают начинающего автора тестов. -Вместо того, чтобы написать три различных теста, он изложил их в виде одного потока вычислений, с несколькими `assert`. +Вместо того, чтобы написать три различных теста, он изложил их в виде одного потока вычислений, с несколькими `assert`. Иногда так написать легче и проще, однако при ошибке в тесте гораздо менее очевидно, что же пошло не так. diff --git a/1-js/3-writing-js/4-testing/3-pow-test-wrong/task.md b/1-js/3-writing-js/4-testing/3-pow-test-wrong/task.md index 4d656c2f..0f54f782 100644 --- a/1-js/3-writing-js/4-testing/3-pow-test-wrong/task.md +++ b/1-js/3-writing-js/4-testing/3-pow-test-wrong/task.md @@ -1,6 +1,8 @@ -# Что не так в тесте? +importance: 5 -[importance 5] +--- + +# Что не так в тесте? Что не так в этом тесте функции `pow`? @@ -11,12 +13,12 @@ it("Возводит x в степень n", function() { var result = x; assert.equal(pow(x, 1), result); - var result *= x; + result *= x; assert.equal(pow(x, 2), result); - var result *= x; + result *= x; assert.equal(pow(x, 3), result); }); ``` -P.S. Синтаксически он верен и работает, но спроектирован неправильно. \ No newline at end of file +P.S. Синтаксически он верен и работает, но спроектирован неправильно. diff --git a/1-js/3-writing-js/4-testing/article.md b/1-js/3-writing-js/4-testing/article.md index 6c04f26a..9958ceee 100644 --- a/1-js/3-writing-js/4-testing/article.md +++ b/1-js/3-writing-js/4-testing/article.md @@ -6,36 +6,35 @@ ## Зачем нужны тесты? -При написании функции мы обычно представляем, что она должна делать, какое значение -- на каких аргументах выдавать. +При написании функции мы обычно представляем, что она должна делать, какое значение на каких аргументах выдавать. -В процессе разработки мы, время от времени, проверяем, правильно ли работает функция. Самый простой способ проверить -- это запустить её, например, в консоли, и посмотреть результат. +В процессе разработки мы время от времени проверяем, правильно ли работает функция. Самый простой способ проверить -- это запустить её, например в консоли, и посмотреть результат. -Если что-то не так -- поправить, опять запустить -- посмотреть результат... И так -- "до победного конца". +Если что-то не так, поправить, опять запустить -- посмотреть результат... И так "до победного конца". Но такие ручные запуски -- очень несовершенное средство проверки. **Когда проверяешь работу кода вручную -- легко его "недотестировать".** -Например, пишем функцию `f`. Написали, тестируем с разными аргументами. Вызов функции `f(a)` -- работает, а вот `f(b)` -- не работает. Поправили код -- стало работать `f(b)`, вроде закончили. Но при этом забыли заново протестировать `f(a)` -- упс, вот и возможная ошибка в коде. +Например, пишем функцию `f`. Написали, тестируем с разными аргументами. Вызов функции `f(a)` работает, а вот `f(b)` не работает. Поправили код -- стало работать `f(b)`, вроде закончили. Но при этом забыли заново протестировать `f(a)` -- упс, вот и возможная ошибка в коде. **Автоматизированное тестирование -- это когда тесты написаны отдельно от кода, и можно в любой момент запустить их и проверить все важные случаи использования.** ## BDD -- поведенческие тесты кода -Мы рассмотрим методику тестирования, которая входит в [BDD](http://en.wikipedia.org/wiki/Behavior-driven_development) -- Behavior Driven Development. Подход BDD давно и с успехом используется во многих проектах. +Мы рассмотрим методику тестирования, которая входит в [BDD](http://en.wikipedia.org/wiki/Behavior-driven_development) -- Behavior Driven Development. Подход BDD давно и с успехом используется во многих проектах. -BDD -- это не просто тесты. Это гораздо больше. +BDD -- это не просто тесты. Это гораздо больше. -**Тесты BDD -- это три в одном: И тесты И документация И примеры использования одновременно.** +**Тесты BDD -- это три в одном: И тесты, И документация, И примеры использования.** Впрочем, хватит слов. Рассмотрим примеры. -## Разработка pow: спецификация +## Разработка pow: спецификация Допустим, мы хотим разработать функцию `pow(x, n)`, которая возводит `x` в целую степень `n`, для простоты `n≥0`. - -Ещё до разработки мы можем представить себе, что эта функция будет делать и описать это по методике BDD. +Ещё до разработки мы можем представить себе, что эта функция будет делать, и описать это по методике BDD. Это описание называется *спецификация* (или, как говорят в обиходе, "спека") и выглядит так: @@ -50,66 +49,60 @@ describe("pow", function() { ``` У спецификации есть три основных строительных блока, которые вы видите в примере выше: -
    -
    `describe(название, function() { ... })`
    -
    Задаёт, что именно мы описываем, используется для группировки "рабочих лошадок" -- блоков `it`. В данном случае мы описываем функцию `pow`.
    -
    `it(название, function() { ... })`
    -
    В названии блока `it` *человеческим языком* описывается, что должна делать функция, далее следует *тест*, который проверяет это.
    -
    `assert.equal(value1, value2)`
    -
    Код внутри `it`, если реализация верна, должен выполняться без ошибок. -Различные функции вида `assert.*` используются, чтобы проверить, делает ли `pow` то, что задумано. Пока что нас интересует только одна из них -- `assert.equal`, она сравнивает свой первый аргумент со вторым и выдаёт ошибку в случае, когда они не равны. В данном случае она проверяет, что результат `pow(2, 3)` равен `8`. +`describe(название, function() { ... })` +: Задаёт, что именно мы описываем, используется для группировки "рабочих лошадок" -- блоков `it`. В данном случае мы описываем функцию `pow`. + +`it(название, function() { ... })` +: В названии блока `it` *человеческим языком* описывается, что должна делать функция, далее следует *тест*, который проверяет это. +`assert.equal(value1, value2)` +: Код внутри `it`, если реализация верна, должен выполняться без ошибок. -Есть и другие виды сравнений и проверок, которые мы увидим далее.
    -
    + Различные функции вида `assert.*` используются, чтобы проверить, делает ли `pow` то, что задумано. Пока что нас интересует только одна из них -- `assert.equal`, она сравнивает свой первый аргумент со вторым и выдаёт ошибку в случае, когда они не равны. В данном случае она проверяет, что результат `pow(2, 3)` равен `8`. + Есть и другие виды сравнений и проверок, которые мы увидим далее. ## Поток разработки Как правило, поток разработки таков: -
      -
    1. Пишется спецификация, которая описывает самый базовый функционал.
    2. -
    3. Делается начальная реализация.
    4. -
    5. Для проверки соответствия спецификации мы задействуем одновременно фреймворк, в нашем случае [Mocha](http://mochajs.org/) вместе со спецификацией и реализацией. Фреймворк запускает все тесты `it` и выводит ошибки, если они возникнут. При ошибках вносятся исправления.
    6. -
    7. Спецификация расширяется, в неё добавляются возможности, которые пока, возможно, не поддерживаются реализацией.
    8. -
    9. Идём на пункт 3, делаем реализацию, и так далее, до победного конца.
    10. -
    -Разработка ведётся *итеративно*, один проход за другим, пока спецификация и реализация не будут завершены. +1. Пишется спецификация, которая описывает самый базовый функционал. +2. Делается начальная реализация. +3. Для проверки соответствия спецификации мы задействуем фреймворк (в нашем случае [Mocha](http://mochajs.org/)). Фреймворк запускает все тесты `it` и выводит ошибки, если они возникнут. При ошибках вносятся исправления. +4. Спецификация расширяется, в неё добавляются возможности, которые пока, возможно, не поддерживаются реализацией. +5. Идём на пункт 2, делаем реализацию. И так "до победного конца". + +Разработка ведётся *итеративно*: один проход за другим, пока спецификация и реализация не будут завершены. В нашем случае первый шаг уже завершён, начальная спецификация готова, хорошо бы приступить к реализации. Но перед этим проведём "нулевой" запуск спецификации, просто чтобы увидеть, что уже в таком виде, даже без реализации -- тесты работают. ## Пример в действии -Для запуска тестов нужны соответствующие JavaScript-библиотеки. +Для запуска тестов нужны соответствующие JavaScript-библиотеки. Мы будем использовать: -
      -
    • [Mocha](http://mochajs.org/) -- эта библиотека содержит общие функции для тестирования, включая `describe` и `it`.
    • -
    • [Chai](http://chaijs.com) -- библиотека поддерживает разнообразные функции для проверок. Есть разные "стили" проверки результатов, с которыми мы познакомимся позже, на текущий момент мы будем использовать лишь `assert.equal`.
    • -
    • [Sinon](http://sinonjs.org/) -- для эмуляции и хитрой подмены функций "заглушками", понадобится позднее.
    • -
    + +- [Mocha](http://mochajs.org/) -- эта библиотека содержит общие функции для тестирования, включая `describe` и `it`. +- [Chai](http://chaijs.com) -- библиотека поддерживает разнообразные функции для проверок. Есть разные "стили" проверки результатов, с которыми мы познакомимся позже, на текущий момент мы будем использовать лишь `assert.equal`. +- [Sinon](http://sinonjs.org/) -- для эмуляции и хитрой подмены функций "заглушками", понадобится позднее. Эти библиотеки позволяют тестировать JS не только в браузере, но и на сервере Node.JS. Здесь мы рассмотрим браузерный вариант, серверный использует те же функции. Пример HTML-страницы для тестов: -```html - -``` +[html src="http://github.com/index.html"] Эту страницу можно условно разделить на четыре части: -
      -
    1. Блок `` -- в нём мы подключаем библиотеки и стили для тестирования, нашего кода там нет.
    2. -
    3. Блок ` -``` +1. Переопределение переменной, у которой такое же имя, как и `id` элемента, приведет к ошибке: -А если сделать через `var`, то всё будет хорошо. + ```html run +
      ...
      + + ``` -Это была реклама того, что надо везде ставить `var`. + А если сделать через `var`, то всё будет хорошо. -
    4. -
    5. Ошибка при рекурсии через функцию-свойство `window`. Следующий код "умрет" в IE8-: + Это была реклама того, что надо везде ставить `var`. +2. Ошибка при рекурсии через функцию-свойство `window`. Следующий код "умрет" в IE8-: -```html - - -``` + ```html run height=0 + + ``` -**Этот пример выдаст ошибку только в настоящем IE8!** Не IE9 в режиме эмуляции. Вообще, режим эмуляции позволяет отлавливать где-то 95% несовместимостей и проблем, а для оставшихся 5% вам нужен будет настоящий IE8 в виртуальной машине. -
    6. -
    -[/warn] + Проблема здесь возникает из-за того, что функция напрямую присвоена в `window.recurse = ...`. Ее не будет при обычном объявлении функции. + **Этот пример выдаст ошибку только в настоящем IE8!** Не IE9 в режиме эмуляции. Вообще, режим эмуляции позволяет отлавливать где-то 95% несовместимостей и проблем, а для оставшихся 5% вам нужен будет настоящий IE8 в виртуальной машине. +```` ## Итого В результате инициализации, к началу выполнения кода: -
      -
    1. Функции, объявленные как `Function Declaration`, создаются полностью и готовы к использованию.
    2. -
    3. Переменные объявлены, но равны `undefined`. Присваивания выполнятся позже, когда выполнение дойдет до них.
    4. -
    +1. Функции, объявленные как `Function Declaration`, создаются полностью и готовы к использованию. +2. Переменные объявлены, но равны `undefined`. Присваивания выполнятся позже, когда выполнение дойдет до них. diff --git a/1-js/5-functions-closures/2-closures/1-say-phrase-first/solution.md b/1-js/5-functions-closures/2-closures/1-say-phrase-first/solution.md index ab6567e4..e630872f 100644 --- a/1-js/5-functions-closures/2-closures/1-say-phrase-first/solution.md +++ b/1-js/5-functions-closures/2-closures/1-say-phrase-first/solution.md @@ -1,7 +1,6 @@ Ошибки не будет, выведет `"Вася, undefined"`. -```js -//+ run +```js run *!* say('Вася'); // Что выведет? Не будет ли ошибки? */!* diff --git a/1-js/5-functions-closures/2-closures/1-say-phrase-first/task.md b/1-js/5-functions-closures/2-closures/1-say-phrase-first/task.md index fe548c0c..3253a9b0 100644 --- a/1-js/5-functions-closures/2-closures/1-say-phrase-first/task.md +++ b/1-js/5-functions-closures/2-closures/1-say-phrase-first/task.md @@ -1,8 +1,10 @@ -# Что выведет say в начале кода? +importance: 5 + +--- -[importance 5] +# Что выведет say в начале кода? -Что будет, если вызов `sayHi('Вася');` стоит в самом-самом начале, в первой строке кода? +Что будет, если вызов `say('Вася');` стоит в самом-самом начале, в первой строке кода? ```js *!* diff --git a/1-js/5-functions-closures/2-closures/2-which-value-is-modified/solution.md b/1-js/5-functions-closures/2-closures/2-which-value-is-modified/solution.md index eed571f3..277ce234 100644 --- a/1-js/5-functions-closures/2-closures/2-which-value-is-modified/solution.md +++ b/1-js/5-functions-closures/2-closures/2-which-value-is-modified/solution.md @@ -4,6 +4,6 @@ **Внешняя переменная не изменится.** -P.S. Если `var` нет, то в функции переменная не будет найдена. Интерпретатор обратится за ней в `window` и изменит её там. +P.S. Если `var` нет, то в функции переменная не будет найдена. Интерпретатор обратится за ней в `window` и изменит её там. **Так что без `var` результат будет также `true`, но внешняя переменная изменится.** diff --git a/1-js/5-functions-closures/2-closures/2-which-value-is-modified/task.md b/1-js/5-functions-closures/2-closures/2-which-value-is-modified/task.md index b9f72a30..117f1a21 100644 --- a/1-js/5-functions-closures/2-closures/2-which-value-is-modified/task.md +++ b/1-js/5-functions-closures/2-closures/2-which-value-is-modified/task.md @@ -1,6 +1,8 @@ -# В какую переменную будет присвоено значение? +importance: 5 + +--- -[importance 5] +# В какую переменную будет присвоено значение? Каков будет результат выполнения этого кода? diff --git a/1-js/5-functions-closures/2-closures/3-var-window/solution.md b/1-js/5-functions-closures/2-closures/3-var-window/solution.md index e1609417..7ea67256 100644 --- a/1-js/5-functions-closures/2-closures/3-var-window/solution.md +++ b/1-js/5-functions-closures/2-closures/3-var-window/solution.md @@ -1,7 +1,6 @@ Результатом будет `undefined`, затем `5`. -```js -//+ run +```js run function test() { alert( window ); diff --git a/1-js/5-functions-closures/2-closures/3-var-window/task.md b/1-js/5-functions-closures/2-closures/3-var-window/task.md index 8c0e1c7d..58e339af 100644 --- a/1-js/5-functions-closures/2-closures/3-var-window/task.md +++ b/1-js/5-functions-closures/2-closures/3-var-window/task.md @@ -1,6 +1,8 @@ -# var window +importance: 5 + +--- -[importance 5] +# var window Каков будет результат выполнения этого кода? Почему? diff --git a/1-js/5-functions-closures/2-closures/4-call-inplace/solution.md b/1-js/5-functions-closures/2-closures/4-call-inplace/solution.md index 4f08292e..2aa6c4c9 100644 --- a/1-js/5-functions-closures/2-closures/4-call-inplace/solution.md +++ b/1-js/5-functions-closures/2-closures/4-call-inplace/solution.md @@ -1,7 +1,6 @@ Результат - **ошибка**. Попробуйте: -```js -//+ run no-beautify +```js run no-beautify var a = 5 (function() { @@ -9,12 +8,11 @@ var a = 5 })() ``` -Дело в том, что после `var a = 5` нет точки с запятой. +Дело в том, что после `var a = 5` нет точки с запятой. JavaScript воспринимает этот код как если бы перевода строки не было: -```js -//+ run no-beautify +```js run no-beautify var a = 5(function() { alert(a) })() @@ -24,8 +22,7 @@ var a = 5(function() { Если точку с запятой поставить, все будет хорошо: -```js -//+ run no-beautify +```js run no-beautify var a = 5; (function() { diff --git a/1-js/5-functions-closures/2-closures/4-call-inplace/task.md b/1-js/5-functions-closures/2-closures/4-call-inplace/task.md index aa4c1b4a..a00d195d 100644 --- a/1-js/5-functions-closures/2-closures/4-call-inplace/task.md +++ b/1-js/5-functions-closures/2-closures/4-call-inplace/task.md @@ -1,11 +1,12 @@ -# Вызов "на месте" +importance: 4 + +--- -[importance 4] +# Вызов "на месте" Каков будет результат выполнения кода? Почему? -```js -//+ no-beautify +```js no-beautify var a = 5 (function() { diff --git a/1-js/5-functions-closures/2-closures/5-access-outer-variable/solution.md b/1-js/5-functions-closures/2-closures/5-access-outer-variable/solution.md index 4e0a619e..2b6c96d6 100644 --- a/1-js/5-functions-closures/2-closures/5-access-outer-variable/solution.md +++ b/1-js/5-functions-closures/2-closures/5-access-outer-variable/solution.md @@ -1,3 +1,3 @@ Нет, нельзя. -Локальная переменная полностью перекрывает внешнюю. +Локальная переменная полностью перекрывает внешнюю. diff --git a/1-js/5-functions-closures/2-closures/5-access-outer-variable/task.md b/1-js/5-functions-closures/2-closures/5-access-outer-variable/task.md index 13e61904..5e143ead 100644 --- a/1-js/5-functions-closures/2-closures/5-access-outer-variable/task.md +++ b/1-js/5-functions-closures/2-closures/5-access-outer-variable/task.md @@ -1,6 +1,8 @@ -# Перекрытие переменной +importance: 4 + +--- -[importance 4] +# Перекрытие переменной Если во внутренней функции есть своя переменная с именем `currentCount` -- можно ли в ней получить `currentCount` из внешней функции? diff --git a/1-js/5-functions-closures/2-closures/6-counter-window-variable/solution.md b/1-js/5-functions-closures/2-closures/6-counter-window-variable/solution.md index b9b3771c..7fb133ba 100644 --- a/1-js/5-functions-closures/2-closures/6-counter-window-variable/solution.md +++ b/1-js/5-functions-closures/2-closures/6-counter-window-variable/solution.md @@ -4,8 +4,7 @@ В результате все счётчики будут разделять единое, глобальное текущее значение. -```js -//+ run +```js run var currentCount = 1; function makeCounter() { diff --git a/1-js/5-functions-closures/2-closures/6-counter-window-variable/task.md b/1-js/5-functions-closures/2-closures/6-counter-window-variable/task.md index 48068df9..e321d005 100644 --- a/1-js/5-functions-closures/2-closures/6-counter-window-variable/task.md +++ b/1-js/5-functions-closures/2-closures/6-counter-window-variable/task.md @@ -1,6 +1,8 @@ -# Глобальный счётчик +importance: 5 + +--- -[importance 5] +# Глобальный счётчик Что выведут эти вызовы, если переменная `currentCount` находится вне `makeCounter`? diff --git a/1-js/5-functions-closures/2-closures/article.md b/1-js/5-functions-closures/2-closures/article.md index d4301d75..fc860a74 100644 --- a/1-js/5-functions-closures/2-closures/article.md +++ b/1-js/5-functions-closures/2-closures/article.md @@ -1,7 +1,9 @@ # Замыкания, функции изнутри В этой главе мы продолжим рассматривать, как работают переменные, и, как следствие, познакомимся с замыканиями. От глобального объекта мы переходим к работе внутри функций. + [cut] + ## Лексическое окружение Все переменные внутри функции -- это свойства специального внутреннего объекта `LexicalEnvironment`, который создаётся при её запуске. @@ -26,56 +28,50 @@ sayHi('Вася'); ``` При вызове функции: -
      -
    1. До выполнения первой строчки её кода, на стадии инициализации, интерпретатор создает пустой объект `LexicalEnvironment` и заполняет его. -В данном случае туда попадает аргумент `name` и единственная переменная `phrase`: +1. До выполнения первой строчки её кода, на стадии инициализации, интерпретатор создает пустой объект `LexicalEnvironment` и заполняет его. -```js -function sayHi(name) { -*!* - // LexicalEnvironment = { name: 'Вася', phrase: undefined } -*/!* - var phrase = "Привет, " + name; - alert( phrase ); -} + В данном случае туда попадает аргумент `name` и единственная переменная `phrase`: -sayHi('Вася'); -``` + ```js + function sayHi(name) { + *!* + // LexicalEnvironment = { name: 'Вася', phrase: undefined } + */!* + var phrase = "Привет, " + name; + alert( phrase ); + } -
    2. -
    3. Функция выполняется. + sayHi('Вася'); + ``` +2. Функция выполняется. -Во время выполнения происходит присвоение локальной переменной `phrase`, то есть, другими словами, присвоение свойству `LexicalEnvironment.phrase` нового значения: + Во время выполнения происходит присвоение локальной переменной `phrase`, то есть, другими словами, присвоение свойству `LexicalEnvironment.phrase` нового значения: -```js -function sayHi(name) { - // LexicalEnvironment = { name: 'Вася', phrase: undefined } - var phrase = "Привет, " + name; + ```js + function sayHi(name) { + // LexicalEnvironment = { name: 'Вася', phrase: undefined } + var phrase = "Привет, " + name; -*!* - // LexicalEnvironment = { name: 'Вася', phrase: 'Привет, Вася'} -*/!* - alert( phrase ); -} - -sayHi('Вася'); -``` + *!* + // LexicalEnvironment = { name: 'Вася', phrase: 'Привет, Вася'} + */!* + alert( phrase ); + } -
    4. -
    5. В конце выполнения функции объект с переменными обычно выбрасывается и память очищается. В примерах выше так и происходит. Через некоторое время мы рассмотрим более сложные ситуации, при которых объект с переменными сохраняется и после завершения функции.
    6. -
    + sayHi('Вася'); + ``` +3. В конце выполнения функции объект с переменными обычно выбрасывается и память очищается. В примерах выше так и происходит. Через некоторое время мы рассмотрим более сложные ситуации, при которых объект с переменными сохраняется и после завершения функции. -[smart header="Тонкости спецификации"] -Если почитать спецификацию ECMA-262, то мы увидим, что речь идёт о двух объектах: `VariableEnvironment` и `LexicalEnvironment`. +```smart header="Тонкости спецификации" +Если почитать спецификацию ECMA-262, то мы увидим, что речь идёт о двух объектах: `VariableEnvironment` и `LexicalEnvironment`. Но там же замечено, что в реализациях эти два объекта могут быть объединены. Так что мы избегаем лишних деталей и используем везде термин `LexicalEnvironment`, это достаточно точно позволяет описать происходящее. Более формальное описание находится в спецификации ECMA-262, секции 10.2-10.5 и 13. -[/smart] - +``` -## Доступ ко внешним переменным +## Доступ ко внешним переменным Из функции мы можем обратиться не только к локальной переменной, но и к внешней: @@ -94,25 +90,23 @@ function sayHi() { **При создании функция получает скрытое свойство `[[Scope]]`, которое ссылается на лексическое окружение, в котором она была создана.** В примере выше таким окружением является `window`, так что создаётся свойство: -```js -//+ no-beautify +```js no-beautify sayHi.[[Scope]] = window ``` Это свойство никогда не меняется. Оно всюду следует за функцией, привязывая её, таким образом, к месту своего рождения. -При запуске функции её объект переменных `LexicalEnvironment` получает ссылку на "внешнее лексическое окружение" со значением из `[[Scope]]`. +При запуске функции её объект переменных `LexicalEnvironment` получает ссылку на "внешнее лексическое окружение" со значением из `[[Scope]]`. Если переменная не найдена в функции -- она будет искаться снаружи. Именно благодаря этой механике в примере выше `alert(userName)` выводит внешнюю переменную. На уровне кода это выглядит как поиск во внешней области видимости, вне функции. Если обобщить: -
      -
    • Каждая функция при создании получает ссылку `[[Scope]]` на объект с переменными, в контексте которого была создана.
    • -
    • При запуске функции создаётся новый объект с переменными `LexicalEnvironment`. Он получает ссылку на внешний объект переменных из `[[Scope]]`.
    • -
    • При поиске переменных он осуществляется сначала в текущем объекте переменных, а потом -- по этой ссылке.
    • -
    + +- Каждая функция при создании получает ссылку `[[Scope]]` на объект с переменными, в контексте которого была создана. +- При запуске функции создаётся новый объект с переменными `LexicalEnvironment`. Он получает ссылку на внешний объект переменных из `[[Scope]]`. +- При поиске переменных он осуществляется сначала в текущем объекте переменных, а потом -- по этой ссылке. Выглядит настолько просто, что непонятно -- зачем вообще говорить об этом `[[Scope]]`, об объектах переменных. Сказали бы: "Функция читает переменные снаружи" -- и всё. Но знание этих деталей позволит нам легко объяснить и понять более сложные ситуации, с которыми мы столкнёмся далее. @@ -122,23 +116,21 @@ sayHi.[[Scope]] = window Например, в коде ниже функция `sayHi` берёт `phrase` из внешней области: -```js -//+ run no-beautify - +```js run no-beautify var phrase = 'Привет'; -function say(name) { +function sayHi(name) { alert(phrase + ', ' + name); } *!* -say('Вася'); // Привет, Вася (*) +sayHi('Вася'); // Привет, Вася (*) */!* phrase = 'Пока'; *!* -say('Вася'); // Пока, Вася (**) +sayHi('Вася'); // Пока, Вася (**) */!* ``` @@ -148,12 +140,11 @@ say('Вася'); // Пока, Вася (**) ## Вложенные функции -Внутри функции можно объявлять не только локальные переменные, но и другие функции. +Внутри функции можно объявлять не только локальные переменные, но и другие функции. К примеру, вложенная функция может помочь лучше организовать код: -```js -//+ run +```js run function sayHiBye(firstName, lastName) { alert( "Привет, " + getFullName() ); @@ -161,8 +152,8 @@ function sayHiBye(firstName, lastName) { *!* function getFullName() { - return firstName + " " + lastName; - } + return firstName + " " + lastName; + } */!* } @@ -174,8 +165,7 @@ sayHiBye("Вася", "Пупкин"); // Привет, Вася Пупкин ; Вложенные функции получают `[[Scope]]` так же, как и глобальные. В нашем случае: -```js -//+ no-beautify +```js no-beautify getFullName.[[Scope]] = объект переменных текущего запуска sayHiBye ``` @@ -183,8 +173,7 @@ getFullName.[[Scope]] = объект переменных текущего за Заметим, что если переменная не найдена во внешнем объекте переменных, то она ищется в ещё более внешнем (через `[[Scope]]` внешней функции), то есть, такой пример тоже будет работать: -```js -//+ run +```js run var phrase = 'Привет'; function say() { @@ -195,6 +184,8 @@ function say() { go(); } + +say(); ``` ## Возврат функции @@ -207,8 +198,7 @@ function say() { В примере ниже `makeCounter` создает такую функцию: -```js -//+ run +```js run function makeCounter() { *!* var currentCount = 1; @@ -222,7 +212,7 @@ function makeCounter() { var counter = makeCounter(); // (*) // каждый вызов увеличивает счётчик и возвращает результат -alert( counter() ); // 1 +alert( counter() ); // 1 alert( counter() ); // 2 alert( counter() ); // 3 @@ -237,40 +227,36 @@ alert( counter2() ); // 1 Если подробнее описать происходящее: -
      -
    1. В строке `(*)` запускается `makeCounter()`. При этом создаётся `LexicalEnvironment` для переменных текущего вызова. В функции есть одна переменная `var currentCount`, которая станет свойством этого объекта. Она изначально инициализуется в `undefined`, затем, в процессе выполнения, получит значение `1`: +1. В строке `(*)` запускается `makeCounter()`. При этом создаётся `LexicalEnvironment` для переменных текущего вызова. В функции есть одна переменная `var currentCount`, которая станет свойством этого объекта. Она изначально инициализуется в `undefined`, затем, в процессе выполнения, получит значение `1`: -```js -function makeCounter() { -*!* - // LexicalEnvironment = { currentCount: undefined } -*/!* + ```js + function makeCounter() { + *!* + // LexicalEnvironment = { currentCount: undefined } + */!* - var currentCount = 1; + var currentCount = 1; -*!* - // LexicalEnvironment = { currentCount: 1 } -*/!* + *!* + // LexicalEnvironment = { currentCount: 1 } + */!* - return function() { // [[Scope]] -> LexicalEnvironment (**) - return currentCount++; - }; -} - -var counter = makeCounter(); // (*) -``` + return function() { // [[Scope]] -> LexicalEnvironment (**) + return currentCount++; + }; + } -
    2. -
    3. В процессе выполнения `makeCounter()` создаёт функцию в строке `(**)`. При создании эта функция получает внутреннее свойство `[[Scope]]` со ссылкой на текущий `LexicalEnvironment`.
    4. -
    5. Далее вызов `makeCounter()` завершается и функция `(**)` возвращается и сохраняется во внешней переменной `counter` `(*)`.
    6. -
    + var counter = makeCounter(); // (*) + ``` +2. В процессе выполнения `makeCounter()` создаёт функцию в строке `(**)`. При создании эта функция получает внутреннее свойство `[[Scope]]` со ссылкой на текущий `LexicalEnvironment`. +3. Далее вызов `makeCounter()` завершается и функция `(**)` возвращается и сохраняется во внешней переменной `counter` `(*)`. -На этом создание "счётчика" завершено. +На этом создание "счётчика" завершено. Итоговым значением, записанным в переменную `counter`, является функция: ```js -function() { // [[Scope]] -> {currentCount: 1} +function() { // [[Scope]] -> {currentCount: 1} return currentCount++; }; ``` @@ -279,14 +265,13 @@ function() { // [[Scope]] -> {currentCount: 1} Это и используется для хранения текущего значения счётчика. -Далее, когда-нибудь, функция `counter` будет вызвана. Мы не знаем, когда это произойдёт. Может быть, прямо сейчас, но, вообще говоря, совсем не факт. +Далее, когда-нибудь, функция `counter` будет вызвана. Мы не знаем, когда это произойдёт. Может быть, прямо сейчас, но, вообще говоря, совсем не факт. Эта функция состоит из одной строки: `return currentCount++`, ни переменных ни параметров в ней нет, поэтому её собственный объект переменных, для краткости назовём его `LE` -- будет пуст. Однако, у неё есть свойство `[[Scope]]`, которое указывает на внешнее окружение. Чтобы увеличить и вернуть `currentCount`, интерпретатор ищет в текущем объекте переменных `LE`, не находит, затем идёт во внешний объект, там находит, изменяет и возвращает новое значение: -```js -//+ run +```js run function makeCounter() { var currentCount = 1; @@ -295,7 +280,7 @@ function makeCounter() { }; } -var counter = makeCounter(); // [[Scope]] -> {currentCount: 1} +var counter = makeCounter(); // [[Scope]] -> {currentCount: 1} alert( counter() ); // 1, [[Scope]] -> {currentCount: 1} alert( counter() ); // 2, [[Scope]] -> {currentCount: 2} @@ -304,7 +289,6 @@ alert( counter() ); // 3, [[Scope]] -> {currentCount: 3} **Переменную во внешней области видимости можно не только читать, но и изменять.** - В примере выше было создано несколько счётчиков. Все они взаимно независимы: ```js @@ -321,13 +305,11 @@ alert( counter2() ); // 1, *!*счётчики независимы*/!* Они независимы, потому что при каждом запуске `makeCounter` создаётся свой объект переменных `LexicalEnvironment`, со своим свойством `currentCount`, на который новый счётчик получит ссылку `[[Scope]]`. - ## Свойства функции Функция в JavaScript является объектом, поэтому можно присваивать свойства прямо к ней, вот так: -```js -//+ run +```js run function f() {} f.test = 5; @@ -342,8 +324,7 @@ alert( f.test ); В качестве демонстрации, перепишем пример со счётчиком: -```js -//+ run +```js run function makeCounter() { *!* function counter() { @@ -360,7 +341,7 @@ alert( counter() ); // 1 alert( counter() ); // 2 ``` -При запуске пример работает также. +При запуске пример работает также. Принципиальная разница -- во внутренней механике и в том, что свойство функции, в отличие от переменной из замыкания -- общедоступно, к нему имеет доступ любой, у кого есть объект функции. @@ -377,35 +358,33 @@ counter.currentCount = 5; alert( counter() ); // 5 ``` -[smart header="Статические переменные"] +```smart header="Статические переменные" Иногда свойства, привязанные к функции, называют "статическими переменными". В некоторых языках программирования можно объявлять переменную, которая сохраняет значение между вызовами функции. В JavaScript ближайший аналог -- такое вот свойство функции. -[/smart] - +``` ## Итого: замыкания -[Замыкание](http://en.wikipedia.org/wiki/Closure_(computer_science)) -- это функция вместе со всеми внешними переменными, которые ей доступны. +[Замыкание](http://en.wikipedia.org/wiki/Closure_(computer_science)) -- это функция вместе со всеми внешними переменными, которые ей доступны. Таково стандартное определение, которое есть в Wikipedia и большинстве серьёзных источников по программированию. То есть, замыкание -- это функция + внешние переменные. -Тем не менее, в JavaScript есть небольшая терминологическая особенность. +Тем не менее, в JavaScript есть небольшая терминологическая особенность. **Обычно, говоря "замыкание функции", подразумевают не саму эту функцию, а именно внешние переменные.** Иногда говорят "переменная берётся из замыкания". Это означает -- из внешнего объекта переменных. - -[smart header="Что это такое -- \"понимать замыкания?\""] +```smart header="Что это такое -- \"понимать замыкания?\"" Иногда говорят "Вася молодец, понимает замыкания!". Что это такое -- "понимать замыкания", какой смысл обычно вкладывают в эти слова? "Понимать замыкания" в JavaScript означает понимать следующие вещи: -
      -
    1. Все переменные и параметры функций являются свойствами объекта переменных `LexicalEnvironment`. Каждый запуск функции создает новый такой объект. На верхнем уровне им является "глобальный объект", в браузере -- `window`.
    2. -
    3. При создании функция получает системное свойство `[[Scope]]`, которое ссылается на `LexicalEnvironment`, в котором она была создана.
    4. -
    5. При вызове функции, куда бы её ни передали в коде -- она будет искать переменные сначала у себя, а затем во внешних `LexicalEnvironment` с места своего "рождения".
    6. -
    + +1. Все переменные и параметры функций являются свойствами объекта переменных `LexicalEnvironment`. Каждый запуск функции создает новый такой объект. На верхнем уровне им является "глобальный объект", в браузере -- `window`. +2. При создании функция получает системное свойство `[[Scope]]`, которое ссылается на `LexicalEnvironment`, в котором она была создана. +3. При вызове функции, куда бы её ни передали в коде -- она будет искать переменные сначала у себя, а затем во внешних `LexicalEnvironment` с места своего "рождения". В следующих главах мы углубим это понимание дополнительными примерами, а также рассмотрим, что происходит с памятью. -[/smart] +``` + diff --git a/1-js/5-functions-closures/3-scope-new-function/article.md b/1-js/5-functions-closures/3-scope-new-function/article.md index 51cebe40..43160f80 100644 --- a/1-js/5-functions-closures/3-scope-new-function/article.md +++ b/1-js/5-functions-closures/3-scope-new-function/article.md @@ -1,25 +1,23 @@ # [[Scope]] для new Function +## Присвоение [[Scope]] для new Function [#scope-Function] -## Присвоение [[Scope]] для new Function [#scope-Function] - -Есть одно исключение из общего правила присвоения `[[Scope]]`, которое мы рассматривали в предыдущей главе. +Есть одно исключение из общего правила присвоения `[[Scope]]`, которое мы рассматривали в предыдущей главе. При создании функции с использованием `new Function`, её свойство `[[Scope]]` ссылается не на текущий `LexicalEnvironment`, а на `window`. -## Пример +## Пример Следующий пример демонстрирует как функция, созданная `new Function`, игнорирует внешнюю переменную `a` и выводит глобальную вместо неё: -```js -//+ run untrusted refresh +```js run untrusted refresh var a = 1; function getFunc() { var a = 2; - + *!* - var func = new Function('', 'alert(a)'); + var func = new Function('', 'alert(a)'); */!* return func; @@ -30,29 +28,27 @@ getFunc()(); // *!*1*/!*, из window Сравним с обычным поведением: -```js -//+ run untrusted refresh +```js run untrusted refresh var a = 1; function getFunc() { var a = 2; - + *!* var func = function() { alert(a); }; */!* - return func; + return func; } getFunc()(); // *!*2*/!*, из LexicalEnvironment функции getFunc ``` - ## Почему так сделано? -[warn header="Продвинутые знания"] +```warn header="Продвинутые знания" Содержимое этой секции содержит продвинутую информацию теоретического характера, которая прямо сейчас не обязательна для дальнейшего изучения JavaScript. -[/warn] +``` Эта особенность `new Function`, хоть и выглядит странно, на самом деле весьма полезна. @@ -60,20 +56,19 @@ getFunc()(); // *!*2*/!*, из LexicalEnvironment функции getFunc Предположим, что этому коду надо будет взаимодействовать с внешними переменными основного скрипта. -Но проблема в том, что JavaScript при выкладывании на "боевой сервер" предварительно сжимается минификатором -- специальной программой, которая уменьшает размер кода, убирая из него лишние комментарии, пробелы, что очень важно -- переименовывает локальные переменные на более короткие. +Но проблема в том, что JavaScript при выкладывании на "боевой сервер" предварительно сжимается минификатором -- специальной программой, которая уменьшает размер кода, убирая из него лишние комментарии, пробелы, что очень важно -- переименовывает локальные переменные на более короткие. -То есть, если внутри функции есть `var userName`, то минификатор заменит её на `var a` (или другую букву, чтобы не было конфликта), предполагая, что так как переменная видна только внутри функции, то этого всё равно никто не заметит, а код станет короче. И обычно проблем нет. +То есть, если внутри функции есть `var userName`, то минификатор заменит её на `var a` (или другую букву, чтобы не было конфликта), предполагая, что так как переменная видна только внутри функции, то этого всё равно никто не заметит, а код станет короче. И обычно проблем нет. -...Но если бы `new Function` могла обращаться к внешним переменным, то при попытке доступа к `userName` в сжатом коде была бы ошибка, так как минификатор переименовал её. +...Но если бы `new Function` могла обращаться к внешним переменным, то при попытке доступа к `userName` в сжатом коде была бы ошибка, так как минификатор переименовал её. **Получается, что даже если бы мы захотели использовать локальные переменные в `new Function`, то после сжатия были бы проблемы, так как минификатор переименовывает локальные переменные.** -Описанная особенность `new Function` просто-таки спасает нас от ошибок. +Описанная особенность `new Function` просто-таки спасает нас от ошибок. Ну а если внутри функции, создаваемой через `new Function`, всё же нужно использовать какие-то данные -- без проблем, нужно всего лишь предусмотреть соответствующие параметры и передавать их явным образом, например так: -```js -//+ run untrusted refresh no-beautify +```js run untrusted refresh no-beautify *!* var sum = new Function('a, b', ' return a + b; '); */!* @@ -87,7 +82,6 @@ alert( sum(a, b) ); // 3 ## Итого -
      -
    • Функции, создаваемые через `new Function`, имеют значением `[[Scope]]` не внешний объект переменных, а `window`.
    • -
    • Следствие -- такие функции не могут использовать замыкание. Но это хорошо, так как бережёт от ошибок проектирования, да и при сжатии JavaScript проблем не будет. Если же внешние переменные реально нужны -- их можно передать в качестве параметров.
    • -
    +- Функции, создаваемые через `new Function`, имеют значением `[[Scope]]` не внешний объект переменных, а `window`. +- Следствие -- такие функции не могут использовать замыкание. Но это хорошо, так как бережёт от ошибок проектирования, да и при сжатии JavaScript проблем не будет. Если же внешние переменные реально нужны -- их можно передать в качестве параметров. + diff --git a/1-js/5-functions-closures/4-closures-usage/1-closure-sum/solution.md b/1-js/5-functions-closures/4-closures-usage/1-closure-sum/solution.md index 2e28ae36..e5ac7745 100644 --- a/1-js/5-functions-closures/4-closures-usage/1-closure-sum/solution.md +++ b/1-js/5-functions-closures/4-closures-usage/1-closure-sum/solution.md @@ -1,9 +1,8 @@ -Чтобы вторые скобки в вызове работали - первые должны возвращать функцию. +Чтобы вторые скобки в вызове работали - первые должны возвращать функцию. Эта функция должна знать про `a` и уметь прибавлять `a` к `b`. Вот так: -```js -//+ run +```js run function sum(a) { return function(b) { diff --git a/1-js/5-functions-closures/4-closures-usage/1-closure-sum/task.md b/1-js/5-functions-closures/4-closures-usage/1-closure-sum/task.md index 5de93f5e..11c17955 100644 --- a/1-js/5-functions-closures/4-closures-usage/1-closure-sum/task.md +++ b/1-js/5-functions-closures/4-closures-usage/1-closure-sum/task.md @@ -1,6 +1,8 @@ -# Сумма через замыкание +importance: 4 + +--- -[importance 4] +# Сумма через замыкание Напишите функцию `sum`, которая работает так: `sum(a)(b) = a+b`. diff --git a/1-js/5-functions-closures/4-closures-usage/2-stringbuffer/solution.md b/1-js/5-functions-closures/4-closures-usage/2-stringbuffer/solution.md index 5f946bfb..e3474a32 100644 --- a/1-js/5-functions-closures/4-closures-usage/2-stringbuffer/solution.md +++ b/1-js/5-functions-closures/4-closures-usage/2-stringbuffer/solution.md @@ -1,7 +1,6 @@ Текущее значение текста удобно хранить в замыкании, в локальной переменной `makeBuffer`: -```js -//+ run +```js run function makeBuffer() { var text = ''; diff --git a/1-js/5-functions-closures/4-closures-usage/2-stringbuffer/task.md b/1-js/5-functions-closures/4-closures-usage/2-stringbuffer/task.md index 9f28b7cd..66319fcc 100644 --- a/1-js/5-functions-closures/4-closures-usage/2-stringbuffer/task.md +++ b/1-js/5-functions-closures/4-closures-usage/2-stringbuffer/task.md @@ -1,19 +1,18 @@ -# Функция - строковый буфер +importance: 5 + +--- -[importance 5] +# Функция - строковый буфер В некоторых языках программирования существует объект "строковый буфер", который аккумулирует внутри себя значения. Его функционал состоит из двух возможностей: -
      -
    1. Добавить значение в буфер.
    2. -
    3. Получить текущее содержимое.
    4. -
    + +1. Добавить значение в буфер. +2. Получить текущее содержимое. **Задача -- реализовать строковый буфер на функциях в JavaScript, со следующим синтаксисом:** -
      -
    • Создание объекта: `var buffer = makeBuffer();`.
    • -
    • Вызов `makeBuffer` должен возвращать такую функцию `buffer`, которая при вызове `buffer(value)` добавляет значение в некоторое внутреннее хранилище, а при вызове без аргументов `buffer()` -- возвращает его.
    • -
    +- Создание объекта: `var buffer = makeBuffer();`. +- Вызов `makeBuffer` должен возвращать такую функцию `buffer`, которая при вызове `buffer(value)` добавляет значение в некоторое внутреннее хранилище, а при вызове без аргументов `buffer()` -- возвращает его. Вот пример работы: diff --git a/1-js/5-functions-closures/4-closures-usage/3-stringbuffer-with-clear/solution.md b/1-js/5-functions-closures/4-closures-usage/3-stringbuffer-with-clear/solution.md index c4c45809..785888e7 100644 --- a/1-js/5-functions-closures/4-closures-usage/3-stringbuffer-with-clear/solution.md +++ b/1-js/5-functions-closures/4-closures-usage/3-stringbuffer-with-clear/solution.md @@ -1,7 +1,6 @@ -```js -//+ run +```js run function makeBuffer() { var text = ''; diff --git a/1-js/5-functions-closures/4-closures-usage/3-stringbuffer-with-clear/task.md b/1-js/5-functions-closures/4-closures-usage/3-stringbuffer-with-clear/task.md index 319af876..492688fa 100644 --- a/1-js/5-functions-closures/4-closures-usage/3-stringbuffer-with-clear/task.md +++ b/1-js/5-functions-closures/4-closures-usage/3-stringbuffer-with-clear/task.md @@ -1,8 +1,10 @@ -# Строковый буфер с очисткой +importance: 5 + +--- -[importance 5] +# Строковый буфер с очисткой -Добавьте буферу из решения задачи [](/task/stringbuffer) метод `buffer.clear()`, который будет очищать текущее содержимое буфера: +Добавьте буферу из решения задачи метод `buffer.clear()`, который будет очищать текущее содержимое буфера: ```js function makeBuffer() { diff --git a/1-js/5-functions-closures/4-closures-usage/4-sort-by-field/solution.md b/1-js/5-functions-closures/4-closures-usage/4-sort-by-field/solution.md index 43ded8f9..6f89fd99 100644 --- a/1-js/5-functions-closures/4-closures-usage/4-sort-by-field/solution.md +++ b/1-js/5-functions-closures/4-closures-usage/4-sort-by-field/solution.md @@ -1,7 +1,6 @@ -```js -//+ run +```js run var users = [{ name: "Вася", surname: 'Иванов', diff --git a/1-js/5-functions-closures/4-closures-usage/4-sort-by-field/task.md b/1-js/5-functions-closures/4-closures-usage/4-sort-by-field/task.md index 36701c23..f584364c 100644 --- a/1-js/5-functions-closures/4-closures-usage/4-sort-by-field/task.md +++ b/1-js/5-functions-closures/4-closures-usage/4-sort-by-field/task.md @@ -1,6 +1,8 @@ -# Сортировка +importance: 5 + +--- -[importance 5] +# Сортировка У нас есть массив объектов: diff --git a/1-js/5-functions-closures/4-closures-usage/5-filter-through-function/_js.view/source.js b/1-js/5-functions-closures/4-closures-usage/5-filter-through-function/_js.view/source.js index e512f26c..a6d279fd 100644 --- a/1-js/5-functions-closures/4-closures-usage/5-filter-through-function/_js.view/source.js +++ b/1-js/5-functions-closures/4-closures-usage/5-filter-through-function/_js.view/source.js @@ -1,4 +1,4 @@ -function filter(arr, fuc) { +function filter(arr, func) { // ...ваш код... } @@ -8,4 +8,4 @@ function inBetween(a, b) { function inArray(arr) { // ...ваш код... -} \ No newline at end of file +} diff --git a/1-js/5-functions-closures/4-closures-usage/5-filter-through-function/solution.md b/1-js/5-functions-closures/4-closures-usage/5-filter-through-function/solution.md index 114fdd5f..b9657971 100644 --- a/1-js/5-functions-closures/4-closures-usage/5-filter-through-function/solution.md +++ b/1-js/5-functions-closures/4-closures-usage/5-filter-through-function/solution.md @@ -1,7 +1,6 @@ # Функция фильтрации -```js -//+ run +```js run function filter(arr, func) { var result = []; @@ -24,8 +23,7 @@ alert(filter(arr, function(a) { # Фильтр inBetween -```js -//+ run +```js run function filter(arr, func) { var result = []; @@ -53,8 +51,7 @@ alert( filter(arr, inBetween(3, 6)) ); // 3,4,5,6 # Фильтр inArray -```js -//+ run +```js run function filter(arr, func) { var result = []; diff --git a/1-js/5-functions-closures/4-closures-usage/5-filter-through-function/task.md b/1-js/5-functions-closures/4-closures-usage/5-filter-through-function/task.md index 7ee691b5..8e162ab4 100644 --- a/1-js/5-functions-closures/4-closures-usage/5-filter-through-function/task.md +++ b/1-js/5-functions-closures/4-closures-usage/5-filter-through-function/task.md @@ -1,17 +1,16 @@ +importance: 5 + +--- + # Фильтрация через функцию -[importance 5] - -
      -
    1. Создайте функцию `filter(arr, func)`, которая получает массив `arr` и возвращает новый, в который входят только те элементы `arr`, для которых `func` возвращает `true`.
    2. -
    3. Создайте набор "готовых фильтров": `inBetween(a,b)` -- "между a,b", `inArray([...])` -- "в массиве `[...]`". -Использование должно быть таким: -
        -
      • `filter(arr, inBetween(3,6))` -- выберет только числа от 3 до 6,
      • -
      • `filter(arr, inArray([1,2,3]))` -- выберет только элементы, совпадающие с одним из значений массива.
      • -
      -
    4. -
    +1. Создайте функцию `filter(arr, func)`, которая получает массив `arr` и возвращает новый, в который входят только те элементы `arr`, для которых `func` возвращает `true`. +2. Создайте набор "готовых фильтров": `inBetween(a,b)` -- "между a,b", `inArray([...])` -- "в массиве `[...]`". +Использование должно быть таким: + +- `filter(arr, inBetween(3,6))` -- выберет только числа от 3 до 6, +- `filter(arr, inArray([1,2,3]))` -- выберет только элементы, совпадающие с одним из значений массива. + Пример, как это должно работать: ```js diff --git a/1-js/5-functions-closures/4-closures-usage/6-make-army/solution.md b/1-js/5-functions-closures/4-closures-usage/6-make-army/solution.md index 5628e800..8326dbc2 100644 --- a/1-js/5-functions-closures/4-closures-usage/6-make-army/solution.md +++ b/1-js/5-functions-closures/4-closures-usage/6-make-army/solution.md @@ -1,37 +1,32 @@ # Что происходит в этом коде Функция `makeArmy` делает следующее: -
      -
    1. Создаёт пустой массив `shooter`: -```js -var shooters = []; -``` +1. Создаёт пустой массив `shooters`: -
    2. -
    3. В цикле заполняет массив элементами через `shooter.push`. + ```js + var shooters = []; + ``` +2. В цикле заполняет массив элементами через `shooters.push`. При этом каждый элемент массива -- это функция, так что в итоге после цикла массив будет таким: -```js -//+ no-beautify -shooters = [ - function () { alert(i); }, - function () { alert(i); }, - function () { alert(i); }, - function () { alert(i); }, - function () { alert(i); }, - function () { alert(i); }, - function () { alert(i); }, - function () { alert(i); }, - function () { alert(i); }, - function () { alert(i); } -]; -``` - -Этот массив возвращается из функции. -
    4. -
    5. Вызов `army[5]()` -- это получение элемента массива (им будет функция), и тут же -- её запуск.
    6. -
    + ```js no-beautify + shooters = [ + function () { alert(i); }, + function () { alert(i); }, + function () { alert(i); }, + function () { alert(i); }, + function () { alert(i); }, + function () { alert(i); }, + function () { alert(i); }, + function () { alert(i); }, + function () { alert(i); }, + function () { alert(i); } + ]; + ``` + + Этот массив возвращается из функции. +3. Вызов `army[5]()` -- это получение элемента массива (им будет функция), и тут же -- её запуск. # Почему ошибка @@ -43,7 +38,7 @@ shooters = [ К моменту вызова `army[0]()`, функция `makeArmy` уже закончила работу. Цикл завершился, последнее значение было `i=10`. -В результате все функции `shooter` получают из внешнего лексического кружения это, одно и то же, последнее, значение `i=10`. +В результате все функции `shooter` получают из внешнего лексического окружения это, одно и то же, последнее, значение `i=10`. Попробуйте исправить проблему самостоятельно. @@ -51,168 +46,160 @@ shooters = [ Есть несколько способов исправить ситуацию. -
      -
    1. **Первый способ исправить код - это привязать значение непосредственно к функции-стрелку:** +1. **Первый способ исправить код - это привязать значение непосредственно к функции-стрелку:** -```js -//+ run -function makeArmy() { + ```js run + function makeArmy() { - var shooters = []; + var shooters = []; - for (var i = 0; i < 10; i++) { + for (var i = 0; i < 10; i++) { -*!* + *!* + var shooter = function me() { + alert( me.i ); + }; + shooter.i = i; + */!* + + shooters.push(shooter); + } + + return shooters; + } + + var army = makeArmy(); + + army[0](); // 0 + army[1](); // 1 + ``` + + В этом случае каждая функция хранит в себе свой собственный номер. + + Кстати, обратите внимание на использование Named Function Expression, вот в этом участке: + + ```js + ... var shooter = function me() { alert( me.i ); }; - shooter.i = i; -*/!* - - shooters.push(shooter); - } - - return shooters; -} + ... + ``` + + Если убрать имя `me` и оставить обращение через `shooter`, то работать не будет: + + ```js + for (var i = 0; i < 10; i++) { + var shooter = function() { + *!* + alert( shooter.i ); // вывести свой номер (не работает!) + // потому что откуда функция возьмёт переменную shooter? + // ..правильно, из внешнего объекта, а там она одна на всех + */!* + }; + shooter.i = i; + shooters.push(shooter); + } + ``` -var army = makeArmy(); + Вызов `alert(shooter.i)` при вызове будет искать переменную `shooter`, а эта переменная меняет значение по ходу цикла, и к моменту вызова она равна последней функции, созданной в цикле. -army[0](); // 0 -army[1](); // 1 -``` + Если использовать Named Function Expression, то имя жёстко привязывается к конкретной функции, и поэтому в коде выше `me.i` возвращает правильный `i`. +2. **Другое, более продвинутое решение -- использовать дополнительную функцию для того, чтобы "поймать" текущее значение `i`**: -В этом случае каждая функция хранит в себе свой собственный номер. + ```js run + function makeArmy() { -Кстати, обратите внимание на использование Named Function Expression, вот в этом участке: + var shooters = []; -```js -... -var shooter = function me() { - alert( me.i ); -}; -... -``` + for (var i = 0; i < 10; i++) { -Если убрать имя `me` и оставить обращение через `shooter`, то работать не будет: + *!* + var shooter = (function(x) { -```js -for (var i = 0; i < 10; i++) { - var shooter = function() { -*!* - alert( shooter.i ); // вывести свой номер (не работает!) - // потому что откуда функция возьмёт переменную shooter? - // ..правильно, из внешнего объекта, а там она одна на всех -*/!* - }; - shooter.i = i; - shooters.push(shooter); -} -``` + return function() { + alert( x ); + }; -Вызов `alert(shooter.i)` при вызове будет искать переменную `shooter`, а эта переменная меняет значение по ходу цикла, и к моменту вызову она равна последней функции, созданной в цикле. + })(i); + */!* -Если использовать Named Function Expression, то имя жёстко привязывается к конкретной функции, и поэтому в коде выше `me.i` возвращает правильный `i`. + shooters.push(shooter); + } -
    2. -
    3. **Другое, более продвинутое решение -- использовать дополнительную функцию для того, чтобы "поймать" текущее значение `i`**: + return shooters; + } -```js -//+ run -function makeArmy() { + var army = makeArmy(); - var shooters = []; + army[0](); // 0 + army[1](); // 1 + ``` - for (var i = 0; i < 10; i++) { + Посмотрим выделенный фрагмент более внимательно, чтобы понять, что происходит: -*!* + ```js var shooter = (function(x) { - return function() { alert( x ); }; - })(i); -*/!* + ``` - shooters.push(shooter); - } + Функция `shooter` создана как результат вызова промежуточного функционального выражения `function(x)`, которое объявляется -- и тут же выполняется, получая `x = i`. - return shooters; -} + Так как `function(x)` тут же завершается, то значение `x` больше не меняется. Оно и будет использовано в возвращаемой функции-стрелке. -var army = makeArmy(); + Для красоты можно изменить название переменной `x` на `i`, суть происходящего при этом не изменится: -army[0](); // 0 -army[1](); // 1 -``` - -Посмотрим выделенный фрагмент более внимательно, чтобы понять, что происходит: - -```js -var shooter = (function(x) { - return function() { - alert( x ); - }; -})(i); -``` - -Функция `shooter` создана как результат вызова промежуточного функционального выражения `function(x)`, которое объявляется -- и тут же выполняется, получая `x = i`. - -Так как `function(x)` тут же завершается, то значение `x` больше не меняется. Оно и будет использовано в возвращаемой функции-стрелке. - -Для красоты можно изменить название переменной `x` на `i`, суть происходящего при этом не изменится: + ```js + var shooter = (function(i) { + return function() { + alert( i ); + }; + })(i); + ``` -```js -var shooter = (function(i) { - return function() { - alert( i ); - }; -})(i); -``` + **Кстати, обратите внимание -- скобки вокруг `function(i)` не нужны**, можно и так: -**Кстати, обратите внимание -- скобки вокруг `function(i)` не нужны**, можно и так: + ```js + var shooter = function(i) { // *!*без скобок вокруг function(i)*/!* + return function() { + alert( i ); + }; + }(i); + ``` -```js -var shooter = function(i) { // *!*без скобок вокруг function(i)*/!* - return function() { - alert( i ); - }; -}(i); -``` + Скобки добавлены в код для лучшей читаемости, чтобы человек, который просматривает его, не подумал, что `var shooter = function`, а понял что это вызов "на месте", и присваивается его результат. +3. **Еще один забавный способ - обернуть весь цикл во временную функцию**: -Скобки добавлены в код для лучшей читаемости, чтобы человек, который просматривает его, не подумал, что `var shooter = function`, а понял что это вызов "на месте", и присваивается его результат. -
    4. -
    5. **Еще один забавный способ - обернуть весь цикл во временную функцию**: + ```js run + function makeArmy() { -```js -//+ run -function makeArmy() { + var shooters = []; - var shooters = []; + *!* + for (var i = 0; i < 10; i++)(function(i) { -*!* - for (var i = 0; i < 10; i++)(function(i) { + var shooter = function() { + alert( i ); + }; - var shooter = function() { - alert( i ); - }; + shooters.push(shooter); - shooters.push(shooter); + })(i); + */!* - })(i); -*/!* + return shooters; + } - return shooters; -} + var army = makeArmy(); -var army = makeArmy(); + army[0](); // 0 + army[1](); // 1 + ``` -army[0](); // 0 -army[1](); // 1 -``` + Вызов `(function(i) { ... })` обернут в скобки, чтобы интерпретатор понял, что это `Function Expression`. -Вызов `(function(i) { ... })` обернут в скобки, чтобы интерпретатор понял, что это `Function Expression`. + Плюс этого способа - в большей читаемости. Фактически, мы не меняем создание `shooter`, а просто обертываем итерацию в функцию. -Плюс этого способа - в большей читаемости. Фактически, мы не меняем создание `shooter`, а просто обертываем итерацию в функцию. -
    6. -
    \ No newline at end of file diff --git a/1-js/5-functions-closures/4-closures-usage/6-make-army/task.md b/1-js/5-functions-closures/4-closures-usage/6-make-army/task.md index 15e0d2ae..ca96fb2c 100644 --- a/1-js/5-functions-closures/4-closures-usage/6-make-army/task.md +++ b/1-js/5-functions-closures/4-closures-usage/6-make-army/task.md @@ -1,11 +1,12 @@ -# Армия функций +importance: 5 + +--- -[importance 5] +# Армия функций Следующий код создает массив функций-стрелков `shooters`. По замыслу, каждый стрелок должен выводить свой номер: -```js -//+ run +```js run function makeArmy() { var shooters = []; diff --git a/1-js/5-functions-closures/4-closures-usage/article.md b/1-js/5-functions-closures/4-closures-usage/article.md index 0fa5da2c..5f488079 100644 --- a/1-js/5-functions-closures/4-closures-usage/article.md +++ b/1-js/5-functions-closures/4-closures-usage/article.md @@ -8,12 +8,11 @@ ## Счётчик-объект -Ранее мы сделали счётчик. +Ранее мы сделали счётчик. Напомню, как он выглядел: -```js -//+ run +```js run function makeCounter() { var currentCount = 1; @@ -25,7 +24,7 @@ function makeCounter() { var counter = makeCounter(); // каждый вызов возвращает результат, увеличивая счётчик -alert( counter() ); // 1 +alert( counter() ); // 1 alert( counter() ); // 2 alert( counter() ); // 3 ``` @@ -34,8 +33,7 @@ alert( counter() ); // 3 **Чтобы добавить счётчику возможностей -- перейдём с функции на полноценный объект:** -```js -//+ run +```js run function makeCounter() { var currentCount = 1; @@ -65,11 +63,9 @@ alert( counter.getNext() ); // 5 Теперь функция `makeCounter` возвращает не одну функцию, а объект с несколькими методами: -
      -
    • `getNext()` -- получить следующее значение, то, что раньше делал вызов `counter()`.
    • -
    • `set(value)` -- поставить значение.
    • -
    • `reset()` -- обнулить счётчик.
    • -
    +- `getNext()` -- получить следующее значение, то, что раньше делал вызов `counter()`. +- `set(value)` -- поставить значение. +- `reset()` -- обнулить счётчик. Все они получают ссылку `[[Scope]]` на текущий (внешний) объект переменных. Поэтому вызов любого из этих методов будет получать или модифицировать одно и то же внешнее значение `currentCount`. @@ -77,12 +73,11 @@ alert( counter.getNext() ); // 5 Изначально, счётчик делался функцией во многом ради красивого вызова: `counter()`, который увеличивал значение и возвращал результат. -К сожалению, при переходе на объект короткий вызов пропал, вместо него теперь `counter.getNext()`. Но он ведь был таким простым и удобным... +К сожалению, при переходе на объект короткий вызов пропал, вместо него теперь `counter.getNext()`. Но он ведь был таким простым и удобным... Поэтому давайте вернём его! -```js -//+ run +```js run function makeCounter() { var currentCount = 1; @@ -99,7 +94,7 @@ function makeCounter() { }; counter.reset = function() { - currentCount = 0; + currentCount = 1; }; return counter; @@ -122,4 +117,3 @@ alert( counter() ); // 5 Далее вы найдёте различные задачи на понимание замыканий. Рекомендуется их сделать самостоятельно. - diff --git a/1-js/5-functions-closures/5-closures-module/article.md b/1-js/5-functions-closures/5-closures-module/article.md index 384a86fb..73e7375f 100644 --- a/1-js/5-functions-closures/5-closures-module/article.md +++ b/1-js/5-functions-closures/5-closures-module/article.md @@ -2,7 +2,7 @@ Приём программирования "модуль" имеет громадное количество вариаций. Он немного похож на счётчик, который мы рассматривали ранее, использует аналогичный приём, но на уровне выше. -Его цель -- скрыть внутренние детали реализации скрипта. В том числе: временные переменные, константы, вспомогательные мини-функции и т.п. +Его цель -- скрыть внутренние детали реализации скрипта. В том числе: временные переменные, константы, вспомогательные мини-функции и т.п. ## Зачем нужен модуль? @@ -12,8 +12,7 @@ Файл `hello.js` -```js -//+ run +```js run // глобальная переменная нашего скрипта var message = "Привет"; @@ -45,11 +44,11 @@ showMessage(); ``` -[edit src="http://github.com/hello-conflict"/] +[edit src="http://github.com/hello-conflict"] Автор страницы ожидает, что библиотека `"hello.js"` просто отработает, без побочных эффектов. А она вместе с этим переопределила `message` в `"Привет"`. @@ -65,8 +64,7 @@ showMessage(); Файл `hello.js`, оформленный как модуль: -```js -//+ run +```js run (function() { // глобальная переменная нашего скрипта @@ -83,19 +81,17 @@ showMessage(); })(); ``` -[edit src="http://github.com/hello-module"/] +[edit src="http://github.com/hello-module"] -Этот скрипт при подключении к той же странице будет работать корректно. +Этот скрипт при подключении к той же странице будет работать корректно. Будет выводиться "Привет", а затем "Пожалуйста, нажмите на кнопку". - ### Зачем скобки вокруг функции? В примере выше объявление модуля выглядит так: -```js -//+ run +```js run (function() { alert( "объявляем локальные переменные, функции, работаем" ); @@ -108,8 +104,7 @@ showMessage(); Вот, для сравнения, неверный вариант: -```js -//+ run +```js run function() { // будет ошибка }(); @@ -119,8 +114,7 @@ function() { Впрочем, даже если имя поставить, то работать тоже не будет: -```js -//+ run +```js run function work() { // ... }(); // syntax error @@ -130,17 +124,14 @@ function work() { Общее правило таково: -
      -
    • Если браузер видит `function` в основном потоке кода -- он считает, что это `Function Declaration`.
    • -
    • Если же `function` идёт в составе более сложного выражения, то он считает, что это `Function Expression`.
    • -
    +- Если браузер видит `function` в основном потоке кода -- он считает, что это `Function Declaration`. +- Если же `function` идёт в составе более сложного выражения, то он считает, что это `Function Expression`. Для этого и нужны скобки -- показать, что у нас `Function Expression`, который по правилам JavaScript можно вызвать "на месте". Можно показать это другим способом, например поставив перед функцией оператор: -```js -//+ run no-beautify +```js run no-beautify +function() { alert('Вызов на месте'); }(); @@ -158,23 +149,19 @@ function work() { Посмотрим, к примеру, на библиотеку [Lodash](http://lodash.com/), хотя могли бы и [jQuery](http://jquery.com/), там почти то же самое. -Если её подключить, то появится специальная переменная `lodash` (короткое имя `_`), которую можно использовать как функцию, и кроме того в неё записаны различные полезных свойства, например: - -
      -
    • `_.defaults(src, dst1, dst2...)` -- копирует в объект `src` те свойства из объектов `dst1`, `dst2` и других, которых там нет.
    • -
    • `_.cloneDeep(obj)` -- делает глубокое копирование объекта `obj`, создавая полностью независимый клон.
    • -
    • `_.size(obj)` -- возвращает количество свойств в объекте, полиморфная функция: можно передать массив или даже 1 значение.
    • -
    +Если её подключить, то появится специальная переменная `lodash` (короткое имя `_`), которую можно использовать как функцию, и кроме того в неё записаны различные полезные свойства, например: +- `_.defaults(src, dst1, dst2...)` -- копирует в объект `src` те свойства из объектов `dst1`, `dst2` и других, которых там нет. +- `_.cloneDeep(obj)` -- делает глубокое копирование объекта `obj`, создавая полностью независимый клон. +- `_.size(obj)` -- возвращает количество свойств в объекте, полиморфная функция: можно передать массив или даже 1 значение. Есть и много других функций, подробнее описанных в [документации](https://lodash.com/docs). Пример использования: -```html - +```html run

    Подключим библиотеку

    - +

    Функция _.defaults() добавляет отсутствующие свойства.

    ``` - -Здесь нам не важно, какие, нас интересует именно как описана эта библиотека, как в ней применяется приём "модуль". +Здесь нам не важно, какие функции или методы библиотеки используются, нас интересует именно как описана эта библиотека, как в ней применяется приём "модуль". Вот примерная выдержка из исходного файла: -```js -//+ run no-beautify +```js run no-beautify ;(function() { *!* @@ -214,7 +199,7 @@ function work() { *!* // вспомогательная переменная */!* - var version = '2.4.1'; + var version = '2.4.1'; // ... другие вспомогательные переменные и функции *!* @@ -240,17 +225,16 @@ function work() { ``` Внутри внешней функции: -
      -
    1. Происходит что угодно, объявляются свои локальные переменные, функции.
    2. -
    3. В `window` выносится то, что нужно снаружи.
    4. -
    + +1. Происходит что угодно, объявляются свои локальные переменные, функции. +2. В `window` выносится то, что нужно снаружи. Технически, мы могли бы вынести в `window` не только `lodash`, но и вообще все объекты и функции. На практике, как раз наоборот, всё прячут внутри модуля, глобальную область во избежание конфликтов хранят максимально чистой. -[smart header="Зачем точка с запятой в начале?"] +````smart header="Зачем точка с запятой в начале?" В начале кода выше находится точка с запятой `;` -- это не опечатка, а особая "защита от дураков". -Если получится, что несколько JS-файлы объединены в один (и, скорее всего, сжаты минификатором, но это не важно), и программист забыл поставить точку с запятой, то будет ошибка. +Если получится, что несколько JS-файлов объединены в один (и, скорее всего, сжаты минификатором, но это не важно), и программист забыл поставить точку с запятой, то будет ошибка. Например, первый файл `a.js`: ```js @@ -258,8 +242,7 @@ var a = 5 ``` Второй файл `lib.js`: -```js -//+ no-beautify +```js no-beautify (function() { // без точки с запятой в начале })() @@ -267,8 +250,7 @@ var a = 5 После объединения в один файл: -```js -//+ run no-beautify +```js run no-beautify *!* var a = 5 */!* @@ -279,18 +261,16 @@ var a = 5 })(); ``` -При запуске будет ошибка, потому что интерпретатор перед скобкой сам не вставит точку с запятой. Он просто поймёт код как `var a = 5(function ...)`, то есть пытается вызвать число `5` как функцию. +При запуске будет ошибка, потому что интерпретатор перед скобкой сам не вставит точку с запятой. Он просто поймёт код как `var a = 5(function ...)`, то есть пытается вызвать число `5` как функцию. Таковы правила языка, и поэтому рекомендуется явно ставить точку с запятой. В данном случае автор lodash ставит `;` перед функцией, чтобы предупредить эту ошибку. -[/smart] - +```` ## Экспорт через return Можно оформить модуль и чуть по-другому, например передать значение через `return`: -```js -//+ no-beautify +```js no-beautify var lodash = (function() { var version; @@ -303,7 +283,7 @@ var lodash = (function() { })(); ``` -Здесь, кстати, скобки вокруг внешней `function() { ... }` не обязательны, ведь функция и так объявлена внутри выражения присваивания, а значит -- является Function Expression. +Здесь, кстати, скобки вокруг внешней `function() { ... }` не обязательны, ведь функция и так объявлена внутри выражения присваивания, а значит -- является Function Expression. Тем не менее, лучше их ставить, для улучшения читаемости кода, чтобы было сразу видно, что это не простое присвоение функции. @@ -315,8 +295,7 @@ var lodash = (function() { Например, `defaults` из примера выше имеет доступ к `assignDefaults`. -Но снаружи программист, использующий модуль, может обращаться напрямую только к тем, которые экспортированы. Благодаря этому будут скрыты внутренние аспекты реализации, которые нужны только разработчику модуля. +Но снаружи программист, использующий модуль, может обращаться напрямую только к тем переменным и функциям, которые экспортированы. Благодаря этому будут скрыты внутренние аспекты реализации, которые нужны только разработчику модуля. Можно придумать и много других вариаций такого подхода. В конце концов, "модуль" -- это всего лишь функция-обёртка для скрытия переменных. - diff --git a/1-js/5-functions-closures/6-memory-management/article.md b/1-js/5-functions-closures/6-memory-management/article.md index 0a8c2b36..a52b26db 100644 --- a/1-js/5-functions-closures/6-memory-management/article.md +++ b/1-js/5-functions-closures/6-memory-management/article.md @@ -5,23 +5,20 @@ Что происходит с объектом, когда он становится "не нужен"? Возможно ли "переполнение" памяти? Для ответа на эти вопросы -- залезем "под капот" интерпретатора. [cut] + ## Управление памятью в JavaScript Главной концепцией управления памятью в JavaScript является принцип *достижимости* (англ. reachability). -
      -
    1. Определённое множество значений считается достижимым изначально, в частности: -
        -
      • Значения, ссылки на которые содержатся в стеке вызова, то есть -- все локальные переменные и параметры функций, которые в настоящий момент выполняются или находятся в ожидании окончания вложенного вызова.
      • -
      • Все глобальные переменные.
      • -
      +1. Определённое множество значений считается достижимым изначально, в частности: + +- Значения, ссылки на которые содержатся в стеке вызова, то есть -- все локальные переменные и параметры функций, которые в настоящий момент выполняются или находятся в ожидании окончания вложенного вызова. +- Все глобальные переменные. -Эти значения гарантированно хранятся в памяти. Мы будем называть их *корнями*. -
    2. -
    3. **Любое другое значение сохраняется в памяти лишь до тех пор, пока доступно из корня по ссылке или цепочке ссылок.**
    4. -
    + Эти значения гарантированно хранятся в памяти. Мы будем называть их *корнями*. +2. **Любое другое значение сохраняется в памяти лишь до тех пор, пока доступно из корня по ссылке или цепочке ссылок.** -Для очистки памяти от недостижимых значений в браузерах используется автоматический Сборщик мусора (англ. Garbage collection, GC), встроенный в интерпретатор, который наблюдает за объектами и время от времени удаляет недостижимые. +Для очистки памяти от недостижимых значений в браузерах используется автоматический [Сборщик мусора](https://en.wikipedia.org/wiki/Garbage_collection_(computer_science)) (англ. Garbage collection, GC), встроенный в интерпретатор, который наблюдает за объектами и время от времени удаляет недостижимые. Самая простая ситуация здесь с примитивами. При присвоении они копируются целиком, ссылок на них не создаётся, так что если в переменной была одна строка, а её заменили на другую, то предыдущую можно смело выбросить. @@ -31,46 +28,42 @@ ### Достижимость и наличие ссылок -Есть одно упрощение для работы с памятью: "значение остаётся в памяти, пока на него есть ссылка". +Есть одно упрощение для работы с памятью: "значение остаётся в памяти, пока на него есть хотя бы одна ссылка". Но такое упрощение будет верным лишь в одну сторону. -
      -
    • **Верно -- в том плане, что если ссылок на значение нет, то память из-под него очищается.** +- **Верно -- в том плане, что если ссылок на значение нет, то память из-под него очищается.** -Например, была создана ссылка в переменной, и эту переменную тут же перезаписали: + Например, была создана ссылка в переменной, и эту переменную тут же перезаписали: -```js -var user = { - name: "Вася" -}; -user = null; -``` + ```js + var user = { + name: "Вася" + }; + user = null; + ``` -Теперь объект `{ name: "Вася" }` более недоступен. Память будет освобождена. -
    • -
    • **Неверно -- в другую сторону: наличие ссылки не гарантирует, что значение останется в памяти.** + Теперь объект `{ name: "Вася" }` более недоступен. Память будет освобождена. +- **Неверно -- в другую сторону: наличие ссылки не гарантирует, что значение останется в памяти.** -Такая ситуация возникает с объектами, которые ссылаются друг на друга: + Такая ситуация возникает с объектами, которые ссылаются друг на друга: -```js -var vasya = {}; -var petya = {}; -vasya.friend = petya; -petya.friend = vasya; + ```js + var vasya = {}; + var petya = {}; + vasya.friend = petya; + petya.friend = vasya; -vasya = petya = null; -``` + vasya = petya = null; + ``` -Несмотря на то, что на объекты `vasya`, `petya` ссылаются друг на друга через ссылку `friend`, то есть можно сказать, что на каждый из них есть ссылка, последняя строка делает эти объекты в совокупности недостижимыми. + Несмотря на то, что объекты `vasya` и `petya` ссылаются друг на друга через ссылку `friend`, то есть можно сказать, что на каждый из них есть ссылка, последняя строка делает эти объекты в совокупности недостижимыми. -Поэтому они будут удалены из памяти. + Поэтому они будут удалены из памяти. -Здесь как раз и играет роль "достижимость" -- оба этих объекта становятся недостижимы из корней, в первую очередь, из глобальной области, стека. + Здесь как раз и играет роль "достижимость" -- оба этих объекта становятся недостижимы из корней, в первую очередь, из глобальной области, стека. -[Сборщик мусора](http://ru.wikipedia.org/wiki/%D0%A1%D0%B1%D0%BE%D1%80%D0%BA%D0%B0_%D0%BC%D1%83%D1%81%D0%BE%D1%80%D0%B0) отслеживает такие ситуации и очищает память. -
    • -
    +[Сборщик мусора](https://ru.wikipedia.org/wiki/%D0%A1%D0%B1%D0%BE%D1%80%D0%BA%D0%B0_%D0%BC%D1%83%D1%81%D0%BE%D1%80%D0%B0_(%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)) отслеживает такие ситуации и очищает память. ## Алгоритм сборки мусора @@ -100,7 +93,7 @@ var family = marry({ Получившийся объект `family` можно изобразить так: - +![](family.png) Здесь стрелочками показаны ссылки, а вот свойство `name` ссылкой не является, там хранится примитив, поэтому оно внутри самого объекта. @@ -115,13 +108,13 @@ delete family.mother.husband; А если две, то получается, что от бывшего `family.father` ссылки выходят, но в него -- ни одна не идёт: - +![](family-no-father.png) **Совершенно неважно, что из объекта выходят какие-то ссылки, они не влияют на достижимость этого объекта.** -Бывший `family.father` стал недостижимым и будет удалён вместе со своми данными, которые также более недоступны из программы. +Бывший `family.father` стал недостижимым и будет удалён вместе со своими данными, которые также более недоступны из программы. - +![](family-no-father-2.png) А теперь -- рассмотрим более сложный случай. Что будет, если удалить главную ссылку `family`? @@ -133,12 +126,12 @@ window.family = null; Результат: - +![](family-no-family.png) Как видим, объекты в конструкции всё ещё связаны между собой. Однако, поиск от корня их не находит, они не достижимы, и значит сборщик мусора удалит их из памяти. -[smart header="Оптимизации"] -Проблема описанного алгоритма -- в больших задержках. Если объектов много, то на поиск всех достижимых уйдёт довольно много времени. А ведь выполнение скрипта при этом должно быть остановлено, уже просканированные объекты не должны поменяться до окончания процесса. Получатся небольшие, но неприятные паузы-зависания в работе скрипта. +````smart header="Оптимизации" +Проблема описанного алгоритма -- в больших задержках. Если объектов много, то на поиск всех достижимых уйдёт довольно много времени. А ведь выполнение скрипта при этом должно быть остановлено, уже просканированные объекты не должны поменяться до окончания процесса. Получаются небольшие, но неприятные паузы-зависания в работе скрипта. Поэтому современные интерпретаторы применяют различные оптимизации. @@ -152,87 +145,77 @@ function showTime() { ``` Если вы знаете низкоуровневые языки программирования, то более подробно об организации сборки мусора в V8 можно почитать, например, в статье [A tour of V8: Garbage Collection](http://jayconrod.com/posts/55/a-tour-of-v8-garbage-collection). - -[/smart] +```` ## Замыкания -Объекты переменных, о которых шла речь ранее, в главе про замыкания, также подвержены сборке мусора. Они следуют тем же правилам, что и обычные объекты. +Объекты переменных, о которых шла речь ранее, в главе про замыкания, также подвержены сборке мусора. Они следуют тем же правилам, что и обычные объекты. Объект переменных внешней функции существует в памяти до тех пор, пока существует хоть одна внутренняя функция, ссылающаяся на него через свойство `[[Scope]]`. Например: -
      -
    • Обычно объект переменных удаляется по завершении работы функции. Даже если в нём есть объявление внутренней функции: - -```js -function f() { - var value = 123; - - function g() {} // g видна только изнутри -} +- Обычно объект переменных удаляется по завершении работы функции. Даже если в нём есть объявление внутренней функции: -f(); -``` + ```js + function f() { + var value = 123; -В коде выше `value` и `g` являются свойствами объекта переменных. Во время выполнения `f()` её объект переменных находится в текущем стеке выполнения, поэтому жив. По окончанию, он станет недостижимым и будет убран из памяти вместе с остальными локальными переменными. -
    • -
    • ...А вот в этом случае лексическое окружение, включая переменную `value`, будет сохранено: + function g() {} // g видна только изнутри + } -```js -function f() { - var value = 123; + f(); + ``` - function g() {} + В коде выше `value` и `g` являются свойствами объекта переменных. Во время выполнения `f()` её объект переменных находится в текущем стеке выполнения, поэтому жив. По окончанию, он станет недостижимым и будет убран из памяти вместе с остальными локальными переменными. +- ...А вот в этом случае лексическое окружение, включая переменную `value`, будет сохранено: -*!* - return g; -*/!* -} + ```js + function f() { + var value = 123; -var g = f(); // функция g будет жить и сохранит ссылку на объект переменных -``` + function g() {} -В скрытом свойстве `g.[[Scope]]` находится ссылка на объект переменных, в котором была создана `g`. Поэтому этот объект переменных останется в памяти, а в нём -- и `value`. -
    • -
    • -Если `f()` будет вызываться много раз, а полученные функции будут сохраняться, например, складываться в массив, то будут сохраняться и объекты `LexicalEnvironment` с соответствующими значениями `value`: + *!* + return g; + */!* + } -```js -function f() { - var value = Math.random(); + var g = f(); // функция g будет жить и сохранит ссылку на объект переменных + ``` - return function() {}; -} + В скрытом свойстве `g.[[Scope]]` находится ссылка на объект переменных, в котором была создана `g`. Поэтому этот объект переменных останется в памяти, а в нём -- и `value`. +- Если `f()` будет вызываться много раз, а полученные функции будут сохраняться, например, складываться в массив, то будут сохраняться и объекты `LexicalEnvironment` с соответствующими значениями `value`: -// 3 функции, каждая ссылается на свой объект переменных, -// каждый со своим значением value -var arr = [f(), f(), f()]; -``` + ```js + function f() { + var value = Math.random(); -
    • -
    • Объект `LexicalEnvironment` живёт ровно до тех пор, пока на него существуют ссылки. В коде ниже после удаления ссылки на `g` умирает: + return function() { return value; }; + } -```js -function f() { - var value = 123; + // 3 функции, каждая ссылается на свой объект переменных, + // каждый со своим значением value + var arr = [f(), f(), f()]; + ``` +- Объект `LexicalEnvironment` живёт ровно до тех пор, пока на него существуют ссылки. В коде ниже после удаления ссылки на `g` умирает: - function g() {} + ```js + function f() { + var value = 123; - return g; -} + function g() {} -var g = f(); // функция g жива -// а значит в памяти остается соответствующий объект переменных f() + return g; + } -g = null; // ..а вот теперь память будет очищена -``` + var g = f(); // функция g жива + // а значит в памяти остается соответствующий объект переменных f() -
    • -
    + g = null; // ..а вот теперь память будет очищена + ``` -### Оптимизация в V8 и её последствия +### Оптимизация в V8 и её последствия Современные JS-движки делают оптимизации замыканий по памяти. Они анализируют использование переменных и в случае, когда переменная из замыкания абсолютно точно не используется, удаляют её. @@ -242,8 +225,7 @@ g = null; // ..а вот теперь память будет очищена Попробуйте запустить пример ниже с открытой консолью Chrome. Когда он остановится, в консоли наберите `alert(value)`. -```js -//+ run +```js run function f() { var value = Math.random(); @@ -262,8 +244,7 @@ g(); Это может привести к забавным казусам при отладке, вплоть до того что вместо этой переменной будет другая, внешняя: -```js -//+ run +```js run var value = "Сюрприз"; function f() { @@ -280,13 +261,13 @@ var g = f(); g(); ``` -[warn header="Ещё увидимся"] +```warn header="Ещё увидимся" Об этой особенности важно знать. Если вы отлаживаете под Chrome/Opera, то наверняка рано или поздно с ней встретитесь! Это не глюк отладчика, а особенность работы V8, которая, возможно, будет когда-нибудь изменена. Вы всегда сможете проверить, не изменилось ли чего, запустив примеры на этой странице. -[/warn] +``` -## Влияние управления памятью на скорость +## Влияние управления памятью на скорость На создание новых объектов и их удаление тратится время. Это важно иметь в виду в случае, когда важна производительность. @@ -294,8 +275,7 @@ g(); Пример ниже тестирует сложение чисел до данного через рекурсию по сравнению с обычным циклом: -```js -//+ run +```js run function sumTo(n) { // обычный цикл 1+2+...+n var result = 0; for (var i = 1; i <= n; i++) { @@ -319,8 +299,8 @@ timeRecursion = performance.now() - timeRecursion; alert( "Разница в " + (timeRecursion / timeLoop) + " раз" ); ``` -Различие в скорости на таком примере может составлять, в зависимости от интерпретатора, 2-10 раз. +Различие в скорости на таком примере может составлять, в зависимости от интерпретатора, 2-10 раз. Вообще, этот пример -- не показателен. Ещё раз обращаю ваше внимание на то, что такие искусственные "микротесты" часто врут. Правильно их делать -- отдельная наука, которая выходит за рамки этой главы. Но и на практике ускорение в 2-10 раз оптимизацией по количеству объектов (и вообще, любых значений) -- отнюдь не миф, а вполне достижимо. -В реальной жизни в большинстве ситуаций такая оптимизация несущественна, просто потому что "JavaScript и так достаточно быстр". Но она может быть эффективной для "узких мест" кода. +В реальной жизни в большинстве ситуаций такая оптимизация несущественна, просто потому что "JavaScript и так достаточно быстр". Но она может быть эффективной для "узких мест" кода. diff --git a/1-js/5-functions-closures/6-memory-management/family-no-family.png b/1-js/5-functions-closures/6-memory-management/family-no-family.png index d2aeecb5..f72e25f1 100644 Binary files a/1-js/5-functions-closures/6-memory-management/family-no-family.png and b/1-js/5-functions-closures/6-memory-management/family-no-family.png differ diff --git a/1-js/5-functions-closures/6-memory-management/family-no-family@2x.png b/1-js/5-functions-closures/6-memory-management/family-no-family@2x.png index 004d951d..966eedee 100644 Binary files a/1-js/5-functions-closures/6-memory-management/family-no-family@2x.png and b/1-js/5-functions-closures/6-memory-management/family-no-family@2x.png differ diff --git a/1-js/5-functions-closures/6-memory-management/family-no-father-2.png b/1-js/5-functions-closures/6-memory-management/family-no-father-2.png index 6abcd447..994d441a 100644 Binary files a/1-js/5-functions-closures/6-memory-management/family-no-father-2.png and b/1-js/5-functions-closures/6-memory-management/family-no-father-2.png differ diff --git a/1-js/5-functions-closures/6-memory-management/family-no-father-2@2x.png b/1-js/5-functions-closures/6-memory-management/family-no-father-2@2x.png index de0506de..0a54a706 100644 Binary files a/1-js/5-functions-closures/6-memory-management/family-no-father-2@2x.png and b/1-js/5-functions-closures/6-memory-management/family-no-father-2@2x.png differ diff --git a/1-js/5-functions-closures/6-memory-management/family-no-father.png b/1-js/5-functions-closures/6-memory-management/family-no-father.png index 59391ec6..d1599fc4 100644 Binary files a/1-js/5-functions-closures/6-memory-management/family-no-father.png and b/1-js/5-functions-closures/6-memory-management/family-no-father.png differ diff --git a/1-js/5-functions-closures/6-memory-management/family-no-father@2x.png b/1-js/5-functions-closures/6-memory-management/family-no-father@2x.png index 2d10279e..1d399ee9 100644 Binary files a/1-js/5-functions-closures/6-memory-management/family-no-father@2x.png and b/1-js/5-functions-closures/6-memory-management/family-no-father@2x.png differ diff --git a/1-js/5-functions-closures/6-memory-management/family.png b/1-js/5-functions-closures/6-memory-management/family.png index eeb5c210..af4402db 100644 Binary files a/1-js/5-functions-closures/6-memory-management/family.png and b/1-js/5-functions-closures/6-memory-management/family.png differ diff --git a/1-js/5-functions-closures/6-memory-management/family@2x.png b/1-js/5-functions-closures/6-memory-management/family@2x.png index 97d73db4..bd9f1e96 100644 Binary files a/1-js/5-functions-closures/6-memory-management/family@2x.png and b/1-js/5-functions-closures/6-memory-management/family@2x.png differ diff --git a/1-js/5-functions-closures/7-with/1-with-function/solution.md b/1-js/5-functions-closures/7-with/1-with-function/solution.md index d4a5284b..7d8e39ef 100644 --- a/1-js/5-functions-closures/7-with/1-with-function/solution.md +++ b/1-js/5-functions-closures/7-with/1-with-function/solution.md @@ -2,8 +2,7 @@ Соответственно, будет выведено `2`: -```js -//+ run +```js run function f() { alert(1) } diff --git a/1-js/5-functions-closures/7-with/1-with-function/task.md b/1-js/5-functions-closures/7-with/1-with-function/task.md index bdf540ec..5bddc9ac 100644 --- a/1-js/5-functions-closures/7-with/1-with-function/task.md +++ b/1-js/5-functions-closures/7-with/1-with-function/task.md @@ -1,6 +1,8 @@ -# With + функция +importance: 5 + +--- -[importance 5] +# With + функция Какая из функций будет вызвана? diff --git a/1-js/5-functions-closures/7-with/2-with-variables/solution.md b/1-js/5-functions-closures/7-with/2-with-variables/solution.md index 531c8a81..2e7a2e90 100644 --- a/1-js/5-functions-closures/7-with/2-with-variables/solution.md +++ b/1-js/5-functions-closures/7-with/2-with-variables/solution.md @@ -4,8 +4,7 @@ Код в задаче эквивалентен такому: -```js -//+ run +```js run var a = 1; *!* var b; diff --git a/1-js/5-functions-closures/7-with/2-with-variables/task.md b/1-js/5-functions-closures/7-with/2-with-variables/task.md index 13705a1d..f0041094 100644 --- a/1-js/5-functions-closures/7-with/2-with-variables/task.md +++ b/1-js/5-functions-closures/7-with/2-with-variables/task.md @@ -1,6 +1,8 @@ -# With + переменные +importance: 5 + +--- -[importance 5] +# With + переменные Что выведет этот код? diff --git a/1-js/5-functions-closures/7-with/article.md b/1-js/5-functions-closures/7-with/article.md index 9c6434ff..88995924 100644 --- a/1-js/5-functions-closures/7-with/article.md +++ b/1-js/5-functions-closures/7-with/article.md @@ -1,10 +1,11 @@ -# Устаревшая конструкция "with" +# Устаревшая конструкция "with" -Конструкция `with` позволяет использовать в качестве области видимости для переменных произвольный объект. +Конструкция `with` позволяет использовать в качестве области видимости для переменных произвольный объект. В современном JavaScript от этой конструкции отказались. С `use strict` она не работает, но её ещё можно найти в старом коде, так что стоит познакомиться с ней, чтобы если что -- понимать, о чём речь. [cut] + Синтаксис: ```js @@ -15,12 +16,11 @@ with(obj) { Любое обращение к переменной внутри `with` сначала ищет её среди свойств `obj`, а только потом -- вне `with`. -## Пример +## Пример В примере ниже переменная будет взята не из глобальной области, а из `obj`: -```js -//+ run +```js run no-strict var a = 5; var obj = { @@ -36,8 +36,7 @@ with(obj) { Попробуем получить переменную, которой в `obj` нет: -```js -//+ run +```js run no-strict var b = 1; var obj = { @@ -55,8 +54,7 @@ with(obj) { Особенно забавно выглядит применение вложенных `with`: -```js -//+ run +```js run no-strict var obj = { weight: 10, size: { @@ -76,15 +74,13 @@ with(obj) { Свойства из разных объектов используются как обычные переменные... Магия! Порядок поиска переменных в выделенном коде: `size => obj => window`. - ## Изменения переменной При использовании `with`, как и во вложенных функциях -- переменная изменяется в той области, где была найдена. Например: -```js -//+ run +```js run no-strict var obj = { a: 10 } @@ -97,69 +93,61 @@ with(obj) { alert( obj.a ); // 20, переменная была изменена в объекте ``` -## Почему отказались от with? +## Почему отказались от with? Есть несколько причин. -
      -
    1. В современном стандарте `JavaScript` отказались от `with`, потому что конструкция `with` подвержена ошибкам и непрозрачна. +1. В современном стандарте `JavaScript` отказались от `with`, потому что конструкция `with` подвержена ошибкам и непрозрачна. -Проблемы возникают в том случае, когда в `with(obj)` присваивается переменная, которая по замыслу должна быть в свойствах `obj`, но ее там нет. + Проблемы возникают в том случае, когда в `with(obj)` присваивается переменная, которая по замыслу должна быть в свойствах `obj`, но ее там нет. -Например: + Например: -```js -//+ run -var obj = { - weight: 10 -}; + ```js run no-strict + var obj = { + weight: 10 + }; -with(obj) { - weight = 20; // (1) - size = 35; // (2) -} + with(obj) { + weight = 20; // (1) + size = 35; // (2) + } -alert( obj.size ); -alert( window.size ); -``` + alert( obj.size ); + alert( window.size ); + ``` -В строке `(2)` присваивается свойство, отсутствующее в `obj`. В результате интерпретатор, не найдя его, создает новую глобальную переменную `window.size`. + В строке `(2)` присваивается свойство, отсутствующее в `obj`. В результате интерпретатор, не найдя его, создает новую глобальную переменную `window.size`. -Такие ошибки редки, но очень сложны в отладке, особенно если `size` изменилась не в `window`, а где-нибудь во внешнем `LexicalEnvironment`. -
    2. -
    3. Еще одна причина -- алгоритмы сжатия JavaScript не любят `with`. Перед выкладкой на сервер JavaScript сжимают. Для этого есть много инструментов, например [Closure Compiler](http://code.google.com/intl/ru-RU/closure/compiler/) и [UglifyJS](https://github.com/mishoo/UglifyJS). Обычно они переименовывают локальные переменные в более короткие имена, но не свойства объектов. С конструкцией `with` до запуска кода непонятно -- откуда будет взята переменная. Поэтому выходит, что, на всякий случай (если это свойство), лучше её не переименовывать. Таким образом, качество сжатия кода страдает.
    4. -
    5. Ну и, наконец, производительность -- усложнение поиска переменной из-за `with` влечет дополнительные накладные расходы. + Такие ошибки редки, но очень сложны в отладке, особенно если `size` изменилась не в `window`, а где-нибудь во внешнем `LexicalEnvironment`. +2. Еще одна причина -- алгоритмы сжатия JavaScript не любят `with`. Перед выкладкой на сервер JavaScript сжимают. Для этого есть много инструментов, например [Closure Compiler](http://code.google.com/intl/ru-RU/closure/compiler/) и [UglifyJS](https://github.com/mishoo/UglifyJS). Обычно они переименовывают локальные переменные в более короткие имена, но не свойства объектов. С конструкцией `with` до запуска кода непонятно -- откуда будет взята переменная. Поэтому выходит, что, на всякий случай (если это свойство), лучше её не переименовывать. Таким образом, качество сжатия кода страдает. +3. Ну и, наконец, производительность -- усложнение поиска переменной из-за `with` влечет дополнительные накладные расходы. -Современные движки применяют много внутренних оптимизаций, ряд которых не могут быть применены к коду, в котором есть `with`. + Современные движки применяют много внутренних оптимизаций, ряд которых не могут быть применены к коду, в котором есть `with`. -Вот, к примеру, запустите этот код в современном браузере. Производительность функции `fast` существенно отличается `slow` с пустым(!) `with`. И дело тут именно в `with`, т.к. наличие этой конструкции препятствует оптимизации. + Вот, к примеру, запустите этот код в современном браузере. Производительность функции `fast` существенно отличается `slow` с пустым(!) `with`. И дело тут именно в `with`, т.к. наличие этой конструкции препятствует оптимизации. -```js -//+ run -var i = 0; + ```js run no-strict + var i = 0; -function fast() { - i++; -} - -function slow() { - with(i) {} - i++; -} + function fast() { + i++; + } + function slow() { + with(i) {} + i++; + } -var time = performance.now(); -while (i < 1000000) fast(); -alert( "Без with: " + (performance.now() - time) ); - -var time = performance.now(); -i = 0; -while (i < 1000000) slow(); -alert( "С with: " + (performance.now() - time) ); -``` + var time = performance.now(); + while (i < 1000000) fast(); + alert( "Без with: " + (performance.now() - time) ); -
    6. -
    + var time = performance.now(); + i = 0; + while (i < 1000000) slow(); + alert( "С with: " + (performance.now() - time) ); + ``` ### Замена with @@ -183,8 +171,6 @@ s.left = '0'; ## Итого -
      -
    • Конструкция `with(obj) { ... }` использует `obj` как дополнительную область видимости. Все переменные, к которым идет обращение внутри блока, сначала ищутся в `obj`.
    • -
    • Конструкция `with` устарела и не рекомендуется по ряду причин. Избегайте её.
    • -
    +- Конструкция `with(obj) { ... }` использует `obj` как дополнительную область видимости. Все переменные, к которым идет обращение внутри блока, сначала ищутся в `obj`. +- Конструкция `with` устарела и не рекомендуется по ряду причин. Избегайте её. diff --git a/1-js/6-objects-more/1-object-methods/1-call-array-this/solution.md b/1-js/6-objects-more/1-object-methods/1-call-array-this/solution.md index dad88eea..23abcb61 100644 --- a/1-js/6-objects-more/1-object-methods/1-call-array-this/solution.md +++ b/1-js/6-objects-more/1-object-methods/1-call-array-this/solution.md @@ -1,9 +1,8 @@ -Вызов `arr[2]()` -- это обращение к методу объекта `obj[method]()`, в роли `obj` выступает `arr`, а в роли метода: `2`. +Вызов `arr[2]()` -- это обращение к методу объекта `obj[method]()`, в роли `obj` выступает `arr`, а в роли метода: `2`. Поэтому, как это бывает при вызове функции как метода, функция `arr[2]` получит `this = arr` и выведет массив: -```js -//+ run +```js run var arr = ["a", "b"]; arr.push(function() { diff --git a/1-js/6-objects-more/1-object-methods/1-call-array-this/task.md b/1-js/6-objects-more/1-object-methods/1-call-array-this/task.md index 6ab74bfc..e5a954d6 100644 --- a/1-js/6-objects-more/1-object-methods/1-call-array-this/task.md +++ b/1-js/6-objects-more/1-object-methods/1-call-array-this/task.md @@ -1,6 +1,8 @@ -# Вызов в контексте массива +importance: 5 + +--- -[importance 5] +# Вызов в контексте массива Каким будет результат? Почему? diff --git a/1-js/6-objects-more/1-object-methods/2-check-syntax/solution.md b/1-js/6-objects-more/1-object-methods/2-check-syntax/solution.md index b030823d..eab0877c 100644 --- a/1-js/6-objects-more/1-object-methods/2-check-syntax/solution.md +++ b/1-js/6-objects-more/1-object-methods/2-check-syntax/solution.md @@ -1,9 +1,8 @@ -**Ошибка**! +**Ошибка**! Попробуйте: -```js -//+ run +```js run var obj = { go: function() { alert(this) @@ -19,8 +18,7 @@ var obj = { JavaScript игнорирует перевод строки перед скобкой `(obj.go)()` и читает этот код как: -```js -//+ no-beautify +```js no-beautify var obj = { go:... }(obj.go)() ``` diff --git a/1-js/6-objects-more/1-object-methods/2-check-syntax/task.md b/1-js/6-objects-more/1-object-methods/2-check-syntax/task.md index 44a93141..9c8b5b15 100644 --- a/1-js/6-objects-more/1-object-methods/2-check-syntax/task.md +++ b/1-js/6-objects-more/1-object-methods/2-check-syntax/task.md @@ -1,11 +1,12 @@ -# Проверка синтаксиса +importance: 2 + +--- -[importance 2] +# Проверка синтаксиса Каков будет результат этого кода? -```js -//+ no-beautify +```js no-beautify var obj = { go: function() { alert(this) } } diff --git a/1-js/6-objects-more/1-object-methods/3-why-this/solution.md b/1-js/6-objects-more/1-object-methods/3-why-this/solution.md index 693d3944..d22dc560 100644 --- a/1-js/6-objects-more/1-object-methods/3-why-this/solution.md +++ b/1-js/6-objects-more/1-object-methods/3-why-this/solution.md @@ -1,30 +1,26 @@ -
      -
    1. Обычный вызов функции в контексте объекта.
    2. -
    3. То же самое, скобки ни на что не влияют.
    4. -
    5. Здесь не просто вызов `obj.method()`, а более сложный вызов вида `(выражение).method()`. Такой вызов работает, как если бы он был разбит на две строки: - -```js -//+ no-beautify -f = obj.go; // сначала вычислить выражение -f(); // потом вызвать то, что получилось -``` - -При этом `f()` выполняется как обычная функция, без передачи `this`. -
    6. -
    7. Здесь также слева от точки находится выражение, вызов аналогичен двум строкам.
    8. -
    + +1. Обычный вызов функции в контексте объекта. +2. То же самое, скобки ни на что не влияют. +3. Здесь не просто вызов `obj.method()`, а более сложный вызов вида `(выражение).method()`. Такой вызов работает, как если бы он был разбит на две строки: + + ```js no-beautify + f = obj.go; // сначала вычислить выражение + f(); // потом вызвать то, что получилось + ``` + + При этом `f()` выполняется как обычная функция, без передачи `this`. +4. Здесь также слева от точки находится выражение, вызов аналогичен двум строкам. В спецификации это объясняется при помощи специального внутреннего типа [Reference Type](http://es5.github.com/x8.html#x8.7). Если подробнее -- то `obj.go()` состоит из двух операций: -
      -
    1. Сначала получить свойство `obj.go`.
    2. -
    3. Потом вызвать его как функцию.
    4. -
    + +1. Сначала получить свойство `obj.go`. +2. Потом вызвать его как функцию. Но откуда на шаге 2 получить `this`? Как раз для этого операция получения свойства `obj.go` возвращает значение особого типа `Reference Type`, который в дополнение к свойству `go` содержит информацию об `obj`. Далее, на втором шаге, вызов его при помощи скобок `()` правильно устанавливает `this`. -**Любые другие операции, кроме вызова, превращают `Reference Type` в обычный тип, в данном случае -- функцию `go` (так уж этот тип устроен).** +**Любые другие операции, кроме вызова, превращают `Reference Type` в обычный тип, в данном случае -- функцию `go` (так уж этот тип устроен).** Поэтому получается, что `(method = obj.go)` присваивает в переменную `method` функцию `go`, уже без всякой информации об объекте `obj`. diff --git a/1-js/6-objects-more/1-object-methods/3-why-this/task.md b/1-js/6-objects-more/1-object-methods/3-why-this/task.md index 486c3eb7..49665eb0 100644 --- a/1-js/6-objects-more/1-object-methods/3-why-this/task.md +++ b/1-js/6-objects-more/1-object-methods/3-why-this/task.md @@ -1,14 +1,15 @@ -# Почему this присваивается именно так? +importance: 3 + +--- -[importance 3] +# Почему this присваивается именно так? Вызовы `(1)` и `(2)` в примере ниже работают не так, как `(3)` и `(4)`: -```js -//+ run no-beautify +```js run no-beautify "use strict" -var obj, f; +var obj, method; obj = { go: function() { alert(this); } diff --git a/1-js/6-objects-more/1-object-methods/4-object-property-this/solution.md b/1-js/6-objects-more/1-object-methods/4-object-property-this/solution.md index 82f562d9..e0a77e75 100644 --- a/1-js/6-objects-more/1-object-methods/4-object-property-this/solution.md +++ b/1-js/6-objects-more/1-object-methods/4-object-property-this/solution.md @@ -1,22 +1,19 @@ -**Ответ: пустая строка.** - -```js -//+ run -var name = ""; +**Ответ: `undefined`.** +```js run var user = { - name: "Василий", + firstName: "Василий", *!* export: this // (*) */!* }; -alert( user.export.name ); +alert( user.export.firstName ); ``` Объявление объекта само по себе не влияет на `this`. Никаких функций, которые могли бы повлиять на контекст, здесь нет. -Так как код находится вообще вне любых функций, то `this` в нём равен `window` (при `use strict` было бы `undefined`). +Так как код находится вообще вне любых функций, то `this` в нём равен `window` (в браузере так всегда для кода вне функций, вне зависимости от `use strict`). -Получается, что в строке `(*)` мы имеем `export: window`, так что далее `alert(user.export.name)` выводит свойство `window.name`, то есть глобальную переменную `name`, которая равна пустой строке. +Получается, что в строке `(*)` мы имеем `export: window`, так что далее `alert(user.export.firstName)` выводит свойство `window.firstName`, которое не определено. diff --git a/1-js/6-objects-more/1-object-methods/4-object-property-this/task.md b/1-js/6-objects-more/1-object-methods/4-object-property-this/task.md index 6b2ec0ac..5f3c983b 100644 --- a/1-js/6-objects-more/1-object-methods/4-object-property-this/task.md +++ b/1-js/6-objects-more/1-object-methods/4-object-property-this/task.md @@ -1,18 +1,18 @@ -# Значение this в объявлении объекта +importance: 5 + +--- -[importance 5] +# Значение this в объявлении объекта Что выведет `alert` в этом коде? Почему? ```js -var name = ""; - var user = { - name: "Василий", + firstName: "Василий", export: this }; -alert( user.export.name ); +alert( user.export.firstName ); ``` diff --git a/1-js/6-objects-more/1-object-methods/5-return-this/solution.md b/1-js/6-objects-more/1-object-methods/5-return-this/solution.md index b34977b4..cac06109 100644 --- a/1-js/6-objects-more/1-object-methods/5-return-this/solution.md +++ b/1-js/6-objects-more/1-object-methods/5-return-this/solution.md @@ -1,5 +1,5 @@ **Ответ: `Василий`.** -Вызов `user.export()` использует `this`, который равен объекту до точки, то есть внутри `user.export()` строка `return this` возвращает объект `user`. +Вызов `user.export()` использует `this`, который равен объекту до точки, то есть внутри `user.export()` строка `return this` возвращает объект `user`. В итоге выводится свойство `name` объекта `user`, равное `"Василий"`. diff --git a/1-js/6-objects-more/1-object-methods/5-return-this/task.md b/1-js/6-objects-more/1-object-methods/5-return-this/task.md index 03ebd8cb..81dfae9a 100644 --- a/1-js/6-objects-more/1-object-methods/5-return-this/task.md +++ b/1-js/6-objects-more/1-object-methods/5-return-this/task.md @@ -1,6 +1,8 @@ -# Возврат this +importance: 5 + +--- -[importance 5] +# Возврат this Что выведет `alert` в этом коде? Почему? diff --git a/1-js/6-objects-more/1-object-methods/6-return-object-this/solution.md b/1-js/6-objects-more/1-object-methods/6-return-object-this/solution.md index 2df08422..d581399d 100644 --- a/1-js/6-objects-more/1-object-methods/6-return-object-this/solution.md +++ b/1-js/6-objects-more/1-object-methods/6-return-object-this/solution.md @@ -1,14 +1,12 @@ **Ответ: `Василий`.** -Во время выполнения `user.export()` значение `this = user`. +Во время выполнения `user.export()` значение `this = user`. При создании объекта `{ value: this }`, в свойство `value` копируется ссылка на текущий контекст, то есть на `user`. Получается что `user.export().value == user`. - -```js -//+ run +```js run var name = ""; var user = { @@ -23,4 +21,4 @@ var user = { }; alert( user.export().value == user ); // true -``` \ No newline at end of file +``` diff --git a/1-js/6-objects-more/1-object-methods/6-return-object-this/task.md b/1-js/6-objects-more/1-object-methods/6-return-object-this/task.md index b78e6553..93376340 100644 --- a/1-js/6-objects-more/1-object-methods/6-return-object-this/task.md +++ b/1-js/6-objects-more/1-object-methods/6-return-object-this/task.md @@ -1,6 +1,8 @@ -# Возврат объекта с this +importance: 5 + +--- -[importance 5] +# Возврат объекта с this Что выведет `alert` в этом коде? Почему? diff --git a/1-js/6-objects-more/1-object-methods/7-calculator/solution.md b/1-js/6-objects-more/1-object-methods/7-calculator/solution.md index 1849cc94..3090992e 100644 --- a/1-js/6-objects-more/1-object-methods/7-calculator/solution.md +++ b/1-js/6-objects-more/1-object-methods/7-calculator/solution.md @@ -1,7 +1,6 @@ -```js -//+ run demo +```js run demo var calculator = { sum: function() { return this.a + this.b; diff --git a/1-js/6-objects-more/1-object-methods/7-calculator/task.md b/1-js/6-objects-more/1-object-methods/7-calculator/task.md index 5e5790fc..f6f23d94 100644 --- a/1-js/6-objects-more/1-object-methods/7-calculator/task.md +++ b/1-js/6-objects-more/1-object-methods/7-calculator/task.md @@ -1,13 +1,14 @@ +importance: 5 + +--- + # Создайте калькулятор -[importance 5] +Создайте объект `calculator` с тремя методами: -Создайте объект `calculator` с тремя методами: -
      -
    • `read()` запрашивает `prompt` два значения и сохраняет их как свойства объекта
    • -
    • `sum()` возвращает сумму этих двух значений
    • -
    • `mul()` возвращает произведение этих двух значений
    • -
    +- `read()` запрашивает `prompt` два значения и сохраняет их как свойства объекта +- `sum()` возвращает сумму этих двух значений +- `mul()` возвращает произведение этих двух значений ```js var calculator = { @@ -19,4 +20,5 @@ alert( calculator.sum() ); alert( calculator.mul() ); ``` -[demo /] +[demo] + diff --git a/1-js/6-objects-more/1-object-methods/8-chain-calls/solution.md b/1-js/6-objects-more/1-object-methods/8-chain-calls/solution.md index 8d89b88e..3c87d00e 100644 --- a/1-js/6-objects-more/1-object-methods/8-chain-calls/solution.md +++ b/1-js/6-objects-more/1-object-methods/8-chain-calls/solution.md @@ -1,7 +1,6 @@ Решение состоит в том, чтобы каждый раз возвращать текущий объект. Это делается добавлением `return this` в конце каждого метода: -```js -//+ run +```js run var ladder = { step: 0, up: function() { diff --git a/1-js/6-objects-more/1-object-methods/8-chain-calls/task.md b/1-js/6-objects-more/1-object-methods/8-chain-calls/task.md index 73dc4e8e..5ece84a0 100644 --- a/1-js/6-objects-more/1-object-methods/8-chain-calls/task.md +++ b/1-js/6-objects-more/1-object-methods/8-chain-calls/task.md @@ -1,6 +1,8 @@ -# Цепочка вызовов +importance: 2 + +--- -[importance 2] +# Цепочка вызовов Есть объект "лестница" ladder: diff --git a/1-js/6-objects-more/1-object-methods/article.md b/1-js/6-objects-more/1-object-methods/article.md index 4a70f595..47f6c5b3 100644 --- a/1-js/6-objects-more/1-object-methods/article.md +++ b/1-js/6-objects-more/1-object-methods/article.md @@ -1,14 +1,14 @@ # Методы объектов, this До этого мы говорили об объекте лишь как о хранилище значений. Теперь пойдём дальше и поговорим об объектах как о сущностях со своими функциями ("методами"). + [cut] ## Методы у объектов При объявлении объекта можно указать свойство-функцию, например: -```js -//+ run +```js run var user = { name: 'Василий', @@ -29,8 +29,7 @@ user.sayHi(); Свойства-функции называют "методами" объектов. Их можно добавлять и удалять в любой момент, в том числе и явным присваиванием: -```js -//+ run +```js run var user = { name: 'Василий' }; @@ -49,12 +48,11 @@ user.sayHi = function() { // присвоили метод после созда Для полноценной работы метод должен иметь доступ к данным объекта. В частности, вызов `user.sayHi()` может захотеть вывести имя пользователя. -**Для доступа к текущему объекту из метода используется ключевое слово `this`**. +**Для доступа к текущему объекту из метода используется ключевое слово `this`**. Значением `this` является объект перед "точкой", в контексте которого вызван метод, например: -```js -//+ run +```js run var user = { name: 'Василий', @@ -66,7 +64,7 @@ var user = { user.sayHi(); // sayHi в контексте user ``` -Здесь при выполнении функции `user.sayHi()` в `this` будет храниться ссылка на текущий объект `user`. +Здесь при выполнении функции `user.sayHi()` в `this` будет храниться ссылка на текущий объект `user`. Вместо `this` внутри `sayHi` можно было бы обратиться к объекту, используя переменную `user`: @@ -80,8 +78,7 @@ user.sayHi(); // sayHi в контексте user ...Однако, такое решение нестабильно. Если мы решим скопировать объект в другую переменную, например `admin = user`, а в переменную `user` записать что-то другое -- обращение будет совсем не по адресу: -```js -//+ run +```js run var user = { name: 'Василий', @@ -100,12 +97,11 @@ admin.sayHi(); // упс! внутри sayHi обращение по старо Через `this` метод может не только обратиться к любому свойству объекта, но и передать куда-то ссылку на сам объект целиком: -```js -//+ run no-beautify +```js run no-beautify var user = { name: 'Василий', -*!* +*!* sayHi: function() { showName(this); // передать текущий объект в showName } @@ -113,13 +109,13 @@ var user = { }; function showName(namedObj) { - alert( namedObj.name ); + alert( namedObj.name ); } user.sayHi(); // Василий ``` -## Подробнее про this +## Подробнее про this Любая функция может иметь в себе `this`. Совершенно неважно, объявлена ли она в объекте или отдельно от него. @@ -128,7 +124,7 @@ user.sayHi(); // Василий Например, такая функция, объявленная без объекта, вполне допустима: ```js -function sayHi() { +function sayHi() { alert( *!*this.firstName*/!* ); } ``` @@ -137,17 +133,16 @@ function sayHi() { **Если одну и ту же функцию запускать в контексте разных объектов, она будет получать разный `this`:** -```js -//+ run no-beautify +```js run no-beautify var user = { firstName: "Вася" }; var admin = { firstName: "Админ" }; -function func() { +function func() { alert( this.firstName ); } -user.f = func; -admin.g = func; +user.f = func; +admin.g = func; *!* // this равен объекту перед точкой: @@ -161,14 +156,13 @@ admin['g'](); // Админ (не важно, доступ к объекту ч ## Значение this при вызове без контекста -Если функция использует `this` -- это подразумевает работу с объектом. Но и прямой вызов `func()` технически возможен. +Если функция использует `this` -- это подразумевает работу с объектом. Но и прямой вызов `func()` технически возможен. Как правило, такая ситуация возникает при ошибке в разработке. При этом `this` получает значение `window`, глобального объекта: -```js -//+ run +```js run function func() { alert( this ); // выведет [object Window] или [object global] } @@ -180,8 +174,7 @@ func(); А в режиме `use strict` вместо глобального объекта `this` будет `undefined`: -```js -//+ run +```js run function func() { "use strict"; alert( this ); // выведет undefined (кроме IE9-) @@ -194,12 +187,11 @@ func(); ## Ссылочный тип -Контекст `this` никак не привязан к функции, даже если она создана в объявлении объекта. Чтобы `this` передался, нужно вызвать функцию именно через точку (или квадратные скобки). +Контекст `this` никак не привязан к функции, даже если она создана в объявлении объекта. Чтобы `this` передался, нужно вызвать функцию именно через точку (или квадратные скобки). Любой более хитрый вызов приведёт к потере контекста, например: -```js -//+ run no-beautify +```js run no-beautify var user = { name: "Вася", hi: function() { alert(this.name); }, @@ -214,7 +206,7 @@ user.hi(); // Вася (простой вызов работает) */!* ``` -В последней строке примера метод получен в результате выполнения тернарного оператора и тут же вызван. Но `this` при этом теряется. +В последней строке примера метод получен в результате выполнения тернарного оператора и тут же вызван. Но `this` при этом теряется. Если хочется понять, почему, то причина кроется в деталях работы вызова `obj.method()`. @@ -223,22 +215,17 @@ user.hi(); // Вася (простой вызов работает) Функция, как мы говорили раньше, сама по себе не запоминает контекст. Чтобы "донести его" до скобок, JavaScript применяет "финт ушами" -- точка возвращает не функцию, а значение специального "ссылочного" типа [Reference Type](https://people.mozilla.org/~jorendorff/es6-draft.html#sec-reference-specification-type). Этот тип представляет собой связку "base-name-strict", где: -
      -
    • *base* -- как раз объект,
    • -
    • *name* -- имя свойства,
    • -
    • *strict* -- вспомогательный флаг для передачи `use strict`.
    • -
    + +- *base* -- как раз объект, +- *name* -- имя свойства, +- *strict* -- вспомогательный флаг для передачи `use strict`. То есть, ссылочный тип (Reference Type) -- это своеобразное "три-в-одном". Он существует исключительно для целей спецификации, мы его не видим, поскольку любой оператор тут же от него избавляется: -
      -
    • Скобки `()` получают из `base` значение свойства `name` и вызывают в контексте base.
    • -
    • Другие операторы получают из `base` значение свойства `name` и используют, а остальные компоненты игнорируют.
    • -
    +- Скобки `()` получают из `base` значение свойства `name` и вызывают в контексте base. +- Другие операторы получают из `base` значение свойства `name` и используют, а остальные компоненты игнорируют. Поэтому любая операция над результатом операции получения свойства, кроме вызова, приводит к потере контекста. Аналогично работает и получение свойства через квадратные скобки `obj[method]`. - - diff --git a/1-js/6-objects-more/2-object-conversion/1-array-equals-string/solution.md b/1-js/6-objects-more/2-object-conversion/1-array-equals-string/solution.md index b7a70199..0f6399fb 100644 --- a/1-js/6-objects-more/2-object-conversion/1-array-equals-string/solution.md +++ b/1-js/6-objects-more/2-object-conversion/1-array-equals-string/solution.md @@ -1,4 +1,4 @@ -Если с одной стороны -- объект, а с другой -- нет, то сначала приводится объект. +Если с одной стороны -- объект, а с другой -- нет, то сначала приводится объект. В данном случае сравнение означает численное приведение. У массивов нет `valueOf`, поэтому вызывается `toString`, который возвращает список элементов через запятую. @@ -7,8 +7,7 @@ P.S. По той же причине верны равенства: -```js -//+ run +```js run alert( ['x', 'y'] == 'x,y' ); // true alert( [] == '' ); // true ``` diff --git a/1-js/6-objects-more/2-object-conversion/1-array-equals-string/task.md b/1-js/6-objects-more/2-object-conversion/1-array-equals-string/task.md index 190bbeb4..b15e3d4a 100644 --- a/1-js/6-objects-more/2-object-conversion/1-array-equals-string/task.md +++ b/1-js/6-objects-more/2-object-conversion/1-array-equals-string/task.md @@ -1,11 +1,12 @@ -# ['x'] == 'x' +importance: 5 + +--- -[importance 5] +# ['x'] == 'x' Почему результат `true` ? -```js -//+ run +```js run alert( ['x'] == 'x' ); ``` diff --git a/1-js/6-objects-more/2-object-conversion/2-tostring-valueof/task.md b/1-js/6-objects-more/2-object-conversion/2-tostring-valueof/task.md index 86bd17cb..5cba3590 100644 --- a/1-js/6-objects-more/2-object-conversion/2-tostring-valueof/task.md +++ b/1-js/6-objects-more/2-object-conversion/2-tostring-valueof/task.md @@ -1,6 +1,8 @@ -# Преобразование +importance: 5 + +--- -[importance 5] +# Преобразование Объявлен объект с `toString` и `valueOf`. diff --git a/1-js/6-objects-more/2-object-conversion/3-compare-empty-arrays/solution.md b/1-js/6-objects-more/2-object-conversion/3-compare-empty-arrays/solution.md index ef69d31c..8f482d8d 100644 --- a/1-js/6-objects-more/2-object-conversion/3-compare-empty-arrays/solution.md +++ b/1-js/6-objects-more/2-object-conversion/3-compare-empty-arrays/solution.md @@ -6,29 +6,22 @@ # Ответ по второму равенству -
      -
    1. Первым делом, обе части сравнения вычисляются. Справа находится `![]`. Логическое НЕ `'!'` преобразует аргумент к логическому типу. Массив является объектом, так что это `true`. Значит, правая часть становится `![] = !true = false`. Так что получили: +1. Первым делом, обе части сравнения вычисляются. Справа находится `![]`. Логическое НЕ `'!'` преобразует аргумент к логическому типу. Массив является объектом, так что это `true`. Значит, правая часть становится `![] = !true = false`. Так что получили: -```js -alert( [] == false ); -``` + ```js + alert( [] == false ); + ``` +2. Проверка равенства между объектом и примитивом вызывает численное преобразование объекта. -
    2. -
    3. Проверка равенства между объектом и примитивом вызывает численное преобразование объекта. + У массива нет `valueOf`, сработает `toString` и преобразует массив в список элементов, то есть - в пустую строку: -У массива нет `valueOf`, сработает `toString` и преобразует массив в список элементов, то есть - в пустую строку: + ```js + alert( '' == false ); + ``` +3. Сравнение различных типов вызывает численное преобразование слева и справа: -```js -alert( '' == false ); -``` + ```js + alert( 0 == 0 ); + ``` -
    4. -
    5. Сравнение различных типов вызывает численное преобразование слева и справа: - -```js -alert( 0 == 0 ); -``` - -Теперь результат очевиден. -
    6. -
    \ No newline at end of file + Теперь результат очевиден. diff --git a/1-js/6-objects-more/2-object-conversion/3-compare-empty-arrays/task.md b/1-js/6-objects-more/2-object-conversion/3-compare-empty-arrays/task.md index 6945834b..3e8d50fb 100644 --- a/1-js/6-objects-more/2-object-conversion/3-compare-empty-arrays/task.md +++ b/1-js/6-objects-more/2-object-conversion/3-compare-empty-arrays/task.md @@ -1,11 +1,12 @@ -# Почему [] == [] неверно, а [ ] == ![ ] верно? +importance: 5 + +--- -[importance 5] +# Почему [] == [] неверно, а [ ] == ![ ] верно? Почему первое равенство -- неверно, а второе -- верно? -```js -//+ run +```js run alert( [] == [] ); // false alert( [] == ![] ); // true ``` diff --git a/1-js/6-objects-more/2-object-conversion/4-object-types-conversion-questions/solution.md b/1-js/6-objects-more/2-object-conversion/4-object-types-conversion-questions/solution.md index 3d7c6689..e1685c34 100644 --- a/1-js/6-objects-more/2-object-conversion/4-object-types-conversion-questions/solution.md +++ b/1-js/6-objects-more/2-object-conversion/4-object-types-conversion-questions/solution.md @@ -1,10 +1,9 @@ -```js -//+ no-beautify +```js no-beautify new Date(0) - 0 = 0 // (1) new Array(1)[0] + "" = "undefined" // (2) -({})[0]
 = undefined // (3) +({})[0] = undefined // (3) [1] + 1 = "11" // (4) [1,2] + [3,4] = "1,23,4" // (5) [] + null + 1 = "null1" // (6) @@ -12,24 +11,21 @@ new Array(1)[0] + "" = "undefined" // (2) ({} + {}) = "[object Object][object Object]" // (8) ``` -
      -
    1. `new Date(0)` -- дата, созданная по миллисекундам и соответствующая 0мс от 1 января 1970 года 00:00:00 UTC. Оператор минус `-` преобразует дату обратно в число миллисекунд, то есть в `0`.
    2. -
    3. `new Array(num)` при вызове с единственным аргументом-числом создаёт массив данной длины, без элементов. Поэтому его нулевой элемент равен `undefined`, при сложении со строкой получается строка `"undefined"`.
    4. -
    5. Фигурные скобки -- это создание пустого объекта, у него нет свойства `'0'`. Так что значением будет `undefined`. -Обратите внимание на внешние, круглые скобки. Если их убрать и запустить `{}[0]` в отладочной консоли браузера -- будет `0`, т.к. скобки `{}` будут восприняты как пустой блок кода, после которого идёт массив.
    6. -
    7. Массив преобразуется в строку `"1"`. Оператор `"+"` при сложении со строкой приводит второй аргумент к строке -- значит будет `"1" + "1" = "11"`.
    8. -
    9. Массивы приводятся к строке и складываются.
    10. -
    11. Массив преобразуется в пустую строку `"" + null + 1`, оператор `"+"` видит, что слева строка и преобразует `null` к строке, получается `"null" + 1`, и в итоге `"null1"`.
    12. -
    13. `[[0]]` -- это вложенный массив `[0]` внутри внешнего `[ ]`. Затем мы берём от него нулевой элемент, и потом еще раз. +1. `new Date(0)` -- дата, созданная по миллисекундам и соответствующая 0 мс от 1 января 1970 года 00:00:00 UTC. Оператор минус `-` преобразует дату обратно в число миллисекунд, то есть в `0`. +2. `new Array(num)` при вызове с единственным аргументом-числом создаёт массив данной длины, без элементов. Поэтому его нулевой элемент равен `undefined`, при сложении со строкой получается строка `"undefined"`. +3. Фигурные скобки -- это создание пустого объекта, у него нет свойства `'0'`. Так что значением будет `undefined`. +Обратите внимание на внешние, круглые скобки. Если их убрать и запустить `{}[0]` в отладочной консоли браузера -- будет `0`, т.к. скобки `{}` будут восприняты как пустой блок кода, после которого идёт массив. +4. Массив преобразуется в строку `"1"`. Оператор `"+"` при сложении со строкой приводит второй аргумент к строке -- значит будет `"1" + "1" = "11"`. +5. Массивы приводятся к строке и складываются. +6. Массив преобразуется в пустую строку `"" + null + 1`, оператор `"+"` видит, что слева строка и преобразует `null` к строке, получается `"null" + 1`, и в итоге `"null1"`. +7. `[[0]]` -- это вложенный массив `[0]` внутри внешнего `[ ]`. Затем мы берём от него нулевой элемент, и потом еще раз. -Если это непонятно, то посмотрите на такой пример: + Если это непонятно, то посмотрите на такой пример: -```js -//+ no-beautify -alert( [1,[0],2][1] ); -``` + ```js no-beautify + alert( [1,[0],2][1] ); + ``` + + Квадратные скобки после массива/объекта обозначают не другой массив, а взятие элемента. +8. Каждый объект преобразуется к примитиву. У встроенных объектов `Object` нет подходящего `valueOf`, поэтому используется `toString`, так что складываются в итоге строковые представления объектов. -Квадратные скобки после массива/объекта обозначают не другой массив, а взятие элемента. -
    14. -
    15. Каждый объект преобразуется к примитиву. У встроенных объектов `Object` нет подходящего `valueOf`, поэтому используется `toString`, так что складываются в итоге строковые представления объектов.
    16. -
    diff --git a/1-js/6-objects-more/2-object-conversion/4-object-types-conversion-questions/task.md b/1-js/6-objects-more/2-object-conversion/4-object-types-conversion-questions/task.md index 85f767a5..b40473bc 100644 --- a/1-js/6-objects-more/2-object-conversion/4-object-types-conversion-questions/task.md +++ b/1-js/6-objects-more/2-object-conversion/4-object-types-conversion-questions/task.md @@ -1,18 +1,18 @@ -# Вопросник по преобразованиям, для объектов +importance: 5 + +--- -[importance 5] +# Вопросник по преобразованиям, для объектов Подумайте, какой результат будет у выражений ниже. Когда закончите -- сверьтесь с решением. -```js -//+ no-beautify +```js no-beautify new Date(0) - 0 new Array(1)[0] + "" -({})[0]
 +({})[0] [1] + 1 [1,2] + [3,4] [] + null + 1 [[0]][0][0] ({} + {}) ``` - diff --git a/1-js/6-objects-more/2-object-conversion/5-sum-many-brackets/solution.md b/1-js/6-objects-more/2-object-conversion/5-sum-many-brackets/solution.md index 4b36c185..52ffab87 100644 --- a/1-js/6-objects-more/2-object-conversion/5-sum-many-brackets/solution.md +++ b/1-js/6-objects-more/2-object-conversion/5-sum-many-brackets/solution.md @@ -10,8 +10,7 @@ Удобнее всего хранить его в замыкании, в переменной `currentSum`. Каждый вызов прибавляет к ней очередное значение: -```js -//+ run +```js run function sum(a) { var currentSum = a; @@ -38,7 +37,7 @@ alert( sum(0)(1)(2)(3)(4)(5) ); // 15 Затем, при каждом запуске функция `f` добавляет параметр к сумме `currentSum`, хранящейся в замыкании, и возвращает сама себя. -**В последней строчке `f` нет рекурсивного вызова.** +**В последней строчке `f` нет рекурсивного вызова.** Вот так была бы рекурсия: diff --git a/1-js/6-objects-more/2-object-conversion/5-sum-many-brackets/task.md b/1-js/6-objects-more/2-object-conversion/5-sum-many-brackets/task.md index b220c704..047870be 100644 --- a/1-js/6-objects-more/2-object-conversion/5-sum-many-brackets/task.md +++ b/1-js/6-objects-more/2-object-conversion/5-sum-many-brackets/task.md @@ -1,6 +1,8 @@ -# Сумма произвольного количества скобок +importance: 2 + +--- -[importance 2] +# Сумма произвольного количества скобок Напишите функцию `sum`, которая будет работать так: @@ -14,4 +16,4 @@ sum(0)(1)(2)(3)(4)(5) == 15 Количество скобок может быть любым. -Пример такой функции для двух аргументов -- есть в решении задачи [](/task/closure-sum). \ No newline at end of file +Пример такой функции для двух аргументов -- есть в решении задачи . \ No newline at end of file diff --git a/1-js/6-objects-more/2-object-conversion/article.md b/1-js/6-objects-more/2-object-conversion/article.md index e473266b..9f8d98c8 100644 --- a/1-js/6-objects-more/2-object-conversion/article.md +++ b/1-js/6-objects-more/2-object-conversion/article.md @@ -1,16 +1,16 @@ # Преобразование объектов: toString и valueOf -Ранее, в главе [](/types-conversion) мы рассматривали преобразование типов для примитивов. Теперь добавим в нашу картину мира объекты. +Ранее, в главе мы рассматривали преобразование типов для примитивов. Теперь добавим в нашу картину мира объекты. Бывают операции, при которых объект должен быть преобразован в примитив. + [cut] + Например: -
      -
    • Строковое преобразование -- если объект выводится через `alert(obj)`.
    • -
    • Численное преобразование -- при арифметических операциях, сравнении с примитивом.
    • -
    • Логическое преобразование -- при `if(obj)` и других логических операциях.
    • -
    +- Строковое преобразование -- если объект выводится через `alert(obj)`. +- Численное преобразование -- при арифметических операциях, сравнении с примитивом. +- Логическое преобразование -- при `if(obj)` и других логических операциях. Рассмотрим эти преобразования по очереди. @@ -20,8 +20,7 @@ **Любой объект в логическом контексте -- `true`, даже если это пустой массив `[]` или объект `{}`.** -```js -//+ run +```js run if ({} && []) { alert( "Все объекты - true!" ); // alert сработает } @@ -31,8 +30,7 @@ if ({} && []) { Строковое преобразование проще всего увидеть, если вывести объект при помощи `alert`: -```js -//+ run +```js run var user = { firstName: 'Василий' }; @@ -42,12 +40,11 @@ alert( user ); // [object Object] Как видно, содержимое объекта не вывелось. Это потому, что стандартным строковым представлением пользовательского объекта является строка `"[object Object]"`. -Такой вывод объекта не содержит интересной информации. Поэтому имеет смысл его поменять на что-то более полезное. +Такой вывод объекта не содержит интересной информации. Поэтому имеет смысл его поменять на что-то более полезное. **Если в объекте присутствует метод `toString`, который возвращает примитив, то он используется для преобразования.** -```js -//+ run +```js run var user = { firstName: 'Василий', @@ -60,13 +57,12 @@ var user = { alert( user ); // Пользователь Василий ``` -[smart header="Результатом `toString` может быть любой примитив"] -Метод `toString` не обязан возвращать именно строку. +````smart header="Результатом `toString` может быть любой примитив" +Метод `toString` не обязан возвращать именно строку. Его результат может быть любого примитивного типа. Например, это может быть число, как в примере ниже: -```js -//+ run +```js run var obj = { toString: function() { return 123; @@ -77,12 +73,11 @@ alert( obj ); // 123 ``` Поэтому мы и называем его здесь *"строковое преобразование"*, а не "преобразование к строке". -[/smart] +```` Все объекты, включая встроенные, имеют свои реализации метода `toString`, например: -```js -//+ run +```js run alert( [1, 2] ); // toString для массивов выводит список элементов "1,2" alert( new Date ); // toString для дат выводит дату в виде строки alert( function() {} ); // toString для функции выводит её код @@ -92,8 +87,7 @@ alert( function() {} ); // toString для функции выводит её к Для численного преобразования объекта используется метод `valueOf`, а если его нет -- то `toString`: -```js -//+ run +```js run var room = { number: 777, @@ -110,33 +104,30 @@ alert( +room ); // 777, *!*вызвался toString*/!* Метод `valueOf` обязан возвращать примитивное значение, иначе его результат будет проигнорирован. При этом -- не обязательно числовое. -[smart header="У большинства объектов нет `valueOf`"] +````smart header="У большинства объектов нет `valueOf`" У большинства встроенных объектов такого `valueOf` нет, поэтому численное и строковое преобразования для них работают одинаково. Исключением является объект `Date`, который поддерживает оба типа преобразований: -```js -//+ run +```js run alert( new Date() ); // toString: Дата в виде читаемой строки alert( +new Date() ); // valueOf: кол-во миллисекунд, прошедших с 01.01.1970 ``` +```` -[/smart] - -[smart header="Детали спецификации"] +```smart header="Детали спецификации" Если посмотреть в стандарт, то в пункте [15.2.4.4](http://es5.github.com/x15.2.html#x15.2.4.4) говорится о том, что `valueOf` есть у любых объектов. Но он ничего не делает, просто возвращает сам объект (не-примитивное значение!), а потому игнорируется. -[/smart] +``` ## Две стадии преобразования -Итак, объект преобразован в примитив при помощи `toString` или `valueOf`. +Итак, объект преобразован в примитив при помощи `toString` или `valueOf`. Но на этом преобразования не обязательно заканчиваются. Вполне возможно, что в процессе вычислений этот примитив будет преобразован во что-то другое. Например, рассмотрим применение к объекту операции `==`: -```js -//+ run +```js run var obj = { valueOf: function() { return 1; @@ -146,14 +137,13 @@ var obj = { alert( obj == true ); // true ``` -Объект `obj` был сначала преобразован в примитив, используя численное преобразование, получилось `1 == true`. +Объект `obj` был сначала преобразован в примитив, используя численное преобразование, получилось `1 == true`. Далее, так как значения всё ещё разных типов, применяются правила преобразования примитивов, результат: `true`. То же самое -- при сложении с объектом при помощи `+`: -```js -//+ run +```js run var obj = { valueOf: function() { return 1; @@ -165,8 +155,7 @@ alert( obj + "test" ); // 1test Или вот, для разности объектов: -```js -//+ run +```js run var a = { valueOf: function() { return "1"; @@ -182,35 +171,33 @@ alert( a + b ); // "12" alert( a - b ); // "1" - "2" = -1 ``` -[warn header="Исключение: `Date`"] +````warn header="Исключение: `Date`" Объект `Date`, по историческим причинам, является исключением. Бинарный оператор плюс `+` обычно использует числовое преобразование и метод `valueOf`. Как мы уже знаем, если подходящего `valueOf` нет (а его нет у большинства объектов), то используется `toString`, так что в итоге преобразование происходит к строке. Но если есть `valueOf`, то используется `valueOf`. Выше в примере как раз `a + b` это демонстрируют. -У объектов `Date` есть и `valueOf` -- возвращает количество миллисекунд, и `toString` -- возвращает строку с датой. +У объектов `Date` есть и `valueOf` -- возвращает количество миллисекунд, и `toString` -- возвращает строку с датой. ...Но оператор `+` для `Date` использует именно `toString` (хотя должен бы `valueOf`). Это и есть исключение: -```js -//+ run +```js run // бинарный плюс для даты toString, для остальных объектов valueOf alert( new Date + "" ); // "строка даты" ``` -Других подобных исключений нет. -[/warn] +Других подобных исключений нет. +```` -[warn header="Как испугать Java-разработчика"] +````warn header="Как испугать Java-разработчика" В языке Java (это не JavaScript, другой язык, здесь приведён для примера) логические значения можно создавать, используя синтаксис `new Boolean(true/false)`, например `new Boolean(true)`. -В JavaScript тоже есть подобная возможность, которая возвращает "объектную обёртку" для логического значения. +В JavaScript тоже есть подобная возможность, которая возвращает "объектную обёртку" для логического значения. Эта возможность давно существует лишь для совместимости, она и не используется на практике, поскольку приводит к странным результатам. Некоторые из них могут сильно удивить человека, не привыкшего к JavaScript, например: -```js -//+ run +```js run var value = new Boolean(false); if (value) { alert( true ); // сработает! @@ -219,8 +206,7 @@ if (value) { Почему запустился `alert`? Ведь в `if` находится `false`... Проверим: -```js -//+ run +```js run var value = new Boolean(false); *!* @@ -228,54 +214,45 @@ alert( value ); // выводит false, все ок.. */!* if (value) { - alert( true ); // ..но тогда почему выполняется alert в if ?!? + alert( true ); // ..но тогда почему выполняется alert в if ?!? } ``` Дело в том, что `new Boolean` -- это не примитивное значение, а объект. Поэтому в логическом контексте он преобразуется к `true`, в результате работает первый пример. -А второй пример вызывает `alert`, который преобразует объект к строке, и он становится `"false"`. +А второй пример вызывает `alert`, который преобразует объект к строке, и он становится `"false"`. **В JavaScript вызовы `new Boolean/String/Number` не используются, а используются простые вызовы соответствующих функций, они преобразуют значение в примитив нужного типа, например `Boolean(val) === !!val`.** -[/warn] +```` ## Итого -
      -
    • В логическом контексте объект -- всегда `true`.
    • -
    • При строковом преобразовании объекта используется его метод `toString`. Он должен возвращать примитивное значение, причём не обязательно именно строку. -
    • -
    • Для численного преобразования используется метод `valueOf`, который также может возвратить любое примитивное значение. У большинства объектов `valueOf` не работает (возвращает сам объект и потому игнорируется), при этом для численного преобразования используется `toString`.
    • -
    - -Полный алгоритм преобразований есть в спецификации EcmaScript, смотрите пункты [11.8.5](http://es5.github.com/x11.html#x11.8.5), [11.9.3](http://es5.github.com/x11.html#x11.9.3), а также [9.1](http://es5.github.com/x9.html#x9.1) и [9.3](http://es5.github.com/x9.html#x9.3). +- В логическом контексте объект -- всегда `true`. +- При строковом преобразовании объекта используется его метод `toString`. Он должен возвращать примитивное значение, причём не обязательно именно строку. +- Для численного преобразования используется метод `valueOf`, который также может возвратить любое примитивное значение. У большинства объектов `valueOf` не работает (возвращает сам объект и потому игнорируется), при этом для численного преобразования используется `toString`. +Полный алгоритм преобразований есть в спецификации ECMAScript, смотрите пункты [11.8.5](http://es5.github.com/x11.html#x11.8.5), [11.9.3](http://es5.github.com/x11.html#x11.9.3), а также [9.1](http://es5.github.com/x9.html#x9.1) и [9.3](http://es5.github.com/x9.html#x9.3). Заметим, для полноты картины, что некоторые тесты знаний в интернет предлагают вопросы типа: -```js -//+ no-beautify -{}[0] // чему равно? +```js no-beautify +{}[0] // чему равно? {} + {} // а так? ``` Если вы запустите эти выражения в консоли, то результат может показаться странным. Подвох здесь в том, что если фигурные скобки `{...}` идут не в выражении, а в основном потоке кода, то JavaScript считает, что это не объект, а "блок кода" (как `if`, `for`, но без оператора, просто группировка команд вместе, используется редко). Вот блок кода с командой: -```js -//+run +```js run { alert("Блок") } ``` А если команду изъять, то будет пустой блок `{}`, который ничего не делает. Два примера выше как раз содержат пустой блок в начале, который ничего не делает. Иначе говоря: -```js -//+ no-beautify -{}[0] // то же что и: [0] +```js no-beautify +{}[0] // то же что и: [0] {} + {} // то же что и: + {} ``` То есть, такие вопросы -- не на преобразование типов, а на понимание, что если `{ ... }` находится вне выражений, то это не объект, а блок. - - diff --git a/1-js/6-objects-more/3-constructor-new/1-two-functions-one-object/solution.md b/1-js/6-objects-more/3-constructor-new/1-two-functions-one-object/solution.md index 5b27cec2..f875fca0 100644 --- a/1-js/6-objects-more/3-constructor-new/1-two-functions-one-object/solution.md +++ b/1-js/6-objects-more/3-constructor-new/1-two-functions-one-object/solution.md @@ -4,8 +4,7 @@ Например, они могут вернуть один и тот же объект `obj`, определённый снаружи: -```js -//+ run no-beautify +```js run no-beautify var obj = {}; function A() { return obj; } diff --git a/1-js/6-objects-more/3-constructor-new/1-two-functions-one-object/task.md b/1-js/6-objects-more/3-constructor-new/1-two-functions-one-object/task.md index 72c4e8eb..287bfc1f 100644 --- a/1-js/6-objects-more/3-constructor-new/1-two-functions-one-object/task.md +++ b/1-js/6-objects-more/3-constructor-new/1-two-functions-one-object/task.md @@ -1,11 +1,12 @@ -# Две функции один объект +importance: 2 + +--- -[importance 2] +# Две функции один объект Возможны ли такие функции `A` и `B` в примере ниже, что соответствующие объекты `a,b` равны (см. код ниже)? -```js -//+ no-beautify +```js no-beautify function A() { ... } function B() { ... } @@ -15,4 +16,4 @@ var b = new B; alert( a == b ); // true ``` -Если да -- приведите пример кода с такими функциями. \ No newline at end of file +Если да -- приведите пример кода с такими функциями. \ No newline at end of file diff --git a/1-js/6-objects-more/3-constructor-new/2-calculator-constructor/solution.md b/1-js/6-objects-more/3-constructor-new/2-calculator-constructor/solution.md index a78010d9..bbff1040 100644 --- a/1-js/6-objects-more/3-constructor-new/2-calculator-constructor/solution.md +++ b/1-js/6-objects-more/3-constructor-new/2-calculator-constructor/solution.md @@ -1,7 +1,6 @@ -```js -//+ run demo +```js run demo function Calculator() { this.read = function() { diff --git a/1-js/6-objects-more/3-constructor-new/2-calculator-constructor/task.md b/1-js/6-objects-more/3-constructor-new/2-calculator-constructor/task.md index 31258cb7..54f844c9 100644 --- a/1-js/6-objects-more/3-constructor-new/2-calculator-constructor/task.md +++ b/1-js/6-objects-more/3-constructor-new/2-calculator-constructor/task.md @@ -1,13 +1,14 @@ -# Создать Calculator при помощи конструктора +importance: 5 + +--- -[importance 5] +# Создать Calculator при помощи конструктора Напишите *функцию-конструктор* `Calculator`, которая создает объект с тремя методами: -
      -
    • Метод `read()` запрашивает два значения при помощи `prompt` и запоминает их в свойствах объекта.
    • -
    • Метод `sum()` возвращает сумму запомненных свойств.
    • -
    • Метод `mul()` возвращает произведение запомненных свойств.
    • -
    + +- Метод `read()` запрашивает два значения при помощи `prompt` и запоминает их в свойствах объекта. +- Метод `sum()` возвращает сумму запомненных свойств. +- Метод `mul()` возвращает произведение запомненных свойств. Пример использования: @@ -19,4 +20,5 @@ alert( "Сумма=" + calculator.sum() ); alert( "Произведение=" + calculator.mul() ); ``` -[demo /] +[demo] + diff --git a/1-js/6-objects-more/3-constructor-new/3-accumulator/solution.md b/1-js/6-objects-more/3-constructor-new/3-accumulator/solution.md index f5b2ba43..f348cdc2 100644 --- a/1-js/6-objects-more/3-constructor-new/3-accumulator/solution.md +++ b/1-js/6-objects-more/3-constructor-new/3-accumulator/solution.md @@ -1,7 +1,6 @@ -```js -//+ run demo +```js run demo function Accumulator(startingValue) { this.value = startingValue; diff --git a/1-js/6-objects-more/3-constructor-new/3-accumulator/task.md b/1-js/6-objects-more/3-constructor-new/3-accumulator/task.md index 7852e561..3bd5b5a0 100644 --- a/1-js/6-objects-more/3-constructor-new/3-accumulator/task.md +++ b/1-js/6-objects-more/3-constructor-new/3-accumulator/task.md @@ -1,15 +1,17 @@ -# Создать Accumulator при помощи конструктора +importance: 5 + +--- -[importance 5] +# Создать Accumulator при помощи конструктора -Напишите *функцию-конструктор* `Accumulator(startingValue)`. +Напишите *функцию-конструктор* `Accumulator(startingValue)`. Объекты, которые она создает, должны хранить текущую сумму и прибавлять к ней то, что вводит посетитель. Более формально, объект должен: -
      -
    • Хранить текущее значение в своём свойстве `value`. Начальное значение свойства `value` ставится конструктором равным `startingValue`.
    • -
    • Метод `read()` вызывает `prompt`, принимает число и прибавляет его к свойству `value`.
    • -
    + +- Хранить текущее значение в своём свойстве `value`. Начальное значение свойства `value` ставится конструктором равным `startingValue`. +- Метод `read()` вызывает `prompt`, принимает число и прибавляет его к свойству `value`. + Таким образом, свойство `value` является текущей суммой всего, что ввел посетитель при вызовах метода `read()`, с учетом начального значения `startingValue`. Ниже вы можете посмотреть работу кода: @@ -21,4 +23,5 @@ accumulator.read(); // прибавит ввод prompt к текущему зн alert( accumulator.value ); // выведет текущее значение ``` -[demo /] +[demo] + diff --git a/1-js/6-objects-more/3-constructor-new/4-calculator-extendable/_js.view/solution.js b/1-js/6-objects-more/3-constructor-new/4-calculator-extendable/_js.view/solution.js index 4bf9f22b..e2942e02 100644 --- a/1-js/6-objects-more/3-constructor-new/4-calculator-extendable/_js.view/solution.js +++ b/1-js/6-objects-more/3-constructor-new/4-calculator-extendable/_js.view/solution.js @@ -20,10 +20,10 @@ function Calculator() { return NaN; } - return methods[op](+a, +b); + return methods[op](a, b); } this.addMethod = function(name, func) { methods[name] = func; }; -} \ No newline at end of file +} diff --git a/1-js/6-objects-more/3-constructor-new/4-calculator-extendable/solution.md b/1-js/6-objects-more/3-constructor-new/4-calculator-extendable/solution.md index 3e5f26e5..22826840 100644 --- a/1-js/6-objects-more/3-constructor-new/4-calculator-extendable/solution.md +++ b/1-js/6-objects-more/3-constructor-new/4-calculator-extendable/solution.md @@ -1,7 +1,6 @@ -```js -//+ run +```js run function Calculator() { var methods = { @@ -24,7 +23,7 @@ function Calculator() { return NaN; } - return methods[op](+a, +b); + return methods[op](a, b); } this.addMethod = function(name, func) { @@ -48,8 +47,6 @@ var result = calc.calculate("2 ** 3"); alert( result ); // 8 ``` -
      -
    • Обратите внимание на хранение методов. Они просто добавляются к внутреннему объекту.
    • -
    • Все проверки и преобразование к числу производятся в методе `calculate`. В дальнейшем он может быть расширен для поддержки более сложных выражений.
    • -
    +- Обратите внимание на хранение методов. Они просто добавляются к внутреннему объекту. +- Все проверки и преобразование к числу производятся в методе `calculate`. В дальнейшем он может быть расширен для поддержки более сложных выражений. diff --git a/1-js/6-objects-more/3-constructor-new/4-calculator-extendable/task.md b/1-js/6-objects-more/3-constructor-new/4-calculator-extendable/task.md index 244a932c..01be7349 100644 --- a/1-js/6-objects-more/3-constructor-new/4-calculator-extendable/task.md +++ b/1-js/6-objects-more/3-constructor-new/4-calculator-extendable/task.md @@ -1,47 +1,42 @@ -# Создайте калькулятор +importance: 5 + +--- -[importance 5] +# Создайте калькулятор Напишите конструктор `Calculator`, который создаёт расширяемые объекты-калькуляторы. Эта задача состоит из двух частей, которые можно решать одна за другой. -
      -
    1. Первый шаг задачи: вызов `calculate(str)` принимает строку, например "1 + 2", с жёстко заданным форматом "ЧИСЛО операция ЧИСЛО" (по одному пробелу вокруг операции), и возвращает результат. Понимает плюс `+` и минус `-`. - -Пример использования: - -```js -var calc = new Calculator; - -alert( calc.calculate("3 + 7") ); // 10 -``` - -
    2. -
    3. Второй шаг -- добавить калькулятору метод `addMethod(name, func)`, который учит калькулятор новой операции. Он получает имя операции `name` и функцию от двух аргументов `func(a,b)`, которая должна её реализовывать. - -Например, добавим операции умножить `*`, поделить `/` и возвести в степень `**`: - -```js -var powerCalc = new Calculator; -powerCalc.addMethod("*", function(a, b) { - return a * b; -}); -powerCalc.addMethod("/", function(a, b) { - return a / b; -}); -powerCalc.addMethod("**", function(a, b) { - return Math.pow(a, b); -}); - -var result = powerCalc.calculate("2 ** 3"); -alert( result ); // 8 -``` - -
    4. -
    - -
      -
    • Поддержка скобок и сложных математических выражений в этой задаче не требуется.
    • -
    • Числа и операции могут состоять из нескольких символов. Между ними ровно один пробел.
    • -
    • Предусмотрите обработку ошибок. Какая она должна быть - решите сами.
    • -
    \ No newline at end of file + +1. Первый шаг задачи: вызов `calculate(str)` принимает строку, например "1 + 2", с жёстко заданным форматом "ЧИСЛО операция ЧИСЛО" (по одному пробелу вокруг операции), и возвращает результат. Понимает плюс `+` и минус `-`. + + Пример использования: + + ```js + var calc = new Calculator; + + alert( calc.calculate("3 + 7") ); // 10 + ``` +2. Второй шаг -- добавить калькулятору метод `addMethod(name, func)`, который учит калькулятор новой операции. Он получает имя операции `name` и функцию от двух аргументов `func(a,b)`, которая должна её реализовывать. + + Например, добавим операции умножить `*`, поделить `/` и возвести в степень `**`: + + ```js + var powerCalc = new Calculator; + powerCalc.addMethod("*", function(a, b) { + return a * b; + }); + powerCalc.addMethod("/", function(a, b) { + return a / b; + }); + powerCalc.addMethod("**", function(a, b) { + return Math.pow(a, b); + }); + + var result = powerCalc.calculate("2 ** 3"); + alert( result ); // 8 + ``` + +- Поддержка скобок и сложных математических выражений в этой задаче не требуется. +- Числа и операции могут состоять из нескольких символов. Между ними ровно один пробел. +- Предусмотрите обработку ошибок. Какая она должна быть - решите сами. diff --git a/1-js/6-objects-more/3-constructor-new/article.md b/1-js/6-objects-more/3-constructor-new/article.md index f4ce62cf..f10c04e2 100644 --- a/1-js/6-objects-more/3-constructor-new/article.md +++ b/1-js/6-objects-more/3-constructor-new/article.md @@ -5,6 +5,7 @@ Для этого используют "функции-конструкторы", запуская их при помощи специального оператора `new`. [cut] + ## Конструктор Конструктором становится любая функция, вызванная через `new`. @@ -28,13 +29,10 @@ var animal = new Animal("ёжик"); Детальнее -- функция, запущенная через `new`, делает следующее: -
      -
    1. Создаётся новый пустой объект.
    2. -
    3. Ключевое слово `this` получает ссылку на этот объект.
    4. -
    5. Функция выполняется. Как правило, она модифицирует `this`, добавляет методы, свойства.
    6. -
    7. Возвращается `this`.
    8. -
    - +1. Создаётся новый пустой объект. +2. Ключевое слово `this` получает ссылку на этот объект. +3. Функция выполняется. Как правило, она модифицирует `this` (т.е. этот новый объект), добавляет методы, свойства. +4. Возвращается `this`. В результате вызова `new Animal("ёжик");` получаем такой объект: @@ -53,7 +51,7 @@ function Animal(name) { // this = {}; */!* - // в this пишем свойства, методы + // в this пишем свойства, методы this.name = name; this.canWalk = true; @@ -65,7 +63,7 @@ function Animal(name) { Теперь многократными вызовами `new Animal` с разными параметрами мы можем создать столько объектов, сколько нужно. Поэтому такую функцию и называют *конструктором* -- она предназначена для "конструирования" объектов. -[smart header="new function() { ... }"] +````smart header="new function() { ... }" Иногда функцию-конструктор объявляют и тут же используют, вот так: ```js var animal = new function() { @@ -73,32 +71,32 @@ var animal = new function() { this.canWalk = true; }; ``` -Так делают, когда хотят создать единственный объект данного типа. Примере выше с тем же успехом можно было бы переписать как: + +Так делают, когда хотят создать единственный объект данного типа. Пример выше с тем же успехом можно было бы переписать как: ```js var animal = { name: "Васька", canWalk: true } ``` + ...Но обычный синтаксис `{...}` не подходит, когда при создании свойств объекта нужны более сложные вычисления. Их можно проделать в функции-конструкторе и записать результат в `this`. -[/smart] +```` ## Правила обработки return Как правило, конструкторы ничего не возвращают. Их задача -- записать всё, что нужно, в `this`, который автоматически станет результатом. Но если явный вызов `return` всё же есть, то применяется простое правило: -
      -
    • При вызове `return` с объектом, будет возвращён он, а не `this`.
    • -
    • При вызове `return` с примитивным значением, оно будет отброшено.
    • -
    + +- При вызове `return` с объектом, будет возвращён он, а не `this`. +- При вызове `return` с примитивным значением, оно будет отброшено. Иными словами, вызов `return` с объектом вернёт объект, а с чем угодно, кроме объекта -- возвратит, как обычно, `this`. Например, возврат объекта: -```js -//+ run no-beautify +```js run no-beautify function BigAnimal() { this.name = "Мышь"; @@ -111,8 +109,7 @@ alert( new BigAnimal().name ); // Годзилла, получили объек А вот пример с возвратом строки: -```js -//+ run +```js run function BigAnimal() { this.name = "Мышь"; @@ -125,19 +122,19 @@ alert( new BigAnimal().name ); // Мышь, получили this (а Годзи Эта особенность работы `new` прописана в стандарте, но используется она весьма редко. -[smart header="Можно без скобок"] +````smart header="Можно без скобок" Кстати, при вызове `new` без аргументов скобки можно не ставить: ```js var animal = new BigAnimal; // <-- без скобок -// то же самое что +// то же самое что var animal = new BigAnimal(); ``` Не сказать, что выбрасывание скобок -- "хороший стиль", но такой синтаксис допустим стандартом. -[/smart] +```` -## Создание методов в конструкторе +## Создание методов в конструкторе Использование функций для создания объекта дает большую гибкость. Можно передавать конструктору параметры, определяющие как его создавать, и он будет "клепать" объекты заданным образом. @@ -145,8 +142,7 @@ var animal = new BigAnimal(); Например, `new User(name)` создает объект с заданным значением свойства `name` и методом `sayHi`: -```js -//+ run +```js run function User(name) { this.name = name; @@ -161,20 +157,19 @@ var ivan = new User("Иван"); ivan.sayHi(); // Моё имя: Иван */!* -/* +/* ivan = { - name: "Иван", + name: "Иван", sayHi: функция -} +} */ ``` -## Локальные переменные +## Локальные переменные В функции-конструкторе бывает удобно объявить вспомогательные локальные переменные и вложенные функции, которые будут видны только внутри: -```js -//+ run +```js run function User(firstName, lastName) { *!* // вспомогательная переменная @@ -195,22 +190,14 @@ var vasya = new User("Вася", "Петров"); vasya.sayHi(); // Привет, Вася Петров ``` -Мы уже говорили об этом подходе ранее, в главе [](/closures-usage). +Мы уже говорили об этом подходе ранее, в главе . Те функции и данные, которые должны быть доступны для внешнего кода, мы пишем в `this` -- и к ним можно будет обращаться, как например `vasya.sayHi()`, а вспомогательные, которые нужны только внутри самого объекта, сохраняем в локальной области видимости. -[] - ## Итого Объекты могут быть созданы при помощи функций-конструкторов: -
      -
    • Любая функция может быть вызвана с `new`, при этом она получает новый пустой объект в качестве `this`, в который она добавляет свойства. Если функция не решит возвратить свой объект, то её результатом будет `this`.
    • -
    • Функции, которые предназначены для создания объектов, называются *конструкторами*. Их названия пишут с большой буквы, чтобы отличать от обычных.
    • -
    - - - - +- Любая функция может быть вызвана с `new`, при этом она получает новый пустой объект в качестве `this`, в который она добавляет свойства. Если функция не решит возвратить свой объект, то её результатом будет `this`. +- Функции, которые предназначены для создания объектов, называются *конструкторами*. Их названия пишут с большой буквы, чтобы отличать от обычных. diff --git a/1-js/6-objects-more/4-descriptors-getters-setters/1-replace-property-getter/solution.md b/1-js/6-objects-more/4-descriptors-getters-setters/1-replace-property-getter/solution.md index f00b3bc8..5c5c5963 100644 --- a/1-js/6-objects-more/4-descriptors-getters-setters/1-replace-property-getter/solution.md +++ b/1-js/6-objects-more/4-descriptors-getters-setters/1-replace-property-getter/solution.md @@ -1,6 +1,5 @@ -```js -//+ run +```js run function User(fullName) { this.fullName = fullName; @@ -30,7 +29,6 @@ function User(fullName) { } - }); } diff --git a/1-js/6-objects-more/4-descriptors-getters-setters/1-replace-property-getter/task.md b/1-js/6-objects-more/4-descriptors-getters-setters/1-replace-property-getter/task.md index 81152200..5e0d584e 100644 --- a/1-js/6-objects-more/4-descriptors-getters-setters/1-replace-property-getter/task.md +++ b/1-js/6-objects-more/4-descriptors-getters-setters/1-replace-property-getter/task.md @@ -1,6 +1,8 @@ -# Добавить get/set-свойства +importance: 5 + +--- -[importance 5] +# Добавить get/set-свойства Вам попал в руки код объекта `User`, который хранит имя и фамилию в свойстве `this.fullName`: diff --git a/1-js/6-objects-more/4-descriptors-getters-setters/article.md b/1-js/6-objects-more/4-descriptors-getters-setters/article.md index 551b1f1e..25854e40 100644 --- a/1-js/6-objects-more/4-descriptors-getters-setters/article.md +++ b/1-js/6-objects-more/4-descriptors-getters-setters/article.md @@ -2,9 +2,10 @@ В этой главе мы рассмотрим возможности, которые позволяют очень гибко и мощно управлять всеми свойствами объекта, включая их аспекты -- изменяемость, видимость в цикле `for..in` и даже незаметно делать их функциями. -Они поддерживаются всеми современными браузерами, но не IE8-. Впрочем, даже в IE8 их поддерживает, но только для DOM-объектов (используются при работе со страницей, это сейчас вне нашего рассмотрения). +Они поддерживаются всеми современными браузерами, но не IE8-. Впрочем, даже IE8 их поддерживает, но только для DOM-объектов (используются при работе со страницей, это сейчас вне нашего рассмотрения). [cut] + ## Дескрипторы в примерах Основной метод для управления свойствами -- [Object.defineProperty](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/defineProperty). @@ -18,24 +19,24 @@ Object.defineProperty(obj, prop, descriptor) ``` Аргументы: -
    -
    `obj`
    -
    Объект, в котором объявляется свойство.
    -
    `prop`
    -
    Имя свойства, которое нужно объявить или модифицировать.
    -
    `descriptor`
    -
    Дескриптор -- объект, который описывает поведение свойства. + +`obj` +: Объект, в котором объявляется свойство. + +`prop` +: Имя свойства, которое нужно объявить или модифицировать. + +`descriptor` +: Дескриптор -- объект, который описывает поведение свойства. В нём могут быть следующие поля: -
      -
    • `value` -- значение свойства, по умолчанию `undefined`
    • -
    • `writable` -- значение свойства можно менять, если `true`. По умолчанию `false`.
    • -
    • `configurable` -- если `true`, то свойство можно удалять, а также менять его в дальнейшем при помощи новых вызовов `defineProperty`. По умолчанию `false`.
    • -
    • `enumerable` -- если `true`, то свойство будет участвовать в переборе `for..in`. По умолчанию `false`.
    • -
    • `get` -- функция, которая возвращает значение свойства. По умолчанию `undefined`.
    • -
    • `set` -- функция, которая записывает значение свойства. По умолчанию `undefined`.
    • -
    +- `value` -- значение свойства, по умолчанию `undefined` +- `writable` -- значение свойства можно менять, если `true`. По умолчанию `false`. +- `configurable` -- если `true`, то свойство можно удалять, а также менять его в дальнейшем при помощи новых вызовов `defineProperty`. По умолчанию `false`. +- `enumerable` -- если `true`, то свойство просматривается в цикле `for..in` и методе `Object.keys()`. По умолчанию `false`. +- `get` -- функция, которая возвращает значение свойства. По умолчанию `undefined`. +- `set` -- функция, которая записывает значение свойства. По умолчанию `undefined`. Чтобы избежать конфликта, запрещено одновременно указывать значение `value` и функции `get/set`. Либо значение, либо функции для его чтения-записи, одно из двух. Также запрещено и не имеет смысла указывать `writable` при наличии `get/set`-функций. @@ -43,27 +44,25 @@ Object.defineProperty(obj, prop, descriptor) ## Обычное свойство -Обычное свойство добавить очень просто. - Два таких вызова работают одинаково: -```js -//+ no-beautify +```js no-beautify var user = {}; // 1. простое присваивание user.name = "Вася"; // 2. указание значения через дескриптор -Object.defineProperty(user, "name", { value: "Вася" }); +Object.defineProperty(user, "name", { value: "Вася", configurable: true, writable: true, enumerable: true }); ``` +Оба вызова выше добавляют в объект `user` обычное (удаляемое, изменяемое, перечисляемое) свойство. + ## Свойство-константа -Для того, чтобы сделать свойство неизменяемым, добавим ему флаги `writable` и `configurable`: +Для того, чтобы сделать свойство неизменяемым, изменим его флаги `writable` и `configurable`: -```js -//+ run +```js run *!* "use strict"; */!* @@ -72,8 +71,10 @@ var user = {}; Object.defineProperty(user, "name", { value: "Вася", - writable: false, // запретить присвоение "user.name=" +*!* + writable: false, // запретить присвоение "user.name=" configurable: false // запретить удаление "delete user.name" +*/!* }); // Теперь попытаемся изменить это свойство. @@ -84,7 +85,7 @@ user.name = "Петя"; */!* ``` -Заметим, что без `use strict` операция записи "молча" не сработает, а при `use strict` дополнительно генерируется ошибка. +Заметим, что без `use strict` операция записи "молча" не сработает. Лишь если установлен режим `use strict`, то дополнительно сгенерируется ошибка. ## Свойство, скрытое для for..in @@ -92,8 +93,7 @@ user.name = "Петя"; К сожалению, свойство `toString`, объявленное обычным способом, будет видно в цикле `for..in`, например: -```js -//+ run no-beautify +```js run no-beautify var user = { name: "Вася", toString: function() { return this.name; } @@ -108,8 +108,7 @@ for(var key in user) alert(key); // name, toString `Object.defineProperty` может исключить `toString` из списка итерации, поставив ему флаг `enumerable: false`. По стандарту, у встроенного `toString` этот флаг уже стоит. -```js -//+ run no-beautify +```js run no-beautify var user = { name: "Вася", toString: function() { return this.name; } @@ -129,12 +128,11 @@ for(var key in user) alert(key); // name Дескриптор позволяет задать свойство, которое на самом деле работает как функция. Для этого в нём нужно указать эту функцию в `get`. -Например, у объекта `user` есть обычные свойства: имя `firstName` и фамилия `surname`. +Например, у объекта `user` есть обычные свойства: имя `firstName` и фамилия `surname`. Создадим свойство `fullName`, которое на самом деле является функцией: -```js -//+ run +```js run var user = { firstName: "Вася", surname: "Петров" @@ -157,8 +155,7 @@ alert(user.fullName); // Вася Петров Например, добавим возможность присвоения `user.fullName` к примеру выше: -```js -//+ run +```js run var user = { firstName: "Вася", surname: "Петров" @@ -182,7 +179,7 @@ Object.defineProperty(user, "fullName", { *!* user.fullName = "Петя Иванов"; */!* -alert( user.firstName ); // Петя +alert( user.firstName ); // Петя alert( user.surname ); // Иванов ``` @@ -190,12 +187,11 @@ alert( user.surname ); // Иванов Если мы создаём объект при помощи синтаксиса `{ ... }`, то задать свойства-функции можно прямо в его определении. -Для этого используется особый синтаксис: `get свойство` или `set свойство`. +Для этого используется особый синтаксис: `get свойство` или `set свойство`. Например, ниже объявлен геттер-сеттер `fullName`: -```js -//+ run +```js run var user = { firstName: "Вася", surname: "Петров", @@ -256,14 +252,13 @@ function User(name, birthday) { var pete = new User("Петя", new Date(1987, 6, 1)); ``` -Что теперь делать со старым кодом, который выводит свойство `age`? +Что теперь делать со старым кодом, который выводит свойство `age`? Можно, конечно, найти все места и поправить их, но это долго, а иногда и невозможно, скажем, если вы взаимодействуете со сторонней библиотекой, код в которой -- чужой и влезать в него нежелательно. Добавление `get`-функции `age` позволяет обойти проблему легко и непринуждённо: -```js -//+ run no-beautify +```js run no-beautify function User(name, birthday) { this.name = name; this.birthday = birthday; @@ -291,113 +286,112 @@ alert( pete.age ); // и возраст ## Другие методы работы со свойствами -
    -
    [Object.defineProperties(obj, descriptors)](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/defineProperties)
    -
    Позволяет объявить несколько свойств сразу: - -```js -//+ run -var user = {} - -Object.defineProperties(user, { -*!* - firstName: { -*/!* - value: "Петя" - }, - -*!* - surname: { -*/!* - value: "Иванов" - }, - -*!* - fullName: { -*/!* - get: function() { - return this.firstName + ' ' + this.surname; - } - } -}); - -alert( user.fullName ); // Петя Иванов -``` - -
    -
    [Object.keys(obj)](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/keys), [Object.getOwnPropertyNames(obj)](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/getOwnPropertyNames)
    -
    Возвращают массив -- список свойств объекта. - -`Object.keys` возвращает только `enumerable`-свойства. - -`Object.getOwnPropertyNames` -- возвращает все: - -```js -//+ run -var obj = { - a: 1, - b: 2, - internal: 3 -}; - -Object.defineProperty(obj, "internal", { - enumerable: false -}); - -*!* -alert( Object.keys(obj) ); // a,b -alert( Object.getOwnPropertyNames(obj) ); // a, internal, b -*/!* -``` +[Object.defineProperties(obj, descriptors)](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/defineProperties) +: Позволяет объявить несколько свойств сразу: + + ```js run + var user = {} + + Object.defineProperties(user, { + *!* + firstName: { + */!* + value: "Петя" + }, + + *!* + surname: { + */!* + value: "Иванов" + }, + + *!* + fullName: { + */!* + get: function() { + return this.firstName + ' ' + this.surname; + } + } + }); + + alert( user.fullName ); // Петя Иванов + ``` + +[Object.keys(obj)](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/keys), [Object.getOwnPropertyNames(obj)](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/getOwnPropertyNames) +: Возвращают массив -- список свойств объекта. + + `Object.keys` возвращает только `enumerable`-свойства. + + `Object.getOwnPropertyNames` -- возвращает все: + + ```js run + var obj = { + a: 1, + b: 2, + internal: 3 + }; + + Object.defineProperty(obj, "internal", { + enumerable: false + }); + + *!* + alert( Object.keys(obj) ); // a,b + alert( Object.getOwnPropertyNames(obj) ); // a, internal, b + */!* + ``` + +[Object.getOwnPropertyDescriptor(obj, prop)](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptor) +: Возвращает дескриптор для свойства `obj[prop]`. + + Полученный дескриптор можно изменить и использовать `defineProperty` для сохранения изменений, например: + + ```js run + var obj = { + test: 5 + }; + *!* + var descriptor = Object.getOwnPropertyDescriptor(obj, 'test'); + */!* + + *!* + // заменим value на геттер, для этого... + */!* + delete descriptor.value; // ..нужно убрать value/writable + delete descriptor.writable; + descriptor.get = function() { // и поставить get + alert( "Preved :)" ); + }; + + *!* + // поставим новое свойство вместо старого + */!* + + // если не удалить - defineProperty объединит старый дескриптор с новым + delete obj.test; + + Object.defineProperty(obj, 'test', descriptor); + + obj.test; // Preved :) + ``` -
    -
    [Object.getOwnPropertyDescriptor(obj, prop)](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptor)
    -
    Возвращает дескриптор для свойства `obj[prop]`. - -Полученный дескриптор можно изменить и использовать `defineProperty` для сохранения изменений, например: - -```js -//+ run -var obj = { - test: 5 -}; -*!* -var descriptor = Object.getOwnPropertyDescriptor(obj, 'test'); -*/!* - -*!* -// заменим value на геттер, для этого... -*/!* -delete descriptor.value; // ..нужно убрать value/writable -delete descriptor.writable; -descriptor.get = function() { // и поставить get - alert( "Preved :)" ); -}; +...И несколько методов, которые используются очень редко: -*!* -// поставим новое свойство вместо старого -*/!* +[Object.preventExtensions(obj)](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/preventExtensions) +: Запрещает добавление свойств в объект. -// если не удалить - defineProperty объединит старый дескриптор с новым -delete obj.test; +[Object.seal(obj)](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/seal) +: Запрещает добавление и удаление свойств, все текущие свойства делает `configurable: false`. -Object.defineProperty(obj, 'test', descriptor); +[Object.freeze(obj)](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/freeze) +: Запрещает добавление, удаление и изменение свойств, все текущие свойства делает `configurable: false, writable: false`. -obj.test; // Preved :) -``` +[Object.isExtensible(obj)](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/isExtensible) +: Возвращает `false`, если добавление свойств объекта было запрещено вызовом метода `Object.preventExtensions`. -
    -
    +[Object.isSealed(obj)](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/isSealed) +: Возвращает `true`, если добавление и удаление свойств объекта запрещено, и все текущие свойства являются `configurable: false`. -...И несколько методов, которые используются очень редко: -
    -
    [Object.preventExtensions(obj)](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/preventExtensions)
    -
    Запрещает добавление свойств в объект.
    -
    [Object.seal(obj)](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/seal)
    -
    Запрещает добавление и удаление свойств, все текущие свойства делает `configurable: false`.
    -
    [Object.freeze(obj)](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/freeze)
    -
    Запрещает добавление, удаление и изменение свойств, все текущие свойства делает `configurable: false, writable: false`.
    -
    [Object.isExtensible(obj)](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/isExtensible), [Object.isSealed(obj)](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/isSealed), [Object.isFrozen(obj)](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/isFrozen)
    -
    Возвращают `true`, если на объекте были вызваны методы `Object.preventExtensions/seal/freeze`.
    -
    +[Object.isFrozen(obj)](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/isFrozen) +: Возвращает `true`, если добавление, удаление и изменение свойств объекта запрещено, и все текущие свойства являются `configurable: false, writable: false`. diff --git a/1-js/6-objects-more/5-static-properties-and-methods/1-objects-counter/solution.md b/1-js/6-objects-more/5-static-properties-and-methods/1-objects-counter/solution.md index 45ce7d39..5d771904 100644 --- a/1-js/6-objects-more/5-static-properties-and-methods/1-objects-counter/solution.md +++ b/1-js/6-objects-more/5-static-properties-and-methods/1-objects-counter/solution.md @@ -1,16 +1,15 @@ Решение (как вариант): -```js -//+ run +```js run function Article() { - this.created = new Date; + this.created = new Date(); *!* Article.count++; // увеличиваем счетчик при каждом вызове Article.last = this.created; // и запоминаем дату */!* } -Article.count = 0; // начальное значение +Article.count = 0; // начальное значение // (нельзя оставить undefined, т.к. Article.count++ будет NaN) Article.showStats = function() { diff --git a/1-js/6-objects-more/5-static-properties-and-methods/1-objects-counter/task.md b/1-js/6-objects-more/5-static-properties-and-methods/1-objects-counter/task.md index 513ed072..a7b84327 100644 --- a/1-js/6-objects-more/5-static-properties-and-methods/1-objects-counter/task.md +++ b/1-js/6-objects-more/5-static-properties-and-methods/1-objects-counter/task.md @@ -1,12 +1,14 @@ -# Счетчик объектов +importance: 5 + +--- -[importance 5] +# Счетчик объектов Добавить в конструктор `Article`: -
      -
    • Подсчёт общего количества созданных объектов.
    • -
    • Запоминание даты последнего созданного объекта.
    • -
    + +- Подсчёт общего количества созданных объектов. +- Запоминание даты последнего созданного объекта. + Используйте для этого статические свойства. Пусть вызов `Article.showStats()` выводит то и другое. diff --git a/1-js/6-objects-more/5-static-properties-and-methods/article.md b/1-js/6-objects-more/5-static-properties-and-methods/article.md index 3595a734..af19a301 100644 --- a/1-js/6-objects-more/5-static-properties-and-methods/article.md +++ b/1-js/6-objects-more/5-static-properties-and-methods/article.md @@ -4,7 +4,7 @@ [cut] -## Статические свойства +## Статические свойства В коде ниже используются статические свойства `Article.count` и `Article.DEFAULT_FORMAT`: @@ -17,7 +17,7 @@ Article.count = 0; // статическое свойство-переменна Article.DEFAULT_FORMAT = "html"; // статическое свойство-константа ``` -Они хранят данные, специфичные не для одного объекта, а для всех статей целиком. +Они хранят данные, специфичные не для одного объекта, а для всех статей целиком. Как правило, это чаще константы, такие как формат "по умолчанию" `Article.DEFAULT_FORMAT`. @@ -27,8 +27,7 @@ Article.DEFAULT_FORMAT = "html"; // статическое свойство-ко Создадим для `Article` статический метод `Article.showCount()`: -```js -//+ run +```js run function Article() { Article.count++; @@ -56,7 +55,7 @@ Article.showCount(); // (2) Ещё один хороший способ применения -- сравнение объектов. -Например, у нас есть объект `Journal` для журналов. Журналы можно сравнивать -- по толщине, по весу, по другим параметрам. +Например, у нас есть объект `Journal` для журналов. Журналы можно сравнивать -- по толщине, по весу, по другим параметрам. Объявим "стандартную" функцию сравнения, которая будет сравнивать по дате издания. Эта функция сравнения, естественно, не привязана к конкретному журналу, но относится к журналам вообще. @@ -76,8 +75,7 @@ Journal.compare = function(journalA, journalB) { В примере ниже эта функция используется для поиска самого раннего журнала из массива: -```js -//+ run +```js run function Journal(date) { this.date = date; @@ -124,8 +122,7 @@ alert( findMin(journals).getTitle() ); Например: -```js -//+ run +```js run function Journal() { /*...*/ } Journal.formatDate = function(date) { @@ -140,19 +137,16 @@ alert( *!*Journal.formatDate(new Date)*/!* ); Рассмотрим ситуацию, когда объект нужно создавать различными способами. Например, это реализовано во встроенном объекте [Date](/datetime). Он по-разному обрабатывает аргументы разных типов: -
      -
    • `new Date()` -- создаёт объект с текущей датой,
    • -
    • `new Date(milliseconds)` -- создаёт дату по количеству миллисекунд `milliseconds`,
    • -
    • `new Date(year, month, day ...)` -- создаёт дату по компонентам год, месяц, день...
    • -
    • `new Date(datestring)` -- читает дату из строки `datestring`
    • -
    +- `new Date()` -- создаёт объект с текущей датой, +- `new Date(milliseconds)` -- создаёт дату по количеству миллисекунд `milliseconds`, +- `new Date(year, month, day ...)` -- создаёт дату по компонентам год, месяц, день... +- `new Date(datestring)` -- читает дату из строки `datestring` **"Фабричный статический метод" -- удобная альтернатива такому конструктору. Так называется статический метод, который служит для создания новых объектов (поэтому и называется "фабричным").** Пример встроенного фабричного метода -- [String.fromCharCode(code)](http://javascript.ru/String.fromCharCode). Этот метод создает строку из кода символа: -```js -//+ run +```js run var str = String.fromCharCode(65); alert( str ); // 'A' ``` @@ -163,8 +157,7 @@ alert( str ); // 'A' Можно, конечно, создать полиморфную функцию-конструктор `User`: -```js -//+ run +```js run function User(userData) { if (userData) { // если указаны данные -- одна ветка if this.name = userData.name; @@ -191,12 +184,11 @@ var knownUser = new User({ knownUser.sayHi(); // Вася ``` -Подход с использованием фабричных методов был бы другим. Вместо разбора параметров в конструкторе -- делаем два метода: `User.createAnonymous` и `User.createFromData`. +Подход с использованием фабричных методов был бы другим. Вместо разбора параметров в конструкторе -- делаем два метода: `User.createAnonymous` и `User.createFromData`. Код: -```js -//+ run +```js run function User() { this.sayHi = function() { alert(this.name) @@ -232,13 +224,13 @@ knownUser.sayHi(); // Вася Преимущества использования фабричных методов: -[compare] -+Лучшая читаемость кода. Как конструктора -- вместо одной большой функции несколько маленьких, так и вызывающего кода -- явно видно, что именно создаётся. -+Лучший контроль ошибок, т.к. если в `createFromData` ничего не передали, то будет ошибка, а полиморфный конструктор создал бы анонимного посетителя. -+Удобная расширяемость. Например, нужно добавить создание администратора, без аргументов. Фабричный метод сделать легко: `User.createAdmin = function() { ... }`. А для полиморфного конструктора вызов без аргумента создаст анонима, так что нужно добавить параметр -- "тип посетителя" и усложнить этим код. -[/compare] +```compare ++ Лучшая читаемость кода. Как конструктора -- вместо одной большой функции несколько маленьких, так и вызывающего кода -- явно видно, что именно создаётся. ++ Лучший контроль ошибок, т.к. если в `createFromData` ничего не передали, то будет ошибка, а полиморфный конструктор создал бы анонимного посетителя. ++ Удобная расширяемость. Например, нужно добавить создание администратора, без аргументов. Фабричный метод сделать легко: `User.createAdmin = function() { ... }`. А для полиморфного конструктора вызов без аргумента создаст анонима, так что нужно добавить параметр -- "тип посетителя" и усложнить этим код. +``` -**Поэтому полиморфные конструкторы лучше использовать там, где нужна именно полиморфность**, т.е. когда непонятно, какого типа аргумент передадут, и хочется в одном конструкторе охватить все варианты. +**Поэтому полиморфные конструкторы лучше использовать там, где нужен именно полиморфизм**, т.е. когда непонятно, какого типа аргумент передадут, и хочется в одном конструкторе охватить все варианты. А в остальных случаях отличная альтернатива -- фабричные методы. @@ -246,10 +238,8 @@ knownUser.sayHi(); // Вася Статические свойства и методы объекта удобно применять в следующих случаях: -
      -
    • Общие действия и подсчёты, имеющие отношения ко всем объектам данного типа. В примерах выше это подсчёт количества.
    • -
    • Методы, не привязанные к конкретному объекту, например сравнение.
    • -
    • Вспомогательные методы, которые полезны вне объекта, например для форматирования даты.
    • -
    • Фабричные методы.
    • -
    +- Общие действия и подсчёты, имеющие отношения ко всем объектам данного типа. В примерах выше это подсчёт количества. +- Методы, не привязанные к конкретному объекту, например сравнение. +- Вспомогательные методы, которые полезны вне объекта, например для форматирования даты. +- Фабричные методы. diff --git a/1-js/6-objects-more/6-call-apply/1-rewrite-sum-arguments/solution.md b/1-js/6-objects-more/6-call-apply/1-rewrite-sum-arguments/solution.md index 9e5ca007..fe80254f 100644 --- a/1-js/6-objects-more/6-call-apply/1-rewrite-sum-arguments/solution.md +++ b/1-js/6-objects-more/6-call-apply/1-rewrite-sum-arguments/solution.md @@ -1,7 +1,6 @@ # Первый вариант -```js -//+ run +```js run function sumArgs() { // скопируем reduce из массива arguments.reduce = [].reduce; @@ -17,8 +16,7 @@ alert( sumArgs(4, 5, 6) ); // 15 Метод `call` здесь вполне подойдёт, так как требуется вызвать `reduce` в контексте `arguments` с одним аргументом. -```js -//+ run +```js run function sumArgs() { // запустим reduce из массива напрямую return [].reduce.call(arguments, function(a, b) { diff --git a/1-js/6-objects-more/6-call-apply/1-rewrite-sum-arguments/task.md b/1-js/6-objects-more/6-call-apply/1-rewrite-sum-arguments/task.md index b03a9fc7..d1776136 100644 --- a/1-js/6-objects-more/6-call-apply/1-rewrite-sum-arguments/task.md +++ b/1-js/6-objects-more/6-call-apply/1-rewrite-sum-arguments/task.md @@ -1,11 +1,12 @@ -# Перепишите суммирование аргументов +importance: 5 + +--- -[importance 5] +# Перепишите суммирование аргументов Есть функция `sum`, которая суммирует все элементы массива: -```js -//+ run +```js run function sum(arr) { return arr.reduce(function(a, b) { return a + b; diff --git a/1-js/6-objects-more/6-call-apply/2-apply-function-skip-first-argument/_js.view/test.js b/1-js/6-objects-more/6-call-apply/2-apply-function-skip-first-argument/_js.view/test.js index 871a19a7..16693ca9 100644 --- a/1-js/6-objects-more/6-call-apply/2-apply-function-skip-first-argument/_js.view/test.js +++ b/1-js/6-objects-more/6-call-apply/2-apply-function-skip-first-argument/_js.view/test.js @@ -1,6 +1,6 @@ describe("applyAll", function() { - it("применяет функцию ко всем аргументам, начиная со 2го", function() { + it("применяет функцию ко всем аргументам, начиная со 2-го", function() { var min = applyAll(Math.min, 1, 2, 3); assert.equal(min, 1); }); diff --git a/1-js/6-objects-more/6-call-apply/2-apply-function-skip-first-argument/solution.md b/1-js/6-objects-more/6-call-apply/2-apply-function-skip-first-argument/solution.md index c8579b0a..af01a5a7 100644 --- a/1-js/6-objects-more/6-call-apply/2-apply-function-skip-first-argument/solution.md +++ b/1-js/6-objects-more/6-call-apply/2-apply-function-skip-first-argument/solution.md @@ -1,7 +1,6 @@ -```js -//+ run +```js run function sum() { return [].reduce.call(arguments, function(a, b) { return a + b; diff --git a/1-js/6-objects-more/6-call-apply/2-apply-function-skip-first-argument/task.md b/1-js/6-objects-more/6-call-apply/2-apply-function-skip-first-argument/task.md index 95bd2413..f45a936b 100644 --- a/1-js/6-objects-more/6-call-apply/2-apply-function-skip-first-argument/task.md +++ b/1-js/6-objects-more/6-call-apply/2-apply-function-skip-first-argument/task.md @@ -1,10 +1,12 @@ -# Примените функцию к аргументам +importance: 5 + +--- -[importance 5] +# Примените функцию к аргументам -Напишите функцию `applyAll(func, arg1, arg2...)`, которая получает функцию `func` и произвольное количество аргументов. +Напишите функцию `applyAll(func, arg1, arg2...)`, которая получает функцию `func` и произвольное количество аргументов. -Она должна вызвать `func(arg1, arg2...)`, то есть передать в `func` все аргументы, начиная со второго, и возвратить результат. +Она должна вызвать `func(arg1, arg2...)`, то есть передать в `func` все аргументы, начиная со второго, и возвратить результат. Например: @@ -18,8 +20,7 @@ alert( applyAll(Math.min, 2, -2, 3) ); // -2 Область применения `applyAll`, конечно, шире, можно вызывать её и со своими функциями: -```js -//+ run +```js run function sum() { // суммирует аргументы: sum(1,2,3) = 6 return [].reduce.call(arguments, function(a, b) { return a + b; diff --git a/1-js/6-objects-more/6-call-apply/article.md b/1-js/6-objects-more/6-call-apply/article.md index 08f9f386..d86c4d1c 100644 --- a/1-js/6-objects-more/6-call-apply/article.md +++ b/1-js/6-objects-more/6-call-apply/article.md @@ -6,7 +6,7 @@ [cut] -## Метод call +## Метод call Синтаксис метода `call`: @@ -30,8 +30,7 @@ function showFullName() { Вызов `showFullName.call(user)` запустит функцию, установив `this = user`, вот так: -```js -//+ run +```js run function showFullName() { alert( this.firstName + " " + this.lastName ); } @@ -49,8 +48,7 @@ showFullName.call(user) // "Василий Петров" После контекста в `call` можно передать аргументы для функции. Вот пример с более сложным вариантом `showFullName`, который конструирует ответ из указанных свойств объекта: -```js -//+ run +```js run var user = { firstName: "Василий", surname: "Петров", @@ -70,7 +68,7 @@ showFullName.call(user, 'firstName', 'patronym') // "Василий Иванов ## "Одалживание метода" -При помощи `call` можно легко взять метод одного объекта, в том числе встроенного, и вызвать в контексте другого. +При помощи `call` можно легко взять метод одного объекта, в том числе встроенного, и вызвать в контексте другого. Это называется "одалживание метода" (на англ. *method borrowing*). @@ -80,8 +78,7 @@ showFullName.call(user, 'firstName', 'patronym') // "Василий Иванов Нет ничего проще! Давайте скопируем метод `join` из обычного массива: -```js -//+ run +```js run function printArgs() { arguments.join = [].join; // одолжили метод (1) @@ -93,12 +90,10 @@ function printArgs() { printArgs(1, 2, 3); ``` -
      -
    1. В строке `(1)` объявлен пустой массив `[]` и скопирован его метод `[].join`. Обратим внимание, мы не вызываем его, а просто копируем. Функция, в том числе встроенная -- обычное значение, мы можем скопировать любое свойство любого объекта, и `[].join` здесь не исключение.
    2. -
    3. В строке `(2)` запустили `join` в контексте `arguments`, как будто он всегда там был.
    4. - -[smart header="Почему вызов сработает?"] +1. В строке `(1)` объявлен пустой массив `[]` и скопирован его метод `[].join`. Обратим внимание, мы не вызываем его, а просто копируем. Функция, в том числе встроенная -- обычное значение, мы можем скопировать любое свойство любого объекта, и `[].join` здесь не исключение. +2. В строке `(2)` запустили `join` в контексте `arguments`, как будто он всегда там был. +````smart header="Почему вызов сработает?" Здесь метод join массива скопирован и вызван в контексте `arguments`. Не произойдёт ли что-то плохое от того, что `arguments` -- не массив? Почему он, вообще, сработал? Ответ на эти вопросы простой. В соответствии [со спецификацией](http://es5.github.com/x15.4.html#x15.4.4.5), внутри `join` реализован примерно так: @@ -117,12 +112,11 @@ function join(separator) { } ``` -Как видно, используется `this`, числовые индексы и свойство `length`. Если эти свойства есть, то все в порядке. А больше ничего и не нужно. +Как видно, используется `this`, числовые индексы и свойство `length`. Если эти свойства есть, то все в порядке. А больше ничего и не нужно. В качестве `this` подойдёт даже обычный объект: -```js -//+ run +```js run var obj = { // обычный объект с числовыми индексами и length 0: "А", 1: "Б", @@ -135,19 +129,17 @@ obj.join = [].join; alert( obj.join(';') ); // "A;Б;В" */!* ``` - -[/smart] +```` ...Однако, копирование метода из одного объекта в другой не всегда приемлемо! -Представим на минуту, что вместо `arguments` у нас -- произвольный объект. У него тоже есть числовые индексы, `length` и мы хотим вызвать в его контексте метод `[].join`. То есть, ситуация похожа на `arguments`, но (!) вполне возможно, что у объекта есть *свой* метод `join`. +Представим на минуту, что вместо `arguments` у нас -- произвольный объект. У него тоже есть числовые индексы, `length` и мы хотим вызвать в его контексте метод `[].join`. То есть, ситуация похожа на `arguments`, но (!) вполне возможно, что у объекта есть *свой* метод `join`. Поэтому копировать `[].join`, как сделано выше, нельзя: если он перезапишет собственный `join` объекта, то будет страшный бардак и путаница. Безопасно вызвать метод нам поможет `call`: -```js -//+ run +```js run function printArgs() { var join = [].join; // скопируем ссылку на функцию в переменную @@ -163,18 +155,17 @@ function printArgs() { printArgs(1, 2, 3); ``` -Мы вызвали метод без копирования. Чисто, безопасно. +Мы вызвали метод без копирования. Чисто, безопасно. ## Ещё пример: [].slice.call(arguments) -В JavaScript есть очень простой способ сделать из `arguments` настоящий массив. Для этого возьмём метод массива: slice. +В JavaScript есть очень простой способ сделать из `arguments` настоящий массив. Для этого возьмём метод массива: slice. -По стандарту вызов `arr.slice(start, end)` создаёт новый массив и копирует в него элементы массива `arr` от `start` до `end`. А если `start` и `end` не указаны, то копирует весь массив. +По стандарту вызов `arr.slice(start, end)` создаёт новый массив и копирует в него элементы массива `arr` от `start` до `end`. А если `start` и `end` не указаны, то копирует весь массив. Вызовем его в контексте `arguments`: -```js -//+ run +```js run function printArgs() { // вызов arr.slice() скопирует все элементы из this в новый массив *!* @@ -188,7 +179,7 @@ printArgs('Привет', 'мой', 'мир'); // Привет, мой, мир Как и в случае с `join`, такой вызов технически возможен потому, что `slice` для работы требует только нумерованные свойства и `length`. Всё это в `arguments` есть. -## Метод apply +## Метод apply Если нам неизвестно, с каким количеством аргументов понадобится вызвать функцию, можно использовать более мощный метод: `apply`. @@ -200,7 +191,7 @@ func.call(context, arg1, arg2); func.apply(context, [arg1, arg2]); ``` -В частности, эти две строчки cработают одинаково: +В частности, эти две строчки сработают одинаково: ```js showFullName.call(user, 'firstName', 'surname'); @@ -212,15 +203,13 @@ showFullName.apply(user, ['firstName', 'surname']); Например, в JavaScript есть встроенная функция `Math.max(a, b, c...)`, которая возвращает максимальное значение из аргументов: -```js -//+ run +```js run alert( Math.max(1, 5, 2) ); // 5 ``` При помощи `apply` мы могли бы найти максимум в произвольном массиве, вот так: -```js -//+ run +```js run var arr = []; arr.push(1); arr.push(5); @@ -230,19 +219,17 @@ arr.push(2); alert( Math.max.apply(null, arr) ); // 5 ``` -В примере выше мы передали аргументы через массив -- второй параметр `apply`... Но вы, наверное, заметили небольшую странность? В качестве контекста `this` был передан `null`. +В примере выше мы передали аргументы через массив -- второй параметр `apply`... Но вы, наверное, заметили небольшую странность? В качестве контекста `this` был передан `null`. Строго говоря, полным эквивалентом вызову `Math.max(1,2,3)` был бы вызов `Math.max.apply(Math, [1,2,3])`. В обоих этих вызовах контекстом будет объект `Math`. Но в данном случае в качестве контекста можно передавать что угодно, поскольку в своей внутренней реализации метод `Math.max` не использует `this`. Действительно, зачем `this`, если нужно всего лишь выбрать максимальный из аргументов? Вот так, при помощи `apply` мы получили короткий и элегантный способ вычислить максимальное значение в массиве! -[smart header="Вызов `call/apply` с `null` или `undefined`"] - +````smart header="Вызов `call/apply` с `null` или `undefined`" В современном стандарте `call/apply` передают `this` "как есть". А в старом, без `use strict`, при указании первого аргумента `null` или `undefined` в `call/apply`, функция получает `this = window`, например: Современный стандарт: -```js -//+ run +```js run function f() { "use strict"; *!* @@ -255,57 +242,41 @@ f.call(null); Без `use strict`: -```js -//+ run +```js run function f() { alert( this ); // window } f.call(null); ``` - -[/smart] +```` ## Итого про this Значение `this` устанавливается в зависимости от того, как вызвана функция: -
      -
      При вызове функции как метода
      -
      +- При вызове функции как метода: -```js -//+ no-beautify -obj.func(...) // this = obj -obj["func"](...) -``` + ```js no-beautify + obj.func(...) // this = obj + obj["func"](...) + ``` -
      -
      При обычном вызове
      -
      +- При обычном вызове: -```js -func(...) // this = window (ES3) /undefined (ES5) -``` - -
      -
      В `new`
      -
      - -```js -new func() // this = {} (новый объект) -``` - -
      -
      Явное указание
      -
      - -```js -func.apply(context, args) // this = context (явная передача) -func.call(context, arg1, arg2, ...) -``` + ```js + func(...) // this = window (ES3) /undefined (ES5) + ``` -
      -
      +- В `new`: + + ```js + new func() // this = {} (новый объект) + ``` +- Явное указание: + ```js + func.apply(context, args) // this = context (явная передача) + func.call(context, arg1, arg2, ...) + ``` diff --git a/1-js/6-objects-more/7-bind/1-cross-browser-bind/solution.md b/1-js/6-objects-more/7-bind/1-cross-browser-bind/solution.md index 28b52dcb..897c6754 100644 --- a/1-js/6-objects-more/7-bind/1-cross-browser-bind/solution.md +++ b/1-js/6-objects-more/7-bind/1-cross-browser-bind/solution.md @@ -1,8 +1,8 @@ Страшновато выглядит, да? Работает так (по строкам): -
        -
      1. Вызов `bind` сохраняет дополнительные аргументы `args` (они идут со 2го номера) в массив `bindArgs`.
      2. -
      3. ... и возвращает обертку `wrapper`.
      4. -
      5. Эта обёртка делает из `arguments` массив `args` и затем, используя метод [concat](http://javascript.ru/Array/concat), прибавляет их к аргументам `bindArgs` (карринг).
      6. -
      7. Затем передаёт вызов `func` с контекстом и общим массивом аргументов.
      8. -
      + +1. Вызов `bind` сохраняет дополнительные аргументы `args` (они идут со 2-го номера) в массив `bindArgs`. +2. ... и возвращает обертку `wrapper`. +3. Эта обёртка делает из `arguments` массив `args` и затем, используя метод [concat](http://javascript.ru/Array/concat), прибавляет их к аргументам `bindArgs` (карринг). +4. Затем передаёт вызов `func` с контекстом и общим массивом аргументов. + diff --git a/1-js/6-objects-more/7-bind/1-cross-browser-bind/task.md b/1-js/6-objects-more/7-bind/1-cross-browser-bind/task.md index ce481712..0c6897bb 100644 --- a/1-js/6-objects-more/7-bind/1-cross-browser-bind/task.md +++ b/1-js/6-objects-more/7-bind/1-cross-browser-bind/task.md @@ -1,15 +1,16 @@ -# Кросс-браузерная эмуляция bind +importance: 3 + +--- -[importance 3] +# Кросс-браузерная эмуляция bind Если вы вдруг захотите копнуть поглубже -- аналог `bind` для IE8- и старых версий других браузеров будет выглядеть следующим образом: -```js -//+ no-beautify +```js no-beautify function bind(func, context /*, args*/) { var bindArgs = [].slice.call(arguments, 2); // (1) function wrapper() { // (2) - var args = [].slice.call(arguments); + var args = [].slice.call(arguments); var unshiftArgs = bindArgs.concat(args); // (3) return func.apply(context, unshiftArgs); // (4) } @@ -19,5 +20,5 @@ function bind(func, context /*, args*/) { Использование -- вместо `mul.bind(null, 2)` вызывать `bind(mul, null, 2)`. -Не факт, что он вам понадобится, но в качестве упражнение попробуйте разобраться, как это работает. +Не факт, что он вам понадобится, но в качестве упражнения попробуйте разобраться, как это работает. diff --git a/1-js/6-objects-more/7-bind/2-write-to-object-after-bind/solution.md b/1-js/6-objects-more/7-bind/2-write-to-object-after-bind/solution.md index ccd6ca0c..48131a19 100644 --- a/1-js/6-objects-more/7-bind/2-write-to-object-after-bind/solution.md +++ b/1-js/6-objects-more/7-bind/2-write-to-object-after-bind/solution.md @@ -1,7 +1,6 @@ Ответ: `Hello`. -```js -//+ run +```js run function f() { alert( this ); } diff --git a/1-js/6-objects-more/7-bind/2-write-to-object-after-bind/task.md b/1-js/6-objects-more/7-bind/2-write-to-object-after-bind/task.md index a189eeb3..ae7615d6 100644 --- a/1-js/6-objects-more/7-bind/2-write-to-object-after-bind/task.md +++ b/1-js/6-objects-more/7-bind/2-write-to-object-after-bind/task.md @@ -1,6 +1,8 @@ -# Запись в объект после bind +importance: 5 + +--- -[importance 5] +# Запись в объект после bind Что выведет функция? diff --git a/1-js/6-objects-more/7-bind/3-second-bind/solution.md b/1-js/6-objects-more/7-bind/3-second-bind/solution.md index 2a8b26d8..0cb15772 100644 --- a/1-js/6-objects-more/7-bind/3-second-bind/solution.md +++ b/1-js/6-objects-more/7-bind/3-second-bind/solution.md @@ -1,7 +1,6 @@ Ответ: `"Вася"`. -```js -//+ run no-beautify +```js run no-beautify function f() { alert(this.name); } @@ -27,8 +26,7 @@ function bind(func, context) { Код станет таким: -```js -//+ no-beautify +```js no-beautify function f() { alert(this.name); } diff --git a/1-js/6-objects-more/7-bind/3-second-bind/task.md b/1-js/6-objects-more/7-bind/3-second-bind/task.md index 3b0f06b2..09fe9c45 100644 --- a/1-js/6-objects-more/7-bind/3-second-bind/task.md +++ b/1-js/6-objects-more/7-bind/3-second-bind/task.md @@ -1,11 +1,12 @@ -# Повторный bind +importance: 5 + +--- -[importance 5] +# Повторный bind Что выведет этот код? -```js -//+ no-beautify +```js no-beautify function f() { alert(this.name); } diff --git a/1-js/6-objects-more/7-bind/4-function-property-after-bind/task.md b/1-js/6-objects-more/7-bind/4-function-property-after-bind/task.md index b182b4e5..ce9fbd69 100644 --- a/1-js/6-objects-more/7-bind/4-function-property-after-bind/task.md +++ b/1-js/6-objects-more/7-bind/4-function-property-after-bind/task.md @@ -1,6 +1,8 @@ -# Свойство функции после bind +importance: 5 + +--- -[importance 5] +# Свойство функции после bind В свойство функции записано значение. Изменится ли оно после применения `bind`? Обоснуйте ответ. diff --git a/1-js/6-objects-more/7-bind/5-question-use-bind/solution.md b/1-js/6-objects-more/7-bind/5-question-use-bind/solution.md index 7eb5963e..f8af487d 100644 --- a/1-js/6-objects-more/7-bind/5-question-use-bind/solution.md +++ b/1-js/6-objects-more/7-bind/5-question-use-bind/solution.md @@ -1,11 +1,10 @@ # Решение с bind -Ошибка происходит потому, что `ask` получает только функцию, без объекта-контекста. +Ошибка происходит потому, что `ask` получает только функцию, без объекта-контекста. Используем `bind`, чтобы передать в `ask` функцию с уже привязанным контекстом: -```js -//+ run +```js run "use strict"; function ask(question, answer, ok, fail) { @@ -42,13 +41,12 @@ vasya.checkPassword(); Альтернативное решение -- сделать функции-обёртки над `user.loginOk/loginFail`: -```js -//+ no-beautify +```js no-beautify var user = { ... checkPassword: function() { *!* - ask("Ваш пароль?", this.password, + ask("Ваш пароль?", this.password, function() { user.loginOk(); }, function() { user.loginFail(); }); */!* } @@ -65,8 +63,7 @@ vasya.checkPassword(); // упс будет ошибка, ведь в коде Для того, чтобы избежать проблем, можно использовать `this`. Внутри `checkPassword` он всегда будет равен текущему объекту, так что скопируем его в переменную, которую назовём `self`: -```js -//+ run +```js run "use strict"; function ask(question, answer, ok, fail) { diff --git a/1-js/6-objects-more/7-bind/5-question-use-bind/task.md b/1-js/6-objects-more/7-bind/5-question-use-bind/task.md index a36bb858..ccef3e6c 100644 --- a/1-js/6-objects-more/7-bind/5-question-use-bind/task.md +++ b/1-js/6-objects-more/7-bind/5-question-use-bind/task.md @@ -1,15 +1,16 @@ -# Использование функции вопросов +importance: 5 + +--- -[importance 5] +# Использование функции вопросов -Вызов `user.checkPassword()` в коде ниже должен, при помощи `ask`, спрашивать пароль и вызывать `loginOk/loginFail` в зависимости от правильности ответа. +Вызов `user.checkPassword()` в коде ниже должен, при помощи `ask`, спрашивать пароль и вызывать `loginOk/loginFail` в зависимости от правильности ответа. -Однако, его вызов приводит к ошибке. Почему? +Однако, его вызов приводит к ошибке. Почему? Исправьте выделенную строку, чтобы всё работало (других строк изменять не надо). -```js -//+ run +```js run "use strict"; function ask(question, answer, ok, fail) { diff --git a/1-js/6-objects-more/7-bind/6-ask-currying/solution.md b/1-js/6-objects-more/7-bind/6-ask-currying/solution.md index 86877551..e2e5cbad 100644 --- a/1-js/6-objects-more/7-bind/6-ask-currying/solution.md +++ b/1-js/6-objects-more/7-bind/6-ask-currying/solution.md @@ -2,8 +2,7 @@ Первое решение -- передать в `ask` функции с привязанным контекстом и аргументами. -```js -//+ run +```js run "use strict"; function ask(question, answer, ok, fail) { @@ -34,8 +33,7 @@ user.checkPassword(); Второе решение -- это скопировать `this` в локальную переменную (чтобы внешняя перезапись не повлияла): -```js -//+ run +```js run "use strict"; function ask(question, answer, ok, fail) { diff --git a/1-js/6-objects-more/7-bind/6-ask-currying/task.md b/1-js/6-objects-more/7-bind/6-ask-currying/task.md index 7b48e190..e38775e5 100644 --- a/1-js/6-objects-more/7-bind/6-ask-currying/task.md +++ b/1-js/6-objects-more/7-bind/6-ask-currying/task.md @@ -1,21 +1,22 @@ -# Использование функции вопросов с каррингом +importance: 5 + +--- -[importance 5] +# Использование функции вопросов с каррингом -Эта задача -- усложнённый вариант задачи [](/task/question-use-bind). В ней объект `user` изменён. +Эта задача -- усложнённый вариант задачи . В ней объект `user` изменён. -Теперь заменим две функции `user.loginOk()` и `user.loginFail()` на единый метод: `user.loginDone(true/false)`, который нужно вызвать с `true` при верном ответе и `fail` -- при неверном. +Теперь заменим две функции `user.loginOk()` и `user.loginFail()` на единый метод: `user.loginDone(true/false)`, который нужно вызвать с `true` при верном ответе и с `false` -- при неверном. Код ниже делает это, соответствующий фрагмент выделен. **Сейчас он обладает важным недостатком: при записи в `user` другого значения объект перестанет корректно работать, вы увидите это, запустив пример ниже (будет ошибка).** -Как бы вы написали правильно? +Как бы вы написали правильно? **Исправьте выделенный фрагмент, чтобы код заработал.** -```js -//+ run +```js run "use strict"; function ask(question, answer, ok, fail) { @@ -28,7 +29,7 @@ var user = { login: 'Василий', password: '12345', - // метод для вызова из ask + // метод для вызова из ask loginDone: function(result) { alert( this.login + (result ? ' вошёл в сайт' : ' ошибка входа') ); }, diff --git a/1-js/6-objects-more/7-bind/article.md b/1-js/6-objects-more/7-bind/article.md index a18a910e..260297c8 100644 --- a/1-js/6-objects-more/7-bind/article.md +++ b/1-js/6-objects-more/7-bind/article.md @@ -2,7 +2,7 @@ Функции в JavaScript никак не привязаны к своему контексту `this`, с одной стороны, здорово -- это позволяет быть максимально гибкими, одалживать методы и так далее. -Но с другой стороны -- в некоторых случаях контекст может быть потерян. То есть мы вроде как вызываем метод объекта, а на самом деле он получает `this = undefined`. +Но с другой стороны -- в некоторых случаях контекст может быть потерян. То есть мы вроде как вызываем метод объекта, а на самом деле он получает `this = undefined`. Такая ситуация является типичной для начинающих разработчиков, но бывает и у "зубров" тоже. Конечно, "зубры" при этом знают, что с ней делать. @@ -10,14 +10,13 @@ ## Пример потери контекста -В браузере есть встроенная функция `setTimeout(func, ms)`, которая вызывает выполение функции `func` через `ms` миллисекунд (=1/1000 секунды). +В браузере есть встроенная функция `setTimeout(func, ms)`, которая вызывает выполнение функции `func` через `ms` миллисекунд (=1/1000 секунды). -Мы подробно остановимся на ней и её тонкостях позже, в главе [](/settimeout-setinterval), а пока просто посмотрим пример. +Мы подробно остановимся на ней и её тонкостях позже, в главе , а пока просто посмотрим пример. -Этот код выведет "Привет" через 1000мс, то есть 1 секунду: +Этот код выведет "Привет" через 1000 мс, то есть 1 секунду: -```js -//+ run +```js run setTimeout(function() { alert( "Привет" ); }, 1000); @@ -25,8 +24,7 @@ setTimeout(function() { Попробуем сделать то же самое с методом объекта, следующий код должен выводить имя пользователя через 1 секунду: -```js -//+ run +```js run var user = { firstName: "Вася", sayHi: function() { @@ -48,7 +46,6 @@ var f = user.sayHi; setTimeout(f, 1000); // контекст user потеряли ``` - Ситуация довольно типична -- мы хотим передать метод объекта куда-то в другое место кода, откуда он потом может быть вызван. Как бы прикрепить к нему контекст, желательно, с минимумом плясок с бубном и при этом надёжно? Есть несколько способов решения, среди которых мы, в зависимости от ситуации, можем выбирать. @@ -57,8 +54,7 @@ setTimeout(f, 1000); // контекст user потеряли Самый простой вариант решения -- это обернуть вызов в анонимную функцию: -```js -//+ run +```js run var user = { firstName: "Вася", sayHi: function() { @@ -77,9 +73,7 @@ setTimeout(function() { Это решение также позволяет передать дополнительные аргументы: - -```js -//+ run +```js run var user = { firstName: "Вася", sayHi: function(who) { @@ -94,8 +88,7 @@ setTimeout(function() { */!* ``` - -Но тут же появляется и уязвимое место в структуре кода! +Но тут же появляется и уязвимое место в структуре кода! А что, если до срабатывания `setTimeout` (ведь есть целая секунда) в переменную `user` будет записано другое значение? К примеру, в другом месте кода будет присвоено `user=(другой пользователь)`... В этом случае вызов неожиданно будет совсем не тот! @@ -115,8 +108,7 @@ function bind(func, context) { Посмотрим, что она делает, как работает, на таком примере: -```js -//+ run +```js function f() { alert( this ); } @@ -153,8 +145,7 @@ function() { // (*) Если вызвать `g` с аргументами, то также будет работать: -```js -//+ run +```js run function f(a, b) { alert( this ); alert( a + b ); @@ -170,8 +161,7 @@ g(1, 2); // Context, затем 3 Вернёмся к `user.sayHi`. Вариант с `bind`: -```js -//+ run +```js run function bind(func, context) { return function() { return func.apply(context, arguments); @@ -190,14 +180,13 @@ setTimeout(bind(user.sayHi, user), 1000); */!* ``` -Теперь всё в порядке! +Теперь всё в порядке! -Вызов `bind(user.sayHi, user)` возвращает такую функцию-обёртку, которая привязывает `user.sayHi` к контексту `user`. Она будет вызвана через 1000мс. +Вызов `bind(user.sayHi, user)` возвращает такую функцию-обёртку, которая привязывает `user.sayHi` к контексту `user`. Она будет вызвана через 1000 мс. Полученную обёртку можно вызвать и с аргументами -- они пойдут в `user.sayHi` без изменений, фиксирован лишь контекст. -```js -//+ run +```js run var user = { firstName: "Вася", *!* @@ -228,15 +217,14 @@ sayHi("Маша"); // Вася: Привет, Маша Изменения очень небольшие: -```js -//+ run +```js run function f(a, b) { alert( this ); alert( a + b ); } *!* -// вместо +// вместо // var g = bind(f, "Context"); var g = f.bind("Context"); */!* @@ -249,21 +237,20 @@ g(1, 2); // Context, затем 3 var wrapper = func.bind(context[, arg1, arg2...]) ``` -
      -
      `func`
      -
      Произвольная функция
      -
      `context`
      -
      Контекст, который привязывается к `func`
      -
      `arg1`, `arg2`, ...
      -
      Если указаны аргументы `arg1, arg2...` -- они будут прибавлены к каждому вызову новой функции, причем встанут *перед* теми, которые указаны при вызове.
      -
      +`func` +: Произвольная функция + +`context` +: Контекст, который привязывается к `func` + +`arg1`, `arg2`, ... +: Если указаны аргументы `arg1, arg2...` -- они будут прибавлены к каждому вызову новой функции, причем встанут *перед* теми, которые указаны при вызове. Результат вызова `func.bind(context)` аналогичен вызову `bind(func, context)`, описанному выше. То есть, `wrapper` -- это обёртка, фиксирующая контекст и передающая вызовы в `func`. Также можно указать аргументы, тогда и они будут фиксированы, но об этом чуть позже. Пример со встроенным методом `bind`: -```js -//+ run +```js run var user = { firstName: "Вася", sayHi: function() { @@ -272,24 +259,24 @@ var user = { }; *!* -// setTimeout( bind(user.sayHi, user), 1000 ); +// setTimeout( bind(user.sayHi, user), 1000 ); setTimeout(user.sayHi.bind(user), 1000); // аналог через встроенный метод */!* ``` Получили простой и надёжный способ привязать контекст, причём даже встроенный в JavaScript. -Далее мы будем использовать именно встроенный метод `bind`. +Далее мы будем использовать именно встроенный метод `bind`. -[warn header="bind не похож на call/apply"] +```warn header="bind не похож на call/apply" Методы `bind` и `call/apply` близки по синтаксису, но есть важнейшее отличие. Методы `call/apply` вызывают функцию с заданным контекстом и аргументами. А `bind` не вызывает функцию. Он только возвращает "обёртку", которую мы можем вызвать позже, и которая передаст вызов в исходную функцию, с привязанным контекстом. -[/warn] +``` -[smart header="Привязать всё: `bindAll`"] +````smart header="Привязать всё: `bindAll`" Если у объекта много методов и мы планируем их активно передавать, то можно привязать контекст для них всех в цикле: ```js @@ -301,8 +288,7 @@ for (var prop in user) { ``` В некоторых JS-фреймворках есть даже встроенные функции для этого, например [_.bindAll(obj)](http://lodash.com/docs#bindAll). -[/smart] - +```` ## Карринг @@ -322,8 +308,7 @@ function mul(a, b) { При помощи `bind` создадим функцию `double`, удваивающую значения. Это будет вариант функции `mul` с фиксированным первым аргументом: -```js -//+ run +```js run *!* // double умножает только на два var double = mul.bind(null, 2); // контекст фиксируем null, он не используется @@ -340,8 +325,7 @@ alert( double(5) ); // = mul(2, 5) = 10 Другая частичная функция `triple` утраивает значения: -```js -//+ run +```js run *!* var triple = mul.bind(null, 3); // контекст фиксируем null, он не используется */!* @@ -373,8 +357,7 @@ function ask(question, answer, ok, fail) { Пример использования: -```js -//+ run +```js run *!* ask("Выпустить птичку?", "да", fly, die); */!* @@ -390,45 +373,18 @@ function die() { ## Итого -
        -
      • Функция сама по себе не запоминает контекст выполнения.
      • -
      • Чтобы гарантировать правильный контекст для вызова `obj.func()`, нужно использовать функцию-обёртку, задать её через анонимную функцию: -```js -setTimeout(function() { - obj.func(); -}) -``` -
      • -
      • ...Либо использовать `bind`: - -```js -setTimeout(obj.func.bind(obj)); -``` -
      • -
      • Вызов `bind` часто используют для привязки функции к контексту, чтобы затем присвоить её в обычную переменную и вызывать уже без явного указания объекта.
      • -
      • Вызов `bind` также позволяет фиксировать первые аргументы функции ("каррировать" её), и таким образом из общей функции получить её "частные" варианты -- чтобы использовать их многократно без повтора одних и тех же аргументов каждый раз.
      • -
      +- Функция сама по себе не запоминает контекст выполнения. +- Чтобы гарантировать правильный контекст для вызова `obj.func()`, нужно использовать функцию-обёртку, задать её через анонимную функцию: + ```js + setTimeout(function() { + obj.func(); + }) + ``` +- ...Либо использовать `bind`: + + ```js + setTimeout(obj.func.bind(obj)); + ``` +- Вызов `bind` часто используют для привязки функции к контексту, чтобы затем присвоить её в обычную переменную и вызывать уже без явного указания объекта. +- Вызов `bind` также позволяет фиксировать первые аргументы функции ("каррировать" её), и таким образом из общей функции получить её "частные" варианты -- чтобы использовать их многократно без повтора одних и тех же аргументов каждый раз. -[head] - -[/head] diff --git a/1-js/6-objects-more/7-bind/head.html b/1-js/6-objects-more/7-bind/head.html new file mode 100644 index 00000000..dd262f3f --- /dev/null +++ b/1-js/6-objects-more/7-bind/head.html @@ -0,0 +1,21 @@ + \ No newline at end of file diff --git a/1-js/6-objects-more/8-decorators/1-logging-decorator/solution.md b/1-js/6-objects-more/8-decorators/1-logging-decorator/solution.md index 103ff4e5..15a1f708 100644 --- a/1-js/6-objects-more/8-decorators/1-logging-decorator/solution.md +++ b/1-js/6-objects-more/8-decorators/1-logging-decorator/solution.md @@ -1,7 +1,6 @@ Возвратим декоратор `wrapper` который будет записывать аргумент в `log` и передавать вызов в `f`: -```js -//+ run +```js run function work(a) { /*...*/ // work - произвольная функция, один аргумент } @@ -39,6 +38,3 @@ user.method = makeLogging(user.method, log); Теперь при вызове `user.method(...)` в декоратор будет передаваться контекст `this`, который надо передать исходной функции через `call/apply`. - - - diff --git a/1-js/6-objects-more/8-decorators/1-logging-decorator/task.md b/1-js/6-objects-more/8-decorators/1-logging-decorator/task.md index e1efa4f8..7fb3b13f 100644 --- a/1-js/6-objects-more/8-decorators/1-logging-decorator/task.md +++ b/1-js/6-objects-more/8-decorators/1-logging-decorator/task.md @@ -1,10 +1,12 @@ -# Логирующий декоратор (1 аргумент) +importance: 5 + +--- -[importance 5] +# Логирующий декоратор (1 аргумент) -Создайте декоратор `makeLogging(f, log)`, который берет функцию `f` и массив `log`. +Создайте декоратор `makeLogging(f, log)`, который берет функцию `f` и массив `log`. -Он должен возвращать обёртку вокруг `f`, которая при каждом вызове записывает ("логирует") аргументы в `log`, а затем передает вызов в `f`. +Он должен возвращать обёртку вокруг `f`, которая при каждом вызове записывает ("логирует") аргументы в `log`, а затем передает вызов в `f`. **В этой задаче можно считать, что у функции `f` ровно один аргумент.** diff --git a/1-js/6-objects-more/8-decorators/2-logging-decorator-arguments/solution.md b/1-js/6-objects-more/8-decorators/2-logging-decorator-arguments/solution.md index dfc2b902..412ecb20 100644 --- a/1-js/6-objects-more/8-decorators/2-logging-decorator-arguments/solution.md +++ b/1-js/6-objects-more/8-decorators/2-logging-decorator-arguments/solution.md @@ -1,9 +1,8 @@ -Решение аналогично задаче [](/task/logging-decorator), разница в том, что в лог вместо одного аргумента идет весь объект `arguments`. +Решение аналогично задаче , разница в том, что в лог вместо одного аргумента идет весь объект `arguments`. Для передачи вызова с произвольным количеством аргументов используем `f.apply(this, arguments)`. -```js -//+ run +```js run function work(a, b) { alert( a + b ); // work - произвольная функция } diff --git a/1-js/6-objects-more/8-decorators/2-logging-decorator-arguments/task.md b/1-js/6-objects-more/8-decorators/2-logging-decorator-arguments/task.md index e5ab5b6b..241943e7 100644 --- a/1-js/6-objects-more/8-decorators/2-logging-decorator-arguments/task.md +++ b/1-js/6-objects-more/8-decorators/2-logging-decorator-arguments/task.md @@ -1,10 +1,12 @@ -# Логирующий декоратор (много аргументов) +importance: 3 + +--- -[importance 3] +# Логирующий декоратор (много аргументов) -Создайте декоратор `makeLogging(func, log)`, для функции `func` возвращающий обёртку, которая при каждом вызове добавляет её аргументы в массив `log`. +Создайте декоратор `makeLogging(func, log)`, для функции `func` возвращающий обёртку, которая при каждом вызове добавляет её аргументы в массив `log`. -Условие аналогично задаче [](/task/logging-decorator), но допускается `func` с любым набором аргументов. +Условие аналогично задаче , но допускается `func` с любым набором аргументов. Работать должно так: diff --git a/1-js/6-objects-more/8-decorators/3-caching-decorator/solution.md b/1-js/6-objects-more/8-decorators/3-caching-decorator/solution.md index 9b413f92..af6adb1e 100644 --- a/1-js/6-objects-more/8-decorators/3-caching-decorator/solution.md +++ b/1-js/6-objects-more/8-decorators/3-caching-decorator/solution.md @@ -1,17 +1,16 @@ Запоминать результаты вызова функции будем в замыкании, в объекте `cache: { ключ:значение }`. -```js -//+ run no-beautify -function f(x) { +```js run no-beautify +function f(x) { return Math.random()*x; } *!* -function makeCaching(f) { - var cache = {}; +function makeCaching(f) { + var cache = {}; return function(x) { - if (!(x in cache)) { + if (!(x in cache)) { cache[x] = f.call(this, x); } return cache[x]; diff --git a/1-js/6-objects-more/8-decorators/3-caching-decorator/task.md b/1-js/6-objects-more/8-decorators/3-caching-decorator/task.md index f0b3a78f..192dce76 100644 --- a/1-js/6-objects-more/8-decorators/3-caching-decorator/task.md +++ b/1-js/6-objects-more/8-decorators/3-caching-decorator/task.md @@ -1,21 +1,21 @@ -# Кеширующий декоратор +importance: 5 + +--- -[importance 5] +# Кеширующий декоратор Создайте декоратор `makeCaching(f)`, который берет функцию `f` и возвращает обертку, которая кеширует её результаты. **В этой задаче функция `f` имеет только один аргумент, и он является числом.** -
        -
      1. При первом вызове обертки с определенным аргументом -- она вызывает `f` и запоминает значение.
      2. -
      3. При втором и последующих вызовах с тем же аргументом возвращается запомненное значение.
      4. -
      +1. При первом вызове обертки с определенным аргументом -- она вызывает `f` и запоминает значение. +2. При втором и последующих вызовах с тем же аргументом возвращается запомненное значение. Должно работать так: ```js function f(x) { - return Math.random() * x; // random для удобства тестирования + return Math.random() * x; // random для удобства тестирования } function makeCaching(f) { /* ваш код */ } diff --git a/1-js/6-objects-more/8-decorators/article.md b/1-js/6-objects-more/8-decorators/article.md index f089df90..13c23ebe 100644 --- a/1-js/6-objects-more/8-decorators/article.md +++ b/1-js/6-objects-more/8-decorators/article.md @@ -51,8 +51,7 @@ alert( timers.myFunc ); // общее время выполнения всех Его реализация: -```js -//+ run +```js run var timers = {}; // прибавит время выполнения f к таймеру timers[timer] @@ -70,8 +69,8 @@ function timingDecorator(f, timer) { } // функция может быть произвольной, например такой: -function fibonacci(n) { - return (n > 2) ? fibonacci(n - 1) + fibonacci(n - 2) : 1; +var fibonacci = function f(n) { + return (n > 2) ? f(n - 1) + f(n - 2) : 1; } *!* @@ -100,12 +99,11 @@ var result = f.apply(this, arguments); // (*) ## Декоратор для проверки типа -В JavaScript, как правило, пренебрегают проверками типа. В функцию, которая должна получать число, может быть передана строка, булево значение или даже объект. +В JavaScript, как правило, пренебрегают проверками типа. В функцию, которая должна получать число, может быть передана строка, булево значение или даже объект. Например: -```js -//+ no-beautify +```js no-beautify function sum(a, b) { return a + b; } @@ -124,8 +122,7 @@ alert( sum(true, { name: "Вася", age: 35 }) ); // true[Object object] Например, создадим декоратор, который принимает функцию и массив, который описывает для какого аргумента какую проверку типа применять: -```js -//+ run +```js run // вспомогательная функция для проверки на число function checkNumber(value) { return typeof value == 'number'; @@ -164,7 +161,7 @@ sum(1, ["array", "in", "sum?!?"]); // некорректный аргумент */!* ``` -Конечно, этот декоратор можно ещё расширять, улучшать, дописывать проверки, но... Вы уже поняли принцип, не правда ли? +Конечно, этот декоратор можно ещё расширять, улучшать, дописывать проверки, но... Вы уже поняли принцип, не правда ли? **Один раз пишем декоратор и дальше просто применяем этот функционал везде, где нужно.** @@ -191,8 +188,7 @@ function checkPermissionDecorator(f) { Использование декоратора: -```js -//+ no-beautify +```js no-beautify function save() { ... } save = checkPermissionDecorator(save); @@ -211,22 +207,3 @@ save = checkPermissionDecorator(save); Предлагаю вашему вниманию задачи, которые помогут выяснить, насколько вы разобрались в декораторах. Далее в учебнике мы ещё встретимся с ними. - - -[head] - -[/head] \ No newline at end of file diff --git a/1-js/6-objects-more/8-decorators/head.html b/1-js/6-objects-more/8-decorators/head.html new file mode 100644 index 00000000..92ffda5c --- /dev/null +++ b/1-js/6-objects-more/8-decorators/head.html @@ -0,0 +1,15 @@ + \ No newline at end of file diff --git a/1-js/7-js-misc/1-class-instanceof/1-format-date-polymorphic/_js.view/solution.js b/1-js/7-js-misc/1-class-instanceof/1-format-date-polymorphic/_js.view/solution.js index 48113fa4..53d819fa 100644 --- a/1-js/7-js-misc/1-class-instanceof/1-format-date-polymorphic/_js.view/solution.js +++ b/1-js/7-js-misc/1-class-instanceof/1-format-date-polymorphic/_js.view/solution.js @@ -14,7 +14,7 @@ function formatDate(date) { return date.toLocaleString("ru", {day: '2-digit', month: '2-digit', year: '2-digit'}); /* - // можно и вручную, если лень добавлят в старый IE поддержку локализации + // можно и вручную, если лень добавлять в старый IE поддержку локализации var day = date.getDate(); if (day < 10) day = '0' + day; diff --git a/1-js/7-js-misc/1-class-instanceof/1-format-date-polymorphic/solution.md b/1-js/7-js-misc/1-class-instanceof/1-format-date-polymorphic/solution.md index 878033e9..5b1fd3d1 100644 --- a/1-js/7-js-misc/1-class-instanceof/1-format-date-polymorphic/solution.md +++ b/1-js/7-js-misc/1-class-instanceof/1-format-date-polymorphic/solution.md @@ -1,9 +1,8 @@ -Для определения примитивного типа строка/число подойдет оператор [typeof](#type-typeof). +Для определения примитивного типа строка/число подойдет оператор [typeof](info:types-intro#type-typeof). Примеры его работы: -```js -//+ run +```js run alert( typeof 123 ); // "number" alert( typeof "строка" ); // "string" alert( typeof new Date() ); // "object" diff --git a/1-js/7-js-misc/1-class-instanceof/1-format-date-polymorphic/task.md b/1-js/7-js-misc/1-class-instanceof/1-format-date-polymorphic/task.md index 4d4176da..6df34621 100644 --- a/1-js/7-js-misc/1-class-instanceof/1-format-date-polymorphic/task.md +++ b/1-js/7-js-misc/1-class-instanceof/1-format-date-polymorphic/task.md @@ -1,16 +1,18 @@ -# Полиморфная функция formatDate +importance: 5 + +--- -[importance 5] +# Полиморфная функция formatDate -Напишите функцию `formatDate(date)`, которая возвращает дату в формате `dd.mm.yy`. +Напишите функцию `formatDate(date)`, которая возвращает дату в формате `dd.mm.yy`. Ее первый аргумент должен содержать дату в одном из видов: -
        -
      1. Как объект `Date`.
      2. -
      3. Как строку, например `yyyy-mm-dd` или другую в стандартном формате даты.
      4. -
      5. Как число *секунд* с `01.01.1970`.
      6. -
      7. Как массив `[гггг, мм, дд]`, месяц начинается с нуля
      8. -
      + +1. Как объект `Date`. +2. Как строку, например `yyyy-mm-dd` или другую в стандартном формате даты. +3. Как число *секунд* с `01.01.1970`. +4. Как массив `[гггг, мм, дд]`, месяц начинается с нуля + Для этого вам понадобится определить тип данных аргумента и, при необходимости, преобразовать входные данные в нужный формат. Пример работы: diff --git a/1-js/7-js-misc/1-class-instanceof/article.md b/1-js/7-js-misc/1-class-instanceof/article.md index 54b519b2..8865ab36 100644 --- a/1-js/7-js-misc/1-class-instanceof/article.md +++ b/1-js/7-js-misc/1-class-instanceof/article.md @@ -2,16 +2,15 @@ Время от времени бывает удобно создавать так называемые "полиморфные" функции, то есть такие, которые по-разному обрабатывают аргументы, в зависимости от их типа. Например, функция вывода может по-разному форматировать числа и даты. -Для реализации такой возможности нужен способ определить тип переменной. +Для реализации такой возможности нужен способ определить тип переменной. ## Оператор typeof -Мы уже знакомы с простейшим способом -- оператором [typeof](#type-typeof). +Мы уже знакомы с простейшим способом -- оператором [typeof](info:types-intro#type-typeof). Оператор `typeof` надежно работает с примитивными типами, кроме `null`, а также с функциями. Он возвращает для них тип в виде строки: -```js -//+ run no-beautify +```js run no-beautify alert( typeof 1 ); // 'number' alert( typeof true ); // 'boolean' alert( typeof "Текст" ); // 'string' @@ -22,8 +21,7 @@ alert( typeof alert ); // 'function' ...Но все объекты, включая массивы и даты для `typeof` -- на одно лицо, они имеют один тип `'object'`: -```js -//+ run +```js run alert( typeof {} ); // 'object' alert( typeof [] ); // 'object' alert( typeof new Date ); // 'object' @@ -33,7 +31,7 @@ alert( typeof new Date ); // 'object' ## Секретное свойство [[Class]] -Для встроенных объектов есть одна "секретная" возможность узнать их тип, которая связана с методом `toString`. +Для встроенных объектов есть одна "секретная" возможность узнать их тип, которая связана с методом `toString`. Во всех встроенных объектах есть специальное свойство `[[Class]]`, в котором хранится информация о его типе или конструкторе. @@ -43,9 +41,8 @@ alert( typeof new Date ); // 'object' Например: -```js -//+ run -var toString = {}.toString; +```js run +var toString = {}.toString; var arr = [1, 2]; alert( toString.call(arr) ); // [object Array] @@ -67,31 +64,27 @@ alert( toString.call(user) ); // [object Object] Метод также можно использовать с примитивами: -```js -//+ run +```js run alert( {}.toString.call(123) ); // [object Number] alert( {}.toString.call("строка") ); // [object String] ``` -[warn header="Вызов `{}.toString` в консоли может выдать ошибку"] +````warn header="Вызов `{}.toString` в консоли может выдать ошибку" При тестировании кода в консоли вы можете обнаружить, что если ввести в командную строку `{}.toString.call(...)` -- будет ошибка. С другой стороны, вызов `alert( {}.toString... )` -- работает. Эта ошибка возникает потому, что фигурные скобки `{ }` в основном потоке кода интерпретируются как блок. Интерпретатор читает `{}.toString.call(...)` так: -```js -//+ no-beautify +```js no-beautify { } // пустой блок кода .toString.call(...) // а что это за точка в начале? не понимаю, ошибка! ``` Фигурные скобки считаются объектом, только если они находятся в контексте выражения. В частности, оборачивание в скобки `( {}.toString... )` тоже сработает нормально. -[/warn] - +```` Для большего удобства можно сделать функцию `getClass`, которая будет возвращать только сам `[[Class]]`: -```js -//+ run +```js run function getClass(obj) { return {}.toString.call(obj).slice(8, -1); } @@ -104,8 +97,7 @@ alert( getClass([1, 2, 3]) ); // Array Например: -```js -//+ run +```js run function User() {} var user = new User(); @@ -117,10 +109,9 @@ alert( {}.toString.call(user) ); // [object Object], не [object User] ## Метод Array.isArray() -Для проверки на массивов есть специальный метод: `Array.isArray(arr)`. Он возвращает `true` только если `arr` -- массив: +Для проверки типа на массив есть специальный метод: `Array.isArray(arr)`. Он возвращает `true` только если `arr` -- массив: -```js -//+ run +```js run alert( Array.isArray([1,2,3]) ); // true alert( Array.isArray("not array")); // false ``` @@ -129,13 +120,11 @@ alert( Array.isArray("not array")); // false Других аналогичных, типа `Object.isObject`, `Date.isDate` -- нет. - ## Оператор instanceof Оператор `instanceof` позволяет проверить, создан ли объект данной функцией, причём работает для любых функций -- как встроенных, так и наших. -```js -//+ run +```js run function User() {} var user = new User(); @@ -145,21 +134,19 @@ alert( user instanceof User ); // true Таким образом, `instanceof`, в отличие от `[[Class]]` и `typeof` может помочь выяснить тип для новых объектов, созданных нашими конструкторами. -Заметим, что оператор `instanceof` -- сложнее, чем кажется. Он учитывает наследование, которое мы пока не проходили, но скоро изучим, и затем вернёмся к `instanceof` в главе [](/instanceof). - +Заметим, что оператор `instanceof` -- сложнее, чем кажется. Он учитывает наследование, которое мы пока не проходили, но скоро изучим, и затем вернёмся к `instanceof` в главе . ## Утиная типизация Альтернативный подход к типу -- "утиная типизация", которая основана на одной известной пословице: *"If it looks like a duck, swims like a duck and quacks like a duck, then it probably is a duck (who cares what it really is)"*. -В переводе: *"Если это выглядит как утка, плавает как утка и крякает как утка, то, вероятно, это утка (какая разница, что это на самом деле)"*. +В переводе: *"Если это выглядит как утка, плавает как утка и крякает как утка, то, вероятно, это утка (какая разница, что это на самом деле)"*. Смысл утиной типизации -- в проверке необходимых методов и свойств. Например, мы можем проверить, что объект -- массив, не вызывая `Array.isArray`, а просто уточнив наличие важного для нас метода, например `splice`: -```js -//+ run +```js run var something = [1, 2, 3]; if (something.splice) { @@ -171,8 +158,7 @@ if (something.splice) { Проверить на дату можно, определив наличие метода `getTime`: -```js -//+ run +```js run var x = new Date(); if (x.getTime) { @@ -181,26 +167,24 @@ if (x.getTime) { } ``` -С виду такая проверка хрупка, ее можно "сломать", передав похожий объект с тем же методом. +С виду такая проверка хрупка, ее можно "сломать", передав похожий объект с тем же методом. -Но как раз в этом и есть смысл утиной типизации: если объект похож на дату, у него есть методы даты, то будем работать с ним как с датой (какая разница, что это на самом деле). +Но как раз в этом и есть смысл утиной типизации: если объект похож на дату, у него есть методы даты, то будем работать с ним как с датой (какая разница, что это на самом деле). То есть, мы намеренно позволяем передать в код нечто менее конкретное, чем определённый тип, чтобы сделать его более универсальным. -[smart header="Проверка интерфейса"] +```smart header="Проверка интерфейса" Если говорить словами "классического программирования", то "duck typing" -- это проверка реализации объектом требуемого интерфейса. Если реализует -- ок, используем его. Если нет -- значит это что-то другое. -[/smart] - +``` ## Пример полиморфной функции Пример полиморфной функции -- `sayHi(who)`, которая будет говорить "Привет" своему аргументу, причём если передан массив -- то "Привет" каждому: -```js -//+ run +```js run function sayHi(who) { - if (Array.isArray(who)) { + if (Array.isArray(who)) { who.forEach(sayHi); } else { alert( 'Привет, ' + who ); @@ -219,8 +203,7 @@ sayHi(["Саша", "Петя", ["Маша", "Юля"]]); // Привет Саш Проверку на массив в этом примере можно заменить на "утиную" -- нам ведь нужен только метод `forEach`: -```js -//+ run +```js run function sayHi(who) { if (who.forEach) { // если есть forEach @@ -235,18 +218,14 @@ function sayHi(who) { Для написания полиморфных (это удобно!) функций нам нужна проверка типов. -
        -
      • Для примитивов с ней отлично справляется оператор `typeof`. - -У него две особенности: -
          -
        1. Он считает `null` объектом, это внутренняя ошибка в языке.
        2. -
        3. Для функций он возвращает `function`, по стандарту функция не считается базовым типом, но на практике это удобно и полезно.
        4. -
        -
      • -
      • Для встроенных объектов мы можем получить тип из скрытого свойства `[[Class]]`, при помощи вызова `{}.toString.call(obj).slice(8, -1)`. Не работает для конструкторов, которые объявлены нами. -
      • -
      • Оператор `obj instanceof Func` проверяет, создан ли объект `obj` функцией `Func`, работает для любых конструкторов. Более подробно мы разберём его в главе [](/instanceof).
      • -
      • И, наконец, зачастую достаточно проверить не сам тип, а просто наличие нужных свойств или методов. Это называется "утиная типизация".
      • -
      +- Для примитивов с ней отлично справляется оператор `typeof`. + + У него две особенности: + + - Он считает `null` объектом, это внутренняя ошибка в языке. + - Для функций он возвращает `function`, по стандарту функция не считается базовым типом, но на практике это удобно и полезно. + +- Для встроенных объектов мы можем получить тип из скрытого свойства `[[Class]]`, при помощи вызова `{}.toString.call(obj).slice(8, -1)`. Для конструкторов, которые объявлены нами, `[[Class]]` всегда равно `"Object"`. +- Оператор `obj instanceof Func` проверяет, создан ли объект `obj` функцией `Func`, работает для любых конструкторов. Более подробно мы разберём его в главе . +- И, наконец, зачастую достаточно проверить не сам тип, а просто наличие нужных свойств или методов. Это называется "утиная типизация". diff --git a/1-js/7-js-misc/2-json/1-serialize-object/task.md b/1-js/7-js-misc/2-json/1-serialize-object/task.md index cc46554d..c5c84dda 100644 --- a/1-js/7-js-misc/2-json/1-serialize-object/task.md +++ b/1-js/7-js-misc/2-json/1-serialize-object/task.md @@ -1,6 +1,8 @@ -# Превратите объект в JSON +importance: 3 + +--- -[importance 3] +# Превратите объект в JSON Превратите объект `leader` из примера ниже в JSON: @@ -11,4 +13,4 @@ var leader = { }; ``` -После этого прочитайте получившуюся строку обратно в объект. +После этого прочитайте получившуюся строку обратно в объект. diff --git a/1-js/7-js-misc/2-json/2-serialize-object-circular/solution.md b/1-js/7-js-misc/2-json/2-serialize-object-circular/solution.md index 0ec62c2b..658da3b8 100644 --- a/1-js/7-js-misc/2-json/2-serialize-object-circular/solution.md +++ b/1-js/7-js-misc/2-json/2-serialize-object-circular/solution.md @@ -1,63 +1,58 @@ # Ответ на первый вопрос -Обычный вызов `JSON.stringify(team)` выдаст ошибку, так как объекты `leader` и `soldier` внутри структуры ссылаются друг на друга. +Обычный вызов `JSON.stringify(team)` выдаст ошибку, так как объекты `leader` и `soldier` внутри структуры ссылаются друг на друга. Формат JSON не предусматривает средств для хранения ссылок. -# Варианты решения +# Варианты решения Чтобы превращать такие структуры в JSON, обычно используются два подхода: -
        -
      1. Добавить в `team` свой код `toJSON`: +1. Добавить в `team` свой код `toJSON`: -```js -team.toJSON = function() { - /* свой код, который может создавать копию объекта без круговых ссылок и передавать управление JSON.stringify */ -} -``` + ```js + team.toJSON = function() { + /* свой код, который может создавать копию объекта без круговых ссылок и передавать управление JSON.stringify */ + } + ``` -При этом, конечно, понадобится и своя функция чтения из JSON, которая будет восстанавливать объект, а затем дополнять его круговыми ссылками. -
      2. -
      3. Можно учесть возможную проблему в самой структуре, используя вместо ссылок `id`. Как правило, это несложно, ведь на сервере у данных тоже есть идентификаторы. - -Изменённая структура может выглядеть так: - -```js -var leader = { - id: 12, - name: "Василий Иванович" -}; - -var soldier = { - id: 51, - name: "Петька" -}; - -*!* -// поменяли прямую ссылку на ID -leader.soldierId = 51; -soldier.leaderId = 12; -*/!* - -var team = { - 12: leader, - 51: soldier -}; -``` + При этом, конечно, понадобится и своя функция чтения из JSON, которая будет восстанавливать объект, а затем дополнять его круговыми ссылками. +2. Можно учесть возможную проблему в самой структуре, используя вместо ссылок `id`. Как правило, это несложно, ведь на сервере у данных тоже есть идентификаторы. + + Изменённая структура может выглядеть так: + + ```js + var leader = { + id: 12, + name: "Василий Иванович" + }; + + var soldier = { + id: 51, + name: "Петька" + }; + + *!* + // поменяли прямую ссылку на ID + leader.soldierId = 51; + soldier.leaderId = 12; + */!* + + var team = { + 12: leader, + 51: soldier + }; + ``` -..Но действительно ли это решение будет оптимальным? Использовать структуру стало сложнее, и вряд ли это изменение стоит делать лишь из-за JSON. Вот если есть другие преимущества, тогда можно подумать. -
      4. -
      + ..Но действительно ли это решение будет оптимальным? Использовать структуру стало сложнее, и вряд ли это изменение стоит делать лишь из-за JSON. Вот если есть другие преимущества, тогда можно подумать. -Универсальный вариант подхода, описанного выше -- это использование особой реализации JSON, которая не входит в стандарт и поддерживает расширенный формат для поддержки ссылок. +Универсальный вариант подхода, описанного выше -- это использование особой реализации JSON, которая не входит в стандарт и поддерживает расширенный формат для поддержки ссылок. Она, к примеру, есть во фреймворке Dojo. При вызове `dojox.json.ref.toJson(team)` будет создано следующее строковое представление: -```js -//+ no-beautify +```js no-beautify [{"name":"Василий Иванович","soldier":{"name":"Петька","leader":{"$ref":"#0"}}},{"$ref":"#0.soldier"}] ``` diff --git a/1-js/7-js-misc/2-json/2-serialize-object-circular/task.md b/1-js/7-js-misc/2-json/2-serialize-object-circular/task.md index 7aa131ef..5e081ab1 100644 --- a/1-js/7-js-misc/2-json/2-serialize-object-circular/task.md +++ b/1-js/7-js-misc/2-json/2-serialize-object-circular/task.md @@ -1,6 +1,8 @@ -# Превратите объекты со ссылками в JSON +importance: 3 + +--- -[importance 3] +# Превратите объекты со ссылками в JSON Превратите объект `team` из примера ниже в JSON: @@ -20,7 +22,5 @@ soldier.leader = leader; var team = [leader, soldier]; ``` -
        -
      1. Может ли это сделать прямой вызов `JSON.stringify(team)`? Если нет, то почему?
      2. -
      3. Какой подход вы бы предложили для чтения и восстановления таких объектов?
      4. -
      \ No newline at end of file +1. Может ли это сделать прямой вызов `JSON.stringify(team)`? Если нет, то почему? +2. Какой подход вы бы предложили для чтения и восстановления таких объектов? diff --git a/1-js/7-js-misc/2-json/article.md b/1-js/7-js-misc/2-json/article.md index ad99b881..0d3b6218 100644 --- a/1-js/7-js-misc/2-json/article.md +++ b/1-js/7-js-misc/2-json/article.md @@ -1,8 +1,8 @@ # Формат JSON, метод toJSON -В этой главе мы рассмотрим работу с форматом [JSON](http://ru.wikipedia.org/wiki/JSON), который используется для представления объектов в виде строки. +В этой главе мы рассмотрим работу с форматом [JSON](http://ru.wikipedia.org/wiki/JSON), который используется для представления объектов в виде строки. -Это один из наиболее удобных форматов данных при взаимодействии с JavaScript. Если нужно с сервера взять объект с данными и передать на клиенте, то в качестве промежуточного формата -- для передачи по сети, почти всегда используют именно его. +Это один из наиболее удобных форматов данных при взаимодействии с JavaScript. Если нужно с сервера взять объект с данными и передать его клиенту, то в качестве промежуточного формата -- для передачи по сети, почти всегда используют именно его. В современных браузерах есть замечательные методы, знание тонкостей которых делает операции с JSON простыми и комфортными. @@ -11,26 +11,21 @@ ## Формат JSON Данные в формате JSON ([RFC 4627](http://tools.ietf.org/html/rfc4627)) представляют собой: -
        -
      • JavaScript-объекты `{ ... }` или
      • -
      • Массивы `[ ... ]` или
      • -
      • Значения одного из типов: -
          -
        • строки в двойных кавычках,
        • -
        • число,
        • -
        • логическое значение `true`/`false`,
        • -
        • `null`.
        • -
        -
      • -
      + +- JavaScript-объекты `{ ... }` или +- Массивы `[ ... ]` или +- Значения одного из типов: + - строки в двойных кавычках, + - число, + - логическое значение `true`/`false`, + - `null`. Почти все языки программирования имеют библиотеки для преобразования объектов в формат JSON. Основные методы для работы с JSON в JavaScript -- это: -
        -
      • `JSON.parse` -- читает объекты из строки в формате JSON.
      • -
      • `JSON.stringify` -- превращает объекты в строку в формате JSON, используется, когда нужно из JavaScript передать данные по сети.
      • -
      + +- `JSON.parse` -- читает объекты из строки в формате JSON. +- `JSON.stringify` -- превращает объекты в строку в формате JSON, используется, когда нужно из JavaScript передать данные по сети. ## Метод JSON.parse @@ -38,8 +33,7 @@ Например: -```js -//+ run +```js run var numbers = "[0, 1, 2, 3]"; numbers = JSON.parse(numbers); @@ -49,8 +43,7 @@ alert( numbers[1] ); // 1 Или так: -```js -//+ run +```js run var user = '{ "name": "Вася", "age": 35, "isAdmin": false, "friends": [0,1,2,3] }'; user = JSON.parse(user); @@ -60,7 +53,7 @@ alert( user.friends[1] ); // 1 Данные могут быть сколь угодно сложными, объекты и массивы могут включать в себя другие объекты и массивы. Главное чтобы они соответствовали формату. -[warn header="JSON-объекты ≠ JavaScript-объекты"] +````warn header="JSON-объекты ≠ JavaScript-объекты" Объекты в формате JSON похожи на обычные JavaScript-объекты, но отличаются от них более строгими требованиями к строкам -- они должны быть именно в двойных кавычках. В частности, первые два свойства объекта ниже -- некорректны: @@ -69,7 +62,7 @@ alert( user.friends[1] ); // 1 { *!*name*/!*: "Вася", // ошибка: ключ name без кавычек! "surname": *!*'Петров'*/!*,// ошибка: одинарные кавычки у значения 'Петров'! - "age": 35 // .. а тут всё в порядке. + "age": 35, // .. а тут всё в порядке. "isAdmin": false // и тут тоже всё ок } ``` @@ -77,7 +70,7 @@ alert( user.friends[1] ); // 1 Кроме того, в формате JSON не поддерживаются комментарии. Он предназначен только для передачи данных. Есть нестандартное расширение формата JSON, которое называется [JSON5](http://json5.org/) и как раз разрешает ключи без кавычек, комментарии и т.п, как в обычном JavaScript. На данном этапе, это отдельная библиотека. -[/warn] +```` ## Умный разбор: JSON.parse(str, reviver) @@ -88,16 +81,15 @@ alert( user.friends[1] ); // 1 Он выглядит так: ```js -// title: название собятия, date: дата события -var str = '{"title":"Конференция","date":"2012-11-30T12:00:00.000Z"}'; +// title: название события, date: дата события +var str = '{"title":"Конференция","date":"2014-11-30T12:00:00.000Z"}'; ``` ...И теперь нужно *восстановить* его, то есть превратить в JavaScript-объект. Попробуем вызвать для этого `JSON.parse`: -```js -//+ run +```js run var str = '{"title":"Конференция","date":"2014-11-30T12:00:00.000Z"}'; var event = JSON.parse(str); @@ -117,8 +109,7 @@ alert( event.date.getDate() ); // ошибка! В данном случае мы можем создать правило, что ключ `date` всегда означает дату: -```js -//+ run +```js run // дата в строке - в формате UTC var str = '{"title":"Конференция","date":"2014-11-30T12:00:00.000Z"}'; @@ -134,8 +125,7 @@ alert( event.date.getDate() ); // теперь сработает! Кстати, эта возможность работает и для вложенных объектов тоже: -```js -//+ run +```js run var schedule = '{ \ "events": [ \ {"title":"Конференция","date":"2014-11-30T12:00:00.000Z"}, \ @@ -155,12 +145,11 @@ alert( schedule.events[1].date.getDate() ); // сработает! ## Сериализация, метод JSON.stringify -Метод `JSON.stringify(value, replacer, space)` преобразует ("сериализует") значение в JSON-строку. +Метод `JSON.stringify(value, replacer, space)` преобразует ("сериализует") значение в JSON-строку. Пример использования: -```js -//+ run +```js run var event = { title: "Конференция", date: "сегодня" @@ -179,8 +168,7 @@ event = JSON.parse(str); Посмотрим это в примере посложнее: -```js -//+ run +```js run var room = { number: 23, occupy: function() { @@ -188,7 +176,7 @@ var room = { } }; -event = { +var event = { title: "Конференция", date: new Date(Date.UTC(2014, 0, 1)), room: room @@ -205,28 +193,24 @@ alert( JSON.stringify(event) ); ``` Обратим внимание на два момента: -
        -
      1. Дата превратилась в строку. Это не случайно: у всех дат есть встроенный метод `toJSON`. Его результат в данном случае -- строка в таймзоне UTC.
      2. -
      3. У объекта `room` нет метода `toJSON`. Поэтому он сериализуется перечислением свойств. -Мы, конечно, могли бы добавить такой метод, тогда в итог попал бы его результат: +1. Дата превратилась в строку. Это не случайно: у всех дат есть встроенный метод `toJSON`. Его результат в данном случае -- строка в таймзоне UTC. +2. У объекта `room` нет метода `toJSON`. Поэтому он сериализуется перечислением свойств. -```js -//+ run -var room = { - number: 23, -*!* - toJSON: function() { - return this.number; - } -*/!* -}; + Мы, конечно, могли бы добавить такой метод, тогда в итог попал бы его результат: -alert( JSON.stringify(room) ); // 23 -``` + ```js run + var room = { + number: 23, + *!* + toJSON: function() { + return this.number; + } + */!* + }; -
      4. -
      + alert( JSON.stringify(room) ); // 23 + ``` ### Исключение свойств @@ -234,8 +218,7 @@ alert( JSON.stringify(room) ); // 23 Например: -```js -//+ run +```js run var user = { name: "Вася", age: 25, @@ -254,8 +237,7 @@ alert( JSON.stringify(user) ); // ошибка! Например: -```js -//+ run +```js run var user = { name: "Вася", age: 25, @@ -270,8 +252,7 @@ alert( JSON.stringify(user, ["name", "age"]) ); Для более сложных ситуаций вторым параметром можно передать функцию `function(key, value)`, которая возвращает сериализованное `value` либо `undefined`, если его не нужно включать в результат: -```js -//+ run +```js run var user = { name: "Вася", age: 25, @@ -290,9 +271,9 @@ alert( str ); // {"name":"Вася","age":25} В примере выше функция пропустит свойство с названием `window`. Для остальных она просто возвращает значение, передавая его стандартному алгоритму. А могла бы и как-то обработать. -[smart header="Функция `replacer` работает рекурсивно"] -То есть, если объект содержит вложенные объекты, массивы и т.п., то все они пройдут через `replacer`. -[/smart] +```smart header="Функция `replacer` работает рекурсивно" +То есть, если объект содержит вложенные объекты, массивы и т.п., то все они пройдут через `replacer`. +``` ### Красивое форматирование @@ -302,8 +283,7 @@ alert( str ); // {"name":"Вася","age":25} Например: -```js -//+ run +```js run var user = { name: "Вася", age: 25, @@ -332,35 +312,6 @@ alert( str ); ## Итого -
        -
      • JSON -- формат для представления объектов (и не только) в виде строки.
      • -
      • Методы [JSON.parse](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse) и [JSON.stringify](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify) позволяют интеллектуально преобразовать объект в строку и обратно.
      • -
      - - - -[head] - -[/head] \ No newline at end of file diff --git a/1-js/7-js-misc/2-json/head.html b/1-js/7-js-misc/2-json/head.html new file mode 100644 index 00000000..ad6107da --- /dev/null +++ b/1-js/7-js-misc/2-json/head.html @@ -0,0 +1,23 @@ + \ No newline at end of file diff --git a/1-js/7-js-misc/3-setTimeout-setInterval/2-output-numbers-100ms-settimeout/task.md b/1-js/7-js-misc/3-setTimeout-setInterval/2-output-numbers-100ms-settimeout/task.md deleted file mode 100644 index 8334753f..00000000 --- a/1-js/7-js-misc/3-setTimeout-setInterval/2-output-numbers-100ms-settimeout/task.md +++ /dev/null @@ -1,5 +0,0 @@ -# Вывод чисел каждые 100мс, через setTimeout - -[importance 5] - -Сделайте то же самое, что в задаче [](/task/output-numbers-100ms), но с использованием рекурсивного `setTimeout` вместо `setInterval`. \ No newline at end of file diff --git a/1-js/7-js-misc/3-setTimeout-setInterval/4-settimeout-result/solution.md b/1-js/7-js-misc/3-setTimeout-setInterval/4-settimeout-result/solution.md deleted file mode 100644 index ea95474b..00000000 --- a/1-js/7-js-misc/3-setTimeout-setInterval/4-settimeout-result/solution.md +++ /dev/null @@ -1,8 +0,0 @@ -Ответы: -
        -
      • `alert` выведет `100000000`.
      • -
      • **3**, срабатывание будет после окончания работы `hardWork`.
      • -
      - - -Так будет потому, что вызов планируется на `100мс` от времени вызова `setTimeout`, но функция выполняется больше, чем `100мс`, поэтому к моменту ее окончания время уже подошло и отложенный вызов выполняется тут же. \ No newline at end of file diff --git a/1-js/7-js-misc/3-setTimeout-setInterval/setinterval-interval.png b/1-js/7-js-misc/3-setTimeout-setInterval/setinterval-interval.png deleted file mode 100644 index 0a815bdb..00000000 Binary files a/1-js/7-js-misc/3-setTimeout-setInterval/setinterval-interval.png and /dev/null differ diff --git a/1-js/7-js-misc/3-setTimeout-setInterval/setinterval-interval@2x.png b/1-js/7-js-misc/3-setTimeout-setInterval/setinterval-interval@2x.png deleted file mode 100644 index 1e764160..00000000 Binary files a/1-js/7-js-misc/3-setTimeout-setInterval/setinterval-interval@2x.png and /dev/null differ diff --git a/1-js/7-js-misc/3-setTimeout-setInterval/settimeout-interval.png b/1-js/7-js-misc/3-setTimeout-setInterval/settimeout-interval.png deleted file mode 100644 index b6909cc2..00000000 Binary files a/1-js/7-js-misc/3-setTimeout-setInterval/settimeout-interval.png and /dev/null differ diff --git a/1-js/7-js-misc/3-setTimeout-setInterval/settimeout-interval@2x.png b/1-js/7-js-misc/3-setTimeout-setInterval/settimeout-interval@2x.png deleted file mode 100644 index 657ec0db..00000000 Binary files a/1-js/7-js-misc/3-setTimeout-setInterval/settimeout-interval@2x.png and /dev/null differ diff --git a/1-js/7-js-misc/3-setTimeout-setInterval/1-output-numbers-100ms/solution.md b/1-js/7-js-misc/3-settimeout-setinterval/1-output-numbers-100ms/solution.md similarity index 93% rename from 1-js/7-js-misc/3-setTimeout-setInterval/1-output-numbers-100ms/solution.md rename to 1-js/7-js-misc/3-settimeout-setinterval/1-output-numbers-100ms/solution.md index f6e7fbcd..590b9ac6 100644 --- a/1-js/7-js-misc/3-setTimeout-setInterval/1-output-numbers-100ms/solution.md +++ b/1-js/7-js-misc/3-settimeout-setinterval/1-output-numbers-100ms/solution.md @@ -1,7 +1,6 @@ -```js -//+ run +```js run function printNumbersInterval() { var i = 1; var timerId = setInterval(function() { diff --git a/1-js/7-js-misc/3-setTimeout-setInterval/1-output-numbers-100ms/task.md b/1-js/7-js-misc/3-settimeout-setinterval/1-output-numbers-100ms/task.md similarity index 67% rename from 1-js/7-js-misc/3-setTimeout-setInterval/1-output-numbers-100ms/task.md rename to 1-js/7-js-misc/3-settimeout-setinterval/1-output-numbers-100ms/task.md index dc2364f4..70887f57 100644 --- a/1-js/7-js-misc/3-setTimeout-setInterval/1-output-numbers-100ms/task.md +++ b/1-js/7-js-misc/3-settimeout-setinterval/1-output-numbers-100ms/task.md @@ -1,11 +1,13 @@ -# Вывод чисел каждые 100мс +importance: 5 -[importance 5] +--- -Напишите функцию `printNumbersInterval()`, которая последовательно выводит в консоль числа от 1 до 20, с интервалом между числами 100мс. То есть, весь вывод должен занимать 2000мс, в течение которых каждые 100мс в консоли появляется очередное число. +# Вывод чисел каждые 100 мс + +Напишите функцию `printNumbersInterval()`, которая последовательно выводит в консоль числа от 1 до 20, с интервалом между числами 100 мс. То есть, весь вывод должен занимать 2000 мс, в течение которых каждые 100 мс в консоли появляется очередное число. Нажмите на кнопку, открыв консоль, для демонстрации: - diff --git a/1-js/8-oop/5-functional-inheritance/2-coffeemachine-disable-stop/task.md b/1-js/8-oop/5-functional-inheritance/2-coffeemachine-disable-stop/task.md index 298cd301..af8b06cc 100644 --- a/1-js/8-oop/5-functional-inheritance/2-coffeemachine-disable-stop/task.md +++ b/1-js/8-oop/5-functional-inheritance/2-coffeemachine-disable-stop/task.md @@ -1,6 +1,8 @@ -# Останавливать кофеварку при выключении +importance: 5 + +--- -[importance 5] +# Останавливать кофеварку при выключении Когда кофеварку выключают -- текущая варка кофе должна останавливаться. diff --git a/1-js/8-oop/5-functional-inheritance/3-inherit-fridge/task.md b/1-js/8-oop/5-functional-inheritance/3-inherit-fridge/task.md index 150bdb0f..a80006a3 100644 --- a/1-js/8-oop/5-functional-inheritance/3-inherit-fridge/task.md +++ b/1-js/8-oop/5-functional-inheritance/3-inherit-fridge/task.md @@ -1,15 +1,16 @@ -# Унаследуйте холодильник +importance: 4 + +--- -[importance 4] +# Унаследуйте холодильник Создайте класс для холодильника `Fridge(power)`, наследующий от `Machine`, с приватным свойством `food` и методами `addFood(...)`, `getFood()`: -
        -
      • Приватное свойство `food` хранит массив еды.
      • -
      • Публичный метод `addFood(item)` добавляет в массив `food` новую еду, доступен вызов с несколькими аргументами `addFood(item1, item2...)` для добавления нескольких элементов сразу.
      • -
      • Если холодильник выключен, то добавить еду нельзя, будет ошибка.
      • -
      • Максимальное количество еды ограничено `power/100`, где `power` -- мощность холодильника, указывается в конструкторе. При попытке добавить больше -- будет ошибка
      • -
      • Публичный метод `getFood()` возвращает еду в виде массива, добавление или удаление элементов из которого не должно влиять на свойство `food` холодильника.
      • -
      + +- Приватное свойство `food` хранит массив еды. +- Публичный метод `addFood(item)` добавляет в массив `food` новую еду, доступен вызов с несколькими аргументами `addFood(item1, item2...)` для добавления нескольких элементов сразу. +- Если холодильник выключен, то добавить еду нельзя, будет ошибка. +- Максимальное количество еды ограничено `power/100`, где `power` -- мощность холодильника, указывается в конструкторе. При попытке добавить больше -- будет ошибка +- Публичный метод `getFood()` возвращает еду в виде массива, добавление или удаление элементов из которого не должно влиять на свойство `food` холодильника. Код для проверки: diff --git a/1-js/8-oop/5-functional-inheritance/4-add-methods-fridge/solution.md b/1-js/8-oop/5-functional-inheritance/4-add-methods-fridge/solution.md index e7752658..200b269f 100644 --- a/1-js/8-oop/5-functional-inheritance/4-add-methods-fridge/solution.md +++ b/1-js/8-oop/5-functional-inheritance/4-add-methods-fridge/solution.md @@ -1,7 +1,6 @@ -```js -//+ run +```js run function Machine(power) { this._power = power; this._enabled = false; diff --git a/1-js/8-oop/5-functional-inheritance/4-add-methods-fridge/task.md b/1-js/8-oop/5-functional-inheritance/4-add-methods-fridge/task.md index 46a4db21..ded4e465 100644 --- a/1-js/8-oop/5-functional-inheritance/4-add-methods-fridge/task.md +++ b/1-js/8-oop/5-functional-inheritance/4-add-methods-fridge/task.md @@ -1,12 +1,13 @@ -# Добавьте методы в холодильник +importance: 5 + +--- -[importance 5] +# Добавьте методы в холодильник Добавьте в холодильник методы: -
        -
      • Публичный метод `filterFood(func)`, который возвращает всю еду, для которой `func(item) == true`
      • -
      • Публичный метод `removeFood(item)`, который удаляет еду `item` из холодильника.
      • -
      + +- Публичный метод `filterFood(func)`, который возвращает всю еду, для которой `func(item) == true` +- Публичный метод `removeFood(item)`, который удаляет еду `item` из холодильника. Код для проверки: diff --git a/1-js/8-oop/5-functional-inheritance/5-override-disable/solution.md b/1-js/8-oop/5-functional-inheritance/5-override-disable/solution.md index ec5fbb18..b46969ec 100644 --- a/1-js/8-oop/5-functional-inheritance/5-override-disable/solution.md +++ b/1-js/8-oop/5-functional-inheritance/5-override-disable/solution.md @@ -1,7 +1,6 @@ -```js -//+ run +```js run function Machine(power) { this._power = power; this._enabled = false; diff --git a/1-js/8-oop/5-functional-inheritance/5-override-disable/task.md b/1-js/8-oop/5-functional-inheritance/5-override-disable/task.md index 482ffaaa..406f811d 100644 --- a/1-js/8-oop/5-functional-inheritance/5-override-disable/task.md +++ b/1-js/8-oop/5-functional-inheritance/5-override-disable/task.md @@ -1,9 +1,10 @@ -# Переопределите disable +importance: 5 -[importance 5] +--- -Переопределите метод `disable` холодильника, чтобы при наличии в нём еды он выдавал ошибку. +# Переопределите disable +Переопределите метод `disable` холодильника, чтобы при наличии в нём еды он выдавал ошибку. Код для проверки: diff --git a/1-js/8-oop/5-functional-inheritance/article.md b/1-js/8-oop/5-functional-inheritance/article.md index 3afd5a93..19b50506 100644 --- a/1-js/8-oop/5-functional-inheritance/article.md +++ b/1-js/8-oop/5-functional-inheritance/article.md @@ -3,6 +3,7 @@ Наследование -- это создание новых "классов" на основе существующих. В JavaScript его можно реализовать несколькими путями, один из которых -- с использованием наложения конструкторов, мы рассмотрим в этой главе. + [cut] ## Зачем наследование? @@ -13,17 +14,17 @@ В реальной жизни у этих *машин* есть базовые правила пользования. Например, большая кнопка -- включение, шнур с розеткой нужно воткнуть в питание и т.п. -Можно сказать, что "у всех машин есть общие свойства, а конкретные машины могут их дополнять". +Можно сказать, что "у всех машин есть общие свойства, а конкретные машины могут их дополнять". Именно поэтому, увидев новую технику, мы уже можем что-то с ней сделать, даже не читая инструкцию. Механизм наследования позволяет определить базовый класс `Машина`, в нём описать то, что свойственно всем машинам, а затем на его основе построить другие, более конкретные: `Кофеварка`, `Холодильник` и т.п. -[smart header="В веб-разработке всё так же"] +```smart header="В веб-разработке всё так же" В веб-разработке нам могут понадобиться классы `Меню`, `Табы`, `Диалог` и другие компоненты интерфейса. В них всех обычно есть что-то общее. -Можно выделить такой общий функционал в класс `Компонент` и наследовать их от него, чтобы не дублировать код. -[/smart] +Можно выделить такой общий функционал в класс `Компонент` и наследовать их от него, чтобы не дублировать код. +``` ## Наследование от Machine @@ -84,8 +85,7 @@ coffeeMachine.disable(); Иначе говоря, если кофеварка захочет обратиться к `enabled`, то её ждёт разочарование: -```js -//+ run +```js run function Machine() { var enabled = false; @@ -118,8 +118,7 @@ var coffeeMachine = new CoffeeMachine(10000); При этом, чтобы обозначить, что свойство является внутренним, его имя начинают с подчёркивания `_`. -```js -//+ run +```js run function Machine() { *!* this._enabled = false; // вместо var enabled @@ -155,8 +154,7 @@ var coffeeMachine = new CoffeeMachine(10000); У `CoffeeMachine` есть приватное свойство `power`. Сейчас мы его тоже сделаем защищённым и перенесём в `Machine`, поскольку "мощность" свойственна всем машинам, а не только кофеварке. -```js -//+ run +```js run function Machine(power) { *!* this._power = power; // (1) @@ -197,10 +195,10 @@ var coffeeMachine = new CoffeeMachine(10000); Аналогичным образом мы можем унаследовать от `Machine` холодильник `Fridge`, микроволновку `MicroOven` и другие классы, которые разделяют общий "машинный" функционал, то есть имеют мощность и их можно включать/выключать. -Для этого достаточно вызвать `Machine` текущем контексте, а затем добавить свои методы. +Для этого достаточно вызвать `Machine` в текущем контексте, а затем добавить свои методы. ```js -// Fridge может добавить и свои аргументы, +// Fridge может добавить и свои аргументы, // которые в Machine не будут использованы function Fridge(power, temperature) { Machine.apply(this, arguments); @@ -246,21 +244,18 @@ function CoffeeMachine(power) { **Общая схема переопределения метода (по строкам выделенного фрагмента кода):** -
        -
      1. Копируем доставшийся от родителя метод `this.enable` в переменную, например `parentEnable`.
      2. -
      3. Заменяем `this.enable` на свою функцию...
      4. -
      5. ...Которая по-прежнему реализует старый функционал через вызов `parentEnable`.
      6. -
      7. ...И в дополнение к нему делает что-то своё, например запускает приготовление кофе.
      8. -
      +1. Копируем доставшийся от родителя метод `this.enable` в переменную, например `parentEnable`. +2. Заменяем `this.enable` на свою функцию... +3. ...Которая по-прежнему реализует старый функционал через вызов `parentEnable`. +4. ...И в дополнение к нему делает что-то своё, например запускает приготовление кофе. -Обратим внимание на строку `(3)`. +Обратим внимание на строку `(3)`. В ней родительский метод вызывается так: `parentEnable.call(this)`. Если бы вызов был таким: `parentEnable()`, то ему бы не передался текущий `this` и возникла бы ошибка. Технически, можно сделать возможность вызывать его и как `parentEnable()`, но тогда надо гарантировать, что контекст будет правильным, например привязать его при помощи `bind` или при объявлении, в родителе, вообще не использовать `this`, а получать контекст через замыкание, вот так: -```js -//+ run +```js run function Machine(power) { this._enabled = false; @@ -321,83 +316,71 @@ coffeeMachine.enable(); Её общая схема (кратко): -
        -
      1. Объявляется конструктор родителя `Machine`. В нём могут быть приватные (private), публичные (public) и защищённые (protected) свойства: - -```js -function Machine(params) { - // локальные переменные и функции доступны только внутри Machine - var privateProperty; - - // публичные доступны снаружи - this.publicProperty = ...; - - // защищённые доступны внутри Machine и для потомков - // мы договариваемся не трогать их снаружи - this._protectedProperty = ... -} - -var machine = new Machine(...) -machine.public(); -``` +1. Объявляется конструктор родителя `Machine`. В нём могут быть приватные (private), публичные (public) и защищённые (protected) свойства: -
      2. -
      3. Для наследования конструктор потомка вызывает родителя в своём контексте через `apply`. После чего может добавить свои переменные и методы: - -```js -function CoffeeMachine(params) { - // универсальный вызов с передачей любых аргументов -*!* - Machine.apply(this, arguments); -*/!* + ```js + function Machine(params) { + // локальные переменные и функции доступны только внутри Machine + var privateProperty; - this.coffeePublicProperty = ... -} + // публичные доступны снаружи + this.publicProperty = ...; -var coffeeMachine = new CoffeeMachine(...); -coffeeMachine.publicProperty(); -coffeeMachine.coffeePublicProperty(); -``` + // защищённые доступны внутри Machine и для потомков + // мы договариваемся не трогать их снаружи + this._protectedProperty = ... + } -
      4. -
      5. В `CoffeeMachine` свойства, полученные от родителя, можно перезаписать своими. Но обычно требуется не заменить, а расширить метод родителя. Для этого он предварительно копируется в переменную: + var machine = new Machine(...) + machine.public(); + ``` +2. Для наследования конструктор потомка вызывает родителя в своём контексте через `apply`. После чего может добавить свои переменные и методы: -```js -function CoffeeMachine(params) { - Machine.apply(this, arguments); + ```js + function CoffeeMachine(params) { + // универсальный вызов с передачей любых аргументов + *!* + Machine.apply(this, arguments); + */!* -*!* - var parentProtected = this._protectedProperty; - this._protectedProperty = function(args) { - parentProtected.apply(this, args); // (*) - // ... - }; -*/!* -} -``` + this.coffeePublicProperty = ... + } -Строку `(*)` можно упростить до `parentProtected(args)`, если метод родителя не использует `this`, а, например, привязан к `var self = this`: + var coffeeMachine = new CoffeeMachine(...); + coffeeMachine.publicProperty(); + coffeeMachine.coffeePublicProperty(); + ``` +3. В `CoffeeMachine` свойства, полученные от родителя, можно перезаписать своими. Но обычно требуется не заменить, а расширить метод родителя. Для этого он предварительно копируется в переменную: + + ```js + function CoffeeMachine(params) { + Machine.apply(this, arguments); + + *!* + var parentProtected = this._protectedProperty; + this._protectedProperty = function(args) { + parentProtected.apply(this, args); // (*) + // ... + }; + */!* + } + ``` -```js -function Machine(params) { - var self = this; + Строку `(*)` можно упростить до `parentProtected(args)`, если метод родителя не использует `this`, а, например, привязан к `var self = this`: - this._protected = function() { - self.property = "value"; - }; -} -``` + ```js + function Machine(params) { + var self = this; -
      6. -
      + this._protected = function() { + self.property = "value"; + }; + } + ``` Надо сказать, что способ наследования, описанный в этой главе, используется нечасто. -В следующих главах мы будем изучать прототипный подход, который обладаем рядом преимуществ. +В следующих главах мы будем изучать прототипный подход, который обладает рядом преимуществ. Но знать и понимать его необходимо, поскольку во многих существующих библиотеках классы написаны в функциональном стиле, и расширять/наследовать от них можно только так. - - - - diff --git a/1-js/9-prototypes/1-prototype/1-property-after-delete/solution.md b/1-js/9-prototypes/1-prototype/1-property-after-delete/solution.md index f6308c17..3aaa5b7c 100644 --- a/1-js/9-prototypes/1-prototype/1-property-after-delete/solution.md +++ b/1-js/9-prototypes/1-prototype/1-property-after-delete/solution.md @@ -1,5 +1,4 @@ -
        -
      1. `true`, свойство взято из `rabbit`.
      2. -
      3. `null`, свойство взято из `animal`.
      4. -
      5. `undefined`, свойства больше нет.
      6. -
      \ No newline at end of file + +1. `true`, свойство взято из `rabbit`. +2. `null`, свойство взято из `animal`. +3. `undefined`, свойства больше нет. diff --git a/1-js/9-prototypes/1-prototype/1-property-after-delete/task.md b/1-js/9-prototypes/1-prototype/1-property-after-delete/task.md index 70b4e3ba..fbcef87b 100644 --- a/1-js/9-prototypes/1-prototype/1-property-after-delete/task.md +++ b/1-js/9-prototypes/1-prototype/1-property-after-delete/task.md @@ -1,6 +1,8 @@ -# Чему равно cвойство после delete? +importance: 5 -[importance 5] +--- + +# Чему равно свойство после delete? Какие значения будут выводиться в коде ниже? diff --git a/1-js/9-prototypes/1-prototype/2-proto-and-this/solution.md b/1-js/9-prototypes/1-prototype/2-proto-and-this/solution.md index 4206298f..1f438db5 100644 --- a/1-js/9-prototypes/1-prototype/2-proto-and-this/solution.md +++ b/1-js/9-prototypes/1-prototype/2-proto-and-this/solution.md @@ -3,16 +3,13 @@ Если коротко -- то потому что `this` будет указывать на `rabbit`, а прототип при записи не используется. Если в деталях -- посмотрим как выполняется `rabbit.eat()`: -
        -
      1. Интерпретатор ищет `rabbit.eat`, чтобы его вызвать. Но свойство `eat` отсутствует в объекте `rabbit`, поэтому он идет по ссылке `rabbit.__proto__` и находит это свойство там. - -
      2. -
      3. Функция `eat` запускается. Контекст ставится равным объекту перед точкой, т.е. `this = rabbit`. -Итак -- получается, что команда `this.full = true` устанавливает свойство `full` в самом объекте `rabbit`. Итог: +1. Интерпретатор ищет `rabbit.eat`, чтобы его вызвать. Но свойство `eat` отсутствует в объекте `rabbit`, поэтому он идет по ссылке `rabbit.__proto__` и находит это свойство там. +![](proto5.png) +2. Функция `eat` запускается. Контекст ставится равным объекту перед точкой, т.е. `this = rabbit`. + + Итак -- получается, что команда `this.full = true` устанавливает свойство `full` в самом объекте `rabbit`. Итог: + + ![](proto6.png) - -
      4. -
      - Эта задача демонстрирует, что несмотря на то, в каком прототипе находится свойство, это никак не влияет на установку `this`, которая осуществляется по своим, независимым правилам. \ No newline at end of file diff --git a/1-js/9-prototypes/1-prototype/2-proto-and-this/task.md b/1-js/9-prototypes/1-prototype/2-proto-and-this/task.md index 346de8e1..d4fe02ef 100644 --- a/1-js/9-prototypes/1-prototype/2-proto-and-this/task.md +++ b/1-js/9-prototypes/1-prototype/2-proto-and-this/task.md @@ -1,6 +1,8 @@ -# Прототип и this +importance: 5 + +--- -[importance 5] +# Прототип и this Сработает ли вызов `rabbit.eat()` ? diff --git a/1-js/9-prototypes/1-prototype/3-search-algorithm/solution.md b/1-js/9-prototypes/1-prototype/3-search-algorithm/solution.md index 60ba9501..4ca33c24 100644 --- a/1-js/9-prototypes/1-prototype/3-search-algorithm/solution.md +++ b/1-js/9-prototypes/1-prototype/3-search-algorithm/solution.md @@ -1,33 +1,29 @@ -
        -
      1. Расставим `__proto__`: -```js -//+ run -var head = { - glasses: 1 -}; +1. Расставим `__proto__`: -var table = { - pen: 3 -}; -table.__proto__ = head; + ```js run + var head = { + glasses: 1 + }; -var bed = { - sheet: 1, - pillow: 2 -}; -bed.__proto__ = table; + var table = { + pen: 3 + }; + table.__proto__ = head; -var pockets = { - money: 2000 -}; -pockets.__proto__ = bed; + var bed = { + sheet: 1, + pillow: 2 + }; + bed.__proto__ = table; -alert( pockets.pen ); // 3 -alert( bed.glasses ); // 1 -alert( table.money ); // undefined -``` + var pockets = { + money: 2000 + }; + pockets.__proto__ = bed; -
      2. -
      3. **В современных браузерах, с точки зрения производительности, нет разницы, брать свойство из объекта или прототипа.** Они запоминают, где было найдено свойство и в следующий раз при запросе, к примеру, `pockets.glasses` начнут искать сразу в прототипе (`head`).
      4. -
      \ No newline at end of file + alert( pockets.pen ); // 3 + alert( bed.glasses ); // 1 + alert( table.money ); // undefined + ``` +2. **В современных браузерах, с точки зрения производительности, нет разницы, брать свойство из объекта или прототипа.** Они запоминают, где было найдено свойство и в следующий раз при запросе, к примеру, `pockets.glasses` начнут искать сразу в прототипе (`head`). diff --git a/1-js/9-prototypes/1-prototype/3-search-algorithm/task.md b/1-js/9-prototypes/1-prototype/3-search-algorithm/task.md index ab5f9f16..46e1ae82 100644 --- a/1-js/9-prototypes/1-prototype/3-search-algorithm/task.md +++ b/1-js/9-prototypes/1-prototype/3-search-algorithm/task.md @@ -1,6 +1,8 @@ -# Алгоритм для поиска +importance: 5 + +--- -[importance 5] +# Алгоритм для поиска Есть объекты: @@ -24,9 +26,9 @@ var pockets = { ``` Задание состоит из двух частей: -
        -
      1. Присвойте объектам ссылки `__proto__` так, чтобы любой поиск чего-либо шёл по алгоритму `pockets -> bed -> table -> head`. -То есть `pockets.pen == 3`, `bed.glasses == 1`, но `table.money == undefined`.
      2. -
      3. После этого ответьте на вопрос, как быстрее искать `glasses`: обращением к `pockets.glasses` или `head.glasses`? Попробуйте протестировать.
      4. -
      +1. Присвойте объектам ссылки `__proto__` так, чтобы любой поиск чего-либо шёл по алгоритму `pockets -> bed -> table -> head`. + + То есть `pockets.pen == 3`, `bed.glasses == 1`, но `table.money == undefined`. +2. После этого ответьте на вопрос, как быстрее искать `glasses`: обращением к `pockets.glasses` или `head.glasses`? Попробуйте протестировать. + diff --git a/1-js/9-prototypes/1-prototype/article.md b/1-js/9-prototypes/1-prototype/article.md index 3d83be6a..d470e4a4 100644 --- a/1-js/9-prototypes/1-prototype/article.md +++ b/1-js/9-prototypes/1-prototype/article.md @@ -5,7 +5,8 @@ Связующим звеном выступает специальное свойство `__proto__`. [cut] -## Прототип __proto__ + +## Прототип __proto__ Если один объект имеет специальную ссылку `__proto__` на другой объект, то при чтении свойства из него, если свойство отсутствует в самом объекте, оно ищется в объекте `__proto__`. @@ -13,8 +14,7 @@ Пример кода (кроме IE10-): -```js -//+ run +```js run var animal = { eats: true }; @@ -31,25 +31,22 @@ alert( rabbit.jumps ); // true alert( rabbit.eats ); // true ``` -
        -
      1. Первый `alert` здесь работает очевидным образом -- он выводит свойство `jumps` объекта `rabbit`.
      2. -
      3. Второй `alert` хочет вывести `rabbit.eats`, ищет его в самом объекте `rabbit`, не находит -- и продолжает поиск в объекте `rabbit.__proto__`, то есть, в данном случае, в `animal`.
      4. -
      +1. Первый `alert` здесь работает очевидным образом -- он выводит свойство `jumps` объекта `rabbit`. +2. Второй `alert` хочет вывести `rabbit.eats`, ищет его в самом объекте `rabbit`, не находит -- и продолжает поиск в объекте `rabbit.__proto__`, то есть, в данном случае, в `animal`. Иллюстрация происходящего при чтении `rabbit.eats` (поиск идет снизу вверх): - +![](proto-animal-rabbit.png) **Объект, на который указывает ссылка `__proto__`, называется *"прототипом"*. В данном случае получилось, что `animal` является прототипом для `rabbit`.** **Также говорят, что объект `rabbit` *"прототипно наследует"* от `animal`.** -Обратим внимание -- прототип используется исключительно при чтении. Запись значения, например, `rabbit.eats = value` или удаление `delete rabbit.eats` -- работает напрямую с объектом. +Обратим внимание -- прототип используется исключительно при чтении. Запись значения, например, `rabbit.eats = value` или удаление `delete rabbit.eats` -- работает напрямую с объектом. В примере ниже мы записываем свойство в сам `rabbit`, после чего `alert` перестаёт брать его у прототипа, а берёт уже из самого объекта: -```js -//+ run +```js run var animal = { eats: true }; @@ -61,7 +58,7 @@ var rabbit = { rabbit.__proto__ = animal; *!* -alert( rabbit.eats ); // false, свойство взято из rabbit +alert( rabbit.eats ); // false, свойство взято из rabbit */!* ``` @@ -69,21 +66,19 @@ alert( rabbit.eats ); // false, свойство взято из rabbit У объекта, который является `__proto__`, может быть свой `__proto__`, у того -- свой, и так далее. При этом свойства будут искаться по цепочке. -[smart header="Ссылка __proto__ в спецификации"] -Если вы будете читать спецификацию EcmaScript -- свойство `__proto__` обозначено в ней как `[[Prototype]]`. +```smart header="Ссылка __proto__ в спецификации" +Если вы будете читать спецификацию ECMAScript -- свойство `__proto__` обозначено в ней как `[[Prototype]]`. Двойные квадратные скобки здесь важны, чтобы не перепутать его с совсем другим свойством, которое называется `prototype`, и которое мы рассмотрим позже. -[/smart] +``` - ## Метод hasOwnProperty Обычный цикл `for..in` не делает различия между свойствами объекта и его прототипа. Он перебирает всё, например: -```js -//+ run +```js run var animal = { eats: true }; @@ -106,8 +101,7 @@ for (var key in rabbit) { Например: -```js -//+ run +```js run var animal = { eats: true }; @@ -126,8 +120,7 @@ alert( rabbit.hasOwnProperty('eats') ); // false: eats не принадлежи Для того, чтобы перебрать свойства самого объекта, достаточно профильтровать `key` через `hasOwnProperty`: -```js -//+ run +```js run var animal = { eats: true }; @@ -158,8 +151,7 @@ data.age = 35; При дальнейшем поиске в этой коллекции мы найдём не только `text` и `age`, но и встроенные функции: -```js -//+ run +```js run var data = {}; alert(data.toString); // функция, хотя мы её туда не записывали ``` @@ -167,8 +159,7 @@ alert(data.toString); // функция, хотя мы её туда не зап Это может быть неприятным сюрпризом и приводить к ошибкам, если названия свойств приходят от посетителя и могут быть произвольными. Чтобы этого избежать, мы можем исключать свойства, не принадлежащие самому объекту: -```js -//+ run +```js run var data = {}; // выведет toString только если оно записано в сам объект @@ -176,8 +167,7 @@ alert(data.hasOwnProperty('toString') ? data.toString : undefined); ``` Однако, есть путь и проще: -```js -//+ run +```js run *!* var data = Object.create(null); */!* @@ -191,56 +181,36 @@ alert(data.toString); // undefined Объект, создаваемый при помощи `Object.create(null)` не имеет прототипа, а значит в нём нет лишних свойств. Для коллекции -- как раз то, что надо. - ## Методы для работы с __proto__ В современных браузерах есть два дополнительных метода для работы с `__proto__`. Зачем они нужны, если есть `__proto__`? В общем-то, не очень нужны, но по историческим причинам тоже существуют. -
      -
      Чтение: [Object.getPrototypeOf(obj)](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/getPrototypeOf)
      -
      Возвращает `obj.__proto__` (кроме IE8-)
      -
      Запись: [Object.setPrototypeOf(obj, proto)](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/setPrototypeOf)
      -
      Устанавливает `obj.__proto__ = proto` (кроме IE10-).
      -
      +Чтение: [Object.getPrototypeOf(obj)](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/getPrototypeOf) +: Возвращает `obj.__proto__` (кроме IE8-) -Кроме того, есть ещё один вспомогательный метод: -
      Создание объекта с прототипом: [Object.create(proto, descriptors)](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/create)
      -
      Создаёт пустой объект с `__proto__`, равным первому аргументу (кроме IE8-), второй необязательный аргумент может содержать [дескрипторы свойств](/descriptors-getters-setters).
      -
    +Запись: [Object.setPrototypeOf(obj, proto)](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/setPrototypeOf) +: Устанавливает `obj.__proto__ = proto` (кроме IE10-). +Кроме того, есть ещё один вспомогательный метод: +Создание объекта с прототипом: [Object.create(proto, descriptors)](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/create) +: Создаёт пустой объект с `__proto__`, равным первому аргументу (кроме IE8-), второй необязательный аргумент может содержать [дескрипторы свойств](/descriptors-getters-setters). ## Итого - -
      -
    • В JavaScript есть встроенное "наследование" между объектами при помощи специального свойства `__proto__`.
    • -
    • При установке свойства `rabbit.__proto__ = animal` говорят, что объект `animal` будет "прототипом" `rabbit`.
    • -
    • При чтении свойства из объекта, если его в нём нет, оно ищется в `__proto__`. Прототип задействуется только при чтении свойства. Операции присвоения `obj.prop =` или удаления `delete obj.prop` совершаются всегда над самим объектом `obj`.
    • -
    +- В JavaScript есть встроенное "наследование" между объектами при помощи специального свойства `__proto__`. +- При установке свойства `rabbit.__proto__ = animal` говорят, что объект `animal` будет "прототипом" `rabbit`. +- При чтении свойства из объекта, если его в нём нет, оно ищется в `__proto__`. Прототип задействуется только при чтении свойства. Операции присвоения `obj.prop =` или удаления `delete obj.prop` совершаются всегда над самим объектом `obj`. Несколько прототипов одному объекту присвоить нельзя, но можно организовать объекты в цепочку, когда один объект ссылается на другой при помощи `__proto__`, тот ссылается на третий, и так далее. В современных браузерах есть методы для работы с прототипом: -
      -
    • [Object.getPrototypeOf(obj)](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/getPrototypeOf) (кроме IE8-)
    • -
    • [Object.setPrototypeOf(obj, proto)](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/setPrototypeOf) (кроме IE10-)
    • -
    • [Object.create(proto, descriptors)](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/create) (кроме IE8-)
    • -
    +- [Object.getPrototypeOf(obj)](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/getPrototypeOf) (кроме IE8-) +- [Object.setPrototypeOf(obj, proto)](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/setPrototypeOf) (кроме IE10-) +- [Object.create(proto, descriptors)](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/create) (кроме IE8-) Возможно, вас смущает недостаточная поддержка `__proto__` в старых IE. Но это не страшно. В последующих главах мы рассмотрим дополнительные методы работы с `__proto__`, включая те, которые работают везде. Также мы рассмотрим, как свойство `__proto__` используется внутри самого языка JavaScript и как организовать классы с его помощью. - - -[head] - -[/head] diff --git a/1-js/9-prototypes/1-prototype/head.html b/1-js/9-prototypes/1-prototype/head.html new file mode 100644 index 00000000..ec9611f8 --- /dev/null +++ b/1-js/9-prototypes/1-prototype/head.html @@ -0,0 +1,7 @@ + \ No newline at end of file diff --git a/1-js/9-prototypes/1-prototype/proto-animal-rabbit.png b/1-js/9-prototypes/1-prototype/proto-animal-rabbit.png index 89b49b19..d78e2805 100644 Binary files a/1-js/9-prototypes/1-prototype/proto-animal-rabbit.png and b/1-js/9-prototypes/1-prototype/proto-animal-rabbit.png differ diff --git a/1-js/9-prototypes/1-prototype/proto-animal-rabbit@2x.png b/1-js/9-prototypes/1-prototype/proto-animal-rabbit@2x.png index 2e9e7441..e8f1c940 100644 Binary files a/1-js/9-prototypes/1-prototype/proto-animal-rabbit@2x.png and b/1-js/9-prototypes/1-prototype/proto-animal-rabbit@2x.png differ diff --git a/1-js/9-prototypes/2-new-prototype/1-prototype-after-new/task.md b/1-js/9-prototypes/2-new-prototype/1-prototype-after-new/task.md index f8ea33a7..b9badb82 100644 --- a/1-js/9-prototypes/2-new-prototype/1-prototype-after-new/task.md +++ b/1-js/9-prototypes/2-new-prototype/1-prototype-after-new/task.md @@ -1,8 +1,10 @@ -# Прототип после создания +importance: 5 + +--- -[importance 5] +# Прототип после создания -В примерах ниже создаётся объект `new Rabbit`, а затем проводятся различные действия с `prototype`. +В примерах ниже создаётся объект `new Rabbit`, а затем проводятся различные действия с `prototype`. Каковы будут результаты выполнения? Почему? diff --git a/1-js/9-prototypes/2-new-prototype/2-default-arguments/solution.md b/1-js/9-prototypes/2-new-prototype/2-default-arguments/solution.md index 222e9083..9017d99d 100644 --- a/1-js/9-prototypes/2-new-prototype/2-default-arguments/solution.md +++ b/1-js/9-prototypes/2-new-prototype/2-default-arguments/solution.md @@ -1,15 +1,23 @@ Можно прототипно унаследовать от `options` и добавлять/менять опции в наследнике: -```js -//+ run +```js run function Menu(options) { options = Object.create(options); - options.width = options.width || 300; + options.width = 300; - alert( options.width ); // возьмёт width из наследника - alert( options.height ); // возьмёт height из исходного объекта - ... + alert("width: " + options.width); // возьмёт width из наследника + alert("height: " + options.height); // возьмёт height из исходного объекта } + +var options = { + width: 100, + height: 200 +}; + +var menu = new Menu(options); + +alert("original width: " + options.width); // width исходного объекта +alert("original height: " + options.height); // height исходного объекта ``` -Все изменения будут происходить не в самом `options`, а в его наследнике, при этом исходный объект останется незатронутым. +Все изменения будут происходить не в исходном `options`, а в его наследнике, при этом `options` останется незатронутым. diff --git a/1-js/9-prototypes/2-new-prototype/2-default-arguments/task.md b/1-js/9-prototypes/2-new-prototype/2-default-arguments/task.md index 1f1c84d5..199a2f5b 100644 --- a/1-js/9-prototypes/2-new-prototype/2-default-arguments/task.md +++ b/1-js/9-prototypes/2-new-prototype/2-default-arguments/task.md @@ -1,6 +1,8 @@ -# Аргументы по умолчанию +importance: 4 + +--- -[importance 4] +# Аргументы по умолчанию Есть функция `Menu`, которая получает аргументы в виде объекта `options`: @@ -22,6 +24,6 @@ function Menu(options) { ...Но такие изменения могут привести к непредвиденным результатам, т.к. объект `options` может быть повторно использован во внешнем коде. Он передается в `Menu` для того, чтобы параметры из него читали, а не писали. -Один из способов безопасно назначить значения по умолчанию -- скопировать все свойства `options` в локальные переменные и затем уже менять. Другой способ -- клонировать `options` путём копирования всех свойств из него в новый объект, который уже изменяется. +Один из способов безопасно назначить значения по умолчанию -- скопировать все свойства `options` в локальные переменные и затем уже менять. Другой способ -- клонировать `options` путём копирования всех свойств из него в новый объект, который уже изменяется. При помощи наследования и `Object.create` предложите третий способ, который позволяет избежать копирования объекта и не требует новых переменных. diff --git a/1-js/9-prototypes/2-new-prototype/3-compare-calls/solution.md b/1-js/9-prototypes/2-new-prototype/3-compare-calls/solution.md index 1b268b84..f6f4a873 100644 --- a/1-js/9-prototypes/2-new-prototype/3-compare-calls/solution.md +++ b/1-js/9-prototypes/2-new-prototype/3-compare-calls/solution.md @@ -1,19 +1,18 @@ # Разница между вызовами -Первый вызов ставит `this == rabbit`, остальные ставят `this` равным `Rabbit.prototype`, следуя правилу "`this` -- объект перед точкой". +Первый вызов ставит `this == rabbit`, остальные ставят `this` равным `Rabbit.prototype`, следуя правилу "`this` -- объект перед точкой". Так что только первый вызов выведет `Rabbit`, в остальных он будет `undefined`. Код для проверки: -```js -//+ run +```js run function Rabbit(name) { this.name = name; } Rabbit.prototype.sayHi = function() { alert( this.name ); -} +}; var rabbit = new Rabbit("Rabbit"); @@ -23,11 +22,9 @@ Object.getPrototypeOf(rabbit).sayHi(); rabbit.__proto__.sayHi(); ``` -# Совместимость +# Совместимость -
      -
    1. Первый вызов работает везде.
    2. -
    3. Второй вызов работает везде.
    4. -
    5. Третий вызов не будет работать в IE8-, там нет метода `getPrototypeOf`
    6. -
    7. Четвёртый вызов -- самый "несовместимый", он не будет работать в IE10-, ввиду отсутствия свойства `__proto__`.
    8. -
    \ No newline at end of file +1. Первый вызов работает везде. +2. Второй вызов работает везде. +3. Третий вызов не будет работать в IE8-, там нет метода `getPrototypeOf` +4. Четвёртый вызов -- самый "несовместимый", он не будет работать в IE10-, ввиду отсутствия свойства `__proto__`. diff --git a/1-js/9-prototypes/2-new-prototype/3-compare-calls/task.md b/1-js/9-prototypes/2-new-prototype/3-compare-calls/task.md index b3773dfa..a538b961 100644 --- a/1-js/9-prototypes/2-new-prototype/3-compare-calls/task.md +++ b/1-js/9-prototypes/2-new-prototype/3-compare-calls/task.md @@ -1,6 +1,8 @@ -# Есть ли разница между вызовами? +importance: 5 + +--- -[importance 5] +# Есть ли разница между вызовами? Создадим новый объект, вот такой: @@ -10,7 +12,7 @@ function Rabbit(name) { } Rabbit.prototype.sayHi = function() { alert( this.name ); -} +}; var rabbit = new Rabbit("Rabbit"); ``` diff --git a/1-js/9-prototypes/2-new-prototype/4-new-object-same-constructor/solution.md b/1-js/9-prototypes/2-new-prototype/4-new-object-same-constructor/solution.md index eae8b524..ef215f2c 100644 --- a/1-js/9-prototypes/2-new-prototype/4-new-object-same-constructor/solution.md +++ b/1-js/9-prototypes/2-new-prototype/4-new-object-same-constructor/solution.md @@ -2,8 +2,7 @@ В частности, без вмешательства в прототип код точно работает, например: -```js -//+ run +```js run function User(name) { this.name = name; } @@ -14,21 +13,29 @@ var obj2 = new obj.constructor('Петя'); alert( obj2.name ); // Петя (сработало) ``` -Сработало, так как `User.prototype.constructor == User`. +Сработало, так как `User.prototype.constructor == User`. Но если кто-то, к примеру, перезапишет `User.prototype` и забудет указать `constructor`, то такой фокус не пройдёт, например: -```js -//+ run +```js run function User(name) { this.name = name; } *!* -User.prototype = {}; +User.prototype = {}; // (*) */!* var obj = new User('Вася'); var obj2 = new obj.constructor('Петя'); alert( obj2.name ); // undefined -``` \ No newline at end of file +``` + +Почему obj2.name равен undefined? Вот как это работает: + +1. При вызове new `obj.constructor('Петя')`, `obj` ищет у себя свойство `constructor` -- не находит. +2. Обращается к своему свойству `__proto__`, которое ведёт к прототипу. +3. Прототипом будет (*), пустой объект. +4. Далее здесь также ищется свойство constructor -- его нет. +5. Где ищем дальше? Правильно -- у следующего прототипа выше, а им будет `Object.prototype`. +6. Свойство `Object.prototype.constructor` существует, это встроенный конструктор объектов, который, вообще говоря, не предназначен для вызова с аргументом-строкой, поэтому создаст совсем не то, что ожидается, но то же самое, что вызов `new Object('Петя')`, и у такого объекта не будет `name`. diff --git a/1-js/9-prototypes/2-new-prototype/4-new-object-same-constructor/task.md b/1-js/9-prototypes/2-new-prototype/4-new-object-same-constructor/task.md index 99cb4e90..a2abea54 100644 --- a/1-js/9-prototypes/2-new-prototype/4-new-object-same-constructor/task.md +++ b/1-js/9-prototypes/2-new-prototype/4-new-object-same-constructor/task.md @@ -1,6 +1,8 @@ -# Создать объект тем же конструктором +importance: 5 + +--- -[importance 5] +# Создать объект тем же конструктором Пусть у нас есть произвольный объект `obj`, созданный каким-то конструктором, каким -- мы не знаем, но хотели бы создать новый объект с его помощью. diff --git a/1-js/9-prototypes/2-new-prototype/article.md b/1-js/9-prototypes/2-new-prototype/article.md index 70120f02..95ba8150 100644 --- a/1-js/9-prototypes/2-new-prototype/article.md +++ b/1-js/9-prototypes/2-new-prototype/article.md @@ -1,8 +1,9 @@ # Свойство F.prototype и создание объектов через new -До этого момента мы говорили о наследовании объектов, объявленных через `{...}`. +До этого момента мы говорили о наследовании объектов, объявленных через `{...}`. Но в реальных проектах объекты обычно создаются функцией-конструктором через `new`. Посмотрим, как указать прототип в этом случае. + [cut] ## Свойство F.prototype @@ -11,8 +12,7 @@ Например, если я хочу, чтобы у всех объектов, которые создаются `new Rabbit`, был прототип `animal`, я могу сделать так: -```js -//+ run +```js run var animal = { eats: true }; @@ -29,7 +29,7 @@ var rabbit = new Rabbit("Кроль"); alert( rabbit.eats ); // true, из прототипа ``` -Недостаток этого подхода -- он не работает в IE10-. +Недостаток этого подхода -- он не работает в IE10-. К счастью, в JavaScript с древнейших времён существует альтернативный, встроенный в язык и полностью кросс-браузерный способ. @@ -39,8 +39,7 @@ alert( rabbit.eats ); // true, из прототипа Например, код ниже полностью аналогичен предыдущему, но работает всегда и везде: -```js -//+ run +```js run var animal = { eats: true }; @@ -58,25 +57,23 @@ var rabbit = new Rabbit("Кроль"); // rabbit.__proto__ == animal alert( rabbit.eats ); // true ``` -Установка `Rabbit.prototype = animal` буквально говорит интерпретатору следующее: *"При создании объекта через `new Rabbit` запиши ему `__proto__ = animal`".* +Установка `Rabbit.prototype = animal` буквально говорит интерпретатору следующее: *"При создании объекта через `new Rabbit` запиши ему `__proto__ = animal`".* -[smart header="Свойство `prototype` имеет смысл только у конструктора"] +```smart header="Свойство `prototype` имеет смысл только у конструктора" Свойство с именем `prototype` можно указать на любом объекте, но особый смысл оно имеет, лишь если назначено функции-конструктору. Само по себе, без вызова оператора `new`, оно вообще ничего не делает, его единственное назначение -- указывать `__proto__` для новых объектов. -[/smart] - - +``` -[warn header="Значением `prototype` может быть только объект"] +```warn header="Значением `prototype` может быть только объект" Технически, в это свойство можно записать что угодно. -Однако, при работе `new`, свойство `prototype` будет использовано лишь в том случае, если это объект. Примитивное значение, такое как число или строка, будет проигнорировано. -[/warn] +Однако, при работе `new`, свойство `prototype` будет использовано лишь в том случае, если это объект. Примитивное значение, такое как число или строка, будет проигнорировано. +``` ## Свойство constructor -У каждой функции по умолчанию уже есть свойство `prototype`. +У каждой функции по умолчанию уже есть свойство `prototype`. Оно содержит объект такого вида: @@ -92,8 +89,7 @@ Rabbit.prototype = { Проверим: -```js -//+ run +```js run function Rabbit() {} // в Rabbit.prototype есть одно свойство: constructor @@ -105,8 +101,7 @@ alert( Rabbit.prototype.constructor == Rabbit ); // true Можно его использовать для создания объекта с тем же конструктором, что и данный: -```js -//+ run +```js run function Rabbit(name) { this.name = name; alert( name ); @@ -119,12 +114,12 @@ var rabbit2 = new rabbit.constructor("Крольчиха"); Эта возможность бывает полезна, когда, получив объект, мы не знаем в точности, какой у него был конструктор (например, сделан вне нашего кода), а нужно создать такой же. -[warn header="Свойство `constructor` легко потерять"] -JavaScript никак не использует свойство `constructor`. То есть, оно создаётся автоматически, а что с ним происходит дальше -- это уже наша забота. В стандарте прописано только его создание. +````warn header="Свойство `constructor` легко потерять" +JavaScript никак не использует свойство `constructor`. То есть, оно создаётся автоматически, а что с ним происходит дальше -- это уже наша забота. В стандарте прописано только его создание. В частности, при перезаписи `Rabbit.prototype = { jumps: true }` свойства `constructor` больше не будет. -Сам интерпретатор JavaScript его в служебных целях не требует, поэтому в работе объектов ничего не "сломается". Но если мы хотим, чтобы возможность получить конструктор, всё же, была, то можно при перезаписи гарантировать наличие `constructor` вручную: +Сам интерпретатор JavaScript его в служебных целях не требует, поэтому в работе объектов ничего не "сломается". Но если мы хотим, чтобы возможность получить конструктор, всё же, была, то можно при перезаписи гарантировать наличие `constructor` вручную: ```js Rabbit.prototype = { jumps: true, @@ -139,8 +134,7 @@ Rabbit.prototype = { // сохранится встроенный constructor Rabbit.prototype.jumps = true ``` -[/warn] - +```` ## Эмуляция Object.create для IE8- [#inherit] @@ -148,7 +142,7 @@ Rabbit.prototype.jumps = true Теперь небольшое "лирическое отступление" в область совместимости. -Прямые методы работы с прототипом осутствуют в старых IE, но один из них -- `Object.create(proto)` можно эмулировать, как раз при помощи `prototype`. И он будет работать везде, даже в самых устаревших браузерах. +Прямые методы работы с прототипом отсутствуют в старых IE, но один из них -- `Object.create(proto)` можно эмулировать, как раз при помощи `prototype`. И он будет работать везде, даже в самых устаревших браузерах. Кросс-браузерный аналог -- назовём его `inherit`, состоит буквально из нескольких строк: @@ -165,8 +159,7 @@ function inherit(proto) { Например: -```js -//+ run +```js run var animal = { eats: true }; @@ -177,11 +170,10 @@ alert( rabbit.eats ); // true ``` Посмотрите внимательно на функцию `inherit` и вы, наверняка, сами поймёте, как она работает... - + Если где-то неясности, то её построчное описание: -```js -//+ no-beautify +```js no-beautify function inherit(proto) { function F() {} // (1) F.prototype = proto // (2) @@ -190,12 +182,10 @@ function inherit(proto) { } ``` -
      -
    1. Создана новая функция `F`. Она ничего не делает с `this`, так что если вызвать `new F`, то получим пустой объект.
    2. -
    3. Свойство `F.prototype` устанавливается в будущий прототип `proto`
    4. -
    5. Результатом вызова `new F` будет пустой объект с `__proto__` равным значению `F.prototype`.
    6. -
    7. Мы получили пустой объект с заданным прототипом, как и хотели. Возвратим его.
    8. -
    +1. Создана новая функция `F`. Она ничего не делает с `this`, так что если вызвать `new F`, то получим пустой объект. +2. Свойство `F.prototype` устанавливается в будущий прототип `proto` +3. Результатом вызова `new F` будет пустой объект с `__proto__` равным значению `F.prototype`. +4. Мы получили пустой объект с заданным прототипом, как и хотели. Возвратим его. Для унификации можно запустить такой код, и метод `Object.create` станет кросс-браузерным: @@ -205,25 +195,11 @@ if (!Object.create) Object.create = inherit; /* определение inherit - В частности, аналогичным образом работает библиотека [es5-shim](https://github.com/es-shims/es5-shim), при подключении которой `Object.create` станет доступен для всех браузеров. - ## Итого -Для произвольной функции -- назовём её `Constructor`, верно следующее: - -
      -
    • Прототип `__proto__` новых объектов, создаваемых через `new Constructor`, можно задавать при помощи свойства `Constructor.prototype`.
    • -
    • Значением `Constructor.prototype` по умолчанию является объект с единственным свойством `constructor`, содержащим ссылку на `Constructor`. Его можно использовать, чтобы из самого объекта получить функцию, которая его создала. Однако, JavaScript никак не поддерживает корректность этого свойства, поэтому программист может его изменить или удалить.
    • -
    • Современный метод `Object.create(proto)` можно эмулировать при помощи `prototype`, если хочется, чтобы он работал в IE8-.
    • -
    +Для произвольной функции -- назовём её `Person`, верно следующее: +- Прототип `__proto__` новых объектов, создаваемых через `new Person`, можно задавать при помощи свойства `Person.prototype`. +- Значением `Person.prototype` по умолчанию является объект с единственным свойством `constructor`, содержащим ссылку на `Person`. Его можно использовать, чтобы из самого объекта получить функцию, которая его создала. Однако, JavaScript никак не поддерживает корректность этого свойства, поэтому программист может его изменить или удалить. +- Современный метод `Object.create(proto)` можно эмулировать при помощи `prototype`, если хочется, чтобы он работал в IE8-. - -[head] - -[/head] \ No newline at end of file diff --git a/1-js/9-prototypes/2-new-prototype/head.html b/1-js/9-prototypes/2-new-prototype/head.html new file mode 100644 index 00000000..ec9611f8 --- /dev/null +++ b/1-js/9-prototypes/2-new-prototype/head.html @@ -0,0 +1,7 @@ + \ No newline at end of file diff --git a/1-js/9-prototypes/3-native-prototypes/1-defer-to-prototype/solution.md b/1-js/9-prototypes/3-native-prototypes/1-defer-to-prototype/solution.md index 46d09410..7d457ce4 100644 --- a/1-js/9-prototypes/3-native-prototypes/1-defer-to-prototype/solution.md +++ b/1-js/9-prototypes/3-native-prototypes/1-defer-to-prototype/solution.md @@ -1,7 +1,6 @@ -```js -//+ run +```js run Function.prototype.defer = function(ms) { setTimeout(this, ms); } diff --git a/1-js/9-prototypes/3-native-prototypes/1-defer-to-prototype/task.md b/1-js/9-prototypes/3-native-prototypes/1-defer-to-prototype/task.md index 000adcc5..8d16ebf7 100644 --- a/1-js/9-prototypes/3-native-prototypes/1-defer-to-prototype/task.md +++ b/1-js/9-prototypes/3-native-prototypes/1-defer-to-prototype/task.md @@ -1,6 +1,8 @@ -# Добавить функциям defer +importance: 5 + +--- -[importance 5] +# Добавить функциям defer Добавьте всем функциям в прототип метод `defer(ms)`, который откладывает вызов функции на `ms` миллисекунд. diff --git a/1-js/9-prototypes/3-native-prototypes/2-defer-to-prototype-extended/solution.md b/1-js/9-prototypes/3-native-prototypes/2-defer-to-prototype-extended/solution.md index 6c421265..004906a4 100644 --- a/1-js/9-prototypes/3-native-prototypes/2-defer-to-prototype-extended/solution.md +++ b/1-js/9-prototypes/3-native-prototypes/2-defer-to-prototype-extended/solution.md @@ -1,7 +1,6 @@ -```js -//+ run +```js run Function.prototype.defer = function(ms) { var f = this; return function() { diff --git a/1-js/9-prototypes/3-native-prototypes/2-defer-to-prototype-extended/task.md b/1-js/9-prototypes/3-native-prototypes/2-defer-to-prototype-extended/task.md index ddb3d4ba..2a8c2051 100644 --- a/1-js/9-prototypes/3-native-prototypes/2-defer-to-prototype-extended/task.md +++ b/1-js/9-prototypes/3-native-prototypes/2-defer-to-prototype-extended/task.md @@ -1,6 +1,8 @@ -# Добавить функциям defer с аргументами +importance: 4 + +--- -[importance 4] +# Добавить функциям defer с аргументами Добавьте всем функциям в прототип метод defer(ms), который возвращает обёртку, откладывающую вызов функции на ms миллисекунд. @@ -16,4 +18,3 @@ f.defer(1000)(1, 2); // выведет 3 через 1 секунду. То есть, должны корректно передаваться аргументы. - diff --git a/1-js/9-prototypes/3-native-prototypes/article.md b/1-js/9-prototypes/3-native-prototypes/article.md index 1c8ce795..e96f707d 100644 --- a/1-js/9-prototypes/3-native-prototypes/article.md +++ b/1-js/9-prototypes/3-native-prototypes/article.md @@ -8,8 +8,7 @@ Начнём мы с того, что создадим пустой объект и выведем его. -```js -//+ run +```js run var obj = {}; alert( obj ); // "[object Object]" ? ``` @@ -20,25 +19,21 @@ alert( obj ); // "[object Object]" ? ...Конечно же, это сделал метод `toString`, который находится... Конечно, не в самом объекте (он пуст), а в его прототипе `obj.__proto__`, можно его даже вывести: -```js -//+ run +```js run alert( {}.__proto__.toString ); // function toString ``` Откуда новый объект `obj` получает такой `__proto__`? -
      -
    1. Запись `obj = {}` является краткой формой `obj = new Object`, где `Object` -- встроенная функция-конструктор для объектов.
    2. -
    3. При выполнении `new Object`, создаваемому объекту ставится `__proto__` по `prototype` конструктора, который в данном случае равен встроенному `Object.prototype`.
    4. -
    5. В дальнейшем при обращении к `obj.toString()` -- функция будет взята из `Object.prototype`.
    6. -
    +1. Запись `obj = {}` является краткой формой `obj = new Object`, где `Object` -- встроенная функция-конструктор для объектов. +2. При выполнении `new Object`, создаваемому объекту ставится `__proto__` по `prototype` конструктора, который в данном случае равен встроенному `Object.prototype`. +3. В дальнейшем при обращении к `obj.toString()` -- функция будет взята из `Object.prototype`. - +![](native-prototypes-object.png) Это можно легко проверить: -```js -//+ run +```js run var obj = {}; // метод берётся из прототипа? @@ -55,7 +50,7 @@ alert( obj.__proto__.__proto__ ); // null, нет Точно такой же подход используется в массивах `Array`, функциях `Function` и других объектах. Встроенные методы для них находятся в `Array.prototype`, `Function.prototype` и т.п. - +![](native-prototypes-classes.png) Например, когда мы создаём массив, `[1, 2, 3]`, то это альтернативный вариант синтаксиса `new Array`, так что у массивов есть стандартный прототип `Array.prototype`. @@ -63,7 +58,7 @@ alert( obj.__proto__.__proto__ ); // null, нет Аналогично, для функций. -Лишь для чисел (как и других примитивов) всё немного иначе, но об этом чуть далее. +Лишь для чисел (как и других примитивов) всё немного иначе, но об этом чуть далее. Объект `Object.prototype` -- вершина иерархии, единственный, у которого `__proto__` равно `null`. @@ -73,23 +68,19 @@ alert( obj.__proto__.__proto__ ); // null, нет При наследовании часть методов переопределяется, например, у массива `Array` есть свой `toString`, который выводит элементы массива через запятую: -```js -//+ run +```js run var arr = [1, 2, 3] alert( arr ); // 1,2,3 <-- результат Array.prototype.toString ``` Как мы видели раньше, у `Object.prototype` есть свой `toString`, но так как в `Array.prototype` он ищется первым, то берётся именно вариант для массивов: - - - -[smart header="Вызов методов через `apply` из прототипа"] +![](native-prototypes-array-tostring.png) +````smart header="Вызов методов через `call` и `apply` из прототипа" Ранее мы говорили о применении методов массивов к "псевдомассивам", например, можно использовать `[].join` для `arguments`: -```js -//+ run +```js run function showList() { *!* alert( [].join.call(arguments, " - ") ); @@ -101,8 +92,7 @@ showList("Вася", "Паша", "Маша"); // Вася - Паша - Маша Так как метод `join` находится в `Array.prototype`, то можно вызвать его оттуда напрямую, вот так: -```js -//+ run +```js run function showList() { *!* alert( Array.prototype.join.call(arguments, " - ") ); @@ -113,7 +103,7 @@ showList("Вася", "Паша", "Маша"); // Вася - Паша - Маша ``` Это эффективнее, потому что не создаётся лишний объект массива `[]`, хотя, с другой стороны -- больше букв писать. -[/smart] +```` ## Примитивы @@ -121,26 +111,24 @@ showList("Вася", "Паша", "Маша"); // Вася - Паша - Маша По стандарту, если обратиться к свойству числа, строки или логического значения, то будет создан объект соответствующего типа, например `new String` для строки, `new Number` для чисел, `new Boolean` -- для логических выражений. -Далее будет произведена операция со свойством или вызов метода по обычным правилам, с поиском в прототипе, а затем этот объект будет уничтожен. +Далее будет произведена операция со свойством или вызов метода по обычным правилам, с поиском в прототипе, а затем этот объект будет уничтожен. Именно так работает код ниже: -```js -//+ run +```js run var user = "Вася"; // создали строку (примитив) *!* alert( user.toUpperCase() ); // ВАСЯ // был создан временный объект new String -// вызван метод +// вызван метод // new String уничтожен, результат возвращён */!* ``` Можно даже попробовать записать в этот временный объект свойство: -```js -//+ run +```js run // попытаемся записать свойство в строку: var user = "Вася"; user.age = 30; @@ -152,11 +140,10 @@ alert( user.age ); // undefined Свойство `age` было записано во временный объект, который был тут же уничтожен, так что смысла в такой записи немного. -[warn header="Конструкторы `String/Number/Boolean` -- только для внутреннего использования"] +````warn header="Конструкторы `String/Number/Boolean` -- только для внутреннего использования" Технически, можно создавать объекты для примитивов и вручную, например `new Number`. Но в ряде случаев получится откровенно бредовое поведение. Например: -```js -//+ run +```js run alert( typeof 1 ); // "number" alert( typeof new Number(1) ); // "object" ?!? @@ -164,8 +151,7 @@ alert( typeof new Number(1) ); // "object" ?!? Или, ещё страннее: -```js -//+ run +```js run var zero = new Number(0); if (zero) { // объект - true, так что alert выполнится @@ -174,14 +160,13 @@ if (zero) { // объект - true, так что alert выполнится ``` Поэтому в явном виде `new String`, `new Number` и `new Boolean` никогда не вызываются. -[/warn] +```` -[warn header="Значения `null` и `undefined` не имеют свойств"] +```warn header="Значения `null` и `undefined` не имеют свойств" Значения `null` и `undefined` стоят особняком. Вышесказанное к ним не относится. Для них нет соответствующих классов, в них нельзя записать свойство (будет ошибка), в общем, на конкурсе "самое примитивное значение" они точно разделили бы первое место. -[/warn] - +``` ## Изменение встроенных прототипов [#native-prototype-change] @@ -189,8 +174,7 @@ if (zero) { // объект - true, так что alert выполнится Мы можем написать метод для многократного повторения строки, и он тут же станет доступным для всех строк: -```js -//+ run +```js run String.prototype.repeat = function(times) { return new Array(times + 1).join(this); }; @@ -200,8 +184,7 @@ alert( "ля".repeat(3) ); // ляляля Аналогично мы могли бы создать метод `Object.prototype.each(func)`, который будет применять `func` к каждому свойству: -```js -//+ run +```js run Object.prototype.each = function(f) { for (var prop in this) { var value = this[prop]; @@ -209,7 +192,7 @@ Object.prototype.each = function(f) { } } -// Попробуем! (внимание, пока что это работает неверно!) +// Попробуем! (внимание, пока что это работает неверно!) var user = { name: 'Вася', age: 25 @@ -224,8 +207,7 @@ user.each(function(prop, val) { Конечно, это легко поправить добавлением проверки `hasOwnProperty`: -```js -//+ run +```js run Object.prototype.each = function(f) { for (var prop in this) { @@ -257,8 +239,7 @@ obj.each(function(prop, val) { Это, конечно, не будет работать в IE8-: -```js -//+ run +```js run Object.prototype.each = function(f) { for (var prop in this) { @@ -288,11 +269,11 @@ obj.each(function(prop, val) { Есть несколько "за" и "против" модификации встроенных прототипов: -[compare] -+Методы в прототипе автоматически доступны везде, их вызов прост и красив. --Новые свойства, добавленные в прототип из разных мест, могут конфликтовать между собой. Представьте, что вы подключили две библиотеки, которые добавили одно и то же свойство в прототип, но определили его по-разному. Конфликт неизбежен. --Изменения встроенных прототипов влияют глобально, на все-все скрипты, делать их не очень хорошо с архитектурной точки зрения. -[/compare] +```compare ++ Методы в прототипе автоматически доступны везде, их вызов прост и красив. +- Новые свойства, добавленные в прототип из разных мест, могут конфликтовать между собой. Представьте, что вы подключили две библиотеки, которые добавили одно и то же свойство в прототип, но определили его по-разному. Конфликт неизбежен. +- Изменения встроенных прототипов влияют глобально, на все-все скрипты, делать их не очень хорошо с архитектурной точки зрения. +``` Как правило, минусы весомее, но есть одно исключение, когда изменения встроенных прототипов не только разрешены, но и приветствуются. @@ -316,13 +297,11 @@ if (!Object.create) { ## Итого -
      -
    • Методы встроенных объектов хранятся в их прототипах.
    • -
    • Встроенные прототипы можно расширить или поменять.
    • -
    • Добавление методов в `Object.prototype`, если оно не сопровождается `Object.defineProperty` с установкой `enumerable` (IE9+), "сломает" циклы `for..in`, поэтому стараются в этот прототип методы не добавлять. +- Методы встроенных объектов хранятся в их прототипах. +- Встроенные прототипы можно расширить или поменять. +- Добавление методов в `Object.prototype`, если оно не сопровождается `Object.defineProperty` с установкой `enumerable` (IE9+), "сломает" циклы `for..in`, поэтому стараются в этот прототип методы не добавлять. -Другие прототипы изменять менее опасно, но все же не рекомендуется во избежание конфликтов. + Другие прототипы изменять менее опасно, но все же не рекомендуется во избежание конфликтов. -Отдельно стоит изменение с целью добавления современных методов в старые браузеры, таких как Object.create, Object.keys, Function.prototype.bind и т.п. Это допустимо и как раз делается [es5-shim](https://github.com/kriskowal/es5-shim).
    • -
    + Отдельно стоит изменение с целью добавления современных методов в старые браузеры, таких как Object.create, Object.keys, Function.prototype.bind и т.п. Это допустимо и как раз делается [es5-shim](https://github.com/kriskowal/es5-shim). diff --git a/1-js/9-prototypes/3-native-prototypes/native-prototypes-array-tostring.png b/1-js/9-prototypes/3-native-prototypes/native-prototypes-array-tostring.png index 0c6f18e5..e8b379c6 100644 Binary files a/1-js/9-prototypes/3-native-prototypes/native-prototypes-array-tostring.png and b/1-js/9-prototypes/3-native-prototypes/native-prototypes-array-tostring.png differ diff --git a/1-js/9-prototypes/3-native-prototypes/native-prototypes-array-tostring@2x.png b/1-js/9-prototypes/3-native-prototypes/native-prototypes-array-tostring@2x.png index 6f58e781..bf361ff9 100644 Binary files a/1-js/9-prototypes/3-native-prototypes/native-prototypes-array-tostring@2x.png and b/1-js/9-prototypes/3-native-prototypes/native-prototypes-array-tostring@2x.png differ diff --git a/1-js/9-prototypes/3-native-prototypes/native-prototypes-classes.png b/1-js/9-prototypes/3-native-prototypes/native-prototypes-classes.png index 451f4548..34bb1849 100644 Binary files a/1-js/9-prototypes/3-native-prototypes/native-prototypes-classes.png and b/1-js/9-prototypes/3-native-prototypes/native-prototypes-classes.png differ diff --git a/1-js/9-prototypes/3-native-prototypes/native-prototypes-classes@2x.png b/1-js/9-prototypes/3-native-prototypes/native-prototypes-classes@2x.png index 28783af2..a3b705c2 100644 Binary files a/1-js/9-prototypes/3-native-prototypes/native-prototypes-classes@2x.png and b/1-js/9-prototypes/3-native-prototypes/native-prototypes-classes@2x.png differ diff --git a/1-js/9-prototypes/3-native-prototypes/native-prototypes-object.png b/1-js/9-prototypes/3-native-prototypes/native-prototypes-object.png index 5ac3315e..3daf03c4 100644 Binary files a/1-js/9-prototypes/3-native-prototypes/native-prototypes-object.png and b/1-js/9-prototypes/3-native-prototypes/native-prototypes-object.png differ diff --git a/1-js/9-prototypes/3-native-prototypes/native-prototypes-object@2x.png b/1-js/9-prototypes/3-native-prototypes/native-prototypes-object@2x.png index 26ae7911..66055753 100644 Binary files a/1-js/9-prototypes/3-native-prototypes/native-prototypes-object@2x.png and b/1-js/9-prototypes/3-native-prototypes/native-prototypes-object@2x.png differ diff --git a/1-js/9-prototypes/4-classes/1-rewrite-by-class/solution.md b/1-js/9-prototypes/4-classes/1-rewrite-by-class/solution.md index a43508c8..b073d2d5 100644 --- a/1-js/9-prototypes/4-classes/1-rewrite-by-class/solution.md +++ b/1-js/9-prototypes/4-classes/1-rewrite-by-class/solution.md @@ -1,7 +1,6 @@ -```js -//+ run +```js run function CoffeeMachine(power) { // свойства конкретной кофеварки this._power = power; diff --git a/1-js/9-prototypes/4-classes/1-rewrite-by-class/task.md b/1-js/9-prototypes/4-classes/1-rewrite-by-class/task.md index cfc8aa91..23eb51aa 100644 --- a/1-js/9-prototypes/4-classes/1-rewrite-by-class/task.md +++ b/1-js/9-prototypes/4-classes/1-rewrite-by-class/task.md @@ -1,15 +1,16 @@ -# Перепишите в виде класса +importance: 5 + +--- -[importance 5] +# Перепишите в виде класса -Есть класс `CoffeeMachine`, заданный в функциональном стиле. +Есть класс `CoffeeMachine`, заданный в функциональном стиле. Задача: переписать `CoffeeMachine` в виде класса с использованием прототипа. Исходный код: -```js -//+ run +```js run function CoffeeMachine(power) { var waterAmount = 0; diff --git a/1-js/9-prototypes/4-classes/2-hamsters-with-proto/solution.md b/1-js/9-prototypes/4-classes/2-hamsters-with-proto/solution.md index 113334fd..8d04de88 100644 --- a/1-js/9-prototypes/4-classes/2-hamsters-with-proto/solution.md +++ b/1-js/9-prototypes/4-classes/2-hamsters-with-proto/solution.md @@ -1,13 +1,12 @@ # Почему возникает проблема Давайте подробнее разберем происходящее при вызове `speedy.found("яблоко")`: -
      -
    1. Интерпретатор ищет свойство `found` в `speedy`. Но `speedy` -- пустой объект, т.к. `new Hamster` ничего не делает с `this`.
    2. -
    3. Интерпретатор идёт по ссылке `speedy.__proto__ (==Hamster.prototype)` и находят там метод `found`, запускает его.
    4. -
    5. Значение `this` устанавливается в объект перед точкой, т.е. в `speedy`.
    6. -
    7. Для выполнения `this.food.push()` нужно найти свойство `this.food`. Оно отсутствует в `speedy`, но есть в `speedy.__proto__`.
    8. -
    9. Значение `"яблоко"` добавляется в `speedy.__proto__.food`.
    10. -
    + +1. Интерпретатор ищет свойство `found` в `speedy`. Но `speedy` -- пустой объект, т.к. `new Hamster` ничего не делает с `this`. +2. Интерпретатор идёт по ссылке `speedy.__proto__ (==Hamster.prototype)` и находят там метод `found`, запускает его. +3. Значение `this` устанавливается в объект перед точкой, т.е. в `speedy`. +4. Для выполнения `this.food.push()` нужно найти свойство `this.food`. Оно отсутствует в `speedy`, но есть в `speedy.__proto__`. +5. Значение `"яблоко"` добавляется в `speedy.__proto__.food`. **У всех хомяков общий живот!** Или, в терминах JavaScript, свойство `food` изменяется в прототипе, который является общим для всех объектов-хомяков. @@ -21,14 +20,9 @@ this.food = something; **Проблема возникает только со свойствами-объектами в прототипе.** -Исправьте её? - -# Исправление - Для исправления проблемы нужно дать каждому хомяку свой живот. Это можно сделать, присвоив его в конструкторе. -```js -//+ run +```js run function Hamster() { *!* this.food = []; @@ -49,4 +43,4 @@ alert(speedy.food.length) // 2 alert(lazy.food.length) // 0(!) ``` -Теперь всё в порядке. У каждого хомяка -- свой живот. \ No newline at end of file +Теперь всё в порядке. У каждого хомяка -- свой живот. diff --git a/1-js/9-prototypes/4-classes/2-hamsters-with-proto/task.md b/1-js/9-prototypes/4-classes/2-hamsters-with-proto/task.md index 48aa3220..6bbbd8e9 100644 --- a/1-js/9-prototypes/4-classes/2-hamsters-with-proto/task.md +++ b/1-js/9-prototypes/4-classes/2-hamsters-with-proto/task.md @@ -1,17 +1,18 @@ -# Хомяки с __proto__ +importance: 5 + +--- -[importance 5] +# Хомяки с __proto__ Вы -- руководитель команды, которая разрабатывает игру, хомяковую ферму. Один из программистов получил задание создать класс "хомяк" (англ - `"Hamster"`). -Объекты-хомяки должны иметь массив `food` для хранения еды и метод `found`, который добавляет к нему. +Объекты-хомяки должны иметь массив `food` для хранения еды и метод `found` для добавления. -Ниже -- его решение. При создании двух хомяков, если поел один -- почему-то сытым становится и второй тоже. +Ниже -- его решение. При создании двух хомяков, если поел один -- почему-то сытым становится и второй тоже. В чём дело? Как поправить? -```js -//+ run +```js run function Hamster() {} Hamster.prototype.food = []; // пустой "живот" diff --git a/1-js/9-prototypes/4-classes/article.md b/1-js/9-prototypes/4-classes/article.md index 082c22f2..f3748da4 100644 --- a/1-js/9-prototypes/4-classes/article.md +++ b/1-js/9-prototypes/4-classes/article.md @@ -3,14 +3,14 @@ Используем ту же структуру, что JavaScript использует внутри себя, для объявления своих классов. [cut] + ## Обычный конструктор Вспомним, как мы объявляли классы ранее. Например, этот код задаёт класс `Animal` в функциональном стиле, без всяких прототипов: -```js -//+ run +```js run function Animal(name) { this.speed = 0; this.name = name; @@ -40,15 +40,12 @@ animal.stop(); // Зверь стоит Чтобы объявить свой класс, нужно: -
      -
    1. Объявить функцию-конструктор.
    2. -
    3. Записать методы и свойства, нужные всем объектам класса, в `prototype`.
    4. -
    +1. Объявить функцию-конструктор. +2. Записать методы и свойства, нужные всем объектам класса, в `prototype`. Опишем класс `Animal`: -```js -//+ run +```js run // конструктор function Animal(name) { this.name = name; @@ -74,7 +71,7 @@ animal.run(5); // Зверь бежит, скорость 10 animal.stop(); // Зверь стоит ``` -В объекте `animal` будут хранится свойства конкретного экземпляра: `name` и `speed`, а общие методы -- в прототипе. +В объекте `animal` будут храниться свойства конкретного экземпляра: `name` и `speed`, а общие методы -- в прототипе. Совершенно такой же подход, как и для встроенных классов в JavaScript. @@ -82,17 +79,16 @@ animal.stop(); // Зверь стоит Чем такое задание класса лучше и хуже функционального стиля? -[compare] -+Функциональный стиль записывает в каждый объект и свойства и методы, а прототипный -- только свойства. Поэтому прототипный стиль -- быстрее и экономнее по памяти. --При создании методов через прототип, мы теряем возможность использовать локальные переменные как приватные свойства, у них больше нет общей области видимости с конструктором. -[/compare] +```compare ++ Функциональный стиль записывает в каждый объект и свойства и методы, а прототипный -- только свойства. Поэтому прототипный стиль -- быстрее и экономнее по памяти. +- При создании методов через прототип, мы теряем возможность использовать локальные переменные как приватные свойства, у них больше нет общей области видимости с конструктором. +``` Таким образом, прототипный стиль -- быстрее и экономнее, но немного менее удобен. К примеру, есть у нас приватное свойство `name` и метод `sayHi` в функциональном стиле ООП: -```js -//+ run +```js run function Animal(name) { this.sayHi = function() { *!* @@ -107,8 +103,7 @@ animal.sayHi(); // Зверь При задании методов в прототипе мы не сможем её так оставить, ведь методы находятся *вне* конструктора, у них нет общей области видимости, поэтому приходится записывать `name` в сам объект, обозначив его как защищённое: -```js -//+ run +```js run function Animal(name) { *!* this._name = name; diff --git a/1-js/9-prototypes/5-class-inheritance/1-inheritance-error-assign/solution.md b/1-js/9-prototypes/5-class-inheritance/1-inheritance-error-assign/solution.md index 8987b2d7..a118251d 100644 --- a/1-js/9-prototypes/5-class-inheritance/1-inheritance-error-assign/solution.md +++ b/1-js/9-prototypes/5-class-inheritance/1-inheritance-error-assign/solution.md @@ -8,13 +8,12 @@ Rabbit.prototype = Animal.prototype; Получится, что все животные прыгают, вот пример: -```js -//+ run no-beautify +```js run no-beautify function Animal(name) { this.name = name; } -Animal.prototype.walk = function() { +Animal.prototype.walk = function() { alert("ходит " + this.name); }; @@ -25,7 +24,7 @@ function Rabbit(name) { Rabbit.prototype = Animal.prototype; */!* -Rabbit.prototype.walk = function() { +Rabbit.prototype.walk = function() { alert("прыгает! и ходит: " + this.name); }; diff --git a/1-js/9-prototypes/5-class-inheritance/1-inheritance-error-assign/task.md b/1-js/9-prototypes/5-class-inheritance/1-inheritance-error-assign/task.md index ddb177e9..c033569a 100644 --- a/1-js/9-prototypes/5-class-inheritance/1-inheritance-error-assign/task.md +++ b/1-js/9-prototypes/5-class-inheritance/1-inheritance-error-assign/task.md @@ -1,6 +1,8 @@ -# Найдите ошибку в наследовании +importance: 5 + +--- -[importance 5] +# Найдите ошибку в наследовании Найдите ошибку в прототипном наследовании. К чему она приведёт? diff --git a/1-js/9-prototypes/5-class-inheritance/2-inheritance-error-constructor/task.md b/1-js/9-prototypes/5-class-inheritance/2-inheritance-error-constructor/task.md index b97447f5..68552e19 100644 --- a/1-js/9-prototypes/5-class-inheritance/2-inheritance-error-constructor/task.md +++ b/1-js/9-prototypes/5-class-inheritance/2-inheritance-error-constructor/task.md @@ -1,11 +1,12 @@ -# В чём ошибка в наследовании +importance: 5 + +--- -[importance 5] +# В чём ошибка в наследовании Найдите ошибку в прототипном наследовании. К чему она приведёт? -```js -//+ run +```js run function Animal(name) { this.name = name; diff --git a/1-js/9-prototypes/5-class-inheritance/3-clock-class/solution.md b/1-js/9-prototypes/5-class-inheritance/3-clock-class/solution.md index d1c08b87..f2fc2545 100644 --- a/1-js/9-prototypes/5-class-inheritance/3-clock-class/solution.md +++ b/1-js/9-prototypes/5-class-inheritance/3-clock-class/solution.md @@ -1,7 +1,4 @@ -```js -//+ src="http://github.com/clock.js" -``` +[js src="http://github.com/clock.js"] -[edit src="http://github.com/solution"]Открыть полное решение[/edit] \ No newline at end of file diff --git a/1-js/9-prototypes/5-class-inheritance/3-clock-class/task.md b/1-js/9-prototypes/5-class-inheritance/3-clock-class/task.md index 59355fee..0ab36a42 100644 --- a/1-js/9-prototypes/5-class-inheritance/3-clock-class/task.md +++ b/1-js/9-prototypes/5-class-inheritance/3-clock-class/task.md @@ -1,11 +1,11 @@ -# Класс "часы" +importance: 5 + +--- -[importance 5] +# Класс "часы" Есть реализация часиков, оформленная в виде одной функции-конструктора. У неё есть приватные свойства `timer`, `template` и метод `render`. Задача: переписать часы на прототипах. Приватные свойства и методы сделать защищёнными. - - P.S. Часики тикают в браузерной консоли (надо открыть её, чтобы увидеть). \ No newline at end of file diff --git a/1-js/9-prototypes/5-class-inheritance/4-clock-class-extended/solution.md b/1-js/9-prototypes/5-class-inheritance/4-clock-class-extended/solution.md index f58d46d8..6595dcc5 100644 --- a/1-js/9-prototypes/5-class-inheritance/4-clock-class-extended/solution.md +++ b/1-js/9-prototypes/5-class-inheritance/4-clock-class-extended/solution.md @@ -1,7 +1,4 @@ Наследник: -```js -//+ src="http://github.com/extended-clock.js" -``` +[js src="http://github.com/extended-clock.js"] -[edit src="http://github.com/solution"]Открыть полное решение в редакторе[/edit] \ No newline at end of file diff --git a/1-js/9-prototypes/5-class-inheritance/4-clock-class-extended/task.md b/1-js/9-prototypes/5-class-inheritance/4-clock-class-extended/task.md index 94bee22a..520c38d7 100644 --- a/1-js/9-prototypes/5-class-inheritance/4-clock-class-extended/task.md +++ b/1-js/9-prototypes/5-class-inheritance/4-clock-class-extended/task.md @@ -1,15 +1,13 @@ -# Класс "расширенные часы" - -[importance 5] +importance: 5 -Есть реализация часиков на прототипах. Создайте класс, расширяющий её, добавляющий поддержку параметра `precision`, который будет задавать частоту тика в `setInterval`. Значение по умолчанию: `1000`. +--- +# Класс "расширенные часы" +Есть реализация часиков на прототипах. Создайте класс, расширяющий её, добавляющий поддержку параметра `precision`, который будет задавать частоту тика в `setInterval`. Значение по умолчанию: `1000`. -
      -
    • Для этого класс `Clock` надо унаследовать. Пишите ваш новый код в файле `extended-clock.js`.
    • -
    • Исходный класс `Clock` менять нельзя.
    • -
    • Пусть конструктор потомка вызывает конструктор родителя. Это позволит избежать проблем при расширении `Clock` новыми опциями.
    • -
    +- Для этого класс `Clock` надо унаследовать. Пишите ваш новый код в файле `extended-clock.js`. +- Исходный класс `Clock` менять нельзя. +- Пусть конструктор потомка вызывает конструктор родителя. Это позволит избежать проблем при расширении `Clock` новыми опциями. P.S. Часики тикают в браузерной консоли (надо открыть её, чтобы увидеть). \ No newline at end of file diff --git a/1-js/9-prototypes/5-class-inheritance/5-menu-timer-animated/solution.md b/1-js/9-prototypes/5-class-inheritance/5-menu-timer-animated/solution.md index 9cb942f7..b9803eb0 100644 --- a/1-js/9-prototypes/5-class-inheritance/5-menu-timer-animated/solution.md +++ b/1-js/9-prototypes/5-class-inheritance/5-menu-timer-animated/solution.md @@ -1,3 +1,2 @@ -[edit src="http://github.com/solution"]Открыть решение в редакторе[/edit] Обратите внимание: константы состояний перенесены в прототип, чтобы `AnimatingMenu` их тоже унаследовал. diff --git a/1-js/9-prototypes/5-class-inheritance/5-menu-timer-animated/task.md b/1-js/9-prototypes/5-class-inheritance/5-menu-timer-animated/task.md index dd080260..c8f5297f 100644 --- a/1-js/9-prototypes/5-class-inheritance/5-menu-timer-animated/task.md +++ b/1-js/9-prototypes/5-class-inheritance/5-menu-timer-animated/task.md @@ -1,14 +1,14 @@ -# Меню с таймером для анимации +importance: 5 + +--- -[importance 5] +# Меню с таймером для анимации Есть класс `Menu`. У него может быть два состояния: открыто `STATE_OPEN` и закрыто `STATE_CLOSED`. Создайте наследника `AnimatingMenu`, который добавляет третье состояние `STATE_ANIMATING`. -
      -
    • При вызове `open()` состояние меняется на `STATE_ANIMATING`, а через 1 секунду, по таймеру, открытие завершается вызовом `open()` родителя.
    • -
    • Вызов `close()` при необходимости отменяет таймер анимации (назначаемый в `open`) и передаёт вызов родительскому `close`.
    • -
    • Метод `showState` для нового состояния выводит `"анимация"`, для остальных -- полагается на родителя.
    • -
    -[edit src="http://github.com/source"]Исходный документ, вместе с тестом[/edit] \ No newline at end of file +- При вызове `open()` состояние меняется на `STATE_ANIMATING`, а через 1 секунду, по таймеру, открытие завершается вызовом `open()` родителя. +- Вызов `close()` при необходимости отменяет таймер анимации (назначаемый в `open`) и передаёт вызов родительскому `close`. +- Метод `showState` для нового состояния выводит `"анимация"`, для остальных -- полагается на родителя. + diff --git a/1-js/9-prototypes/5-class-inheritance/6-constructor-inherited/solution.md b/1-js/9-prototypes/5-class-inheritance/6-constructor-inherited/solution.md index 4339ffe9..efddb664 100644 --- a/1-js/9-prototypes/5-class-inheritance/6-constructor-inherited/solution.md +++ b/1-js/9-prototypes/5-class-inheritance/6-constructor-inherited/solution.md @@ -3,14 +3,12 @@ Свойство `constructor` содержится в `prototype` функции по умолчанию, интерпретатор не поддерживает его корректность. Посмотрим, чему оно равно и откуда оно будет взято в данном случае. Порядок поиска свойства `rabbit.constructor`, по цепочке прототипов: -
      -
    1. `rabbit` -- это пустой объект, в нём нет.
    2. -
    3. `Rabbit.prototype` -- в него при помощи `Object.create` записан пустой объект, наследующий от `Animal.prototype`. Поэтому `constructor'а` в нём также нет.
    4. -
    5. `Animal.prototype` -- у функции `Animal` свойство `prototype` никто не менял. Поэтому оно содержит `Animal.prototype.constructor == Animal`.
    6. -
    - -```js -//+ run + +1. `rabbit` -- это пустой объект, в нём нет. +2. `Rabbit.prototype` -- в него при помощи `Object.create` записан пустой объект, наследующий от `Animal.prototype`. Поэтому `constructor'а` в нём также нет. +3. `Animal.prototype` -- у функции `Animal` свойство `prototype` никто не менял. Поэтому оно содержит `Animal.prototype.constructor == Animal`. + +```js run function Animal() {} function Rabbit() {} diff --git a/1-js/9-prototypes/5-class-inheritance/6-constructor-inherited/task.md b/1-js/9-prototypes/5-class-inheritance/6-constructor-inherited/task.md index ea7fde62..ad13fcb8 100644 --- a/1-js/9-prototypes/5-class-inheritance/6-constructor-inherited/task.md +++ b/1-js/9-prototypes/5-class-inheritance/6-constructor-inherited/task.md @@ -1,6 +1,8 @@ -# Что содержит constructor? +importance: 5 + +--- -[importance 5] +# Что содержит constructor? В коде ниже создаётся простейшая иерархия классов: `Animal -> Rabbit`. diff --git a/1-js/9-prototypes/5-class-inheritance/article.md b/1-js/9-prototypes/5-class-inheritance/article.md index 3aa7012c..4b110f6f 100644 --- a/1-js/9-prototypes/5-class-inheritance/article.md +++ b/1-js/9-prototypes/5-class-inheritance/article.md @@ -12,14 +12,12 @@ Взглянем на него ещё раз на примере `Array`, который наследует от `Object`: - +![](class-inheritance-array-object.png) -
      -
    • Методы массивов `Array` хранятся в `Array.prototype`.
    • -
    • `Array.prototype` имеет прототипом `Object.prototype`.
    • -
    +- Методы массивов `Array` хранятся в `Array.prototype`. +- `Array.prototype` имеет прототипом `Object.prototype`. -Поэтому когда экземпляры класса `Array` хотят получить метод массива -- они берут его из своего прототипа, например `Array.prototype.slice`. +Поэтому когда экземпляры класса `Array` хотят получить метод массива -- они берут его из своего прототипа, например `Array.prototype.slice`. Если же нужен метод объекта, например, `hasOwnProperty`, то его в `Array.prototype` нет, и он берётся из `Object.prototype`. @@ -27,13 +25,13 @@ Вывод в Chrome будет примерно таким: - +![](console_dir_array.png) Здесь отчётливо видно, что сами данные и `length` находятся в массиве, дальше в `__proto__` идут методы для массивов `concat`, то есть `Array.prototype`, а далее -- `Object.prototype`. -[smart header="`console.dir` для доступа к свойствам"] +```smart header="`console.dir` для доступа к свойствам" Обратите внимание, я использовал именно `console.dir`, а не `console.log`, поскольку `log` зачастую выводит объект в виде строки, без доступа к свойствам. -[/smart] +``` ## Наследование в наших классах @@ -78,7 +76,7 @@ var rabbit = new Rabbit('Кроль'); Для того, чтобы наследование работало, объект `rabbit = new Rabbit` должен использовать свойства и методы из своего прототипа `Rabbit.prototype`, а если их там нет, то -- свойства и метода родителя, которые хранятся в `Animal.prototype`. -Если ещё короче -- порядок поиска свойств и методов должен быть таким: `rabbit -> Rabbit.prototype -> Animal.prototype`, по аналогии с тем, как это сделано для объектов и массивов. +Если ещё короче -- порядок поиска свойств и методов должен быть таким: `rabbit -> Rabbit.prototype -> Animal.prototype`, по аналогии с тем, как это сделано для объектов и массивов. Для этого можно поставить ссылку `__proto__` с `Rabbit.prototype` на `Animal.prototype`. @@ -91,8 +89,7 @@ Rabbit.prototype.__proto__ = Animal.prototype; Класс `Animal` остаётся без изменений, а `Rabbit.prototype` мы будем создавать с нужным прототипом, используя `Object.create`: -```js -//+ no-beautify +```js no-beautify function Rabbit(name) { this.name = name; this.speed = 0; @@ -109,7 +106,7 @@ Rabbit.prototype.jump = function() { ... }; Теперь выглядеть иерархия будет так: - +![](class-inheritance-rabbit-animal.png) В `prototype` по умолчанию всегда находится свойство `constructor`, указывающее на функцию-конструктор. В частности, `Rabbit.prototype.constructor == Rabbit`. Если мы рассчитываем использовать это свойство, то при замене `prototype` через `Object.create` нужно его явно сохранить: @@ -123,7 +120,7 @@ Rabbit.prototype.constructor = Rabbit; Для наглядности -- вот итоговый код с двумя классами `Animal` и `Rabbit`: ```js -// 1. Конструктор Animal +// 1. Конструктор Animal function Animal(name) { this.name = name; this.speed = 0; @@ -141,7 +138,6 @@ Animal.prototype.run = function(speed) { alert( this.name + ' бежит, скорость ' + this.speed ); }; - // 2. Конструктор Rabbit function Rabbit(name) { this.name = name; @@ -161,10 +157,9 @@ Rabbit.prototype.jump = function() { Как видно, наследование задаётся всего одной строчкой, поставленной в правильном месте. -Обратим внимание: `Rabbit.prototype = Object.create(proto)` присваивается сразу после объявления конструктора, иначе он перезатрёт уже записанные в прототип методы. - -[warn header="Неправильный вариант: `Rabbit.prototype = new Animal`"] +Обратим внимание: `Rabbit.prototype = Object.create(Animal.prototype)` присваивается сразу после объявления конструктора, иначе он перезатрёт уже записанные в прототип методы. +````warn header="Неправильный вариант: `Rabbit.prototype = new Animal`" В некоторых устаревших руководствах предлагают вместо `Object.create(Animal.prototype)` записывать в прототип `new Animal`, вот так: ```js @@ -172,13 +167,12 @@ Rabbit.prototype.jump = function() { Rabbit.prototype = new Animal(); ``` - Частично, он рабочий, поскольку иерархия прототипов будет такая же, ведь `new Animal` -- это объект с прототипом `Animal.prototype`, как и `Object.create(Animal.prototype)`. Они в этом плане идентичны. Но у этого подхода важный недостаток. Как правило мы не хотим создавать `Animal`, а хотим только унаследовать его методы! Более того, на практике создание объекта может требовать обязательных аргументов, влиять на страницу в браузере, делать запросы к серверу и что-то ещё, чего мы хотели бы избежать. Поэтому рекомендуется использовать вариант с `Object.create`. -[/warn] +```` ## Вызов конструктора родителя @@ -206,13 +200,13 @@ function Rabbit(name) { } ``` -Такой вызов запустит функцию `Animal` в контексте текущего объекта, со всеми аргументами, она выполнится и запишет в `this` всё, что нужно. +Такой вызов запустит функцию `Animal` в контексте текущего объекта, со всеми аргументами, она выполнится и запишет в `this` всё, что нужно. Здесь можно было бы использовать и `Animal.call(this, name)`, но `apply` надёжнее, так как работает с любым количеством аргументов. -## Переопределение метода +## Переопределение метода -Итак, `Rabbit` наследует `Animal`. Теперь если какого-то метода нет в `Rabbit.prototype` -- он будет взят из `Animal.prototype`. +Итак, `Rabbit` наследует `Animal`. Теперь если какого-то метода нет в `Rabbit.prototype` -- он будет взят из `Animal.prototype`. В `Rabbit` может понадобиться задать какие-то методы, которые у родителя уже есть. Например, кролики бегают не так, как остальные животные, поэтому переопределим метод `run()`: @@ -225,8 +219,7 @@ Rabbit.prototype.run = function(speed) { Вызов `rabbit.run()` теперь будет брать `run` из своего прототипа: - - +![](class-inheritance-rabbit-run-animal.png) ### Вызов метода родителя внутри своего @@ -235,7 +228,6 @@ Rabbit.prototype.run = function(speed) { Для вызова метода родителя можно обратиться к нему напрямую, взяв из прототипа: ```js -//+ run Rabbit.prototype.run = function() { *!* // вызвать метод родителя, передав ему текущие аргументы @@ -245,50 +237,40 @@ Rabbit.prototype.run = function(speed) { } ``` -Обратите внимание на вызов через `apply` и явное указание контекста. +Обратите внимание на вызов через `apply` и явное указание контекста. Если вызвать просто `Animal.prototype.run()`, то в качестве `this` функция `run` получит `Animal.prototype`, а это неверно, нужен текущий объект. - ## Итого -
      -
    • Для наследования нужно, чтобы "склад методов потомка" (`Child.prototype`) наследовал от "склада метода родителей" (`Parent.prototype`). - -Это можно сделать при помощи `Object.create`: - -Код: - -```js -Rabbit.prototype = Object.create(Animal.prototype); -``` +- Для наследования нужно, чтобы "склад методов потомка" (`Child.prototype`) наследовал от "склада метода родителей" (`Parent.prototype`). -
    • -
    • Для того, чтобы наследник создавался так же, как и родитель, он вызывает конструктор родителя в своём контексте, используя `apply(this, arguments)`, вот так: + Это можно сделать при помощи `Object.create`: -```js -function Rabbit(...) { - Animal.apply(this, arguments); -} -``` + Код: -
    • -
    • При переопределении метода родителя в потомке, к исходному методу можно обратиться, взяв его напрямую из прототипа: + ```js + Rabbit.prototype = Object.create(Animal.prototype); + ``` +- Для того, чтобы наследник создавался так же, как и родитель, он вызывает конструктор родителя в своём контексте, используя `apply(this, arguments)`, вот так: -```js -Rabbit.prototype.run = function() { - var result = Animal.prototype.run.apply(this, ...); - // result -- результат вызова метода родителя -} -``` + ```js + function Rabbit(...) { + Animal.apply(this, arguments); + } + ``` +- При переопределении метода родителя в потомке, к исходному методу можно обратиться, взяв его напрямую из прототипа: -
    • -
    + ```js + Rabbit.prototype.run = function() { + var result = Animal.prototype.run.apply(this, ...); + // result -- результат вызова метода родителя + } + ``` Структура наследования полностью: -```js -//+ run +```js run *!* // --------- Класс-Родитель ------------ */!* @@ -352,7 +334,7 @@ function Rabbit() { } ``` -...Которой нет в прототипном подходе, потому что в процессе создания `new Rabbit` мы вовсе не обязаны вызывать конструктор родителя. Ведь методы находятся в прототипе. +...Которой нет в прототипном подходе, потому что в процессе создания `new Rabbit` мы вовсе не обязаны вызывать конструктор родителя. Ведь методы находятся в прототипе. Поэтому прототипный подход стоит предпочитать функциональному как более быстрый и универсальный. А что касается красоты синтаксиса -- она сильно лучше в новом стандарте ES6, которым можно пользоваться уже сейчас, если взять транслятор [babeljs](https://babeljs.io/). diff --git a/1-js/9-prototypes/5-class-inheritance/class-inheritance-array-object.png b/1-js/9-prototypes/5-class-inheritance/class-inheritance-array-object.png index 9bb33ca2..4c32902e 100644 Binary files a/1-js/9-prototypes/5-class-inheritance/class-inheritance-array-object.png and b/1-js/9-prototypes/5-class-inheritance/class-inheritance-array-object.png differ diff --git a/1-js/9-prototypes/5-class-inheritance/class-inheritance-array-object@2x.png b/1-js/9-prototypes/5-class-inheritance/class-inheritance-array-object@2x.png index 975c24fc..ed978f24 100644 Binary files a/1-js/9-prototypes/5-class-inheritance/class-inheritance-array-object@2x.png and b/1-js/9-prototypes/5-class-inheritance/class-inheritance-array-object@2x.png differ diff --git a/1-js/9-prototypes/5-class-inheritance/class-inheritance-rabbit-animal.png b/1-js/9-prototypes/5-class-inheritance/class-inheritance-rabbit-animal.png index e1a75f0e..dfb9938e 100644 Binary files a/1-js/9-prototypes/5-class-inheritance/class-inheritance-rabbit-animal.png and b/1-js/9-prototypes/5-class-inheritance/class-inheritance-rabbit-animal.png differ diff --git a/1-js/9-prototypes/5-class-inheritance/class-inheritance-rabbit-animal@2x.png b/1-js/9-prototypes/5-class-inheritance/class-inheritance-rabbit-animal@2x.png index ad834718..fcb852ea 100644 Binary files a/1-js/9-prototypes/5-class-inheritance/class-inheritance-rabbit-animal@2x.png and b/1-js/9-prototypes/5-class-inheritance/class-inheritance-rabbit-animal@2x.png differ diff --git a/1-js/9-prototypes/5-class-inheritance/class-inheritance-rabbit-run-animal.png b/1-js/9-prototypes/5-class-inheritance/class-inheritance-rabbit-run-animal.png index 9582a793..e73d4340 100644 Binary files a/1-js/9-prototypes/5-class-inheritance/class-inheritance-rabbit-run-animal.png and b/1-js/9-prototypes/5-class-inheritance/class-inheritance-rabbit-run-animal.png differ diff --git a/1-js/9-prototypes/5-class-inheritance/class-inheritance-rabbit-run-animal@2x.png b/1-js/9-prototypes/5-class-inheritance/class-inheritance-rabbit-run-animal@2x.png index 3316df5a..58056312 100644 Binary files a/1-js/9-prototypes/5-class-inheritance/class-inheritance-rabbit-run-animal@2x.png and b/1-js/9-prototypes/5-class-inheritance/class-inheritance-rabbit-run-animal@2x.png differ diff --git a/1-js/9-prototypes/6-instanceof/1-strange-instanceof/solution.md b/1-js/9-prototypes/6-instanceof/1-strange-instanceof/solution.md index 6bf55d43..fd0c01a2 100644 --- a/1-js/9-prototypes/6-instanceof/1-strange-instanceof/solution.md +++ b/1-js/9-prototypes/6-instanceof/1-strange-instanceof/solution.md @@ -1,4 +1,4 @@ -Да, это выглядит достаточно странно, поскольку объект `a` не создавался функцией `B`. +Да, это выглядит достаточно странно, поскольку объект `a` не создавался функцией `B`. Но методу `instanceof` на самом деле вообще не важна функция. Он смотрит на её `prototype` и сверяет его с цепочкой `__proto__` объекта. diff --git a/1-js/9-prototypes/6-instanceof/1-strange-instanceof/task.md b/1-js/9-prototypes/6-instanceof/1-strange-instanceof/task.md index a3c73f74..feae0b4f 100644 --- a/1-js/9-prototypes/6-instanceof/1-strange-instanceof/task.md +++ b/1-js/9-prototypes/6-instanceof/1-strange-instanceof/task.md @@ -1,11 +1,12 @@ -# Странное поведение instanceof +importance: 5 + +--- -[importance 5] +# Странное поведение instanceof Почему `instanceof` в коде ниже возвращает `true`, ведь объект `a` явно создан не `B()`? -```js -//+ run +```js run function A() {} function B() {} diff --git a/1-js/9-prototypes/6-instanceof/2-instanceof-result/solution.md b/1-js/9-prototypes/6-instanceof/2-instanceof-result/solution.md index 646e3aa1..115797e3 100644 --- a/1-js/9-prototypes/6-instanceof/2-instanceof-result/solution.md +++ b/1-js/9-prototypes/6-instanceof/2-instanceof-result/solution.md @@ -2,8 +2,7 @@ Он проверяет наследование с учётом цепочки прототипов. -```js -//+ run +```js run function Animal() {} function Rabbit() {} diff --git a/1-js/9-prototypes/6-instanceof/2-instanceof-result/task.md b/1-js/9-prototypes/6-instanceof/2-instanceof-result/task.md index 56c971e4..a08ac348 100644 --- a/1-js/9-prototypes/6-instanceof/2-instanceof-result/task.md +++ b/1-js/9-prototypes/6-instanceof/2-instanceof-result/task.md @@ -1,10 +1,12 @@ -# Что выведет instanceof? +importance: 5 + +--- -[importance 5] +# Что выведет instanceof? В коде ниже создаётся простейшая иерархия классов: `Animal -> Rabbit`. -Что выведет [instanceof](/instanceof)? +Что выведет [instanceof](/instanceof)? Распознает ли он `rabbit` как `Animal`, `Rabbit` и к тому же `Object`? diff --git a/1-js/9-prototypes/6-instanceof/article.md b/1-js/9-prototypes/6-instanceof/article.md index 174333cf..90fabf98 100644 --- a/1-js/9-prototypes/6-instanceof/article.md +++ b/1-js/9-prototypes/6-instanceof/article.md @@ -1,4 +1,4 @@ -# Проверка класса: "instanceof" +# Проверка класса: "instanceof" Оператор `instanceof` позволяет проверить, какому классу принадлежит объект, с учетом прототипного наследования. @@ -6,12 +6,11 @@ ## Алгоритм работы instanceof [#ref-instanceof] -Вызов `obj instanceof Constructor` возвращает `true`, если объект принадлежит классу `Constructor` или его родителям. +Вызов `obj instanceof Constructor` возвращает `true`, если объект принадлежит классу `Constructor` или классу, наследующему от него. Пример использования: -```js -//+ run +```js run function Rabbit() {} *!* @@ -27,33 +26,29 @@ alert( rabbit instanceof Rabbit ); // true, верно Массив `arr` принадлежит классу `Array`, но также и является объектом `Object`. Это верно, так как массивы наследуют от объектов: -```js -//+ run +```js run var arr = []; alert( arr instanceof Array ); // true alert( arr instanceof Object ); // true ``` -Как это часто бывает в JavaScript, здесь есть ряд тонкостей. В некоторых ситуациях, проверка может даже ошибаться! +Как это часто бывает в JavaScript, здесь есть ряд тонкостей. Проверка происходит через сравнение прототипов, поэтому в некоторых ситуациях может даже ошибаться! **Алгоритм проверки `obj instanceof Constructor`:** -
      -
    1. Получить `obj.__proto__`
    2. -
    3. Сравнить `obj.__proto__` с `Constructor.prototype`
    4. -
    5. Если не совпадает, тогда заменить `obj` на `obj.__proto__` и повторить проверку на шаге 2 до тех пор, пока либо не найдется совпадение (результат `true`), либо цепочка прототипов не закончится (результат `false`).
    6. -
    +1. Получить `obj.__proto__` +2. Сравнить `obj.__proto__` с `Constructor.prototype` +3. Если не совпадает, тогда заменить `obj` на `obj.__proto__` и повторить проверку на шаге 2 до тех пор, пока либо не найдется совпадение (результат `true`), либо цепочка прототипов не закончится (результат `false`). В проверке `rabbit instanceof Rabbit` совпадение происходит на первом же шаге этого алгоритма, так как: `rabbit.__proto__ == Rabbit.prototype`. А если рассмотреть `arr instanceof Object`, то совпадение будет найдено на следующем шаге, так как `arr.__proto__.__proto__ == Object.prototype`. -Забавно, что сама функция-констуктор не участвует в процессе проверки! Важна только цепочка прототипов для проверяемого объекта. +Забавно, что сама функция-конструктор не участвует в процессе проверки! Важна только цепочка прототипов для проверяемого объекта. Это может приводить к забавному результату и даже ошибкам в проверке при изменении `prototype`, например: -```js -//+ run +```js run // Создаём объект rabbit, как обычно function Rabbit() {} var rabbit = new Rabbit(); @@ -69,23 +64,19 @@ alert( rabbit instanceof Rabbit ); // false Стоит ли говорить, что это один из доводов для того, чтобы никогда не менять `prototype`? Так сказать, во избежание. -[warn header="Не друзья: `instanceof` и фреймы"] - +```warn header="Не друзья: `instanceof` и фреймы" Оператор `instanceof` не срабатывает, когда значение приходит из другого окна или фрейма. Например, массив, который создан в ифрейме и передан родительскому окну -- будет массивом *в том ифрейме*, но не в родительском окне. Проверка `instanceof Array` в родительском окне вернёт `false`. Вообще, у каждого окна и фрейма -- своя иерархия объектов и свой `window` . -Как правило, эта проблема возникает со встроенными объектами, в этом случае используется проверка внутреннего свойства `[[Class]]`, которое подробнее описано в главе [](/class-instanceof). -[/warn] - +Как правило, эта проблема возникает со встроенными объектами, в этом случае используется проверка внутреннего свойства `[[Class]]`, которое подробнее описано в главе . +``` ## Итого -
      -
    • Оператор `obj instanceof Func` проверяет тот факт, что `obj` является результатом вызова `new Func`. Он учитывает цепочку `__proto__`, поэтому наследование поддерживается.
    • -
    • Оператор `instanceof` не сможет проверить тип значения, если объект создан в одном окне/фрейме, а проверяется в другом. Это потому, что в каждом окне -- своя иерархия объектов. Для точной проверки типов встроенных объектов можно использовать свойство `[[Class]]`.
    • -
    +- Оператор `obj instanceof Func` проверяет тот факт, что `obj` является результатом вызова `new Func`. Он учитывает цепочку `__proto__`, поэтому наследование поддерживается. +- Оператор `instanceof` не сможет проверить тип значения, если объект создан в одном окне/фрейме, а проверяется в другом. Это потому, что в каждом окне -- своя иерархия объектов. Для точной проверки типов встроенных объектов можно использовать свойство `[[Class]]`. Оператор `instanceof` особенно востребован в случаях, когда мы работаем с иерархиями классов. Это наилучший способ проверить принадлежность тому или иному классу с учётом наследования. diff --git a/1-js/9-prototypes/7-oop-errors/1-format-error/solution.md b/1-js/9-prototypes/7-oop-errors/1-format-error/solution.md index 1532bd3c..ba8c9f46 100644 --- a/1-js/9-prototypes/7-oop-errors/1-format-error/solution.md +++ b/1-js/9-prototypes/7-oop-errors/1-format-error/solution.md @@ -1,5 +1,4 @@ -```js -//+ run +```js run function FormatError(message) { this.name = "FormatError"; diff --git a/1-js/9-prototypes/7-oop-errors/1-format-error/task.md b/1-js/9-prototypes/7-oop-errors/1-format-error/task.md index bd78ad35..d405227f 100644 --- a/1-js/9-prototypes/7-oop-errors/1-format-error/task.md +++ b/1-js/9-prototypes/7-oop-errors/1-format-error/task.md @@ -1,6 +1,8 @@ -# Унаследуйте от SyntaxError +importance: 5 + +--- -[importance 5] +# Унаследуйте от SyntaxError Создайте ошибку `FormatError`, которая будет наследовать от встроенного класса `SyntaxError`. @@ -15,3 +17,4 @@ alert( err.stack ); // стек на момент генерации ошибк alert( err instanceof SyntaxError ); // true ``` + diff --git a/1-js/9-prototypes/7-oop-errors/article.md b/1-js/9-prototypes/7-oop-errors/article.md index 13fd9715..79a01ece 100644 --- a/1-js/9-prototypes/7-oop-errors/article.md +++ b/1-js/9-prototypes/7-oop-errors/article.md @@ -1,6 +1,6 @@ # Свои ошибки, наследование от Error -Когда мы работаем с внешними данными, возможны самые разные ошибки. +Когда мы работаем с внешними данными, возможны самые разные ошибки. Если приложение сложное, то ошибки естественным образом укладываются в иерархию, разобраться в которой помогает `instanceof`. @@ -8,11 +8,11 @@ Для примера создадим функцию `readUser(json)`, которая будет разбирать JSON с данными посетителя. Мы его получаем с сервера -- может, нашего, а может -- чужого, в общем -- желательно проверить на ошибки. А может, это даже и не JSON, а какие-то другие данные -- не важно, для наглядности поработаем с JSON. -Пример `json` на входе в функцию: `{ "name": "Вася", "age": 30 }`. +Пример `json` на входе в функцию: `{ "name": "Вася", "age": 30 }`. В процессе работы `readUser` возможны различные ошибки. Одна -- очевидно, `SyntaxError` -- если передан некорректный JSON. -Но могут быть и другие, например `PropertyError` -- эта ошибка будет возникать, если в прочитанном объекте нет свойства `name` или `age`. +Но могут быть и другие, например `PropertyError` -- эта ошибка будет возникать, если в прочитанном объекте нет свойства `name` или `age`. Реализуем класс `PropertyError`: @@ -37,54 +37,50 @@ PropertyError.prototype = Object.create(Error.prototype); В этом коде вы можете видеть ряд важных деталей, важных именно для ошибок: -
    -
    `name` -- имя ошибки.
    -
    Должно совпадать с именем функции.
    -
    `message` -- сообщение об ошибке.
    -
    Несмотря на то, что `PropertyError` наследует от `Error` (последняя строка), конструктор у неё немного другой. Он принимает не сообщение об ошибке, а название свойства `property`, ну а сообщение генерируется из него. +`name` -- имя ошибки. +: Должно совпадать с именем функции. -В результате в объекте ошибки есть как стандартное свойство `message`, так и более точное `property`. +`message` -- сообщение об ошибке. +: Несмотря на то, что `PropertyError` наследует от `Error` (последняя строка), конструктор у неё немного другой. Он принимает не сообщение об ошибке, а название свойства `property`, ну а сообщение генерируется из него. -Это частая практика -- добавлять в объект ошибки свойства, которых нет в базовых объектах `Error`, более подробно описывающие ситуацию для данного класса ошибок.
    -
    `stack` -- стек вызовов, которые в итоге привели к ошибке.
    -
    У встроенных объектов `Error` это свойство есть автоматически, вот к примеру: -```js -//+ run -function f() { - alert( new Error().stack ); -} + В результате в объекте ошибки есть как стандартное свойство `message`, так и более точное `property`. -f(); // выведет список вложенных вызовов, с номерами строк, где они были сделаны -``` + Это частая практика -- добавлять в объект ошибки свойства, которых нет в базовых объектах `Error`, более подробно описывающие ситуацию для данного класса ошибок. -Если же объект ошибки делаем мы, то "по умолчанию" у него такого свойства у него не будет. Нам нужно как-то самим узнавать последовательность вложенных вызовов на текущий момент. Однако удобного способа сделать это в JavaScript нет, поэтому мы поступаем хитро и копируем его из нового объекта `new Error`, который генерируем тут же. +`stack` -- стек вызовов, которые в итоге привели к ошибке. +: У встроенных объектов `Error` это свойство есть автоматически, вот к примеру: + ```js run + function f() { + alert( new Error().stack ); + } -В V8 (Chrome, Opera, Node.JS) есть нестандартное расширение [Error.captureStackTrace](https://code.google.com/p/v8-wiki/wiki/JavaScriptStackTraceApi), которое позволяет стек получать. + f(); // выведет список вложенных вызовов, с номерами строк, где они были сделаны + ``` -Это делает строка из кода выше: -```js -Error.captureStackTrace(this, PropertyError); -``` + Если же объект ошибки делаем мы, то "по умолчанию" такого свойства у него не будет. Нам нужно как-то самим узнавать последовательность вложенных вызовов на текущий момент. Однако удобного способа сделать это в JavaScript нет, поэтому мы поступаем хитро и копируем его из нового объекта `new Error`, который генерируем тут же. -Такой вызов записывает в объект `this` (текущий объект ошибки) стек вызовов, ну а второй аргумент -- вообще не обязателен, но если есть, то говорит, что при генерации стека нужно на этой функции остановиться. В результате в стеке будет информация о цепочке вложенных вызовов вплоть до вызова `PropertyError`. + В V8 (Chrome, Opera, Node.JS) есть нестандартное расширение [Error.captureStackTrace](https://github.com/v8/v8/wiki/Stack-Trace-API#stack-trace-collection-for-custom-exceptions), которое позволяет получить стек. -То есть, будет последовательность вызовов до генерации ошибки, но не включая код самого конструктора ошибки, который, как правило, не интересен. Такое поведение максимально соответствует встроенным ошибкам JavaScript. -
    -
    + Это делает строка из кода выше: + ```js + Error.captureStackTrace(this, PropertyError); + ``` + + Такой вызов записывает в объект `this` (текущий объект ошибки) стек вызовов, ну а второй аргумент -- вообще не обязателен, но если есть, то говорит, что при генерации стека нужно на этой функции остановиться. В результате в стеке будет информация о цепочке вложенных вызовов вплоть до вызова `PropertyError`. -[smart header="Конструктор родителя здесь не обязателен"] -Обычно, когда мы наследуем, то вызываем конструктор родителя. В данном случае вызов выглядит как `Error.call(this, message)`. + То есть, будет последовательность вызовов до генерации ошибки, но не включая код самого конструктора ошибки, который, как правило, не интересен. Такое поведение максимально соответствует встроенным ошибкам JavaScript. -Строго говоря, этот вызов здесь не обязателен. Встроенный конструктор `Error` ничего полезного не делает, даже свойство `this.message` (не говоря уже об `name` и `stack`) не назначает. Единственный возможный смысл его вызова -- он ставит специальное внутреннее свойство `[[ErrorData]]`, которое выводится в `toString` и позволяет увидить, что это ошибка. Поэтому по стандарту вызывать конструктор `Error` при наследовании в таких случаях рекомендовано. -[/smart] +```smart header="Конструктор родителя здесь не обязателен" +Обычно, когда мы наследуем, то вызываем конструктор родителя. В данном случае вызов выглядит как `Error.call(this, message)`. +Строго говоря, этот вызов здесь не обязателен. Встроенный конструктор `Error` ничего полезного не делает, даже свойство `this.message` (не говоря уже о `name` и `stack`) не назначает. Единственный возможный смысл его вызова -- он ставит специальное внутреннее свойство `[[ErrorData]]`, которое выводится в `toString` и позволяет увидеть, что это ошибка. Поэтому по стандарту вызывать конструктор `Error` при наследовании в таких случаях рекомендовано. +``` ## instanceof + try..catch = ♡ Давайте теперь используем наш новый класс для `readUser`: -```js -//+ run +```js run *!* // Объявление */!* @@ -152,9 +148,9 @@ try { ## Дальнейшее наследование -`PropertyError` -- это просто общего вида ошибка в свойстве. Создадим ошибку `PropertyRequiredError`, которая означает, что свойства нет. +`PropertyError` -- это просто общего вида ошибка в свойстве. Создадим ошибку `PropertyRequiredError`, которая означает, что свойства нет. -Эт подвид `PropertyError`, так что унаследуем он неё. Общий вид конструктора-наследника -- стандартный: +Это подвид `PropertyError`, так что унаследуем от неё. Общий вид конструктора-наследника -- стандартный: ```js function PropertyRequiredError(property) { @@ -168,7 +164,7 @@ function PropertyRequiredError(property) { Если так поступить, то свойство `this.name` будет некорректным, да и `Error.captureStackTrace` тоже получит неправильную функцию вторым параметром. -Можно ли как-то поправить конструктор родителя, чтобы от него было проще наследовать? +Можно ли как-то поправить конструктор родителя, чтобы от него было проще наследовать? Для этого нужно убрать из него упоминания о конкретном классе `PropertyError`, чтобы сделать код универсальным. Частично -- это возможно. Как мы помним, существует свойство `constructor`, которое есть в `prototype` по умолчанию, и которое мы можем намеренно сохранить при наследовании. @@ -222,10 +218,8 @@ alert( err instanceof PropertyError ); // true ## Итого -
      -
    • Чтобы наследовать от ошибок `Error`, нужно самостоятельно позаботиться о `name`, `message` и `stack`.
    • -
    • Благодаря тому, что `instanceof` поддерживает наследование, удобно организуются проверки на нужный тип. В иерархию ошибок можно в любой момент добавить новые классы, с понятным кодом и предсказуемым поведением.
    • -
    +- Чтобы наследовать от ошибок `Error`, нужно самостоятельно позаботиться о `name`, `message` и `stack`. +- Благодаря тому, что `instanceof` поддерживает наследование, удобно организуются проверки на нужный тип. В иерархию ошибок можно в любой момент добавить новые классы, с понятным кодом и предсказуемым поведением. Чтобы создавать наследники от `Error` было проще, можно создать класс `CustomError`, записать в него универсальный код, наподобие `PropertyError` и далее наследовать уже от него: @@ -280,6 +274,7 @@ var err = new PropertyRequiredError("age"); // пройдёт проверку alert( err instanceof PropertyRequiredError ); // true alert( err instanceof PropertyError ); // true -alert( err isntanceof CustomError ); // true -alert( err isntanceof Error ); // true +alert( err instanceof CustomError ); // true +alert( err instanceof Error ); // true ``` + diff --git a/1-js/9-prototypes/8-mixins/article.md b/1-js/9-prototypes/8-mixins/article.md index a87d1bf3..7eccfc6e 100644 --- a/1-js/9-prototypes/8-mixins/article.md +++ b/1-js/9-prototypes/8-mixins/article.md @@ -2,7 +2,7 @@ В JavaScript невозможно унаследовать от двух и более объектов. Ссылка `__proto__` -- только одна. -Но потребность такая существует -- к примеру, мы написали код, релизующий методы работы с шаблонизатором или методы по обмену событиями, и хочется легко и непринуждённо добавлять эти возможности к любому классу. +Но потребность такая существует -- к примеру, мы написали код, реализующий методы работы с шаблонизатором или методы по обмену событиями, и хочется легко и непринуждённо добавлять эти возможности к любому классу. Обычно это делают через примеси. @@ -16,8 +16,7 @@ Например: -```js -//+ run +```js run *!* // примесь */!* @@ -48,7 +47,6 @@ new User("Вася").sayHi(); // Привет Вася Если какие-то из методов примеси не нужны -- их можно перезаписать своими после копирования. - ## Примесь для событий Теперь пример из реальной жизни. @@ -72,7 +70,7 @@ var eventMixin = { * Подписка на событие * Использование: * menu.on('select', function(item) { ... } - */ + */ on: function(eventName, handler) { if (!this._eventHandlers) this._eventHandlers = {}; if (!this._eventHandlers[eventName]) { @@ -116,11 +114,10 @@ var eventMixin = { ``` Здесь есть три метода: -
      -
    1. `.on(имя события, функция)` -- назначает функцию к выполнению при наступлении события с данным именем. Такие функции хранятся в защищённом свойстве объекта `_eventHandlers`.
    2. -
    3. `.off(имя события, функция)` -- удаляет функцию из списка предназначенных к выполнению.
    4. -
    5. `.trigger(имя события, аргументы)` -- генерирует событие, при этом вызываются все назначенные на него функции, и им передаются аргументы.
    6. -
    + +1. `.on(имя события, функция)` -- назначает функцию к выполнению при наступлении события с данным именем. Такие функции хранятся в защищённом свойстве объекта `_eventHandlers`. +2. `.off(имя события, функция)` -- удаляет функцию из списка предназначенных к выполнению. +3. `.trigger(имя события, аргументы)` -- генерирует событие, при этом вызываются все назначенные на него функции, и им передаются аргументы. Использование: @@ -155,16 +152,14 @@ menu.on("select", function(value) { menu.choose("123"); ``` -...То есть, смысл событий -- обычно в том, что объект, в процессе своей деятельности, внутри себя (`this.trigger`) генерирует уведомления, на которые внешний код через `menu.on(...)` может быть подписан. И узнавать из них ценную информцию о происходящем, например -- что выбран некий пункт меню. +...То есть, смысл событий -- обычно в том, что объект, в процессе своей деятельности, внутри себя (`this.trigger`) генерирует уведомления, на которые внешний код через `menu.on(...)` может быть подписан. И узнавать из них ценную информацию о происходящем, например -- что выбран некий пункт меню. -Один раз написав методы `on/off/trigger` в примеси, мы затем можем использовать их во множестве прототипов. +Один раз написав методы `on/off/trigger` в примеси, мы затем можем использовать их во множестве прототипов. ## Итого -
      -
    • Примесь -- объект, содержащий методы и свойства для реализации конкретного функционала. -Возможны вариации этого приёма проектирования. Например, примесь может предусматривать конструктор, который должен запускаться в конструкторе объекта. Но как правило просто набора методов хватает.
    • -
    • Для добавления примеси в класс -- её просто "подмешивают" в прототип.
    • -
    • "Подмешать" можно сколько угодно примесей, но если имена методов в разных примесях совпадают, то возможны конфликты. Их уже разрешать -- разработчику. Например, можно заменить конфликтующий метод на свой, который будет решать несколько задач сразу. Конфликты при грамотно оформленных примесях возникают редко.
    - +- Примесь -- объект, содержащий методы и свойства для реализации конкретного функционала. +Возможны вариации этого приёма проектирования. Например, примесь может предусматривать конструктор, который должен запускаться в конструкторе объекта. Но как правило просто набора методов хватает. +- Для добавления примеси в класс -- её просто "подмешивают" в прототип. +- "Подмешать" можно сколько угодно примесей, но если имена методов в разных примесях совпадают, то возможны конфликты. Их уже разрешать -- разработчику. Например, можно заменить конфликтующий метод на свой, который будет решать несколько задач сразу. Конфликты при грамотно оформленных примесях возникают редко. diff --git a/10-regular-expressions-javascript/1-regexp-introduction/article.md b/10-regular-expressions-javascript/1-regexp-introduction/article.md index 3fb0195a..9fa5304d 100644 --- a/10-regular-expressions-javascript/1-regexp-introduction/article.md +++ b/10-regular-expressions-javascript/1-regexp-introduction/article.md @@ -1,8 +1,9 @@ # Паттерны и флаги -Регулярные выражения –- мощное средство поиска и замены в строке. +Регулярные выражения -- мощное средство поиска и замены в строке. В JavaScript регулярные выражения реализованы отдельным объектом `RegExp` и интегрированы в методы строк. + [cut] ## Регэкспы @@ -15,14 +16,14 @@ var regexp = new RegExp("шаблон", "флаги"); ``` -Как правило, используют более короткую запись: шаблон внутри слешей `"/"`: +Как правило, используют более короткую запись (шаблон внутри слешей `"/"`): ```js var regexp = /шаблон/; // без флагов var regexp = /шаблон/gmi; // с флагами gmi (изучим их дальше) ``` -Слэши `"/"` говорят JavaScript о том, что это регулярное выражение. Они играют здесь ту же роль, что и кавычки для обозначения строк. +Слеши `"/"` говорят JavaScript о том, что это регулярное выражение. Они играют здесь ту же роль, что и кавычки для обозначения строк. ## Использование @@ -30,8 +31,7 @@ var regexp = /шаблон/gmi; // с флагами gmi (изучим их да В простейшем случае, если флагов и специальных символов нет, поиск по паттерну -- то же самое, что и обычный поиск подстроки: -```js -//+ run +```js run var str = "Я люблю JavaScript!"; // будем искать в этой строке var regexp = /лю/; @@ -40,65 +40,59 @@ alert( str.search(regexp) ); // 2 Сравните с обычным поиском: -```js -//+ run +```js run var str = "Я люблю JavaScript!"; var substr = "лю"; alert( str.indexOf(substr) ); // 2 ``` -Как видим, то же самое, разве что для регэкспа использован метод [search](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/String/search) -- он как раз работает с регулярными выражениями, а для подстроки -- [indexOf](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/String/indexOf). +Как видим, то же самое, разве что для регэкспа использован метод [search](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/String/search) -- он как раз работает с регулярными выражениями, а для подстроки -- [indexOf](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/String/indexOf). Но это соответствие лишь кажущееся. Очень скоро мы усложним регулярные выражения, и тогда увидим, что они гораздо мощнее. -[smart header="Цветовые обозначения"] +```smart header="Цветовые обозначения" Здесь и далее в тексте используется следующая цветовая схема: -
      -
    • регэксп (регулярное выражение) - красный
    • -
    • строка - синий
    • -
    • результат - зеленый
    • -
    -[/smart] + +- регэксп (регулярное выражение) - `pattern:красный` +- строка - `subject:синий` +- результат - `match:зеленый` +``` ## Флаги -Регулярные выражения могут иметь флаги, которые влияют на поиск. +Регулярные выражения могут иметь флаги, которые влияют на поиск. В JavaScript их всего три: -
    -
    `i`
    -
    Если этот флаг есть, то регэксп ищет независимо от регистра, то есть не различает между `А` и `а`.
    -
    `g`
    -
    Если этот флаг есть, то регэксп ищет все совпадения, иначе -- только первое.
    -
    `m`
    -
    Многострочный режим.
    -
    +`i` +: Если этот флаг есть, то регэксп ищет независимо от регистра, то есть не различает между `А` и `а`. -Самый простой для понимания из этих флагов -- безусловно, `i`. +`g` +: Если этот флаг есть, то регэксп ищет все совпадения, иначе -- только первое. + +`m` +: Многострочный режим. + +Самый простой для понимания из этих флагов -- безусловно, `i`. Пример его использования: -```js -//+ run +```js run var str = "Я люблю JavaScript!"; // будем искать в этой строке - + alert( str.search( *!*/ЛЮ/*/!* ) ); // -1 alert( str.search( *!*/ЛЮ/i*/!* ) ); // 2 ``` -
      -
    1. С регом /ЛЮ/ вызов вернул `-1`, что означает "не найдено" (как и в `indexOf`),
    2. -
    3. С регом /ЛЮ/i вызов нашёл совпадение на позиции 2, так как стоит флаг `i`, а значит "лю" тоже подходит.
    4. -
    +1. С регом `pattern:/ЛЮ/` вызов вернул `-1`, что означает "не найдено" (как и в `indexOf`), +2. С регом `pattern:/ЛЮ/i` вызов нашёл совпадение на позиции 2, так как стоит флаг `i`, а значит "лю" тоже подходит. Другие флаги мы рассмотрим в последующих главах. ## Итого -
      -
    • Регулярное выражение состоит из шаблона и необязательных флагов `g`, `i` и `m`.
    • -
    • Поиск по регулярному выражению без флагов и спец. символов, которые мы изучим далее -- это то же самое, что и обычный поиск подстроки в строке. Но флаги и спец. символы, как мы увидим далее, могут сделать его гораздо мощнее.
    • -
    • Метод строки `str.search(regexp)` возвращает индекс, на котором найдено совпадение.
    • -
    +- Регулярное выражение состоит из шаблона и необязательных флагов `g`, `i` и `m`. +- Поиск по регулярному выражению без флагов и спец. символов, которые мы изучим далее -- это то же самое, что и обычный поиск подстроки в строке. Но флаги и спец. символы, как мы увидим далее, могут сделать его гораздо мощнее. +- Метод строки `str.search(regexp)` возвращает индекс, на котором найдено совпадение. + diff --git a/10-regular-expressions-javascript/10-regexp-ahchors/1-start-end/solution.md b/10-regular-expressions-javascript/10-regexp-ahchors/1-start-end/solution.md index e5118b47..3be0d244 100644 --- a/10-regular-expressions-javascript/10-regexp-ahchors/1-start-end/solution.md +++ b/10-regular-expressions-javascript/10-regexp-ahchors/1-start-end/solution.md @@ -1,6 +1,6 @@ Нам нужна строка, которая начинается -- и тут же кончается. То есть, пустая. -Или, если быть ближе к механике регэкспов, то движок сначала будет искать в тексте начальную позицию ``pattern`^`, а как только найдёт её -- будет ожидать конечной ``pattern`$`. +Или, если быть ближе к механике регэкспов, то движок сначала будет искать в тексте начальную позицию `pattern:^`, а как только найдёт её -- будет ожидать конечной `pattern:$`. -Заметим, что и ``pattern`^` и ``pattern`$` не требуют наличия символов. Это -- проверки. В пустой строке движок сначала проверит первую, а потом -- вторую -- и зафиксирует совпадение. \ No newline at end of file +Заметим, что и `pattern:^` и `pattern:$` не требуют наличия символов. Это -- проверки. В пустой строке движок сначала проверит первую, а потом -- вторую -- и зафиксирует совпадение. \ No newline at end of file diff --git a/10-regular-expressions-javascript/10-regexp-ahchors/1-start-end/task.md b/10-regular-expressions-javascript/10-regexp-ahchors/1-start-end/task.md index f5df587e..4d92d9fe 100644 --- a/10-regular-expressions-javascript/10-regexp-ahchors/1-start-end/task.md +++ b/10-regular-expressions-javascript/10-regexp-ahchors/1-start-end/task.md @@ -1,4 +1,4 @@ # Регэксп ^$ -Предложите строку, которая подойдёт под регулярное выражение ``pattern`^$`. +Предложите строку, которая подойдёт под регулярное выражение `pattern:^$`. diff --git a/10-regular-expressions-javascript/10-regexp-ahchors/2-test-mac/solution.md b/10-regular-expressions-javascript/10-regexp-ahchors/2-test-mac/solution.md index 5a91a896..be40b372 100644 --- a/10-regular-expressions-javascript/10-regexp-ahchors/2-test-mac/solution.md +++ b/10-regular-expressions-javascript/10-regexp-ahchors/2-test-mac/solution.md @@ -1,15 +1,13 @@ -Двузначное шестнадцатиричное число -- это ``pattern`[0-9a-f]{2}` (с учётом флага ``pattern`/i`). +Двузначное шестнадцатиричное число -- это `pattern:[0-9a-f]{2}` (с учётом флага `pattern:/i`). -Нам нужно одно такое число, и за ним ещё 5 с двоеточиями перед ними: ``pattern`[0-9a-f]{2}(:[0-9a-f]{2}){5}` +Нам нужно одно такое число, и за ним ещё 5 с двоеточиями перед ними: `pattern:[0-9a-f]{2}(:[0-9a-f]{2}){5}` -И, наконец, совпадение должно начинаться в начале строки и заканчиваться -- в конце. То есть, строка целиком должна подходить под шаблон. Для этого обернём шаблон в ``pattern`^...$`. +И, наконец, совпадение должно начинаться в начале строки и заканчиваться -- в конце. То есть, строка целиком должна подходить под шаблон. Для этого обернём шаблон в `pattern:^...$`. Итого, в действии: - -```js -//+ run -var re = /^[0-9a-fA-F]{2}(:[0-9a-fA-F]{2}){5}$/i; +```js run +var re = /^[0-9a-f]{2}(:[0-9a-f]{2}){5}$/i; alert( re.test('01:32:54:67:89:AB') ); // true @@ -19,3 +17,4 @@ alert( re.test('01:32:54:67:89') ); // false (5 чисел, а не 6) alert( re.test('01:32:54:67:89:ZZ') ) // false (ZZ в конце) ``` + diff --git a/10-regular-expressions-javascript/10-regexp-ahchors/2-test-mac/task.md b/10-regular-expressions-javascript/10-regexp-ahchors/2-test-mac/task.md index fecf7dd0..00628147 100644 --- a/10-regular-expressions-javascript/10-regexp-ahchors/2-test-mac/task.md +++ b/10-regular-expressions-javascript/10-regexp-ahchors/2-test-mac/task.md @@ -1,8 +1,8 @@ # Проверьте MAC-адрес -MAC-адрес сетевого интерфейса состоит из шести двузначиных шестандцатиричных чисел, разделённых двоеточием. +MAC-адрес сетевого интерфейса состоит из шести двузначных шестнадцатеричных чисел, разделённых двоеточием. -Например: ``subject`'01:32:54:67:89:AB'`. +Например: `subject:'01:32:54:67:89:AB'`. Напишите регулярное выражение, которое по строке проверяет, является ли она корректным MAC-адресом. @@ -18,3 +18,4 @@ alert( re.test('01:32:54:67:89') ); // false (5 чисел, а не 6) alert( re.test('01:32:54:67:89:ZZ') ) // false (ZZ в конце) ``` + diff --git a/10-regular-expressions-javascript/10-regexp-ahchors/article.md b/10-regular-expressions-javascript/10-regexp-ahchors/article.md index 16abf01c..41ac5c90 100644 --- a/10-regular-expressions-javascript/10-regexp-ahchors/article.md +++ b/10-regular-expressions-javascript/10-regexp-ahchors/article.md @@ -1,9 +1,10 @@ # Начало строки ^ и конец $ -Знак каретки '^' и доллара '$' имеют в регулярном выражении особый смысл. Их называют "якорями" (anchor - англ.). +Знак каретки `pattern:'^'` и доллара `pattern:'$'` имеют в регулярном выражении особый смысл. Их называют "якорями" (anchor - англ.). + [cut] -Каретка ^ совпадает в начале текста, а доллар $ -- в конце. +Каретка `pattern:^` совпадает в начале текста, а доллар `pattern:$` -- в конце. **Якоря являются не символами, а проверками.** @@ -11,30 +12,27 @@ А якоря -- не такие. Когда поиск ходит до якоря -- он проверяет, есть ли соответствие, если есть -- продолжает идти по шаблону, не прибавляя ничего к результату. -Каретку ^ обычно используют, чтобы указать, что регулярное выражение необходимо проверить именно с начала текста. +Каретку `pattern:^` обычно используют, чтобы указать, что регулярное выражение необходимо проверить именно с начала текста. Например, без каретки найдёт все числа: -```js -//+ run +```js run var str = '100500 попугаев съели 500100 бананов!'; alert( str.match(/\d+/ig) ); // 100500, 500100 (нашло все числа) ``` А с кареткой -- только первое: -```js -//+ run +```js run var str = '100500 попугаев съели 500100 бананов!'; alert( str.match(/^\d+/ig) ); // 100500 (только в начале строки)*!* ``` -Знак доллара $ используют, чтобы указать, что паттерн должен заканчиваться в конце текста. +Знак доллара `pattern:$` используют, чтобы указать, что паттерн должен заканчиваться в конце текста. Аналогичный пример с долларом для поиска числа в конце: -```js -//+ run +```js run var str = '100500 попугаев съели 500100'; alert( str.match(/\d+$/ig) ); // 500100 ``` @@ -43,20 +41,18 @@ alert( str.match(/\d+$/ig) ); // 500100 Например, мы хотим проверить, что в переменной `num` хранится именно десятичная дробь. -Ей соответствует регэксп \d+\.\d+. Но простой поиск найдёт дробь в любом тексте: +Ей соответствует регэксп `pattern:\d+\.\d+`. Но простой поиск найдёт дробь в любом тексте: -```js -//+ run +```js run var num = "ля-ля 12.34"; alert( num.match(/\d+\.\d+/ig) ); // 12.34 ``` -Наша же задача -- проверить, что `num` *целиком* соответствует паттерну \d+\.\d+. +Наша же задача -- проверить, что `num` *целиком* соответствует паттерну `pattern:\d+\.\d+`. -Для этого обернём шаблон в якоря ^...$: +Для этого обернём шаблон в якоря `pattern:^...$`: -```js -//+ run +```js run var num = "ля-ля 12.34"; alert( num.match(/^\d+\.\d+$/ig) ); // null, не дробь diff --git a/10-regular-expressions-javascript/11-regexp-multiline-mode/article.md b/10-regular-expressions-javascript/11-regexp-multiline-mode/article.md index 918a85eb..f109356d 100644 --- a/10-regular-expressions-javascript/11-regexp-multiline-mode/article.md +++ b/10-regular-expressions-javascript/11-regexp-multiline-mode/article.md @@ -1,18 +1,18 @@ # Многострочный режим, флаг "m" -Многострочный режим включается, если у регэкспа есть флаг /m. +Многострочный режим включается, если у регэкспа есть флаг `pattern:/m`. + [cut] -В этом случае изменяется поведение ^ и $. +В этом случае изменяется поведение `pattern:^` и `pattern:$`. В многострочном режиме якоря означают не только начало/конец текста, но и начало/конец строки. ## Начало строки ^ -В примере ниже текст состоит из нескольких строк. Паттерн /^\d+/gm берёт число с начала каждой строки: +В примере ниже текст состоит из нескольких строк. Паттерн `pattern:/^\d+/gm` берёт число с начала каждой строки: -```js -//+ run +```js run var str = '1е место: Винни\n' + '2е место: Пятачок\n' + '33е место: Слонопотам'; @@ -22,10 +22,9 @@ alert( str.match(/^\d+/gm) ); // 1, 2, 33 */!* ``` -Обратим внимание -- без флага /m было бы найдено только первое число: +Обратим внимание -- без флага `pattern:/m` было бы найдено только первое число: -```js -//+ run +```js run var str = '1е место: Винни\n' + '2е место: Пятачок\n' + '33е место: Слонопотам'; @@ -33,18 +32,17 @@ var str = '1е место: Винни\n' + alert( str.match(/^\d+/g) ); // 1 ``` -Это потому что в обычном режиме каретка ^ -- это только начало текста, а в многострочном -- начало любой строки. +Это потому что в обычном режиме каретка `pattern:^` -- это только начало текста, а в многострочном -- начало любой строки. -Движок регулярных выражений двигается по тексту, и как только видит начало строки, начинает искать там \d+. +Движок регулярных выражений двигается по тексту, и как только видит начало строки, начинает искать там `pattern:\d+`. ## Конец строки $ -Символ доллара $ ведёт себя аналогично. +Символ доллара `pattern:$` ведёт себя аналогично. -Регулярное выражение [а-я]+$ в следующем примере находит последнее слово в каждой строке: +Регулярное выражение `pattern:[а-я]+$` в следующем примере находит последнее слово в каждой строке: -```js -//+ run +```js run var str = '1е место: Винни\n' + '2е место: Пятачок\n' + '33е место: Слонопотам'; @@ -52,38 +50,36 @@ var str = '1е место: Винни\n' + alert( str.match(/[а-я]+$/gim) ); // Винни,Пятачок,Слонопотам ``` -Без флага m якорь $ обозначал бы конец всего текста, и было бы найдено только последнее слово. +Без флага `pattern:m` якорь `pattern:$` обозначал бы конец всего текста, и было бы найдено только последнее слово. -[smart header="Якорь `$` против `\n`"] +````smart header="Якорь `$` против `\n`" Для того, чтобы найти конец строки, можно использовать не только `$`, но и символ `\n`. Но, в отличие от `$`, символ `\n` во-первых берёт символ в результат, а во-вторых -- не совпадает в конце текста (если, конечно, последний символ -- не конец строки). -Посмотрим, что будет с примером выше, если вместо [а-я]+$ использовать [а-я]+\n: +Посмотрим, что будет с примером выше, если вместо `pattern:[а-я]+$` использовать `pattern:[а-я]+\n`: -```js -//+ run +```js run var str = '1е место: Винни\n' + '2е место: Пятачок\n' + '33е место: Слонопотам'; -alert( str.match(/[а-я]+\n/gim) ); +alert( str.match(/[а-я]+\n/gim) ); /* Винни ,Пятачок */ ``` -Всего два результата: Винни\n (с символом перевода строки) и Пятачок\n. Последнее слово "Слонопотам" здесь не даёт совпадения, так как после него нет перевода строки. -[/smart] +Всего два результата: `match:Винни\n` (с символом перевода строки) и `match:Пятачок\n`. Последнее слово "Слонопотам" здесь не даёт совпадения, так как после него нет перевода строки. +```` ## Итого В мультистрочном режиме: -
      -
    • Символ `^` означает начало строки.
    • -
    • Символ `$` означает конец строки.
    • -
    + +- Символ `^` означает начало строки. +- Символ `$` означает конец строки. Оба символа являются проверками, они не добавляют ничего к результату. Про них также говорят, что "они имеют нулевую длину". diff --git a/10-regular-expressions-javascript/13-regexp-infinite-backtracking-problem/article.md b/10-regular-expressions-javascript/13-regexp-infinite-backtracking-problem/article.md index 582c1066..d5e42afe 100644 --- a/10-regular-expressions-javascript/13-regexp-infinite-backtracking-problem/article.md +++ b/10-regular-expressions-javascript/13-regexp-infinite-backtracking-problem/article.md @@ -1,4 +1,4 @@ -# Чёрная дыра бэктрекинга +# Чёрная дыра бэктрекинга Некоторые регулярные выражения, с виду являясь простыми, могут выполняться оооочень долго, и даже "подвешивать" интерпретатор JavaScript. @@ -16,16 +16,14 @@ План изложения у нас будет таким: -
      -
    1. Сначала посмотрим на проблему в реальной ситуации.
    2. -
    3. Потом упростим реальную ситуацию до "корней" и увидим, откуда она берётся.
    4. -
    +1. Сначала посмотрим на проблему в реальной ситуации. +2. Потом упростим реальную ситуацию до "корней" и увидим, откуда она берётся. Рассмотрим, например, поиск по HTML. -Мы хотим найти теги с атрибутами, то есть совпадения вида <a href="http://github.com/..." class=doc ...>. +Мы хотим найти теги с атрибутами, то есть совпадения вида `subject:`. -Самый простой способ это сделать -- <[^>]*>. Но он же и не совсем корректный, так как тег может выглядеть так: <a test="<>" href="#">. То есть, внутри "закавыченного" атрибута может быть символ `>`. Простейший регэксп на нём остановится и найдёт <a test="<>. +Самый простой способ это сделать -- `pattern:<[^>]*>`. Но он же и не совсем корректный, так как тег может выглядеть так: `subject:`. То есть, внутри "закавыченного" атрибута может быть символ `>`. Простейший регэксп на нём остановится и найдёт `match:<тег (ключ=значение)*>. +Для того, чтобы правильно обрабатывать такие ситуации, нужно учесть их в регулярном выражении. Оно будет иметь вид `pattern:<тег (ключ=значение)*>`. -Если перевести на язык регэкспов, то: <\w+(\s*\w+=(\w+|"[^"]*")\s*)*>: -
      -
    1. <\w+ -- начало тега
    2. -
    3. (\s*\w+=(\w+|"[^"]*")\s*)* -- произвольное количество пар вида `слово=значение`, где "значение" может быть также словом \w+, либо строкой в кавычках "[^"]*".
    4. -
    +Если перевести на язык регэкспов, то: `pattern:<\w+(\s*\w+=(\w+|"[^"]*")\s*)*>`: +1. `pattern:<\w+` -- начало тега +2. `pattern:(\s*\w+=(\w+|"[^"]*")\s*)*` -- произвольное количество пар вида `слово=значение`, где "значение" может быть также словом `pattern:\w+`, либо строкой в кавычках `pattern:"[^"]*"`. Мы пока не учитываем все детали грамматики HTML, ведь строки возможны и в 'одинарных' кавычках, но на данный момент этого достаточно. Главное, что регулярное выражение получилось в меру простым и понятным. - Испытаем полученный регэксп в действии: -```js -//+ run +```js run var reg = /<\w+(\s*\w+=(\w+|"[^"]*")\s*)*>/g; var str='...
    ... ...'; @@ -58,14 +52,13 @@ var str='...... ...'; alert( str.match(reg) ); // , ``` -Отлично, всё работает! Нашло как длинный тег <a test="<>" href="#">, так и одинокий <b>. +Отлично, всё работает! Нашло как длинный тег `match:`, так и одинокий `match:`. -А теперь -- демонстрация проблемы. +А теперь -- демонстрация проблемы. Если запустить пример ниже, то он может подвесить браузер: -```js -//+ run +```js run var reg = /<\w+(\s*\w+=(\w+|"[^"]*")\s*)*>/g; var str = "/g; @@ -104,122 +95,110 @@ alert( str.match(reg) ); ## Бектрекинг -В качестве ещё более простого регулярного выражения, рассмотрим (\d+)*$. +В качестве ещё более простого регулярного выражения, рассмотрим `pattern:(\d+)*$`. В большинстве движков регэкспов, например в Chrome или IE, этот поиск выполняется очень долго (осторожно, может "подвесить" браузер): -```js -//+ run +```js run alert( '12345678901234567890123456789123456789z'.match(/(\d+)*$/) ); ``` В чём же дело, что не так с регэкспом? -Внимательный читатель, посмотрев на него, наверняка удивится, ведь он "какой-то странный". Квантификатор * здесь выглядит лишним. +Внимательный читатель, посмотрев на него, наверняка удивится, ведь он "какой-то странный". Квантификатор `pattern:*` здесь выглядит лишним. -Если хочется найти число, то с тем же успехом можно искать \d+$. +Если хочется найти число, то с тем же успехом можно искать `pattern:\d+$`. -Да, этот регэксп носит искусственный характер, но, разобравшись с ним, мы поймём и практический пример, данный выше. Причина их медленной работы одинакова. +Да, этот регэксп носит искусственный характер, но, разобравшись с ним, мы поймём и практический пример, данный выше. Причина их медленной работы одинакова. В целом, с регэкспом "всё так", синтаксис вполне допустимый. Проблема в том, как выполняется поиск по нему. -Посмотрим, что происходит при поиске в строке 123456789z: +Посмотрим, что происходит при поиске в строке `subject:123456789z`: -
      -
    1. Первым делом, движок регэкспов пытается найти \d+. Плюс + является жадным по умолчанию, так что он хватает все цифры, какие может: +1. Первым делом, движок регэкспов пытается найти `pattern:\d+`. Плюс `pattern:+` является жадным по умолчанию, так что он хватает все цифры, какие может: -``` -\d+....... -(123456789)z -``` -
    2. -
    3. Затем движок пытается применить звёздочку вокруг скобок (\d+)*, но больше цифр нет, так что звёздочка не даёт повторений. + ``` + \d+....... + (123456789)z + ``` +2. Затем движок пытается применить звёздочку вокруг скобок `pattern:(\d+)*`, но больше цифр нет, так что звёздочка не даёт повторений. -Затем в шаблоне идёт символ конца строки $, а в тексте -- символ z. + Затем в шаблоне идёт символ конца строки `pattern:$`, а в тексте -- символ `subject:z`. -``` - X -\d+........$ -(123456789)z -``` -Соответствия нет. -
    4. -
    5. Так как соответствие не найдено, то "жадный" плюс + отступает на один символ (бэктрекинг). + ``` + X + \d+........$ + (123456789)z + ``` -Теперь `\d+` -- это все цифры, за исключением последней: -``` -\d+....... -(12345678)9z -``` -
    6. -
    7. После бэктрекинга, \d+ содержит всё число, кроме последней цифры. Движок снова пытается найти совпадение, уже с новой позиции (`9`). + Соответствия нет. +3. Так как соответствие не найдено, то "жадный" плюс `pattern:+` отступает на один символ (бэктрекинг). -Звёздочка (\d+)* теперь может быть применена -- она даёт число 9: + Теперь `\d+` -- это все цифры, за исключением последней: + ``` + \d+....... + (12345678)9z + ``` +4. После бэктрекинга, `pattern:\d+` содержит всё число, кроме последней цифры. Движок снова пытается найти совпадение, уже с новой позиции (`9`). -``` - -\d+.......\d+ -(12345678)(9)z -``` -Движок пытается найти `$`, но это ему не удаётся -- на его пути опять `z`: + Звёздочка `pattern:(\d+)*` теперь может быть применена -- она даёт число `match:9`: -``` - X -\d+.......\d+ -(12345678)(9)z -``` + ``` -Так как совпадения нет, то поисковой движок отступает назад ещё раз. -
    8. -
    9. Теперь первое число \d+ будет содержать 7 цифр, а остаток строки 89 становится вторым \d+: + \d+.......\d+ + (12345678)(9)z + ``` + Движок пытается найти `$`, но это ему не удаётся -- на его пути опять `z`: -``` - X -\d+......\d+ -(1234567)(89)z -``` + ``` + X + \d+.......\d+ + (12345678)(9)z + ``` -Увы, всё ещё нет соответствия для $. + Так как совпадения нет, то поисковой движок отступает назад ещё раз. +5. Теперь первое число `pattern:\d+` будет содержать 7 цифр, а остаток строки `subject:89` становится вторым `pattern:\d+`: -Поисковой движок снова должен отступить назад. При этом последний жадный квантификатор отпускает символ. В данном случае это означает, что укорачивается второй \d+, до одного символа 8, и звёздочка забирает следующий 9. + ``` + X + \d+......\d+ + (1234567)(89)z + ``` + Увы, всё ещё нет соответствия для `pattern:$`. -``` - X -\d+......\d+\d+ -(1234567)(8)(9)z -``` -
    10. -
    11. ...И снова неудача. Второе и третье \d+ отступили по-максимуму, так что сокращается снова первое число, до 123456, а звёздочка берёт оставшееся: + Поисковой движок снова должен отступить назад. При этом последний жадный квантификатор отпускает символ. В данном случае это означает, что укорачивается второй `pattern:\d+`, до одного символа `subject:8`, и звёздочка забирает следующий `subject:9`. -``` - X -\d+.......\d+ -(123456)(789)z -``` + ``` + X + \d+......\d+\d+ + (1234567)(8)(9)z + ``` +6. ...И снова неудача. Второе и третье `pattern:\d+` отступили по-максимуму, так что сокращается снова первое число, до `subject:123456`, а звёздочка берёт оставшееся: -Снова нет совпадения. Процесс повторяется, последний жадный квантификатор + отпускает один символ (`9`): + ``` + X + \d+.......\d+ + (123456)(789)z + ``` -``` - X -\d+.....\d+ \d+ -(123456)(78)(9)z -``` -
    12. -
    13. -...И так далее. -
    14. -
    + Снова нет совпадения. Процесс повторяется, последний жадный квантификатор `pattern:+` отпускает один символ (`9`): + + ``` + X + \d+.....\d+ \d+ + (123456)(78)(9)z + ``` +7. ...И так далее. Получается, что движок регулярных выражений перебирает все комбинации из `123456789` и их подпоследовательности. А таких комбинаций очень много. На этом месте умный читатель может воскликнуть: "Во всём виноват бэктрекинг? Давайте включим ленивый режим -- и не будет никакого бэктрекинга!" -Что ж, заменим \d+ на \d+? и посмотрим (аккуратно, может подвесить браузер): +Что ж, заменим `pattern:\d+` на `pattern:\d+?` и посмотрим (аккуратно, может подвесить браузер): -```js -//+ run +```js run alert( '12345678901234567890123456789123456789z'.match(/(\d+?)*$/) ); ``` @@ -229,11 +208,11 @@ alert( '12345678901234567890123456789123456789z'.match(/(\d+?)*$/) ); Просто подумайте о том, как будет в этом случае работать поисковой движок. -Некоторые движки регулярных выражений содержат хитрые проверки и конечные автоматы, которые позволяют избежать бесконечного перебора или кардинально ускорить его, но все движки и не всегда. +Некоторые движки регулярных выражений содержат хитрые проверки и конечные автоматы, которые позволяют избежать бесконечного перебора или кардинально ускорить его, но не все движки и не всегда. -Возвращаясь к примеру выше -- при поиске <(\s*\w+=\w+\s*)*> в строке <a=b a=b a=b a=b происходит то же самое. +Возвращаясь к примеру выше -- при поиске `pattern:<(\s*\w+=\w+\s*)*>` в строке `subject:\s*\w+=\w+\s*, которая, так как в конце нет `>`, оказывается не подходящей. Движок честно отступает, пробует другую комбинацию -- и так далее. +Поиск успешно начинается, выбирается некая комбинация из `pattern:\s*\w+=\w+\s*`, которая, так как в конце нет `>`, оказывается не подходящей. Движок честно отступает, пробует другую комбинацию -- и так далее. ## Что делать? @@ -241,11 +220,11 @@ alert( '12345678901234567890123456789123456789z'.match(/(\d+?)*$/) ); Движок регулярных выражений перебирает кучу возможных вариантов скобок там, где это не нужно. -Например, в регэкспе (\d+)*$ нам (людям) очевидно, что в (\d+) откатываться не нужно. От того, что вместо одного \d+ у нас два независимых \d+\d+, ничего не изменится. +Например, в регэкспе `pattern:(\d+)*$` нам (людям) очевидно, что в `pattern:(\d+)` откатываться не нужно. От того, что вместо одного `pattern:\d+` у нас два независимых `pattern:\d+\d+`, ничего не изменится. Без разницы: -``` +``` \d+........ (123456789)z @@ -253,16 +232,16 @@ alert( '12345678901234567890123456789123456789z'.match(/(\d+?)*$/) ); (1234)(56789)z ``` -Если вернуться к более реальному примеру <(\s*\w+=\w+\s*)*> то -cам алгоритм поиска, который у нас в голове, предусматривает, что мы "просто" ищем тег, а потом пары `атрибут=значение` (сколько получится). +Если вернуться к более реальному примеру `pattern:<(\s*\w+=\w+\s*)*>` то +сам алгоритм поиска, который у нас в голове, предусматривает, что мы "просто" ищем тег, а потом пары `атрибут=значение` (сколько получится). Никакого "отката" здесь не нужно. -В современных регулярных выражениях для решения этой проблемы придумали "possessive" (сверхжадные? неоткатные? точный перевод пока не устоялся) квантификаторы, которые вообще не используют бэктрегинг. +В современных регулярных выражениях для решения этой проблемы придумали "possessive" (сверхжадные? неоткатные? точный перевод пока не устоялся) квантификаторы, которые вообще не используют бэктрегинг. То есть, они даже проще, чем "жадные" -- берут максимальное количество символов и всё. Поиск продолжается дальше. При несовпадении никакого возврата не происходит. -Это, c стороны уменьшает количество возможных результатов, но с другой стороны -- в ряде случаев очевидно, что возврат (уменьшение количество повторений квантификатора) результата не даст. А только потратит время, что как раз и доставляет проблемы. Как раз такие ситуации и описаны выше. +Это, с одной стороны, уменьшает количество возможных результатов, но, с другой стороны, в ряде случаев очевидно, что возврат (уменьшение количество повторений квантификатора) результата не даст. А только потратит время, что как раз и доставляет проблемы. Как раз такие ситуации и описаны выше. Есть и другое средство -- "атомарные скобочные группы", которые запрещают перебор внутри скобок, по сути позволяя добиваться того же, что и сверхжадные квантификаторы, @@ -270,22 +249,21 @@ cам алгоритм поиска, который у нас в голове, Однако, можно получить подобный эффект при помощи предпросмотра. Подробное описание соответствия с учётом синтаксиса сверхжадных квантификаторов и атомарных групп есть в статьях [Regex: Emulate Atomic Grouping (and Possessive Quantifiers) with LookAhead](http://instanceof.me/post/52245507631/regex-emulate-atomic-grouping-with-lookahead) и [Mimicking Atomic Groups](http://blog.stevenlevithan.com/archives/mimic-atomic-groups), здесь же мы останемся в рамках синтаксиса JavaScript. -Взятие максимального количества повторений `a+` без отката выглядит так: (?=(a+))\1. +Взятие максимального количества повторений `a+` без отката выглядит так: `pattern:(?=(a+))\1`. -То есть, иными словами, предпросмотр ?= ищет максимальное количество повторений a+, доступных с текущей позиции. А затем они "берутся в результат" обратной ссылкой \1. Дальнейший поиск -- после найденных повторений. +То есть, иными словами, предпросмотр `pattern:?=` ищет максимальное количество повторений `pattern:a+`, доступных с текущей позиции. А затем они "берутся в результат" обратной ссылкой `pattern:\1`. Дальнейший поиск -- после найденных повторений. -Откат в этой логике принципе не предусмотрен, поскольку предпросмотр "откатываться" не умеет. То есть, если предпросмотр нашёл 5 штук a+, и в результате поиск не удался, то он не будет откатываться на 4 повторения. Эта возможность в предпросмотре отсутствует, а в данном случае она как раз и не нужна. +Откат в этой логике в принципе не предусмотрен, поскольку предпросмотр "откатываться" не умеет. То есть, если предпросмотр нашёл 5 штук `pattern:a+`, и в результате поиск не удался, то он не будет откатываться на 4 повторения. Эта возможность в предпросмотре отсутствует, а в данном случае она как раз и не нужна. -Исправим регэксп для поиска тега с атрибутами <\w+(\s*\w+=(\w+|"[^"]*")\s*)*>, описанный в начале главы. Используем предпросмотр, чтобы запретить откат на меньшее количество пар `атрибут=значение`: +Исправим регэксп для поиска тега с атрибутами `pattern:<\w+(\s*\w+=(\w+|"[^"]*")\s*)*>`, описанный в начале главы. Используем предпросмотр, чтобы запретить откат на меньшее количество пар `атрибут=значение`: -```js -//+ run +```js run // регэксп для пары атрибут=значение var attr = /(\s*\w+=(\w+|"[^"]*")\s*)/ // используем его внутри регэкспа для тега var reg = new RegExp('<\\w+(?=(' + attr.source + '*))\\1>', 'g'); - + var good = '...
    ... ...'; var bad = ", alert( bad.match(reg) ); // null (нет результатов, быстро) ``` -Отлично, всё работает! Нашло как длинный тег <a test="<>" href="#">, так и одинокий <b>. - - - - - - +Отлично, всё работает! Нашло как длинный тег `match:`, так и одинокий `match:`. diff --git a/10-regular-expressions-javascript/2-regexp-methods/article.md b/10-regular-expressions-javascript/2-regexp-methods/article.md index fdf66de8..db9c31b4 100644 --- a/10-regular-expressions-javascript/2-regexp-methods/article.md +++ b/10-regular-expressions-javascript/2-regexp-methods/article.md @@ -1,6 +1,6 @@ # Методы RegExp и String -Регулярные выражения в JavaScript являются объектами класса [RegExp](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/RegExp). +Регулярные выражения в JavaScript являются объектами класса [RegExp](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/RegExp). Кроме того, методы для поиска по регулярным выражениям встроены прямо в обычные строки `String`. @@ -8,14 +8,13 @@ [cut] -## str.search(reg) +## str.search(reg) -Этот метод мы уже видели. +Этот метод мы уже видели. Он возвращает позицию первого совпадения или `-1`, если ничего не найдено. -```js -//+ run +```js run var str = "Люблю регэкспы я, но странною любовью"; alert( str.search( *!*/лю/i*/!* ) ); // 0 @@ -35,8 +34,7 @@ alert( str.search( *!*/лю/i*/!* ) ); // 0 Например: -```js -//+ run +```js run var str = "ОЙ-Ой-ой"; var result = str.match( *!*/ой/i*/!* ); @@ -46,25 +44,24 @@ alert( result.index ); // 0 (позиция) alert( result.input ); // ОЙ-Ой-ой (вся поисковая строка) ``` -У этого массива не всегда только один элемент. +У этого массива не всегда только один элемент. **Если часть шаблона обозначена скобками, то она станет отдельным элементом массива.** Например: -```js -//+ run +```js run var str = "javascript - это такой язык"; var result = str.match( *!*/JAVA(SCRIPT)/i*/!* ); -alert( result[0] ); // javascript (всё совпадение полностью) +alert( result[0] ); // javascript (всё совпадение полностью) alert( result[1] ); // script (часть совпадения, соответствующая скобкам) alert( result.index ); // 0 alert( result.input ); // javascript - это такой язык ``` -Благодаря флагу `i` поиск не обращает внимание на регистр буквы, поэтому находит javascript. При этом часть строки, соответствующая SCRIPT, выделена в отдельный элемент массива. +Благодаря флагу `i` поиск не обращает внимание на регистр буквы, поэтому находит `match:javascript`. При этом часть строки, соответствующая `pattern:SCRIPT`, выделена в отдельный элемент массива. Позже мы ещё вернёмся к скобочным выражениям, они особенно удобны для поиска с заменой. @@ -76,8 +73,7 @@ alert( result.input ); // javascript - это такой язык Например: -```js -//+ run +```js run var str = "ОЙ-Ой-ой"; var result = str.match( *!*/ой/ig*/!* ); @@ -87,8 +83,7 @@ alert( result ); // ОЙ, Ой, ой Пример со скобками: -```js -//+ run +```js run var str = "javascript - это такой язык"; var result = str.match( *!*/JAVA(SCRIPT)/gi*/!* ); @@ -102,28 +97,26 @@ alert( result.index ); // undefined Для расширенного глобального поиска, который позволит получить все позиции и, при желании, скобки, нужно использовать метод [RegExp#exec](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/RegExp/exec), которые будет рассмотрен далее. -[warn header="В случае, если совпадений не было, `match` возвращает `null`"] +````warn header="В случае, если совпадений не было, `match` возвращает `null`" Обратите внимание, это важно -- если `match` не нашёл совпадений, он возвращает не пустой массив, а именно `null`. Это важно иметь в виду, чтобы не попасть в такую ловушку: -```js -//+ run +```js run var str = "Ой-йой-йой"; // результат match не всегда массив! alert(str.match(/лю/gi).length) // ошибка! нет свойства length у null ``` -[/warn] +```` -## str.split(reg|substr, limit) +## str.split(reg|substr, limit) -Разбивает строку в массив по разделителю -- регулярному выражению `regexp` или подстроке `substr`. +Разбивает строку в массив по разделителю -- регулярному выражению `regexp` или подстроке `substr`. Обычно мы используем метод `split` со строками, вот так: -```js -//+ run +```js run alert('12-34-56'.split('-')) // [12, 34, 56] ``` @@ -131,34 +124,30 @@ alert('12-34-56'.split('-')) // [12, 34, 56] Тот же пример с регэкспом: -```js -//+ run +```js run alert('12-34-56'.split(/-/)) // [12, 34, 56] ``` -## str.replace(reg, str|func) +## str.replace(reg, str|func) Швейцарский нож для работы со строками, поиска и замены любого уровня сложности. Его простейшее применение -- поиск и замена подстроки в строке, вот так: -```js -//+ run +```js run // заменить дефис на двоеточие alert('12-34-56'.replace("-", ":")) // 12:34-56 ``` **При вызове со строкой замены `replace` всегда заменяет только первое совпадение.** -Чтобы заменить *все* совпадения, нужно использовать для поиска не строку `"-"`, а регулярное выражение /-/g, причём обязательно с флагом `g`: +Чтобы заменить *все* совпадения, нужно использовать для поиска не строку `"-"`, а регулярное выражение `pattern:/-/g`, причём обязательно с флагом `g`: -```js -//+ run +```js run // заменить дефис на двоеточие alert( '12-34-56'.replace( *!*/-/g*/!*, ":" ) ) // 12:34:56 ``` - В строке для замены можно использовать специальные символы: @@ -170,11 +159,11 @@ alert( '12-34-56'.replace( *!*/-/g*/!*, ":" ) ) // 12:34:56 - - + + - + @@ -191,15 +180,14 @@ alert( '12-34-56'.replace( *!*/-/g*/!*, ":" ) ) // 12:34:56 - +
    `$$`Вставляет `"$"`.$$Вставляет "$".
    `$&`$& Вставляет всё найденное совпадение.
    $*n* где `n` -- цифра или двузначное число, обозначает `n-ю` по счёту скобку, если считать слева-направо.где n -- цифра или двузначное число, обозначает n-ю по счёту скобку, если считать слева-направо.
    Пример использования скобок и `$1`, `$2`: -```js -//+ run +```js run var str = "Василий Пупкин"; alert(str.replace(/(Василий) (Пупкин)/, '$2, $1')) // Пупкин, Василий @@ -207,21 +195,19 @@ alert(str.replace(/(Василий) (Пупкин)/, '$2, $1')) // Пупкин, Ещё пример, с использованием `$&`: -```js -//+ run +```js run var str = "Василий Пупкин"; alert(str.replace(/Василий Пупкин/, 'Великий $&!')) // Великий Василий Пупкин! ``` -**Для ситуаций, который требуют максимально "умной" замены, в качестве второго аргумента предусмотрена функция.** +**Для ситуаций, которые требуют максимально "умной" замены, в качестве второго аргумента предусмотрена функция.** Она будет вызвана для каждого совпадения, и её результат будет вставлен как замена. Например: -```js -//+ run +```js run var i = 0; // заменить каждое вхождение "ой" на результат вызова функции @@ -232,21 +218,18 @@ alert("ОЙ-Ой-ой".replace(/ой/gi, function() { В примере выше функция просто возвращала числа по очереди, но обычно она основывается на поисковых данных. -Эта функция получает следующие аргументы: +Эта функция получает следующие аргументы: -
      -
    1. `str` -- найденное совпадение,
    2. -
    3. `p1, p2, ..., pn` -- содержимое скобок (если есть),
    4. -
    5. `offset` -- позиция, на которой найдено совпадение,
    6. -
    7. `s` -- исходная строка.
    8. -
    +1. `str` -- найденное совпадение, +2. `p1, p2, ..., pn` -- содержимое скобок (если есть), +3. `offset` -- позиция, на которой найдено совпадение, +4. `s` -- исходная строка. Если скобок в регулярном выражении нет, то у функции всегда будет ровно 3 аргумента: `replacer(str, offset, s)`. Используем это, чтобы вывести полную информацию о совпадениях: -```js -//+ run +```js run // вывести и заменить все совпадения function replacer(str, offset, s) { alert( "Найдено: " + str + " на позиции: " + offset + " в строке: " + s ); @@ -259,18 +242,19 @@ alert( 'Результат: ' + result ); // Результат: ой-ой-ой С двумя скобочными выражениями -- аргументов уже 5: -```js -//+ run +```js run function replacer(str, name, surname, offset, s) { return surname + ", " + name; } +var str = "Василий Пупкин"; + alert(str.replace(/(Василий) (Пупкин)/, replacer)) // Пупкин, Василий ``` Функция -- это самый мощный инструмент для замены, какой только может быть. Она владеет всей информацией о совпадении и имеет доступ к замыканию, поэтому может всё. -## regexp.test(str) +## regexp.test(str) Теперь переходим к методам класса `RegExp`. @@ -278,8 +262,7 @@ alert(str.replace(/(Василий) (Пупкин)/, replacer)) // Пупкин, Работает, по сути, так же, как и проверка `str.search(reg) != -1`, например: -```js -//+ run +```js run var str = "Люблю регэкспы я, но странною любовью"; // эти две проверки идентичны @@ -289,39 +272,35 @@ alert( str.search(*!*/лю/i*/!*) != -1 ) // true Пример с отрицательным результатом: -```js -//+ run +```js run var str = "Ой, цветёт калина..."; alert( *!*/javascript/i*/!*.test(str) ) // false alert( str.search(*!*/javascript/i*/!*) != -1 ) // false ``` -## regexp.exec(str) +## regexp.exec(str) Для поиска мы уже видели методы: -
      -
    • `search` -- ищет индекс
    • -
    • `match` -- если регэксп без флага `g` -- ищет совпадение с подрезультатами в скобках
    • -
    • `match` -- если регэксп с флагом `g` -- ищет все совпадения, но без скобочных групп.
    • -
    + +- `search` -- ищет индекс +- `match` -- если регэксп без флага `g` -- ищет совпадение с подрезультатами в скобках +- `match` -- если регэксп с флагом `g` -- ищет все совпадения, но без скобочных групп. Метод `regexp.exec` дополняет их. Он позволяет искать и все совпадения и скобочные группы в них. Он ведёт себя по-разному, в зависимости от того, есть ли у регэкспа флаг `g`. -
      -
    • Если флага `g` нет, то `regexp.exec(str)` ищет и возвращает первое совпадение, является полным аналогом вызова `str.match(reg)`.
    • -
    • Если флаг `g` есть, то вызов `regexp.exec` возвращает первое совпадение и *запоминает* его позицию в свойстве `regexp.lastIndex`. Последующий поиск он начнёт уже с этой позиции. Если совпадений не найдено, то сбрасывает `regexp.lastIndex` в ноль.
    • -
    +- Если флага `g` нет, то `regexp.exec(str)` ищет и возвращает первое совпадение, является полным аналогом вызова `str.match(reg)`. +- Если флаг `g` есть, то вызов `regexp.exec` возвращает первое совпадение и *запоминает* его позицию в свойстве `regexp.lastIndex`. Последующий поиск он начнёт уже с этой позиции. Если совпадений не найдено, то сбрасывает `regexp.lastIndex` в ноль. Это используют для поиска всех совпадений в цикле: -```js -//+ run +```js run var str = 'Многое по JavaScript можно найти на сайте http://javascript.ru'; var regexp = /javascript/ig; +var result; alert( "Начальное значение lastIndex: " + regexp.lastIndex ); @@ -337,55 +316,39 @@ alert( 'Конечное значение lastIndex: ' + regexp.lastIndex ); Найденные результаты последовательно помещаются в `result`, причём находятся там в том же формате, что и `match` -- с учётом скобок, со свойствами `result.index` и `result.input`. -[smart header="Поиск с нужной позиции"] +````smart header="Поиск с нужной позиции" Можно заставить `regexp.exec` искать сразу с нужной позиции, если поставить `lastIndex` вручную: -```js -//+ run +```js run var str = 'Многое по JavaScript можно найти на сайте http://javascript.ru'; var regexp = /javascript/ig; regexp.lastIndex = 40; -alert( regexp.exec(str).index ); // 49, поиск начат с 40й позиции +alert( regexp.exec(str).index ); // 49, поиск начат с 40-й позиции ``` -[/smart] +```` ## Итого, рецепты Методы становятся гораздо понятнее, если разбить их использование по задачам, которые нужны в реальной жизни. -
    -
    Для поиска только одного совпадения:
    -
    -
      -
    • Найти позицию первого совпадения -- `str.search(reg)`.
    • -
    • Найти само совпадение -- `str.match(reg)`.
    • -
    • Проверить, есть ли хоть одно совпадение -- `regexp.test(str)` или `str.search(reg) != -1`.
    • -
    • Найти совпадение с нужной позиции -- `regexp.exec(str)`, начальную позицию поиска задать в `regexp.lastIndex`.
    • -
    -
    -
    Для поиска всех совпадений:
    -
    -
      -
    • Найти массив совпадений -- `str.match(reg)`, с флагом `g`.
    • -
    • Получить все совпадения, с подробной информацией о каждом -- `regexp.exec(str)` с флагом `g`, в цикле.
    • -
    -
    +Для поиска только одного совпадения: +: - Найти позицию первого совпадения -- `str.search(reg)`. +- Найти само совпадение -- `str.match(reg)`. +- Проверить, есть ли хоть одно совпадение -- `regexp.test(str)` или `str.search(reg) != -1`. +- Найти совпадение с нужной позиции -- `regexp.exec(str)`, начальную позицию поиска задать в `regexp.lastIndex`. + +Для поиска всех совпадений: +: - Найти массив совпадений -- `str.match(reg)`, с флагом `g`. +- Получить все совпадения, с подробной информацией о каждом -- `regexp.exec(str)` с флагом `g`, в цикле. + -
    Для поиска-и-замены:
    -
    -
      -
    • Замена на другую строку или функцией -- `str.replace(reg, str|func)`
    • -
    -
    -
    Для разбивки строки на части:
    -
    -
      -
    • `str.split(str|reg)`
    • -
    -
    -
    +Для поиска-и-замены: +: - Замена на другую строку или результат функции -- `str.replace(reg, str|func)` + +Для разбивки строки на части: +: - `str.split(str|reg)` Зная эти методы, мы уже можем использовать регулярные выражения. diff --git a/10-regular-expressions-javascript/3-regexp-character-classes/1-find-time-hh-mm/solution.md b/10-regular-expressions-javascript/3-regexp-character-classes/1-find-time-hh-mm/solution.md index 1fd6b26a..95c1b51a 100644 --- a/10-regular-expressions-javascript/3-regexp-character-classes/1-find-time-hh-mm/solution.md +++ b/10-regular-expressions-javascript/3-regexp-character-classes/1-find-time-hh-mm/solution.md @@ -1,8 +1,7 @@ -Ответ: \d\d:\d\d. +Ответ: `pattern:\d\d:\d\d`. -```js -//+ run +```js run alert( "Завтрак в 09:00.".match( /\d\d:\d\d/ ) ); // 09:00 ``` diff --git a/10-regular-expressions-javascript/3-regexp-character-classes/1-find-time-hh-mm/task.md b/10-regular-expressions-javascript/3-regexp-character-classes/1-find-time-hh-mm/task.md index f92b52a5..fe9df2d5 100644 --- a/10-regular-expressions-javascript/3-regexp-character-classes/1-find-time-hh-mm/task.md +++ b/10-regular-expressions-javascript/3-regexp-character-classes/1-find-time-hh-mm/task.md @@ -2,7 +2,7 @@ Время имеет формат `часы:минуты`. И часы и минуты состоят из двух цифр, например: `09:00`. -Напишите регулярное выражение для поиска времени в строке: Завтрак в 09:00. +Напишите регулярное выражение для поиска времени в строке: `subject:Завтрак в 09:00.` -P.S. В этой задаче выражению позволительно найти и некорректное время, например `25:99`. +P.S. В этой задаче выражению позволительно найти и некорректное время, например `25:99`. diff --git a/10-regular-expressions-javascript/3-regexp-character-classes/article.md b/10-regular-expressions-javascript/3-regexp-character-classes/article.md index 26fd9277..803c9a4f 100644 --- a/10-regular-expressions-javascript/3-regexp-character-classes/article.md +++ b/10-regular-expressions-javascript/3-regexp-character-classes/article.md @@ -10,10 +10,9 @@ Например, есть класс "любая цифра". Он обозначается `\d`. Это обозначение вставляется в шаблон, и при поиске под него подходит любая цифра. -То есть, регулярное выражение /\d/ ищет ровно одну цифру: +То есть, регулярное выражение `pattern:/\d/` ищет ровно одну цифру: -```js -//+ run +```js run var str = "+7(903)-123-45-67"; var reg = /\d/; @@ -24,8 +23,7 @@ alert( str.match(reg) ); // 7 ...Ну а для поиска всех цифр достаточно добавить к регэкспу флаг `g`: -```js -//+ run +```js run var str = "+7(903)-123-45-67"; var reg = /\d/g; @@ -40,23 +38,23 @@ alert( str.match(reg) ); // массив всех совпадений: 7,9,0,3, Конечно же, есть и другие. Наиболее часто используются: -
    -
    `\d` (от английского "digit" -- "цифра")
    -
    Цифра, символ от `0` до `9`.
    -
    `\s` (от английского "space" -- "пробел")
    -
    Пробельный символ, включая табы, переводы строки и т.п.
    -
    `\w` (от английского "word" -- "слово")
    -
    Символ "слова", а точнее -- буква латинского алфавита или цифра или подчёркивание `'_'`. Не-английские буквы не являются `\w`, то есть русская буква не подходит.
    -
    -Например, \d\s\w обозначает цифру, за которой идёт пробельный символ, а затем символ слова. +`\d` (от английского "digit" -- "цифра") +: Цифра, символ от `0` до `9`. + +`\s` (от английского "space" -- "пробел") +: Пробельный символ, включая табы, переводы строки и т.п. + +`\w` (от английского "word" -- "слово") +: Символ "слова", а точнее -- буква латинского алфавита или цифра или подчёркивание `'_'`. Не-английские буквы не являются `\w`, то есть русская буква не подходит. + +Например, `pattern:\d\s\w` обозначает цифру, за которой идёт пробельный символ, а затем символ слова. Регулярное выражение может содержать одновременно и обычные символы и классы. -Например, CSS\d найдёт строку CSS, с любой цифрой после неё: +Например, `pattern:CSS\d` найдёт строку `match:CSS`, с любой цифрой после неё: -```js -//+ run +```js run var str = "Стандарт CSS4 - это здорово"; var reg = /CSS\d/ @@ -65,50 +63,44 @@ alert( str.match(reg) ); // CSS4 И много классов подряд: -```js -//+ run +```js run alert( "Я люблю HTML5!".match(/\s\w\w\w\w\d/) ); // 'HTML5' ``` Совпадение (каждому классу в регэкспе соответствует один символ результата): - +![](love-html5-classes.png) ## Граница слова \b -Граница слова \b -- это особый класс. +Граница слова `pattern:\b` -- это особый класс. Он интересен тем, что обозначает не символ, а границу между символами. -Например, \bJava\b найдёт слово Java в строке Hello, Java!, но не в строке Hello, Javascript!. - - -```js -//+ run +Например, `pattern:\bJava\b` найдёт слово `match:Java` в строке `subject:Hello, Java!`, но не в строке `subject:Hello, Javascript!`. +```js run alert( "Hello, Java!".match(/\bJava\b/) ); // Java alert( "Hello, Javascript!".match(/\bJava\b/) ); // null ``` -Граница имеет "нулевую ширину" в том смысле, что обычно символам регулярного выражения соответствуют символы строки, но не в этом случае. +Граница имеет "нулевую ширину" в том смысле, что обычно символам регулярного выражения соответствуют символы строки, но не в этом случае. Граница -- это проверка. -При поиске движок регулярных выражений идёт по шаблону и одновременно по строке, пытаясь построить соответствие. Когда он видит \b, то проверяет, что текущая позиция в строке подходит под одно из условий: -
      -
    • Начало текста, если первый символ `\w`.
    • -
    • Конец текста, если последний символ `\w`.
    • -
    • Внутри текста, если с одной стороны `\w`, а с другой -- не `\w`.
    • -
    +При поиске движок регулярных выражений идёт по шаблону и одновременно по строке, пытаясь построить соответствие. Когда он видит `pattern:\b`, то проверяет, что текущая позиция в строке подходит под одно из условий: -Например, в строке Hello, Java! под `\b` подходят следующие позиции: +- Начало текста, если первый символ `\w`. +- Конец текста, если последний символ `\w`. +- Внутри текста, если с одной стороны `\w`, а с другой -- не `\w`. - +Например, в строке `subject:Hello, Java!` под `\b` подходят следующие позиции: -Как правило, `\b` используется, чтобы искать отдельно стоящее слово. Не на русском конечно, хотя подобную проверку, как мы увидим далее, можно легко сделать для любого языка. А вот на английском, как в примере выше или для чисел, которые являются частным случаем `\w` -- легко. +![](hello-java-boundaries.png) -Например, регэксп \b\d\d\b ищет отдельно двузначные числа. Иными словами, он требует, чтобы до и после \d\d был символ, отличный от `\w` (или начало/конец текста). +Как правило, `\b` используется, чтобы искать отдельно стоящее слово. Не на русском конечно, хотя подобную проверку, как мы увидим далее, можно легко сделать для любого языка. А вот на английском, как в примере выше или для чисел, которые являются частным случаем `\w` -- легко. +Например, регэксп `pattern:\b\d\d\b` ищет отдельно двузначные числа. Иными словами, он требует, чтобы до и после `pattern:\d\d` был символ, отличный от `\w` (или начало/конец текста). ## Обратные классы @@ -116,25 +108,25 @@ alert( "Hello, Javascript!".match(/\bJava\b/) ); // null "Обратный" -- означает, что ему соответствуют все остальные символы, например: -
    -
    `\D`
    -
    Не-цифра, то есть любой символ кроме `\d`, например буква.
    -
    `\S`
    -
    Не-пробел, то есть любой символ кроме `\s`, например буква.
    -
    `\W`
    -
    Любой символ, кроме `\w`, то есть не латинница, не подчёркивание, не цифра. В частности, русские буквы принадлежат этому классу.
    -
    `\B`
    -
    Проверка, обратная `\b`.
    -
    +`\D` +: Не-цифра, то есть любой символ кроме `\d`, например буква. -В начале этой главы мы видели, как получить из телефона +7(903)-123-45-67 все цифры. +`\S` +: Не-пробел, то есть любой символ кроме `\s`, например буква. + +`\W` +: Любой символ, кроме `\w`, то есть не латинница, не подчёркивание, не цифра. В частности, русские буквы принадлежат этому классу. + +`\B` +: Проверка, обратная `\b`. + +В начале этой главы мы видели, как получить из телефона `subject:+7(903)-123-45-67` все цифры. Первый способ -- найти все цифры через `match(/\d/g)`. Обратные классы помогут реализовать альтернативный -- найти все НЕцифры и удалить их из строки: -```js -//+ run +```js run var str = "+7(903)-123-45-67"; alert( str.replace(/\D/g, "") ); // 79031234567 @@ -144,28 +136,25 @@ alert( str.replace(/\D/g, "") ); // 79031234567 Заметим, что в регулярных выражениях пробел -- такой же символ, как и другие. -Обычно мы не обращаем внимание на пробелы. Для нашего взгляда строки 1-5 и 1 - 5 почти идентичны. +Обычно мы не обращаем внимание на пробелы. Для нашего взгляда строки `subject:1-5` и `subject:1 - 5` почти идентичны. Однако, если регэксп не учитывает пробелов, то он не сработает. Попытаемся найти цифры, разделённые дефисом: -```js -//+ run +```js run alert( "1 - 5".match(/\d-\d/) ); // null, нет совпадений! ``` Поправим это, добавив в регэксп пробелы: -```js -//+ run +```js run alert( "1 - 5".match(/\d - \d/) ); // работает, пробелы вокруг дефиса ``` Конечно же, пробелы в регэкспе нужны лишь тогда, когда мы их ищем. Лишние пробелы (как и любые лишние символы) могут навредить: -```js -//+ run +```js run alert( "1-5".match(/\d - \d/) ); // null, так как в строке 1-5 нет пробелов ``` @@ -175,67 +164,61 @@ alert( "1-5".match(/\d - \d/) ); // null, так как в строке 1-5 не Особым классом символов является точка `"."`. -В регулярном выражении, точка "." обозначает *любой символ*, кроме перевода строки: +В регулярном выражении, точка `pattern:"."` обозначает *любой символ*, кроме перевода строки: -```js -//+ run +```js run alert( "Z".match(/./) ); // найдено Z ``` Посередине регулярного выражения: -```js -//+ run +```js run var re = /CS.4/; alert( "CSS4".match(re) ); // найдено "CSS4" -alert( "CS-4".match(re) ); // найдено "CS-4" +alert( "CS-4".match(re) ); // найдено "CS-4" alert( "CS 4".match(re) ); // найдено "CS 4" (пробел тоже символ) ``` -Обратим внимание -- точка означает именно "произвольный символ". +Обратим внимание -- точка означает именно "произвольный символ". То есть какой-то символ на этом месте в строке должен быть: -```js -//+ run +```js run alert( "CS4".match(/CS.4/) ); // нет совпадений, так как для точки нет символа ``` ## Экранирование специальных символов -В регулярных выражениях есть и другие символы, имеющие особый смысл. +В регулярных выражениях есть и другие символы, имеющие особый смысл. -Они используются, чтобы расширить возможности поиска. +Они используются, чтобы расширить возможности поиска. -Вот их полный список: [ \ ^ $ . | ? * + ( ). +Вот их полный список: `pattern:[ \ ^ $ . | ? * + ( )`. Не пытайтесь запомнить его -- когда мы разберёмся с каждым из них по отдельности, он запомнится сам собой. -**Чтобы использовать специальный символ в качестве обычного, он должен быть *экранирован*.** +**Чтобы использовать специальный символ в качестве обычного, он должен быть *экранирован*.** -Или, другими словами, перед символом должен быть обратный слэш `'\'`. +Или, другими словами, перед символом должен быть обратный слэш `'\'`. -Например, нам нужно найти точку '.'. В регулярном выражении она означает "любой символ, кроме новой строки", поэтому чтобы найти именно сам символ "точка" -- её нужно экранировать: \.. +Например, нам нужно найти точку `pattern:'.'`. В регулярном выражении она означает "любой символ, кроме новой строки", поэтому чтобы найти именно сам символ "точка" -- её нужно экранировать: `pattern:\.`. -```js -//+ run +```js run alert( "Глава 5.1".match(/\d\.\d/) ); // 5.1 ``` Круглые скобки также являются специальными символами, так что для поиска именно скобки нужно использовать `\(`. Пример ниже ищет строку `"g()"`: -```js -//+ run +```js run alert( "function g()".match(/g\(\)/) ); // "g()" ``` -Сам символ слэш `'/'`, хотя и не является специальными символом в регулярных выражениях, но открывает-закрывает регэксп в синтаксисе /...pattern.../, поэтому его тоже нужно экранировать. +Сам символ слэш `'/'`, хотя и не является специальными символом в регулярных выражениях, но открывает-закрывает регэксп в синтаксисе `pattern:/...pattern.../`, поэтому его тоже нужно экранировать. Так выглядит поиск слэша `'/'`: -```js -//+ run +```js run alert( "/".match(/\//) ); // '/' ``` @@ -243,29 +226,23 @@ alert( "/".match(/\//) ); // '/' Так выглядит поиск обратного слэша `"\"`: -```js -//+ run +```js run alert( "1\2".match(/\\/) ); // '\' ``` - ## Итого Мы рассмотрели классы для поиска типов символов: -
      -
    • `\d` -- цифры.
    • -
    • `\D` -- не-цифры.
    • -
    • `\s` -- пробельные символы, переводы строки.
    • -
    • `\S` -- всё, кроме `\s`.
    • -
    • `\w` -- латинница, цифры, подчёркивание `'_'`.
    • -
    • `'.'` -- точка обозначает любой символ, кроме перевода строки.
    • -
    - -Если хочется поискать именно сочетание `"\d"` или символ "точка", то его экранируют обратным слэшем, вот так: \. - -Заметим, что регулярное выражение может также содержать перевод строки `\n`, табуляцию `\t` и прочие спецсимволы для строк. Конфликта с классами не происходит, так как для них зарезервированы другие буквы. - +- `\d` -- цифры. +- `\D` -- не-цифры. +- `\s` -- пробельные символы, переводы строки. +- `\S` -- всё, кроме `\s`. +- `\w` -- латинница, цифры, подчёркивание `'_'`. +- `\W` -- всё, кроме `\w`. +- `'.'` -- точка обозначает любой символ, кроме перевода строки. +Если хочется поискать именно сочетание `"\d"` или символ "точка", то его экранируют обратным слэшем, вот так: `pattern:\.` +Заметим, что регулярное выражение может также содержать перевод строки `\n`, табуляцию `\t` и прочие спецсимволы для строк. Конфликта с классами не происходит, так как для них зарезервированы другие буквы. diff --git a/10-regular-expressions-javascript/3-regexp-character-classes/hello-java-boundaries.png b/10-regular-expressions-javascript/3-regexp-character-classes/hello-java-boundaries.png index 3a47f13c..9235b3a3 100644 Binary files a/10-regular-expressions-javascript/3-regexp-character-classes/hello-java-boundaries.png and b/10-regular-expressions-javascript/3-regexp-character-classes/hello-java-boundaries.png differ diff --git a/10-regular-expressions-javascript/3-regexp-character-classes/hello-java-boundaries@2x.png b/10-regular-expressions-javascript/3-regexp-character-classes/hello-java-boundaries@2x.png index e8f14d87..480b50a2 100644 Binary files a/10-regular-expressions-javascript/3-regexp-character-classes/hello-java-boundaries@2x.png and b/10-regular-expressions-javascript/3-regexp-character-classes/hello-java-boundaries@2x.png differ diff --git a/10-regular-expressions-javascript/3-regexp-character-classes/love-html5-classes.png b/10-regular-expressions-javascript/3-regexp-character-classes/love-html5-classes.png index 387fac9d..8c7ea25e 100644 Binary files a/10-regular-expressions-javascript/3-regexp-character-classes/love-html5-classes.png and b/10-regular-expressions-javascript/3-regexp-character-classes/love-html5-classes.png differ diff --git a/10-regular-expressions-javascript/3-regexp-character-classes/love-html5-classes@2x.png b/10-regular-expressions-javascript/3-regexp-character-classes/love-html5-classes@2x.png index f5b3e380..a82b0949 100644 Binary files a/10-regular-expressions-javascript/3-regexp-character-classes/love-html5-classes@2x.png and b/10-regular-expressions-javascript/3-regexp-character-classes/love-html5-classes@2x.png differ diff --git a/10-regular-expressions-javascript/4-regexp-character-sets-and-ranges/1-find-range-1/solution.md b/10-regular-expressions-javascript/4-regexp-character-sets-and-ranges/1-find-range-1/solution.md index 6177c8e6..991a1b68 100644 --- a/10-regular-expressions-javascript/4-regexp-character-sets-and-ranges/1-find-range-1/solution.md +++ b/10-regular-expressions-javascript/4-regexp-character-sets-and-ranges/1-find-range-1/solution.md @@ -1,18 +1,13 @@ Ответы: **нет, да**. -
      -
    • В строке Java он ничего не найдёт, так как исключающие квадратные скобки в `Java[^...]` означают "один символ, кроме указанных". А после "Java" -- конец строки, символов больше нет. +- В строке `subject:Java` он ничего не найдёт, так как исключающие квадратные скобки в `Java[^...]` означают "один символ, кроме указанных". А после "Java" -- конец строки, символов больше нет. -```js -//+ run -alert( "Java".match(/Java[^script]/) ); // нет совпадений -``` -
    • -
    • Да, найдёт. Поскольку регэксп регистрозависим, то под `[^script]` вполне подходит символ `"S"`. + ```js run + alert( "Java".match(/Java[^script]/) ); // нет совпадений + ``` +- Да, найдёт. Поскольку регэксп регистрозависим, то под `[^script]` вполне подходит символ `"S"`. + + ```js run + alert( "JavaScript".match(/Java[^script]/) ); // "JavaS" + ``` -```js -//+ run -alert( "JavaScript".match(/Java[^script]/) ); // "JavaS" -``` -
    • -
    diff --git a/10-regular-expressions-javascript/4-regexp-character-sets-and-ranges/1-find-range-1/task.md b/10-regular-expressions-javascript/4-regexp-character-sets-and-ranges/1-find-range-1/task.md index 6f0b0e37..3229a17f 100644 --- a/10-regular-expressions-javascript/4-regexp-character-sets-and-ranges/1-find-range-1/task.md +++ b/10-regular-expressions-javascript/4-regexp-character-sets-and-ranges/1-find-range-1/task.md @@ -1,5 +1,5 @@ # Java[^script] -Найдет ли регэксп /Java[^script]/ что-нибудь в строке Java? +Найдет ли регэксп `pattern:/Java[^script]/` что-нибудь в строке `subject:Java`? -А в строке JavaScript? +А в строке `subject:JavaScript`? diff --git a/10-regular-expressions-javascript/4-regexp-character-sets-and-ranges/2-find-time-2-formats/solution.md b/10-regular-expressions-javascript/4-regexp-character-sets-and-ranges/2-find-time-2-formats/solution.md index 969966a2..349743dd 100644 --- a/10-regular-expressions-javascript/4-regexp-character-sets-and-ranges/2-find-time-2-formats/solution.md +++ b/10-regular-expressions-javascript/4-regexp-character-sets-and-ranges/2-find-time-2-formats/solution.md @@ -1,9 +1,8 @@ -Ответ: \d\d[-:]\d\d. +Ответ: `pattern:\d\d[-:]\d\d`. -```js -//+ run +```js run var re = /\d\d[-:]\d\d/g; alert( "Завтрак в 09:00. Обед - в 21-30".match(re) ); ``` -Обратим внимание, что дефис '-' не экранирован, поскольку в начале скобок он не может иметь специального смысла. +Обратим внимание, что дефис `pattern:'-'` не экранирован, поскольку в начале скобок он не может иметь специального смысла. diff --git a/10-regular-expressions-javascript/4-regexp-character-sets-and-ranges/article.md b/10-regular-expressions-javascript/4-regexp-character-sets-and-ranges/article.md index c8f2041f..184ed992 100644 --- a/10-regular-expressions-javascript/4-regexp-character-sets-and-ranges/article.md +++ b/10-regular-expressions-javascript/4-regexp-character-sets-and-ranges/article.md @@ -6,100 +6,90 @@ ## Набор -Например, [еао] означает любой символ из этих трёх: `'а'`, `'е'`, или `'о'`. +Например, `pattern:[еао]` означает любой символ из этих трёх: `'а'`, `'е'`, или `'о'`. Такое обозначение называют *набором*. Наборы используются в регулярном выражении наравне с обычными символами: -```js -//+ run +```js run // найти [г или т], а затем "оп" alert( "Гоп-стоп".match(/[гт]оп/gi) ); // "Гоп", "топ" ``` -Обратим внимание: несмотря на то, что в наборе указано несколько символов, в совпадении должен присутствовать *ровно один* из них. +Обратим внимание: несмотря на то, что в наборе указано несколько символов, в совпадении должен присутствовать *ровно один* из них. Поэтому в примере ниже нет результатов: -```js -//+ run +```js run // найти "В", затем [у или а], затем "ля" alert( "Вуаля".match(/В[уа]ля/) ); // совпадений нет ``` Поиск подразумевает: -
      -
    • В,
    • -
    • затем *одну* из букв набора [уа],
    • -
    • а затем ля
    • -
    -Таким образом, совпадение было бы для строки Вуля или Валя. +- `pattern:В`, +- затем *одну* из букв набора `pattern:[уа]`, +- а затем `pattern:ля` + +Таким образом, совпадение было бы для строки `match:Вуля` или `match:Валя`. ## Диапазоны Квадратные скобки могут также содержать *диапазоны символов*. -Например, [a-z] -- произвольный символ от `a` до `z`, [0-5] -- цифра от `0` до `5`. +Например, `pattern:[a-z]` -- произвольный символ от `a` до `z`, `pattern:[0-5]` -- цифра от `0` до `5`. В примере ниже мы будем искать `"x"`, после которого идёт два раза любая цифра или буква от A до F: -```js -//+ run +```js run // найдёт "xAF" alert( "Exception 0xAF".match(/x[0-9A-F][0-9A-F]/g) ); ``` -Обратим внимание, в слове Exception есть сочетание xce, но оно не подошло, потому что буквы в нём маленькие, а в диапазоне [0-9A-F] -- большие. +Обратим внимание, в слове `subject:Exception` есть сочетание `subject:xce`, но оно не подошло, потому что буквы в нём маленькие, а в диапазоне `pattern:[0-9A-F]` -- большие. -Если хочется искать и его тоже, можно добавить в скобки диапазон `a-f`: [0-9A-Fa-f]. Или же просто указать у всего регулярного выражения флаг `i`. +Если хочется искать и его тоже, можно добавить в скобки диапазон `a-f`: `pattern:[0-9A-Fa-f]`. Или же просто указать у всего регулярного выражения флаг `i`. **Символьные классы -- всего лишь более короткие записи для диапазонов, в частности:** -
      -
    • **\d** -- то же самое, что [0-9],
    • -
    • **\w** -- то же самое, что [a-zA-Z0-9_],
    • -
    • **\s** -- то же самое, что [\t\n\v\f\r ] плюс несколько юникодных пробельных символов.
    • -
    +- **\d** -- то же самое, что `pattern:[0-9]`, +- **\w** -- то же самое, что `pattern:[a-zA-Z0-9_]`, +- **\s** -- то же самое, что `pattern:[\t\n\v\f\r ]` плюс несколько юникодных пробельных символов. В квадратных скобках можно использовать и диапазоны и символьные классы -- вместе. Например, нам нужно найти все слова в тексте. Если они на английском -- это достаточно просто: -```js -//+ run +```js run var str = "The sun is rising!"; alert( str.match(/\w+/g) ); // The, sun, is, rising*!* ``` -А если есть слова и на русском? +В этом примере мы забежали немного вперёд и использовали `pattern`\w+`, что означает один или более символов, подходящих под класс `pattern`\w`. Позже мы рассмотрим `+` детальнее, а пока -- давайте посмотрим, найдутся ли слова на русском? -```js -//+ run +```js run var str = "Солнце встаёт!"; alert( str.match(/\w+/g) ); // null*!* ``` -Ничего не найдено! Это можно понять, ведь \w -- это именно английская букво-цифра, как можно видеть из аналога [a-zA-Z0-9_]. +Ничего не найдено! Это можно понять, ведь `pattern:\w` -- это именно английская букво-цифра, как можно видеть из аналога `pattern:[a-zA-Z0-9_]`. -Чтобы находило слово на русском -- нужно использовать диапазон, например /[а-я]/. +Чтобы находило слово на русском -- нужно использовать диапазон, например `pattern:/[а-я]/`. А чтобы на обоих языках -- и то и другое вместе: -```js -//+ run +```js run var str = "Солнце (the sun) встаёт!"; alert( str.match(/[\wа-я]+/gi) ); // Солнце, the, sun, вста, т*!* ``` -...Присмотритесь внимательно к предыдущему примеру! Вы видите странность? Оно не находит букву ё, более того -- считает её разрывом в слове. Причина -- в кодировке юникод, она подробно раскрыта в главе [](/string). +...Присмотритесь внимательно к предыдущему примеру! Вы видите странность? Оно не находит букву `match:ё`, более того -- считает её разрывом в слове. Причина -- в кодировке юникод, она подробно раскрыта в главе . Буква `ё` лежит в стороне от основной кириллицы и её следует добавить в диапазон дополнительно, вот так: -```js -//+ run +```js run var str = "Солнце (the sun) встаёт!"; alert( str.match(/[\wа-яё]+/gi) ); // Солнце, the, sun, встаёт*!* @@ -109,22 +99,19 @@ alert( str.match(/[\wа-яё]+/gi) ); // Солнце, the, sun, встаёт*!* ## Диапазоны "кроме" -**Кроме обычных, существуют также *исключающие* диапазоны: [^…].** +**Кроме обычных, существуют также *исключающие* диапазоны: `pattern:[^…]`.** -Квадратные скобки, начинающиеся со знака каретки: [^…] находят любой символ, *кроме указанных*. +Квадратные скобки, начинающиеся со знака каретки: `pattern:[^…]` находят любой символ, *кроме указанных*. Например: -
      -
    • [^аеуо] -- любой символ, кроме `'a'`, `'e'`, `'y'`, `'o'`.
    • -
    • [^0-9] -- любой символ, кроме цифры, то же что `\D`.
    • -
    • [^\s] -- любой не-пробельный символ, то же что `\S`.
    • -
    +- `pattern:[^аеуо]` -- любой символ, кроме `'a'`, `'e'`, `'y'`, `'o'`. +- `pattern:[^0-9]` -- любой символ, кроме цифры, то же что `\D`. +- `pattern:[^\s]` -- любой не-пробельный символ, то же что `\S`. Пример ниже ищет любые символы, кроме букв, цифр и пробелов: -```js -//+ run +```js run alert( "alice15@gmail.com".match(/[^\d\sA-Z]/gi) ); // "@", "." ``` @@ -135,23 +122,21 @@ alert( "alice15@gmail.com".match(/[^\d\sA-Z]/gi) ); // "@", "." В квадратных скобках большинство специальных символов можно использовать без экранирования, если конечно они не имеют какой-то особый смысл именно внутри квадратных скобок. То есть, "как есть", без экранирования можно использовать символы: -
      -
    • Точка '.'.
    • -
    • Плюс '+'.
    • -
    • Круглые скобки '( )'.
    • -
    • Дефис '-', если он находится в начале или конце квадратных скобок, то есть не выделяет диапазон.
    • -
    • Символ каретки '^', если не находится в начале квадратных скобок.
    • -
    • А также открывающая квадратная скобка '['.
    • -
    + +- Точка `pattern:'.'`. +- Плюс `pattern:'+'`. +- Круглые скобки `pattern:'( )'`. +- Дефис `pattern:'-'`, если он находится в начале или конце квадратных скобок, то есть не выделяет диапазон. +- Символ каретки `pattern:'^'`, если не находится в начале квадратных скобок. +- А также открывающая квадратная скобка `pattern:'['`. То есть, точка `"."` в квадратных скобках означает не "любой символ", а обычную точку. -Регэксп [.,] ищет один из символов "точка" или "запятая". +Регэксп `pattern:[.,]` ищет один из символов "точка" или "запятая". -В примере ниже регэксп [-().^+] ищет один из символов `-().^`. Они не экранированы: +В примере ниже регэксп `pattern:[-().^+]` ищет один из символов `-().^`. Они не экранированы: -```js -//+ run +```js run // Без экранирования var re = /[-().^+]/g; @@ -160,8 +145,7 @@ alert( "1 + 2 - 3".match(re) ); // найдёт +, - ...Впрочем, даже если вы решите "на всякий случай" заэкранировать эти символы, поставив перед ними обратный слэш `\` -- вреда не будет: -```js -//+ run +```js run // Всё заэкранировали var re = /[\-\(\)\.\^\+]/g; diff --git a/10-regular-expressions-javascript/5-regexp-quantifiers/1-find-text-manydots/solution.md b/10-regular-expressions-javascript/5-regexp-quantifiers/1-find-text-manydots/solution.md index 5108d628..83e99017 100644 --- a/10-regular-expressions-javascript/5-regexp-quantifiers/1-find-text-manydots/solution.md +++ b/10-regular-expressions-javascript/5-regexp-quantifiers/1-find-text-manydots/solution.md @@ -1,8 +1,7 @@ Решение: -```js -//+ run +```js run var reg = /\.{3,}/g; alert( "Привет!... Как дела?.....".match(reg) ); // ..., ..... ``` diff --git a/10-regular-expressions-javascript/5-regexp-quantifiers/1-find-text-manydots/task.md b/10-regular-expressions-javascript/5-regexp-quantifiers/1-find-text-manydots/task.md index 4e94ba20..40e05c29 100644 --- a/10-regular-expressions-javascript/5-regexp-quantifiers/1-find-text-manydots/task.md +++ b/10-regular-expressions-javascript/5-regexp-quantifiers/1-find-text-manydots/task.md @@ -1,6 +1,8 @@ -# Как найти многоточие... ? +importance: 5 + +--- -[importance 5] +# Как найти многоточие... ? Напишите регулярное выражения для поиска многоточий: трёх или более точек подряд. diff --git a/10-regular-expressions-javascript/5-regexp-quantifiers/2-find-html-colors-6hex/solution.md b/10-regular-expressions-javascript/5-regexp-quantifiers/2-find-html-colors-6hex/solution.md index 09576d99..811381cf 100644 --- a/10-regular-expressions-javascript/5-regexp-quantifiers/2-find-html-colors-6hex/solution.md +++ b/10-regular-expressions-javascript/5-regexp-quantifiers/2-find-html-colors-6hex/solution.md @@ -1,13 +1,12 @@ Итак, нужно написать выражение для описания цвета, который начинается с "#", за которым следуют 6 шестнадцатеричных символов. -Шестнадцатеричный символ можно описать с помощью [0-9a-fA-F]. Мы можем сократить выражение, используя не чувствительный к регистру шаблон [0-9a-f]. +Шестнадцатеричный символ можно описать с помощью `pattern:[0-9a-fA-F]`. Мы можем сократить выражение, используя не чувствительный к регистру шаблон `pattern:[0-9a-f]`. -Для его шестикратного повторения мы будем использовать квантификатор {6}. +Для его шестикратного повторения мы будем использовать квантификатор `pattern:{6}`. -В итоге, получаем выражение вида /#[a-f0-9]{6}/gi. +В итоге, получаем выражение вида `pattern:/#[a-f0-9]{6}/gi`. -```js -//+ run +```js run var re = /#[a-f0-9]{6}/gi; var str = "color:#121212; background-color:#AA00ef bad-colors:f#fddee #fd2"; @@ -17,18 +16,17 @@ alert( str.match(re) ); // #121212,#AA00ef Проблема этого выражения в том, что оно находит цвет и в более длинных последовательностях: -```js -//+ run +```js run alert( "#12345678".match( /#[a-f0-9]{6}/gi ) ) // #12345678 ``` Чтобы такого не было, можно добавить в конец `\b`: -```js -//+ run +```js run // цвет alert( "#123456".match( /#[a-f0-9]{6}\b/gi ) ); // #123456 // не цвет alert( "#12345678".match( /#[a-f0-9]{6}\b/gi ) ); // null ``` + diff --git a/10-regular-expressions-javascript/5-regexp-quantifiers/3-find-decimal-positive-numbers/solution.md b/10-regular-expressions-javascript/5-regexp-quantifiers/3-find-decimal-positive-numbers/solution.md index 41f71b50..fa6db2c0 100644 --- a/10-regular-expressions-javascript/5-regexp-quantifiers/3-find-decimal-positive-numbers/solution.md +++ b/10-regular-expressions-javascript/5-regexp-quantifiers/3-find-decimal-positive-numbers/solution.md @@ -1,18 +1,18 @@ -Целое число -- это \d+. +Целое число -- это `pattern:\d+`. -Десятичная точка с дробной частью -- \.\d+. +Десятичная точка с дробной частью -- `pattern:\.\d+`. -Она не обязательна, так что обернём её в скобки с квантификатором '?'. +Она не обязательна, так что обернём её в скобки с квантификатором `pattern:'?'`. -Итого, получилось регулярное выражение \d+(\.\d+)?: +Итого, получилось регулярное выражение `pattern:\d+(\.\d+)?`: -```js -//+ run +```js run var re = /\d+(\.\d+)?/g var str = "1.5 0 12. 123.4."; alert( str.match(re) ); // 1.5, 0, 12, 123.4 ``` + diff --git a/10-regular-expressions-javascript/5-regexp-quantifiers/3-find-decimal-positive-numbers/task.md b/10-regular-expressions-javascript/5-regexp-quantifiers/3-find-decimal-positive-numbers/task.md index 17e4d836..e353cf43 100644 --- a/10-regular-expressions-javascript/5-regexp-quantifiers/3-find-decimal-positive-numbers/task.md +++ b/10-regular-expressions-javascript/5-regexp-quantifiers/3-find-decimal-positive-numbers/task.md @@ -10,4 +10,4 @@ var re = /* ваш регэксп */ var str = "1.5 0 12. 123.4."; alert( str.match(re) ); // 1.5, 0, 12, 123.4 -``` \ No newline at end of file +``` diff --git a/10-regular-expressions-javascript/5-regexp-quantifiers/4-find-decimal-numbers/solution.md b/10-regular-expressions-javascript/5-regexp-quantifiers/4-find-decimal-numbers/solution.md index 2abd91c0..417dc1b0 100644 --- a/10-regular-expressions-javascript/5-regexp-quantifiers/4-find-decimal-numbers/solution.md +++ b/10-regular-expressions-javascript/5-regexp-quantifiers/4-find-decimal-numbers/solution.md @@ -1,13 +1,12 @@ -Целое число с необязательной дробной частью -- это \d+(\.\d+)?. +Целое число с необязательной дробной частью -- это `pattern:\d+(\.\d+)?`. К этому нужно добавить необязательный `-` в начале: - -```js -//+ run +```js run var re = /-?\d+(\.\d+)?/g var str = "-1.5 0 2 -123.4."; alert( str.match(re) ); // -1.5, 0, 2, -123.4 ``` + diff --git a/10-regular-expressions-javascript/5-regexp-quantifiers/4-find-decimal-numbers/task.md b/10-regular-expressions-javascript/5-regexp-quantifiers/4-find-decimal-numbers/task.md index 5c5d4d09..01825ef3 100644 --- a/10-regular-expressions-javascript/5-regexp-quantifiers/4-find-decimal-numbers/task.md +++ b/10-regular-expressions-javascript/5-regexp-quantifiers/4-find-decimal-numbers/task.md @@ -10,4 +10,4 @@ var re = /* ваш регэксп */ var str = "-1.5 0 2 -123.4."; alert( str.match(re) ); // -1.5, 0, 2, -123.4 -``` \ No newline at end of file +``` diff --git a/10-regular-expressions-javascript/5-regexp-quantifiers/article.md b/10-regular-expressions-javascript/5-regexp-quantifiers/article.md index 17605289..4b412429 100644 --- a/10-regular-expressions-javascript/5-regexp-quantifiers/article.md +++ b/10-regular-expressions-javascript/5-regexp-quantifiers/article.md @@ -12,157 +12,121 @@ У него есть несколько подформ записи: -
    -
    Точное количество: `{5}`
    -
    Регэксп \d{5} обозначает ровно 5 цифр, в точности как \d\d\d\d\d. +Точное количество: `{5}` +: Регэксп `pattern:\d{5}` обозначает ровно 5 цифр, в точности как `pattern:\d\d\d\d\d`. -Следующий пример находит пятизначное число. + Следующий пример находит пятизначное число. -```js -//+ run -alert( "Мне 12345 лет".match(/\d{5}/) ); // "12345" -``` + ```js run + alert( "Мне 12345 лет".match(/\d{5}/) ); // "12345" + ``` -
    -
    Количество от-до: `{3,5}`
    -
    Для того, чтобы найти, например, числа размером от трёх до пяти знаков, нужно указать границы в фигурных скобках: \d{3,5} +Количество от-до: `{3,5}` +: Для того, чтобы найти, например, числа размером от трёх до пяти знаков, нужно указать границы в фигурных скобках: `pattern:\d{3,5}` -```js -//+ run -alert( "Мне не 12, а 1234 года".match(/\d{3,5}/) ); // "1234" -``` + ```js run + alert( "Мне не 12, а 1234 года".match(/\d{3,5}/) ); // "1234" + ``` -Последнее значение можно и не указывать. Тогда выражение \d{3,} найдет числа, длиной от трех цифр: + Последнее значение можно и не указывать. Тогда выражение `pattern:\d{3,}` найдет числа, длиной от трех цифр: -```js -//+ run -alert( "Мне не 12, а 345678 лет".match(/\d{3,5}/) ); // "345678" -``` -
    -
    + ```js run + alert( "Мне не 12, а 345678 лет".match(/\d{3,}/) ); // "345678" + ``` -В случае с телефоном нам нужны числа -- одна или более цифр подряд. Этой задаче соответствует регулярное выражение \d{1,}: +В случае с телефоном нам нужны числа -- одна или более цифр подряд. Этой задаче соответствует регулярное выражение `pattern:\d{1,}`: -```js -//+ run +```js run var str = "+7(903)-123-45-67"; alert( str.match(/\d{1,}/g) ); // 7,903,123,45,67 ``` - ## Короткие обозначения -Для самые часто востребованных квантификаторов есть специальные короткие обозначения. - -
    -
    `+`
    -
    Означает "один или более", то же что `{1,}`. +Для самых часто востребованных квантификаторов есть специальные короткие обозначения. -Например, \d+ находит числа -- последовательности из 1 или более цифр: +`+` +: Означает "один или более", то же что `{1,}`. -```js -//+ run -var str = "+7(903)-123-45-67"; + Например, `pattern:\d+` находит числа -- последовательности из 1 или более цифр: -alert( str.match(/\d+/g) ); // 7,903,123,45,67 -``` + ```js run + var str = "+7(903)-123-45-67"; -
    -
    `?`
    -
    Означает "ноль или один", то же что и `{0,1}`. По сути, делает символ необязательным. + alert( str.match(/\d+/g) ); // 7,903,123,45,67 + ``` -Например, регэксп ou?r найдёт o, после которого, возможно, следует u, а затем r. +`?` +: Означает "ноль или один", то же что и `{0,1}`. По сути, делает символ необязательным. -Этот регэксп найдёт or в слове color и our в colour: + Например, регэксп `pattern:ou?r` найдёт `match:o`, после которого, возможно, следует `match:u`, а затем `match:r`. -```js -//+ run -var str = "Можно писать color или colour (британский вариант)"; + Этот регэксп найдёт `match:or` в слове `subject:color` и `match:our` в `subject:colour`: -alert( str.match(/colou?r/g) ); // color, colour -``` + ```js run + var str = "Можно писать color или colour (британский вариант)"; -
    -
    `*`
    -
    Означает "ноль или более", то же что `{0,}`. То есть, символ может повторяться много раз или вообще отсутствовать. + alert( str.match(/colou?r/g) ); // color, colour + ``` -Пример ниже находит цифру, после которой идёт один или более нулей: +`*` +: Означает "ноль или более", то же что `{0,}`. То есть, символ может повторяться много раз или вообще отсутствовать. -```js -//+ run -alert( "100 10 1".match(/\d0*/g) ); // 100, 10, 1 -``` + Пример ниже находит цифру, после которой идёт один или более нулей: -Сравните это с `'+'` (один или более): + ```js run + alert( "100 10 1".match(/\d0*/g) ); // 100, 10, 1 + ``` -```js -//+ run -alert( "100 10 1".match(/\d0+/g) ); // 100, 10 -``` + Сравните это с `'+'` (один или более): -
    -
    + ```js run + alert( "100 10 1".match(/\d0+/g) ); // 100, 10 + ``` ## Ещё примеры Эти квантификаторы принадлежат к числу самых важных "строительных блоков" для сложных регулярных выражений, поэтому мы рассмотрим ещё примеры. -
    -
    Регэксп "десятичная дробь" (число с точкой внутри): \d+\.\d+
    -
    +Регэксп "десятичная дробь" (число с точкой внутри): `pattern:\d+\.\d+` +: В действии: + ```js run + alert( "0 1 12.345 7890".match(/\d+\.\d+/g) ); // 12.345 + ``` -В действии: -```js -//+ run -alert( "0 1 12.345 7890".match(/\d+\.\d+/g) ); // 12.345 -``` - -
    -
    Регэксп "открывающий HTML-тег без атрибутов", такой как `` или `

    `: /<[a-z]+>/i

    -
    Пример: +Регэксп "открывающий HTML-тег без атрибутов", такой как `` или `

    `: `pattern:/<[a-z]+>/i` +: Пример: -```js -//+ run -alert( " ... ".match(/<[a-z]+>/gi) ); // -``` + ```js run + alert( " ... ".match(/<[a-z]+>/gi) ); // + ``` -Это регулярное выражение ищет символ '<', за которым идут одна или более букв английского алфавита, и затем '>'. -

    -
    Регэксп "открывающий HTML-тег без атрибутов" (лучше): /<[a-z][a-z0-9]*>/i
    -
    -Здесь регулярное выражение расширено: в соответствие со стандартом, HTML-тег может иметь символ на любой позиции, кроме первой, например `

    `. - -```js -//+ run -alert( "

    Привет!

    ".match(/<[a-z][a-z0-9]*>/gi) ); //

    -``` + Это регулярное выражение ищет символ `pattern:'<'`, за которым идут одна или более букв английского алфавита, и затем `pattern:'>'`. -

    -
    Регэксп "открывающий или закрывающий HTML-тег без атрибутов": /<\/?[a-z][a-z0-9]*>/i
    -
    В предыдущий паттерн добавили необязательный слэш /? перед тегом. Его понадобилось заэкранировать, чтобы JavaScript не принял его за конец шаблона. +Регэксп "открывающий HTML-тег без атрибутов" (лучше): `pattern:/<[a-z][a-z0-9]*>/i` +: Здесь регулярное выражение расширено: в соответствие со стандартом, HTML-тег может иметь символ цифры на любой позиции, кроме первой, например `

    `. -```js -//+ run -alert( "

    Привет!

    ".match(/<\/?[a-z][a-z0-9]*>/gi) ); //

    ,

    -``` + ```js run + alert( "

    Привет!

    ".match(/<[a-z][a-z0-9]*>/gi) ); //

    + ``` -

    -
    +Регэксп "открывающий или закрывающий HTML-тег без атрибутов": `pattern:/<\/?[a-z][a-z0-9]*>/i` +: В предыдущий паттерн добавили необязательный слэш `pattern:/?` перед тегом. Его понадобилось заэкранировать, чтобы JavaScript не принял его за конец шаблона. + ```js run + alert( "

    Привет!

    ".match(/<\/?[a-z][a-z0-9]*>/gi) ); //

    ,

    + ``` -[smart header="Точнее -- значит сложнее"] +```smart header="Точнее -- значит сложнее" В этих примерах мы видим общее правило, которое повторяется из раза в раз: чем точнее регулярное выражение, тем оно длиннее и сложнее. -Например, для HTML-тегов, скорее всего, подошло бы и более короткое регулярное выражение <\w+>. +Например, для HTML-тегов, скорее всего, подошло бы и более короткое регулярное выражение `pattern:<\w+>`. -Так как класс `\w` означает "любая цифра или английская буква или `'_'`, то под такой регэксп подойдут и не теги, например <_>. Однако он гораздо проще, чем более точный регэксп <[a-z][a-z0-9]*>. +Так как класс `\w` означает "любая цифра или английская буква или `'_'`, то под такой регэксп подойдут и не теги, например `match:<_>`. Однако он гораздо проще, чем более точный регэксп `pattern:<[a-z][a-z0-9]*>`. -Подойдёт ли нам <\w+> или нужно использовать именно <[a-z][a-z0-9]*>? +Подойдёт ли нам `pattern:<\w+>` или нужно использовать именно `pattern:<[a-z][a-z0-9]*>`? В реальной жизни допустимы оба варианта. Ответ на подобные вопросы зависит от того, насколько реально важна точность и насколько сложно потом будет отфильтровать лишние совпадения (если появятся). -[/smart] - - - +``` diff --git a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/1-lazy-greedy/solution.md b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/1-lazy-greedy/solution.md index 083b3044..838534ee 100644 --- a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/1-lazy-greedy/solution.md +++ b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/1-lazy-greedy/solution.md @@ -3,4 +3,4 @@ Ленивый `\d+?` будет брать цифры до пробела, то есть `123`. После каждой цифры он будет останавливаться, проверять -- не пробел ли дальше? Если нет -- брать ещё цифру, в итоге возьмёт `123`. -З в дело вступит `\d+`, который по-максимуму возьмёт дальнейшие цифры, то есть `456`. \ No newline at end of file +Затем в дело вступит `\d+`, который по-максимуму возьмёт дальнейшие цифры, то есть `456`. \ No newline at end of file diff --git a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/1-lazy-greedy/task.md b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/1-lazy-greedy/task.md index 7076396f..f8889ea4 100644 --- a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/1-lazy-greedy/task.md +++ b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/1-lazy-greedy/task.md @@ -1,6 +1,6 @@ -# Совпадение для /d+? d+/ +# Совпадение для /d+? d+/ -Что будет при таком поиске, когда сначало стоит ленивый, а потом жадный квантификаторы? +Что будет при таком поиске, когда сначала стоит ленивый, а потом жадный квантификаторы? ```js "123 456".match(/\d+? \d+/g) ); // какой результат? diff --git a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/2-difference-find-quote/solution.md b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/2-difference-find-quote/solution.md index b47d89b3..e8b0ee73 100644 --- a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/2-difference-find-quote/solution.md +++ b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/2-difference-find-quote/solution.md @@ -1,14 +1,13 @@ Они очень похожи и, да, *почти* одинаковы. Оба ищут от одной кавычки до другой. -Различие здесь в символе точка '.'. Как мы помним, точка '.' обозначает *любой символ, кроме перевода строки*. +Различие здесь в символе точка `pattern:'.'`. Как мы помним, точка `pattern:'.'` обозначает *любой символ, кроме перевода строки*. -А [^"] -- это *любой символ, кроме кавычки '"'. +А `pattern:[^"]` -- это *любой символ, кроме кавычки `pattern:'"'`. -Получатся, что первый регэксп "[^"]*" найдёт закавыченные строки с `\n` внутри, а второй регэксп ".*?" -- нет. +Получается, что первый регэксп `pattern:"[^"]*"` найдёт закавыченные строки с `\n` внутри, а второй регэксп `pattern:".*?"` -- нет. Вот пример: -```js -//+ run +```js run alert( '"многострочный \n текст"'.match(/"[^"]*"/) ); // найдёт alert( '"многострочный \n текст"'.match(/".*?"/) ); // null (нет совпадений) diff --git a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/2-difference-find-quote/task.md b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/2-difference-find-quote/task.md index 5e9dcf17..fb9c168e 100644 --- a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/2-difference-find-quote/task.md +++ b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/2-difference-find-quote/task.md @@ -1,5 +1,5 @@ # Различие между "[^"]*" и ".*?" -Регулярные выражения "[^"]*" и ".*?" -- при выполнении одинаковы? +Регулярные выражения `pattern:"[^"]*"` и `pattern:".*?"` -- при выполнении одинаковы? Иначе говоря, существует ли такая строка, на которой они дадут разные результаты? Если да -- дайте такую строку. diff --git a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/3-find-html-comments/solution.md b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/3-find-html-comments/solution.md index 17bf43eb..b5c3b9e3 100644 --- a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/3-find-html-comments/solution.md +++ b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/3-find-html-comments/solution.md @@ -1,18 +1,17 @@ -Нужно найти начало комментария <!--, затем всё до конца -->. +Нужно найти начало комментария `match:`. -С первого взгляда кажется, что это сделает регулярное выражение <!--.*?--> -- квантификатор сделан ленивым, чтобы остановился, достигнув -->. +С первого взгляда кажется, что это сделает регулярное выражение `pattern:` -- квантификатор сделан ленивым, чтобы остановился, достигнув `match:-->`. Однако, точка в JavaScript -- любой символ, *кроме* конца строки. Поэтому такой регэксп не найдёт многострочный комментарий. -Всё получится, если вместо точки использовать полностю "всеядный" [\s\S]. +Всё получится, если вместо точки использовать полностю "всеядный" `pattern:[\s\S]`. Итого: -```js -//+ run +```js run var re = //g; var str = '.. .. .. '; alert( str.match(re) ); // '', '' -``` \ No newline at end of file +``` diff --git a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/3-find-html-comments/task.md b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/3-find-html-comments/task.md index af0b1b2a..f14f839b 100644 --- a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/3-find-html-comments/task.md +++ b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/3-find-html-comments/task.md @@ -9,3 +9,4 @@ var str = '.. .. alert( str.match(re) ); // '', '' ``` + diff --git a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/4-find-html-tags-greedy-lazy/solution.md b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/4-find-html-tags-greedy-lazy/solution.md index 6a87a597..bd0934dd 100644 --- a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/4-find-html-tags-greedy-lazy/solution.md +++ b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/4-find-html-tags-greedy-lazy/solution.md @@ -1,9 +1,8 @@ -Начнём поиск с <, затем один или более произвольный символ, но до закрывающего "уголка": .+?>. +Начнём поиск с `pattern:<`, затем один или более произвольный символ, но до закрывающего "уголка": `pattern:.+?>`. Проверим, как работает этот регэксп: -```js -//+ run +```js run var re = /<.+?>/g; var str = '<>
    '; @@ -11,9 +10,9 @@ var str = '<> '; alert( str.match(re) ); // <> , , ``` -Результат неверен! В качестве первого тега регэксп нашёл подстроку <> <a href="http://github.com/">, но это явно не тег. +Результат неверен! В качестве первого тега регэксп нашёл подстроку `match:<> `, но это явно не тег. -Всё потому, что .+? -- это "любой символ (кроме `\n`), повторяющийся один и более раз до того, как оставшаяся часть шаблона совпадёт (ленивость)". +Всё потому, что `pattern:.+?` -- это "любой символ (кроме `\n`), повторяющийся один и более раз до того, как оставшаяся часть шаблона совпадёт (ленивость)". Поэтому он находит первый `<`, затем есть "всё подряд" до следующего `>`. @@ -24,10 +23,9 @@ alert( str.match(re) ); // <> , , <> ``` -Правильным решением будет использовать <[^>]+>: +Правильным решением будет использовать `pattern:<[^>]+>`: -```js -//+ run +```js run var re = /<[^>]+>/g var str = '<> '; diff --git a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/4-find-html-tags-greedy-lazy/task.md b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/4-find-html-tags-greedy-lazy/task.md index 43935130..e0f4ce4f 100644 --- a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/4-find-html-tags-greedy-lazy/task.md +++ b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/4-find-html-tags-greedy-lazy/task.md @@ -3,8 +3,7 @@ Создайте регулярное выражение для поиска всех (открывающихся и закрывающихся) HTML-тегов вместе с атрибутами. Пример использования: -```js -//+ run +```js run var re = /* ваш регэксп */ var str = '<> '; diff --git a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/article.md b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/article.md index 09758bab..db3f0a47 100644 --- a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/article.md +++ b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/article.md @@ -1,8 +1,8 @@ -# Жадные и ленивые квантификаторы +# Жадные и ленивые квантификаторы Квантификаторы -- с виду очень простая, но на самом деле очень хитрая штука. -Необходимо очень хорошо понимать, как именно происходит поиск, если конечно мы хотим искать что-либо сложнее чем /\d+/. +Необходимо очень хорошо понимать, как именно происходит поиск, если конечно мы хотим искать что-либо сложнее чем `pattern:/\d+/`. [cut] @@ -10,12 +10,11 @@ Для этого нужно сначала найти все слова в таких кавычках. -Соотверствующее регулярное выражение может выглядеть так: /".+"/g, то есть мы ищем кавычку, после которой один или более произвольный символ, и в конце опять кавычка. +Соответствующее регулярное выражение может выглядеть так: `pattern:/".+"/g`, то есть мы ищем кавычку, после которой один или более произвольный символ, и в конце опять кавычка. Однако, если попробовать применить его на практике, даже на таком простом случае... -```js -//+ run +```js run var reg = /".+"/g; var str = 'a "witch" and her "broom" is one'; @@ -25,7 +24,7 @@ alert( str.match(reg) ); // "witch" and her "broom" ...Мы увидим, что оно работает совсем не так, как задумано! -Вместо того, чтобы найти два совпадения "witch" и "broom", оно находит одно: "witch" and her "broom". +Вместо того, чтобы найти два совпадения `match:"witch"` и `match:"broom"`, оно находит одно: `match:"witch" and her "broom"`. Это как раз тот случай, когда *жадность* -- причина всех зол. @@ -33,68 +32,65 @@ alert( str.match(reg) ); // "witch" and her "broom" Чтобы найти совпадение, движок регулярных выражений обычно использует следующий алгоритм: -
      -
    • Для каждой позиции в поисковой строке -
        -
      • Проверить совпадение на данной позиции -
        • Посимвольно, с учётом классов и квантификаторов сопоставив с ней регулярное выражение.
        -
      • -
      -
    • -
    +- Для каждой позиции в поисковой строке + - Проверить совпадение на данной позиции + - Посимвольно, с учётом классов и квантификаторов сопоставив с ней регулярное выражение. -Это общие слова, гораздо понятнее будет, если мы проследим, что именно он делает для регэкспа ".+". +Это общие слова, гораздо понятнее будет, если мы проследим, что именно он делает для регэкспа `pattern:".+"`. -
      -
    1. Первый символ шаблона -- это кавычка ". +1. Первый символ шаблона -- это кавычка `pattern:"`. -Движок регулярных выражений пытается сопоставить её на 0й позиции в строке, но символ `a`, поэтому на 0й позиции соответствия явно нет. + Движок регулярных выражений пытается сопоставить её на 0-й позиции в строке, но символ `a`, поэтому на 0-й позиции соответствия явно нет. -Далее он переходит 1ю, 2ю позицию в исходной строке и, наконец, обнаруживает кавычку на 3й позиции: - -
    2. -
    3. Кавычка найдена, далее движок проверяет, есть ли соответствие для остальной части паттерна. + Далее он переходит 1ю, 2ю позицию в исходной строке и, наконец, обнаруживает кавычку на 3-й позиции: -В данном случае следующий символ шаблона: . (точка). Она обозначает "любой символ", так что следующая буква строки 'w' вполне подходит: - -
    4. -
    5. Далее "любой символ" повторяется, так как стоит квантификатор .+. Движок регулярных выражений берёт один символ за другим, до тех пор, пока у него это получается. + ![](witch_greedy1.png) -В данном случае это означает "до конца строки": - -
    6. -
    7. Итак, текст закончился, движок регулярных выражений больше не может найти "любой символ", он закончил повторения для .+ и переходит к следующему символу шаблона. +2. Кавычка найдена, далее движок проверяет, есть ли соответствие для остальной части паттерна. -Следующий символ шаблона -- это кавычка. Её тоже необходимо найти, чтобы соответствие было полным. А тут -- беда, ведь поисковый текст завершился! + В данном случае следующий символ шаблона: `pattern:.` (точка). Она обозначает "любой символ", так что следующая буква строки `match:'w'` вполне подходит: -Движок регулярных выражений понимает, что, наверное, взял многовато .+ и начинает отступать обратно. + ![](witch_greedy2.png) -Иными словами, он сокращает текущее совпадение на один символ: +3. Далее "любой символ" повторяется, так как стоит квантификатор `pattern:.+`. Движок регулярных выражений берёт один символ за другим, до тех пор, пока у него это получается. - + В данном случае это означает "до конца строки": -Это называется "фаза возврата" или "фаза бэктрекинга" (backtracking -- англ.). + ![](witch_greedy3.png) -Теперь .+ соответствует почти вся оставшаяся строка, за исключением одного символа, и движок регулярных выражений ещё раз пытается подобрать соответствие для остатка шаблона, начиная с оставшейся части строки. +4. Итак, текст закончился, движок регулярных выражений больше не может найти "любой символ", он закончил повторения для `pattern:.+` и переходит к следующему символу шаблона. -Если бы последним символом строки была кавычка '"', то на этом бы всё и закончилось. Но последний символ 'e', так что совпадения нет.
    8. -
    9. ...Поэтому движок уменьшает число повторений .+ ещё на один символ: + Следующий символ шаблона -- это кавычка. Её тоже необходимо найти, чтобы соответствие было полным. А тут -- беда, ведь поисковый текст завершился! - + Движок регулярных выражений понимает, что, наверное, взял многовато `pattern:.+` и начинает отступать обратно. -Кавычка '"' не совпадает с 'n'. Опять неудача.
    10. -
    11. Движок продолжает отступать, он уменьшает количество повторений точки '.' до тех пор, пока остаток паттерна, то есть в данном случае кавычка '"', не совпадёт: + Иными словами, он сокращает текущее совпадение на один символ: - -
    12. -
    13. Совпадение получено. Дальнейший поиск по оставшейся части строки is one новых совпадений не даст.
    14. -
    + ![](witch_greedy4.png) + + Это называется "фаза возврата" или "фаза бэктрекинга" (backtracking -- англ.). + + Теперь `pattern:.+` соответствует почти вся оставшаяся строка, за исключением одного символа, и движок регулярных выражений ещё раз пытается подобрать соответствие для остатка шаблона, начиная с оставшейся части строки. + + Если бы последним символом строки была кавычка `pattern:'"'`, то на этом бы всё и закончилось. Но последний символ `subject:'e'`, так что совпадения нет. + +5. ...Поэтому движок уменьшает число повторений `pattern:.+` ещё на один символ: + + ![](witch_greedy5.png) + + Кавычка `pattern:'"'` не совпадает с `subject:'n'`. Опять неудача. + +6. Движок продолжает отступать, он уменьшает количество повторений точки `pattern:'.'` до тех пор, пока остаток паттерна, то есть в данном случае кавычка `pattern:'"'`, не совпадёт: + + ![](witch_greedy6.png) + +7. Совпадение получено. Дальнейший поиск по оставшейся части строки `subject:is one` новых совпадений не даст. Возможно, это не совсем то, что мы ожидали. **В жадном режиме (по умолчанию) регэксп повторяет квантификатор настолько много раз, насколько это возможно, чтобы найти соответствие.** -То есть, любой символ .+ повторился максимальное количество раз, что и привело к такой длинной строке. +То есть, любой символ `pattern:.+` повторился максимальное количество раз, что и привело к такой длинной строке. А мы, наверное, хотели, чтобы каждая строка в кавычках была независимым совпадением? Для этого можно переключить квантификатор `+` в "ленивый" режим, о котором будет речь далее. @@ -102,14 +98,13 @@ alert( str.match(reg) ); // "witch" and her "broom" Ленивый режим работы квантификаторов -- противоположность жадному, он означает "повторять минимальное количество раз". -Его можно включить, если поставить знак вопроса '?' после квантификатора, так что он станет таким: *? или +? или даже ?? для '?'. +Его можно включить, если поставить знак вопроса `pattern:'?'` после квантификатора, так что он станет таким: `pattern:*?` или `pattern:+?` или даже `pattern:??` для `pattern:'?'`. Чтобы не возникло путаницы -- важно понимать: обычно `?` сам является квантификатором (ноль или один). Но если он стоит *после другого квантификатора (или даже после себя)*, то обретает другой смысл -- в этом случае он меняет режим его работы на ленивый. -Регэксп /".+?"/g работает, как задумано -- находит отдельно witch и broom: +Регэксп `pattern:/".+?"/g` работает, как задумано -- находит отдельно `match:witch` и `match:broom`: -```js -//+ run +```js run var reg = /".+?"/g; var str = 'a "witch" and her "broom" is one'; @@ -119,36 +114,31 @@ alert( str.match(reg) ); // witch, broom Чтобы в точности понять, как поменялась работа квантификатора, разберём поиск по шагам. -
      -
    1. Первый шаг -- тот же, кавычка '"' найдена на 3й позиции: - -
    2. +1. Первый шаг -- тот же, кавычка `pattern:'"'` найдена на 3-й позиции: + + ![](witch_greedy1.png) + +2. Второй шаг -- тот же, находим произвольный символ `pattern:'.'`: -
    3. Второй шаг -- тот же, находим произвольный символ '.': - -
    4. + ![](witch_greedy2.png) -
    5. А вот дальше -- так как стоит ленивый режим работы `+`, то движок не повторет точку (произвольный символ) ещё раз, а останавливается на достигнутом и пытается проверить, есть ли соответствие остальной части шаблона, то есть '"': - +3. А вот дальше -- так как стоит ленивый режим работы `+`, то движок не повторит точку (произвольный символ) ещё раз, а останавливается на достигнутом и пытается проверить, есть ли соответствие остальной части шаблона, то есть `pattern:'"'`: -Если бы остальная часть шаблона на данной позиции совпала, то совпадение было бы найдено. Но в данном случе -- нет, символ `'i'` не равен '"'. -
    6. -
    7. Движок регулярных выражений увиличивает количество повторений точки на одно и пытается найти соответствие остатку шаблона ещё раз: + ![](witch_lazy3.png) - + Если бы остальная часть шаблона на данной позиции совпала, то совпадение было бы найдено. Но в данном случае -- нет, символ `'i'` не равен '"'. +4. Движок регулярных выражений увиличивает количество повторений точки на одно и пытается найти соответствие остатку шаблона ещё раз: + + ![](witch_lazy4.png) Опять неудача. Тогда поисковой движок увеличивает количество повторений ещё и ещё... -
    8. -
    9. Только на 5м шаге поисковой движок наконец находит соответствие для остатка паттерна: +5. Только на пятом шаге поисковой движок наконец находит соответствие для остатка паттерна: - -
    10. -
    11. Так как поиск происходит с флагом `g`, то он продолжается с конца текущего совпадения, давая ещё один результат: + ![](witch_lazy5.png) +6. Так как поиск происходит с флагом `g`, то он продолжается с конца текущего совпадения, давая ещё один результат: - -
    12. -
    + ![](witch_lazy6.png) -В примере выше продемонстрирована работа ленивого режима для +?. Квантификаторы +? и ?? ведут себя аналогично -- "ленивый" движок увеличивает количество повторений только в том случае, если для остальной части шаблона на данной позиции нет соответствия. +В примере выше продемонстрирована работа ленивого режима для `pattern:+?`. Квантификаторы `pattern:+?` и `pattern:??` ведут себя аналогично -- "ленивый" движок увеличивает количество повторений только в том случае, если для остальной части шаблона на данной позиции нет соответствия. **Ленивость распространяется только на тот квантификатор, после которого стоит `?`.** @@ -156,40 +146,36 @@ alert( str.match(reg) ); // witch, broom Например: -```js -//+ run +```js run alert( "123 456".match(/\d+ \d+?/g) ); // 123 4 ``` -
      -
    1. Подшаблон \d+ пытается найти столько цифр, сколько возможно (работает жадно), так что он находит 123 и останавливается, поскольку символ пробела ' ' не подходит под \d.
    2. -
    3. Далее в шаблоне пробел, он совпадает.
    4. -
    5. Далее в шаблоне идёт \d+?. +1. Подшаблон `pattern:\d+` пытается найти столько цифр, сколько возможно (работает жадно), так что он находит `match:123` и останавливается, поскольку символ пробела `pattern:' '` не подходит под `pattern:\d`. +2. Далее в шаблоне пробел, он совпадает. +3. Далее в шаблоне идёт `pattern:\d+?`. -Квантификатор указан в ленивом режиме, поэтому он находит одну цифру 4 и пытается проверить, есть ли совпадение с остатком шаблона. + Квантификатор указан в ленивом режиме, поэтому он находит одну цифру `match:4` и пытается проверить, есть ли совпадение с остатком шаблона. -Но после \d+? в шаблоне ничего нет. + Но после `pattern:\d+?` в шаблоне ничего нет. -**Ленивый режим без необходимости лишний раз квантификатор не повторит.** + **Ленивый режим без необходимости лишний раз квантификатор не повторит.** -Так как шаблон завершился, то искать дальше, в общем-то нечего. Получено совпадение 123 4.
    6. -
    7. Следующий поиск продолжится с `5`, но ничего не найдёт.
    8. -
    + Так как шаблон завершился, то искать дальше, в общем-то нечего. Получено совпадение `match:123 4`. +4. Следующий поиск продолжится с `5`, но ничего не найдёт. -[smart header="Конечные автоматы и не только"] +```smart header="Конечные автоматы и не только" Современные движки регулярных выражений могут иметь более хитрую реализацию внутренних алгоритмов, чтобы искать быстрее. Однако, чтобы понять, как работает регулярное выражение, и строить регулярные выражения самому, знание этих хитрых алгоритмов ни к чему. Они служат лишь внутренней оптимизации способа поиска, описанного выше. Кроме того, сложные регулярные выражения плохо поддаются всяким оптимизациям, так что поиск вполне может работать и в точности как здесь описано. -[/smart] +``` ## Альтернативный подход -В данном конкретном случае, возможно искать строки в кавычках, оставаясь в жадном режиме, с использованием регулярного выражения "[^"]+": +В данном конкретном случае, возможно искать строки в кавычках, оставаясь в жадном режиме, с использованием регулярного выражения `pattern:"[^"]+"`: -```js -//+ run +```js run var reg = /"[^"]+"/g; var str = 'a "witch" and her "broom" is one'; @@ -197,13 +183,12 @@ var str = 'a "witch" and her "broom" is one'; alert( str.match(reg) ); // witch, broom ``` -Регэксп "[^"]+" даст правильные результаты, поскольку ищет кавычку '"', за которой идут столько не-кавычек (исключающие квадратные скобки), сколько возможно. +Регэксп `pattern:"[^"]+"` даст правильные результаты, поскольку ищет кавычку `pattern:'"'`, за которой идут столько не-кавычек (исключающие квадратные скобки), сколько возможно. -Так что вторая кавычка автоматически прекращает повторения [^"]+ и позволяет найти остаток шаблона ". +Так что вторая кавычка автоматически прекращает повторения `pattern:[^"]+` и позволяет найти остаток шаблона `pattern:"`. **Эта логика ни в коей мере не заменяет ленивые квантификаторы!** - Она просто другая. И то и другое бывает полезно. Давайте посмотрим пример, когда нужен именно такой вариант, а ленивые квантификаторы не подойдут. @@ -212,11 +197,10 @@ alert( str.match(reg) ); // witch, broom Какое регулярное выражение для этого подойдёт? -Первый вариант может выглядеть так: /<a href="http://github.com/.*" class="doc">/g. +Первый вариант может выглядеть так: `pattern:/
    /g`. Проверим его: -```js -//+ run +```js run var str = '......'; var reg = //g; @@ -226,8 +210,7 @@ alert( str.match(reg) ); // А если в тексте несколько ссылок? -```js -//+ run +```js run var str = '...... ...'; var reg = //g; @@ -237,18 +220,17 @@ alert( str.match(reg) ); // ... .* взял слишком много символов. +Жадный `pattern:.*` взял слишком много символов. Соответствие получилось таким: ``` - + ... ``` -Модифицируем шаблон -- добавим ленивость квантификатору .*?: +Модифицируем шаблон -- добавим ленивость квантификатору `pattern:.*?`: -```js -//+ run +```js run var str = '...... ...'; var reg = //g; @@ -259,16 +241,15 @@ alert( str.match(reg) ); // , + ... ``` -Почему теперь всё в порядке -- для внимательного читателя, после объяснений, данных выше в этой главе, должно быть полностью очевидно. +Почему теперь всё в порядке -- для внимательного читателя, после объяснений, данных выше в этой главе, должно быть полностью очевидно. Поэтому не будем останавливаться здесь на деталях, а попробуем ещё пример: -```js -//+ run +```js run var str = '......

    ...'; var reg = //g; @@ -276,38 +257,34 @@ var reg = //g; alert( str.match(reg) ); // ...

    ``` -Совпадение -- не ссылка, а более длинный текст. +Совпадение -- не ссылка, а более длинный текст. Получилось следующее: -

      -
    1. Найдено совпадение <a href=".
    2. -
    3. Лениво ищем .*?, после каждого символа проверяя, есть ли совпадение остальной части шаблона. -Подшаблон .*? будет брать символы до тех пор, пока не найдёт class="doc">. +1. Найдено совпадение `match:`. -``` - -...

      -``` -

    4. -
    + В данном случае этот поиск закончится уже за пределами ссылки, в теге `

    `, вообще не имеющем отношения к ``. +3. Получившееся совпадение: -Итак, ленивость нам не помогла. + ``` + + ...

    + ``` -Необходимо как-то прекратить поиск .*, чтобы он не вышел за пределы кавычек. +Итак, ленивость нам не помогла. + +Необходимо как-то прекратить поиск `pattern:.*`, чтобы он не вышел за пределы кавычек. Для этого мы используем более точное указание, какие символы нам подходят, а какие нет. -Правильный вариант: [^"]*. Этот шаблон будет брать все символы до ближайшей кавычки, как раз то, что требуется. +Правильный вариант: `pattern:[^"]*`. Этот шаблон будет брать все символы до ближайшей кавычки, как раз то, что требуется. Рабочий пример: -```js -//+ run +```js run var str1 = '......

    ...'; var str2 = '...... ...'; var reg = //g; @@ -320,16 +297,12 @@ alert( str2.match(reg) ); // , -

    Жадный
    -
    Режим по умолчанию -- движок регулярных выражений повторяет его по-максимуму. Когда повторять уже нельзя, например нет больше цифр для `\d+`, он продолжает поиск с оставшейся части текста. Если совпадение найти не удалось -- отступает обратно, уменьшая количество повторений.
    -
    Ленивый
    -
    При указании после квантификатора символа `?` он работает в ленивом режиме. То есть, он перед каждым повторением проверяет совпадение оставшейся части шаблона на текущей позиции.
    - - -Как мы видели в примере выше, ленивый режим -- не панацея от "слишком жадного" забора символов. Альтернатива -- более аккуратно настроенный "жадный", с исключением символов. Как мы увидим далее, можно исключать не только символы, но и целые подшаблоны. - +Жадный +: Режим по умолчанию -- движок регулярных выражений повторяет его по-максимуму. Когда повторять уже нельзя, например нет больше цифр для `\d+`, он продолжает поиск с оставшейся части текста. Если совпадение найти не удалось -- отступает обратно, уменьшая количество повторений. +Ленивый +: При указании после квантификатора символа `?` он работает в ленивом режиме. То есть, он перед каждым повторением проверяет совпадение оставшейся части шаблона на текущей позиции. +Как мы видели в примере выше, ленивый режим -- не панацея от "слишком жадного" забора символов. Альтернатива -- более аккуратно настроенный "жадный", с исключением символов. Как мы увидим далее, можно исключать не только символы, но и целые подшаблоны. diff --git a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy1.png b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy1.png index a4fc2c4f..907c065e 100644 Binary files a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy1.png and b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy1.png differ diff --git a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy1@2x.png b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy1@2x.png index e1b2ca0a..a12c0e5a 100644 Binary files a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy1@2x.png and b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy1@2x.png differ diff --git a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy2.png b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy2.png index b7b84c1b..2b4b1e46 100644 Binary files a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy2.png and b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy2.png differ diff --git a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy2@2x.png b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy2@2x.png index ce87d89e..eb75d374 100644 Binary files a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy2@2x.png and b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy2@2x.png differ diff --git a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy3.png b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy3.png index 0e5b07fd..9b4912e7 100644 Binary files a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy3.png and b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy3.png differ diff --git a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy3@2x.png b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy3@2x.png index 975fc49c..db52e1dd 100644 Binary files a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy3@2x.png and b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy3@2x.png differ diff --git a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy4.png b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy4.png index 0ca3df76..009daa3e 100644 Binary files a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy4.png and b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy4.png differ diff --git a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy4@2x.png b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy4@2x.png index fe514d4d..6154c8d9 100644 Binary files a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy4@2x.png and b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy4@2x.png differ diff --git a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy5.png b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy5.png index 43b01a9a..6acaf3c2 100644 Binary files a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy5.png and b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy5.png differ diff --git a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy5@2x.png b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy5@2x.png index 353f69e2..3fefa4b4 100644 Binary files a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy5@2x.png and b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy5@2x.png differ diff --git a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy6.png b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy6.png index 2ae63d20..7667320c 100644 Binary files a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy6.png and b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy6.png differ diff --git a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy6@2x.png b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy6@2x.png index 5b09faa0..7de364a7 100644 Binary files a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy6@2x.png and b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy6@2x.png differ diff --git a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_lazy3.png b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_lazy3.png index af0482bb..4b5f754c 100644 Binary files a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_lazy3.png and b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_lazy3.png differ diff --git a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_lazy3@2x.png b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_lazy3@2x.png index 7d5fd2c6..d4694feb 100644 Binary files a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_lazy3@2x.png and b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_lazy3@2x.png differ diff --git a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_lazy4.png b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_lazy4.png index 920530bd..52ae5bb7 100644 Binary files a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_lazy4.png and b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_lazy4.png differ diff --git a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_lazy4@2x.png b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_lazy4@2x.png index 1e14c11c..e4f7cd8e 100644 Binary files a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_lazy4@2x.png and b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_lazy4@2x.png differ diff --git a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_lazy5.png b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_lazy5.png index 0a07eeae..05784f4f 100644 Binary files a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_lazy5.png and b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_lazy5.png differ diff --git a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_lazy5@2x.png b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_lazy5@2x.png index 8c9ff19f..5547be49 100644 Binary files a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_lazy5@2x.png and b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_lazy5@2x.png differ diff --git a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_lazy6.png b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_lazy6.png index 67f0d27a..7e3d1362 100644 Binary files a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_lazy6.png and b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_lazy6.png differ diff --git a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_lazy6@2x.png b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_lazy6@2x.png index e7eb1aa0..9f91d5ff 100644 Binary files a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_lazy6@2x.png and b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_lazy6@2x.png differ diff --git a/10-regular-expressions-javascript/7-regexp-groups/1-find-webcolor-3-or-6/solution.md b/10-regular-expressions-javascript/7-regexp-groups/1-find-webcolor-3-or-6/solution.md index ece24fb8..e6af9289 100644 --- a/10-regular-expressions-javascript/7-regexp-groups/1-find-webcolor-3-or-6/solution.md +++ b/10-regular-expressions-javascript/7-regexp-groups/1-find-webcolor-3-or-6/solution.md @@ -1,16 +1,15 @@ -Регулярное выражение для поиска 3-значного цвета вида `#abc`: /#[a-f0-9]{3}/i. +Регулярное выражение для поиска 3-значного цвета вида `#abc`: `pattern:/#[a-f0-9]{3}/i`. Нужно добавить ещё три символа, причём нужны именно три, четыре или семь символов не нужны. Эти три символа либо есть, либо нет. -Самый простой способ добавить -- просто дописать в конец регэкспа: /#[a-f0-9]{3}([a-f0-9]{3})?/i +Самый простой способ добавить -- просто дописать в конец регэкспа: `pattern:/#[a-f0-9]{3}([a-f0-9]{3})?/i` -Можно поступить и хитрее: /#([a-f0-9]{3}){1,2}/i. +Можно поступить и хитрее: `pattern:/#([a-f0-9]{3}){1,2}/i`. -Здесь регэксп [a-f0-9]{3} заключён в скобки, чтобы квантификатор {1,2} применялся целиком ко всей этой структуре. +Здесь регэксп `pattern:[a-f0-9]{3}` заключён в скобки, чтобы квантификатор `pattern:{1,2}` применялся целиком ко всей этой структуре. В действии: -```js -//+ run +```js run var re = /#([a-f0-9]{3}){1,2}/gi; var str = "color: #3f3; background-color: #AA00ef; and: #abcd"; @@ -18,13 +17,13 @@ var str = "color: #3f3; background-color: #AA00ef; and: #abcd"; alert( str.match(re) ); // #3f3 #AA0ef #abc ``` -В последнем выражении #abcd было найдено совпадение #abc. Чтобы этого не происходило, добавим в конец \b: +В последнем выражении `subject:#abcd` было найдено совпадение `match:#abc`. Чтобы этого не происходило, добавим в конец `pattern:\b`: -```js -//+ run +```js run var re = /#([a-f0-9]{3}){1,2}\b/gi; var str = "color: #3f3; background-color: #AA00ef; and: #abcd"; alert( str.match(re) ); // #3f3 #AA0ef ``` + diff --git a/10-regular-expressions-javascript/7-regexp-groups/2-parse-expression/solution.md b/10-regular-expressions-javascript/7-regexp-groups/2-parse-expression/solution.md index 05fedcd8..d224acdc 100644 --- a/10-regular-expressions-javascript/7-regexp-groups/2-parse-expression/solution.md +++ b/10-regular-expressions-javascript/7-regexp-groups/2-parse-expression/solution.md @@ -1,18 +1,17 @@ -Регулярное выражение для числа, возможно, дробного и отрицательного: -?\d+(\.\d+)?. Мы уже разбирали его в предыдущих задачах. +Регулярное выражение для числа, возможно, дробного и отрицательного: `pattern:-?\d+(\.\d+)?`. Мы уже разбирали его в предыдущих задачах. -Оператор -- это [-+*/]. Заметим, что дефис - идёт в списке первым, так как на любой позиции, кроме первой и последней, он имеет специальный смысл внутри [...], и его понадобилось бы экранировать. +Оператор -- это `pattern:[-+*/]`. Заметим, что дефис `pattern:-` идёт в списке первым, так как на любой позиции, кроме первой и последней, он имеет специальный смысл внутри `pattern:[...]`, и его понадобилось бы экранировать. -Кроме того, когда мы оформим это в JavaScript-синтаксис /.../ -- понадобится заэкранировать слэш /. +Кроме того, когда мы оформим это в JavaScript-синтаксис `pattern:/.../` -- понадобится заэкранировать слэш `pattern:/`. Нам нужно число, затем оператор, затем число, и необязательные пробелы между ними. -Полное регулярное выражение будет таким: -?\d+(\.\d+)?\s*[-+*/]\s*-?\d+(\.\d+)?. +Полное регулярное выражение будет таким: `pattern:-?\d+(\.\d+)?\s*[-+*/]\s*-?\d+(\.\d+)?`. -Чтобы получить результат в виде массива, добавим скобки вокруг тех данных, которые нам интересны, то есть -- вокруг чисел и оператора: (-?\d+(\.\d+)?)\s*([-+*/])\s*(-?\d+(\.\d+)?). +Чтобы получить результат в виде массива, добавим скобки вокруг тех данных, которые нам интересны, то есть -- вокруг чисел и оператора: `pattern:(-?\d+(\.\d+)?)\s*([-+*/])\s*(-?\d+(\.\d+)?)`. Посмотрим в действии: -```js -//+ run +```js run var re = /(-?\d+(\.\d+)?)\s*([-+*\/])\s*(-?\d+(\.\d+)?)/; alert( "1.2 + 12".match(re) ); @@ -20,23 +19,20 @@ alert( "1.2 + 12".match(re) ); Итоговый массив будет включать в себя компоненты: -
      -
    • `result[0] == "1.2 + 12"` (вначале всегда полное совпадение)
    • -
    • `result[1] == "1"` (первая скобка)
    • -
    • `result[2] == "2"` (вторая скобка -- дробная часть `(\.\d+)?`)
    • -
    • `result[3] == "+"` (...)
    • -
    • `result[4] == "12"` (...)
    • -
    • `result[5] == undefined` (последняя скобка, но у второго числа дробная часть отсутствует)
    • -
    +- `result[0] == "1.2 + 12"` (вначале всегда полное совпадение) +- `result[1] == "1"` (первая скобка) +- `result[2] == "2"` (вторая скобка -- дробная часть `(\.\d+)?`) +- `result[3] == "+"` (...) +- `result[4] == "12"` (...) +- `result[5] == undefined` (последняя скобка, но у второго числа дробная часть отсутствует) Нам из этого массива нужны только числа и оператор. А, скажем, дробная часть сама по себе -- не нужна. -Уберём её из запоминания, добавив в начало скобки ?:, то есть: (?:\.\d+)?. +Уберём её из запоминания, добавив в начало скобки `pattern:?:`, то есть: `pattern:(?:\.\d+)?`. Итого, решение: -```js -//+ run +```js run function parse(expr) { var re = /(-?\d+(?:\.\d+)?)\s*([-+*\/])\s*(-?\d+(?:\.\d+)?)/; diff --git a/10-regular-expressions-javascript/7-regexp-groups/2-parse-expression/task.md b/10-regular-expressions-javascript/7-regexp-groups/2-parse-expression/task.md index c48e5a0c..56ad74bb 100644 --- a/10-regular-expressions-javascript/7-regexp-groups/2-parse-expression/task.md +++ b/10-regular-expressions-javascript/7-regexp-groups/2-parse-expression/task.md @@ -1,20 +1,19 @@ # Разобрать выражение Арифметическое выражение состоит из двух чисел и операции между ними, например: -
      -
    • `1 + 2`
    • -
    • `1.2 * 3.4`
    • -
    • `-3 / -6`
    • -
    • `-2 - 2`
    • -
    + +- `1 + 2` +- `1.2 * 3.4` +- `-3 / -6` +- `-2 - 2` Список операций: `"+"`, `"-"`, `"*"` и `"/"`. -Также могут присутсововать пробелы вокруг оператора и чисел. +Также могут присутствовать пробелы вокруг оператора и чисел. Напишите функцию, которая будет получать выражение и возвращать массив из трёх аргументов: -
      -
    1. Первое число.
    2. -
    3. Оператор.
    4. -
    5. Второе число.
    6. -
    + +1. Первое число. +2. Оператор. +3. Второе число. + diff --git a/10-regular-expressions-javascript/7-regexp-groups/article.md b/10-regular-expressions-javascript/7-regexp-groups/article.md index 515c6175..d8ab6ad6 100644 --- a/10-regular-expressions-javascript/7-regexp-groups/article.md +++ b/10-regular-expressions-javascript/7-regexp-groups/article.md @@ -1,51 +1,46 @@ # Скобочные группы -Часть шаблона может быть заключена в скобки (...). Такие выделенные части шаблона называют "скобочными выражениями" или "скобочными группами". +Часть шаблона может быть заключена в скобки `pattern:(...)`. Такие выделенные части шаблона называют "скобочными выражениями" или "скобочными группами". У такого выделения есть два эффекта: -
      -
    1. Он позволяет выделить часть совпадения в отдельный элемент массива при поиске через [String#match](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/String/match) или [RegExp#exec](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/RegExp/exec).
    2. -
    3. Если поставить квантификатор после скобки, то он применится *ко всей скобке*, а не всего лишь к одному символу.
    4. -
    + +1. Он позволяет выделить часть совпадения в отдельный элемент массива при поиске через [String#match](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/String/match) или [RegExp#exec](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/RegExp/exec). +2. Если поставить квантификатор после скобки, то он применится *ко всей скобке*, а не всего лишь к одному символу. [cut] ## Пример -В примере ниже, шаблон (go)+ находит один или более повторяющихся 'go': +В примере ниже, шаблон `pattern:(go)+` находит один или более повторяющихся `pattern:'go'`: -```js -//+ run -alert( 'Gogogo now!'.match(/(go)+/i ); // "Gogogo" +```js run +alert( 'Gogogo now!'.match(/(go)+/i) ); // "Gogogo" ``` -Без скобок, шаблон /go+/ означал бы g, после которого идёт одна или более o, например: goooo. А скобки "группируют" (go) вместе. - +Без скобок, шаблон `pattern:/go+/` означал бы `subject:g`, после которого идёт одна или более `subject:o`, например: `match:goooo`. А скобки "группируют" `pattern:(go)` вместе. ## Содержимое группы Скобки нумеруются слева направо. Поисковой движок запоминает содержимое каждой скобки и позволяет обращаться к нему -- в шаблоне и строке замены и, конечно же, в результатах. -Например, найти HTML-тег можно шаблоном <.*?>. +Например, найти HTML-тег можно шаблоном `pattern:<.*?>`. -После поиска мы захотим что-то сделать с результатом. Для удобства заключим содержимое `<...>` в скобки: <(.*?)>. Тогда оно будет доступно отдельно. +После поиска мы захотим что-то сделать с результатом. Для удобства заключим содержимое `<...>` в скобки: `pattern:<(.*?)>`. Тогда оно будет доступно отдельно. -При поиске методом [String#match](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/String/match) в результирующем массиве будет сначала всё совпадение, а далее -- скобочные группы. В шаблоне <(.*?)> скобочная группа только одна: +При поиске методом [String#match](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/String/match) в результирующем массиве будет сначала всё совпадение, а далее -- скобочные группы. В шаблоне `pattern:<(.*?)>` скобочная группа только одна: -```js -//+ run +```js run var str = '

    Привет, мир!

    '; var reg = /<(.*?)>/; alert( str.match(reg) ); // массив:

    , h1 ``` -Заметим, что метод [String#match](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/String/match) выдаёт скобочные группы только при поиске без флага `/.../g`. В примере выше он нашёл только первое совпадение <h1>, а закрывающий </h1> не нашёл, поскольку без флага `/.../g` ищется только первое совпадение. +Заметим, что метод [String#match](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/String/match) выдаёт скобочные группы только при поиске без флага `/.../g`. В примере выше он нашёл только первое совпадение `match:

    `, а закрывающий `match:

    ` не нашёл, поскольку без флага `/.../g` ищется только первое совпадение. Для того, чтобы искать и с флагом `/.../g` и со скобочными группами, используется метод [RegExp#exec](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/RegExp/exec): -```js -//+ run +```js run var str = '

    Привет, мир!

    '; var reg = /<(.*?)>/g; @@ -58,33 +53,30 @@ while ((match = reg.exec(str)) !== null) { } ``` -Теперь найдено оба совпадения <(.*?)>, каждое -- массив из полного совпадения и скобочных групп (одна в данном случае). +Теперь найдено оба совпадения `pattern:<(.*?)>`, каждое -- массив из полного совпадения и скобочных групп (одна в данном случае). ## Вложенные группы Скобки могут быть и вложенными. В этом случае нумерация также идёт слева направо. -Например, при поиске тега в <span class="my"> нас может интересовать: +Например, при поиске тега в `subject:` нас может интересовать: -
      -
    1. Содержимое тега целиком: `span class="my"`.
    2. -
    3. В отдельную переменную для удобства хотелось бы поместить тег: `span`.
    4. -
    5. Также может быть удобно отдельно выделить атрибуты `class="my"`.
    6. -
    +1. Содержимое тега целиком: `span class="my"`. +2. В отдельную переменную для удобства хотелось бы поместить тег: `span`. +3. Также может быть удобно отдельно выделить атрибуты `class="my"`. Добавим скобки в регулярное выражение: -```js -//+ run +```js run var str = ''; -reg = /<(([a-z]+)\s*([^>]*))>/; +var reg = /<(([a-z]+)\s*([^>]*))>/; -alert( str.match(reg) ); // , span, s +alert( str.match(reg) ); // , span class="my", span, class="my" ``` Вот так выглядят скобочные группы: - +![](regexp-nested-groups.png) На нулевом месте -- всегда совпадение полностью, далее -- группы. Нумерация всегда идёт слева направо, по открывающей скобке. @@ -92,13 +84,12 @@ alert( str.match(reg) ); // , span, s **Даже если скобочная группа необязательна и не входит в совпадение, соответствующий элемент массива существует (и равен `undefined`).** -Например, рассмотрим регэксп a(z)?(c)?. Он ищет `"a"`, за которой не обязательно идёт буква `"z"`, за которой необязательно идёт буква `"c"`. +Например, рассмотрим регэксп `pattern:a(z)?(c)?`. Он ищет `"a"`, за которой не обязательно идёт буква `"z"`, за которой необязательно идёт буква `"c"`. Если напустить его на строку из одной буквы `"a"`, то результат будет таков: -```js -//+ run -match = 'a'.match(/a(z)?(c)?/) +```js run +var match = 'a'.match(/a(z)?(c)?/) alert( match.length ); // 3 alert( match[0] ); // a @@ -108,11 +99,10 @@ alert( match[2] ); // undefined Массив получился длины `3`, но все скобочные группы -- `undefined`. -А теперь более сложная ситуация, строка ack: +А теперь более сложная ситуация, строка `subject:ack`: -```js -//+ run -match = 'ack'.match(/a(z)?(c)?/) +```js run +var match = 'ack'.match(/a(z)?(c)?/) alert( match.length ); // 3 alert( match[0] ); // ac, всё совпадение @@ -120,22 +110,21 @@ alert( match[1] ); // undefined, для (z)? ничего нет alert( match[2] ); // c ``` -Длина массива результатов по-прежнему `3`. Она постоянна. А вот для скобочной группы (z)? в ней ничего нет, поэтому результат: `["ac", undefined, "c"]`. +Длина массива результатов по-прежнему `3`. Она постоянна. А вот для скобочной группы `pattern:(z)?` в ней ничего нет, поэтому результат: `["ac", undefined, "c"]`. ## Исключение из запоминания через ?: -Бывает так, что скобки нужны, чтобы квантификатор правильно применился, а вот запоминать её в массиве не нужно. +Бывает так, что скобки нужны, чтобы квантификатор правильно применился, а вот запоминать их содержимое в массиве не нужно. -Скобочную группу можно исключить из запоминаемых и нумеруемых, добавив в её начало ?:. +Скобочную группу можно исключить из запоминаемых и нумеруемых, добавив в её начало `pattern:?:`. -Например, мы хотим найти (go)+, но содержимое скобок (`go`) в отдельный элемент массива выделять не хотим. +Например, мы хотим найти `pattern:(go)+`, но содержимое скобок (`go`) в отдельный элемент массива выделять не хотим. -Для этого нужно сразу после открывающей скобки поставить `?:`, то есть: (?:go)+. +Для этого нужно сразу после открывающей скобки поставить `?:`, то есть: `pattern:(?:go)+`. Например: -```js -//+ run +```js run var str = "Gogo John!"; *!* var reg = /(?:go)+ (\w+)/i; @@ -147,4 +136,4 @@ alert( result.length ); // 2 alert( result[1] ); // John ``` -В примере выше массив результатов имеет длину `2` и содержит только полное совпадение и результат (\w+). Это удобно в тех случаях, когда содержимое скобок нас не интересует. +В примере выше массив результатов имеет длину `2` и содержит только полное совпадение и результат `pattern:(\w+)`. Это удобно в тех случаях, когда содержимое скобок нас не интересует. diff --git a/10-regular-expressions-javascript/7-regexp-groups/regexp-nested-groups.png b/10-regular-expressions-javascript/7-regexp-groups/regexp-nested-groups.png index 9103ea28..9eb37f80 100644 Binary files a/10-regular-expressions-javascript/7-regexp-groups/regexp-nested-groups.png and b/10-regular-expressions-javascript/7-regexp-groups/regexp-nested-groups.png differ diff --git a/10-regular-expressions-javascript/7-regexp-groups/regexp-nested-groups@2x.png b/10-regular-expressions-javascript/7-regexp-groups/regexp-nested-groups@2x.png index 4f5ee487..372ac81b 100644 Binary files a/10-regular-expressions-javascript/7-regexp-groups/regexp-nested-groups@2x.png and b/10-regular-expressions-javascript/7-regexp-groups/regexp-nested-groups@2x.png differ diff --git a/10-regular-expressions-javascript/8-regexp-backreferences/1-find-matching-bbtags/solution.md b/10-regular-expressions-javascript/8-regexp-backreferences/1-find-matching-bbtags/solution.md index 234032d4..31f24848 100644 --- a/10-regular-expressions-javascript/8-regexp-backreferences/1-find-matching-bbtags/solution.md +++ b/10-regular-expressions-javascript/8-regexp-backreferences/1-find-matching-bbtags/solution.md @@ -1,14 +1,13 @@ -Открывающий тег -- это \[(b|url|quote)\]. +Открывающий тег -- это `pattern:\[(b|url|quote)\]`. -Для того, чтобы найти всё до закрывающего -- используем ленивый поиск [\s\S]*? и обратную ссылку на открывающий тег. +Для того, чтобы найти всё до закрывающего -- используем ленивый поиск `pattern:[\s\S]*?` и обратную ссылку на открывающий тег. -Итого, получится: \[(b|url|quote)\][\s\S]*?\[/\1\]. +Итого, получится: `pattern:\[(b|url|quote)\][\s\S]*?\[/\1\]`. В действии: -```js -//+ run +```js run var re = /\[(b|url|quote)\][\s\S]*?\[\/\1\]/g; var str1 = "..[url]http://ya.ru[/url].."; diff --git a/10-regular-expressions-javascript/8-regexp-backreferences/article.md b/10-regular-expressions-javascript/8-regexp-backreferences/article.md index 3814da12..25aefb97 100644 --- a/10-regular-expressions-javascript/8-regexp-backreferences/article.md +++ b/10-regular-expressions-javascript/8-regexp-backreferences/article.md @@ -8,19 +8,18 @@ ## Группа в строке замены -Ссылки в строке замены имеют вид `$n`, где `n` -- это номер скобочной группы. +Ссылки в строке замены имеют вид `$n`, где `n` -- это номер скобочной группы. Вместо `$n` подставляется содержимое соответствующей скобки: -```js -//+ run +```js run var name = "Александр Пушкин"; name = name.replace(/([а-яё]+) ([а-яё]+)/i, *!*"$2, $1"*/!*); alert( name ); // Пушкин, Александр ``` -В примере выше вместо $2 подставляется второе найденное слово, а вместо $1 -- первое. +В примере выше вместо `pattern:$2` подставляется второе найденное слово, а вместо `pattern:$1` -- первое. ## Группа в шаблоне @@ -28,41 +27,37 @@ alert( name ); // Пушкин, Александр Но к скобочной группе можно также обратиться в самом поисковом шаблоне, ссылкой вида `\номер`. -Чтобы было яснее, рассмотрим это на реальной задаче -- необходимо найти в тексте строку в кавычках. Причём кавычки могут быть одинарными '...' или двойными "..." -- и то и другое должно искаться корректно. +Чтобы было яснее, рассмотрим это на реальной задаче -- необходимо найти в тексте строку в кавычках. Причём кавычки могут быть одинарными `subject:'...'` или двойными `subject:"..."` -- и то и другое должно искаться корректно. -Как такие строки искать? +Как такие строки искать? -Можно в регэкспе предусмотреть произвольные кавычки: `['"](.*?)['"]`. Такой регэксп найдёт строки вида "...", '...', но он даст неверный ответ в случае, если одна кавычка ненароком оказалась внутри другой, как например в строке "She's the one!": +Можно в регэкспе предусмотреть произвольные кавычки: `pattern:['"](.*?)['"]`. Такой регэксп найдёт строки вида `match:"..."`, `match:'...'`, но он даст неверный ответ в случае, если одна кавычка ненароком оказалась внутри другой, как например в строке `subject:"She's the one!"`: -```js -//+ run -str = "He said: \"She's the one!\"."; +```js run +var str = "He said: \"She's the one!\"."; -reg = /['"](.*?)['"]/g; +var reg = /['"](.*?)['"]/g; // Результат не соответствует замыслу alert( str.match(reg) ); // "She' ``` -Как видно, регэксп нашёл открывающую кавычку ", затем текст, вплоть до новой кавычки ', которая закрывает соответствие. +Как видно, регэксп нашёл открывающую кавычку `match:"`, затем текст, вплоть до новой кавычки `match:'`, которая закрывает соответствие. Для того, чтобы попросить регэксп искать закрывающую кавычку -- такую же, как открывающую, мы обернём её в скобочную группу и используем обратную ссылку на неё: -```js -//+ run -str = "He said: \"She's the one!\"."; +```js run +var str = "He said: \"She's the one!\"."; -reg = /(['"])(.*?)\1/g; +var reg = /(['"])(.*?)\1/g; alert( str.match(reg) ); // "She's the one!" ``` -Теперь работает верно! Движок регулярных выражений, найдя первое скобочное выражение -- кавычку (['"]), запоминает его и далее \1 означает "найти то же самое, что в первой скобочной группе". +Теперь работает верно! Движок регулярных выражений, найдя первое скобочное выражение -- кавычку `pattern:(['"])`, запоминает его и далее `pattern:\1` означает "найти то же самое, что в первой скобочной группе". Обратим внимание на два нюанса: -
      -
    • Чтобы использовать скобочную группу в строке замены -- нужно использовать ссылку вида `$1`, а в шаблоне -- обратный слэш: `\1`.
    • -
    • Чтобы в принципе иметь возможность обратиться к скобочной группе -- не важно откуда, она не должна быть исключена из запоминаемых при помощи `?:`. Скобочные группы вида `(?:...)` не участвуют в нумерации.
    • -
    - +- Чтобы использовать скобочную группу в строке замены -- нужно использовать ссылку вида `$1`, а в шаблоне -- обратный слэш: `\1`. +- Чтобы в принципе иметь возможность обратиться к скобочной группе -- не важно откуда, она не должна быть исключена из запоминаемых при помощи `?:`. Скобочные группы вида `(?:...)` не участвуют в нумерации. + diff --git a/10-regular-expressions-javascript/9-regexp-alternation/1-find-programming-language/solution.md b/10-regular-expressions-javascript/9-regexp-alternation/1-find-programming-language/solution.md index 483f818e..289ba6d0 100644 --- a/10-regular-expressions-javascript/9-regexp-alternation/1-find-programming-language/solution.md +++ b/10-regular-expressions-javascript/9-regexp-alternation/1-find-programming-language/solution.md @@ -2,8 +2,7 @@ Если перечислить языки один за другим через `|`, то получится совсем не то: -```js -//+ run +```js run var reg = /Java|JavaScript|PHP|C|C\+\+/g; var str = "Java, JavaScript, PHP, C, C++"; @@ -11,26 +10,24 @@ var str = "Java, JavaScript, PHP, C, C++"; alert( str.match(reg) ); // Java,Java,PHP,C,C ``` -Как видно, движок регулярных выражений ищет альтернации в порядке их перечисления. То есть, он сначала смотрит, есть ли Java, а если нет -- ищет JavaScript. +Как видно, движок регулярных выражений ищет альтернации в порядке их перечисления. То есть, он сначала смотрит, есть ли `match:Java`, а если нет -- ищет `match:JavaScript`. -Естественно, при этом JavaScript не будет найдено никогда. +Естественно, при этом `match:JavaScript` не будет найдено никогда. -То же самое -- с языками C и C++. +То же самое -- с языками `match:C` и `match:C++`. Есть два решения проблемы: -
      -
    1. Поменять порядок, чтобы более длинное совпадение проверялось первым: JavaScript|Java|C\+\+|C|PHP.
    2. -
    3. Соединить длинный вариант с коротким: Java(Script)?|C(\+\+)?|PHP.
    4. -
    +1. Поменять порядок, чтобы более длинное совпадение проверялось первым: `pattern:JavaScript|Java|C\+\+|C|PHP`. +2. Соединить длинный вариант с коротким: `pattern:Java(Script)?|C(\+\+)?|PHP`. В действии: -```js -//+ run +```js run var reg = /Java(Script)?|C(\+\+)?|PHP/g; var str = "Java, JavaScript, PHP, C, C++"; alert( str.match(reg) ); // Java,JavaScript,PHP,C,C++ ``` + diff --git a/10-regular-expressions-javascript/9-regexp-alternation/2-match-quoted-string/solution.md b/10-regular-expressions-javascript/9-regexp-alternation/2-match-quoted-string/solution.md index c959b6fa..ece03804 100644 --- a/10-regular-expressions-javascript/9-regexp-alternation/2-match-quoted-string/solution.md +++ b/10-regular-expressions-javascript/9-regexp-alternation/2-match-quoted-string/solution.md @@ -1,19 +1,17 @@ -Решение задачи: /"(\\.|[^"\\])*"/g. +Решение задачи: `pattern:/"(\\.|[^"\\])*"/g`. То есть: -
      -
    • Сначала ищем кавычку "
    • -
    • Затем, если далее слэш \\ (удвоение слэша -- техническое, для вставки в регэксп, на самом деле там один слэш), то после него также подойдёт любой символ (точка).
    • -
    • Если не слэш, то берём любой символ, кроме кавычек (которые будут означать конец строки) и слэша (чтобы предотвратить одинокие слэши, сам по себе единственный слэш не нужен, он должен экранировать какой-то символ) [^"\\]
    • -
    • ...И так жадно, до закрывающей кавычки.
    • -
    + +- Сначала ищем кавычку `pattern:"` +- Затем, если далее слэш `pattern:\\` (удвоение слэша -- техническое, для вставки в регэксп, на самом деле там один слэш), то после него также подойдёт любой символ (точка). +- Если не слэш, то берём любой символ, кроме кавычек (которые будут означать конец строки) и слэша (чтобы предотвратить одинокие слэши, сам по себе единственный слэш не нужен, он должен экранировать какой-то символ) `pattern:[^"\\]` +- ...И так жадно, до закрывающей кавычки. В действии: -```js -//+ run +```js run var re = /"(\\.|[^"\\])*"/g; var str = '.. "test me" .. "Скажи \\"Привет\\"!" .. "\\r\\n\\\\" ..'; alert( str.match(re) ); // "test me","Скажи \"Привет\"!","\r\n\\" -``` \ No newline at end of file +``` diff --git a/10-regular-expressions-javascript/9-regexp-alternation/2-match-quoted-string/task.md b/10-regular-expressions-javascript/9-regexp-alternation/2-match-quoted-string/task.md index 4db27891..2bde0073 100644 --- a/10-regular-expressions-javascript/9-regexp-alternation/2-match-quoted-string/task.md +++ b/10-regular-expressions-javascript/9-regexp-alternation/2-match-quoted-string/task.md @@ -1,8 +1,8 @@ # Найдите строки в кавычках -Найдите в тексте при помощи регэкспа строки в двойных кавычках "...". +Найдите в тексте при помощи регэкспа строки в двойных кавычках `subject:"..."`. -В строке поддерживается экранирование при помощи слеша -- примерно в таком же виде, как в обычных строках JavaScript. То есть, строка может содержать любые символы, экранированные слэшем, в частности: \", \n, и даже сам слэш в экранированном виде: \\. +В строке поддерживается экранирование при помощи слеша -- примерно в таком же виде, как в обычных строках JavaScript. То есть, строка может содержать любые символы, экранированные слэшем, в частности: `subject:\"`, `subject:\n`, и даже сам слэш в экранированном виде: `subject:\\`. Здесь особо важно, что двойная кавычка после слэша не оканчивает строку, а считается её частью. В этом и состоит основная сложность задачи, которая без этого условия была бы элементарной. @@ -16,11 +16,10 @@ Заметим, что в JavaScript такие строки удобнее всего задавать в одинарных кавычках, и слеши придётся удвоить (в одинарных кавычках они являются экранирующими символами): Пример задания тестовой строки в JavaScript: -```js -//+ run -var str = ' .. "test me" .. "Скажи \\"Привет\\"!" .. "\\r\\n\\\\" .. '; +```js run +var str = ' .. "test me" .. "Скажи \\"Привет\\"!" .. "\\r\\n\\\\" .. '; // эта строка будет такой: -alert(str); // .. "test me" .. "Скажи \"Привет\"!" .. "\r\n\\" .. +alert(str); // .. "test me" .. "Скажи \"Привет\"!" .. "\r\n\\" .. ``` diff --git a/10-regular-expressions-javascript/9-regexp-alternation/3-match-exact-tag/solution.md b/10-regular-expressions-javascript/9-regexp-alternation/3-match-exact-tag/solution.md index 0a422af1..e8895fdd 100644 --- a/10-regular-expressions-javascript/9-regexp-alternation/3-match-exact-tag/solution.md +++ b/10-regular-expressions-javascript/9-regexp-alternation/3-match-exact-tag/solution.md @@ -1,16 +1,15 @@ -Начало шаблона очевидно: ``pattern``, так как ``match`` удовлетворяет этому регэкспу. +А вот дальше... Мы не можем написать просто `pattern:`, так как `match:` удовлетворяет этому регэкспу. -Нужно уточнить его. После ``match`|\s.*?>)`. +На языке регэкспов: `pattern:|\s.*?>)`. В действии: -```js -//+ run +```js run var re = /|\s.*?>)/g; alert( " -[/head] \ No newline at end of file diff --git a/2-ui/1-document/12-multi-insert/head.html b/2-ui/1-document/12-multi-insert/head.html new file mode 100644 index 00000000..cf6d5425 --- /dev/null +++ b/2-ui/1-document/12-multi-insert/head.html @@ -0,0 +1,87 @@ + + + \ No newline at end of file diff --git a/2-ui/1-document/13-document-write/article.md b/2-ui/1-document/13-document-write/article.md index ae83994f..82d1c968 100644 --- a/2-ui/1-document/13-document-write/article.md +++ b/2-ui/1-document/13-document-write/article.md @@ -3,15 +3,16 @@ Метод `document.write` -- один из наиболее древних методов добавления текста к документу. У него есть существенные ограничения, поэтому он используется редко, но по своей сути он совершенно уникален и иногда, хоть и редко, может быть полезен. + [cut] -## Как работает document.write + +## Как работает document.write Метод `document.write(str)` работает только пока HTML-страница находится в процессе загрузки. Он дописывает текст в текущее место HTML ещё до того, как браузер построит из него DOM. HTML-документ ниже будет содержать `1 2 3`. -```html - +```html run 1 @@ -59,29 +59,24 @@ HTML-документ ниже будет содержать `1 2 3`. Текущая страница, скорее всего, уже загрузилась, поэтому если вы нажмёте на эту кнопку -- её содержимое удалится: -[pre no-typography] -[/pre] Из-за этой особенности `document.write` для загруженных документов не используют. -[warn header="XHTML и `document.write`"] -В некоторых современных браузерах при получении страницы с заголовком `Content-Type: text/xml` или `Content-Type: text/xhtml+xml` включается "XML-режим" чтения документа. Метод `document.write` при этом не работает. +```warn header="XHTML и `document.write`" +В некоторых современных браузерах при получении страницы с заголовком `Content-Type: text/xml` или `Content-Type: text/xhtml+xml` включается "XML-режим" чтения документа. Метод `document.write` при этом не работает. Это лишь одна из причин, по которой XML-режим обычно не используют. -[/warn] - +``` ## Преимущества перед innerHTML -Метод `document.write` -- динозавр, он существовал десятки миллионов лет назад. С тех пор, как появился и стал стандартным метод `innerHTML`, нужда в нём возникает редко, но некоторые преимущества, всё же, есть. +Метод `document.write` -- динозавр, он существовал десятки миллионов лет назад. С тех пор, как появился и стал стандартным метод `innerHTML`, нужда в нём возникает редко, но некоторые преимущества всё же есть. -
      -
    • Метод `document.write` работает быстрее, фактически это самый быстрый способ добавить на страницу текст, сгенерированный скриптом. +- Метод `document.write` работает быстрее, фактически это самый быстрый способ добавить на страницу текст, сгенерированный скриптом. -Это естественно, ведь он не модифицирует существующий DOM, а пишет в текст страницы до его генерации.
    • -
    • Метод `document.write` вставляет любой текст на страницу "как есть", в то время как `innerHTML` может вписать лишь валидный HTML (при попытке подсунуть невалидный -- браузер скорректирует его).
    • -
    + Это естественно, ведь он не модифицирует существующий DOM, а пишет в текст страницы до его генерации. +- Метод `document.write` вставляет любой текст на страницу "как есть", в то время как `innerHTML` может вписать лишь валидный HTML (при попытке подсунуть невалидный -- браузер скорректирует его). Эти преимущества являются скорее средством оптимизации, которое нужно использовать именно там, где подобная оптимизация нужна или уместна. @@ -101,8 +96,8 @@ HTML-документ ниже будет содержать `1 2 3`. ``` -[smart] -Закрывающий тег </script> в строке разделён, чтобы браузер не увидел `` и не посчитал его концом скрипта. +````smart +Закрывающий тег </script> в строке разделён, чтобы браузер не увидел `` и не посчитал его концом скрипта. Также используют запись: @@ -110,11 +105,11 @@ HTML-документ ниже будет содержать `1 2 3`. document.write('`: обратный слеш `\` обычно используется для вставки спецсимволов типа `\n`, а если такого спецсимвола нет, в данном случае `\/` не является спецсимволом, то будет проигнорирован. Так что получается такой альтернативный способ безопасно вставить строку ``. -[/smart] +Здесь `<\/script>` вместо ``: обратный слеш `\` обычно используется для вставки спецсимволов типа `\n`, а если такого спецсимвола нет, в данном случае `\/` не является спецсимволом, то будет проигнорирован. Так что получается такой альтернативный способ безопасно вставить строку ``. +```` Сервер, получив запрос с такими параметрами, обрабатывает его и, учитывая переданную информацию, генерирует текст скрипта, в котором обычно есть какой-то другой `document.write`, рисующий на этом месте баннер. - + **Проблема здесь в том, что загрузка такого скрипта блокирует отрисовку всей страницы.** То есть, дело даже не в самом `document.write`, а в том, что в страницу вставляется сторонний скрипт, а браузер устроен так, что пока он его не загрузит и не выполнит -- он не будет дальше строить DOM и показывать документ. @@ -127,23 +122,21 @@ document.write(' @@ -90,33 +86,30 @@ setTimeout(function() { Обратите внимание на то, как браузер "распаковал" свойство `style.margin`, предоставив для чтения `style.marginTop`. То же самое произойдет и для `border`, `background` и т.д. - -[warn header="Свойство `style` мы используем лишь там, где не работают классы"] +```warn header="Свойство `style` мы используем лишь там, где не работают классы" В большинстве случаев внешний вид элементов задаётся классами. А JavaScript добавляет или удаляет их. Такой код красив и гибок, дизайн можно легко изменять. Свойство `style` нужно использовать лишь там, где классы не подходят, например если точное значение цвета/отступа/высоты вычисляется в JavaScript. -[/warn] - +``` -### Строка стилей style.cssText +### Строка стилей style.cssText -Свойство `style` является специальным объектом, ему нельзя присваивать строку. +Свойство `style` является специальным объектом, ему нельзя присваивать строку. Запись `div.style="color:blue"` работать не будет. Но как же, всё-таки, поставить свойство стиля, если хочется задать его строкой? -Можно попробовать использовать атрибут: `elem.setAttribute("style", ...)`, но самым правильным и, главное, кросс-браузерным (с учётом старых IE) решением такой задачи будет использование свойства `style.cssText`. +Можно попробовать использовать атрибут: `elem.setAttribute("style", ...)`, но самым правильным и, главное, кросс-браузерным (с учётом старых IE) решением такой задачи будет использование свойства `style.cssText`. **Свойство `style.cssText` позволяет поставить стиль целиком в виде строки.** Например: -```html - +```html run
    Button
    ``` +```` -[/warn] +```smart header="Стили посещенных ссылок -- тайна!" +У посещенных ссылок может быть другой цвет, фон, чем у обычных. Это можно поставить в CSS с помощью псевдокласса `:visited`. +Но `getComputedStyle` не дает доступ к этой информации, чтобы произвольная страница не могла определить, посещал ли пользователь ту или иную ссылку. -[smart header="Стили посещенных ссылок -- тайна!"] -У посещенных ссылок может быть другой цвет, фон, чем у обычных. Это можно поставить в CSS с помощью псевдокласса `:visited`. - -Но `getComputedStyle` не дает доступ к этой информации, чтобы произвольная страница не могла определить, посещал ли пользователь ту или иную ссылку. - -Кроме того, большинство браузеров запрещают применять к `:visited` CSS-стили, которые могут изменить геометрию элемента, чтобы даже окольным путем нельзя было это понять. В целях безопасности. -[/smart] +Кроме того, большинство браузеров запрещают применять к `:visited` CSS-стили, которые могут изменить геометрию элемента, чтобы даже окольным путем нельзя было это понять. В целях безопасности. +``` ## currentStyle для IE8- @@ -265,8 +252,7 @@ function getStyle(elem) { Если вы откроете такой документ в IE8-, то размеры будут в процентах, а в современных браузерах -- в пикселях. -```html - +```html run height=100 -[/head] +Также некоторые виды элементов предоставляют дополнительные ссылки для большего удобства, например у таблиц есть свойства для доступа к строкам/ячейкам. diff --git a/2-ui/1-document/4-traversing-dom/dom-links-elements.png b/2-ui/1-document/4-traversing-dom/dom-links-elements.png index c1d1a0fe..e54d9e08 100644 Binary files a/2-ui/1-document/4-traversing-dom/dom-links-elements.png and b/2-ui/1-document/4-traversing-dom/dom-links-elements.png differ diff --git a/2-ui/1-document/4-traversing-dom/dom-links-elements@2x.png b/2-ui/1-document/4-traversing-dom/dom-links-elements@2x.png index 2f2b8313..40dad7b1 100644 Binary files a/2-ui/1-document/4-traversing-dom/dom-links-elements@2x.png and b/2-ui/1-document/4-traversing-dom/dom-links-elements@2x.png differ diff --git a/2-ui/1-document/4-traversing-dom/dom-links.png b/2-ui/1-document/4-traversing-dom/dom-links.png index d7a3ec1a..78ec491b 100644 Binary files a/2-ui/1-document/4-traversing-dom/dom-links.png and b/2-ui/1-document/4-traversing-dom/dom-links.png differ diff --git a/2-ui/1-document/4-traversing-dom/dom-links@2x.png b/2-ui/1-document/4-traversing-dom/dom-links@2x.png index d800f76e..9e07e3b6 100644 Binary files a/2-ui/1-document/4-traversing-dom/dom-links@2x.png and b/2-ui/1-document/4-traversing-dom/dom-links@2x.png differ diff --git a/2-ui/1-document/4-traversing-dom/head.html b/2-ui/1-document/4-traversing-dom/head.html new file mode 100644 index 00000000..5d763505 --- /dev/null +++ b/2-ui/1-document/4-traversing-dom/head.html @@ -0,0 +1,8 @@ + \ No newline at end of file diff --git a/2-ui/1-document/5-searching-elements-dom/1-find-elements/solution.md b/2-ui/1-document/5-searching-elements-dom/1-find-elements/solution.md index 210933da..9842595f 100644 --- a/2-ui/1-document/5-searching-elements-dom/1-find-elements/solution.md +++ b/2-ui/1-document/5-searching-elements-dom/1-find-elements/solution.md @@ -24,3 +24,4 @@ document.getElementsByName("info[0]")[0]; // 7 document.querySelector('form[name="search-person"] [name="info[0]"]'); ``` + diff --git a/2-ui/1-document/5-searching-elements-dom/1-find-elements/task.md b/2-ui/1-document/5-searching-elements-dom/1-find-elements/task.md index fa959a95..6cbdbfa9 100644 --- a/2-ui/1-document/5-searching-elements-dom/1-find-elements/task.md +++ b/2-ui/1-document/5-searching-elements-dom/1-find-elements/task.md @@ -1,20 +1,20 @@ -# Поиск элементов +importance: 4 + +--- -[importance 4] +# Поиск элементов Ниже находится документ с таблицей и формой. -Найдите (получите в переменную) в нём: +Найдите (получите в переменную) в нём: -
      -
    1. Все элементы `label` внутри таблицы. Должно быть 3 элемента.
    2. -
    3. Первую ячейку таблицы (со словом `"Возраст"`).
    4. -
    5. Вторую форму в документе.
    6. -
    7. Форму с именем `search`, без использования её позиции в документе.
    8. -
    9. Элемент `input` в форме с именем `search`. Если их несколько, то нужен первый.
    10. -
    11. Элемент с именем `info[0]`, без точного знания его позиции в документе.
    12. -
    13. Элемент с именем `info[0]`, внутри формы с именем `search-person`.
    14. -
    +1. Все элементы `label` внутри таблицы. Должно быть 3 элемента. +2. Первую ячейку таблицы (со словом `"Возраст"`). +3. Вторую форму в документе. +4. Форму с именем `search`, без использования её позиции в документе. +5. Элемент `input` в форме с именем `search`. Если их несколько, то нужен первый. +6. Элемент с именем `info[0]`, без точного знания его позиции в документе. +7. Элемент с именем `info[0]`, внутри формы с именем `search-person`. Используйте для этого консоль браузера, открыв страницу [table.html](table.html) в отдельном окне. diff --git a/2-ui/1-document/5-searching-elements-dom/2-tree-info/solution.md b/2-ui/1-document/5-searching-elements-dom/2-tree-info/solution.md index 55b45737..615c88d7 100644 --- a/2-ui/1-document/5-searching-elements-dom/2-tree-info/solution.md +++ b/2-ui/1-document/5-searching-elements-dom/2-tree-info/solution.md @@ -8,7 +8,7 @@ for (i = 0; i < lis.length; i++) { } ``` -В цикле для каждого `lis[i]` можно получить текст, используя свойство `firstChild`. Ведь первым в `
  • ` является как раз текстовый узел, содержащий текст названия. +В цикле для каждого `lis[i]` можно получить текст, используя свойство `firstChild`. Ведь первым в `
  • ` является как раз текстовый узел, содержащий текст названия. Также можно получить количество потомков, используя `lis[i].getElementsByTagName('li')`. diff --git a/2-ui/1-document/5-searching-elements-dom/2-tree-info/task.md b/2-ui/1-document/5-searching-elements-dom/2-tree-info/task.md index 5ffd808d..c1fdc1db 100644 --- a/2-ui/1-document/5-searching-elements-dom/2-tree-info/task.md +++ b/2-ui/1-document/5-searching-elements-dom/2-tree-info/task.md @@ -1,14 +1,15 @@ -# Дерево +importance: 5 + +--- -[importance 5] +# Дерево Есть дерево из тегов `
      /
    • `. Напишите код, который для каждого элемента `
    • ` выведет: -
        -
      1. Текст непосредственно в нём (без подразделов).
      2. -
      3. Количество вложенных в него элементов `
      4. ` -- всех, с учётом вложенных.
      5. -
      + +1. Текст непосредственно в нём (без подразделов). +2. Количество вложенных в него элементов `
    • ` -- всех, с учётом вложенных. [demo src="http://github.com/solution"] diff --git a/2-ui/1-document/5-searching-elements-dom/article.md b/2-ui/1-document/5-searching-elements-dom/article.md index d9faf1d1..dda0aed7 100644 --- a/2-ui/1-document/5-searching-elements-dom/article.md +++ b/2-ui/1-document/5-searching-elements-dom/article.md @@ -14,8 +14,7 @@ Например: -```html - +```html run
      Элемент
      @@ -34,9 +33,7 @@ Например: - -```html - +```html run
      Выделим этот элемент
      ``` -[smart header="Должен остаться только один"] +```smart header="Должен остаться только один" По стандарту значение `id` должно быть уникально, то есть в документе может быть только один элемент с данным `id`. И именно он будет возвращён. -Если в документе есть несколько элементов с уникальным `id`, то поведение неопределено. То есть, нет гарантии, что браузер вернёт именно первый или последний -- вернёт случайным образом. +Если в документе есть несколько элементов с уникальным `id`, то поведение неопределено. То есть, нет гарантии, что браузер вернёт именно первый или последний -- вернёт случайным образом. Поэтому стараются следовать правилу уникальности `id`. -[/smart] - +``` Далее в примерах я часто буду использовать прямое обращение через переменную, чтобы было меньше букв и проще было понять происходящее. Но предпочтительным методом является `document.getElementById`. - -## getElementsByTagName +## getElementsByTagName Метод `elem.getElementsByTagName(tag)` ищет все элементы с заданным тегом `tag` внутри элемента `elem` и возвращает их в виде списка. @@ -80,8 +75,7 @@ var elements = document.getElementsByTagName('div'); Например, найдём все элементы `input` внутри таблицы: -```html - +```html run height=50 @@ -124,13 +118,13 @@ document.getElementsByTagName('*'); elem.getElementsByTagName('*'); ``` -[warn header="Не забываем про букву `\"s\"`!"] +```warn header="Не забываем про букву `\"s\"`!" Одна из самых частых ошибок начинающих (впрочем, иногда и не только) -- это забыть букву `"s"`, то есть пробовать вызывать метод `getElementByTagName` вместо getElementsByTagName. Буква `"s"` не нужна там, где элемент только один, то есть в `getElementById`, в остальных методах она обязательна. -[/warn] +``` -[warn header="Возвращается коллекция, а не элемент"] +````warn header="Возвращается коллекция, а не элемент" Другая частая ошибка -- это код вида: ```js @@ -146,10 +140,9 @@ document.getElementsByTagName('input').value = 5; // работает document.getElementsByTagName('input')[0].value = 5; ``` +```` -[/warn] - -## getElementsByName +## document.getElementsByName Вызов `document.getElementsByName(name)` позволяет получить все элементы с данным атрибутом `name`. @@ -163,7 +156,7 @@ var elems = document.getElementsByName('age'); Используется этот метод весьма редко. -## getElementsByClassName +## getElementsByClassName Вызов `elem.getElementsByClassName(className)` возвращает коллекцию элементов с классом `className`. Находит элемент и в том случае, если у него несколько классов, а искомый - один из них. @@ -171,8 +164,7 @@ var elems = document.getElementsByName('age'); Например: -```html - +```html run height=50
      Статья
      Длинная статья
      @@ -182,8 +174,7 @@ var elems = document.getElementsByName('age'); ``` -Как и `getElementsByTagName`, этот метод может быть вызван и в контексте DOM-элемента и в контексте документа. - +Как и `getElementsByTagName`, этот метод может быть вызван и в контексте DOM-элемента, и в контексте документа. ## querySelectorAll [#querySelectorAll] @@ -195,8 +186,7 @@ var elems = document.getElementsByName('age'); Следующий запрос получает все элементы `LI`, которые являются последними потомками в `UL`: -```html - +```html run
      • Этот
      • тест
      • @@ -216,17 +206,23 @@ var elems = document.getElementsByName('age'); ``` +```smart header="Псевдо-класс тоже работает" +Псевдо-классы в CSS-селекторе, в частности `:hover` и `:active`, также поддерживаются. Например, `document.querySelectorAll(':hover')` вернёт список, в порядке вложенности, из текущих элементов под курсором мыши. +``` + ## querySelector [#querySelector] Вызов `elem.querySelector(css)` возвращает не все, а только первый элемент, соответствующий CSS-селектору `css`. Иначе говоря, результат -- такой же, как и при `elem.querySelectorAll(css)[0]`, но в последнем вызове сначала ищутся все элементы, а потом берётся первый, а в `elem.querySelector(css)` ищется только первый, то есть он эффективнее. +Этот метод часто используется, когда мы заведомо знаем, что подходящий элемент только один, и хотим получить в переменную сразу его. + ## matches Предыдущие методы искали по DOM. -Метод [elem.matches(css)](http://dom.spec.whatwg.org/#dom-element-matches) ничего не ищет, а проверяет, удовлетворяет ли `elem` селектору `css`. Он возвращает `true` либо `false`. +Метод [elem.matches(css)](http://dom.spec.whatwg.org/#dom-element-matches) ничего не ищет, а проверяет, удовлетворяет ли `elem` селектору `css`. Он возвращает `true` либо `false`. Не поддерживается в IE8-. @@ -236,8 +232,7 @@ var elems = document.getElementsByName('age'); Например: -```html - +```html run ... ... @@ -260,12 +255,11 @@ var elems = document.getElementsByName('age'); Иначе говоря, метод `closest` бежит от текущего элемента вверх по цепочке родителей и проверяет, подходит ли элемент под указанный CSS-селектор. Если подходит -- останавливается и возвращает его. -Он самый новый из методов, рассмотренных в этой главе, поэтому старые браузеры его слабо поддерживают. Это, конечно, легко поправимо, как мы увидим позже в главе [](/dom-polyfill). +Он самый новый из методов, рассмотренных в этой главе, поэтому старые браузеры его слабо поддерживают. Это, конечно, легко поправимо, как мы увидим позже в главе . Пример использования (браузер должен поддерживать `closest`): -```html - +```html run
        • Глава I
            @@ -275,7 +269,6 @@ var elems = document.getElementsByName('age');
          - ``` -## XPath в современных браузерах +## XPath в современных браузерах -Для полноты картины рассмотрим ещё один способ поиска, который обычно используется в XML. Это язык запросов XPath. +Для полноты картины рассмотрим ещё один способ поиска, который обычно используется в XML. Это язык запросов XPath. Он очень мощный, во многом мощнее CSS, но сложнее. Например, запрос для поиска элементов `H2`, содержащих текст `"XPath"`, будет выглядеть так: `//h2[contains(., "XPath")]`. @@ -301,9 +294,8 @@ var elems = document.getElementsByName('age'); Найдем заголовки с текстом `XPath` в текущем документе: -```js -//+ run no-beautify -var result = document.evaluate("//h2[contains(., 'XPath')]", document.documentElement, null, +```js run no-beautify +var result = document.evaluate("//h2[contains(., 'XPath')]", document.documentElement, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); for (var i = 0; i < result.snapshotLength; i++) { @@ -311,7 +303,7 @@ for (var i = 0; i < result.snapshotLength; i++) { } ``` -IE тоже поддерживает XPath, но эта поддержка не соответствует стандарту и работает только для XML-документов, например, полученных с помощью `XMLHTTPRequest` (AJAX). Для обычных же HTML-документов XPath в IE не поддерживается. +IE тоже поддерживает XPath, но эта поддержка не соответствует стандарту и работает только для XML-документов, например, полученных с помощью `XMLHTTPRequest` (AJAX). Для обычных же HTML-документов XPath в IE не поддерживается. Так как XPath сложнее и длиннее CSS, то используют его очень редко. @@ -329,37 +321,37 @@ IE тоже поддерживает XPath, но эта поддержка не
      - - + + - - + + - - + + - + - + - + @@ -370,13 +362,8 @@ IE тоже поддерживает XPath, но эта поддержка не Практика показывает, что в 95% ситуаций достаточно `querySelector/querySelectorAll`. Хотя более специализированные методы `getElement*` работают чуть быстрее, но разница в миллисекунду-другую редко играет роль. Кроме того: -
        -
      • Есть метод `elem.matches(css)`, который проверяет, удовлетворяет ли элемент CSS-селектору. Он поддерживается большинством браузеров в префиксной форме (`ms`, `moz`, `webkit`).
      • -
      • Язык запросов XPath поддерживается большинством браузеров, кроме IE, даже 9й версии, но `querySelector` удобнее. Поэтому XPath используется редко.
      • -
      - - - - +- Есть метод `elem.matches(css)`, который проверяет, удовлетворяет ли элемент CSS-селектору. Он поддерживается большинством браузеров в префиксной форме (`ms`, `moz`, `webkit`). +- Метод `elem.closest(css)` ищет ближайший элемент выше по иерархии DOM, подходящий под CSS-селектор css. Сам элемент тоже включается в поиск. +- Язык запросов XPath поддерживается большинством браузеров, кроме IE, даже 9-й версии, но `querySelector` удобнее. Поэтому XPath используется редко. diff --git a/2-ui/1-document/6-searching-elements-internals/1-collection-length-after-remove/solution.md b/2-ui/1-document/6-searching-elements-internals/1-collection-length-after-remove/solution.md index 90507daa..83d4b63d 100644 --- a/2-ui/1-document/6-searching-elements-internals/1-collection-length-after-remove/solution.md +++ b/2-ui/1-document/6-searching-elements-internals/1-collection-length-after-remove/solution.md @@ -1,9 +1,8 @@ -# Ответ на первый вопрос +# Ответ на первый вопрос Ответ: 0, пустая коллекция. -```html - +```html run
      Ваш возраст:
      `getElementById``id`getElementByIdid - везде
      `getElementsByName``name`getElementsByNamename - везде
      `getElementsByTagName`тег или `'*'`getElementsByTagNameтег или '*' везде
      `getElementsByClassName`getElementsByClassName классу кроме IE8-
      `querySelector`querySelector CSS-селектор везде
      `querySelectorAll`querySelectorAll CSS-селектор везде
      ` и ловить все события в нём. Но события `mouseenter/leave` не всплывают, они срабатывают именно на том элементе, на котором стоит обработчик и только на нём. +Естественное решение -- поставить обработчик на верхний элемент `
      ` и ловить все события в нём. Но события `mouseenter/leave` не всплывают, они срабатывают именно на том элементе, на котором стоит обработчик и только на нём. Если обработчики `mouseenter/leave` стоят на `
      `, то они сработают при входе-выходе из таблицы, но получить из них какую-то информацию о переходах по её ячейкам невозможно. @@ -170,33 +160,32 @@ table.onmouseout = function(event) { target.style.background = ''; }; ``` -[online] +```online [codetabs height=480 src="http://github.com/mouseenter-mouseleave-delegation"] - -[/online] +``` В таком виде они срабатывают при переходе с любого элемента на любой. Нас же интересуют переходы строго с одной ячейки ` - - - + + - + - - - + + - - - +
      ` на другую. Нужно фильтровать события. Один из вариантов: -
        -
      • Запоминать текущий подсвеченный `
      ` в переменной. -
    • При `mouseover` проверять, остались ли мы внутри того же `
    • `, если да -- игнорировать. -
    • При `mouseout` проверять, если мы ушли с текущего `
    • `, а не перешли куда-то внутрь него, то игнорировать. - -[offline] -Детали кода вы можете посмотреть в [edit src="http://github.com/mouseenter-mouseleave-delegation-2"]полном примере[/edit]. -[/offline] +- Запоминать текущий подсвеченный `` в переменной. +- При `mouseover` проверять, остались ли мы внутри того же ``, если да -- игнорировать. +- При `mouseout` проверять, если мы ушли с текущего ``, а не перешли куда-то внутрь него, то игнорировать. + +```offline +Детали кода вы можете посмотреть в [полном примере](sandbox:mouseenter-mouseleave-delegation-2). +``` -[online] +```online Детали кода вы можете посмотреть в примере ниже, который демонстрирует этот подход: [codetabs height=380 src="http://github.com/mouseenter-mouseleave-delegation-2"] -Попробуйте по-разному, быстро или медленно заходить и выходить в ячейки таблицы. Обработчики `mouseover/mouseout` стоят на `table`, но при помощи делегирования корректно обрабатывают вход-выход.[/online] +Попробуйте по-разному, быстро или медленно заходить и выходить в ячейки таблицы. Обработчики `mouseover/mouseout` стоят на `table`, но при помощи делегирования корректно обрабатывают вход-выход. +``` ## Особенности IE8- @@ -216,14 +205,10 @@ function fixRelatedTarget(e) { ## Итого У `mouseover, mousemove, mouseout` есть следующие особенности: -
        -
      • При быстром движении мыши события `mouseover, mousemove, mouseout` могут пропускать промежуточные элементы.
      • -
      • События `mouseover` и `mouseout` -- единственные, у которых есть вторая цель: `relatedTarget` (`toElement/fromElement` в IE).
      • -
      • События `mouseover/mouseout` подразумевают, что курсор находится над одним, самым глубоким элементом. Они срабатывают при переходе с родительского элемента на дочерний.
      • -
      - -События `mouseenter/mouseleave` не всплывают и не учитывают переходы внутри элемента. - +- При быстром движении мыши события `mouseover, mousemove, mouseout` могут пропускать промежуточные элементы. +- События `mouseover` и `mouseout` -- единственные, у которых есть вторая цель: `relatedTarget` (`toElement/fromElement` в IE). +- События `mouseover/mouseout` подразумевают, что курсор находится над одним, самым глубоким элементом. Они срабатывают при переходе с родительского элемента на дочерний. +События `mouseenter/mouseleave` не всплывают и не учитывают переходы внутри элемента. diff --git a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout-from-outside.png b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout-from-outside.png index ffc5c06b..3a56239b 100644 Binary files a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout-from-outside.png and b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout-from-outside.png differ diff --git a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout-from-outside@2x.png b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout-from-outside@2x.png index a0154d63..4b852fa1 100644 Binary files a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout-from-outside@2x.png and b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout-from-outside@2x.png differ diff --git a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout-over-elems.png b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout-over-elems.png index e3bef0ed..5df6be24 100644 Binary files a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout-over-elems.png and b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout-over-elems.png differ diff --git a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout-over-elems@2x.png b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout-over-elems@2x.png index e64afd24..50a381b9 100644 Binary files a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout-over-elems@2x.png and b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout-over-elems@2x.png differ diff --git a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout.png b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout.png index c72dd275..024a1a7f 100644 Binary files a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout.png and b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout.png differ diff --git a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout@2x.png b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout@2x.png index c60b440d..4d0ca726 100644 Binary files a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout@2x.png and b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout@2x.png differ diff --git a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-to-child.png b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-to-child.png index 63377d1c..cdc37a2d 100644 Binary files a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-to-child.png and b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-to-child.png differ diff --git a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-to-child@2x.png b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-to-child@2x.png index 05bc2c4b..3f2ec02b 100644 Binary files a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-to-child@2x.png and b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-to-child@2x.png differ diff --git a/2-ui/3-event-details/4-drag-and-drop/1-slider/task.md b/2-ui/3-event-details/4-drag-and-drop/1-slider/task.md index 58537031..6f165a41 100644 --- a/2-ui/3-event-details/4-drag-and-drop/1-slider/task.md +++ b/2-ui/3-event-details/4-drag-and-drop/1-slider/task.md @@ -1,14 +1,16 @@ -# Слайдер +importance: 5 -[importance 5] +--- + +# Слайдер Создайте слайдер: + [iframe src="http://github.com/solution" height=60 border=1] Захватите мышкой синий бегунок и двигайте его, чтобы увидеть в работе. Важно: -
        -
      • Слайдер должен нормально работать при резком движении мыши влево или вправо, за пределы полосы. При этом бегунок должен останавливаться четко в нужном конце полосы.
      • -
      • При нажатом бегунке мышь может выходить за пределы полосы слайдера, но слайдер пусть все равно работает (это удобно для пользователя).
      • -
      \ No newline at end of file + +- Слайдер должен нормально работать при резком движении мыши влево или вправо, за пределы полосы. При этом бегунок должен останавливаться четко в нужном конце полосы. +- При нажатом бегунке мышь может выходить за пределы полосы слайдера, но слайдер пусть все равно работает (это удобно для пользователя). diff --git a/2-ui/3-event-details/4-drag-and-drop/2-drag-heroes/solution.view/soccer.js b/2-ui/3-event-details/4-drag-and-drop/2-drag-heroes/solution.view/soccer.js index e0998e7a..b2765c0b 100644 --- a/2-ui/3-event-details/4-drag-and-drop/2-drag-heroes/solution.view/soccer.js +++ b/2-ui/3-event-details/4-drag-and-drop/2-drag-heroes/solution.view/soccer.js @@ -88,8 +88,8 @@ document.onmousedown = function(e) { // зажать в границах экрана по горизонтали // здесь прокрутки нет, всё просто if (newX < 0) newX = 0; - if (newX > document.documentElement.clientWidth - dragElement.offsetHeight) { - newX = document.documentElement.clientWidth - dragElement.offsetHeight; + if (newX > document.documentElement.clientWidth - dragElement.offsetWidth) { + newX = document.documentElement.clientWidth - dragElement.offsetWidth; } dragElement.style.left = newX + 'px'; diff --git a/2-ui/3-event-details/4-drag-and-drop/2-drag-heroes/task.md b/2-ui/3-event-details/4-drag-and-drop/2-drag-heroes/task.md index f87efbc2..78b39823 100644 --- a/2-ui/3-event-details/4-drag-and-drop/2-drag-heroes/task.md +++ b/2-ui/3-event-details/4-drag-and-drop/2-drag-heroes/task.md @@ -1,20 +1,21 @@ -# Расставить супергероев по полю +importance: 5 + +--- -[importance 5] +# Расставить супергероев по полю В этой задаче вы можете проверить своё понимание сразу нескольких аспектов Drag'n'Drop. Сделайте так, чтобы элементы с классом `draggable` можно было переносить мышкой. По окончании переноса элемент остаётся на том месте в документе, где его положили. Требования к реализации: -
        -
      • Должен быть 1 обработчик на `document`, использующий делегирование.
      • -
      • Если элементы подносят к вертикальным краям окна -- оно должно прокручиваться вниз/вверх.
      • -
      • Горизонтальной прокрутки в этой задаче не существует.
      • -
      • Элемент при переносе, даже при резких движениях мышкой, не должен попасть вне окна.
      • -
      + +- Должен быть 1 обработчик на `document`, использующий делегирование. +- Если элементы подносят к вертикальным краям окна -- оно должно прокручиваться вниз/вверх. +- Горизонтальной прокрутки в этой задаче не существует. +- Элемент при переносе, даже при резких движениях мышкой, не должен попасть вне окна. Футбольное поле в этой задаче слишком большое, чтобы показывать его здесь, поэтому откройте его, кликнув по ссылке ниже. Там же и подробное описание задачи (осторожно, винни-пух и супергерои!). -[demo src="http://github.com/solution"] +[demo src="http://github.com/solution"] diff --git a/2-ui/3-event-details/4-drag-and-drop/article.md b/2-ui/3-event-details/4-drag-and-drop/article.md index e271d988..c276ed1f 100644 --- a/2-ui/3-event-details/4-drag-and-drop/article.md +++ b/2-ui/3-event-details/4-drag-and-drop/article.md @@ -1,4 +1,4 @@ -# Мышь: Drag'n'Drop +# Мышь: Drag'n'Drop Drag'n'Drop -- это возможность захватить мышью элемент и перенести его. В свое время это было замечательным открытием в области интерфейсов, которое позволило упростить большое количество операций. @@ -8,26 +8,24 @@ Drag'n'Drop -- это возможность захватить мышью эл ## Отличия от HTML5 Drag'n'Drop -В современном стандарте HTML5 есть поддержка Drag'n'Drop при помощи [специальных событий](http://www.html5rocks.com/en/tutorials/dnd/basics/). +В современном стандарте HTML5 есть поддержка Drag'n'Drop при помощи [специальных событий](http://www.html5rocks.com/en/tutorials/dnd/basics/). Эти события поддерживаются всеми современными браузерами, и у них есть свои интересные особенности, например, можно перетащить файл в браузер, так что JS получит доступ к его содержимому. Они заслуживают отдельного рассмотрения. Но в плане именно Drag'n'Drop у них есть существенные ограничения. Например, нельзя организовать перенос "только по горизонтали" или "только по вертикали". Также нельзя ограничить перенос внутри заданной зоны. Есть и другие интерфейсные задачи, которые такими встроенными событиями нереализуемы. -Поэтому здесь мы будем рассматривать Drag'n'Drop при помощи событий мыши. +Поэтому здесь мы будем рассматривать Drag'n'Drop при помощи событий мыши. Рассматриваемые приёмы, вообще говоря, применяются не только в Drag'n'Drop, но и для любых интерфейсных взаимодействий вида "захватить - потянуть - отпустить". ## Алгоритм Drag'n'Drop - + Основной алгоритм Drag'n'Drop выглядит так: -
        -
      1. Отслеживаем нажатие кнопки мыши на переносимом элементе при помощи события `mousedown`.
      2. -
      3. При нажатии -- подготовить элемент к перемещению.
      4. -
      5. Далее отслеживаем движение мыши через mousemove и передвигаем переносимый элемент на новые координаты путём смены `left/top` и `position:absolute`.
      6. -
      7. При отпускании кнопки мыши, то есть наступлении события mouseup -- остановить перенос элемента и произвести все действия, связанные с окончанием Drag'n'Drop.
      8. -
      + 1. Отслеживаем нажатие кнопки мыши на переносимом элементе при помощи события `mousedown`. + 2. При нажатии -- подготовить элемент к перемещению. +3. Далее отслеживаем движение мыши через mousemove и передвигаем переносимый элемент на новые координаты путём смены `left/top` и `position:absolute`. + 4. При отпускании кнопки мыши, то есть наступлении события mouseup -- остановить перенос элемента и произвести все действия, связанные с окончанием Drag'n'Drop. В следующем примере эти шаги реализованы для переноса мяча: @@ -45,7 +43,7 @@ ball.onmousedown = function(e) { // 1. отследить нажатие*!* ball.style.zIndex = 1000; // показывать мяч над другими элементами - // передвинуть мяч под координаты курсора + // передвинуть мяч под координаты курсора // и сдвинуть на половину ширины/высоты для центрирования function moveAt(e) { ball.style.left = e.pageX - ball.offsetWidth / 2 + 'px'; @@ -67,13 +65,13 @@ ball.onmousedown = function(e) { // 1. отследить нажатие*!* Если запустить этот код, то мы заметим нечто странное. При начале переноса мяч "раздваивается" и переносится не сам мяч, а его "клон". -[online] +```online Это можно увидеть в действии внутри ифрейма: [iframe src="http://github.com/ball" height=230] Попробуйте перенести мяч мышкой и вы увидите описанное, довольно-таки странное, поведение. -[/online] +``` Это потому, что браузер имеет свой собственный Drag'n'Drop, который автоматически запускается и вступает в конфликт с нашим. Это происходит именно для картинок и некоторых других элементов. @@ -87,19 +85,19 @@ ball.ondragstart = function() { Теперь всё будет в порядке. -[online] +```online В действии (внутри ифрейма): [iframe src="http://github.com/ball2" height=230] -[/online] +``` -Ещё одна особенность правильного Drag'd'Drop -- событие `mousemove` отслеживается на `document`, а не на `ball`. +Ещё одна особенность правильного Drag'n'Drop -- событие `mousemove` отслеживается на `document`, а не на `ball`. С первого взгляда кажется, что мышь всегда над мячом и обработчик `mousemove` можно повесить на сам мяч, а не на документ. -Однако, на самом деле мышь во время переноса не всегда над мячом. +Однако, на самом деле мышь во время переноса не всегда над мячом. -Вспомним, событие `mousemove` возникает хоть и часто, но не для каждого пикселя. Быстрое движение курсора вызовет `mousemove` уже не над мячом, а, например, в дальнем конце страницы. +Вспомним, событие `mousemove` возникает хоть и часто, но не для каждого пикселя. Быстрое движение курсора вызовет `mousemove` уже не над мячом, а, например, в дальнем конце страницы. Вот почему мы должны отслеживать `mousemove` на всём `document`. @@ -114,44 +112,37 @@ self.style.top = e.pageY - ball.offsetHeight / 2 + 'px'; Если поставить `left/top` ровно в `pageX/pageY`, то мячик прилипнет верхним-левым углом к курсору мыши. Будет некрасиво. Поэтому мы сдвигаем его на половину высоты/ширины, чтобы был центром под мышью. Уже лучше. -Но не идеально. В частности, в самом начале переноса, особенно если мячик "взят" за край -- он резко "прыгает" центром под курсор мыши. +Но не идеально. В частности, в самом начале переноса, особенно если мячик "взят" за край -- он резко "прыгает" центром под курсор мыши. **Для правильного переноса необходимо, чтобы изначальный сдвиг курсора относительно элемента сохранялся.** Где захватили, за ту "часть элемента" и переносим: - +![](ball_shift.png) -
        -
      1. Когда человек нажимает на мячик `mousedown` -- курсор сдвинут относительно левого-верхнего угла мяча на расстояние, которое мы обозначим `shiftX/shiftY`. И нужно при переносе сохранить этот сдвиг. +1. Когда человек нажимает на мячик `mousedown` -- курсор сдвинут относительно левого-верхнего угла мяча на расстояние, которое мы обозначим `shiftX/shiftY`. И нужно при переносе сохранить этот сдвиг. -Получить значения `shiftX/shiftY` легко: достаточно вычесть из координат курсора `pageX/pageY` левую-верхнюю границу мячика, полученную при помощи функции [getCoords](#getCoords). + Получить значения `shiftX/shiftY` легко: достаточно вычесть из координат курсора `pageX/pageY` левую-верхнюю границу мячика, полученную при помощи функции [getCoords](info:coordinates-document#getCoords). -**При Drag'n'Drop мы везде используем координаты относительно документа, так как они подходят в большем количестве ситуаций.** + **При Drag'n'Drop мы везде используем координаты относительно документа, так как они подходят в большем количестве ситуаций.** -Конечно же, не проблема перейти к координатам относительно окна, если это понадобится. Достаточно использовать `position:fixed`, `elem.getBoundingClientRect()` для определения координат и `e.clientX/Y`. + Конечно же, не проблема перейти к координатам относительно окна, если это понадобится. Достаточно использовать `position:fixed`, `elem.getBoundingClientRect()` для определения координат и `e.clientX/Y`. -```js -// onmousedown -shiftX = e.pageX - getCoords(ball).left; -shiftY = e.pageY - getCoords(ball).top; -``` - -
      2. -
      3. Далее при переносе мяча мы располагаем его `left/top` с учетом сдвига, то есть вот так: - -```js -// onmousemove -ball.style.left = e.pageX - *!*shiftX*/!* + 'px'; -ball.style.top = e.pageY - *!*shiftY*/!* + 'px'; -``` -
      4. -
      + ```js + // onmousedown + shiftX = e.pageX - getCoords(ball).left; + shiftY = e.pageY - getCoords(ball).top; + ``` +2. Далее при переносе мяча мы располагаем его `left/top` с учетом сдвига, то есть вот так: + ```js + // onmousemove + ball.style.left = e.pageX - *!*shiftX*/!* + 'px'; + ball.style.top = e.pageY - *!*shiftY*/!* + 'px'; + ``` Итоговый код с правильным позиционированием: - ```js var ball = document.getElementById('ball'); @@ -174,8 +165,8 @@ ball.onmousedown = function(e) { ball.style.top = e.pageY - *!*shiftY*/!* + 'px'; } - document.onmousemove = function(e) { - moveAt(e); + document.onmousemove = function(e) { + moveAt(e); }; ball.onmouseup = function() { @@ -185,37 +176,33 @@ ball.onmousedown = function(e) { } -ball.ondragstart = function() { - return false; +ball.ondragstart = function() { + return false; }; ``` -[online] +```online В действии (внутри ифрейма): [iframe src="http://github.com/ball3" height=230] -[/online] +``` Различие особенно заметно, если захватить мяч за правый-нижний угол. В предыдущем примере мячик "прыгнет" серединой под курсор, в этом -- будет плавно переноситься с текущей позиции. ## Итого -Мы рассмотрели "минимальный каркас" `Drag'n'Drop`. +Мы рассмотрели "минимальный каркас" `Drag'n'Drop`. Его компоненты: -
        -
      1. События `ball.mousedown` -> `document.mousemove` -> `ball.mouseup`.
      2. -
      3. Передвижение с учётом изначального сдвига `shiftX/shiftY`.
      4. -
      5. Отмена действия браузера по событию `dragstart`.
      6. -
      +1. События `ball.mousedown` -> `document.mousemove` -> `ball.mouseup`. +2. Передвижение с учётом изначального сдвига `shiftX/shiftY`. +3. Отмена действия браузера по событию `dragstart`. -На этой основе можно сделать очень многое. +На этой основе можно сделать очень многое. -
        -
      • При `mouseup` можно обработать окончание переноса, произвести изменения в данных, если они нужны.
      • -
      • Во время самого переноса можно подсвечивать элементы, над которыми проходит элемент.
      • -
      • При обработке событий `mousedown` и `mouseup` можно использовать делегирование, так что одного обработчика достаточно для управления переносом в зоне с сотнями элементов. -
      +- При `mouseup` можно обработать окончание переноса, произвести изменения в данных, если они нужны. +- Во время самого переноса можно подсвечивать элементы, над которыми проходит элемент. +- При обработке событий `mousedown` и `mouseup` можно использовать делегирование, так что одного обработчика достаточно для управления переносом в зоне с сотнями элементов. Это и многое другое мы рассмотрим в статье про [Drag'n'Drop объектов](/drag-and-drop-objects). diff --git a/2-ui/3-event-details/5-drag-and-drop-objects/article.md b/2-ui/3-event-details/5-drag-and-drop-objects/article.md index 232361f2..cee2a4f6 100644 --- a/2-ui/3-event-details/5-drag-and-drop-objects/article.md +++ b/2-ui/3-event-details/5-drag-and-drop-objects/article.md @@ -2,7 +2,7 @@ В [предыдущей статье](/drag-and-drop) мы рассмотрели основы Drag'n'Drop. Здесь мы разберём дополнительные "тонкие места" и приёмы реализации, которые возникают на практике. -Почти все javascript-библиотеки реализуют Drag'n'Drop так, как написано (хотя бывает что и менее эффективно). +Почти все javascript-библиотеки реализуют Drag'n'Drop так, как написано (хотя бывает что и менее эффективно). Зная, что и как, вы сможете легко написать свой код переноса или поправить, адаптировать существующую библиотеку под себя. @@ -14,10 +14,8 @@ Как пример задачи -- возьмём документ с иконками браузера ("объекты переноса"), которые можно переносить в компьютер ("цель переноса"): -
        -
      • Элементы, которые можно переносить (иконки браузеров), помечены классом `draggable`.
      • -
      • Элементы, на которые можно положить (компьютер), имеют класс `droppable`.
      • -
      +- Элементы, которые можно переносить (иконки браузеров), помечены классом `draggable`. +- Элементы, на которые можно положить (компьютер), имеют класс `droppable`. ```html @@ -31,7 +29,6 @@ ``` - Работающий пример с переносом: [iframe border=1 src="http://github.com/dragDemo" height=280 link edit] @@ -39,11 +36,10 @@ Далее мы рассмотрим, как делается фреймворк для таких переносов, а в перспективе -- и для более сложных. Требования: -
        -
      • Поддержка большого количества элементов без "тормозов".
      • -
      • Продвинутые возможности по анимации переноса.
      • -
      • Удобная обработка успешного и неудачного переноса.
      • -
      + +- Поддержка большого количества элементов без "тормозов". +- Продвинутые возможности по анимации переноса. +- Удобная обработка успешного и неудачного переноса. ## Начало переноса @@ -51,7 +47,7 @@ Переносимых элементов может быть много. В нашем документе-примере это всего лишь несколько иконок, но если мы хотим переносить элементы списка или дерева, то их может быть 100 штук и более. -Поэтому повесим обработчик `mousedown` на контейнер, который содержит переносимые элементы, и будем определять нужный элемент поиском ближайшего `draggable` вверх по иерархии от `event.target`. +Поэтому повесим обработчик `mousedown` на контейнер, который содержит переносимые элементы, и будем определять нужный элемент поиском ближайшего `draggable` вверх по иерархии от `event.target`. В качестве контейнера здесь будем брать `document`, хотя это может быть и любой элемент. @@ -81,21 +77,21 @@ document.onmousedown = function(e) { } ``` -[warn header="Не начинаем перенос по `mousedown`"] -Ранее мы по `mousedown` начинали перенос. +```warn header="Не начинаем перенос по `mousedown`" +Ранее мы по `mousedown` начинали перенос. Но на самом деле нажатие на элемент вовсе не означает, что его собираются куда-то двигать. Возможно, на нём просто кликают. Это важное различие. Снимать элемент со своего места и куда-то двигать нужно только при переносе. -Чтобы отличить перенос от клика, в том числе -- от клика, который сопровождается нечаянным перемещением на пару пикселей (рука дрогнула), мы будем запоминать в `dragObject`, какой элемент (`elem`) и где (`downX/downY`) был зажат, а начало переноса будем инициировать из `mousemove`, если он передвинут хотя бы на несколько пикселей. -[/warn] +Чтобы отличить перенос от клика, в том числе -- от клика, который сопровождается нечаянным перемещением на пару пикселей (рука дрогнула), мы будем запоминать в `dragObject`, какой элемент (`elem`) и где (`downX/downY`) был зажат, а начало переноса будем инициировать из `mousemove`, если он передвинут хотя бы на несколько пикселей. +``` ## Перенос элемента Первой задачей обработчика `mousemove` является инициировать начало переноса, если элемент передвинули в зажатом состоянии. -Ну а второй задачей -- отображать его перенос при каждом передвижении мыши. +Ну а второй задачей -- отображать его перенос при каждом передвижении мыши. Схематично, обработчик будет иметь такой вид: @@ -113,7 +109,7 @@ document.onmousemove = function(e) { Здесь мы видим новое свойство `dragObject.avatar`. При начале переноса "аватар" делается из элемента и сохраняется в свойство `dragObject.avatar`. -**"Аватар" -- это DOM-элемент, который перемещается по экрану.** +**"Аватар" -- это DOM-элемент, который перемещается по экрану.** Почему бы не перемещать по экрану сам `draggable`-элемент? Зачем, вообще, нужен аватар? @@ -151,9 +147,9 @@ avatar.style.top = новая координата + 'px'; Чтобы элемент сохранял свою позицию под курсором, необходимо при нажатии запомнить его изначальный сдвиг относительно курсора, и сохранять его при переносе. - +![](shiftx.png) -Этот сдвиг по горизонтали обозначен `shiftX` на рисунке выше. Аналогично, есть `shiftY`. Они вычисляются как расстояние между курсором и левой/верхней границей элемента при `mousedown`. Детали вычислений описаны в главе [](/drag-and-drop). +Этот сдвиг по горизонтали обозначен `shiftX` на рисунке выше. Аналогично, есть `shiftY`. Они вычисляются как расстояние между курсором и левой/верхней границей элемента при `mousedown`. Детали вычислений описаны в главе . Таким образом, при `mousemove` мы будем назначать элементу координаты курсора с учетом сдвига `shiftX/shiftY`: @@ -180,7 +176,7 @@ document.onmousemove = function(e) { } *!*dragObject.avatar = createAvatar(e)*/!*; // захватить элемент - if (!dragObject.avatar) { + if (!dragObject.avatar) { dragObject = {}; // аватар создать не удалось, отмена переноса return; // возможно, нельзя захватить за эту часть элемента } @@ -198,13 +194,13 @@ document.onmousemove = function(e) { dragObject.avatar.style.left = e.pageX - dragObject.shiftX + 'px'; dragObject.avatar.style.top = e.pageY - dragObject.shiftY + 'px'; - return false; + return false; } ``` Здесь используются две функции для начала переноса: `createAvatar(e)` и `startDrag(e)`. -Функция `createAvatar(e)` создает аватар. В нашем случае в качестве аватара берется сам `draggable` элемент. После создания аватара в него записывается функция `avatar.rollback`, которая задает поведение при отмене переноса. +Функция `createAvatar(e)` создает аватар. В нашем случае в качестве аватара берется сам `draggable` элемент. После создания аватара в него записывается функция `avatar.rollback`, которая задает поведение при отмене переноса. Как правило, отмена переноса влечет за собой разрушение аватара, если это был клон, или возвращение его на прежнее место, если это сам элемент. @@ -251,22 +247,21 @@ function startDrag(e) { ## Окончание переноса -Окончание переноса происходит по событию `mouseup`. +Окончание переноса происходит по событию `mouseup`. Его обработчик можно поставить на аватаре, т.к. аватар всегда под курсором и `mouseup` происходит на нем. Но для универсальности и большей гибкости (вдруг мы захотим перемещать аватар *рядом* с курсором?) поставим его, как и остальные, на `document`. Задача обработчика `mouseup`: -
        -
      1. Обработать успешный перенос, если он идет (существует аватар)
      2. -
      3. Очистить данные `dragObject`.
      4. -
      + +1. Обработать успешный перенос, если он идет (существует аватар) +2. Очистить данные `dragObject`. Это дает нам следующий код: ```js document.onmouseup = function(e) { // (1) обработать перенос, если он идет - if (dragObject.avatar) { + if (dragObject.avatar) { *!*finishDrag(e)*/!*; } @@ -292,7 +287,7 @@ function finishDrag(e) { ### Определяем элемент под курсором -Чтобы понять, над каким элементом мы остановились -- используем метод [document.elementFromPoint(clientX, clientY)](https://developer.mozilla.org/en/DOM/document.elementFromPoint), который мы обсуждали в разделе [координаты](#elementFromPoint). Этот метод получает координаты *относительно окна* и возвращает самый глубокий элемент, который там находится. +Чтобы понять, над каким элементом мы остановились -- используем метод [document.elementFromPoint(clientX, clientY)](https://developer.mozilla.org/en/DOM/document.elementFromPoint), который мы обсуждали в разделе [координаты](info:coordinates#elementFromPoint). Этот метод получает координаты *относительно окна* и возвращает самый глубокий элемент, который там находится. Функция `findDroppable(event)`, описанная ниже, использует его и находит самый глубокий элемент с атрибутом `droppable` под курсором мыши: @@ -315,15 +310,15 @@ function findDroppable(event) { Вариант выше -- предварительный. Он не будет работать. Если попробовать применить эту функцию, будет все время возвращать один и тот же элемент! А именно -- *текущий переносимый*. Почему так? -...Дело в том, что в процессе переноса под мышкой находится именно аватар. При начале переноса ему даже `z-index` ставится большой, чтобы он был поверх всех остальных. +...Дело в том, что в процессе переноса под мышкой находится именно аватар. При начале переноса ему даже `z-index` ставится большой, чтобы он был поверх всех остальных. **Аватар перекрывает остальные элементы. Поэтому функция `document.elementFromPoint()` увидит на текущих координатах именно его.** Чтобы это изменить, нужно либо поправить код переноса, чтобы аватар двигался *рядом* с курсором мыши, либо дать аватару стиль `pointer-events:none` (кроме IE10-), либо: -
      1. Спрятать аватар.
      2. -
      3. Вызывать `elementFromPoint`.
      4. -
      5. Показать аватар.
      +1. Спрятать аватар. +2. Вызывать `elementFromPoint`. +3. Показать аватар. Напишем функцию `findDroppable(event)`, которая это делает: @@ -355,10 +350,9 @@ function findDroppable(event) { Для его создания используем не обычный синтаксис `{...}`, а вызов `new function`. Это позволит прямо при создании объявить дополнительные переменные и функции в замыкании, которыми могут пользоваться методы объекта, а также назначить обработчики: -```js -//+ no-beautify +```js no-beautify var DragManager = new function() { - + var dragObject = {}; var self = this; // для доступа к себе из обработчиков @@ -383,16 +377,18 @@ var DragManager = new function() { Внутренний объект `dragObject` будет содержать информацию об объекте переноса. У него будут следующие свойства, которые также разобраны выше: -
      -
      `elem`
      -
      Текущий зажатый мышью объект, если есть (ставится в `mousedown`).
      -
      `avatar`
      -
      Элемент-аватар, который передвигается по странице.
      -
      `downX/downY`
      -
      Координаты, на которых был клик `mousedown`
      -
      `shiftX/shiftY`
      -
      Относительный сдвиг курсора от угла элемента, вспомогательное свойство вычисляется в начале переноса.
      -
      + +`elem` +: Текущий зажатый мышью объект, если есть (ставится в `mousedown`). + +`avatar` +: Элемент-аватар, который передвигается по странице. + +`downX/downY` +: Координаты, на которых был клик `mousedown` + +`shiftX/shiftY` +: Относительный сдвиг курсора от угла элемента, вспомогательное свойство вычисляется в начале переноса. Задачей `DragManager` является общее управление переносом. Что же касается действий при его окончании -- их должен назначить внешний код, который использует `DragManager`. @@ -400,8 +396,7 @@ var DragManager = new function() { С использованием `DragManager` пример, с которого начиналась эта глава -- перенос иконок браузеров в компьютер, реализуется совсем просто: -```js -//+ no-beautify +```js no-beautify DragManager.onDragEnd = function(dragObject, dropElem) { // скрыть/удалить переносимый объект @@ -411,14 +406,14 @@ DragManager.onDragEnd = function(dragObject, dropElem) { dropElem.className = 'computer computer-smile'; // убрать улыбку через 0.2 сек - setTimeout(function() { - dropElem.classList.remove('computer-smile'; + setTimeout(function() { + dropElem.classList.remove('computer-smile'); }, 200); }; DragManager.onDragCancel = function(dragObject) { // откат переноса - dragObject.avatar.rollback(); + dragObject.avatar.rollback(); }; ``` @@ -426,8 +421,6 @@ DragManager.onDragCancel = function(dragObject) { [codetabs src="http://github.com/dragDemo" height=280] - - ## Расширения Существует масса возможных применений Drag'n'Drop. Здесь мы не будем реализовывать их все, поскольку не стоит цель создать фреймворк-монстр. @@ -444,15 +437,13 @@ DragManager.onDragCancel = function(dragObject) { ### Проверка прав на droppable -Бывает и так, что не на любое место в `droppable` можно положить элемент. +Бывает и так, что не на любое место в `droppable` можно положить элемент. -Например: в админке есть дерево всех объектов сайта: статей, разделов, посетителей и т.п. +Например: в админке есть дерево всех объектов сайта: статей, разделов, посетителей и т.п. -
        -
      • В этом дереве есть узлы различных типов: "статьи", "разделы" и "пользователи".
      • -
      • Все узлы являются переносимыми объектами.
      • -
      • Узел "статья" (draggable) можно переносить в "раздел" (droppable), а узел "пользователи" -- нельзя. Но и то и другое можно поместить в "корзину".
      • -
      +- В этом дереве есть узлы различных типов: "статьи", "разделы" и "пользователи". +- Все узлы являются переносимыми объектами. +- Узел "статья" (draggable) можно переносить в "раздел" (droppable), а узел "пользователи" -- нельзя. Но и то и другое можно поместить в "корзину". Здесь решение: "можно или нет" переносить или нельзя зависит от "типа" переносимого объекта. @@ -467,41 +458,36 @@ DragManager.onDragCancel = function(dragObject) { Как правило, применяют "оптимистичный" алгоритм, по которому мы считаем, что перенос обычно успешен, но при необходимости можем отменить его. При нём посетитель кладет объект туда, куда он хочет, а затем, в коде `onDragEnd`: -
        -
      1. Визуально обрабатывается завершение переноса, как будто все ок.
      2. -
      3. Производится асинхронный запрос к серверу, содержащий информацию о переносе.
      4. -
      5. Сервер обрабатывает перенос и возвращает ответ, все ли в порядке.
      6. -
      7. Если нет -- выводится ошибка и возвращается `avatar.rollback()`. Аватар в этом случае должен предусматривать возможность отката после успешного завершения.
      8. -
      -Процесс общения с сервером сопровождается индикацией загрузки и, при необходимости, блокировкой новых операций переноса до получения подтверждения. +1. Визуально обрабатывается завершение переноса, как будто все ок. +2. Производится асинхронный запрос к серверу, содержащий информацию о переносе. +3. Сервер обрабатывает перенос и возвращает ответ, все ли в порядке. +4. Если нет -- выводится ошибка и возвращается `avatar.rollback()`. Аватар в этом случае должен предусматривать возможность отката после успешного завершения. +Процесс общения с сервером сопровождается индикацией загрузки и, при необходимости, блокировкой новых операций переноса до получения подтверждения. ### Подсветка текущего droppable Удобно, когда пользователь во время переноса наглядно видит, куда он сейчас положит draggable. Например, текущий droppable (или его часть) подсвечиваются. Для этого в `DragManager` можно добавить дополнительные методы интеграции с внешним кодом: -
        -
      • `onDragEnter` -- будет вызываться при заходе на `droppable`, из `onMouseMove`.
      • -
      • `onDragMove` -- при каждом передвижении внутри `droppable`, из `onMouseMove`.
      • -
      • `onDragLeave` -- при выходе с `droppable`, из `onMouseMove` и `onMouseUp`.
      • -
      -Возможен более сложный вариант, когда нужно поддерживать не только перенос *в элемент*, но и перенос *между элементами*, например вставку одной статьи между двумя другими. +- `onDragEnter` -- будет вызываться при заходе на `droppable`, из `onMouseMove`. +- `onDragMove` -- при каждом передвижении внутри `droppable`, из `onMouseMove`. +- `onDragLeave` -- при выходе с `droppable`, из `onMouseMove` и `onMouseUp`. + +Возможен более сложный вариант, когда нужно поддерживать не только перенос *в элемент*, но и перенос *между элементами*, например вставку одной статьи между двумя другими. Для этого код, который обрабатывает перенос, может "делить на части" droppable, к примеру, в отношении 25% - 50% - 25%, и смотреть: -
        -
      • Если перенос в верхнюю четверть, то это -- "над".
      • -
      • Если перенос в середину, то это "внутрь".
      • -
      • Если перенос в нижнюю четверть, то это -- "под".
      • -
      +- Если перенос в верхнюю четверть, то это -- "над". +- Если перенос в середину, то это "внутрь". +- Если перенос в нижнюю четверть, то это -- "под". Текущий `droppable` и позиция относительно него при этом могут помечаться подсветкой и жирной чертой над/под, если требуется. Пример индикации из Firefox: - +![](between.png) ### Анимация отмены переноса @@ -512,20 +498,17 @@ DragManager.onDragCancel = function(dragObject) { ## Итого Уточнённый алгоритм Drag'n'Drop: + +1. При `mousedown` запомнить координаты нажатия. +2. При `mousemove` инициировать перенос, как только зажатый элемент передвинули на 3 пикселя или больше. Сообщить во внешний код вызовом `onDragStart`. При этом:
        -
      1. При `mousedown` запомнить координаты нажатия.
      2. -
      3. При `mousemove` инициировать перенос, как только зажатый элемент передвинули на 3 пикселя или больше. Сообщить во внешний код вызовом `onDragStart`. При этом:
      4. -
          -
        1. Создать аватар, если можно начать перенос элемента `draggable` с данной позиции курсора.
        2. -
        3. Переместить аватар по экрану, установив его новую позицию из `e.pageX/pageY` с учетом изначального сдвига элемента относительно курсора.
        4. -
        5. Сообщить во внешний код о текущем `droppable` под курсором и позиции над ним вызовами `onDragEnter`, `onDragMove`, `onDragLeave`.
        6. -
        - -
      5. При `mouseup` обработать завершение переноса. Элемент под аватаром получить по координатам, предварительно спрятав аватар. Сообщить во внешний код вызовом `onDragEnd`.
      6. -
      +3. Создать аватар, если можно начать перенос элемента `draggable` с данной позиции курсора. +4. Переместить аватар по экрану, установив его новую позицию из `e.pageX/pageY` с учетом изначального сдвига элемента относительно курсора. +5. Сообщить во внешний код о текущем `droppable` под курсором и позиции над ним вызовами `onDragEnter`, `onDragMove`, `onDragLeave`. +6. При `mouseup` обработать завершение переноса. Элемент под аватаром получить по координатам, предварительно спрятав аватар. Сообщить во внешний код вызовом `onDragEnd`. -Получившаяся реализация Drag'n'Drop проста, эффективна, изящна. +Получившаяся реализация Drag'n'Drop проста, эффективна, изящна. Её очень легко поменять или адаптировать под "особые" потребности. -ООП-вариант фреймворка находится в статье [](/drag-and-drop-plus). +ООП-вариант фреймворка находится в статье . diff --git a/2-ui/3-event-details/6-mousewheel/1-scale-with-mouse-wheel/task.md b/2-ui/3-event-details/6-mousewheel/1-scale-with-mouse-wheel/task.md index 660f9e88..9f824434 100644 --- a/2-ui/3-event-details/6-mousewheel/1-scale-with-mouse-wheel/task.md +++ b/2-ui/3-event-details/6-mousewheel/1-scale-with-mouse-wheel/task.md @@ -1,6 +1,8 @@ -# Масштабирование колёсиком мыши +importance: 5 + +--- -[importance 5] +# Масштабирование колёсиком мыши Сделайте так, чтобы при прокрутке колёсиком мыши над элементом, он масштабировался. @@ -12,4 +14,5 @@ elem.style.transform = elem.style.WebkitTransform = elem.style.MsTransform = 'sc ``` Результат в iframe: -[iframe link border="1" src="http://github.com/solution" height="160"] \ No newline at end of file + +[iframe link border="1" src="http://github.com/solution" height="160"] diff --git a/2-ui/3-event-details/6-mousewheel/2-no-doc-scroll/task.md b/2-ui/3-event-details/6-mousewheel/2-no-doc-scroll/task.md index 553f3b8a..3bb319a5 100644 --- a/2-ui/3-event-details/6-mousewheel/2-no-doc-scroll/task.md +++ b/2-ui/3-event-details/6-mousewheel/2-no-doc-scroll/task.md @@ -1,6 +1,8 @@ -# Прокрутка без влияния на страницу +importance: 4 + +--- -[importance 4] +# Прокрутка без влияния на страницу В большинстве браузеров если в процессе прокрутки `textarea` колёсиком мышки (или жестами) мы достигаем границы элемента, то прокрутка продолжается уже на уровне страницы (в Firefox при этом будет небольшая задержка перед продолжением прокрутки). @@ -10,18 +12,17 @@ То же самое происходит при прокрутке вверх. -В интерфейсах редактирования, когда большая `textarea` является основным элементом страницы, такое поведение может быть неудобно. +В интерфейсах редактирования, когда большая `textarea` является основным элементом страницы, такое поведение может быть неудобно. -Для редакторования более оптимально, чтобы при прокрутке до конца `textarea` страница не "улетала" вверх и вниз. +Для редактирования более оптимально, чтобы при прокрутке до конца `textarea` страница не "улетала" вверх и вниз. Вот тот же документ, но с желаемым поведением `textarea`: [iframe src="http://github.com/solution" border="1" height=300] -Задача: -
        -
      • Создать скрипт, который при подключении к документу исправлял бы поведение всех `textarea`, чтобы при прокрутке они не трогали документ.
      • -
      • Направление прокрутки -- только вверх или вниз.
      • -
      • Редактор прокручивает только мышкой или жестами (на мобильных устройствах), прокрутку клавиатурой здесь рассматривать не нужно (хотя это и возможно).
      • -
      +Задача: + +- Создать скрипт, который при подключении к документу исправлял бы поведение всех `textarea`, чтобы при прокрутке они не трогали документ. +- Направление прокрутки -- только вверх или вниз. +- Редактор прокручивает только мышкой или жестами (на мобильных устройствах), прокрутку клавиатурой здесь рассматривать не нужно (хотя это и возможно). diff --git a/2-ui/3-event-details/6-mousewheel/article.md b/2-ui/3-event-details/6-mousewheel/article.md index dbd5cff2..94f974bf 100644 --- a/2-ui/3-event-details/6-mousewheel/article.md +++ b/2-ui/3-event-details/6-mousewheel/article.md @@ -3,15 +3,15 @@ Колёсико мыши используется редко. Оно есть даже не у всех мышей. Поэтому существуют пользователи, которые в принципе не могут сгенерировать такое событие. ...Но, тем не менее, его использование может быть оправдано. Например, можно добавить дополнительные удобства для тех, у кого колёсико есть. + [cut] -## Отличия колёсика от прокрутки + +## Отличия колёсика от прокрутки Несмотря на то, что колёсико мыши обычно ассоциируется с прокруткой, это совсем разные вещи. -
        -
      • При прокрутке срабатывает событие [onscroll](/onscroll) -- рассмотрим его в дальнейшем. Оно произойдёт *при любой прокрутке*, в том числе через клавиатурy, но *только на прокручиваемых элементах*. Например, элемент с `overflow:hidden` в принципе не может сгенерировать `onscroll`.
      • -
      • А событие `wheel` является чисто "мышиным". Оно генерируется *над любым элементом* при передвижении колеса мыши. При этом не важно, прокручиваемый он или нет. В частности, `overflow:hidden` никак не препятствует обработке колеса мыши.
      • -
      +- При прокрутке срабатывает событие [onscroll](/onscroll) -- рассмотрим его в дальнейшем. Оно произойдёт *при любой прокрутке*, в том числе через клавиатуру, но *только на прокручиваемых элементах*. Например, элемент с `overflow:hidden` в принципе не может сгенерировать `onscroll`. +- А событие `wheel` является чисто "мышиным". Оно генерируется *над любым элементом* при передвижении колеса мыши. При этом не важно, прокручиваемый он или нет. В частности, `overflow:hidden` никак не препятствует обработке колеса мыши. Кроме того, событие `onscroll` происходит *после* прокрутки, а `onwheel` -- *до* прокрутки, поэтому в нём можно отменить саму прокрутку (действие браузера). @@ -22,14 +22,15 @@ До него браузеры обрабатывали прокрутку при помощи событий [mousewheel](http://msdn.microsoft.com/en-us/library/ie/ms536951.aspx) (все кроме Firefox) и [DOMMouseScroll](https://developer.mozilla.org/en-US/docs/DOM/DOM_event_reference/DOMMouseScroll), [MozMousePixelScroll](https://developer.mozilla.org/en-US/docs/DOM/DOM_event_reference/MozMousePixelScroll) (только Firefox). Самые важные свойства современного события и его нестандартных аналогов: -
      -
      `wheel`
      -
      Свойство `deltaY` -- количество прокрученных пикселей по горизонтали и вертикали. Существуют также свойства `deltaX` и `deltaZ` для других направлений прокрутки.
      -
      `MozMousePixelScroll`
      -
      Срабатывает, начиная с Firefox 3.5, только в Firefox. Даёт возможность отменить прокрутку и получить размер в пикселях через свойство `detail`, ось прокрутки в свойстве `axis`.
      + +`wheel` +: Свойство `deltaY` -- количество прокрученных пикселей по горизонтали и вертикали. Существуют также свойства `deltaX` и `deltaZ` для других направлений прокрутки. + +`MozMousePixelScroll` +: Срабатывает, начиная с Firefox 3.5, только в Firefox. Даёт возможность отменить прокрутку и получить размер в пикселях через свойство `detail`, ось прокрутки в свойстве `axis`. +
      `mousewheel` -
      Срабатывает в браузерах, которые ещё не реализовали `wheel`. В свойстве `wheelDelta` -- условный "размер прокрутки", обычно равен `120` для прокрутки вверх и `-120` -- вниз. Он не соответствует какому-либо конкретному количеству пикселей.
      -
      +: Срабатывает в браузерах, которые ещё не реализовали `wheel`. В свойстве `wheelDelta` -- условный "размер прокрутки", обычно равен `120` для прокрутки вверх и `-120` -- вниз. Он не соответствует какому-либо конкретному количеству пикселей. Чтобы кросс-браузерно отловить прокрутку и, при необходимости, отменить её, можно использовать все эти события. @@ -65,13 +66,13 @@ function onWheel(e) { } ``` -В действии: -[iframe src="http://github.com/wheel" link edit] +В действии: -[warn header="Ошибка в IE8"] +[iframe src="http://github.com/wheel" link edit] +```warn header="Ошибка в IE8" В браузере IE8 (только версия 8) есть ошибка. При наличии обработчика `mousewheel` -- элемент не скроллится. Иначе говоря, действие браузера отменяется по умолчанию. Это, конечно, не имеет значения, если элемент в принципе не прокручиваемый. -[/warn] +``` diff --git a/2-ui/3-event-details/7-fixevent/article.md b/2-ui/3-event-details/7-fixevent/article.md index d263a46e..da8d20da 100644 --- a/2-ui/3-event-details/7-fixevent/article.md +++ b/2-ui/3-event-details/7-fixevent/article.md @@ -1,16 +1,16 @@ -# Мышь: IE8-, исправление события +# Мышь: IE8-, исправление события Ранее мы говорили о различных несовместимостях при работе с событиями для IE8-. Самая главная -- это, конечно, назначение событий при помощи `attachEvent/detachEvent` вместо `addEventListener/removeEventListener` и отсутствие фазы перехвата. Но и в самом объекте события есть различия. Что касается событий мыши, различия в свойствах можно легко исправить при помощи функции `fixEvent`, которая описана в этой главе. + [cut] -[warn header="Только IE8-"] +```warn header="Только IE8-" Эта глава и описанная далее функция `fixEvent` нужны только для поддержки IE8-. Если IE8- для Вас неактуален, то пролистывайте дальше, это читать Вам не надо. -[/warn] - +``` Функция `fixEvent` предназначена для запуска в начале обработчика, вот так: @@ -24,14 +24,13 @@ elem.onclick = function(event) { } ``` -Она добавлит объекту события в IE8- следующие стандартные свойства: -
        -
      • `target`
      • -
      • `currentTarget` -- если обработчик назначен не через `attachEvent`.
      • -
      • `relatedTarget` -- для `mouseover/mouseout` и `mouseenter/mouseleave`.
      • -
      • `pageX/pageY`
      • -
      • `which`
      • -
      +Она добавит объекту события в IE8- следующие стандартные свойства: + +- `target` +- `currentTarget` -- если обработчик назначен не через `attachEvent`. +- `relatedTarget` -- для `mouseover/mouseout` и `mouseenter/mouseleave`. +- `pageX/pageY` +- `which` Код функции: diff --git a/2-ui/3-event-details/8-onscroll/1-avatar-above-scroll/task.md b/2-ui/3-event-details/8-onscroll/1-avatar-above-scroll/task.md index b27b44ff..36e7a607 100644 --- a/2-ui/3-event-details/8-onscroll/1-avatar-above-scroll/task.md +++ b/2-ui/3-event-details/8-onscroll/1-avatar-above-scroll/task.md @@ -1,11 +1,14 @@ -# Аватар наверху при прокрутке +importance: 5 + +--- -[importance 5] +# Аватар наверху при прокрутке Сделайте так, чтобы при прокрутке ниже элемента `#avatar` (картинка с Винни-Пухом) -- он продолжал показываться в левом-верхнем углу. При прокрутке вверх -- должен возвращаться на обычное место. Прокрутите вниз, чтобы увидеть: + [iframe src="http://github.com/solution" height=300 link border="1"] diff --git a/2-ui/3-event-details/8-onscroll/2-updown-button/task.md b/2-ui/3-event-details/8-onscroll/2-updown-button/task.md index 0a2af14e..aa949cbe 100644 --- a/2-ui/3-event-details/8-onscroll/2-updown-button/task.md +++ b/2-ui/3-event-details/8-onscroll/2-updown-button/task.md @@ -1,20 +1,20 @@ -# Кнопка вверх-вниз +importance: 3 + +--- -[importance 3] +# Кнопка вверх-вниз Создайте кнопку навигации, которая помогает при прокрутке страницы. Работать должна следующим образом: -
        -
      • Пока страница промотана меньше чем на высоту экрана вниз -- кнопка не видна.
      • -
      • При промотке страницы вниз больше, чем на высоту экрана, появляется кнопка "стрелка вверх".
      • -
      • При нажатии на нее страница прыгает вверх, но не только. Дополнительно, кнопка меняется на "стрелка вниз" и при клике возвратит на старое место. Если же в этом состоянии посетитель сам прокрутит вниз больше, чем один экран, то она вновь изменится на "стрелка вверх".
      • -
      + +- Пока страница промотана меньше чем на высоту экрана вниз -- кнопка не видна. +- При промотке страницы вниз больше, чем на высоту экрана, появляется кнопка "стрелка вверх". +- При нажатии на нее страница прыгает вверх, но не только. Дополнительно, кнопка меняется на "стрелка вниз" и при клике возвратит на старое место. Если же в этом состоянии посетитель сам прокрутит вниз больше, чем один экран, то она вновь изменится на "стрелка вверх". Должен получиться удобный навигационный помощник. -Посмотрите, как оно должно работать, в ифрейме ниже. Прокрутите ифрейм, навигационная стрелка появится слева-сверху. +Посмотрите, как оно должно работать, в ифрейме ниже. Прокрутите ифрейм, навигационная стрелка появится слева-сверху. [iframe border="1" height="200" link src="http://github.com/solution"] - diff --git a/2-ui/3-event-details/8-onscroll/3-load-visible-img/solution.md b/2-ui/3-event-details/8-onscroll/3-load-visible-img/solution.md index 1608bab1..5887804a 100644 --- a/2-ui/3-event-details/8-onscroll/3-load-visible-img/solution.md +++ b/2-ui/3-event-details/8-onscroll/3-load-visible-img/solution.md @@ -18,7 +18,6 @@ function isVisible(elem) { return topVisible || bottomVisible; } - *!* showVisible(); window.onscroll = showVisible; @@ -33,4 +32,3 @@ window.onscroll = showVisible; В решении также указан вариант с `isVisible`, который расширяет область видимости на +-1 страницу (высота страницы -- `document.documentElement.clientHeight`). -[edit src="http://github.com/solution"]Открыть полное решение в песочнице[/edit] \ No newline at end of file diff --git a/2-ui/3-event-details/8-onscroll/3-load-visible-img/task.md b/2-ui/3-event-details/8-onscroll/3-load-visible-img/task.md index ca90f490..c6c091bc 100644 --- a/2-ui/3-event-details/8-onscroll/3-load-visible-img/task.md +++ b/2-ui/3-event-details/8-onscroll/3-load-visible-img/task.md @@ -1,6 +1,8 @@ -# Загрузка видимых изображений +importance: 4 + +--- -[importance 4] +# Загрузка видимых изображений Задача, которая описана ниже, демонстрирует результативный метод оптимизации страницы. @@ -12,8 +14,7 @@ ``` - - +![|width="128" height="128"](https://js.cx/clipart/yozhik.jpg) Стоит вот такое: @@ -21,9 +22,9 @@ ``` - +![|width="128" height="128"](https://js.cx/lazyimg/1.gif) -То есть настоящий URL находится в атрибуте `realsrc` (название атрибута можно выбрать любое). А в `src` поставлен серый GIF размера 1x1, и так как `width/height` правильные, то он растягивается, так что вместо изображения виден серый прямоугольник. +То есть настоящий URL находится в атрибуте `realsrc` (название атрибута можно выбрать любое). А в `src` поставлен серый GIF размера 1x1, и так как `width/height` правильные, то он растягивается, так что вместо изображения виден серый прямоугольник. При этом, чтобы браузер загрузил изображение, нужно заменить значение `src` на то, которое находится в `realsrc`. @@ -33,18 +34,17 @@ Напишите код, который при прокрутке окна загружает ставшие видимыми изображения. -То есть, как только изображение попало в видимую часть документа -- в `src` нужно прописать правильный URL из `realsrc`. +То есть, как только изображение попало в видимую часть документа -- в `src` нужно прописать правильный URL из `realsrc`. -Пример работы вы можете увидеть в `iframe` ниже, если прокрутите его: +Пример работы вы можете увидеть в `iframe` ниже, если прокрутите его: [iframe src="http://github.com/solution"] Особенности реализации: -
        -
      • При начальной загрузке некоторые изображения должны быть видны сразу, до прокрутки. Код должен это учитывать.
      • -
      • Некоторые изображения могут быть обычными, без `realsrc`. Их код не должен трогать вообще.
      • -
      • Также код не должен перегружать уже показанное изображение.
      • -
      • Желательно предусмотреть загрузку изображений не только видимых сейчас, но и на страницу вперед и назад от текущего места.
      • -
      + +- При начальной загрузке некоторые изображения должны быть видны сразу, до прокрутки. Код должен это учитывать. +- Некоторые изображения могут быть обычными, без `realsrc`. Их код не должен трогать вообще. +- Также код не должен перегружать уже показанное изображение. +- Желательно предусмотреть загрузку изображений не только видимых сейчас, но и на страницу вперед и назад от текущего места. P.S. Горизонтальной прокрутки нет. \ No newline at end of file diff --git a/2-ui/3-event-details/8-onscroll/article.md b/2-ui/3-event-details/8-onscroll/article.md index da325c24..a6be837b 100644 --- a/2-ui/3-event-details/8-onscroll/article.md +++ b/2-ui/3-event-details/8-onscroll/article.md @@ -5,10 +5,10 @@ В отличие от события `onwheel` (колесико мыши), его могут генерировать только прокручиваемые элементы или окно `window`. Но зато оно генерируется всегда, при любой прокрутке, не обязательно "мышиной". [cut] + Например, следующая функция при прокрутке окна выдает количество прокрученных пикселей: -```js -//+ autorun +```js autorun window.onscroll = function() { var scrolled = window.pageYOffset || document.documentElement.scrollTop; document.getElementById('showScroll').innerHTML = scrolled + 'px'; @@ -18,14 +18,12 @@ window.onscroll = function() { В действии: Текущая прокрутка = прокрутите окно -Каких-либо особенностей события здесь нет, разве что для его использования нужно отлично представлять, как получить текущее значение прокрутки или прокрутить документ. Об этом мы говорили ранее, в главе [](/metrics). +Каких-либо особенностей события здесь нет, разве что для его использования нужно отлично представлять, как получить текущее значение прокрутки или прокрутить документ. Об этом мы говорили ранее, в главе . Некоторые области применения `onscroll`: -
        -
      • Показ дополнительных элементов навигации при прокрутке.
      • -
      • Подгрузка и инициализация элементов интерфейса, ставших видимыми после прокрутки.
      • -
      -Вашему вниманию предлагаются несколько задач, которые вы можете решить сами или посмотреть использование `onscroll` на их примере. +- Показ дополнительных элементов навигации при прокрутке. +- Подгрузка и инициализация элементов интерфейса, ставших видимыми после прокрутки. +Вашему вниманию предлагаются несколько задач, которые вы можете решить сами или посмотреть использование `onscroll` на их примере. diff --git a/2-ui/3-event-details/9-keyboard-events/1-numeric-input/solution.md b/2-ui/3-event-details/9-keyboard-events/1-numeric-input/solution.md index 35f6c937..c9ac344e 100644 --- a/2-ui/3-event-details/9-keyboard-events/1-numeric-input/solution.md +++ b/2-ui/3-event-details/9-keyboard-events/1-numeric-input/solution.md @@ -10,7 +10,7 @@ Алгоритм такой: получаем символ и проверяем, является ли он цифрой. Если не является, то отменяем действие по умолчанию. -Кроме того, игнорируем специальные символы и нажатия со включенным [key Ctrl]/[key Alt]/[key Cmd]. +Кроме того, игнорируем специальные символы и нажатия со включенным `key:Ctrl`/`key:Alt`/`key:Cmd`. Итак, вот решение: @@ -22,7 +22,7 @@ input.onkeypress = function(e) { var chr = getChar(e); - // с null надо осторожно в неравенствах, + // с null надо осторожно в неравенствах, // т.к. например null >= '0' => true // на всякий случай лучше вынести проверку chr == null отдельно if (chr == null) return; @@ -33,4 +33,3 @@ input.onkeypress = function(e) { } ``` -[edit src="http://github.com/solution"]Открыть полное решение в песочнице[/edit] \ No newline at end of file diff --git a/2-ui/3-event-details/9-keyboard-events/1-numeric-input/task.md b/2-ui/3-event-details/9-keyboard-events/1-numeric-input/task.md index 3aa964e4..b91c5d99 100644 --- a/2-ui/3-event-details/9-keyboard-events/1-numeric-input/task.md +++ b/2-ui/3-event-details/9-keyboard-events/1-numeric-input/task.md @@ -1,13 +1,13 @@ -# Поле только для цифр +importance: 5 + +--- -[importance 5] +# Поле только для цифр При помощи событий клавиатуры сделайте так, чтобы в поле можно было вводить только цифры. Пример ниже. [iframe border=1 src="http://github.com/solution"] -В поле должны нормально работать специальные клавиши [key Delete]/[key Backspace] и сочетания c [key Ctrl]/[key Alt]/[key Cmd]. - - +В поле должны нормально работать специальные клавиши `key:Delete`/`key:Backspace` и сочетания с `key:Ctrl`/`key:Alt`/`key:Cmd`. P.S. Конечно, при помощи альтернативных способов ввода (например, вставки мышью), посетитель всё же может ввести что угодно. \ No newline at end of file diff --git a/2-ui/3-event-details/9-keyboard-events/2-check-sync-keydown/solution.md b/2-ui/3-event-details/9-keyboard-events/2-check-sync-keydown/solution.md index 095b0c0c..e4a3350d 100644 --- a/2-ui/3-event-details/9-keyboard-events/2-check-sync-keydown/solution.md +++ b/2-ui/3-event-details/9-keyboard-events/2-check-sync-keydown/solution.md @@ -1,10 +1,8 @@ # Ход решения -
        -
      • Функция `runOnKeys` -- с переменным числом аргументов. Для их получения используйте `arguments`.
      • -
      • Используйте два обработчика: `document.onkeydown` и `document.onkeyup`. Первый отмечает нажатие клавиши в объекте `pressed = {}`, устанавливая `pressed[keyCode] = true`, а второй -- удаляет это свойство. Если все клавиши с кодами из `arguments` нажаты -- запускайте `func`.
      • -
      • Возникнет проблема с повторным нажатием сочетания клавиш после `alert`, решите её.
      • -
      +- Функция `runOnKeys` -- с переменным числом аргументов. Для их получения используйте `arguments`. +- Используйте два обработчика: `document.onkeydown` и `document.onkeyup`. Первый отмечает нажатие клавиши в объекте `pressed = {}`, устанавливая `pressed[keyCode] = true`, а второй -- удаляет это свойство. Если все клавиши с кодами из `arguments` нажаты -- запускайте `func`. +- Возникнет проблема с повторным нажатием сочетания клавиш после `alert`, решите её. # Решение diff --git a/2-ui/3-event-details/9-keyboard-events/2-check-sync-keydown/task.md b/2-ui/3-event-details/9-keyboard-events/2-check-sync-keydown/task.md index 285e4f92..0b6711e5 100644 --- a/2-ui/3-event-details/9-keyboard-events/2-check-sync-keydown/task.md +++ b/2-ui/3-event-details/9-keyboard-events/2-check-sync-keydown/task.md @@ -1,18 +1,19 @@ -# Отследить одновременное нажатие +importance: 3 -[importance 3] +--- + +# Отследить одновременное нажатие Создайте функцию `runOnKeys(func, code1, code2, ... code_n)`, которая запускает `func` при одновременном нажатии клавиш со скан-кодами `code1`, `code2`, ..., `code_n`. Например, код ниже выведет `alert` при одновременном нажатии клавиш `"Q"` и `"W"` (в любом регистре, в любой раскладке) -```js -//+ no-beautify +```js no-beautify runOnKeys( - function() { alert("Привет!") }, - "Q".charCodeAt(0), + function() { alert("Привет!") }, + "Q".charCodeAt(0), "W".charCodeAt(0) ); ``` -[demo src="http://github.com/solution"] \ No newline at end of file +[demo src="http://github.com/solution"] diff --git a/2-ui/3-event-details/9-keyboard-events/article.md b/2-ui/3-event-details/9-keyboard-events/article.md index 815716ff..cfd1faf3 100644 --- a/2-ui/3-event-details/9-keyboard-events/article.md +++ b/2-ui/3-event-details/9-keyboard-events/article.md @@ -1,6 +1,7 @@ # Клавиатура: keyup, keydown, keypress Здесь мы рассмотрим основные "клавиатурные" события и работу с ними. + [cut] ## Тестовый стенд [#keyboard-test-stand] @@ -17,9 +18,9 @@ События `keydown/keyup` происходят при нажатии/отпускании клавиши и позволяют получить её *скан-код* в свойстве `keyCode`. -Скан-код клавиши одинаков в любой раскладке и в любом регистре. Например, клавиша [key z] может означать символ `"z"`, `"Z"` или `"я"`, `"Я"` в русской раскладке, но её *скан-код* будет всегда одинаков: `90`. +Скан-код клавиши одинаков в любой раскладке и в любом регистре. Например, клавиша `key:z` может означать символ `"z"`, `"Z"` или `"я"`, `"Я"` в русской раскладке, но её *скан-код* будет всегда одинаков: `90`. -[online] +````online В действии: ```html @@ -27,41 +28,26 @@ ``` -[/online] +```` ### Скан-коды Для буквенно-цифровых клавиш есть очень простое правило: скан-код будет равен коду соответствующей заглавной английской буквы/цифры. -Например, при нажатии клавиши [key S] (не важно, каков регистр и раскладка) её скан-код будет равен `"S".charCodeAt(0)`. +Например, при нажатии клавиши `key:S` (не важно, каков регистр и раскладка) её скан-код будет равен `"S".charCodeAt(0)`. Для других символов, в частности, знаков пунктуации, есть таблица кодов, которую можно взять, например, из статьи Джона Уолтера: JavaScript Madness: Keyboard Events, или же можно нажать на нужную клавишу на [тестовом стенде](#keyboard-test-stand) и получить код. Когда-то в этих кодах была масса кросс-браузерных несовместимостей. Сейчас всё проще -- таблицы кодов в различных браузерах почти полностью совпадают. Но некоторые несовместимости, всё же, остались. Вы можете увидеть их в таблице ниже. Слева -- клавиша с символом, а справа -- скан-коды в различных браузерах. Таблица несовместимостей: - - - - - - - - - - - - - - - - - - - - - -
      КлавишаFirefoxОстальные браузеры
      [key ;]59186
      [key =]107187
      [key -]109189
      + +| Клавиша | Firefox | Остальные браузеры | +|-----------|---------|--------------------| +| `key:;` | 59 | 186 | +| `key:=` | 107 | 187 | +| `key:-` | 109 | 188 | + Остальные коды одинаковы, код для нужного символа будет в тестовом стенде. @@ -69,9 +55,9 @@ Событие `keypress` возникает сразу после `keydown`, если нажата *символьная* клавиша, т.е. нажатие приводит к появлению символа. -Любые буквы, цифры генерируют `keypress`. Управляющие клавиши, такие как [key Ctrl], [key Shift], [key F1], [key F2].. -- `keypress` не генерируют. +Любые буквы, цифры генерируют `keypress`. Управляющие клавиши, такие как `key:Ctrl`, `key:Shift`, `key:F1`, `key:F2`.. -- `keypress` не генерируют. -Событие `keypress` позволяет получить *код символа*. В отличие от скан-кода, он специфичен именно для символа и различен для `"z"` и `"я"`. +Событие `keypress` позволяет получить *код символа*. В отличие от скан-кода, он специфичен именно для символа и различен для `"z"` и `"я"`. Код символа хранится в свойствах: `charCode` и `which`. Здесь скрывается целое "гнездо" кросс-браузерных несовместимостей, разбираться с которыми нет никакого смысла -- запомнить сложно, а на практике нужна лишь одна "правильная" функция, позволяющая получить код везде. @@ -80,7 +66,6 @@ Кросс-браузерная функция для получения символа из события `keypress`: ```js -//+ autorun // event.type должен быть keypress function getChar(event) { if (event.which == null) { // IE @@ -99,13 +84,11 @@ function getChar(event) { Для общей информации -- вот основные браузерные особенности, учтённые в `getChar(event)`: -
        -
      1. Во всех браузерах, кроме IE, у события `keypress` есть свойство `charCode`, которое содержит код символа.
      2. -
      3. Браузер IE для `keypress` не устанавливает `charCode`, а вместо этого он записывает код символа в `keyCode` (в `keydown/keyup` там хранится скан-код).
      4. -
      5. Также в функции выше используется проверка `if(event.which!=0)`, а не более короткая `if(event.which)`. Это не случайно! При `event.which=null` первое сравнение даст `true`, а второе -- `false`.
      6. -
      +1. Во всех браузерах, кроме IE, у события `keypress` есть свойство `charCode`, которое содержит код символа. +2. Браузер IE для `keypress` не устанавливает `charCode`, а вместо этого он записывает код символа в `keyCode` (в `keydown/keyup` там хранится скан-код). +3. Также в функции выше используется проверка `if(event.which!=0)`, а не более короткая `if(event.which)`. Это не случайно! При `event.which=null` первое сравнение даст `true`, а второе -- `false`. -[online] +````online В действии: ```html @@ -113,9 +96,9 @@ function getChar(event) { ``` -[/online] +```` -[warn header="Неправильный `getChar`"] +````warn header="Неправильный `getChar`" В сети вы можете найти другую функцию того же назначения: ```js @@ -125,13 +108,13 @@ function getChar(event) { ``` Она работает неверно для многих специальных клавиш, потому что не фильтрует их. Например, она возвращает символ амперсанда `"&"`, когда нажата клавиша 'Стрелка Вверх'. Лучше использовать ту, что приведена выше. -[/warn] +```` Как и у других событий, связанных с пользовательским вводом, поддерживаются свойства `shiftKey`, `ctrlKey`, `altKey` и `metaKey`. -Они установлены в `true`, если нажаты клавиши-модификаторы -- соответственно, [key Shift], [key Ctrl], [key Alt] и [key Cmd] для Mac. +Они установлены в `true`, если нажаты клавиши-модификаторы -- соответственно, `key:Shift`, `key:Ctrl`, `key:Alt` и `key:Cmd` для Mac. -## Отмена пользовательского ввода +## Отмена пользовательского ввода Появление символа можно предотвратить, если отменить действие браузера на `keydown/keypress`: @@ -141,40 +124,37 @@ function getChar(event) { ``` -[online] +```online Попробуйте что-нибудь ввести в этих полях (не получится): -[/online] +``` При тестировании на стенде вы можете заметить, что отмена действия браузера при `keydown` также предотвращает само событие `keypress`. - -[warn header="При `keydown/keypress` значение ещё старое"] -На момент срабатывания `keydown/keypress` *клавиша ещё не обработана браузером*. + +```warn header="При `keydown/keypress` значение ещё старое" +На момент срабатывания `keydown/keypress` *клавиша ещё не обработана браузером*. Поэтому в обработчике значение `input.value` -- старое, т.е. до ввода. Это можно увидеть в примере ниже. Вводите символы `abcd..`, а справа будет текущее `input.value`: `abc..` -А что, если мы хотим обработать `input.value` именно после ввода? Самое простое решение -- использовать событие `keyup`, либо запланировать обрабочик через `setTimeout(..,0)`. -[/warn] - - +А что, если мы хотим обработать `input.value` именно после ввода? Самое простое решение -- использовать событие `keyup`, либо запланировать обработчик через `setTimeout(..,0)`. +``` -### Отмена любых действий +### Отмена любых действий Отменять можно не только символ, а любое действие клавиш. Например: -
        -
      • При отмене [key Backspace] -- символ не удалится.
      • -
      • При отмене [key PageDown] -- страница не прокрутится.
      • -
      • При отмене [key Tab] -- курсор не перейдёт на следующее поле.
      • -
      -Конечно же, есть действия, которые в принципе нельзя отменить, в первую очередь -- те, которые происходят на уровне операционной системы. Комбинация Alt+F4 инициирует закрытие браузера в Windows, что бы мы ни делали в JavaScript. +- При отмене `key:Backspace` -- символ не удалится. +- При отмене `key:PageDown` -- страница не прокрутится. +- При отмене `key:Tab` -- курсор не перейдёт на следующее поле. + +Конечно же, есть действия, которые в принципе нельзя отменить, в первую очередь -- те, которые происходят на уровне операционной системы. Комбинация Alt+F4 инициирует закрытие браузера в Windows, что бы мы ни делали в JavaScript. ### Демо: перевод символа в верхний регистр @@ -184,7 +164,7 @@ function getChar(event) { ``` -[online] +```online В действии: -[/online] +``` ## Несовместимости [#keyboard-events-order] @@ -231,58 +211,57 @@ document.getElementById('only-upper').onkeypress = function(e) {
      Печатные клавиши [key S] [key 1] [key ,]`keydown` -`keypress` -`keyup`Нажатие вызывает `keydown` и `keypress`. -Когда клавишу отпускают, срабатывает `keyup`. - -Исключение -- CapsLock под MacOS, с ним есть проблемы: + Печатные клавиши S 1 ,keydown
      +keypress
      +keyup
      Нажатие вызывает keydown и keypress. +Когда клавишу отпускают, срабатывает keyup. + +

      Исключение – CapsLock под MacOS, с ним есть проблемы:

      +
        -
      • В Safari/Chrome/Opera: при включении только `keydown`, при отключении только `keyup`.
      • -
      • В Firefox: при включении и отключении только `keydown`.
      • +
      • В Safari/Chrome/Opera: при включении только keydown, при отключении только keyup.
      • +
      • В Firefox: при включении и отключении только keydown.
      Специальные клавиши [key Alt] [key Esc] [key ⇧]`keydown` -`keyup`Нажатие вызывает `keydown`. -Когда клавишу отпускают, срабатывает `keyup`. + Специальные клавиши Alt Esc keydown +keyupНажатие вызывает keydown. +Когда клавишу отпускают, срабатывает keyup. -Некоторые браузеры могут дополнительно генерировать и `keypress`, например IE для [key Esc]. +

      Некоторые браузеры могут дополнительно генерировать и keypress, например IE для Esc.

      -На практике это не доставляет проблем, так как для специальных клавиш мы всегда используем `keydown/keyup`. +

      На практике это не доставляет проблем, так как для специальных клавиш мы всегда используем keydown/keyup.

      Сочетания с печатной клавишей - [key Alt+E] - [key Ctrl+У] - [key Cmd+1] + Сочетания с печатной клавишей + Alt+E
      + Ctrl+У
      + Cmd+1
      `keydown` -`keypress?` -`keyup`keydown
      +keypress?
      +keyup
      -Браузеры под Windows -- не генерируют `keypress`, браузеры под MacOS -- генерируют. +

      Браузеры под Windows – не генерируют keypress, браузеры под MacOS – генерируют.

      -Кроме того, если сочетание вызвало браузерное действие или диалог ("Сохранить файл", "Открыть" и т.п., ряд диалогов можно отменить при `keydown`), то может быть только `keydown`. +

      Кроме того, если сочетание вызвало браузерное действие или диалог ("Сохранить файл", "Открыть" и т.п., ряд диалогов можно отменить при keydown), то может быть только keydown.

      Общий вывод можно сделать такой: -
        -
      • Обычные символы работают везде корректно.
      • -
      • CapsLock под MacOS ведёт себя плохо, не стоит ставить на него обработчики вообще.
      • -
      • Для других специальных клавиш и сочетаний с ними следует использовать только `keydown`.
      • -
      + +- Обычные символы работают везде корректно. +- CapsLock под MacOS ведёт себя плохо, не стоит ставить на него обработчики вообще. +- Для других специальных клавиш и сочетаний с ними следует использовать только `keydown`. ## Автоповтор @@ -312,19 +291,17 @@ keyup keyup ``` -...А Chrome под MacOS не генерирует `keypress`. В общем, "зоопарк". +...А Chrome под MacOS не генерирует `keypress`. В общем, "зоопарк". -Полагаться можно только на `keydown` при каждом автонажатии и `keyup` по отпусканию клавиши. +Полагаться можно только на `keydown` при каждом автонажатии и `keyup` по отпусканию клавиши. -## Итого +## Итого Ряд рецептов по итогу этой главы: -
        -
      1. Для реализации горячих клавиш, включая сочетания -- используем `keydown`. Скан-код будет в `keyCode`, почти все скан-коды кросс-браузерны, кроме нескольких пунктуационных, перечисленных в таблице выше.
      2. -
      3. Если нужен именно символ -- используем `keypress`. При этом функция `getChar` позволит получить символ и отфильтровать лишние срабатывания. Гарантированно получать символ можно только при нажатии обычных клавиш, если речь о сочетаниях с модификаторами, то `keypress` не всегда генерируется.
      4. -
      5. Ловля CapsLock глючит под MacOS. Её можно организовать при помощи проверки `navigator.userAgent` и `navigator.platform`, а лучше вообще не трогать эту клавишу.
      6. -
      +1. Для реализации горячих клавиш, включая сочетания -- используем `keydown`. Скан-код будет в `keyCode`, почти все скан-коды кросс-браузерны, кроме нескольких пунктуационных, перечисленных в таблице выше. +2. Если нужен именно символ -- используем `keypress`. При этом функция `getChar` позволит получить символ и отфильтровать лишние срабатывания. Гарантированно получать символ можно только при нажатии обычных клавиш, если речь о сочетаниях с модификаторами, то `keypress` не всегда генерируется. +3. Ловля CapsLock глючит под MacOS. Её можно организовать при помощи проверки `navigator.userAgent` и `navigator.platform`, а лучше вообще не трогать эту клавишу. Распространённая ошибка -- использовать события клавиатуры для работы с полями ввода в формах. @@ -335,4 +312,4 @@ keyup Далее мы разберём [события для элементов форм](/events-change), которые позволяют работать с вводом в формы правильно. Их можно использовать как отдельно от событий клавиатуры, так и вместе с ними. - + diff --git a/2-ui/3-event-details/9-keyboard-events/head.html b/2-ui/3-event-details/9-keyboard-events/head.html new file mode 100644 index 00000000..17b273b3 --- /dev/null +++ b/2-ui/3-event-details/9-keyboard-events/head.html @@ -0,0 +1,15 @@ + \ No newline at end of file diff --git a/2-ui/3-event-details/9-keyboard-events/keyboard-dump.view/script.js b/2-ui/3-event-details/9-keyboard-events/keyboard-dump.view/script.js index 8972fb6b..b4dbc205 100644 --- a/2-ui/3-event-details/9-keyboard-events/keyboard-dump.view/script.js +++ b/2-ui/3-event-details/9-keyboard-events/keyboard-dump.view/script.js @@ -1,3 +1,5 @@ +var kinput = document.getElementById('kinput'); +var area = document.getElementById('area'); kinput.onkeydown = kinput.onkeyup = kinput.onkeypress = handle; var lastTime = Date.now(); diff --git a/2-ui/4-forms-controls/1-form-elements/1-add-select-option/solution.md b/2-ui/4-forms-controls/1-form-elements/1-add-select-option/solution.md index 4ec1dec3..891ed929 100644 --- a/2-ui/4-forms-controls/1-form-elements/1-add-select-option/solution.md +++ b/2-ui/4-forms-controls/1-form-elements/1-add-select-option/solution.md @@ -1,7 +1,6 @@ Решение: -```html - +```html run @@ -65,8 +62,7 @@ alert(elems[0].value); // 10, первый input **Свойство `elements` также есть у элементов `
      `.** Вот пример: -```html - +```html run height=80
      @@ -88,15 +84,14 @@ alert(elems[0].value); // 10, первый input Спецификация: [HTML5 Forms](https://html.spec.whatwg.org/multipage/forms.html). -[warn header="Доступ `form.name` тоже работает, но с особенностями"] +````warn header="Доступ `form.name` тоже работает, но с особенностями" Получить доступ к элементам формы можно не только через `form.elements[name/index]`, но и проще: `form[index/name]`. - -Этот способ короче, так как обладает одной неприятной особенностью: если к элементу обратиться по его `name`, а потом свойство `name` изменить, то он по-прежнему будет доступен под старым именем. + +Этот способ короче, но обладает одной неприятной особенностью: если к элементу обратиться по его `name`, а потом свойство `name` изменить, то он по-прежнему будет доступен под старым именем. Звучит странно, поэтому посмотрим на примере. -```html - +```html run height=40 @@ -114,8 +109,7 @@ alert(elems[0].value); // 10, первый input alert( form.text ); // INPUT (а должно быть undefined!) ``` - -[/warn] +```` ## Ссылка на форму element.form @@ -123,8 +117,7 @@ alert(elems[0].value); // 10, первый input Пример: -```html - +```html run height=40
      @@ -146,49 +139,41 @@ alert(elem.form == form); // true ## Элемент label -Элемент `label` -- один из самых важных в формах. +Элемент `label` -- один из самых важных в формах. **Клик на `label` засчитывается как фокусировка или клик на элементе формы, к которому он относится.** Это позволяет посетителям кликать на большой красивой метке, а не на маленьком квадратике `input type=checkbox` (`radio`). Конечно, это очень удобно. - -Есть два способа показать, какой элемент относится к `label`: -
        -
      1. Дать метке атрибут `for`, равный `id` соответствующего `input`: - -```html - - - - - - - - - - -
        - - - -
        - - - -
        -``` - -
      2. -
      3. Завернуть элемент в `label`. В этом случае можно обойтись без дополнительных атрибутов: - -```html - - -``` +Есть два способа показать, какой элемент относится к `label`: -
      4. -
      +1. Дать метке атрибут `for`, равный `id` соответствующего `input`: + + ```html autorun + + + + + + + + + +
      + + + +
      + + + +
      + ``` +2. Завернуть элемент в `label`. В этом случае можно обойтись без дополнительных атрибутов: + + ```html autorun no-beautify + + ``` ## Элементы input и textarea @@ -199,9 +184,9 @@ input.value = "Новое значение"; textarea.value = "Новый текст"; ``` -[warn header="Не используйте `textarea.innerHTML`"] +```warn header="Не используйте `textarea.innerHTML`" Для элементов `textarea` также доступно свойство `innerHTML`, но лучше им не пользоваться: оно хранит только HTML, изначально присутствовавший в элементе, и не меняется при изменении значения. -[/warn] +``` Исключения -- `input type="checkbox"` и `input type="radio"` @@ -229,8 +214,7 @@ select.selectedIndex = 0; // первая опция Пример: -```html - +```html run