JavaScript для начинающих

Источник

Основы JavaScript

JavaScript – один из самых популярных языков программирования в мире. Он используется для создания интерактивных веб-страниц, мобильных приложений, в серверной разработке. Изучать JS мы будем с нуля, с самых азов. Первый модуль – плацдарм для написания осмысленных программ. В нем мы разберем, как написать свой первый код на JS. Расскажем, что такое комментарии и зачем они нужны. На примере проверки ваших решений рассмотрим, что такое тестирование и как читать вывод тестов.

1. Привет, Мир!

JavaScript: Привет, Мир!

По традиции начнем с написания программы ‘Hello, World!’. Эта программа будет выводить на экран текст:

  Hello, World!

Чтобы вывести что-то на экран, нужно дать компьютеру специальную команду. В языке JavaScript такая команда — console.log().

Задание

Наберите в редакторе код из задания символ в символ и нажмите «Проверить».

console.log('Hello, World!');

Внимание: если вы напишете heLLo, woRld! вместо Hello, World!, то это будет считаться другим текстом, потому что заглавные и строчные буквы — это разные символы. Размер буквы называют регистром, и говорят: регистр — важен! Это касается почти всего в коде, поэтому привыкайте всегда обращать внимание на регистр.


Советы

2. Комментарии 

JavaScript: Комментарии

Кроме кода, в файлах с исходным кодом могут находиться комментарии. Это текст, который не является частью программы и нужен программистам для пометок. С их помощью добавляют пояснения, как работает код, какие здесь ошибки нужно поправить или не забыть что-то добавить позже.

// Удалить строку ниже после реализации задачи по регистрации
console.log(10);

Комментарии в JavaScript бывают двух видов:

Однострочные комментарии

Однострочные комментарии начинаются с //. После этих двух символов может следовать любой текст, вся строчка не будет анализироваться и исполняться.

Комментарий может занимать всю строчку. Если одной строчки мало, то создаются несколько комментариев:

// For Winterfell!
// For Lanisters!

Комментарий может находиться на строчке после какого-нибудь кода:

console.log('I am the King'); // For Lannisters!

Многострочные комментарии

Многострочные комментарии начинаются с /* и заканчиваются на */. Между ними каждая строчка должна начинаться с символа *.

/*
 * The night is dark and
 * full of terrors.
 */
console.log('I am the King');

Такие комментарии, обычно, используют для документирования кода, например, функций.

Задание

Создайте однострочный комментарий с текстом: You know nothing, Jon Snow!.


Определения

  • Комментарий – текст в коде программы, который не влияет на функциональность и добавляется программистами для себя и своих коллег.// однострочный комментарий
    /*
    * многострочный комментарий
    * многострочный комментарий
    */
3. Инструкции (Statements)

JavaScript: Инструкции (Statements)

Инструкция — это команда для компьютера выполнить что-то. Код на JavaScript — это набор инструкций, которые, как правило, отделяются друг от друга символом ;.

Вот пример кода с двумя инструкциями.

console.log('Mother of Dragons.');
console.log('Dracarys!');

При запуске этого кода, на экран последовательно выводятся два предложения:

Mother of Dragons.
Dracarys!

Теоретически, инструкции можно написать друг за другом без переноса на новую строчку:

console.log('Mother of Dragons.'); console.log('Drakarys!');

Результат на экране будет таким же, но такой код неудобен для чтения, поэтому инструкции располагают друг под другом.

Почему это важно знать? Инструкция — это единица исполнения. Интерпретатор, программа которая запускает код на JavaScript, выполняет инструкции строго по очереди. И мы, как разработчики, должны понимать этот порядок и уметь мысленно разделять программу на независимые части, удобные для анализа.

Задание

Выведите на экран друг за другом три имени: RobertStannisRenly. В результате на экране должно отобразиться:

Robert
Stannis
Renly

Для каждого имени используйте свой собственный вызов console.log().


Советы

Определения

  • Интерпретатор – программа выполняющая код на JavaScript.
  • Инструкция (statement) – команда для компьютера, написанная на языке программирования. Код на JavaScript — это набор инструкций, разделенных (чаще всего) символом ;.
4. Как мы проверяем ваши решения

JavaScript: Как мы проверяем ваши решения

Сайт автоматически проверяет ваши решения. Как это работает?

В самом простом случае, система просто запускает ваш код и смотрит на то, что вывелось на экран. А потом сверяет с тем, что мы «ожидали» по заданию.

В следующих, более сложных уроках, вы будете писать функции — некие мини-программы, принимающие информацию из внешнего мира и производящие какие-то операции. Проверка ваших решений в таких случаях выглядит немного сложнее: система запускает ваше решение и передаёт какую-то информацию. Система также знает — «ожидает» — какой именно ответ должна дать правильная функция при таких входных данных.

Например, если ваша задача — написать функцию сложения двух чисел, то проверочная система будет передавать ей разные комбинации чисел и сверять ответ вашей функции с реальными суммами. Если во всех случаях ответы совпадут, то решение считается верным.

Такой подход называется тестированием, и он используется в реальной повседневной разработке. Обычно программист сначала пишет тест — проверочную программу, а потом уже ту программу, которую хотел написать. В процессе он постоянно запускает тесты и смотрит, приблизился ли он к решению.

Именно поэтому наш сайт говорит «Тесты пройдены», когда вы правильно решили задачу.

Вот простой пример: в одном из будущих уроков вам нужно будет написать функцию, которая производит вычисления и выдаёт ответ. Предположим, вы допустили небольшую ошибку, и функция выдала неправильное число. Система ответит примерно так:

  ● test

  expect(received).toBe(expected) // Object.is equality

  Expected value to be:
    "Hello, World!"
  Received:
    "ello, World!"

Expected – ожидаемое значение, а Received, то которое выдал ваш код.

Кроме наших тестов, будет крайне полезно экспериментировать с кодом в консоли браузера. В любой ситуации, когда вы недопоняли, или хотите попробовать разные варианты использования, смело открывайте консоль и вводите туда код. Идеально, если вы выполните самостоятельно весь код, который присутствует в уроках. Кроме консоли, полезно использовать сервис repl.it.


Иногда в процессе решения будет казаться, что вы сделали все правильно, но система “капризничает” и не принимает решение. Подобное поведение практически исключено. Нерабочие тесты просто не могут попасть на сайт, они автоматически запускаются после каждого изменения. В подавляющем большинстве таких случаев, (а все наши проекты в сумме провели миллионы проверок за много лет), ошибка содержится в коде решения. Она может быть очень незаметной, вместо английской буквы случайно ввели русскую, вместо верхнего регистра использовали нижний или забыли вывести запятую. Другие случаи сложнее. Возможно ваше решение работает для одного набора входных данных, но не работает для другого. Поэтому всегда внимательно читайте условие задачи и вывод тестов. Там почти наверняка есть указание на ошибку.

Однако, если вы уверены в ошибке или нашли какую-то неточность, то вы всегда можете указать на нее. В конце каждой теории есть ссылка на содержимое урока на гитхабе (этот проект полностью открытый!). Перейдя туда, вы можете написать issue, посмотреть содержимое тестов (там видно, как вызывается ваш код) и даже отправить pullrequest. Если для вас это пока темный лес, то подключитесь в наше сообщество, там в канале #hexlet-feedback мы всегда поможем.

Задание

Выведите на экран число 9780262531962.


Советы

Определения

  • Тесты – специальный код, проверяющий программы на корректность, сверяя правильный результат с реальным.
5. Синтаксические ошибки

JavaScript: Синтаксические ошибки

Если программа на JavaScript написана синтаксически некорректно, то интерпретатор выводит на экран соответствующее сообщение, а также указание на файл и строчку в нём, где, по его мнению, произошла ошибка. Синтаксическая ошибка возникает в том случае, когда код был записан с нарушением грамматических правил. В человеческих языках грамматика важна, но текст с ошибками чаще всего можно понять и прочитать. В программировании всё строго. Любое мельчайшее нарушение, и программа даже не запустится. Примером может быть забытая ;, неправильно расставленные скобки и другие детали.

Вот пример кода с синтаксической ошибкой:

console.log('Hodor'

Если запустить код выше, то мы увидим следующее сообщение: SyntaxError: missing ) after argument list, а так же указание на строку и файл, где возникла эта ошибка. Подобные синтаксические ошибки в JavaScript относятся к разряду SyntaxError.

По техническим причинам, такой код, запущенный на https://code-basics.com не укажет на строку и файл. Проверить этот вывод можно на https://repl.it

С одной стороны, ошибки SyntaxError — самые простые, потому что они связаны исключительно с грамматическими правилами написания кода, а не с самим смыслом кода. Их легко исправить: нужно лишь найти нарушение в записи.

С другой стороны, интерпретатор не всегда может чётко указать на это нарушение. Поэтому бывает, что забытую скобку нужно поставить не туда, куда указывает сообщение об ошибке.

Задание

Это задание не связано с уроком напрямую. Но будет полезным потренироваться с выводом на экран.

Выведите на экран What Is Dead May Never Die


Советы

Определения

  • Синтаксическая ошибка – нарушение грамматических правил языка программирования.
  • SyntaxError (ошибка парсинга) – тип ошибок в JavaScript, возникающих при наличии синтаксических ошибок в коде.

Арифметика

Современные программы создаются для обслуживания бизнесов, помощи в ежедневной жизни и развлечений. Но в основе их работы по-прежнему лежат вычисления. Наиболее простая и базовая тема в программировании — арифметика. В этом модуле мы переведем арифметические действия на язык программирования, поговорим о приоритете операций и операциях с дробным числами. Вспомним школьные правила из уроков математики и узнаем, что получится, если делить на ноль в JavaScript. А под конец расскажем, что такое линтер и почему он может «ругаться».

6. Арифметические операции

JavaScript: Арифметические операции

На базовом уровне компьютеры оперируют только числами. Даже в прикладных программах на высокоуровневых языках внутри много чисел и операций над ними. К счастью, для старта достаточно знать обычную арифметику — с нее и начнем.

Для сложения двух чисел в математике мы пишем, например, 3 + 4. В программировании — то же самое. Вот программа, складывающая два числа:

// Не забываем точку с запятой в конце, так как каждая строчка в коде - инструкция.
3 + 4;

Инструкция 3 + 4; заставит интерпретатор сложить числа и узнать результат. Если запустить эту программу, то ничего не произойдет. А если быть точными, то интерпретатор вычислит сумму, но на этом всё. Результат сложения никак не используется, и такая программа не представляет никакого интереса. Нам нужно попросить интерпретатор сложить 3 + 4 и дать команду сделать что-то с результатом. Например, вывести его на экран:

// Сначала вычисляется сумма,
// затем она передается в функцию печати
console.log(3 + 4);

После запуска на экране появится результат:

7

Кроме сложения, доступны следующие операции:

Теперь давайте выведем на экран результат деления, а потом результат возведения в степень:

console.log(8 / 2);  // => 4
console.log(3 ** 2); // => 9

Иногда для удобства мы будем показывать в комментариях результат запуска строчек кода, вот так: => РЕЗУЛЬТАТ. Например, // => 4.

Первая инструкция выведет на экран 4 (потому что 8 / 2 это 4), а вторая инструкция выведет на экран 9 (потому что 32 это 9).

Задание

Выведите на экран результат деления числа 81 на 9.


Советы

  • Всегда отбивайте арифметические операторы пробелами от самих чисел (операндов) – это хороший стиль программирования. Поэтому в наших примерах console.log(3 + 4), а не console.log(3+4).
  • Деление на ноль — это Infinity (бесконечность). Мы разберёмся с тем, что это значит, в будущих уроках.
  • Арифметические операции
7. Операторы

JavaScript: Операторы

Перед тем как двигаться дальше, разберем базовую терминологию. Знак операции, такой как +, называют оператором. Операторы выполняют операции над определенными значениями, которые называются операндами. Сами операторы, обычно, представлены одним или несколькими символами. Реже словом. Подавляющее большинство операторов соответствуют математическим операциям.

console.log(8 + 2);

В этом примере + — это оператор, а числа 8 и 2 — это операнды.

В случае сложения у нас есть два операнда: один слева, другой справа от знака +. Операции, которые требуют наличия двух операндов, называются бинарными. Если пропустить хотя бы один операнд, например, 3 + ;, то программа завершится с синтаксической ошибкой.

Операции (не операторы) бывают не только бинарными, но и унарными (с одним операндом), и даже тернарными (с тремя операндами)! Причем операторы могут выглядеть одинаково, но обозначать разные операции.

  console.log(-3); // => -3

Выше пример применения унарной операции к числу 3. Оператор минус перед тройкой говорит интерпретатору взять число 3 и найти противоположное, то есть -3.

Это немного может сбить с толку, потому что -3 — это одновременно и число само по себе, и оператор с операндом, но у языков программирования такая структура.

Задание

Напишите программу, которая посчитает разность между числами 6 и -81 и выведет ответ на экран.


Советы

Определения

  • Арифметическая операция – сложение, вычитание, умножение и деление.
  • Унарная операция – операция с одним операндом. Например, -3 — унарная операция для получения числа, противоположного числу три.
  • Бинарная операция – операция с двумя операндами. Например, 3 + 9.
8.  Коммутативная операция

JavaScript: Коммутативная операция

Мы все помним со школы: «от перемены мест слагаемых сумма не меняется». Это один из базовых и интуитивно понятных законов арифметики, он называется коммутативным законом.

Бинарная операция считается коммутативной, если поменяв местами операнды, вы получаете тот же самый результат. Очевидно, что сложение – коммутативная операция: 3 + 2 = 2 + 3.

А вот является ли коммутативной операция вычитания? Конечно, нет: 2 – 3 ≠ 3 – 2. В программировании этот закон работает точно так же, как в арифметике.

Более того, большинство операций, с которыми мы будем сталкиваться в реальной жизни, не являются коммутативными. Отсюда вывод: всегда обращайте внимание на порядок того, с чем работаете.

Задание

Напишите программу, которая считает и выводит последовательно на экран значения следующих математических выражений: «3 в степени 5» и «-8 разделить на -4».


Определения

  • Коммутативность – свойство операции, когда изменение порядка операндов не влияет на результат. Например, сложение — коммутативная операция: от перемены мест слагаемых сумма не меняется.
9. Композиция операций

JavaScript: Композиция операций

А что, если понадобится вычислить такое выражение: 3 * 5 - 2? Именно так мы и запишем:

console.log(3 * 5 - 2); // => 13

Обратите внимание, что интерпретатор производит арифметические вычисления в правильном порядке: сначала деление и умножение, потом сложение и вычитание. Иногда этот порядок нужно изменить — об этом следующий урок.

Или другой пример:

console.log(2 * 4 * 5 * 10);

Как видно, операции можно соединять друг с другом, получая возможность вычислять все более сложные составные выражения. Чтобы представить себе то, как происходят вычисления внутри интерпретатора, давайте разберем пример: 2 * 4 * 5 * 10.

  1. Сначала вычисляется 2 * 4 и получается выражение 8 * 5 * 10.
  2. Затем 8 * 5. В итоге имеем 40 * 10.
  3. В конце концов происходит последнее умножение, и получается результат 400.

Задание

Реализуйте программу, которая вычисляет и выводит на экран значение выражения:

8 / 2 + 5 - -3 / 2

Не вычисляйте ничего самостоятельно, ваша программа должна производить все вычисления сама.

10. Приоритет операций

JavaScript: Приоритет операций

Посмотрите внимательно на выражение 2 + 2 * 2 и посчитайте в уме ответ.

Правильный ответ: 6.

Если у вас получилось 8, то этот урок для вас. В школьной математике мы изучали понятие «приоритет операции». Приоритет определяет то, в какой последовательности должны выполняться операции. Например, умножение и деление имеют больший приоритет, чем сложение и вычитание, а приоритет возведения в степень выше всех остальных арифметических операций: 2 ** 3 * 2 вычислится в 16.

Но нередко вычисления должны происходить в порядке, отличном от стандартного приоритета. В сложных ситуациях приоритет можно (и нужно) задавать круглыми скобками, точно так же, как в школе, например: (2 + 2) * 2.

Скобки можно ставить вокруг любой операции. Они могут вкладываться друг в друга сколько угодно раз. Вот пара примеров:

console.log(3 ** (4 - 2)); // => 9
console.log(7 * 3 + (4 / 2) - (8 + (2 - 1))); // => 14

Главное при этом соблюдать парность, то есть закрывать скобки в правильном порядке. Это, кстати, часто становится причиной ошибок не только у новичков, но и у опытных программистов. Для удобства ставьте сразу открывающую и закрывающую скобку, а потом пишите внутреннюю часть. Редактор на нашем сайте (и большинство других редакторов кода) делают это автоматически: вы пишете (, а редактор сразу добавляет ). Это касается и других парных символов, например, кавычек. О них — в будущих уроках.

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

Было:

console.log(8 / 2 + 5 - -3 / 2); // => 10.5

Стало:

console.log(((8 / 2) + 5) - (-3 / 2)); // => 10.5

Запомните: код пишется для людей, потому что код будут читать люди, а машины будут только исполнять его. Для машин код — или корректный, или не корректный, для них нет «более» понятного или «менее» понятного кода.

Задание

Дано вычисление 70 * 3 + 4 / 8 + 2.

Расставьте скобки так, чтобы оба сложения (3 + 4) и (8 + 2) высчитывались в первую очередь. Выведите на экран результат.


Советы

11. Числа с плавающей точкой

JavaScript: Числа с плавающей точкой

JavaScript не делает различий между рациональными (0.5) и натуральными числами (10), для него и то, и другое – числа (в других языках это не так). Благодаря этому их можно использовать совместно в любых операциях:

3 * 0.5; // 1.5

Но как бы от нас не скрывали, рациональные числа, в силу своих особенностей, устроены совсем по-другому. Нам, как прикладным программистам, это было бы не особенно важно, если бы не одна деталь. Посмотрите на этот пример:

// Проверьте этот код в консоли браузера
0.2 * 0.2 // 0.04000000000000001

Операция умножения двух рациональных чисел внезапно привела к неточному вычислению результата. Тот же самый результат выдадут и другие языки программирования. Такое поведение обуславливается ограничениями вычислительных мощностей. Объем памяти, в отличие от чисел, конечен (бесконечное количество чисел требует бесконечного количества памяти для своего хранения). И если с натуральными числами эта проблема решается простым ограничением по верхней границе (есть некоторое максимальное число, которое можно ввести), то с рациональными такой финт не пройдет.

// Максимальное возможное целое число
console.log(Number.MAX_SAFE_INTEGER);
9007199254740991

Рациональные числа не выстроены в непрерывную цепочку, между 0.1 и 0.2 бесконечное множество чисел. Соответственно возникает серьезная проблема, а как хранить рациональные числа? Это интересный вопрос сам по себе. В интернете множество статей, посвященных организации памяти в таких случаях. Более того, существует стандарт, в котором описано, как это делать правильно, и подавляющее число языков на него опирается.

Для нас, как для разработчиков, важно понимать, что операции с плавающими числами неточны (эту точность можно регулировать), а значит при решении задач, связанных с подобными числами, необходимо прибегать к специальным трюкам, которые позволяют добиться необходимой точности.

Задание

Вычислите и выведите на экран произведение двух чисел: 0.39 и 0.22


Советы

12. Бесконечность (Infinity)

JavaScript: Бесконечность (Infinity)

В программировании широко известна ошибка “деление на ноль”. В низкоуровневых языках она приводит к краху программы и необходимости ее перезапуска. Там, где другие падают, JavaScript продолжает работать.

console.log(1 / 0); // ?

Попробуйте выполнить этот код в браузере. На экран выведется Infinity (бесконечность)! Для тех, кто изучал высшую математику (привет, матан!), в этом нет ничего удивительного. Деление на ноль действительно создает бесконечность. Бесконечность в JavaScript — самое настоящее число, с которым возможно проводить различные операции. В повседневных задачах смысла от этого мало, так как большинство операций с бесконечностью завершаются созданием бесконечности, например, при прибавлении любого числа к бесконечности мы все равно получим бесконечность.

Infinity + 4; // Infinity
Infinity - 4; // Infinity
Infinity * Infinity; // Infinity

Однако есть несколько примеров, где бесконечность нужна. Подробнее этот вопрос рассматривается на Хекслете.

Задание

Распечатайте на экран сумму бесконечностей, поделенную на 10

13. NaN

JavaScript: NaN

Некоторые операции с бесконечностями приводят к странному результату, например, деление бесконечности на бесконечность. В математике такая операция не имеет никакого числового эквивалента. В JavaScript вернется NaN.

Infinity / Infinity; // NaN

NaN – специальное значение “не число”, которое обычно говорит о том, что была выполнена бессмысленная операция. Результатом любой операции, в которой участвует NaN, будет NaN.

NaN + 1; // NaN

NaN интересное значение, хотя оно обозначает “не число” — с точки зрения типов, оно является числом. Парадокс. NaN никогда не является желаемым значением и появляется только в результате ошибок. Если вы его встретили, то нужно отследить момент, в котором выполнилась операция, недопустимая для чисел, и поправить это место.

Задание

Выполните операцию, которая приводит к NaN, и распечатайте её результат на экран с помощью console.log().


Советы

14. Линтер 

JavaScript: Линтер

Теперь, когда мы уже научились писать простые программы, можно немного поговорить о том, как их писать.

Код программы следует оформлять определенным образом, чтобы он был достаточно понятным и простым в поддержке. Специальные наборы правил — стандарты — описывают различные аспекты написания кода. Конкретно в JavaScript самым распространенным стандартом является стандарт от AirBnb.

В любом языке программирования существуют утилиты — так называемые
линтеры. Они проверяют код на соответствие стандартам. В JavaScript это eslint.

Взгляните на пример из предыдущего урока:

console.log(8/2+5 - -3 / 2); // => 10.5

Линтер будет «ругаться» на нарушение сразу нескольких правил:

  • space-infix-ops – Отсутствие пробелов между оператором и операндами.
  • no-mixed-operators – По стандарту нельзя писать код, в котором разные операции используются в одном выражении без явного разделения скобками.

В прошлом уроке мы сами признали, что такое обилие цифр и символов запутывает, и решили добавить скобки исключительно для удобства чтения:

console.log(((8 / 2) + 5) - (-3 / 2)); // => 10.5

Этот вариант уже не нарушает правил, и линтер будет «молчать».

В упражнении прошлого урока у вас скорее всего получилось так:

console.log(70 * (3 + 4) / (8 + 2));

Есть ли здесь нарушение стандарта?

К сожалению, да. На этот раз операции * и / находятся в одном выражении без разделения скобками. Вы можете решить эту проблему, добавив дополнительные скобки. Но в какой-то момент количество скобок может быть уже настолько большим, что код снова станет неудобным и непонятным. В этот момент разумнее будет разделить выражение на отдельные части. Мы научимся это делать в следующих уроках.

no-mixed-operators — лишь одно из большого количества правил. Другие правила описывают отступы, названия создаваемых сущностей, скобки, математические операции, длину строк и множество иных аспектов. Каждое отдельное правило кажется довольно мелким, не очень важным. Но вместе они составляют основу хорошего кода.

Сейчас сайт не будет проверять ваш код линтером, но в ваших будущих практиках на Хекслете и в реальной разработке линтер будет работать и сообщать вам о нарушениях.

Задание

Выведите на экран результат следующего вычисления: «разница между пятью в квадрате и произведением трёх и семи». Расставьте скобки таким образом, чтобы не нарушать правило no-mixed-operators.

Строки

Текст в программировании называется «строками», и эта тема не так проста, как может показаться. Как вывести фразу, в которой есть и одинарные, и двойные кавычки? Как вообще быть с текстом, ведь компьютер не знает ничего о буквах! Модуль посвящен разным аспектам написания текста – от кавычек и экранирования до кодировки.

15. Кавычки 

JavaScript: Кавычки

'Hello'
'Goodbye'
'G'
' '
''

Какие из этих пяти вариантов — строки?

С первыми двумя все понятно, это точно строки, мы уже работали с подобными конструкциями и говорили, что строки – это наборы символов.

Любой одиночный символ в кавычках — это строка. Пустая строка '' — это тоже строка. То есть строкой мы считаем всё, что находится внутри кавычек, даже если это пробел, один символ или вообще отсутствие символов.

Ранее в уроках мы записывали строки в одинарных кавычках, но это не единственный способ. Можно использовать и двойные:

// Стандарт кодирования airbnb, рекомендует
// использовать, по возможности, одинарные
console.log('Dracarys!');

Представьте, что вы хотите напечатать строчку Dragon’s mother. Апостроф перед буквой s — это такой же символ, как одинарная кавычка. Попробуем:

console.log('Dragon's mother');
// Uncaught SyntaxError: missing ) after argument list

Такая программа не будет работать. С точки зрения JavaScript, строчка началась с одинарной кавычки, а потом закончилась после буквы n. Дальше были символы s mother без кавычек — значит, это не строка. А потом была одна открывающая строку кавычка, которая так и не закрылась: ');. Этот код синтаксически некорректен (это видно даже по тому, как подсвечен код).

Здесь нам помогут двойные кавычки. Такой вариант программы отработает корректно:

console.log("Dragon's mother");

Теперь интерпретатор знает, что строка началась с двойной кавычки — значит, и закончиться должна на двойной кавычке. А одинарная кавычка внутри стала частью строки.

Верно и обратное. Если внутри строки мы хотим использовать двойные кавычки, то саму строку надо делать в одинарных. Причем количество кавычек внутри самой строки не важно.

А что, если мы хотим создать такую строку:

Dragon's mother said "No"

В ней есть и одинарные и двойные кавычки. Как быть в этой ситуации? Нужно каким-то образом сказать интерпретатору считать каждую кавычку частью строки, а не началом или концом строки.

Для этого экранируют специальные символы. В нашем случае тот символ, который является признаком конца и начала строки, это либо одинарная кавычка, либо двойная, в зависимости от ситуации. Для экранирования используется обратный слеш \.

// Экранируется только ", так как в этой ситуации
// двойные кавычки имеют специальное значение
console.log("Dragon's mother said \"No\"");
// => Dragon's mother said "No"

Посмотрите внимательно: нам нужно было добавить \ для двойных кавычек, но не для одинарной (апостроф), потому что сама строка создана с двойными кавычками. Если бы строка создавалась с одинарными кавычками, то символ экранирования нужен был бы перед апострофом, но не перед двойными кавычками.

// \ не выводится, если после него идет обычный,
// а не специальный символ
console.log("Death is \so terribly final");
// => Death is so terribly final

А что, если нужно вывести сам обратный слеш? Точно так же, как и любой другой специальный символ, его надо экранировать самим собой.

console.log("\\");
// => \

Вопрос на самопроверку, что выведет этот код?

console.log("\\ \\ \\\\ \\\ \'\"");

Задание

Напишите программу, которая выведет на экран:

"Khal Drogo's favorite word is "athjahakar""

Программа должна в точности вывести на экран именно эту фразу. Обратите внимание на кавычки в начале и в конце фразы:

"Khal Drogo's favorite word is "athjahakar""

Советы

16. Экранирующие последовательности

JavaScript: Экранирующие последовательности

Мы хотим показать диалог Матери Драконов со своим ребёнком:

- Are you hungry?
- Aaaarrrgh!

Если вывести на экран строку с таким текстом:

console.log('- Are you hungry?- Aaaarrrgh!');

то получится так:

- Are you hungry?- Aaaarrrgh!

Не то, что мы хотели. Строки расположены друг за другом, а не одна ниже другой. Нам нужно как-то сказать интерпретатору «нажать на энтер» — сделать перевод строки после вопросительного знака. Это можно сделать, используя символ перевода строки: \n.

console.log('- Are you hungry?\n- Aaaarrrgh!');

результат:

- Are you hungry?
- Aaaarrrgh!

\n — это специальный символ. В литературе его часто обозначают как LF (Line Feed). Возможно вы сейчас подумали, что это опечатка, ведь здесь мы видим два символа \ и n, но это не так. С точки зрения компьютера — это один невидимый символ перевода строки. Доказательство:

// Мы это не изучали, но вы должны знать правду
// Ниже код, который возвращает длину строки
'a'.length;    // 1
'\n'.length;   // 1 !!!
'\n\n'.length; // 2 !!!

Почему так сделано? \n — всего лишь способ записать символ перевода строки, но сам перевод строки по своему смыслу – это один символ, правда, невидимый. Именно поэтому и возникла такая задача. Нужно было как-то представить его на клавиатуре. А поскольку количество знаков на клавиатуре ограничено и отдано под самые важные, то все специальные символы реализуются в виде таких обозначений.

Символ перевода строки не является чем-то специфичным для программирования. Все, кто хоть раз печатал на компьютере, использовал перевод строки, нажимая на Enter. Во многих редакторах есть опция, позволяющая включить отображение невидимых символов — с ее помощью можно понять, где они находятся (хотя это всего лишь схематичное отображение, у этих символов нет графического представления, они невидимые):

- Привет!¶
- О, привет!¶
- Как дела?

Устройство, которое выводит соответствующий текст, учитывает этот символ. Например, принтер при встрече с LF протаскивает бумагу вверх на одну строку, а текстовый редактор переносит весь последующий текст ниже, также на одну строку.

\n — это пример экранирующей последовательности (escape sequence). Их ещё называют управляющими конструкциями. Хотя таких символов не один десяток, в программировании часто встречаются всего несколько. Кроме перевода строки, к таким символам относятся табуляция (разрыв, получаемый при нажатии на кнопку Tab) и возврат каретки (только в Windows). Нам, программистам, часто нужно использовать перевод строки \n для правильного форматирования текста.

console.log('Gregor Clegane\nDunsen\nPolliver\nChiswyck');

На экран выведется:

Gregor Clegane
Dunsen
Polliver
Chiswyck

Обратите внимание на следующие моменты:

  1. Не имеет значения, что стоит перед или после \n: символ или пустая строка. Перевод будет обнаружен и выполнен в любом случае.
  2. Помните, что строка может содержать один символ или вообще ноль символов. А еще строка может содержать только \n. Проанализируйте следующий пример:
    console.log('\n');
    console.log('Dunsen');
    

    Здесь мы сначала выводим строку «перевод строки», а потом делаем вывод обыкновенной строки. Программа выведет на экран:

    Dunsen
    

    Почему перед строкой Dunsen появилось две пустые строки, а не одна? Дело в том, что оператор console.log() при выводе значения автоматически добавляет в конец символ перевода строки. Таким образом, один перевод строки мы указали явно, передав этот символ экранирующей последовательности аргументом в функцию, а второй перевод строки добавлен самой функцией автоматически.

    Ещё пример кода:

    console.log('Polliver');
    console.log('Gregor Clegane');
    console.log();
    console.log('Chiswyck');
    console.log('\n');
    console.log('Dunsen');
    

    Вывод будет таким:

    Polliver
    Gregor Clegane
    Chiswyck
    
    Dunsen
    

    Сейчас у вас достаточно знаний, чтобы самостоятельно разобраться и понять, почему вывод сформировался именно таким образом.

  3. Если нам понадобится вывести \n именно как текст (два отдельных печатных символа), то можно воспользоваться уже известным нам способом экранирования, добавив еще один \ в начале. То есть последовательность \\n отобразится как символы \ и n, идущие друг за другом.
console.log('Joffrey loves using \\n');

на экран выйдет:

Joffrey loves using \n

Небольшое, но важное замечание про Windows. В Windows для перевода строк по умолчанию используется \r\n. Такая комбинация хорошо работает только в Windows, но создаёт проблемы при переносе в другие системы (например, когда в команде разработчиков есть пользователи как Windows, так и Linux). Дело в том, что последовательность \r\n имеет разную трактовку в зависимости от выбранной кодировки (рассматривается позже). По этой причине, в среде разработчиков принято всегда использовать \n без \r, так как LF всегда трактуется одинаково и отлично работает в любой системе. Не забудьте настроить ваш редактор на использование \n.

Задание

Напишите программу, которая выводит на экран:

- Did Joffrey agree?
- He did. He also said "I love using \n".

При этом программа использует только один console.log(), но результат на экране должен выглядеть в точности, как показано выше.


Советы

Определения

  • Экранирующая последовательность – специальная комбинация символов в тексте. Например, \n — это перевод строки.
17. Конкатенация 

JavaScript: Конкатенация

В веб-разработке программы постоянно оперируют строками. Всё, что мы видим на сайтах, так или иначе представлено в виде текста. Этот текст чаще всего динамический, то есть полученный из разных частей, которые соединяются вместе. Операция соединения строк в программировании называется конкатенацией.

// Оператор такой же, как и при сложении чисел
// но здесь он имеет другой смысл (семантику)
console.log('Dragon' + 'stone');
// => 'Dragonstone'

Склеивание строк всегда происходит в том же порядке, в котором записаны операнды. Левый операнд становится левой частью строки, а правый — правой.

Вот еще несколько примеров:

console.log('Kings' + 'wood');     // => Kingswood

// Обратный порядок слов
console.log('road' + 'Kings');     // => roadKings

// Конкатенировать можно абсолютно любые строки
console.log("King's" + 'Landing'); // => King'sLanding

Как видите, строки можно склеивать, даже если они записаны с разными кавычками.

В последнем примере название города получилось с ошибкой: King’s Landing нужно писать через пробел. Но в наших начальных строках не было пробелов, а пробелы в самом коде слева и справа от символа + не имеют значения, потому что они не являются частью строк.

Выхода из этой ситуации два:

// Оба способа равнозначны

// Ставим пробел в левой части
console.log("King's " + 'Landing'); //  => King's Landing
// Ставим пробел в правой части
console.log("King's" + ' Landing'); //  => King's Landing

Пробел — такой же символ, как и другие. Чем больше пробелов, тем шире отступы:

console.log("King's " + ' Landing');   // => King's  Landing

console.log("King's  " + '  Landing'); // => King's    Landing

Задание

Выведите на экран

Winter came for the House of Frey.

используя конкатенацию слов.


Определения

  • Конкатенация – операция соединения двух строк. Например, console.log("King's " + ' Landing');
18. Кодировка 

JavaScript: Кодировка

На самом глубоком уровне компьютер оперирует исключительно цифрами 0 и 1. Это так называемый двоичный код, а единички и нули называются битами, от “binary digit” — «двоичная цифра».

Обычные, привычные нам числа в десятичной системе счисления, закодированы с помощью двоичных чисел:

  • 0 ← 0
  • 1 ← 1
  • 2 ← 10
  • 3 ← 11
  • 4 ← 100
  • 5 ← 101

Но как быть с текстом? Компьютер на самом деле не знает ничего о буквах, знаках пунктуации и прочих текстовых символах. Все эти символы также закодированы числами.

Можно взять английский алфавит и дать каждой букве число, начиная с единицы по порядку:

  • a ← 1
  • b ← 2
  • c ← 3
  • d ← 4
  • z ← 26

Далее можно научить компьютер понимать эту таблицу и переводить текст в числа и наоборот — числа в текст:

  • hello → 8 5 12 12 15
  • 7 15 15 4 → good

Подобные таблицы, в которых сопоставляются буквы и числа, называются кодировками. Кроме букв алфавита, в таблицы кодировок входят знаки препинания и другие полезные символы. Вы наверняка сталкивались с кодировками, например, ASCII или UTF-8.

Разные кодировки содержат различное количество символов. Изначально небольших таблиц вроде ASCII было достаточно для большинства задач. Но в ней есть только латинские буквы, несколько простых символов вроде % и ? и специальные управляющие символы типа перевода строки.

С распространением компьютеров, разным странам понадобились свои, более широкие таблицы. В том числе для кириллических букв, иероглифов, арабской вязи, дополнительных математических и типографских символов, а впоследствии — даже для эмодзи-смайликов.

Сегодня в большинстве случаев используется один из вариантов юникода — utf-8. Он включает в себя знаки почти всех письменных языков мира. Благодаря этому, письмо, сформированное человеком в Китае на китайском, без проблем можно открыть и увидеть в первозданном виде на компьютере в Финляндии (поймет он его или нет, это уже другой вопрос).

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

Задание

В JavaScript можно «запросить» и вывести на экран любой символ из кодировки ASCII. Например:

console.log(String.fromCharCode(63));

На экран выведется символ с номером 63 — вопросительный знак ?. Таким способом можно выводить любой символ.

Найдите в интернете таблицу кодов ASCII. Можно использовать запросы типа “ascii codes table” или «коды ascii». Обычно в таких таблицах коды указаны сразу в нескольких системах счисления: десятичной, двоичной, восьмеричной и шестнадцатеричной. Нас интересует десятичный код (dec или decimal).

Используя пример выше и найденную таблицу, выведите на экран символы ~^ и % (каждый на своей собственной строке).

(Конечно, можно «обмануть» тесты и просто сделать что-то типа console.log('~'), но так будет совсем неинтересно 🙂


Определения

  • Кодировка – набор символов, закодированных с помощью чисел для представления текста в электронном виде.

Переменные в языке JavaScript

Информацию можно помещать в специальные «хранилища» — переменные. Это позволяет переиспользовать уже существующие данные и не дублировать их в разных частях кода. В этом модуле мы разберем как изменять переменные и именовать их, чтобы чтение вашего кода было понятным для любого разработчика. Вы поймете, что придумать название переменной не так-то просто! А еще расскажем, как использовать переменные для упрощения сложных вычислений.

19. Что такое переменная

JavaScript: Что такое переменная

Представьте себе задачу: нам нужно напечатать на экран фразу Father! два раза или даже пять раз. Эту задачу можно решить в лоб:

console.log('Father!');
console.log('Father!');

В простейшем случае так и стоит поступить, но если фраза Father! начнет использоваться чаще, да еще и в разных частях программы, то придется ее везде повторять. Проблемы с таким подходом начнутся тогда, когда понадобится изменить нашу фразу, а такое происходит довольно часто. Нам придется найти все места, где использовалась фраза Father!, и выполнить необходимую замену. А можно поступить по-другому. Вместо копирования нашего выражения достаточно создать переменную с этой фразой.

// greeting - переводится как приветствие
let greeting = 'Father!';
console.log(greeting); // => 'Father!'
console.log(greeting); // => 'Father!'

Переменная указывает на данные, которые были в неё записаны. Благодаря этому, данные можно использовать многократно без необходимости их постоянно дублировать. Сама переменная создается и наполняется данными (инициализируется) с помощью инструкции let greeting = 'Father!'.

Для имени переменной используется любой набор допустимых символов, к которым относятся буквы английского алфавита, цифры, а также знаки _ и $. При этом цифру нельзя ставить в начале. Имена переменных регистрозависимы, то есть имя hello и имя heLLo – это два разных имени, а значит и две переменные. Регистр в JavaScript имеет важное значение, никогда не забывайте про него.

Переменную не обязательно инициализировать данными во время объявления. Иногда бывает нужно ее создать, а наполняться она будет потом:

let greeting;

// Использование
console.log(greeting); // undefined

// Изменение переменной в следующем уроке

Объявленная, но не инициализированная переменная, содержит внутри себя значение undefined. Это специальное значение, используемое тогда, когда ничего не определено.

Количество создаваемых переменных ничем не ограничено, большие программы содержат десятки и сотни тысяч имен переменных:

let greeting1 = 'Father!';
console.log(greeting1);
console.log(greeting1);

let greeting2 = 'Mother!';
console.log(greeting2);
console.log(greeting2);

Для удобства анализа программы, переменные принято создавать как можно ближе к тому месту, где они используются.

Задание

Создайте переменную с именем motto и содержимым What Is Dead May Never Die!. Распечайте содержимое переменной.


Советы

Определения

  • Переменная – способ сохранить информацию и дать ей имя для последующего использования в коде.
20. Изменение переменной

JavaScript: Изменение переменной

Само слово “переменная” говорит о том, что ее можно менять. И действительно, с течением времени внутри программы значения переменных могут изменяться.

let greeting = 'Father!';
console.log(greeting);
console.log(greeting);

greeting = 'Mother!';
console.log(greeting);
console.log(greeting);

Имя осталось тем же, но внутри другие данные. Обратите внимание на ключевое различие между объявлением переменной и ее изменением. Ключевое слово let ставится только при создании переменной, но при изменении оно уже не используется.

Переменные — мощная и в тоже время опасная вещь. Никогда нельзя быть точно уверенным, что записано внутри неё, не проанализировав код, который находится перед переменной. Именно этим занимаются разработчики во время отладки, когда пытаются разобраться почему программа не работает или работает не так, как задумано.

Как вы увидите позже, в JavaScript есть не только переменные. Более того, переменные не так часто используются с целью менять, намного чаще их используют с целью хранить.

Задание

В упражнении определена переменная, внутри которой находится строка. Переопределите значение этой переменной и присвойте ей ту же строку, но в перевернутом виде, т.е. расположите символы первоначальной строки в обратном порядке.


Советы

  • Если в редакторе есть запись // BEGIN и // END, то код нужно писать между этими строчками.

Определения

  • Переменная – способ сохранить информацию и дать ей имя для последующего использования в коде.
21. Выбор имени переменной

JavaScript: Выбор имени переменной

Представим себе, что программа из прошлого урока выглядит так:

let x = 'Father!';
console.log(x);
console.log(x);

Она по прежнему работает, но в ней изменилось имя переменной на x. Компьютеру без разницы, как мы называем переменные, это бездушная машина, но вот программистам — нет. Мы гораздо чаще читаем код, чем пишем. Причём не свой, а написанный другими людьми. От качества и понятности имён переменных зависит половина успеха в анализе кода.

Лучше посидеть и придумать название, которое описывает суть, смысл переменной, чем назвать её как попало, а в будущем переделывать. Постарайтесь давать им такие имена, чтобы они были максимально понятны без контекста, без изучения окружающего кода.

Существует общепринятое правило: не используйте транслит для имён, только английский язык. Если вы испытываете сложности с английским, то пользуйтесь переводчиком. Со временем, копаясь в чужом коде, вы сформируете правильные понятия для именования.

Среди разработчиков есть шутка: «самое сложное в программировании — названия переменных и инвалидация кеша». Придумывать названия и правда сложно. Как бы вы назвали переменную, в которой хранится количество неоплаченных заказов от клиентов, имеющих задолженность в предыдущем квартале?

Самопроверка. Придумайте название для переменной, в которой будет храниться «количество братьев и сестёр короля». Запишите его в блокноте или отправьте себе на почту. Не указывайте там ничего, кроме названия переменной. А через несколько уроков мы вернёмся к этой теме 😉

Задание

Создайте переменную, описывающую количество моих братьев, и присвойте ей значение 2. Распечатайте содержимое переменной. Затем сравните свое имя с именем, которое используется в учительском решении.


Советы

22. Ошибки при работе с переменными

JavaScript: Ошибки при работе с переменными

Порядок следования инструкций в коде с переменными играет огромное значение. Переменная должна быть определена (но не обязательно инициализированна) до того, как будет использована. Ниже пример ошибки, которую очень часто допускают новички:

// Uncaught ReferenceError: greeting is not defined
console.log(greeting);
let greeting = 'Father!';

Запуск программы с примера выше завершается ошибкой ReferenceError: greeting is not definedReferenceError – это ошибка обращения, она означает, что в коде используется имя (говорят идентификатор), которое не определено. Причём в самой ошибке об этом говорят прямо: greeting is not defined, что переводится как greeting не определен. Кроме неправильного порядка определения, в JavaScript встречаются банальные опечатки — как при использовании переменной, так и при её объявлении.

Количество подобных ошибок уменьшается за счет использования правильно настроенного редактора. Такой редактор подсвечивает имена, которые используются без объявления и предупреждает о возможных проблемах.

Еще одна распространенная ошибка — попытаться объявить уже объявленную переменную:

let greeting = 'Father!';
let greeting = 'Father!';

Так делать нельзя. Придётся создать новую переменную.

Задание

Найдите в программе необъявленную переменную и объявите ее, присвоив ей значение ‘Dragon’;


Определения

  • Переменная – способ сохранить информацию и дать ей имя для последующего использования в коде.
23. Выражения в определениях

JavaScript: Выражения в определениях

Переменные полезны не только для хранения и переиспользования информации, но и для упрощения сложных вычислений. Давайте рассмотрим пример: нужно перевести евро в рубли через доллары. Подобные конвертации через промежуточную валюту часто делают банки при покупках за рубежом.

Для начала переведем 50 евро в доллары. Допустим, что один евро — 1.25 долларов:

let dollarsCount = 50 * 1.25;
console.log(dollarsCount); // => 62.5

В предыдущем уроке мы записывали в переменную конкретное значение. А здесь let dollarsCount = 50 * 1.25; справа от знака равно находится выражение. Интерпретатор вычислит результат — 62.5 — и запишет его в переменную. С точки зрения интерпретатора не важно, что перед ним: 62.5 или 50 * 1.25, для него оба варианта — выражения, которые надо вычислить. И они вычисляются в одно и тоже значение — 62.5.

Любая строка — выражение. Конкатенация строк — тоже выражение. Когда интерпретатор видит выражение, он обрабатывает его и генерирует результат — значение выражения. Вот несколько примеров выражений, а в комментариях справа от каждого выражения — итоговое значение:

62.5             // 62.5
50 * 1.25        // 62.5
120 / 10 * 2     // 24

'hello'          // "hello"
'Good' + 'will'  // "Goodwill"

Правила построения кода (грамматика языка) таковы, что в тех местах, где ожидается выражение, можно поставить любое вычисление (не только математическое, но и, например, строковое — как конкатенация), и программа останется работоспособной. По этой причине невозможно описать и показать все случаи использования всех операций.

Программы состоят из множества комбинаций выражений, и понимание этой концепции — один из ключевых шагов на вашем пути.

Основываясь на сказанном выше, подумайте, сработает ли такой код?

let who = "dragon's" + 'mother';
console.log(who);

Запустите его на repl.it и поэкспериментируйте.

Вернемся к нашей валютной программе. Запишем стоимость доллара в рублях как отдельную переменную. Вычислим цену 50 евро в долларах, умножив их на 1.25. Допустим, что 1 доллар — 60 рублей:

let rublesPerDollar = 60;
let dollarsCount = 50 * 1.25; // 62.5
let rublesCount = dollarsCount * rublesPerDollar; // 3750

console.log(rublesCount);

А теперь давайте добавим к выводу текст с помощью конкатенации:

let rublesPerDollar = 60;
let dollarsCount = 50 * 1.25; // 62.5
let rublesCount = dollarsCount * rublesPerDollar; // 3750

console.log('The price is ' + rublesCount + ' rubles');
The price is 3750 rubles

Любая переменная может быть частью любого выражения. В момент вычисления, вместо имени переменной подставляется её значение.

Интерпретатор вычисляет значение dollarsCount до того, как эта переменная начнет использоваться в других выражениях. Когда подходит момент использования переменной, Javascript «знает» значение, потому что уже вычислил его.

Задание

Напишите программу, которая берет исходное количество евро, записанное в переменную eurosCount, переводит евро в доллары и выводит на экран. Затем полученное значение переводит в рубли и выводит на новой строчке.

Пример вывода для 100 евро:

125
7500

Считаем, что:
– 1 евро = 1.25 долларов
– 1 доллар = 60 рублей

24. Переменные и конкатенация

JavaScript: Переменные и конкатенация

Для закрепления предыдущей темы, попробуем использовать переменные с конкатенацией. Синтаксически ничего не меняется: мы умеем конкатенировать (склеивать) две строки:

let what = 'Kings' + 'road';
console.log(what); // => 'Kingsroad'

… а значит сумеем конкатенировать строку и одну переменную, в которой записана строка:

let first = 'Kings';
let what = first + 'road';

console.log(what); // => 'Kingsroad'

… и даже конкатенировать две переменные, в которых записаны строки:

let first = 'Kings';
let last = 'road';

let what = first + last;
console.log(what); // => 'Kingsroad'

Задание

Сайты постоянно посылают письма своим пользователям. Типичная задача — сделать автоматическую отправку персонального письма, где в заголовке будет имя пользователя. Если где-то в базе сайта хранится имя человека в виде строки, то задача генерации заголовка сводится к конкатенации: например, нужно склеить строку Здравствуйте со строкой, где записано имя.

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

Для заголовка используйте переменные firstName и greeting, запятую и восклицательный знак. Выведите это на экран в правильном порядке.

Для тела письма используйте переменные info и intro, при этом второе предложение должно быть на новой строке.

Результат на экране будет выглядеть так:

Hello, Joffrey!
Here is important information about your account security.
We couldn't verify you mother's maiden name.

Выполните задание, используя только два console.log().


Советы

  • Подумайте, с какой строкой и в каком порядке нужно склеивать переменные, чтобы получить такой двустрочный вывод тела письма.
  • Помните, что можно создать строку, которая содержит только управляющую последовательность \n.
25. Именование переменных

JavaScript: Именование переменных

greeting — пример простого имени, но не все имена так просты. Довольно часто они составные, то есть включают в себя несколько слов. Например, «имя пользователя». В разных языках применяются разные стили кодирования, и имя переменной будет отличаться.

В именовании переменных можно выделить три основных подхода, которые иногда комбинируют друг с другом. Все эти подходы проявляют себя, когда имя переменной состоит из нескольких слов:

  • kebab-case — составные части переменной разделяются дефисом. Например: my-super-var.
  • snakecase — для разделения используется подчеркивание. Например: `mysuper_var`.
  • CamelCase — каждое слово в переменной пишется с заглавной буквы. Например: MySuperVar.
  • lowerCamelCase — каждое слово в переменной пишется с заглавной буквы, кроме первого. Например: mySuperVar.

В Javascript используется CamelCase и его вариация lowerCamelCase, при котором первая буква первого слова — строчная. Именно lowerCamelCase применяется для переменных. Это значит, что имена соединяются друг с другом, при этом все имена кроме первого становятся с заглавной буквы: userName. С тремя словами это выглядит так: mySuperVariable.

Задание

Создайте две переменные с именами «первое число» и «второе число» на английском языке, используя lowerCamelCase. Запишите в первую переменную число 11, во вторую — -100. Выведите на экран произведение чисел, записанных в получившихся переменных.

Код будет работать с любыми названиями, а наша система всегда проверяет только результат на экране, поэтому выполнение этого задания — под вашу ответственность.


Определения

  • Стандарт кодирования – Набор синтакстических и стилистических правил написания кода.
26. Магические числа

JavaScript: Магические числа

Вспомним один из прошлых уроков:

let dollarsCount = 50 * 1.25; // 62.5
let rublesCount = dollarsCount * 60; // 3750

console.log(rublesCount);

С точки зрения профессиональной разработки, такой код «пахнет». Так описывают код, который сложен для понимания. И причина здесь вот в чем: уже сейчас, глядя на число 60 и 1.25, вы скорее всего задаетесь вопросом: «что это за числа?». А представьте, что будет через месяц! А как его поймет новый программист, не видевший код ранее? В нашем примере контекст восстанавливается благодаря грамотному именованию, но в реальной жизни код значительно сложнее, и догадаться до смысла чисел зачастую невозможно.

Этот «запах» называют Magic Numbers (магические числа). Числа, происхождение которых невозможно понять без глубокого знания происходящего внутри данного участка кода.

Выход из ситуации прост: достаточно создать переменные с правильными именами, как все встанет на свои места.

let dollarsPerEuro = 1.25;
let rublesPerDollar = 60;

let dollarsCount = 50 * dollarsPerEuro; // 62.5
let rublesCount = dollarsCount * rublesPerDollar; // 3750

console.log(rublesCount);

Обратите внимание на следующие детали:

  • Именование lowerCamelCase.
  • Две новые переменные отделены от последующих вычислений пустой строчкой. Эти переменные имеют смысл и без вычислений, поэтому такое отделение уместно, оно повышает читаемость.
  • Получился хорошо именованный и структурированный код, но он длиннее прошлой версии. Так часто бывает, и это нормально. Код должен быть читабельным.

Задание

Вы столкнулись с таким кодом, который выводит на экран общее количество комнат во владении нынешнего короля:

let king = 'King Balon the 6th';
console.log(king + ' has ' + (6 * 17) + ' rooms.');

Как видите, это магические числа: непонятно, что такое 6 и что такое 17. Можно догадаться, если знать историю королевской семьи: каждый новый король получает в наследство все замки от предков и строит новый замок — точную копию родительского.

Эта странная династия просто плодит одинаковые замки…

Избавьтесь от магических чисел, создав новые переменные, и выведите текст на экран.

Получится так:

King Balon the 6th has 102 rooms.

Названия переменных должны передавать смысл чисел, но должны при этом оставаться достаточно короткими и ёмкими для комфортного чтения.

Помните: код будет работать с любыми названиями, а наша система всегда проверяет только результат на экране, поэтому выполнение этого задания — под вашу ответственность.

27. Константы 

JavaScript: Константы

Во всем модуле подавляющее большинство примеров кода использовало переменные в качестве имен (псевдонимы) конкретных значений, а не как переменные, которые меняют свое значение со временем.

let dollarsInEuro = 1.25
let rublesInDollar = 60;

let dollarsCount = 50 * dollarsInEuro; // 62.5
let rublesCount = dollarsCount * rublesInDollar; // 3750

console.log(rublesCount);

В программировании принято называть такие имена константами, и многие языки поддерживают константы как конструкцию. JavaScript, как раз, относится к таким языкам, и его стандарты кодирования говорят прямо — если значение не меняется, то мы имеем дело с константой. Перепишем пример выше на использование констант:

const dollarsInEuro = 1.25;
const rublesInDollar = 60;

const euros = 1000;
const dollars = euros * dollarsInEuro;    // 1250
const rubles = dollars * rublesInDollar; // 75000

console.log(rubles);

Единственное изменение заключается в том, что ключевое слово let заменилось на const, но это только синтаксис. Теперь, если попытаться изменить любую константу, то мы получим сообщение об ошибке. В остальном они используются точно так же, как и переменные.

const pi = 3.14;
pi = 5; // TypeError: Assignment to constant variable.

Зачем такие сложности? Почему бы не оставить только переменные? Даже если бы мы оставили только переменные, то это не отменяет того факта, что они часто использовались бы как константы, более того, код на JavaScript можно и идиоматично писать без использования переменных вообще. Посмотрите на пример из реального кода Хекслета. На текущем этапе вы его врядли поймете, но попробуйте посчитать количество констант и переменных внутри него, вы увидите, что здесь ровно одна переменная, и целая куча констант.

Константы значительно проще для анализа, когда мы видим константу в коде, то нам сразу понятно, что ее значение всегда остается прежним. При использовании констант отсутствует понятие времени. С переменными все не так, мы не можем быть уверены в их значении, приходится анализировать весь код, чтобы понять, как они могли измениться.

Переменные жизненно необходимы только в одном случае (во всех остальных гарантировано можно обойтись без них) – при работе с циклами, до которых мы ещё дойдем.

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

Задание

Создайте константу army, присвойте ей значение 'the white walkers' и распечайте её значение на экран.

28. Интерполяция 

JavaScript: Интерполяция

В уроке про конкатенацию перед нами стояла задача создать заголовок письма из двух констант и знаков препинания. Вы, скорее всего, решили задачу так:

const firstName = 'Joffrey';
const greeting = 'Hello';

console.log(greeting + ', ' + firstName + '!');
// => 'Hello, Joffrey!'

Это довольно простой случай, но даже здесь нужно приложить усилия, чтобы увидеть, какая в итоге получится строка. Нужно следить за несколькими кавычками и пробелами, и без вглядывания не понять, где что начинается и кончается.

Есть другой, более удобный и изящный способ решения той же задачи — интерполяция. Вот, как это выглядит:

const firstName = 'Joffrey';
const greeting = 'Hello';

// Обратите внимание на ограничители строки, это бектики
// Интерполяция не работает с одинарными и двойными кавычками
console.log(`${greeting}, ${firstName}!`);
// => 'Hello, Joffrey!'

Мы просто создали одну строку и «вставили» в неё в нужные места константы с помощью знака доллара и фигурных скобок ${ }. Получился как будто бланк, куда внесены нужные значения. И нам не нужно больше заботиться об отдельных строках для знаков препинания и пробелов — все эти символы просто записаны в этой строке-шаблоне.

В одной строке можно делать сколько угодно подобных блоков.

Интерполяция работает только со строками в бэктиках. Это символ `.

Почти во всех языках интерполяция предпочтительнее конкатенации для объединения строк. Строка при этом получается склеенная, и внутри неё хорошо просматриваются пробелы и другие символы. Во-первых, интерполяция позволяет не путать строки с числами (из-за знака +), а во-вторых, так гораздо проще (после некоторой практики) понимать строку целиком.

Задание

Выведите на экран строку Do you want to eat, <name>?, где вместо <name> должна использоваться константа stark. Вывод должен получиться таким:

Do you want to eat, Arya?

Советы

Определения

  • Интерполяция – способ соединения строк через вставку значений переменных в строку-шаблон с помощью фигурных скобок. Например, `Hi, ${name}!`.
29. Извлечение символов из строки

JavaScript: Извлечение символов из строки

Иногда нужно получить один символ из строки. Например, если сайт знает имя и фамилию пользователя, и в какой-то момент требуется вывести эту информацию в формате A. Ivanov, то нужно взять первый символ из имени.

const firstName = 'Tirion';

console.log(firstName[0]); // => 'T'

Квадратные скобки с цифрой — это специальный синтаксис извлечения символа из строки. Цифра называется индексом — позицией символа внутри строки. Индексы начинаются с 0 почти во всех языках программирования — поэтому, чтобы получить первый символ, нужно указать индекс 0. Индекс последнего элемента равен длине строки минус единица:

// Длина строки 6, поэтому последний индекс — это 5
const firstName = 'Tirion';

console.log(firstName[5]); // => 'n'

// Вопрос на самопроверку. Что выведет этот код?
const magic = '\nyou'
console.log(magic[1]); // => ?

Индексом может быть не только конкретное число, но и значение переменной. Вот пример, который приведёт к тому же результату — выводу на экран символа T, но индекс внутри квадратных скобок записан не числом, а константой:

const firstName = 'Tirion';
const index = 0;

console.log(firstName[index]); // => 'T'

Технически можно указать индекс и за пределами слова. Для нашего примера — это числа от 6 и выше. JavaScript не считает такое поведение ошибкой. Обращение по несуществующему индексу вернет значение undefined.

const firstName = 'Tirion';

console.log(firstName[10]); // => undefined

Задание

Выведите на экран последний символ строки, находящейся в переменной name

Типы данных JavaScript

JavaScript — язык со слабой типизацией и неизменяемыми примитивными типами данных. Что произойдет, если мы попробуем умножить число на строку? Каким образом JavaScript понимает, что за тип данных перед ним? И что делает JavaScript, когда видит несоответствие типов? Ответы на эти вопросы вы найдете в текущем модуле.

30. Типы данных

JavaScript: Типы данных

Что произойдет, если мы попробуем умножить число на строку? JavaScript вернет NaN (не число) — то самое значение. Оно возникает там, где вместе используются несовместимые значения. В данном случае число и строка:

3 * 'Dracarys'; // NaN

Внутри высокоуровневых языков программирования данные разделяются по типам. Любая строка относится к типу String, а числа — к типу Number и BigInt (очень большие числа). Зачем нужны типы? Для защиты программы от трудноотловимых ошибок. Типы определяют две вещи:

  • Возможные (допустимые) значения. Например, числа в JavaScript делятся на два типа: Number и BigInt. Первые — это все числа ниже определенного порога (его можно посмотреть), вторые — выше. Такое разделение связано с техническими особенностями работы аппаратуры.
  • Набор операций, которые можно выполнять над этим типом. Например, операция умножения имеет смысл для типа «целые числа». Но не имеет смысла для типа «строки»: умножать слово «мама» на слово «блокнот» — бессмыслица.

JavaScript ведет себя двояко, когда встречается с нарушениями. В некоторых ситуациях, он ругается на недопустимость операции и завершается с ошибкой. В других — программа продолжает работать. В этом случае недопустимая операция возвращает что-то похожее на NaN, как в примере выше.

Каким образом JavaScript понимает, что за тип данных перед ним? Достаточно просто. Любое значение где-то инициализируется и, в зависимости от способа инициализации, становится понятно, что перед нами. Например, числа — это просто числа без дополнительных символов, кроме точки для рациональных чисел. А вот строки всегда ограничены специальными символами (в JavaScript три разных варианта). Например, такое значение '234' – строка, несмотря на то, что внутри нее записаны цифры.

JavaScript позволяет узнать тип данных с помощью оператора typeof:

typeof 3; // 'number'
typeof 'Game'; // 'string'

Типы данных Number, BigInt и String — это примитивные типы, они встроены в сам язык. Помимо них, есть еще несколько примитивных типов данных, которые мы изучим позже, например, Boolean. Кроме того, в JavaScript встроен также составной (набор данных) тип Object (а на его базе массивы, даты и другие). Они рассматриваются на Хекслете.

По-английски строки в программировании называются “strings”, а строчки текстовых файлов — “lines”. Например, в коде выше есть две строчки (lines), но только одна строка (strings). В русском иногда может быть путаница, поэтому во всех уроках мы будем говорить строка для обозначения типа данных «строка», и строчка для обозначения строчек (lines) в файлах.

Задание

Выведите на экран число -0.304.


Советы

Определения

  • Тип данных – множество данных в коде (разновидность информации). Тип определяет, что можно делать с элементами конкретного множества. Например, целые числа, рациональные числа, строки — это разные типы данных.
  • Примитивные типы данных – простые типы, встроенные в сам язык программирования.
  • Строка (string) – тип данных, описывающий набор символов (иными словами — текст); например, 'text' или "text".
31. undefined 

JavaScript: undefined

Объявление переменных возможно и без указания конкретного значения. Что будет выведено на экран если её распечатать?

let name;
console.log(name); // ?

На экране появится undefined, специальное значение особого типа, которое означает отсутствие значения. Undefined активно используется самим JavaScript в самых разных ситуациях, например, при обращении к несуществующему символу строки:

const name = 'Arya';
console.log(name[8]);

Смысл (семантика) значения undefined именно в том, что значения нет. Однако, ничто не мешает написать такой код:

let key = undefined;

И хотя интерпретатор позволяет такое сделать, это нарушение семантики значения undefined, ведь в этом коде выполняется присваивание, а значит — подставляется значение.

JavaScript — один из немногих языков, в которых в явном виде присутствует понятие undefined. В остальных языках его роль выполняет значение null, которое, кстати, тоже есть в JavaScript.

Вопрос на самопроверку. Почему нельзя объявить константу без указания значения?

Задание

Выведите на экран значение undefined, не указывая его явно (через присваивание или передав напрямую в console.log()). Если не догадаетесь, как это сделать, подсмотрите решение учителя.

32. Неизменяемость примитивных типов

JavaScript: Неизменяемость примитивных типов

Что произойдет, если попытаться изменить символ в строке?

let firstName = 'Alexander';
// Код выполнится без ошибок
firstName[0] = 'B';
console.log(firstName); // => "Alexander"

Как это ни странно, но значение переменной firstName останется прежним, хотя код выполнится без ошибок. Так происходит из-за неизменяемости примитивных типов в JavaScript — язык не дает никакой физической возможности поменять строку. Неизменяемость примитивных типов важна по многим причинам, ключевая — производительность. Но что делать, если нам действительно нужно её изменить? Для этого и существуют переменные:

let firstName = 'Alexander';
// Код выполнится без ошибок
firstName = 'Blexander'
console.log(firstName); // => "Blexander"

Есть большая разница между изменением значения переменной и изменением самого значения. Примитивные типы в JavaScript поменять нельзя (а вот составные можно, подробнее о них уже на самом Хекслете), а заменить значение переменной — без проблем.

Задание

Вам даны три константы с фамилиями разных людей. Составьте и выведите на экран слово из символов в таком порядке:

  1. Третий символ из первой строки;
  2. Второй символ из второй строки;
  3. Четвертый символ из третьей строки;
  4. Пятый символ из второй строки;
  5. Третий символ из второй строки;

Попробуйте использовать интерполяцию: внутри фигурных скобок можно помещать не только целые переменные, но и отдельные символы с помощью квадратных скобок.

33. Слабая типизация

JavaScript: Слабая типизация

Нам известно про два разных типа данных: числа и строки. Мы, например, можем складывать числа, потому что операция сложения — это операция для типа «числа».

А что, если применить эту операцию не к двум числам, а к числу и строке?

console.log(1 + '7'); // => '17'

Несмотря на то, что '7' — это строка, а не число, интерпретатор JavaScript выдал ответ 17, как если бы мы складывали две строки. Когда JavaScript видит несоответствие типов, он сам пытается преобразовать информацию. В данном случае он преобразовал число 1 в строку '1', а потом спокойно сделал конкатенацию '1' и '7'.

Не все языки так делают. JavaScript — это язык со слабой типизацией. Он знает о существовании разных типов (числа, строки и др.), но относится к их использованию не очень строго, пытаясь преобразовывать информацию, когда это кажется разумным. Иногда JavaScript даже доходит до крайностей. Большинство выражений, не работающих в других языках, прекрасно работают в JavaScript. Попробуйте выполнить любую арифметическую операцию (кроме сложения), подставив туда строки или любые другие типы данных (кроме ситуации, когда оба операнда – это числа или строки, содержащие только число) — вы увидите, что они всегда будут работать и возвращать NaN, что довольно логично.

const result = 'one' * 'two';
console.log(result); // => NaN

В языках со строгой типизацией сложить число со строкой не получится.

JavaScript был создан для интернета, а в интернете вся информация — это строки. Даже когда вы вводите на сайте номер телефона или год рождения, на сервер эта информация поступает не как числа, а как строки. Поэтому авторы языка решили, что автоматически преобразовывать типы — правильно и удобно.

Такое автоматическое неявное преобразование типов с одной стороны и правда удобно. Но на практике это свойство языка создает множество ошибок и проблем, которые трудно найти. Код может иногда работать, а иногда не работать — в зависимости от того, «повезло» ли в конкретном случае с автоматическим преобразованием. Программист это заметит не сразу.

В дальнейших заданиях вы будете встречаться с таким поведением не раз. Часто будет возникать вопрос «почему мой код работает не так, как я ожидаю?».

Слабая типизация красной нитью проходит сквозь всю разработку на Javascript.

Задание

Выведите на экран результат выражения: 7 - (-8 - -2). Попробуйте сделать число 7 не числом, а строкой. Поэкспериментируйте с другими числами тоже.

Вызов функций

Для выражения любой произвольной операции в программировании существует понятие «функция». Функции — кирпичики, из которых программисты строят системы. В этом модуле мы научимся пользоваться уже созданными функциями. Посмотрим на сигнатуру функции в документации и разберемся, как её использовать. Познакомимся со стандартными библиотеками, которые хранят тысячи функций. Все функции невозможно выучить, но каждый программист должен знать, где искать документацию по ним.

34. Функции и их вызов

JavaScript: Функции и их вызов

Сложение, конкатенация, нахождение остатка от деления и остальные рассмотренные операции – все это довольно базовые возможности языков программирования. Математика не ограничена арифметикой, кроме нее есть и множество других разделов со своими операциями, например, геометрия. То же самое касается и строк: их можно переворачивать, менять регистр букв, удалять лишние символы — и это только самое простое. И, наконец, на более высоком уровне есть прикладная логика конкретного приложения. Программы списывают деньги, считают налоги, формируют отчеты. Количество подобных операций бесконечно и индивидуально для каждой программы. И все они должны быть как-то выражены в коде.

Для выражения любой произвольной операции в программировании существует понятие функция. Функции бывают как встроенные, так и добавленные программистом. С одной встроенной функцией мы уже знакомы, это log() в вызове console.log().

Функции — одна из ключевых конструкций в программировании, без них невозможно сделать практически ничего. Знакомство с ними мы начинаем как можно раньше, так как весь дальнейший материал оперирует функциями по максимуму. Сначала мы научимся пользоваться уже созданными функциями, а уже потом научимся создавать свои собственные.

Начнем с простых функций для работы над строками. Ниже пример вызова функции reverse(), которая переворачивает строку:

// reverse это функция
import { reverse } from 'hexlet-basics/string';

// Вызов функции reverse с аргументом 'Hello!'
const result = reverse('Hello!');
console.log(result); // => '!olleH'

Лирическое отступление. Первая строчка в этом коде – импорт функции из другого модуля. Импорты и модули изучаются на Хекслете, здесь же они будут присутствовать в задании «как есть», так как без них невозможно использовать функции, определенные в других файлах. Не заморачивайтесь, если вам не понятен смысл этого действия, подробнее о нем можно узнать из курса введение в программирование.

Мы создали константу result и указали интерпретатору записать в неё результат, возвращаемый функцией reverse() при её вызове. В этом смысле функции подобны операциям – они всегда возвращают результат своей работы. Запись reverse('Hello!') означает, что вызывается функция с именем reverse, в которую был передан аргумент (или параметр) 'Hello!'. Аргументы нужны функциям для работы так же, как операторам нужны операнды. Функция reverse() переворачивает именно ту строку, которая передается ей в аргументах.

Вызов функции всегда обозначается скобками (), идущими сразу за именем функции. В скобках может быть любое количество аргументов, а иногда — вообще ни одного. Количество зависит от используемой функции, например, функция pow() принимает на вход два аргумента и возводит число, переданное первым параметром, в степень, переданную вторым параметром.

import { pow } from 'hexlet-basics/math';

const result = pow(2, 3); // 2 * 2 * 2
console.log(result); // => 8

По большому счету, операторы и функции — это одно и то же. Ключевая разница только в том, как они записываются. Если представить сложение как функцию, то она будет выглядеть так:

3 + 5; // 8
sum(3, 5); // 8
// или даже так
// В js такой синтаксис невозможен, но есть языки (lisp)
// где оно выглядит очень похоже (посмотрите курс по Racket)
+(3, 5);

Вопрос на самопроверку. Как узнать, что возвращает вызов console.log? Проверьте.

Задание

В 7 королевствах жил один человек — Сэм Тарли, который занимался картографией. Он имел доступ к компьютерам и умел программировать, поэтому написал для себя функцию calculateDistance(). Функция высчитывает расстояние между городами в лигах. Она принимает два строковых параметра (названия городов) и возвращает число (расстояние между ними).

Вот пример использования, где на экран выводится расстояние между городами Lannisport и Bayasabhad:

import { calculateDistance } from 'hexlet-basics/got';

const distance = calculateDistance('Lannisport', 'Bayasabhad');
console.log(distance);

Первая строчка — это специальный код, подключающий функцию calculateDistance() в вашу программу. Благодаря ей вы можете запускать функцию, не видя её содержимое. Это обычное дело в программировании: вы знаете, что делает функция и как ей пользоваться, но не знаете, как именно она работает внутри.

Воспользуйтесь функцией calculateDistance() и выведите на экран расстояние между городами Qarth и Vaes Dothrak. Не копируйте пример, а создайте константу с другим именем и напишите код с нуля самостоятельно.


Определения

  • Функция – операция, способная принимать данные и возвращать результат; функция вызывается так: foo().
  • Аргумент – информация, которую функция получает при вызове. Например, foo(42) — передача аргумента 42 функции foo()
35. Математические функции JavaScript

JavaScript: Математические функции JavaScript

Объяснение функций в JavaScript немного осложняется структурой языка. Изначально он появился в браузерах и имел сильно ограниченные возможности по отношению к языкам общего назначения. Со временем все изменилось — JavaScript стал мощным языком, захватившим клиентскую разработку и активно использующимся на сервере. Однако наследие осталось, так как нужно поддерживать обратную совместимость. Поэтому, в некоторых местах есть несостыковки, которые нельзя объяснить системой: на них можно только махнуть рукой и сказать: «Так исторически сложилось».

К подобным «местам» относятся математические функции. В предыдущем задании мы использовали самописную функцию pow() (но сами ее не писали), а теперь давайте рассмотрим её версию, встроенную в сам язык.

Math.pow(2, 3); // 8

Что такое Math? Технически — это объект, доступный из любого места программы, но перед тем, как говорить об объектах, нужно проделать очень большой путь. Сейчас достаточно запомнить, что функции для математических операций вызываются через Math.. Наличие этой приставки никак не влияет на понятие функции, которое мы рассмотрели ранее и будем рассматривать позже.

Полный список функций Math доступен в документации. О том, как её правильно читать, мы поговорим далее.

Задание

Сэм рассчитывал количество вражеских солдат, находящихся в башнях-близнецах, и случайно перепутал знаки операций. Его расчеты оказались верными, не считая того, что результат получился отрицательным. Помогите Сэму найти модуль числа бойцов. Выведите на экран модуль числа, находящегося в переменной soldiersCount, используя функцию Math.abs(). Эта функция возвращает модуль переданного числа:

  Math.abs(-3); // 3

Определения

  • Функция – операция, способная принимать данные и возвращать результат; функция вызывается так: foo().
  • Аргумент – информация, которую функция получает при вызове. Например, foo(42) — передача аргумента 42 функции foo()
36. Сигнатура функции

JavaScript: Сигнатура функции

Функция Math.pow() (напомним, что функция здесь pow(), а Math – объект, о котором мы ничего не знаем), возводящая число в какую-нибудь степень, принимает два параметра: какое число возводить и в какую степень возводить. Если вызывать pow() без параметров, то вернется NaN. Функция честно пытается выполнить возведение в степень, но если значение не передано, то интерпретатор автоматически передает ей undefined. JavaScript заставляет программистов быть более аккуратным, чем остальные языки. В большинстве языков, если передать в функцию меньше параметров, чем она ожидает, то возникнет ошибка, — но только не в JavaScript. NaN вернется и при передаче любых не числовых значений:

const result = Math.pow(2, 'boom');
console.log(result); // => NaN

Другая функция может иметь другое число параметров и другие типы параметров. Например, может существовать функция, которая принимает три параметра: число, строку и ещё одно число.

Откуда мы знаем, сколько каких параметров нужно функции Math.pow() и какого типа будет «возврат»? Мы заглянули в сигнатуру этой функции. Сигнатура определяет входные параметры и их типы, а также выходной параметр и его тип. Про функцию Math.pow() можно почитать в документации. В разделе «Синтаксис» есть такой текст:

Math.pow(base, exponent)

Параметры
  base
    Основание степени.
  exponent
    Показатель степени, в которую возводится основание base.

Это сигнатура функции и короткое пояснение на русском языке. Документация позволяет понять, сколько аргументов у функции и какого они типа, возвращает ли что-то функция и если да, то какого типа возвращаемое значение.

Задание

Теперь ваша очередь посмотреть на сигнатуру функции в документации и разобраться, как её использовать. Можете читать документацию на русском языке, но программист обязан уметь читать документацию на английском. Используйте словари или переводчики при необходимости. Лучше сразу привыкать и подтягивать навыки чтения на английском, иначе будут сложности в будущем.

В Math есть функция ceil(). Изучите её документацию.

Напишите программу, которая использует функцию Math.ceil() с константой number и выводит результат на экран.


Определения

  • Сигнатура функции – формальное описание типов аргументов и типа возвращаемого значения функции.
37. Аргументы по умолчанию

JavaScript: Аргументы по умолчанию

Рассмотрим функцию round(), написанную для этого урока. Она округляет переданное число:

import { round } from 'hexlet-basics/math';

const result = round(10.25, 0); // 10

Мы передали в неё два аргумента: число и точность округления. 0 означает, что округление будет до целого значения, то есть дробная часть просто отбрасывается.

Чаще всего нужно округлять именно до целого числа (а не до десятых, например), поэтому создатели функции round() сделали второй аргумент необязательным и задали ему внутри функции значение по умолчанию 0. Значит, можно не указывать второй аргумент, а результат будет тем же:

const result = round(10.25); // 10

Если нужна другая точность, то можно передать аргумент:

// округление до одного знака после запятой
const result = round(10.25, 1); // 10.3

Если функция в JavaScript принимает необязательные аргументы, то они всегда стоят после обязательных. Их количество может быть любым (это зависит от самой функции), но они всегда идут рядом и в конце списка аргументов.

Задание

Округлите число, записанное в переменную number, до двух знаков после запятой и выведите результат на экран.


Определения

  • Аргумент по умолчанию – необязательный аргумент функции.
38. Функции с переменным числом параметров

JavaScript: Функции с переменным числом параметров

Интересная особенность некоторых функций — принимать переменное число аргументов. Речь не идет о значениях по умолчанию. Посмотрите на этот пример:

Math.max(1, 10, 3); // 10

Функция Math.max() находит максимальное значение среди переданных аргументов. Как вы думаете, сколько аргументов она ожидает на вход? Если открыть документацию этой функции, то мы увидим странную конструкцию:

  Math.max([value1[, value2[, ...]]])

Такая запись говорит о том, что эта функция принимает на вход любое число аргументов (и даже может быть вызвана без них). Необязательность передаваемых аргументов описывается скобками [], точно так же описываются и опциональные параметры, у которых есть значения по умолчанию. Возможность передачи любого числа параметров зашита в этой части [, …].

  Math.max(1, -3, 2, 3, 2); // 3

Задание

Посчитайте программно (а не в голове) минимальное число среди 3, 10, 22, -3, 0 — и выведите его на экран. Воспользуйтесь функцией Math.min(), которая работает аналогично Math.max().


Определения

  • Аргумент по умолчанию – необязательный аргумент функции.
39. Вызов функции — выражение

JavaScript: Вызов функции — выражение

Выражение — это код, который при выполнении программы вычисляется в значение.

Какие из этих фрагментов кода являются выражениями?

  1. 42
  2. 10 * 45
  3. 'Kings ' + 'road'
  4. calculateDistance('Lannisport', 'Bayasabhad')

Числа и математические операции — наверное, самые простые варианты. Выражение 42 вычислится в значение 42, выражение 10 * 45 — в значение 450.

Конкатенация строк — тоже выражение, которое вычислится в соответствующее значение (новую строку).

Но вот четвёртый вариант… Это тоже выражение! Мощность и гибкость языка программирования во многом возможна благодаря тому, что вызов функции — это выражение.

Посмотрите на пример:

const distance = calculateDistance('Lannisport', 'Bayasabhad');

В константу distance записывается результат вычисления выражения. В отличие от операций (например, 10 + 12), где явно видно, какое вычисление производится, в функциях само вычисление скрыто от нас, и мы видим только результат. Поэтому говорят, что функция «возвращает» значение. Можно применить эту терминологию и к обычным операциям. Например, сказать, что конкатенация двух строк возвращает новую строку.

Что является выражением, а что нет? Сейчас может казаться, что это одна из скучных деталей из учебника по программированию. Но это действительно важный вопрос. Всё, что работает как выражение, может быть использовано в других выражениях, а также во всех местах, где на вход ожидаются выражения. Распознавать выражения в коде — важный навык, необходимый программисту каждый день.

Допустим, у нас есть функция numberOfKnights(), которая принимает название замка в королевстве и возвращает количество рыцарей в этом замке. Зная, что вызов функции — выражение, можно допустить, что такой код будет работать:

const result = 4 + numberOfKnights('Winterfell');

Почему? Сложение — это выражение, а значит его операндами могут быть другие выражения: выражения 4 и выражения numberOfKnights('Winterfell'). В итоге получится 4 + какое-то число.

Значит, и такой код будет работать:

const result = numberOfKnights('Winterfell') + numberOfKnights('Oldtown');

Здесь два разных вызова функций, но каждый вызов — выражение, поэтому в итоге получится сложение двух значений — двух чисел (количества рыцарей замка Winterfell и количества рыцарей замка Oldtown).

Как вы увидите в следующих уроках, выражения могут комбинироваться друг с другом в самых причудливых формах. Умея распознавать выражения, вы сможете самостоятельно придумывать новые варианты использования, даже никогда не видев их до этого. В этом и заключается секрет изучения программирования, вместо заучивания конкретных способов работы, мы изучаем принципы по которым работает код.

Задание

Арья собирается в путешествие из Винтерфела в Орлиное гнездо, чтобы навестить Лизу Аррен, но по пути ей нужно заехать к Фреям для совершения акта возмездия. Ей нужно рассчитать общую длину маршрута.

К сожалению, функция calculateDistance() может вычислять расстояние только между двумя точками. Поэтому придется сначала узнать расстояние от Винтерфелла до замка Фреев, а потом расстояние до Орлиного гнезда.

Названия замков на английском языке:

  • Винтерфелл — Winterfell
  • Близнецы (Замок Фреев) — The Twins
  • Орлиное гнездо — The Eyrie

Выведите на экран полную длину маршрута Арьи. Напомним, что функция calculateDistance() принимает два аргумента и возвращает число.

40. Аргументы как выражения

JavaScript: Аргументы как выражения

Вспомним код:

import { round } from 'hexlet-basics/math';

const result = round(10.25); // 10

Функция round() вызывается с аргументом 10.25.

Мы выяснили, что выражения вычисляются в значения. То есть с точки зрения JavaScript, значения и выражения — это что-то схожее. Поэтому любые значения в программе технически можно заменить выражениями.

При вызове функции можно передать в неё аргументом выражение:

const result = round(8 + 2.25); // 10

Результат будет таким же, как в первом примере, потому что выражение 8 + 2.25 вычислится в значение 10.25, и с таким аргументом произойдет вызов round().

Более того, можно использовать переменные вперемешку со значениями и другими выражениями:

const number = 1.25;

const result = round(number + 7 + 2); // 10

Естественно, это работает не только с числами, а с любыми значениями и выражениями. Например, со строками.

console.log('D' + 'ragon'); // => 'Dragon'

Давайте подытожим. Взгляните на несколько примеров из текущего урока:

// простые вызовы
round(10.25);           // 10
console.log('Dragon');  // => 'Dragon'

// выражения в аргументах
round(8 + 2.25);            // 10
console.log('Dra' + 'gon'); // => 'Dragon'

// выражения с переменными в аргументах
number = 1.25;
round(number + 7 + 2); // 10

const text = 'Dr';
console.log(text + 'ag' + 'on');  // => 'Dragon'

Заметьте схожесть: во всех вызовах в функции передается какая-то информация, но иногда это простое, «вычисленное» значение (10.25'Dragon'), а иногда составное выражение — «не вычисленное» значение (8 + 2.25number + 7 + 2text + 'ag' + 'on' и т.д.). При этом во всех примерах передаётся один аргумент. Когда аргументов несколько, они обязательно разделяются запятыми.

Задание

Вам доступна функция calculateDistanceBetweenTowns(). Она принимает один аргумент, в котором должны содержаться названия двух городов через дефис. В ответ она возвращает расстояние между этими городами. Вот пример использования:

const distance = calculateDistanceBetweenTowns('Lannisport-Bayasabhad');

Напишите программу, которая использует функцию calculateDistanceBetweenTowns() и выводит на экран расстояние между городами, записанными в переменные from и to.

41. Вызов функций в аргументах функций

JavaScript: Вызов функций в аргументах функций

Продолжаем тему выражений. Если вызов функции — выражение, значит мы можем вызывать одну функцию в аргументах другой функции (…в вызов функции в вызов функции в вызов… а-а-а!).

import { round } from 'hexlet-basics/math';

const number = -100.234203;
const result = round(Math.abs(number), 2); // 100.23

Мы вызываем функцию round() и передаем ей два аргумента:

  • результат вызова функции abs() с аргументом number
  • число 2

Можно сделать то же самое, но с промежуточными шагами:

const number = -100.234203;
const module = Math.abs(number); // 100.234203
const result = round(module, 2); // 100.23

Какой вариант предпочтительнее? Если вычисление совсем простое и неглубокое (не больше одного вложения функции), то можно смело вкладывать вызов в вызов. В остальных ситуациях предпочтительно разбивать вызовы на промежуточные вычисления.

Причины все те же. Чтение такого кода значительно легче. Во-первых, промежуточные переменные своими названиями отражают суть операций. Во-вторых, такой код легче отлаживать, а промежуточные данные проще исследовать. И в-третьих, глубокие вложенные вызовы сложно читать. В продвинутых языках подобная проблема решается механизмом типа композиции функций, но в JavaScript, к сожалению, подобного нет.

Давайте взглянем на код и попробуем ответить на вопрос: что в каком порядке будет вычисляться?

const number = -100.234203;
const result = round(Math.abs(number), round(2.43));

JavaScript, как и большинство традиционных языков, является языком с жадными вычислениями (в других языках бывают ленивые вычисления). JavaScript пытается вычислить сначала максимально глубокий уровень вызова, затем менее глубокий, и так двигается снизу вверх пока не вычислит всё.

В нашем примере сначала будут вычислены аргументы, а затем получившиеся данные попадут в вызов round().

Ситуация с вложенными вызовами функций часто вводит новичков в ступор. Здесь нет никакой магии, нужно просто чуть больше тренировок. Хорошее упражнение — расписывать процесс по шагам на бумажке, симулируя действия компьютера. Вот так:

  1. round(Math.abs(number), round(2.43));
  2. round(100.234203, round(2.43));
  3. round(100.234203, 2);
  4. 100.23;

Задание

Для построения генеалогического дерева семьи Старков Сэм написал функцию getParentFor(), которая возвращает имя родителя, если передать ей первым параметром полное имя ребенка. Вторым параметром функция принимает строчку 'father' или 'mother‘. Так функция понимает, кого из родителей возвращать. По умолчанию параметр равен 'mother'. То есть, если нужно узнать имя матери, то можно не передавать специально 'mother', а передать лишь один параметр — имя ребенка.

Напишите программу, которая выводит на экран имя деда Джоффри по материнской линии. Полное имя Джоффри на английском: Joffrey Baratheon.

const mother = getParentFor('Joffrey Baratheon');
console.log(mother); // => 'Cersei Lannister'

Советы

42. Детерминированность 

JavaScript: Детерминированность

Независимо от того, какой язык программирования используется, функции внутри него обладают некоторыми фундаментальными свойствами. Зная эти свойства, легче прогнозировать поведение функций, способы их тестирования и место их использования. К таким свойствам относится детерминированность. Функция называется детерминированной тогда, когда для одних и тех же входных аргументов она возвращает один и тот же результат. Например, функция, переворачивающая строку, детерминированная.

import { reverse } from 'hexlet-basics/string';

reverse('cat'); // tac
reverse('cat'); // tac

Сколько бы раз мы её не вызывали, передавая туда значение 'cat', она всегда вернет 'tac' (хотя технически можно написать её и по другому, но смысла в этом никакого, а проблем доставит). В свою очередь функция, возвращающая случайное число, не является детерминированной, так как у одного и того же входа (даже если он пустой, то есть аргументы не принимаются) мы получим всегда разный результат. Насколько он разный – не важно, даже если хотя бы один из миллиона вызовов вернет что-то другое, эта функция автоматически считается недетерминированной.

import { rand } from 'hexlet-basics/math';

rand(); // 0.234111
rand(); // 0.932342

Зачем это нужно знать? Детерминированность серьезно влияет на многие аспекты. Детерминированные функции удобны в работе, их легко оптимизировать, легко тестировать. Если есть возможность сделать функцию детерминированной, не раздумывая делайте её такой.

Задание

Санса хочет повесить на свою дверь просьбу о том, чтобы никто не входил без стука. Она попросила Сэма распечатать лист с надписью “СТУЧАТЬ!”. Помогите Сэму перевести слово в верхний регистр, используя функцию toUpperCase(), которая принимает на вход строку и возвращает такую же, но со всеми буквами в верхнем регистре. Распечатайте на экран текст, записанный в константу text, не забыв перевести его в верхний регистр.

import { toUpperCase } from 'hexlet-basics/string';

console.log(toUpperCase('hello')); // => HELLO

Как вы думаете, что вернет функция toUpperCase(), если передать ей на вход строку HELLO?


Советы

Определения

  • Детерминированность функции – Для одного и того же входа, всегда один и тот же выход
43. Побочные эффекты

JavaScript: Побочные эффекты

console.log() – обычная функция. Внимание, вопрос: что возвращает функция console.log()?
Ответ: что бы она не возвращала, это значение никак не используется.

console.log() выводит что-то на экран, но это не возврат значения — это просто какое-то действие, которое выполняет функция.

Вывод на экран и возврат значения из функции — разные и независимые операции. Технически вывод на экран равносилен записи в файл (немного особый, но всё-таки файл). Для понимания этой темы необходимо немного разобраться в устройстве операционных систем, что крайне важно для программистов.

С точки зрения программы вывод на экран — это так называемый побочный эффект. Побочным эффектом называют действия, которые взаимодействуют с внешним окружением (средой выполнения). К таким действиям относятся любые сетевые взаимодействия, взаимодействие с файловой системой (чтение и запись файлов), вывод информации на экран, печать на принтере и так далее.

Побочные эффекты — один из основных источников проблем и ошибок в программных системах. Код с побочными эффектами сложен в тестировании и ненадежен. При этом без побочных эффектов программирование не имеет смысла. Без них было бы невозможно получить результат работы программы (записать в базу, вывести на экран, отправить по сети и так далее).

Понимание принципов работы с побочными эффектами очень сильно влияет на стиль программирования и способность строить качественные программы. Эта тема полностью раскроется в курсах на Хекслете.

Вопрос для самопроверки. Можно ли определить наличие побочных эффектов внутри функции, опираясь только на её возврат?

Задание

Это задание не связано напрямую с уроком

Выведите на экран значение константы text после обработки функциями reverse() и toLowerCase().

Зависит ли результат функции от порядка применения функций reverse() и toLowerCase()?


Советы

Определения

  • Побочный эффект – действие, которое изменяет внешнее окружение (среду выполнения). Например, вывод на экран или отправка письма.
  • Чистые функции – Детерминированные функции без побочных эффектов
44. Стандартная библиотека

JavaScript: Стандартная библиотека

JavaScript, как и любой другой язык, поставляется с набором полезных функций. Все вместе они составляют так называемую стандартную библиотеку. В неё обычно входят тысячи функций, которые невозможно выучить — этого и не нужно делать. Подразумевается, что любой программист знает, где искать документацию по ним и примерно представляет себе, чего он хочет достичь. А дальше — дело техники. Если отнять у программистов интернет, то большинство не сможет ничего запрограммировать.

Для новичков эта информация часто выглядит так: «Сходи туда, не знаю куда, принеси то, не знаю что». То есть непонятно, как узнавать про эти функции, когда ты ничего не знаешь вообще. Как ни странно, не существует способа раз и навсегда познать всё, что нужно познать. Любой разработчик в процессе своего профессионального взросления знакомится со всё более интересными функциями, решающими его задачи более элегантно, и таким образом пополняет свой арсенал.

Вот некоторые советы, как узнавать о новых функциях:

  • Всегда чётко отслеживайте, с чем вы сейчас работаете (какой тип данных). Почти всегда вы найдете необходимую функцию в соответствующем разделе документации — например, для работы со строками нужно изучать строковые функции.
  • Периодически открывайте раздел со стандартными функциями по изучаемой тематике и просто пробегайтесь по ним, изучая сигнатуры и способы использования.
  • Чаще читайте чужой код, особенно код библиотек, которые вы используете. Он весь доступен на GitHub.

У JavaScript есть свои особенности по структуре стандартной библиотеки. Так как его код может исполняться в разных средах, таких как серверное окружение или браузер, то возможности стандартной библиотеки сильно зависят от варианта использования. Например, из браузера невозможно выполнять некоторые задачи, которые необходимо уметь выполнять на сервере. Документацию по серверной части необходимо смотреть на сайте https://nodejs.org. Серверные части стандартной библиотеки организованы в модули, у каждого модуля есть своя страница с описанием всех функций, находящихся внутри него. Например, модуль fs необходим для работы с файловой системой, через его функции происходит запись и чтение файлов.

Если говорить про браузер, то там вообще мало что есть. По большей части это какие-то базовые функции, встроенные в сам язык — например те же функции для работы с математикой. Остальные возможности добавляются через использование сторонних библиотек.

Задание

Оператор typeof позволяет определить тип передаваемого операнда. Название типа возвращается в виде строки. Например, вызов typeof 'go go go' вернёт строку 'string' (number — число).

Выведите на экран тип значения константы motto.


Советы

Определения

  • Стандартная библиотека – набор полезных функций, входящий в комплект поставки языка программирования.

Свойства и Методы

Данные, которыми мы оперируем в своих программах, могут обладать важными свойствами. В JavaScript свойства встроены прямо в язык. Кроме свойств у данных существуют методы — функции, находящиеся внутри свойств. Свойства и методы — такие же выражения, как переменные, константы или вызовы функции, а значит, их можно всячески комбинировать. Глубже эти темы разбираются на отдельных курсах, посвященных объектно-ориентированным возможностям JavaScript. Мы же в этом модуле изучим основы.

45. Свойства 

JavaScript: Свойства

Данные, которыми мы оперируем в своих программах, могут обладать важными свойствами — например, у строк есть длина. Как вы увидите далее, это свойство очень важно для реализации алгоритмов, связанных с преобразованием строки (как пример — переворот строки). Как узнать длину строки? Во многих языках длина строки вычисляется с помощью специальной функции и выглядит это примерно так:

import { length } from 'hexlet-basics/string';

const name = 'Robb';
console.log(length(name)); // => 4

В JavaScript свойства встроены прямо в язык. Они указываются через точку сразу после переменной (или константы):

const name = 'Robb';
const len = name.length;
console.log(len); // => 4

Свойства связаны с данными, у которых они берутся. Для примитивных типов все свойства описаны в документации, как например, у строк. При этом у чисел вообще нет свойств.

JavaScript позволяет обращаться к свойствам, которые не существуют (например, при опечатках). В таком случае их значением является undefined:

const name = 'Robb';
console.log(name.whatIsThat); // => undefined

Вопрос для самопроверки. Что распечает код console.log(name[name.length]) для name, определенного выше? Почему ответ такой?

Задание

Напечатайте на экран длину строки text.


Советы

  • Ознакомьтесь с документацией String.length. Обратите внимание, что длина строки равна количеству символов в ней. Длина пустой строки '' равна 0.
46. Методы 

JavaScript: Методы

Кроме свойств, у данных существуют методы — функции, находящиеся внутри свойств. С практической точки зрения это значит, что метод работает и вызывается как функция, но делает это как свойство, через точку.

const name = 'Robb';
const upperName = name.toUpperCase();
console.log(upperName); // => 'ROBB'

Встроенные методы всегда оперируют теми данными, с которыми они связаны. Метод .toUpperCase() возвращает ту же строку, но преобразуя все символы в верхний регистр. Методов у данных обычно значительно больше, чем свойств, например, для строк их несколько десятков. В документации, на первый взгляд, они описаны немного странно: String.prototype.toLowerCase(). Это описание раскрывает некоторые внутренние детали реализации, которые сейчас не важны, да и мы не изучили всей необходимой базы для разговора о прототипах.

Методы есть и у чисел:

const temperature = 22.93;
// Округление до одного знака после запятой
const roundedTemperature = temperature.toFixed(1);
// Метод возвращает строку, которая содержит преобразованное число
console.log(roundedTemperature); // => '22.9'

// Напрямую можно вызывать так
// Скобки нужны обязательно, иначе не заработает
(22.93).toFixed(1); // '22.9'

Хозяйке на заметку. Технически всё несколько сложнее. Методы есть не у самих чисел, а у данных (объектов) типа Number. Числа, записанные в переменные или константы, автоматически преобразуются к данному типу во время обращения к ним, в это время происходит так называемый boxing.

Возникает закономерный вопрос: зачем нужны методы, почему не просто функции? С числами ситуация ещё сложнее. Часть операций реализована в виде методов самих чисел, например, .toFixed(), а ещё большая часть — в виде методов, доступных через Math.

Есть две причины почему так сделано:

  1. Исторически так сложилось. JavaScript разрабатывался слишком быстро и поэтому не все было продумано хорошо.
  2. Далеко не все функции имеют отношение к конкретному значению. Возьмем для примера Math.min(). Эта функция находит минимальное число среди всех, которые ему были переданы. Эту функцию нелогично делать методом конкретного числа, например, так — (1).min(). Она не имеет никакой связи с конкретным числом.

С другой стороны, функции, работающие с конкретным числом, для единообразия должны быть реализованы как методы. К таким функциям относится получение модуля числа. То есть вместо такого вызова Math.abs(-10), логично иметь такой: (-10).abs().

Что касается методов в целом, то не все так однозначно. Есть языки, в которых методов нет и там всё прекрасно, есть языки, где методы — это основной способ работы с функциями, но даже в этих языках всегда, наряду с методами, используются обычные функции. JavaScript — язык, в котором прижились оба подхода, в нём активно используются как обычные функции, так и методы. О плюсах и минусах подобных подходов подробно рассказывается в курсах посвященных ООП.

Задание

Приведите строку text к нижнему регистру и напечатайте её на экран.

47. Неизменяемость 

JavaScript: Неизменяемость

Что напечатает на экран последний вызов?

const name = 'Tirion';
console.log(name.toUpperCase()); // => TIRION
console.log(name); // => ?

Ответ на этот вопрос зависит от того, как вы поняли урок про неизменяемость примитивных типов данных. Вызов метода .toUpperCase() возвращает новое значение, в котором все буквы преобразованы в верхний регистр, но он не меняет (и не может этого сделать) исходную строку. Поэтому внутри константы (или переменной — это не важно) окажется старое значение: 'Tirion'. Эта логика справедлива для методов всех примитивных типов. Более того, попытка изменить значение свойства этих данных ни к чему не приведет:

const name = 'Tirion';
console.log(name.length); // => 6
name.length = 100;
console.log(name.length); // => 6

Вместо изменения значения можно заменить значение. Для этого понадобятся переменные:

let name = 'Tirion';
name = name.toUpperCase();
console.log(name); // => TIRION

Задание

Данные, вводимые пользователями, часто содержат лишние пробельные символы в конце или начале строки. Обычно их вырезают с помощью метода .trim(), например, было: ' hello\n ', стало: 'hello'.
Обновите переменную firstName записав в неё то же самое значение, но обработанное методом .trim(). Распечатайте то, что получилось, на экран.

48. Свойства и методы как выражения

JavaScript: Свойства и методы как выражения

Свойства и методы — такие же выражения, как переменные, константы или вызовы функции, а значит, их можно всячески комбинировать.

Использование в операциях:

const name = 'Shaya';
name.length + 5; // 10
`hi, ${name.toUpperCase()}!`; // hi, SHAYA!

Использование в аргументах функций:

const name1 = 'Robb';
const name2 = 'Shaya';
console.log(name2.length); // => 5
console.log(name2.toLowerCase()); // => shaya
console.log(Math.min(name1.length, name2.length)); // => 4

Задание

Выведите на экран первую и последнюю буквы предложения, записанного в константу text, в следующем формате:

First: N
Last: t

Ваша задача извлечь эти символы из строки и вставить в console.log(), не используя промежуточные переменные.

49. Цепочка вызовов

JavaScript: Цепочка вызовов

У чисел есть метод, который преобразует их в строку:

const peopleCount = 5;
peopleCount.toString(); // '5'

Попробуйте ответить на вопрос, заработает ли следующий код — и если да, то что он напечатает на экран?

const name = 'Tirion';
console.log(name.length.toString());

Синтаксис нескольких подряд идущих точек мы видим впервые, но все операции, которые здесь встречаются, нам знакомы. Всё, что произошло в этом коде — это объединение уже известных возможностей языка. Такое в программировании происходит довольно часто. Даже не зная синтаксиса, можно пробовать комбинировать различные подходы, и есть неплохая вероятность, что они заработают.

Самый простой способ понять как работает этот код — разбить цепочку на отдельные операции:

const name = 'Tirion';
const len = name.length;
console.log(len.toString());

Эти примеры абсолютно эквивалентны. Мы можем выполнять операции последовательно с промежуточным созданием констант, а можем строить непрерывную цепочку из свойств и методов. В цепочках вычисления всегда идут слева направо.

Ещё один пример для закрепления:

const name = 'Tirion';
console.log(name.toUpperCase().toLowerCase());

Подобный код требует небольших умственных усилий. Важно понимать, что .toLowerCase() применяется к результату вызова функции, которая находится левее. А функция toUpperCase() возвращает строку. Новички часто делают ошибки в цепочках с методами, забывая ставить вызов:

const name = 'Tirion';
// Этот код отработает неверно!
console.log(name.toUpperCase.toLowerCase());

Продолжая эту идею, возможно строить бесконечно длинные (хотя, в данном случае, бесполезные) цепочки:

// Чему равен результат такого вызова?
console.log(name.toUpperCase().toLowerCase().length.toString().length);

С функциями подобный трюк не сработает, так как при обычном использовании они вкладываются друг в друга f(f(f())), что значительно ухудшает анализ. Но это не значит, что нельзя сделать красиво — можно и даже нужно. В других языках это реализуется через композицию функций или пайплайн-оператор, который, кстати говоря, постепенно начинает использоваться и в самом JavaScript: https://github.com/tc39/proposal-pipeline-operator

Задание

С помощью метода substring() получите часть предложения, записанного в константу text, c 4 по 15 символ включительно. Полученную подстроку обработайте методом trim и выведите на экран длину итоговой подстроки. Выполните эти методы подряд в цепочке без создания промежуточных переменных.

Определение функций

Определение собственных функций значительно упрощает написание и поддержку программ. Например, умение определять функции позволяет объединять сложные (составные) операции в одну – вся сложность может быть скрыта за одной простой функцией. Научившись писать функции, вы сделаете первый шаг на пути к построению по-настоящему полезных программ. И мы вам в этом поможем. В этом модуле вы создадите свою первую функцию и научитесь давать ей (а заодно переменным и константам) понятные названия.

50. Создание (определение) функции

JavaScript: Создание (определение) функции

Определение собственных функций значительно упрощает написание и поддержку программ. Функции позволяют объединять сложные (составные) операции в одну. Например, отправка письма на сайте – это достаточно сложный процесс, включающий в себя взаимодействие с внешними системами (интернет). Благодаря возможности определять функции, вся сложность может быть скрыта за простой функцией:

import { send } from 'some-email-package';

const email = '[email protected]';
const title = 'Помогите';
const body = 'Я написал историю успеха, как я могу получить скидку?';

// Один маленький вызов — и много логики внутри
send(email, title, body);

Создадим нашу первую функцию. Её задача – вывести на экран следующий текст:

Today is: December 5
// Определение функции
// Определение не вызывает и не выполняет функцию
// Мы лишь говорим, что теперь такая функция существует
const showCurrentDay = () => {
  const text = 'Today is: December 5';
  console.log(text);
};

// Вызов функции
showCurrentDay();

Для любознательных. Такая функция в JavaScript называется стрелочной. Она появилась со стандартом языка ES6. Далее мы будем работать только со стрелочными функциями.

Определение функции выше состоит из двух частей:

  • Присваивание функции константе
  • Непосредственно определение функции

Само по себе определение функции – это всё, что находится после присвоения:

// Обратите внимание на стиль
// Пробелы между символами
// Открывающая фигурная скобка в конце той же строчки, где стрелка
// Закрывающая - на своей отдельной строчке в конце
() => {
  const text = 'Today is: December 5';
  console.log(text);
};

Такое определение создает функцию, но не вызывает её. Поскольку это определение не связывается ни с каким именем (константой), то такой код — бесполезен, хотя и является синтаксически корректным.

Связывание функции с именем является обычным присваиванием. В этом смысле функции ведут себя как обычные данные, которые можно записывать в константы. Технически функцию можно присвоить и переменной, но так как функции неизменяемы, то смысла в этом нет.

const doSomething = /* определение любой функции */;

В отличие от обычных данных, функции выполняют действия, поэтому их имена практически всегда должны быть глаголами: «построить что-то», «нарисовать что-то», «открыть что-то».

Всё, что описывается внутри фигурных скобок {}, называется телом функции. Внутри тела можно описывать любой код. Считайте, что это маленькая самостоятельная программа, набор произвольных инструкций. Тело выполняется ровно в тот момент, когда запускается функция. Причём каждый вызов функции запускает тело независимо от других вызовов. Кстати, тело может быть пустым:

// Минимальное определение функции, которая ничего не делает
const noop = () => {};

noop(); // вызов есть, а смысла нет
// Такая функция тоже бывает полезна
// Но это относится к продвинутым темам

Понятие «создать функцию» имеет много синонимов: «реализовать», «определить» и даже «заимплементить» (от слова implement). Все они встречаются в повседневной практике на работе.

Задание

Реализуйте функцию printMotto(), которая печатает на экран фразу Winter is coming.

printMotto(); // => Winter is coming

Важное замечание! В задачах, в которых нужно реализовать функцию, эту функцию вызывать не нужно. Вызывать функцию будут автоматизированные тесты, которые проверяют ее работоспособность. Пример с вызовом выше показан только для того, чтобы вы понимали, как ваша функция будет использоваться.

51. Передача одного аргумента

JavaScript: Передача одного аргумента

Функции без аргументов встречаются редко. Чаще функции принимают на вход данные, как-то их используют и выдают результат обратно. В этом уроке мы познакомимся с определением функций, принимающих на вход один аргумент. Посмотрите на определение ниже:

const showCurrentDay = (text) => {
  console.log(`Today is: ${text}`);
};

showCurrentDay('January 29');
Today is: January 29

Теперь понятно, зачем нужны были круглые скобки после имени функции: в них можно указать аргументы (или, что тоже самое, параметры). Технически, параметры функции всегда являются переменными, а не константами. Но лучше относиться к ним как к константам.

Заметьте: мы не определяем переменную text, но используем её в теле функции. JavaScript работает так: переменная сама создаётся при вызове, и указанное значение (в нашем примере — 'January 29') записывается в эту переменную.

Аргументы можно называть как угодно, их имена имеют смысл исключительно в теле функции. Например, если изменить имя аргумента так:

const showCurrentDate = (lala) => {
  console.log(`Today is: ${lala}`);
};

const date = 'January 29';
showCurrentDate(date);

то поведение функции не изменится. Это касается как имен внутри функции (lala), так и снаружи (date).

Новички иногда пытаются сделать примерно такое определение функции:

const showCurrentDate = ('Today is: December 5') => {
  // какой-нибудь код
};

Запустить такой код не получится — он содержит синтаксическую ошибку. Вместо переменной в аргументе написана строка, то есть значение.

Аргумент должен быть переменной, иначе он не сможет быть аргументом, то есть чем-то, что принимает значение при вызове.

Если же вам нужна какая-то информация в функции, и вы заранее знаете какая именно, то аргумент для этого не нужен, ведь мы уже умеем сохранять значения для последующего использования — достаточно создать константу в самом теле:

const showCurrentDate = () => {
  const text = 'Today is: December 5';
  // какой-нибудь код
};

Задание

Реализуйте функцию printJaimesLine(), которая принимает один аргумент — строку, и выводит реплику на экран в формате JAIME: переданная_строка.

Как назвать переменную, которая будет аргументом — решайте сами.

Наша система содержит код, скрытый от вас. В этом упражнении скрыт вызов функции printJaimesLine(). Так мы проверяем ваше решение.

Вам не нужно самостоятельно вызывать функцию, только определить её. Но для наглядности — вот как наша система вызывает её:

printJaimesLine('Farewell, my friend...');
JAIME:  Farewell, my friend...
52. Передача нескольких аргументов

JavaScript: Передача нескольких аргументов

Аргументов может быть несколько. В таком случае в определении функции мы делаем то же самое, что в вызове: просто указываем аргументы через запятую.

Полный пример определения функции с несколькими аргументами и её вызова:

const showCurrentDay = (month, day) => {
  console.log(`Today is: ${month} ${day}`);
};

showCurrentDay('January', '29');
Today is: January 29

Главное — помнить, в каком порядке аргументы стоят при определении функции, в таком же порядке они должны передаваться при вызове.

Аргументы можно и не передавать, или передать только часть, тогда значением непереданных аргументов станет undefined. Это поведение немного необычно, так как в большинстве языков так делать нельзя.

showCurrentDay('January');
// Today is: January undefined

showCurrentDay();
// Today is: undefined undefined

Задание

Сэм составляет множество карт, и ему часто нужно выводить на экран повторяющиеся символы для визуализации маршрутов. Например, так Сэм иллюстрирует узкие дороги между городами:

Meereen =-=-=-=- Myr

А так иллюстрирует широкие трассы:

Vaes Dothrak ======== Vahar

В документации js он нашёл метод String.prototype.repeat().

console.log('=-'.repeat(4));
=-=-=-=-

Сэм не очень доволен. Ему нужно нарисовать сотни маршрутов разной длины с разными символами. Неудобно вызывать сотни раз repeat() внутри вызова console.log().

Напишите для Сэма функцию printSeq(), которая сама выводит на экран получившиеся повторения. Она принимает два аргумента — строку и число, и выводит повторяющуюся строку на экран.

Вот пример того, как Сэм будет использовать написанную вами printSeq():

printSeq('=-', 4);
=-=-=-=-
53. Возврат значений

JavaScript: Возврат значений

В модуле «Вызов функций» мы в основном работали с функциями, которые выводят на экран, а не возвращают результат. Честно говоря, вывод на экран — фактически обучающий элемент. В реальном коде на экран никто ничего не выводит (за исключением утилит командной строки). Функции возвращают данные, которые передаются в другие функции.

Научиться писать функции, которые возвращают информацию — первый шаг на пути к построению по-настоящему полезных программ.

Начнем с тривиального примера: создадим и вызовем функцию, которая принимает два числа и возвращает первое число минус второе. Назовём её sub(), от англ. “subtract” — «вычесть»:

const sub = (a, b) => {
  const result = a - b;
  return result;
};

const result = sub(10, 7);
console.log(result); // => 3

Возврат значения задаётся специальной инструкцией return. Cправа от return помещается выражение. Любое выражение. То есть, мы можем делать вычисления сразу после return без создания константы result:

const sub = (a, b) => {
  // Сначала вычисляется выражение справа от `return`
  // затем получившееся значение возвращается
  return a - b;
};

console.log(sub(2018, 1975)); // => 43

Обратите внимание: мы знаем, что вызов функции — выражение, поэтому мы передали вызов одной функции в вызов другой функции — console.log(sub(2018, 1975)).

Интерпретатор, встречая return, останавливает дальнейшее выполнение функции и возвращает указанное справа значение в то место, где была вызвана функция.

Посмотрите на эту функцию:

const foo = () => {
  return 7;
  return 10;
};

console.log(foo());

Что выведется на экран?

Правильный ответ: 7. Функция всегда будет возвращать только число 7, так как интерпретатор, наткнувшись на первый return, остановит выполнение функции. Строчка кода return 10; никогда не выполнится.

Задание

Сэм создаёт генеалогические деревья разных семей. Ему постоянно приходится рассчитывать количество места, занимаемое именами родителей на экране.

Создайте функцию getParentNamesTotalLength() для Сэма. Она должна принимать один аргумент — имя ребенка, и возвращать количество символов в именах матери и отца суммарно. Функция не должна выводить ничего на экран, только возвращать число.

Примеры вызова:

getParentNamesTotalLength('Daenerys Targaryen'); // 35

Для получения имён родителей используйте уже существующую функцию getParentFor():

  • Получение имени матери getParentFor(child, 'mother'), где child — имя ребёнка.
  • Получение имени отца getParentFor(child, 'father'), где child — имя ребёнка

Вам не нужно вызывать свою функцию, только определить её.

54. Возврат по умолчанию

JavaScript: Возврат по умолчанию

Рассмотрим немного модифицированную функцию из предыдущего урока:

const sub = (a, b) => {
  // Полученный результат никак не используется
  // и не возвращается наружу
  const answer = a - b;
};

const result = sub(10, 7);
console.log(result); // undefined

Несмотря на отсутствие return внутри функции, console.log() выведет на экран undefined. Это стандартное поведение функций в JavaScript, оно существует не просто так. Вызов функции – выражение, а выражения всегда возвращают результат своего выполнения.

Забыть инструкцию return — частая ошибка новичка. Мы в обучении каждый день сталкиваемся с просьбами о помощи типа «функция правильная, но почему-то не работает». И почти всегда оказывается, что забыт return, а результат, вместо возврата, просто печатается на экран.

С другой стороны, если написать инструкцию return без указания выражения после него, то наружу вернется все тот же undefined. Кажется, что подобный возврат не имеет смысла, но это не так. return без выражения нередко используют для прерывания вычислений. Подробнее эта тема раскрывается в модуле, посвященному условным конструкциям.

Вопрос для самопроверки. Что возвращает функция console.log()?

Задание

Это немного странное задание, но для тренировки будет полезным. Реализуйте функцию doNothing(), которая ничего не делает.

55. Параметры по умолчанию

JavaScript: Параметры по умолчанию

Напомню, что аргумент может быть необязательным. У такого аргумента есть значение по умолчанию.

Например, функция getParentFor(), которую вы использовали в некоторых упражнениях, принимает имя ребёнка первым аргументом, а вторым — строку 'mother' или 'father'. Второй аргумент — необязательный, и если не указывать его при вызове, то автоматически по умолчанию будет использоваться 'mother'.

Эти два вызова равнозначны:

getParentFor('Jon Snow');
getParentFor('Jon Snow', 'mother');

Каким образом там сделаны аргументы по умолчанию? Давайте заглянем в определение этой функции:

const getParentFor = (child, parent = 'mother') => {
  // какой-то код
};

Первый аргумент указан привычно — просто название переменной. Это делает аргумент обязательным.

Второй аргумент указан со значением в формате аргумент = какое_то_значение. Точно так же, как при создании переменных и констант. Этот фрагмент = какое_то_значение делает аргумент необязательным, и задаёт ему значение по умолчанию.

Аргументов по умолчанию может быть любое количество, но желательно, чтобы все они были в конце списка аргументов. То есть такие строчки кода будут некорректны:

  • const getParentFor = (childName = 'Jon', who) => {
  • const calculate = (a, b = 90, c) => {
  • const getPrices = (code = 4161, quantity, place) => {

Важно! Значение по умолчанию присваивается аргументу только если при вызове функции для него не было передано значение. Если передать аргумент с любым значением (кроме undefined), значение по умолчанию использоваться не будет.

getParentFor('Daenerys Targaryen', 'father'); // Aerys II Targaryen

Задание

Реализуйте функцию getCustomParentFor(), которая принимает два аргумента:

  1. Строку с именем ребёнка.
  2. Строку с указанием родителя. Этот аргумент должен по умолчанию быть 'father'.

Функция должна возвращать имя соответствующего родителя.

Примеры вызова:

getCustomParentFor('Cersei Lannister'); // Tywin Lannister
getCustomParentFor('Daenerys Targaryen', 'mother'); // Rhaella Targaryen

Такой вызов вернёт имя отца.

  • Внутри своей функции используйте уже готовую функцию getParentFor(). Как она работает, мы подробно разбирали раньше.
56. Именование 

JavaScript: Именование

Стиль именования функций в JavaScript такой же, как и стиль именования переменных: lowerCamelCase. Но при выборе самих слов есть важное отличие.

Функция — действие, вызов функции всегда подобен указанию «сходи», «возьми», «напечатай», «положи» и так далее. Вспомните, какие функции были в предыдущих уроках:

  • showDate() («показать дату»)
  • sub() (subtract — «вычесть»)
  • round() («округлить»)
  • getMoney() («получить»)

Переменная/Константа — сущность, поэтому мы используем существительные:

  • child
  • result
  • euros

Берите на вооружение следующую структуру: функция — глагол, переменная/константа — существительное.

В уроке про переменные мы просили вас придумать название переменной и записать в блокноте или отправить себе на почту. Найдите это название и посмотрите на него свежим взглядом: оно понятное? Описывает суть однозначно или требует вникания?

Жизнь программиста наполнена такими моментами: открыть старый код и попытаться понять его. Будьте добры к будущему себе и к коллегам, давайте переменным, константам и функциям понятные названия.

Задание

Реализуйте функцию, которая принимает на вход номер кредитки (состоящий из 16 цифр) в виде строки и возвращает его скрытую версию, которая может использоваться на сайте для отображения. Если исходная карта имела номер 2034399002125581, то скрытая версия выглядит так ****5581. Другими словами, функция заменяет первые 12 символов, на звездочки. Количество звездочек регулируется вторым необязательным параметром. Значение по умолчанию — 4.

// Кредитка передается внутрь как строка
getHiddenCard('1234567812345678', 2); // '**5678'
getHiddenCard('1234567812345678', 3); // '***5678'
getHiddenCard('1234567812345678'); // '****5678'
getHiddenCard('2034399002121100', 1); // '*1100'

Полезные методы:

  • String.prototype.slice() – извлекает часть строки или как говорят “подстроку”. Первым параметром принимает индекс элемента, с которого надо начинать извлечение, вторым – индекс элемента, до которого извлекаются символы. По умолчанию, извлекается все до конца строки.
    // с третьего индекса до конца строки
    'java script'.slice(3); // 'a script'
    
    // с первого индекса по четвертый
    'java script'.slice(1, 4); // 'ava'
    
    // можно использовать отрицательные индексы
    // тогда отсчет берется с конца строки
    'java script'.slice(-2); // 'pt'
    
  • String.prototype.padStart() – Дополняет строку “заполнителем” слева, до тех пор пока длина строки не станет равной указанной. Первым параметром функция принимает желаемую длину строки, вторым – заполнитель.
    '5'.padStart(2, '*'); // '*5'
    '10'.padStart(4, '+'); // '++10'
    

Советы

57. Упрощенный синтаксис функций

JavaScript: Упрощенный синтаксис функций

По сравнению с некоторыми (в первую очередь функциональными) языками, определение функции в JavaScript выглядит довольно громоздко:

const square = (x) => {
  return x ** 2;
};

Здесь используется много дополнительных символов и слово return. С версии es6, в языке появился альтернативный, сокращенный синтаксис, который, в некоторых ситуациях, значительно упрощает восприятие и сокращает количество кода.

// Требуется немного времени на привыкание к этой форме, но потом вы не сможете без неё жить
const double = (x) => x ** 2;

Отличия от полного определения два: пропали фигурные скобки и инструкция return. Сокращенная запись функции делает возврат автоматически. Подразумевается, что внутри такой функции ровно одно выражение, которое вычисляется, и её результат сразу возвращается наружу.

Подчеркнём, что отличия исключительно синтаксические, с точки зрения использования различий нет. Пример с двумя аргументами:

Полная версия

const sum = (a, b) => {
  return a + b;
};

Сокращенная версия

const sum = (a, b) => a + b;

Обратите внимание на отсутствие фигурных скобок. Разработчики, которые не привыкли использовать такой синтаксис, иногда пишут подобный код const sum = (a, b) => { a + b };, а потом долго не могут понять, почему он не работает. Ответ очень простой: если стоят фигурные скобки, то это не сокращенная форма, а значит, чтобы функция вернула искомое значение, придётся поставить return.

Задание

Реализуйте функцию capitalize(), которая принимает непустую строку и приводит первую букву первого слова к верхнему регистру:

  const name = 'arya';
  console.log(capitalize(name)); // => Arya

Чтобы получить подстроку (или символ) из строки, используйте метод slice():

  'welcome'.slice(2, 5); // lco

Логика

Логические выражения позволяют отвечать на вопросы, которые возникают во время работы программы. Пользователь аутентифицирован? Подписка оплачена? Год високосный? В этом модуле изучаем функции-предикаты – те, которые задают вопрос и отвечают на него – правда это или ложь. Попрактикуемся в написании таких функций и перейдем к более сложным логическим выражениям.

58. Логический тип

JavaScript: Логический тип

Кроме арифметических операций со школы нам известны операции сравнения. Например, 5 > 4. Это звучит как вопрос: «5 больше 4?». В данном случае ответ «да». В других случаях ответом может быть «нет», например, для 3 < 1.

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

Языки программирования адаптировали все математические операции сравнения практически в неизменном виде. Единственное серьезное отличие – операторы равенства и неравенства. В математике для этого используется обычное равно =, но в программировании такое встречается не часто. Во многих языках символ = используется для присваивания значений переменным, поэтому для сравнения взяли == или ===.

Список операций сравнения в JavaScript:

  • < меньше
  • <= меньше или равно
  • > больше
  • >= больше или равно
  • === равно
  • !== не равно

Небольшая ремарка: для равенства и неравенства также существуют операторы == и !=, которые мы не будем использовать из-за потенциальной опасности. Мы поговорим об этом в будущих уроках.

Логическая операция типа 5 > 4 или password === text — это выражение, и его результат — специальное значение true («истина») или false («ложь»). Это новый для нас тип данных — boolean. Он содержит всего лишь два этих значения.

const result = 5 > 4;
console.log(result); // => true
console.log('one' !== 'one'); // => false

Наряду со строками (string), целыми и рациональными числами (number), логический тип (boolean) — это один из примитивных типов данных в JavaScript.


Попробуем написать примитивную функцию, которая принимает на вход возраст ребенка и определяет, младенец ли он. Младенцами считаются дети до года:

const isInfant = (age) => age < 1;

Пользуемся тем фактом, что любая операция — это выражение, поэтому единственной строчкой функции пишем «вернуть то значение, которое получится в результате сравнения age < 1».

В зависимости от пришедшего аргумента, сравнение будет либо истинным (true), либо ложным (false), и return вернёт этот результат.

const isInfant = (age) => age < 1;

console.log(isInfant(3));
false

А теперь проверим ребенка, которому полгода:

console.log(isInfant(0.5));
true

Задание

Напишите функцию isPensioner(), которая принимает один параметр — возраст человека, и проверяет, является ли он пенсионным. Пенсионером считается человек, достигший возраста 60 лет и больше.

Примеры вызова:

isPensioner(75); // true
isPensioner(18); // false

Определения

  • Логический тип (boolean) – тип данных с двумя возможными значениями: true (истина) и false (ложь).
59. Предикаты 

JavaScript: Предикаты

Вспомним функцию isInfant() из прошлого урока:

const isInfant = (age) => age < 1;
console.log(isInfant(3));
false

Подобные функции называют предикатами. Функции-предикаты (или функции-вопросы) отвечают на какой-то вопрос и всегда (без исключений!) возвращают либо true, либо false.

Предикаты во всех языках принято именовать особым образом для простоты анализа. В JavaScript предикаты, как правило, начинаются с префикса ishas или can, но не ограничены этими словами. Примеры:

  • isInfant() — «младенец ли?»
  • hasChildren() — «есть ли дети?»
  • isEmpty() — «пустой ли?»
  • hasErrors() — «есть ли ошибки?»

Функция может считаться предикатом только если она возвращает boolean.


Давайте напишем ещё одну функцию-предикат. Она принимает строку и проверяет, является ли она словом 'Castle':

const isCastle = (type) => type === 'Castle';

console.log(isCastle('Sea'));
false

Задание

Напишите функцию isMister(), которая принимает строку и проверяет, является ли она словом 'Mister'.

Примеры вызова:

isMister('Mister'); // true
isMister('Miss');   // false

Советы

Определения

  • Предикат – выражение, отвечающее на вопрос «да» или «нет» с помощью типа boolean.
60. Комбинирование операций и функций

JavaScript: Комбинирование операций и функций

Логические операции — это выражения. Значит, логические операции можно комбинировать с другими выражениями.

Например, мы хотим проверить чётность числа, то есть кратность двум. В программировании используют такой подход:

  • проверяют остаток от деления на 2:
    • если остаток 0, то число было чётным
    • если остаток не 0, то число было нечётным

Остаток от деления — простая, но очень важная концепция в арифметике, алгебре, и даже в теории чисел и криптографии. Идея проста: нужно разделить число на несколько равных групп, и если в конце что-то останется — это и есть остаток от деления.

Делим конфеты поровну между людьми:

  • 7 конфет, 2 человека: 2 x 3 + остаток 1. Значит, 7 не кратно 2.
  • 21 конфету, 3 человека: 3 x 7 + остаток 0. Значит, 21 кратно 3.
  • 19 конфет, 5 человек: 5 x 3 + остаток 4. Значит, 19 не кратно 5.

Оператор % вычисляет остаток от деления (не путайте с делением):

  • 7 % 2 → 1
  • 21 % 3 → 0
  • 19 % 5 → 4

С помощью него напишем функцию проверки чётности:

const isEven = (number) => number % 2 === 0;

isEven(10); // true
isEven(3);  // false

В одном выражении мы скомбинировали логический оператор === (проверка равенства) и арифметический оператор %.

Приоритет арифметических операций выше логических. Значит, сначала вычисляется арифметическое выражение number % 2, затем результат участвует в логическом сравнении.

По-русски это можно расшифровать так: «вычислить остаток от деления числа number на 2 и сравнить, равен ли остаток нулю; затем вернуть результат проверки равенства».


Другой пример: напишем функцию, которая принимает строку и проверяет, заглавная ли первая буква.

Алгоритм:

  1. Получим и запишем в переменную первый символ из строки-аргумента.
  2. Сравним, равен ли символ своей большой (заглавной) версии.
  3. Вернём результат.
const isFirstLetterInUpperCase = (string) => {
  const firstLetter = string[0];
  return firstLetter.toUpperCase() === firstLetter;
}

isFirstLetterInUpperCase('marmont'); // false
isFirstLetterInUpperCase('Robb');    // true

Мы использовали метод toUpperCase(). Он возвращает строку, на которой он был вызван, в которой все буквы стали заглавными. Мы вызвали его на первом символе строки.

Попробуйте проговорить происходящее по-русски, аналогично тому, как мы расшифровывали процесс в примере с isEven() в начале урока.


Напомним об извлечении символов из строки с помощью квадратных скобок:

const firstName = 'Alexander';

firstName[0]; // A

Задание

Сэм решил изучить историю Таргариенов со времени первых людей, но книг было много и информация могла находиться в любой из них. К счастью для Сэма, большинство книг были оцифрованы молодыми мейстерами. Он подумал, что неплохо бы написать функцию, которая анализирует тексты на наличие в них упоминания фамилии Таргариенов.

Реализуйте функцию hasTargaryenReference(), которая принимает на вход строку и проверяет, начинается ли она с Targaryen. Сделать это легко используя метод substring(), который принимает на вход два параметра:

  • Индекс, с которого нужно взять подстроку (включает в подстроку)
  • Индекс, до которого нужно взять подстроку (не включает в подстроку)

Этот метод позволяет извлечь начало подстроки такой же длины, как и слово Targaryen, а затем проверить равно ли оно Targaryen. Напомню, что индексы начинаются с нуля.

hasTargaryenReference('');          // false
hasTargaryenReference('Targari');   // false
hasTargaryenReference('targaryen'); // false
hasTargaryenReference('Targaryen'); // true

Советы

61. Логические операторы

JavaScript: Логические операторы

Логические выражения могут объединяться друг с другом, создавая все более хитрые проверки. Хороший пример: проверка пароля. Как вы знаете, некоторые сайты при регистрации хотят пароль от 8 до 20 символов в длину. Честно говоря, это странное ограничение, но что поделать. В математике мы бы написали 8 < x < 20 (где x это длина конкретного пароля), но в JavaScript такой трюк не пройдет. Нам придётся сделать два отдельных логических выражения и соединить их специальным оператором «И»:

Пароль длиннее 8 символов **И** пароль короче 20 символов.

Вот функция, которая принимает пароль и говорит, соответствует ли он условиям, или не соответствует:

const isStrongPassword = (password) => {
  const length = password.length;
  return length > 8 && length < 20;
};

isStrongPassword('qwerty'); // false
isStrongPassword('qwerty1234'); // true
isStrongPassword('zxcvbnmasdfghjkqwertyui'); // false

&& – означает «И» (в математической логике это называют конъюнкцией). Всё выражение считается истинным только в том случае, когда истинен каждый операнд — каждое из составных выражений. Иными словами, && означает «и то, и другое».

Приоритет этого оператора ниже, чем приоритет операторов сравнения, поэтому выражение отрабатывает правильно без скобок.

Кроме &&, часто используется оператор || — «ИЛИ» (дизъюнкция). Он означает «или то, или другое, или оба». Операторы можно комбинировать в любом количестве и любой последовательности, но когда одновременно встречаются && и ||, то приоритет лучше задавать скобками. Ниже пример расширенной функции определения корректности пароля:

const hasSpecialChars = (str) => /* проверяет содержание специальных символов в строке */;

const isStrongPassword = (password) => {
  const length = password.length;
  // Скобки задают приоритет. Понятно что к чему относится.
  return (length > 8 && length < 20) || hasSpecialChars(password);
};

Другой пример. Мы хотим купить квартиру, которая удовлетворяет условиям: площадь от 100 кв. метров и больше на любой улице ИЛИ площадь от 80 кв. метров и больше, но на центральной улице Main Street.

Напишем функцию, проверяющую квартиру. Она принимает два аргумента: площадь (число) и название улицы (строку):

const isGoodApartment = (area, street) => {
  // Через переменную, чтобы функция была не слишком длинной
  const result = area >= 100 || (area >= 80 && street === 'Main Street');
  return result;
};

isGoodApartment(91, 'Queens Street'); // false
isGoodApartment(78, 'Queens Street'); // false
isGoodApartment(70, 'Main Street');   // false

isGoodApartment(120, 'Queens Street'); // true
isGoodApartment(120, 'Main Street');   // true
isGoodApartment(80, 'Main Street');    // true

Область математики, в которой изучаются логические операторы, называется булевой алгеброй. Ниже показаны «таблицы истинности» — по ним можно определить, каким будет результат применения оператора:

И &&

A B A && B
TRUE TRUE TRUE
TRUE FALSE FALSE
FALSE TRUE FALSE
FALSE FALSE FALSE

ИЛИ ||

A B A || B
TRUE TRUE TRUE
TRUE FALSE TRUE
FALSE TRUE TRUE
FALSE FALSE FALSE

Задание

Джон поручил Сэму реализовать автоматическое распознавание солдат Ланнистеров на видео. Идея автоматизировать дозор крепости казалась ему привлекательной. В процессе работы Сэму понадобилось написать функцию, которая определяет, Ланнистер ли перед ним или нет. Немного подумав, Сэм выделил следующие правила определения Ланнистера:


Если у солдата доспехи красного цвета И нет щита

ИЛИ

если у солдата есть щит с изображением льва

то это Ланнистер.


Ланнистер или нет

Напишите функцию isLannisterSoldier(), которая принимает на вход два аргумента:

  1. Цвет доспехов (строка). Если доспехи красные, то строка red.
  2. null если щита нет. Строка lion, если щит есть, и на нём изображен лев.

Функция возвращает true, если распознан Ланнистер, и false если не распознан.

Примеры вызова:

isLannisterSoldier('red', 'lion'); // true
isLannisterSoldier('blue', null);  // false

Советы

Определения

  • Логические операторы – операторы «И» (&&), ИЛИ (||), позволяющие создавать составные логические условия.
62. Отрицание 

JavaScript: Отрицание

Наряду с конъюнкцией (И) и дизъюнкцией (ИЛИ), часто используется операция «отрицание». Отрицание меняет логическое значение на противоположное. В программировании ему соответствует унарный оператор !.

Если есть функция, проверяющая чётность числа, то с помощью отрицания можно выполнить проверку нечётности:

const isEven = (number) => number % 2 === 0;

isEven(10);  // true
!isEven(10); // false

То есть мы просто добавили ! слева от вызова функции и получили обратное действие.

Отрицание — мощный инструмент, который позволяет лаконично выражать задуманные правила в коде без необходимости писать новые функции.

А что если написать так !!isEven(10)? Внезапно, но код сработает. В логике двойное отрицание подобно отсутствию отрицания вообще.

isEven(10);   // true
!isEven(10);  // false
!!isEven(10); // true

Задание

Реализуйте функцию isNotLannisterSoldier(), которая проверяет, что солдат — не Ланнистер. Функция принимает на вход 2 аргумента:

  1. Цвет доспехов (строка). Например, строку red, если доспехи красные.
  2. Изображение на щите. Например, строку lion, если щит с изображением льва. Если щита нет, то будет передан null.

Вам доступна уже готовая функция isLannisterSoldier(). Воспользуйтесь ей, чтобы не писать все логические условия заново.

Условия распознавания Ланнистера описаны в прошлом уроке.

Примеры вызова:

isNotLannisterSoldier('red', 'lion'); // false
isNotLannisterSoldier('blue', null);  // true

Советы

63. Логические операторы

JavaScript: Логические операторы 2

Логические операторы — важная тема, поэтому стоит закрепить её дополнительным примером и упражнением.

Попробуем реализовать функцию, проверяющую год на високосность. Год будет високосным, если он кратен 400 или одновременно кратен 4 и не кратен 100. Как видите, в определении уже заложена вся необходимая логика, осталось только переложить её на код:

const isLeapYear = (year) => year % 400 === 0 || (year % 4 === 0 && year % 100 !== 0);

isLeapYear(2018); // false
isLeapYear(2017); // false
isLeapYear(2016); // true

Разберём по частям:

  • первое условие year % 400 === 0: остаток от деления на 400 равен 0, значит, число кратно 400
  • || ИЛИ
  • второе условие (year % 4 === 0 && year % 100 !== 0)
    • year % 4 === 0: остаток от деления на 4 равен 0, значит, число кратно 4
    • && И
    • year % 100 !== 0: остаток от деления на 100 не равен 0, значит, число не кратно 100

Задание

Напишите функцию isNeutralSoldier(), которая принимает на вход два аргумента:

  1. Цвет доспехов (строка). Возможные варианты: redyellowblack.
  2. Цвет щита (строка). Возможные варианты: redyellowblack.

Функция возвращает true, если цвет доспехов не красный и цвет щита чёрный. В остальных случаях возвращает false.

Примеры вызова:

isNeutralSoldier('yellow', 'black'); // true
isNeutralSoldier('red', 'black');    // false
isNeutralSoldier('red', 'red');      // false
64. Слабая типизация

JavaScript: Слабая типизация — 2

В модуле «Арифметика» мы затронули тему слабой типизации.

JavaScript — это язык со слабой типизацией. Он знает о существовании разных типов (числа, строки и др.), но относится к их использованию не очень строго, пытаясь преобразовывать информацию, когда это кажется ему разумным.

Особенно много автоматических преобразований происходит при работе с логическими операциями.

Пример:

console.log(0 || 1);
1

Что тут произошло:

Оператор ИЛИ работает так, что его выполнение (слева направо) прерывается и возвращается результат первого аргумента, который можно преобразовать в true.

Пример:

console.log(0 && 1);
0

Что тут произошло:

Оператор И работает так, что его выполнение (слева направо) прерывается и возвращается результат первого аргумента, который можно преобразовать в false.

В JavaScript есть два простых правила, по которым происходят преобразования:

  • 0''undefinedNaNnull приводятся к false. Эти значения называют falsy.
  • Всё остальное приводится к true

Этим активно пользуются в разработке, например, для определения значения по умолчанию:

const value = name || '';
// Примеры
234 || ''; // 234
'hexlet' || ''; // 'hexlet'

Но здесь есть потенциальный баг. Если name может содержать falsy значения и это допустимо, то код выше начнет работать неверно:

// Упс
false || ''; // ''
0 || ''; // ''
undefined || ''; // ''

В одном из уроков мы рассмотрели операторы сравнения === и !== и упомянули, что в JavaScript так же есть операторы == и !=, но их не стоит использовать. Отличия как раз заключаются в преобразовании типов:

console.log('' === false); // => false
console.log('' == false);  // => true

Пустая строка и false — это разные значения, поэтому оператор === говорит «ложь! они не равны!».

Но оператор == преобразует типы, и с его точки зрения пустая строка и false равны.

Это преобразование неявное, поэтому по возможности избегайте операторов == и !=.


Вспомните операцию отрицания:

const answer = true;
console.log(!answer); // => false

При двойном отрицании !! итоговое значение равно начальному:

const answer = true;
console.log(!!answer); // => true

Но здесь дополнительно происходят преобразования типа. Поэтому результатом двойного отрицания всегда будет значение типа boolean. Этим приемом иногда пользуются, чтобы поменять тип данных.

Задание

Реализуйте функцию getLetter(), которая извлекает из переданной строки указанный символ (по порядковому номеру, а не индексу) и возвращает его наружу. Если такого символа нет, то функция возвращает пустую строку.

Примеры вызова:

const name = 'Hexlet';

// Обычное обращение возвращает undefined
name[10]; // undefined

// 11 символ соответствует 10 индексу
getLetter(name, 11); // ''

getLetter(name, 1); // 'H'
getLetter(name, 0); // ''
getLetter(name, 6); // 't'

Советы

Условные конструкции

Задача функции-предиката — получить ответ на вопрос, но обычно этого недостаточно и нужно выполнить определенное действие в зависимости от ответа. If и Switch – конструкции JavaScript, с помощью которых программист может выбирать необходимое поведение программы в зависимости от разных условий: пропускать одни инструкции и выполнять другие. Их и разберем на практике в этом модуле.

65. Условная конструкция (if)

JavaScript: Условная конструкция (if)

Задача предиката — получить ответ на вопрос, но обычно этого недостаточно и нужно выполнить определенное действие в зависимости от ответа.

Напишем функцию, которая определяет тип переданного предложения. Для начала она будет отличать обычные предложения от вопросительных.

const getTypeOfSentence = (sentence) => {
  const lastChar = sentence[sentence.length - 1];
  if (lastChar === '?') {
    return 'question';
  }

  return 'general';
};

getTypeOfSentence('Hodor');  // general
getTypeOfSentence('Hodor?'); // question

if – конструкция языка, управляющая порядком выполнения инструкций. В скобках ей передается выражение-предикат, а затем описывается блок кода в фигурных скобках. Этот блок кода будет выполнен, только если предикат — истина.

Если предикат — ложь, то блок кода в фигурных скобках пропускается, и функция продолжает свое выполнение дальше. В нашем случае следующая строчка кода — return 'general'; — заставит функцию вернуть строку и завершиться.

Как видите, return может находиться где угодно в функции. В том числе внутри блока кода с условием.

Если в фигурных скобках после if содержится только одна строчка кода, то фигурные скобки можно не писать и сделать так:

const getTypeOfSentence = (sentence) => {
  const lastChar = sentence[sentence.length - 1];
  if (lastChar === '?')
    return 'question';

  return 'general';
};

console.log(getTypeOfSentence('Hodor'));  // => general
console.log(getTypeOfSentence('Hodor?')); // => question

Советуем не делать так и всегда писать фигурные скобки. В таком случае явно видно, где начинается и заканчивается тело условия. Код становится более чётким и понятным.

Задание

Реализуйте функцию getSentenceTone(), которая принимает строку и определяет тон предложения. Если все символы в верхнем регистре, то это вопль — 'scream'. В ином случае — нормальное предложение — 'general'.

Примеры вызова:

getSentenceTone('Hello'); // general
getSentenceTone('WOW');   // scream

Алгоритм:

  1. Сгенерируйте строку в верхнем регистре на основе строки-аргумента с помощью toUpperCase().
  2. Сравните её с исходной строкой:
    • Если строки равны, значит, строка-аргумент в верхнем регистре.
    • В ином случае — строка-аргумент не в верхнем регистре.
66. else 

JavaScript: else

Напишем функцию getTypeOfSentence(), которая анализирует текст и возвращает описание его тона: для обычных предложений – General sentence, для вопросительных – Question sentence.

  getTypeOfSentence('Hodor');  // General sentence
  getTypeOfSentence('Hodor?'); // Question sentence

Реализация функции:

const getTypeOfSentence = (sentence) => {
  let sentenceType;
  // Предикат, проверяющий окончание текста
  // Если он оканчивается на символ '?', то вернётся true,
  // иначе false
  if (sentence.endsWith('?')) {
    sentenceType = 'Question';
  } else {
    sentenceType = 'General';
  }

  return `${sentenceType} sentence`;
};

Мы добавили ключевое слово else и новый блок с фигурными скобками. Этот блок выполнится, только если условие в if — ложь.

Существует два способа оформления конструкции if-else. С помощью отрицания можно изменить порядок блоков:

const getTypeOfSentence = (sentence) => {
  let sentenceType;
  // Добавилось отрицание
  // Содержимое else переехало в if и наоборот
  if (!sentence.endsWith('?')) {
    sentenceType = 'General';
  } else {
    sentenceType = 'Question';
  }

  return `${sentenceType} sentence`;
};

Какой способ предпочтительнее? Человеческому мозгу проще мыслить прямолинейно, а не через отрицание. Старайтесь выбирать проверку, которая не содержит отрицаний, и подстраивайте содержимое блоков под неё.

Задание

Реализуйте функцию buildUrl(), которая принимает на вход адрес страницы (без указания домена) и имя домена, а возвращает полный url со схемой https.

Примеры вызова:

buildUrl('pages/about', 'hexlet.io'); // 'https://hexlet.io/pages/about'
buildUrl('/pages/about', 'hexlet.io'); // 'https://hexlet.io/pages/about'

// Для главной страницы слэш в конце обязателен
buildUrl('/', 'ru.code-basics.com'); // 'https://ru.code-basics.com/'
buildUrl('', 'ru.code-basics.com'); // 'https://ru.code-basics.com/'

Первым параметром в функцию могут быть переданы адреса с ведущим слешем / и без него. Задача функции обрабатывать эту ситуацию, чтобы слеш не дублировался.

Для реализации этой функции вам могут понадобиться методы slice() и startsWith().


Определения

  • else – способ задать блок кода, который будет выполнен, если условие с if не удовлетворено
67. Конструкция else if

JavaScript: Конструкция else if

Функция getTypeOfSentence() из предыдущего урока различает только вопросительные и обычные предложения. Давайте попробуем добавить поддержку восклицательных предложений:

const getTypeOfSentence = (sentence) => {
  const lastChar = sentence[sentence.length - 1];
  let sentenceType;

  if (lastChar === '!') {
    sentenceType = 'exclamation';
  } else {
    sentenceType = 'normal';
  }

  if (lastChar === '?') {
    sentenceType = 'question';
  }

  return `Sentence is ${sentenceType}`;
};

getTypeOfSentence('Who?'); // 'Sentence is question'
getTypeOfSentence('No');   // 'Sentence is normal'
getTypeOfSentence('No!');  // 'Sentence is exclamation'

Мы добавили ещё одну проверку (“exclamation” переводится «восклицание»). Технически функция работает, но с точки зрения семантики есть проблемы.

  • Проверка на наличие вопросительного знака происходит в любом случае, даже если уже был обнаружен восклицательный знак.
  • Ветка else описана именно для первого условия, но не для второго.

Правильнее будет воспользоваться ещё одной возможностью условной конструкции:

const getTypeOfSentence = (sentence) => {
  const lastChar = sentence[sentence.length - 1];
  let sentenceType;

  if (lastChar === '?') {
    sentenceType = 'question';
  } else if (lastChar === '!') {
    sentenceType = 'exclamation';
  } else {
    sentenceType = 'normal';
  }

  return `Sentence is ${sentenceType}`;
};

getTypeOfSentence('Who?'); // 'Sentence is question'
getTypeOfSentence('No');   // 'Sentence is normal'
getTypeOfSentence('No!');  // 'Sentence is exclamation'

Теперь все условия выстроены в единую конструкцию. else if — это «если не выполнено предыдущее условие, но выполнено текущее». Получается такая схема:

  • если последний символ это ?, то 'question'
  • иначе, если последний символ это !, то 'exclamation'
  • иначе 'normal'

Выполнится только один из блоков кода, относящихся ко всей конструкции if.

Задание

На электронной карте Вестероса, которую реализовал Сэм, союзники Старков отображены зеленым кружком, враги — красным, а нейтральные семьи — серым.

Напишите для Сэма функцию whoIsThisHouseToStarks(), которая принимает на вход фамилию семьи и возвращает одно из трёх значений: 'friend''enemy''neutral'.

Правила определения:

  • Друзья ('friend'): ‘Karstark’, ‘Tally’
  • Враги ('enemy'): ‘Lannister’, ‘Frey’
  • Любые другие семьи считаются нейтральными

Примеры вызова:

whoIsThisHouseToStarks('Karstark'); // 'friend'
whoIsThisHouseToStarks('Frey');     // 'enemy'
whoIsThisHouseToStarks('Joar');     // 'neutral'
whoIsThisHouseToStarks('Ivanov');   // 'neutral'

Определения

  • else if – способ задать несколько альтернативных условий.
68. Тернарный оператор

JavaScript: Тернарный оператор

Посмотрите на определение функции, которая возвращает модуль переданного числа:

const abs = (number) => {
  if (number >= 0) {
    return number;
  }

  return -number;
};

abs(10); // 10
abs(-10); // 10

Можно ли записать её лаконичнее? Что-то вроде return <ответ в зависимости от условия>? Для этого справа от return должно быть выражение, но if — это инструкция, а не выражение.

В JavaScript существует конструкция, которая по своему действию аналогична конструкции if-else, но при этом является выражением. Она называется тернарный оператор.

Тернарный оператор — единственный в своем роде оператор, требующий три операнда:

const abs = (number) => {
  return number >= 0 ? number : -number;
};

Общий паттерн выглядит так: <predicate> ? <expression on true> : <expression on false>.

Сокращенный вариант функции abs(), выглядит так:

const abs = (number) => (number >= 0 ? number : -number);

Обратите внимание на скобки вокруг тернарника. Они не обязательны, но линтер настоятельно рекомендует их ставить, во избежание неоднозначностей.

Давайте перепишем начальный вариант getTypeOfSentence() аналогично:

Было:

const getTypeOfSentence = (sentence) => {
  const lastChar = sentence.slice(-1);

  if (lastChar === '?') {
      return 'question';
  }

  return 'normal';
};

Стало:

const getTypeOfSentence = (sentence) => {
  const lastChar = sentence.slice(-1);

  return (lastChar === '?') ? 'question' : 'normal';
};

getTypeOfSentence('Hodor');  // normal
getTypeOfSentence('Hodor?'); // question

Если вы помните, в чём сила выражений, то вероятно уже догадались, что тернарный оператор можно вкладывать в тернарный оператор. Не делайте этого 🙂 Такой код тяжело и читать, и отлаживать, это очень плохая практика.

Задание

Реализуйте функцию convertText(), которая принимает на вход строку и, если первая буква не заглавная, возвращает перевернутый вариант исходной строки. Если первая буква заглавная, то строка возвращается без изменений.

Примеры вызова:

convertText('Hello'); // 'Hello'
convertText('hello'); // 'olleh'

// Не забудьте учесть пустую строку!
convertText(''); // ''

Перевернуть строчку можно используя функцию reverse(). В качестве аргумента в неё нужно передать строку, которую мы хотим перевернуть:

const result = reverse('Hello!');
console.log(result); // => '!olleH'

Есть разные подходы к решению этой задачи. Возможно, вам пригодится метод toUpperCase() и возможность получения символа из строки (например, str[0]).

Попробуйте написать два варианта функции: с обычным if-else, и с тернарным оператором.


Определения

  • Тернарный оператор – Способ превратить простую условную инструкцию в выражение, например, number >= 0 ? number : -number.
69. Конструкция Switch

JavaScript: Конструкция Switch

Многие языки в дополнение к условной конструкции if включают в себя switch. Это специализированная версия if, созданная для некоторых особых ситуаций. Например, её имеет смысл использовать там, где есть цепочка if else с проверками на равенство. Например:

if (status === 'processing') {
  // Делаем раз
} else if (status === 'paid') {
  // Делаем два
} else if (status === 'new') {
  // Делаем три
} else {
  // Делаем четыре
}

Эта составная проверка обладает одной отличительной чертой: каждая ветка здесь — это проверка значения переменной status. Switch позволяет записать этот код короче и выразительнее:

switch (status) {
  case 'processing': // status == processing
    // Делаем раз
    break;
  case 'paid': // status == paid
    // Делаем два
    break;
  case 'new': // status == new
    // Делаем три
    break;
  default: // else
    // Делаем четыре
}

Свитч — довольно сложная конструкция с точки зрения количества элементов, из которых она состоит:

  • Внешнее описание, в которое входит ключевое слово switch. Переменная, по значениям которой switch будет выбирать поведение. И фигурные скобки для вариантов выбора.
  • Конструкции case и default, внутри которых описывается поведение для разных значений рассматриваемой переменной. Каждый case соответствует if в примере выше. default – это особая ситуация, соответствующая ветке else в условных конструкциях. Как else, указывать default не обязательно (но линтер всегда его просит).
  • break нужен для предотвращения «проваливания». Если его не указать, то после выполнения нужного case выполнение перейдет к следующему case, и так либо до ближайшего break, либо до конца switch.

Фигурные скобки в switch не определяют блок кода, как это было в других местах. Внутри допустим только тот синтаксис, который показан выше. То есть там можно использовать case или default. А вот внутри каждого case (и default) ситуация другая. Здесь можно выполнять любой произвольный код:

switch (count) {
  case 1:
    // Делаем что-то полезное
    break;
  case 2:
    // Делаем что-то полезное
    break;
  default:
    // Что-то делаем
}

Иногда результат, полученный внутри case, — это конец выполнения функции, содержащей switch. В таком случае его нужно как-то вернуть наружу. Для решения этой задачи есть два способа.

Первый. Создать переменную перед switch, заполнить её в case и затем, в конце, вернуть значение этой переменной наружу.

(count) => {
  // Объявляем переменную
  let result;

  // Заполняем
  switch (count) {
    case 1:
      result = 'one';
      break;
    case 2:
      result = 'two';
      break;
    default:
      result = null;
  }

  // Возвращаем
  return result;
};

Второй способ проще и короче. Вместо создания переменной, case позволяет внутри себя делать обычный возврат из функции. А так как после return никакой код не выполняется, то мы можем избавиться от break:

(count) => {
  switch (count) {
    case 1:
      return 'one';
    case 2:
      return 'two';
    default:
      return null;
  }
};

Switch хоть и встречается в коде, но технически всегда можно обойтись без него. Ключевая польза при его использовании в том, что он лучше выражает намерение программиста, когда нужно проверять конкретные значения переменной. Хотя кода и стало физически чуть больше, читать его легче, в отличие от блоков else if.

Задание

Реализуйте функцию getNumberExplanation(), которая принимает на вход число и возвращает объяснение этого числа. Если для числа нет объяснения, то возвращается null:

getNumberExplanation(8);   // null

// Объяснения есть только для указанных ниже чисел
getNumberExplanation(666); // 'devil number'
getNumberExplanation(42);  // 'answer for everything'
getNumberExplanation(7);   // 'prime number'

Советы

Циклы

Любой код может повторяться десятки, тысячи, миллионы раз. В комбинации с другими известными нам инструментами — переменными и условиями — это открывает множество возможностей по построению программ и сложных систем. Приведем простой пример. Вам нужно найти конкретную фразу в учебнике из 500 страниц. Фразу вы помните, а вот номер страницы нет. Самый простой (и долгий) способ — последовательно просматривать страницы до тех пор, пока не найдете нужную. Для выполнения таких повторяющихся действий и нужны циклы.

70. Цикл While

JavaScript: Цикл While

Программы, которые мы пишем, становятся всё сложнее и объемнее. Они все ещё очень далеки от реальных программ, где количество строк кода измеряется десятками и сотнями тысяч (а иногда и миллионами), но текущая сложность уже способна заставить напрячься людей без опыта. Начиная с этого урока, мы переходим к одной из самых сложных базовых тем в программировании – циклам.

Любые прикладные программы служат очень прагматичным целям. Они помогают управлять сотрудниками, финансами, развлекают в конце концов. Несмотря на различия, все эти программы выполняют заложенные в них алгоритмы, которые очень похожи между собой. Что это такое? Алгоритм — это последовательность действий (инструкций), которая приводит нас к некоему ожидаемому результату. В принципе, это описание подходит под любую программу, но под алгоритмами обычно понимается что-то более специфичное.

Представьте себе, что у нас есть книга и мы хотим найти внутри неё какую-то конкретную фразу. Саму фразу мы помним, но не знаем, на какой она странице. Как найти нужную страницу? Самый простой (и долгий) способ — последовательно просматривать страницы до тех пор, пока мы не найдем нужную. В худшем случае придется просмотреть все страницы, но результат мы всё равно получим. Именно этот процесс и называется алгоритмом. Он включает в себя логические проверки (нашли ли фразу) и перебор страниц. Количество страниц, которое придется посмотреть, заранее неизвестно, но сам процесс просмотра повторяется из раза в раз совершенно одинаковым образом. Для выполнения повторяющихся действий как раз и нужны циклы. Каждый повтор, в таком случае, называется итерацией.

Допустим мы хотим написать функцию, которая выводит на экран все числа от 1 до указанного (через аргументы):

printNumbers(3);
// => 1
// => 2
// => 3

Эту функцию невозможно реализовать уже изученными средствами, так как количество выводов на экран заранее неизвестно. А с циклами это не составит никаких проблем:

const printNumbers = (lastNumber) => {
  // i сокращение от index (порядковый номер)
  // используется по общему соглашению во множестве языков
  // как счетчик цикла
  let i = 1;

  while (i <= lastNumber) {
    console.log(i);
    i = i + 1;
  }
  console.log('finished!');
};

printNumbers(3);
1
2
3
finished!

В коде функции использован цикл while. Он состоит из трёх элементов:

  • Ключевое слово while. Несмотря на схожесть с вызовом функций, это не вызов функции.
  • Предикат. Условие, которое указывается в скобках после while. Это условие вычисляется на каждой итерации.
  • Тело цикла. Блок кода в фигурных скобках. Этот блок аналогичен блоку кода в функциях. Всё, что определено внутри этого блока (константы или переменные), видно только внутри этого блока.

Конструкция читается так: «делать то, что указано в теле цикла, пока истинно условие (предикат) i <= lastNumber». Разберём работу этого кода для вызова printNumbers(3):

// Инициализируется i
let i = 1;

// Предикат возвращает true, поэтому выполняется тело цикла
while (1 <= 3)
// console.log(1);
// i = 1 + 1;

// Закончилось тело цикла, поэтому происходит возврат в начало
while (2 <= 3)
// console.log(2);
// i = 2 + 1;

// Закончилось тело цикла, поэтому происходит возврат в начало
while (3 <= 3)
// console.log(3);
// i = 3 + 1;

// Предикат возвращает false, поэтому выполнение переходит за цикл
while (4 <= 3)

// console.log('finished!');
// На этом этапе i равен 4, но он нам уже не нужен
// функция завершается

Самое главное в цикле — завершение его работы (выход из цикла). Процесс, который порождает цикл, должен в конце концов остановиться. Ответственность за остановку полностью лежит на программисте. Обычно задача сводится к введению переменной, называемой «счётчиком цикла». Сначала счётчик инициализируется, то есть ему задаётся начальное значение. В нашем примере это инструкция let i = 1, выполняемая до входа в цикл. Затем в условии цикла проверяется, достиг ли счётчик своего предельного значения. И, наконец, счётчик меняет свое значение i = i + 1.

На этом моменте новички делают больше всего ошибок. Например, случайно забытое увеличение счётчика или неправильная проверка в предикате способны привести к зацикливанию. Это ситуация, при которой цикл работает бесконечно и программа никогда не останавливается. В таком случае приходится её завершать принудительно (кто знает, может быть когда зависают реальные программы, в этот момент внутри них выполняется бесконечный цикл).

const printNumbers = (lastNumber) => {
  let i = 1;

  // Этот цикл никогда не остановится
  // и будет печатать всегда одно значение
  while (i <= lastNumber) {
    console.log(i);
  }
  console.log('finished!');
};

В некоторых случаях бесконечные циклы полезны. Здесь мы такие случаи не рассматриваем, но полезно видеть как выглядит этот код:

while (true) {
  // Что-то делаем
}

Подводя итог. Когда всё же нужны циклы, а когда можно обойтись без них? Физически невозможно обойтись без циклов тогда, когда алгоритм решения задачи требует повторения каких-то действий, как в примере с книгой, и количество этих операций заранее неизвестно.

Задание

Модифицируйте функцию printNumbers() так, чтобы она выводила числа в обратном порядке. Для этого нужно идти от верхней границы к нижней. То есть счётчик должен быть инициализирован максимальным значением, а в теле цикла его нужно уменьшать до нижней границы.

Пример вызова и вывода:

printNumbers(4);
4
3
2
1
finished!

Советы

Определения

  • Цикл While – инструкция для повторения кода, пока удовлетворяется какое-то условие.
71. Агрегация данных (Числа)

JavaScript: Агрегация данных (Числа)

Отдельный класс задач, который не может обойтись без циклов, называется агрегированием данных. К таким задачам относятся поиск максимального, минимального, суммы, среднего арифметического и т.п. Их главная особенность в том, что результат зависит от всего набора данных. Для рассчета суммы нужно сложить все числа, для вычисления максимального нужно сравнить все числа.

С такими задачами хорошо знакомы все, кто занимаются числами, например бухгалтеры или маркетологи. Обычно их выполняют в таблицах наподобие Microsoft Excel или Google Tables.

Разберем самый простой пример – поиск суммы набора чисел. Реализуем функцию, которая складывает числа в указанном диапазоне, включая границы. Диапазоном в данном случае называется ряд чисел от какого-то начала до определенного конца. Например, диапазон [1, 10] включает в себя все целые числа от 1 до 10.

sumNumbersFromRange(5, 7); // 5 + 6 + 7 = 18
sumNumbersFromRange(1, 2); // 1 + 2 = 3

// [1, 1] диапазон с одинаковым началом и концом – тоже диапазон
// он в себя включает ровно одно число – саму границу диапазона
sumNumbersFromRange(1, 1); // 1
sumNumbersFromRange(100, 100); // 100

Для реализации этого кода нам понадобится цикл, так как сложение чисел – это итеративный процесс (он повторяется для каждого числа), а количество итераций зависит от размера диапазона. Перед тем, как смотреть код, попробуйте ответьте на вопросы ниже:

  • Каким значением инициализировать счетчик?
  • Как он будет изменяться?
  • Когда цикл должен остановиться?

Попробуйте сначала подумать над этими вопросами, а затем посмотрите код ниже:

const sumNumbersFromRange = (start, finish) => {
  // Технически можно менять start
  // Но входные аргументы нужно оставлять в исходном значении
  // Это сделает код проще для анализа
  let i = start;
  let sum = 0; // Инициализация суммы

  while (i <= finish) { // Двигаемся до конца диапазона
    sum = sum + i; // Считаем сумму для каждого числа
    i = i + 1; // Переходим к следующему числу в диапазоне
  }

  // Возвращаем получившийся результат
  return sum;
};

Общая структура цикла здесь стандартна. Есть счетчик, который инициализируется начальным значением диапазона, есть сам цикл с условием остановки при достижении конца диапазона, и, наконец, изменение счетчика в конце тела цикла. Количество итераций в таком цикле равно finish - start + 1. То есть для диапазона от 5 до 7 – это 7 – 5 + 1, то есть 3 итерации.

Главные отличия от обычной обработки связаны с логикой вычислений результата. В задачах на агрегацию всегда есть какая-то переменная, которая хранит внутри себя результат работы цикла. В коде выше это sum. На каждой итерации цикла происходит её изменение, прибавление следующего числа в диапазоне: sum = sum + i. Весь процесс выглядит так:

// Для вызова sumNumbersFromRange(2, 5);
let sum = 0;
sum = sum + 2; // 2
sum = sum + 3; // 5
sum = sum + 4; // 9
sum = sum + 5; // 14
// 14 – результат сложения чисел в диапазоне [2, 5]

У переменной sum есть начальное значение, равное 0. Зачем вообще задавать значение? Любая повторяющаяся операция начинается с какого-то значения. Нельзя просто так объявить переменную и начать с ней работать внутри цикла. Это приведет к неверному результату:

// начальное значение не задано
// js автоматически делает его равным undefined
let sum;

// первая итерация цикла
sum = sum + 2; // ?

В результате такого вызова внутри sum окажется NaN, то есть не-число. Оно возникает из-за попытки сложить 2 и undefined. Значит какое-то значение всё же нужно. Почему в коде выше выбран 0? Очень легко проверить, что все остальные варианты приведут к неверному результату. Если начальное значение будет равно 1, то результат получится на 1 больше, чем нужно.

В математике существует понятие нейтральный элемент операции (у каждой операции свой элемент). Это понятие имеет очень простой смысл. Операция с этим элементом не изменяет то значение, над которым проводится операция. В сложении любое число плюс ноль дает само число. При вычитании – тоже самое. Даже у конкатенации есть нейтральный элемент – это пустая строка: '' + 'one' будет ‘one’.

Вопрос на самопроверку. Какой нейтральный элемент у операции умножения?

Задание

Реализуйте функцию multiplyNumbersFromRange(), которая перемножает числа в указанном диапазоне включая границы диапазона. Пример вызова:

multiplyNumbersFromRange(1, 5); // 1 * 2 * 3 * 4 * 5 = 120
multiplyNumbersFromRange(2, 3); // 2 * 3 = 6
multiplyNumbersFromRange(6, 6); // 6
72. Агрегация данных (Строки)

JavaScript: Агрегация данных (Строки)

Агрегация применяется не только к числам, но и к строкам. Это такие задачи, в которых строка формируется динамически, то есть заранее неизвестно, какого она размера и что будет содержать.

Представьте себе функцию, которая умеет «умножать» строку, то есть она повторяет её указанное количество раз:

repeat('hexlet', 3); // 'hexlethexlethexlet'

Принцип работы этой функции довольно простой: в цикле происходит «наращивание» строки указанное количество раз:

const repeat = (text, times) => {
  // Нейтральный элемент для строк – пустая строка
  let result = '';
  let i = 1;

  while (i <= times) {
    // Каждый раз добавляем строку к результату
    result = `${result}${text}`;
    i = i + 1;
  }

  return result;
};

Распишем выполнение этого кода по шагам:

// Для вызова repeat('hexlet', 3);
let result = '';
result = `${result}hexlet`; // hexlet
result = `${result}hexlet`; // hexlethexlet
result = `${result}hexlet`; // hexlethexlethexlet

Задание

Реализуйте функцию joinNumbersFromRange(), которая объединяет все числа из диапазона в строку:

joinNumbersFromRange(1, 1); // '1'
joinNumbersFromRange(2, 3); // '23'
joinNumbersFromRange(5, 10); // '5678910'
73. Обход строк

JavaScript: Обход строк

Циклы подходят не только для обработки чисел, но и при работе со строками. В первую очередь благодаря возможности получить конкретный символ по его индексу. Ниже пример кода, который распечатывает буквы каждого слова на отдельной строке:

const printNameBySymbol = (name) => {
  let i = 0;
  // Такая проверка будет выполняться до конца строки
  // включая последний символ. Его индекс `length - 1`.
  while (i < name.length) {
    // Обращаемся к символу по индексу
    console.log(name[i]);
    i = i + 1;
  }
};

const name = 'Arya';
printNameBySymbol(name);
// => 'A'
// => 'r'
// => 'y'
// => 'a'

Самое главное в этом коде, поставить правильное условие в while. Это можно сделать сразу двумя способами: i < name.length или i <= name.length - 1. Оба способа приводят к одному результату.

Задание

Реализуйте функцию printReversedNameBySymbol(), которая печатает переданное слово посимвольно, как в примере из теории, но делает это в обратном порядке.

const name = 'Arya';

printReversedNameBySymbol(name);
// => 'a'
// => 'y'
// => 'r'
// => 'A'
74. Условия внутри тела цикла

JavaScript: Условия внутри тела цикла

Тело цикла, как и тело функции — это место выполнения инструкций. Значит, мы можем использовать внутри него всё изученное ранее, например — условные конструкции.

Представьте себе функцию, которая считает, сколько раз входит буква в предложение. Пример её работы:

countChars('Fear cuts deeper than swords.', 'e'); // 4
// Если вы ничего не нашли, то результат — 0 совпадений
countChars('Sansa', 'y'); // 0

Перед тем как посмотреть её содержимое, попробуйте ответить на вопросы:

  • Является ли эта операция агрегацией?
  • Какой будет проверка на вхождение символа?
const countChars = (str, char) => {
  let i = 0;
  let count = 0;
  while (i < str.length) {
    if (str[i] === char) {
      // Считаем только подходящие символы
      count = count + 1;
    }
    // Счетчик увеличивается в любом случае
    i = i + 1;
  }

  return count;
};

Эта задача является агрегирующей. Несмотря на то, что она считает не все символы, для подсчета самой суммы все равно приходится анализировать каждый символ.

Ключевое отличие этого цикла от рассмотренных в наличии условия внутри тела. Переменная count увеличивается только в том случае, когда текущий рассматриваемый символ совпадает с ожидаемым.

В остальном — это типичная агрегатная функция, которая возвращает количество нужных символов вызываемому коду.

Задание

Функция из теории учитывает регистр букв. То есть A и a с её точки зрения разные символы. Реализуйте вариант этой же функции, так чтобы регистр букв был не важен:

countChars('HexlEt', 'e'); // 2
countChars('HexlEt', 'E'); // 2
75. Формирование строк в циклах

JavaScript: Формирование строк в циклах

Ещё одно использование циклов – формирование строк. Подобная задача нередко встречается в веб-программировании. Она сводится к обычной агрегации с применением интерполяции или конкатенации.

Есть одна задача, крайне популярная среди людей, проводящих собеседования, это переворот строки. Её можно решить множеством разных способов, но именно посимвольный перебор считается самым базовым. Пример работы этой функции:

reverse('Hexlet'); // telxeH

Общая идея переворота состоит в следующем: нужно брать символы по очереди с начала строки и соединять их в обратном порядке. Звучит довольно просто. Давайте проверим:

const reverse = (str) => {
  let i = 0;
  // Нейтральный элемент для строк это пустая строка
  let result = '';
  while (i < str.length) {
    // Соединяем в обратном порядке
    result = `${str[i]}${result}`;
    // То же самое через конкатенацию
    // result = str[i] + result;
    i = i + 1;
  }

  return result;
};

const name = 'Bran';
reverse(name); // 'narB'
// Проверка нейтрального элемента
reverse(''); // ''

Единственный возможно сложный момент в этом коде – прочувствовать, как собирается сама строка. Так как каждый следующий символ прикрепляется к результирующей строке слева, то, в конечном итоге, строка оказывается перевернута.

Задание

Реализуйте такую же функцию reverse(), но выполняющую обход строки не с первого элемента по последний, а наоборот, от последнего к первому. Общая структура функции при этом останется такой же. Изменится начальный индекс, условие окончания цикла, сборка новой строки и формирование нового индекса в цикле.

76. Синтаксический сахар

JavaScript: Синтаксический сахар

Подобные конструкции index = index + 1 в JavaScript используются довольно часто, поэтому создатели языка добавили сокращённый вариант записи: index += 1. Такие сокращения принято называть синтаксическим сахаром, потому что они делают процесс написания кода немного проще и приятнее, «подслащивая» его 🙂

Существуют сокращённые формы для всех арифметических операций и для конкатенации строк:

  • a = a + 1 → a += 1
  • a = a - 1 → a -= 1
  • a = a * 2 → a *= 2
  • a = a / 1 → a /= 1
  • a = a + 'foo' → a += 'foo'

Задание

Реализуйте функцию filterString(), принимающую на вход строку и символ, и возвращающую новую строку, в которой удален переданный символ во всех его позициях.

Пример вызова:

const str = 'If I look back I am lost';
filterString(str, 'I'); // 'f  look back  am lost'
filterString(str, 'o'); // 'If I lk back I am lst'
77. Инкремент и декремент

JavaScript: Инкремент и декремент

Из языка Си в JavaScript перекочевали две операции: инкремент ++ и декремент --, которые очень часто встречаются вместе с циклами. Эти унарные операции увеличивают и уменьшают на единицу число, записанное в переменную:

let i = 0;
i++; // 0
i++; // 1

i--; // 2
i--; // 1

Кроме постфиксной формы, у них есть и префиксная:

let i = 0;
++i; // 1
++i; // 2

--i; // 1
--i; // 0

Кажется, что нет никакой разницы между постфиксной и префиксной формами. Но тут начинаются сложности.

В отличие от всех остальных операций, которые не имеют побочных эффектов и просто возвращают новое значение, инкремент и декремент не только возвращают значение, но и изменяют значение переменной.

При использовании префиксной нотации сначала происходит изменение переменной, а потом возврат.

При использовании постфиксной нотации — наоборот: можно считать, что сначала происходит возврат, а потом изменение переменной.

Правило работает одинаково для инкремента и декремента. Для простоты рассмотрим только инкремент:

let x = 5;

console.log(++x); // => 6
console.log(x);   // => 6

console.log(x++); // => 6
console.log(x);   // => 7

Что происходит?

  1. Вывели на экран ++x. Это префиксный инкремент, поэтому сначала значение переменной увеличилось на 1, потом результат вернулся и вывелся на экран.
  2. Так как значение изменилось, console.log(x) вывел 6.
  3. Теперь выводим на экран x++. Это постфиксный инкремент, поэтому возвращено значение, содержавшееся в переменной до её увеличения на 1.
  4. Так как значение изменилось, console.log(x) вывел 7.

Особенно страшным это становится тогда, когда инкремент вставляют внутрь других операций: x = i++ - 7 + --h. Понять такой код почти невозможно, и его написание должно рассматриваться как тяжкое преступление.

Например, в языке JavaScript линтер (программа, проверяющая код) сразу начинает ругаться, когда встречает использование инкремента или декремента.

Рекомендации по использованию:

  • Никогда не мешайте в рамках одного выражения операции/функции без побочных эффектов с операциями/функциями, обладающими побочными эффектами.
  • Используйте инкремент и декремент только там, где нет разницы между префиксным и постфиксным вариантом: отдельно от всего, на своей собственной строчке кода.

Задание

Напишите функцию makeItFunny(), которая принимает на вход строку и возвращает её копию, у которой каждый n-ный элемент переведен в верхний регистр. n – задается на входе в функцию.

Для определения каждого n-ного элемента понадобится остаток от деления %. Подумайте, как его можно использовать.

Пример вызова:

const text = 'I never look back';
// Каждый третий элемент
makeItFunny(text, 3); // 'I NevEr LooK bAck'

Советы

78. Возврат из циклов

JavaScript: Возврат из циклов

Работа с циклами обычно сводится к двум сценариям:

  1. Агрегация. Накопление результата во время итераций и работа с ним после цикла. Переворот строки как раз относится к такому варианту.
  2. Выполнение цикла до достижения необходимого результата и выход. Например, задача поиска простых чисел. Напомним, что простое число — это число, которое делится без остатка только на себя и на единицу.

Рассмотрим простой алгоритм проверки простоты числа. Будем делить искомое число x на все числа из диапазона от двух до x - 1 и смотреть остаток от деления. Если в этом диапазоне не найден делитель, который делит число x без остатка, значит перед нами простое число.

Если задуматься, то можно заметить, что достаточно проверять числа не до x - 1, а до половины числа. Например, 11 не делится на 2, 3, 4, 5. Но и дальше гарантированно не будет делиться на числа больше своей половины. Значит, можно провести небольшую оптимизацию и проверять деление только до x / 2.

const isPrime = (number) => {
  if (number < 2) {
      return false;
  }

  let divider = 2;

  while (divider <= number / 2) {
    if (number % divider === 0) {
        return false;
    }

    divider += 1;
  }

  return true;
}

isPrime(1); // false
isPrime(2); // true
isPrime(3); // true
isPrime(4); // false

Алгоритм построен таким образом, что если во время последовательного деления на числа до x / 2 находится хоть одно, которое делит без остатка, то переданный аргумент — не простое число, а значит дальнейшие вычисления не имеют смысла. В этом месте стоит возврат false.

И только если цикл отработал целиком, можно сделать вывод, что число — простое, так как не было найдено ни одного числа, которое делит число без остатка.

Задание

Реализуйте функцию hasChar(), которая проверяет (с учётом регистра), содержит ли строка указанную букву. Функция принимает два параметра:

  • Строка
  • Буква для поиска

Пример вызова:

hasChar('Renly', 'R'); // true
hasChar('Renly', 'r'); // false
hasChar('Tommy', 'm'); // true
hasChar('Tommy', 'd'); // false

Советы

79. Цикл For

JavaScript: Цикл For

Цикл while идеален для ситуаций, когда количество итераций неизвестно заранее, например, при поиске простого числа. Когда количество итераций известно, предпочтительнее использовать цикл for.

Посмотрим реализацию переворота строки через цикл for:

const reverseString = (str) => {
  let result = '';
  for (let i = 0; i < str.length; i += 1) {
    result = `${str[i]}${result}`;
  }

  return result;
};

Можно читать так: цикл с индексом i повторяется пока i < str.length и после каждого шага увеличивает i на 1.

В определении цикла for есть:

  1. Начальное значение счётчика. Этот код выполняется ровно один раз перед первой итерацией.
  2. Предикат — условие повторения циклов. Выполняется на каждой итерации. Точно так же как и в while
  3. Описание изменения счётчика. Этот код выполняется в конце каждой итерации.

В остальном принцип работы точно такой же, как у цикла while.

Задание

Сэмвелл обнаружил, что его сообщения перехватываются в замке «Близнецы» и там читаются. Из-за этого их атаки перестали быть внезапными. Немного подумав, он разработал программу, которая бы шифровала сообщения по следующему алгоритму. Она бы брала текст и переставляла в нем каждые два подряд идущих символа.

encrypt('attack'); // 'taatkc'
// Если число символов нечётное
// то последний символ остается на своем месте
encrypt('go!'); // 'og!'

Реализуйте функцию encrypt(), которая принимает на вход исходное сообщение и возвращает зашифрованное.

Подумайте. Может ли эта функция расшифровать зашифрованное сообщение?

Серия публикаций:

Программирование и веб-разработка

Добавить комментарий

Авторизация
*
*
Регистрация
*
*
*
*
Открытый профиль общедоступен. (это не распространяется на вкладки вашего личного кабинета). Закрытый профиль закрывает все возможности найти или увидеть ваш личный кабинет посторонним. В любой момент, вы сможете изменить этот параметр в настройках личного кабинета.
Генерация пароля
логотип
Рекомендуем

Total Flow

Рекомендуем

Все самое интересное (Статьи)

Рекомендуем

Все самое вкусное (Рецепты)