Перейти к содержимому

Введение в JavaScript

JavaScript (JS) — язык программирования, который позволяет обрабатывать данные и задавать поведение веб-страницы. HTML описывает содержимое страницы, CSS оформляет и располагает элементы, а JavaScript выполняет вычисления и реагирует на действия пользователя. Например, с его помощью можно проверить заполнение формы, отфильтровать список товаров или пересчитать стоимость заказа. Код выполняет JavaScript-движок браузера.

Часть 1. Вычисления и функции

Подготовка и подключение скрипта

Откройте папку website с готовой визиткой и сохраните её копию. Внутри assets создайте папку js с двумя файлами: script.js для работы над визиткой и practice.js для экспериментов. Для экспериментов используйте отдельную страницу practice.html рядом с index.html.

    • index.html
    • practice.html
        • style.css
        • photo.jpg
        • script.js
        • practice.js

В уже существующий <head> файла index.html добавьте подключение:

<script src="assets/js/script.js" defer></script>

Атрибут src задаёт путь от HTML-файла к скрипту. Атрибут defer позволяет загружать внешний скрипт параллельно с разбором HTML и выполнять его после этого разбора. Закрывающий тег </script> обязателен.

В script.js запишите:

console.log("JavaScript подключён");

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

Откройте index.html в браузере, затем инструменты разработчика через меню браузера или F12 и выберите вкладку Console («Консоль»). Сохраните скрипт и обновите страницу. Должно появиться сообщение «JavaScript подключён». Вызов console.log выводит сообщение в консоль и не меняет содержимое страницы.

Для экспериментов сохраните в practice.html полный документ:

<!DOCTYPE html>
<html lang="ru">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Практика JavaScript</title>
    <script src="assets/js/practice.js" defer></script>
</head>
<body>
    <h1>Практика JavaScript</h1>
    <p>Результаты вычислений находятся в консоли.</p>
</body>
</html>

Примеры из разделов запускайте по одному, заменяя содержимое practice.js. В script.js сохраняйте код, который подключён к index.html. Так повторяющиеся имена из разных примеров не будут мешать друг другу. Внутри .js записывается только JavaScript, без тегов <script>.

Если сообщение не появилось

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

Сообщение SyntaxError означает, что браузер не смог разобрать синтаксис: например, пропущена кавычка или скобка. ReferenceError часто появляется при обращении к необъявленному имени. JavaScript различает регистр: console.log и Console.log — разные записи. Исправьте первую ошибку и обновите страницу.

Переменные и значения

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

const studentName = "Анна";
let completedLessons = 2;
completedLessons = 3;

console.log(studentName, completedLessons); // Анна 3

Здесь у console.log два аргумента, разделённых запятой. Запись после // — комментарий до конца строки, он не выполняется. Для многострочного комментария используется /* ... */.

Выбирайте понятные имена, например completedLessons вместо x. Имя не должно начинаться с цифры или содержать пробелы. Несколько слов удобно соединять, начиная каждое следующее с заглавной буквы. Объявляйте переменную до первого использования и задавайте начальное значение каждой const. Для объявления переменных используйте const и let.

Значение Тип Для чего подходит
3, 2.5 number Количество и вычисления. Дробная часть отделяется точкой
"Анна", 'HTML' string Текст в двойных или одинарных кавычках
true, false boolean Ответ «да» или «нет»
undefined undefined Значение ещё не задано, например у let result;
null null Намеренное отсутствие значения

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

Оператор typeof помогает проверить тип: typeof 3 вернёт строку "number", а typeof "3" — "string". У null есть историческая особенность: typeof null возвращает "object", хотя само значение примитивное. Для проверки отсутствия значения будем сравнивать его непосредственно с null.

В записи completedLessons = completedLessons + 1 сначала вычисляется значение справа от =: к текущему количеству прибавляется единица. Затем результат сохраняется в той же переменной.

Сколько выведет пример после увеличения счётчика? Сначала предположите ответ, затем замените 2 в начальном значении на 5 и проверьте результат.

Начальное значение и присваивание

Код примера

Во встроенных примерах переключитесь на вкладку JS и откройте панель «Консоль» в нижней части LiveCodes. Результат обновляется после паузы во вводе. Для сравнения смотрите сообщения последнего запуска. При необходимости очищайте консоль. Проверяйте вывод по условию упражнения.

Если редактор не загрузился, раскройте «Код примера», перенесите JavaScript в practice.js и откройте practice.html. Изменения из LiveCodes нужно переносить в локальные файлы самостоятельно.

Почему const не подходит для счётчика
При начальном значении 5 и прибавлении единицы получится 6. Если заменить let на const, повторное присваивание вызовет TypeError: имя нельзя связать с новым значением. Верните let и повторите запуск.

В script.js после сообщения о подключении объявите studentName, totalLessons и completedLessons. Для первого расчёта возьмите 8 занятий, из которых завершены 3. Имя и общее количество объявите через const, число завершённых занятий через let. Выведите три значения в консоль.

Выражения, строки и преобразование типов

Выражение — запись, которая вычисляет значение. Арифметические операторы +, -, *, / обозначают сложение, вычитание, умножение и деление. Оператор % возвращает остаток от деления: 7 % 3 равно 1. Умножение и деление выполняются раньше сложения. Скобки позволяют явно задать порядок.

const total = 8;
const completed = 3;
const remaining = total - completed;
const percent = (completed / total) * 100;

console.log(remaining); // 5
console.log(percent);   // 37.5

Запись completedLessons += 1 означает то же, что completedLessons = completedLessons + 1. Уменьшить значение можно через -=, умножить через *=, разделить через /=.

Для сообщения с вычисленным значением удобно использовать шаблонную строку. Она заключается в обратные кавычки, а значения вставляются через ${...}:

const student = "Анна";
const completed = 3;
console.log(`${student}: завершено ${completed} занятия.`);

В обычных кавычках "${student}" останется буквальным текстом. Оператор + тоже соединяет строки, но при смешивании текста и чисел его результат может удивить. Чем будут отличаться "3" + 1 и Number("3") + 1? Предположите результат, затем выполните пример.

Строка с цифрами и число

Код примера

Number(...) явно преобразует значение в число, а String(...) — в строку. Числовые данные, полученные как текст, нужно преобразовать перед сложением. Number("три") даст NaN — специальное числовое значение, показывающее неудачное преобразование. Number.isFinite(value) проверяет, что значение является конечным числом, а Number.isInteger(value) — что оно целое. Эти проверки сами строки в числа не превращают.

Результаты преобразований
"3" + 1 даст строку "31", а Number("3") + 1 — число 4. Для явной проверки выведите typeof результата. Значения "" и строка из пробелов при преобразовании через Number дают 0, поэтому пустой пользовательский ввод нужно проверять отдельно. Деление ненулевого числа на ноль даст Infinity или -Infinity, а 0 / 0 — NaN. Все эти результаты не проходят Number.isFinite.

Дополните script.js: вычислите remainingLessons и progressPercent из уже объявленных переменных. Через шаблонную строку выведите имя, число оставшихся занятий и процент выполнения. Для значений 8 и 3 должны получиться 5 и 37.5. Затем измените completedLessons на 4 до вычислений и повторите запуск: результат должен стать 4 и 50. Вычисленная переменная не пересчитывается автоматически при изменении исходных данных.

Сравнения и условия

Сравнение возвращает true или false. Операторы <, >, <=, >= сравнивают значения, === проверяет строгое равенство, а !== — строгое неравенство. В отличие от присваивания =, сравнение === не меняет значение переменной. Строгое сравнение не преобразует типы: 3 === "3" даст false. В упражнениях используйте === и !==. Нестрогие == и != могут скрыть ошибку типов.

Конструкция if выполняет блок в фигурных скобках, если условие истинно. Следующая ветка else if проверяется, только если предыдущая не подошла. Ветка else выполняется, когда не подошло ни одно из условий.

В примере сначала проверяем завершение всех занятий, затем наличие хотя бы одного завершённого. Какое сообщение появится для 0, 3 и 8 завершённых занятий? Меняйте только completed.

Выбор статуса обучения

Код примера
Порядок проверок
Для 0 получится «Ещё не начато», для 3 — «В процессе», для 8 — «Завершено». Если первым поставить условие completed > 0, значение 8 тоже попадёт в эту ветку, и проверка полного завершения не выполнится. Более частный случай проверяем раньше общего. В этом примере total — положительное целое число, а completed — целое число от 0 до total.

Логические операторы объединяют условия: && означает «и», || — «или», ! — отрицание. Например, completed >= 0 && completed <= total проверяет сразу две границы. Выражение !Number.isInteger(completed) истинно, если значение не является целым числом.

У let и const блочная область видимости: имя, объявленное внутри блока { ... } условия, недоступно снаружи. Если статус нужен после условия, объявите переменную до if, а внутри веток присваивайте ей значения.

const completed = 3;
let status;

if (completed > 0) {
    status = "В процессе";
} else {
    status = "Ещё не начато";
}

console.log(status); // В процессе

В script.js добавьте все три статуса из интерактивного примера, используя свои completedLessons и totalLessons. Проверьте начало, промежуточное состояние и завершение. Сохраните вывод одного итогового статуса после условия.

Функции: параметры и результат

Функция объединяет действия, которые можно повторять с разными данными. После function записывают имя, в круглых скобках — параметры, внутри фигурных — тело. При вызове аргументы подставляются на места параметров. Объявление функции само по себе её тело не выполняет.

function getRemaining(total, completed) {
    return total - completed;
}

const remaining = getRemaining(8, 3);
console.log(remaining); // 5
console.log(getRemaining(5, 5)); // 0

Оператор return завершает вызов и возвращает значение в место вызова. console.log только показывает результат: он не заменяет return. Если выполнение функции закончилось без возврата значения, результатом будет undefined. Параметры и переменные, объявленные внутри функции, доступны в её теле. Обращаться к ним из внешнего кода нельзя.

Подготовим функцию расчёта прогресса. Для неё допустимы целое total > 0 и целое completed от 0 до total. Для остальных значений вернём null, чтобы отличить ошибку входных данных от корректных 0%.

Расчёт прогресса с проверкой данных

Код примера

Проверки находятся перед делением. Ранний return null не позволяет дойти до вычисления при неподходящих данных. Условия с || читаются как «неверен хотя бы один параметр». Сначала предскажите, затем проверьте вызовы getProgress(8, 8), getProgress(8, -1) и getProgress(8, "3").

Ноль процентов и отсутствие результата
Результаты этих вызовов — 100, null и null. Строка "3" не проходит проверку Number.isInteger. Вызов getProgress(8, 0) вернёт корректный 0. Проверяйте ошибку через result === null, а не через !result: логическое отрицание даст true и для null, и для числа 0.

Перенесите getProgress в script.js, разместив объявление перед использованием. Замените прямой расчёт progressPercent вызовом этой функции. При null выводите сообщение «Проверьте количество занятий», а при корректном результате — процент и статус. Проверку результата поставьте перед выбором статуса, чтобы некорректные данные не получали сообщение «В процессе».

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

const formatPercent = (value) => `${value}%`;
console.log(formatPercent(50)); // 50%

Справа от => стоит выражение, результат которого возвращается автоматически. Если вместо выражения записать блок { ... }, для возврата значения понадобится return. Запись formatPercent обозначает саму функцию, а formatPercent(50) вызывает её и даёт строку "50%". В аргумент другой функции можно передать как саму функцию, так и результат её вызова — это разные значения.

Практика в JS Hero

В JS Hero можно писать небольшие функции и проверять их тестами прямо в браузере. Задания доступны бесплатно без регистрации. Условия на английском. Выполните восемь упражнений по ссылкам в таблице.

Упражнение Что требуется
10. Parameters Написать функцию, которая возвращает переданный аргумент
30. Fahrenheit Перевести температуру из градусов Цельсия в градусы Фаренгейта по формуле F = 1.8 * C + 32
42. Strict equality Вернуть результат строгого сравнения двух значений
44. Even numbers Проверить чётность числа с помощью остатка от деления
46. Compare numbers Проверить, что число не меньше 100 и меньше 1000
47. if Вернуть строку "EQUAL" для равных значений и "UNEQUAL" для остальных
49. if…else Сложить две суммы, добавив к каждой надбавку: 1 при значении до 10 включительно, иначе 2
50. else if Сложить две суммы с надбавкой к каждой: 1 до 10 включительно, 2 свыше 10 до 20 включительно, иначе 3

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

Для каждой задачи добейтесь сообщения All tests passed!. Сохраните решения в папке website/training/jshero/, каждое в отдельном файле с номером упражнения, например 10-parameters.js. В нескольких задачах требуются одинаковые имена функций, поэтому не объединяйте их в один запускаемый скрипт. Прогресс JS Hero хранится в браузере и может исчезнуть после очистки его данных.

Проверьте, можете ли вы объяснить работу двух своих решений: одного с вычислением и одного с условием. Для задачи с диапазоном отдельно проверьте числа 99, 100, 999 и 1000. Для надбавок — значения на границах 10 и 20.

Часть 2. Обработка данных

Объекты: данные одного участника

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

const profile = {
    name: "Анна",
    totalLessons: 8,
    completedLessons: 3
};

console.log(profile.name); // Анна
profile.completedLessons = 4;
console.log(profile.completedLessons); // 4

const запрещает присвоить переменной другой объект, но разрешает менять свойства существующего. Запись profile.completedLessons = 4 меняет свойство. Запись profile = { ... } была бы повторным присваиванием самой переменной и вызвала бы ошибку.

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

const profile = { name: "Анна", completedLessons: 3 };
const field = "completedLessons";
console.log(profile[field]); // 3
console.log(profile.city);   // undefined: такого свойства нет

Функция, доступная через свойство, называется методом. Например, console.log(...) и Number.isInteger(...) — вызовы методов.

В script.js объедините имя и оба количества в объект profile. Замените обращения к отдельным исходным переменным на profile.name, profile.totalLessons и profile.completedLessons. Старые объявления исходных данных удалите, чтобы значения хранились в одном месте. Вызов расчёта теперь выглядит как getProgress(profile.totalLessons, profile.completedLessons).

Почему присваивание объекта не создаёт копию
const original = { name: "Анна" };
const sameProfile = original;
sameProfile.name = "Борис";
console.log(original.name); // Борис

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

Массивы: список учебных тем

Массив хранит упорядоченный список. Значения записываются в квадратных скобках через запятую. Индексы элементов начинаются с нуля, а свойство length содержит длину массива.

const topics = ["HTML", "CSS", "JavaScript"];
console.log(topics[0]);     // HTML
console.log(topics[2]);     // JavaScript
console.log(topics.length); // 3
console.log(topics[3]);     // undefined: элемента с таким индексом нет

Метод push(...) добавляет элемент в конец, pop() удаляет и возвращает последний элемент, includes(...) проверяет наличие значения. Массив является объектом: при объявлении через const его содержимое тоже можно менять. Присваивание массива другому имени копирует ссылку: оба имени будут обращаться к одному массиву.

Сколько элементов будет в массиве после добавления темы? Проверьте ответ, затем добавьте ещё одну тему и выведите последний элемент через индекс topics.length - 1.

Добавление темы в массив

Код примера

Массив может содержать объекты. Для каждой темы визитки сохраним название и количество занятий:

const topics = [
    { title: "HTML", total: 4, completed: 4 },
    { title: "CSS", total: 6, completed: 3 },
    { title: "JavaScript", total: 5, completed: 0 }
];

console.log(topics[1].title);     // CSS
console.log(topics[1].completed); // 3

В script.js добавьте этот массив. У объекта profile оставьте только имя: общие количества теперь будем получать из тем, чтобы не хранить противоречащие друг другу данные. Уберите прежние вычисления остатка и процента, выбор статуса и итоговое сообщение, которые обращались к отдельным количествам в profile. Функцию getProgress сохраните. Выведите названия первой и последней темы. Для расчёта общей сводки нужно сложить количества занятий всех тем.

Циклы: повторение действий

Цикл for...of перебирает значения массива по порядку. На каждом шаге переменная topic получает очередной объект, а тело цикла выполняется для него. Свойство completed читается так же, как у отдельного объекта.

Добавьте после массива topics в script.js:

let totalLessons = 0;
let completedLessons = 0;

for (const topic of topics) {
    totalLessons += topic.total;
    completedLessons += topic.completed;
    console.log(`${topic.title}: ${topic.completed} из ${topic.total}`);
}

console.log(totalLessons, completedLessons); // 15 7

Накопители объявлены до цикла, поэтому сохраняют сумму между шагами. Если обнулять их внутри тела, предыдущий результат будет теряться. const topic создаётся заново для каждого шага. Это не повторное присваивание одной константе. После цикла имя topic недоступно.

С помощью getProgress рассчитайте общий процент из полученных сумм. Для отображения используйте Math.round(...): этот метод округляет число до ближайшего целого. Например, Math.round(46.666666666666664) вернёт 47. Округляйте итоговую долю, а не проценты отдельных тем. Перед округлением проверьте результат на null.

Когда нужно пройти последовательность чисел, удобен обычный for:

for (let lesson = 1; lesson <= 3; lesson += 1) {
    console.log(`Занятие ${lesson}`);
}

В скобках три части: начальное значение, условие продолжения и изменение счётчика после каждого шага. Здесь выведутся занятия 1, 2 и 3. Предположите, сколько сообщений останется при замене <= на <, затем проверьте. Чтобы цикл закончился, счётчик должен приближаться к границе условия.

Другие способы управлять циклом

while повторяет тело, пока условие истинно. Например, этот код выводит 3, 2, 1:

let remaining = 3;
while (remaining > 0) {
    console.log(remaining);
    remaining -= 1;
}

Без уменьшения remaining цикл не остановится и может заблокировать вкладку. break завершает цикл досрочно, а continue пропускает оставшуюся часть текущего шага. В обычном for после continue сначала выполняется изменение счётчика, затем проверяется условие. В while переход идёт сразу к условию, поэтому изменение счётчика нельзя случайно пропустить.

Самостоятельная работа: сводка учебного прогресса

Перенесите накопление сумм в функцию summarizeTopics(topics). Она получает массив объектов с полями title, total, completed и возвращает новый объект с полями total, completed, remaining, percent, status.

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

  1. Для пустого массива верните total: 0, completed: 0, remaining: 0, percent: null, status: "Нет тем". Проверить пустоту можно условием topics.length === 0.
  2. Сложите total и completed всех тем с помощью for...of.
  3. Вычислите remaining как разность общих количеств. Получите процент через getProgress, затем округлите его через Math.round.
  4. Выберите статус по количествам: «Ещё не начато», «В процессе» или «Завершено». Не определяйте завершение по округлённому проценту: 199 из 200 округлятся до 100%, хотя одно занятие ещё осталось.
  5. Верните объект, а вывод сделайте снаружи функции. Вызовите её для своего массива и сформируйте сообщение с profile.name. При percent === null покажите статус без процента.

Для исходных трёх тем результат должен выглядеть так:

Свойство Значение
total 15
completed 7
remaining 8
percent 47
status "В процессе"

Пример сообщения: «Анна: завершено 7 из 15 занятий (47%). Осталось: 8. В процессе».

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

Входной массив Ожидаемый результат
[] Нулевые количества, percent: null, «Нет тем»
[{ title: "HTML", total: 4, completed: 0 }] total: 4, completed: 0, remaining: 4, percent: 0, «Ещё не начато»
[{ title: "HTML", total: 4, completed: 4 }] total: 4, completed: 4, remaining: 0, percent: 100, «Завершено»
[{ title: "Практика", total: 200, completed: 199 }] remaining: 1, percent: 100, «В процессе»

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

Финальная практика в CodinGame

В CodinGame программа управляет действиями на игровой сцене. Задания бесплатны. Для сохранения прогресса используйте бесплатную учётную запись. Откройте задачу кнопкой Solve it и выберите язык JavaScript.

Чтение данных и игровой ход

Редактор содержит начальный код, который получает данные от игры. Функция readline() читает следующую строку входных данных и возвращает строку. Это функция среды CodinGame: в обычном браузерном скрипте она недоступна.

Для преобразования строки с числом можно использовать знакомый Number(...). В начальном коде также встречается parseInt(readline()): parseInt извлекает целое число из начала строки. Например, parseInt("12", 10) вернёт 12. Второй аргумент явно указывает десятичную систему счисления. Входные данные игры имеют формат, описанный в условии, поэтому сохраняйте порядок их чтения.

Внешний цикл while (true) повторяет действия на каждом игровом ходу: читает новые данные, вычисляет решение и отправляет ответ. Условие true не завершает такой цикл. Выполнение ограничивает игровая среда. Запускайте этот код в CodinGame.

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

Onboarding: выбор цели

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

  1. Найдите в начальном коде переменные с именами целей и расстояниями. Убедитесь, что расстояния преобразованы в числа.
  2. Сравните расстояния через if…else и выведите имя выбранной цели. За один ход должен появиться ровно один ответ.
  3. Запустите доступные тесты и посмотрите, какую цель выбирает пушка. При ошибке проверьте типы расстояний и то, что выводится имя, а не само расстояние.
  4. Отправьте решение на проверку средствами платформы и убедитесь, что оно принято.

Для самопроверки рассмотрите два хода: при расстояниях 10 и 20 должна выбираться первая цель, при 30 и 5 — вторая. Код должен принимать решение заново при каждом чтении данных.

The Descent: поиск самой высокой горы

Откройте The Descent. Перед кораблём расположены восемь гор с номерами от 0 до 7. На каждом ходу игра передаёт их высоты. Найдите самую высокую гору и выведите её номер, чтобы корабль выстрелил по ней.

  1. Перед обходом высот подготовьте переменные для наибольшей высоты и номера соответствующей горы. Объявите их внутри внешнего игрового цикла, чтобы поиск начинался заново на каждом ходу.
  2. Прочитайте все восемь высот в цикле for. Первую гору примите за текущий максимум. Сравнивая остальные, обновляйте и высоту, и номер, когда находите гору выше.
  3. После чтения всех высот выведите один номер. Вывод внутри цикла обхода отправит лишние ответы.
  4. Пройдите доступные тесты и отправьте решение на проверку. Убедитесь, что после выстрела программа использует новые высоты.

Для высот [1, 3, 2, 4, 5, 6, 7, 8] нужно вывести 7, а для [9, 8, 7, 6, 5, 4, 3, 2] — 0. Массив здесь показывает порядок входных значений: в самой игре высоты поступают отдельными строками.

Сохраните принятые решения в website/training/codingame/onboarding.js и website/training/codingame/the-descent.js, а также снимки результатов проверки. Эти файлы предназначены для запуска в CodinGame. Подключать их к index.html не нужно. Подготовьтесь объяснить выбор цели, обновление максимума и расположение вывода относительно циклов.

Проверка и требования к работе

Откройте index.html и practice.html по очереди. Очистите консоль, обновите страницу и проверьте сообщения текущего запуска. Исправляйте первую ошибку до проверки последующих результатов. Если вычисление неверно, временно выводите его входные данные и промежуточные суммы через console.log.

Для коротких проверок можно использовать console.assert(условие, "сообщение"): при ложном условии в консоли появится сообщение об ошибке проверки. Например, после объявления getProgress:

console.assert(getProgress(8, 0) === 0, "Ноль занятий должен давать 0%");
console.assert(getProgress(0, 0) === null, "Нулевое общее количество недопустимо");
console.assert(getProgress(8, 9) === null, "Нельзя завершить больше занятий, чем всего");
console.assert(getProgress(8, "3") === null, "Строка не является числом занятий");

Если код выполнился до конца и сообщения об ошибках проверки не появились, все четыре условия истинны. Дополните их случаями из таблиц для своих функций. Объекты и массивы проверяйте по свойствам, длине и отдельным элементам: два отдельно созданных объекта не равны через ===, даже если содержат одинаковые данные.

  • Визитка открывается с сохранёнными стилями, фотографией и работающей навигацией.
  • script.js подключён к index.html, practice.js — к practice.html. В консоли нет необработанных ошибок.
  • Имя хранится в profile, данные по трём или более темам — в массиве объектов topics.
  • getProgress проверяет параметры и различает корректные 0% и ошибку null.
  • summarizeTopics использует цикл, возвращает объект и не изменяет входные темы.
  • Сообщение содержит имя, завершённые и общие занятия, остаток, процент и статус. Пустой список обработан отдельно.
  • Статус определяется по количествам, а округление используется только для процента.
  • Сохранены проверочные вызовы getProgress и summarizeTopics для обычных и граничных случаев, результат можно объяснить.
  • Все восемь упражнений JS Hero проходят тесты. Решения сохранены отдельными файлами в training/jshero/.
  • В CodinGame приняты решения Onboarding и The Descent на JavaScript. Код и снимки результатов проверки сохранены в training/codingame/.
  • Можно объяснить, какие данные читаются и какой ответ отправляется на каждом игровом ходу.
  • Выполнены любые две задачи из раздела «Задачи на выбор». Решения и проверки сохранены в practice.js, результаты соответствуют условиям.

Для сдачи сохраните всю папку website, включая HTML, CSS, изображения, файлы script.js и practice.js, а также папку training с решениями тренажёров и результатами проверки. Функции и проверочные вызовы должны находиться в файлах, а не только в истории консоли.

Задачи на выбор

Обязательно выполните любые две из четырёх задач: FizzBuzz++, таблицу лидеров, топ слов или слияние массивов со статистикой. Для каждой выбранной задачи выполните основное условие и проверьте обычные и граничные случаи. Решения и проверочные вызовы сохраните в practice.js. Блоки «Усложнение» не входят в обязательный минимум.

FizzBuzz++

Создайте функцию fizzbuzzPlus(n) в practice.js. Вход n должен быть целым числом от 1. Иначе функция возвращает null. Для проверки используйте Number.isInteger.

Для каждого числа от 1 до n добавьте в массив sequence строку:

  • "FizzBuzz", если число делится и на 3, и на 5.
  • "Fizz", если оно делится только на 3.
  • "Buzz", если оно делится только на 5.
  • строковое представление самого числа в остальных случаях.

Верните объект с двумя свойствами: sequence и counts. В counts храните отдельные счётчики fizz, buzz, fizzbuzz. Число, давшее "FizzBuzz", увеличивает только fizzbuzz.

Начните с пустого массива и объекта с нулевыми счётчиками. Используйте for, %, условия, push и String. Проверяйте общий случай делимости на 3 и 5 раньше отдельных делителей. Для возврата уже созданных переменных можно написать return { sequence: sequence, counts: counts };.

Вызов Что проверить
fizzbuzzPlus(1) sequence: ["1"], все счётчики нулевые
fizzbuzzPlus(5) sequence: ["1", "2", "Fizz", "4", "Buzz"], счётчики 1, 1, 0
fizzbuzzPlus(15) Длина 15, последний элемент "FizzBuzz", счётчики 4, 2, 1
fizzbuzzPlus(0), fizzbuzzPlus(2.5), fizzbuzzPlus("5") null

Для обработки текста и упорядочивания результатов понадобятся операции со строками, словарями и массивами:

Приём Что делает
text.trim() Возвращает строку без пробелов по краям
text.split(";") Разбивает строку по разделителю и возвращает массив
text.toLowerCase() Возвращает строку в нижнем регистре
Object.create(null) Создаёт словарь без унаследованных свойств. Имена вроде constructor не конфликтуют с готовыми свойствами
Object.entries(dictionary) Возвращает массив пар [ключ, значение]. К ним можно обращаться через pair[0] и pair[1]
items.slice(0, n) Возвращает новый массив из первых n элементов
items.sort(compare) Сортирует сам массив по функции сравнения
a.localeCompare(b, "ru") Сравнивает строки по правилам русского языка, возвращая отрицательное число, ноль или положительное число

Функция сравнения получает два элемента: отрицательный результат ставит первый раньше второго, положительный — позже, ноль оставляет их равными для сортировки. Например, numbers.sort((a, b) => b - a) сортирует числа по убыванию. Без функции сравнения sort() сравнивает строковые представления, поэтому не подходит для сортировки баллов как чисел.

Таблица лидеров из строки

Реализуйте leaderboard(input, k = null). Запись k = null задаёт значение параметра по умолчанию: оно используется, когда второй аргумент не передан или равен undefined. Считайте, что input — строка, а k — null или целое неотрицательное число.

Дана строка "Аня:10; Борис:7; Ли:12". Разбейте её на пары через split(";"), затем каждую пару через split(":"). Принимайте только фрагменты ровно из двух частей, с непустыми после trim() именем и баллом. Преобразуйте балл через Number, затем проверьте, что он целый и неотрицательный. Некорректные пары пропускайте. Если имя повторяется, учитывайте последнее корректное значение.

Верните объект с полями all и top. all — словарь «имя: балл», созданный через Object.create(null). top — массив объектов с полями name и score, отсортированный по убыванию балла, при равенстве — по name.localeCompare(..., "ru"). Создайте массив из Object.entries(all) с помощью цикла. При k === null оставьте весь список, иначе верните первые k элементов. Для k = 0 список top пуст, но all сохраняет все результаты.

Для leaderboard("Аня:10; Борис:7; Ли:12", 2) в top должны оказаться Ли с 12 баллами и Аня с 10. Проверьте пустую строку, повтор имени, пустой балл, лишнее двоеточие и имя constructor.

Усложнение: добавьте summary с количеством участников, суммой, средним, минимумом и максимумом по итоговому словарю после обработки повторов. Для пустого словаря сумма и количество равны 0, остальные показатели — null.

Топ слов в тексте

Реализуйте topNWords(text, n): верните первые n пар [слово, количество]. Считайте, что text — строка, n — целое неотрицательное число. Слово — непрерывная последовательность латинских букв, русских букв (включая ё) и цифр. Остальные символы разделяют слова. Регистр не учитывается.

Приведите текст к нижнему регистру. Строку можно перебирать через for...of, как массив. Напишите isWordChar(ch), проверяющую диапазоны a–z, а–я, 0–9 и отдельно ё. Собирайте слово в строке currentWord. При разделителе сохраняйте непустое слово и очищайте накопитель. После цикла обработайте оставшееся слово: текст может закончиться без разделителя.

Считайте частоты в Object.create(null). Если counts[word] === undefined, начните с 1, иначе увеличьте счётчик. Преобразуйте словарь через Object.entries, отсортируйте по убыванию частоты, при равенстве — по слову через localeCompare с локалью "ru". Возьмите первые n пар.

Для topNWords("Привет, мир! Привет мир — мир.", 2) результат — [["мир", 3], ["привет", 2]]. Для пустого текста или n = 0 — []. Если n больше числа слов, верните все пары. Проверьте "Ёж ёж", "Tea TEA tea", последнее слово без точки и слово constructor.

Усложнение: добавьте массив stopWords, слова из которого пропускаются при подсчёте. Сравнивайте их в том же нижнем регистре.

Слияние массивов и статистика

Реализуйте mergeStats(...arrays). Запись ...arrays в параметрах собирает все аргументы в массив arrays. Каждый аргумент считайте массивом, элементы которого могут быть произвольными значениями.

Двумя вложенными циклами обойдите массивы слева направо. Сохраните в unique только конечные числа, в порядке первого появления, без повторов. Для проверки используйте Number.isFinite и includes. Строки, даже состоящие из цифр, не преобразовывайте.

Верните объект с unique, sum, avg, min, max. Рассчитайте показатели по уникальным числам. Для непустого списка начните минимум и максимум с первого элемента, а сумму с 0. Для пустого списка зафиксируем результат: unique: [], sum: 0, avg: null, min: null, max: null.

Для mergeStats([1, 2, 2], [2, 3, -1, NaN]) ожидаются unique: [1, 2, 3, -1], сумма 5, среднее 1.25, минимум −1 и максимум 3. Проверьте вызов без аргументов, пустые массивы, только некорректные значения, отрицательные числа и ноль.

Усложнение: попробуйте объединять массивы объектов, определяя уникальность по одному свойству, например id.

Полезные источники и инструменты