Введение в JavaScript

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

JS является интерпретируемым языком (выполняется без предварительной компиляции непосредственно в среде выполнения — браузере). Современные браузеры содержат встроенные JavaScript-движки, которые исполняют код JS. Также благодаря платформе Node.js JavaScript может выполняться вне браузера (на сервере или даже на компьютере в качестве скриптового языка). Однако в контексте веб-программирования основной областью применения JS остаётся фронтенд — код, работающий в браузере пользователя для улучшения взаимодействия с веб-страницей.

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

Для более глубокого изучения JavaScript можно обратиться к онлайн-учебнику «Современный JavaScript» , который регулярно обновляется и содержит множество примеров (доступен на русском языке).

Как подключить JavaScript к странице

Чтобы браузер выполнил ваш JavaScript-код, его необходимо подключить к HTML-странице. Это делается при помощи тега <script>. Существует два основных способа:

  1. Встроенный сценарий: разместить код JS непосредственно внутри тегов <script> в HTML-файле. При встроенном использовании код помещается так:
    <script>
    console.log("Привет из JS!");
    </script>
  2. Внешний файл: вынести код JS в отдельный файл с расширением .js и подключить его с помощью тега <script src="путь_к_файлу.js"></script>.Пример подключения внешнего файла:
    <!-- Подключение внешнего файла script.js -->
    <script src="script.js"></script>

Обычно внешний файл предпочтительнее, так как позволяет разделять логику (JS) и структуру страницы (HTML), облегчает поддержку кода и повторное использование.

Важно учитывать расположение тега <script> в документе. Если скрипт находится в <head> или в начале HTML, он может выполниться до того, как элементы страницы созданы, что приведёт к ошибкам при попытке к ним обратиться. Поэтому распространённой практикой является включать тег <script> в самом конце <body>, чтобы скрипт выполнялся после загрузки HTML-элементов.

Альтернативно можно использовать атрибут defer при подключении внешнего скрипта, чтобы отложить его выполнение до момента, когда HTML-документ будет сформирован браузером.

Для экспериментов используйте вкладку Console в инструментах разработчика (F12).

Переменные и типы данных

В JavaScript переменные используются для хранения данных. Переменную можно объявить одним из трёх ключевых слов:

  • var – устаревший способ объявления, обладающий функциональной областью видимости и поддерживающий всплытие (hoisting). В современном коде обычно не используется.
  • let – современный способ объявления изменяемой переменной с блочной областью видимости (переменная доступна только внутри блока { … }, где объявлена).
  • const – объявление переменной, значение которой не планируется изменять (константа), также с блочной областью видимости.

Примеры:

var oldVar = 10;       // глобальная или функциональная область видимости
let count = 5;         // блочная область видимости, значение можно изменять
const PI = 3.14;       // константа, значение изменить нельзя

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

let answer = 42;
answer = "Спасибо!";  // теперь в переменной хранится строка вместо числа

В момент присвоения нового значения тип переменной автоматически изменится с Number на String.

Примитивные типы

JS оперирует несколькими типами данных. Большинство из них относятся к примитивным (простым) типам:

  • Number – числовой тип для представления как целых, так и дробных чисел. Примеры: 7, 3.14. Также существуют специальные числовые значения Infinity (бесконечность), -Infinity и NaN (Not-a-Number, результат некорректных операций).
  • BigInt – числовой тип для представления целых чисел произвольной точности (введён в ES2020). Позволяет работать с очень большими числами, выходящими за пределы безопасного диапазона типа Number. Литералы типа BigInt записываются с суффиксом n, например: 9007199254740993n.
  • String – строки для хранения текста. Строки задаются в кавычках: одинарных '...', двойных "..." или косых обратных `…`. Примеры: "Привет", 'JavaScript'.
  • Boolean – логический тип, принимающий значение true (истина) или false (ложь). Используется для условных переходов и логических операций.
  • Undefined – специальное значение, автоматически присваиваемое переменной, которая была объявлена, но ей не назначено никакого значения. Также возвращается функциями, у которых нет оператора return.
  • Null – специальное значение «ничего» (является собственным типом). Означает намеренное отсутствие значения.
  • Symbol – примитивный тип для создания уникальных идентификаторов. Каждый созданный символ уникален. Используется, в частности, для ключей свойств объектов, чтобы избежать конфликтов.

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

Рассмотрим пример использования перечисленных типов на небольшом примере:

let age = 20;                        // Number
let name = "Василий";                // String
let isStudent = true;                // Boolean
let undef;                           // Undefined (значение не задано)
let empty = null;                    // Null, значение отсутствует

console.log(name + " — возраст " + age);   // "Василий — возраст 20"
console.log(isStudent ? "Студент" : "Не студент"); // Тернарный оператор -> "Студент"

age = "двадцать";    // переменной age присваивается строка вместо числа
console.log(age);    // "двадцать"

В примере выше используется:

  • Конкатенация (склеивание) строк с другими типами с помощью +. Если хотя бы один из операндов – строка, остальные будут неявно преобразованы к строке.
  • Использование тернарного условного оператора ? для выбора значения на основе логической переменной (isStudent).
  • Динамическая природа типов: переменной age, хранившей число, присваивается строка без ошибок.

Операторы и выражения

Для обработки данных JavaScript предоставляет множество операторов:

  • Арифметические операторы: +, -, *, /, % (остаток от деления), ** (возведение в степень). Они работают как в большинстве языков. Например, 5 + 3 даст 8, 10 % 3 даст 1, а 2 ** 3 равно 8.
  • Операторы присваивания: = (присваивание), а также сочетания с арифметическими: +=, -=, *=, /= для удобного изменения значения переменной. Например, x += 5 эквивалентно x = x + 5.
  • Операторы сравнения: == (нестрогое равенство), === (строгое равенство), != (нестрогое неравенство), !== (строгое неравенство), а также <, >, <=, >=.

    [Интересно] Строгое vs нестрогое сравнение: При использовании == и != перед сравнением JavaScript пытается привести типы сравниваемых значений к одному, что может приводить к неожиданным результатам. Оператор === сравнивает без приведения типов, требуя, чтобы и тип, и значение совпадали. Поэтому выражение "5" == 5 даст true (строка приведётся к числу), а "5" === 5false (разные типы). Как правило, рекомендуется использовать строгие сравнения === и !== во избежание логических ошибок.

  • Логические операторы: && (логическое И), || (логическое ИЛИ), ! (логическое НЕ). Они позволяют объединять или отрицать условия. Например, (a > 0 && b > 0) истинно, если оба числа положительны; (x === 0 || y === 0) истинно, если хотя бы одно из значений равно нулю.
  • Конкатенация строк: оператор + используется для объединения строк. Например, "Hello, " + "world!" даст "Hello, world!".
  • Другие операторы: существуют и другие, например, унарный плюс + (преобразование к числу), оператор typeof (возвращает тип переменной строкой), запятая (,), ?? (nullish-coalescing), ?. (опциональная цепочка) и др. Большинство из них имеет стандартное поведение, знакомое по другим языкам, либо специфично для JS и выходит за рамки базового обзора.

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

let a = 10;
let b = 3;
let c = 5;
let result1 = a + b * c;      // 10 + 3*5 = 25  (умножение выполняется первым)
let result2 = (a + b) * c;    // (10+3)*5 = 65 (скобки изменяют порядок)
console.log(result1, result2);

В первом случае без скобок сначала выполняется 3*5, затем результат складывается с 10. Во втором случае благодаря скобкам сначала вычисляется сумма a+b, и уже потом результат умножается на c.

Управляющие конструкции: условия и циклы

Как и любой другой язык программирования, JavaScript предоставляет конструкции для управления потоком выполнения кода.

Условные операторы

if – базовый условный оператор. Он выполняет блок кода, только если заданное условие истинно (true). Опционально можно указать блок else, который выполнится, если условие ложно. Также можно добавлять промежуточные проверки с помощью else if.

let score = 85;
if (score >= 90) {
  console.log("Отлично!");
} else if (score >= 60) {
  console.log("Хорошо");
} else {
  console.log("Нужно подтянуть знания");
}

В этом примере, если score >= 90, в консоль выведется "Отлично!". Иначе если score >= 60 (но меньше 90) – выведется "Хорошо". Во всех остальных случаях (меньше 60) выполнится ветка "Нужно подтянуть знания".

switch – многовариантный оператор выбора. Удобен, когда нужно сравнить одно значение с несколькими возможными вариантами:

let color = "green";
switch (color) {
  case "red":
    console.log("Красный");
    break;
  case "green":
    console.log("Зелёный");
    break;
  case "blue":
    console.log("Синий");
    break;
  default:
    console.log("Неизвестный цвет");
}

Оператор switch последовательно сравнит значение переменной color со значениями после каждого case. Если совпадение найдено, выполняется соответствующий блок кода до встречи оператора break (который прерывает выполнение switch). Блок default выполняется, если ни один из case не совпал (необязательный, но полезный для обработки «прочих» случаев).

Циклы

Циклы позволяют выполнять повторяющиеся действия (итерации) до тех пор, пока соблюдается определённое условие.

  • for – цикл со счётчиком, наиболее распространённый вариант. Синтаксис: for (начало; условие; шаг) { ... }. Пример:
    for (let i = 1; i <= 5; i++) {
      console.log("Итерация номер " + i);
    }
    Этот цикл выведет в консоль строки "Итерация номер 1", "Итерация номер 2" и т.д. до "Итерация номер 5". Здесь: - начало: let i = 1 – объявляем счётчик i и устанавливаем начальное значение 1. - условие: i <= 5 – цикл будет выполняться, пока это условие истинно. - шаг: i++ – после каждой итерации увеличить i на 1. Существует упрощённый вариант for...of для перебора значений коллекций (например, элементов массива):
    let fruits = ["яблоко", "банан", "вишня"];
    for (let fruit of fruits) {
      console.log("Мне нравится " + fruit);
    }
    Этот цикл пройдётся по каждому элементу массива fruits и выведет строку с названием фрукта. Цикл for...of появился в ES6 и часто удобнее классического цикла с индексом, когда индекс не нужен явно.
  • while – цикл с предусловием. Проверяет условие перед каждой итерацией, и если оно истинно, выполняет тело цикла:
    let n = 5;
    while (n > 0) {
      console.log(n);
      n--;
    }
    console.log("Старт!");
    Данный код выведет числа от 5 до 1, а после выхода из цикла – слово "Старт!". Цикл while продолжает работать, пока условие n > 0 истинно. Внутри мы уменьшаем n, поэтому рано или поздно оно станет 0 и цикл остановится.
    • do...while – цикл с постусловием. Гарантированно выполняет тело хотя бы один раз, а затем проверяет условие:
    let password;
    do {
      password = prompt("Введите пароль (не менее 6 символов):");
    } while (password.length < 6);
    console.log("Пароль принят");
    Здесь тело цикла выполнится как минимум один раз, чтобы запросить пароль у пользователя. После каждой ввода проверяется условие password.length < 6. Если оно истинно (пароль короче 6 символов), цикл повторится и снова запросит ввод. Как только условие станет ложным (пароль достаточно длинный), цикл завершится и выведется "Пароль принят". Во всех циклах можно использовать оператор break для немедленного выхода из цикла, даже если условие ещё истинно. А оператор continue позволяет пропустить текущую итерацию и сразу перейти к следующей (условие цикла будет проверено заново).

Функции

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

function greet(name) {
  console.log("Привет, " + name + "!");
}

greet("Мир");  // Вызов функции с аргументом "Мир"

Здесь определена функция greet с параметром name. При вызове greet("Мир") она выведет в консоль "Привет, Мир!". Переменная name внутри функции при этом обращении получит значение "Мир".

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

function sum(a, b) {
  return a + b;
}

let result = sum(5, 3);
console.log("Сумма:", result);  // Сумма: 8

В этой функции sum складывает параметры a и b и возвращает их сумму. После вызова sum(5, 3) в переменной result окажется значение 8. Если функция не вернула явно какое-то значение, результатом её вызова будет undefined.

Функции как значения

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

const multiply = function(x, y) {
  return x * y;
};
console.log( multiply(4, 5) );  // 20

Здесь объявлена анонимная функция (без имени) и присвоена константе multiply. Вызов multiply(4, 5) даст результат 20, как и ожидалось.

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

Стрелочные функции (arrow functions)

Современный синтаксис для объявления функций — стрелочные функции, появившиеся в ES6. Они позволяют записывать небольшие функции более кратко, чем через function. Пример обычной и стрелочной функции:

// Обычная функция:
function square(x) {
  return x * x;
}

// Стрелочная эквивалент:
const squareArrow = (x) => {
  return x * x;
};

Оба варианта square( ) и squareArrow( ) будут возвращать квадрат аргумента. Разница – в синтаксисе. Стрелочная функция не требует ключевого слова function, вместо этого используется символ => между списком параметров и телом функции.

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

const sumArrow = (a, b) => a + b;
console.log( sumArrow(2, 3) ); // 5

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

Нюанс: Стрелочные функции имеют особенность – у них нет своего собственного this. Значение this внутри стрелочной функции берётся из внешнего контекста.

Объекты

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

let person = {
  name: "Иван",
  age: 25,
  "likes javascript": true  // ключ, содержащий пробелы, заключён в кавычки
};

Здесь создан объект person с тремя свойствами:

  • ключ "name" со значением "Иван",
  • ключ "age" со значением 25,
  • ключ "likes javascript" со значением true. Для доступа к свойствам используются два синтаксиса:
  • Через точку: person.name вернёт "Иван".
  • Через квадратные скобки: person["age"] вернёт 25. Этот способ полезен, когда имя свойства хранится в переменной или не является корректным идентификатором (как "likes javascript" в примере).

Добавлять и изменять свойства можно так же легко:

person.age = 26;              // изменили существующее свойство
person.city = "Москва";       // добавили новое свойство city

Удалить свойство можно оператором delete:

delete person["likes javascript"];

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

let calculator = {
  x: 5,
  y: 3,
  sum: function() {
    return this.x + this.y;
  }
};

console.log( calculator.sum() ); // 8

Здесь у объекта calculator есть метод sum, который возвращает сумму свойств x и y объекта. Ключевое слово this внутри метода ссылается на сам объект (calculator), что позволяет использовать его свойства.

Стоит упомянуть, что JavaScript основан на прототипном наследовании: у каждого объекта есть прототип (другой объект), от которого он наследует свойства. Но в ES6 был введён синтаксический сахар в виде классов, который упрощает работу с прототипами.

Массивы

Массив – это особый вид объекта для хранения упорядоченных значений. Каждый элемент массива имеет номер (индекс), начиная с нуля. Самый простой способ создать массив – использовать литерал массива []:

let fruits = ["яблоко", "банан", "вишня"];

Здесь массив fruits содержит три элемента. Индексы:

  • fruits[0] = "яблоко"
  • fruits[1] = "банан"
  • fruits[2] = "вишня"

Длину массива (количество элементов) можно узнать через свойство .length:

console.log( fruits.length );  // 3

Массивы в JavaScript динамические — их размер не фиксирован. Можно добавлять и удалять элементы на лету:

  • fruits.push("груша") – добавить элемент в конец массива.
  • fruits.pop() – удалить последний элемент и вернуть его.
  • fruits.shift() – удалить первый элемент.
  • fruits.unshift("апельсин") – добавить элемент в начало.

Например, выполнив fruits.push("груша"), массив станет ["яблоко", "банан", "вишня", "груша"], а свойство length изменится на 4. Перебор массива:

for (let i = 0; i < fruits.length; i++) {
  console.log(fruits[i]);
}

Или с использованием современного цикла for...of, как упоминалось ранее:

for (let fruit of fruits) {
  console.log(fruit);
}

Массивы имеют множество встроенных методов: slice() (копирование части массива), indexOf() (поиск индекса элемента), includes() (проверка наличия элемента), join() (объединение элементов в строку) и др. Эти методы облегчают работу со списками данных. С полным списком этих методов можно ознакомиться в официальной документации MDN по массивам (которую я вам уже рекомендовал ранее)

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

Современные возможности JavaScript (ES6+)

В 2015 году был принят стандарт ECMAScript 6 (ES6), привнёсший множество улучшений в язык. Далее ежегодно выходили новые версии (ES7, ES8, … ES2020), расширяющие возможности JS. Рассмотрим некоторые ключевые особенности современного JavaScript, которые важно знать новичкам:

Шаблонные строки – новая форма записи строк, заключённых в обратные кавычки .... Позволяет вставлять переменные и выражения внутрь строки с помощью конструкции ${...}. Это гораздо удобнее конкатенации:

let user = "Иван";
let points = 10;
console.log(`Привет, ${user}! У вас ${points} баллов.`); 
// Выведет: "Привет, Иван! У вас 10 баллов."

Шаблонные строки могут занимать несколько строк и сохранять все переносы и пробелы, что делает их полезными для создания многострочного HTML-шаблона прямо в JS.


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

// Деструктуризация массива
const coords = [10, 20, 30];
const [x, y, z] = coords;
console.log(x, y, z); // 10 20 30

// Деструктуризация объекта
const options = { title: "Меню", width: 300, height: 200 };
const { title, width, height } = options;
console.log(title, width, height); // "Меню", 300, 200

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


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

function greet(name = "гость") {
  console.log("Привет, " + name);
}
greet();          // "Привет, гость"
greet("Саша");    // "Привет, Саша"

Раньше приходилось писать внутри функции проверки if (name === undefined) name = "гость";, теперь это не нужно.


Оператор расширения (spread) и оставшихся параметров (rest) – это оператор ..., который применяется в разных ситуациях:

  • Spread («распыление»): позволяет развернуть массив или объект там, где ожидаются отдельные значения. Примеры:
    let arr1 = [1, 2, 3];
    let arr2 = [...arr1, 4, 5]; 
    console.log(arr2); // [1, 2, 3, 4, 5]
    
    let obj1 = {a: 1, b: 2};
    let obj2 = {...obj1, c: 3};
    console.log(obj2); // {a: 1, b: 2, c: 3}
    
    Здесь ...arr1 вставляет все элементы arr1 в новый массив, а ...obj1 вставляет все свойства obj1 в новый объект.
  • Rest («остаток»): позволяет функции собрать произвольное число аргументов в массив. Пример:
    function maxValue(...numbers) {
      let max = -Infinity;
      for (let num of numbers) {
        if (num > max) max = num;
      }
      return max;
    }
    console.log( maxValue(5, 10, 8, 7) ); // 10
    
    Оператор ...numbers в определении функции собирает все переданные аргументы в массив numbers. Внутри можно обращаться к numbers как к обычному массиву всех аргументов.

Модули – до ES6 в браузере не было стандартного способа организации модулей, приходилось использовать глобальные переменные или такие инструменты как AMD/RequireJS. В ES6 появилась поддержка модулей на уровне языка.Можно обозначить файл как модуль, импортируя/экспортируя из него переменные и функции. Например, создадим файл module.js:

// module.js
export function square(x) {
  return x * x;
}

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

// main.js
import { square } from './module.js';
console.log( square(4) ); // 16

В HTML нужно подключить <script type="module" src="main.js"></script>. Браузер автоматически загрузит module.js при встрече import. Модули помогают структурировать код, избегать глобальных переменных и подключать только то, что нужно.

Для того чтобы потренироваться и закрепить базовые основы JS можно воспользоваться тренажерами: JS Hero и Learn JS .

Практические задачи

FizzBuzz++

Реализуйте: function fizzbuzzPlus(n) -> { sequence: string[], counts: { fizz: number, buzz: number, fizzbuzz: number } }

Дано целое n ≥ 1. Постройте массив строк sequence длины n, где для каждого i (1…n):

  • "FizzBuzz", если i делится на 3 и на 5;
  • "Fizz", если делится только на 3;
  • "Buzz", если делится только на 5;
  • иначе — текстовое представление числа i.

Дополнительно верните объект counts — сколько раз встретились "Fizz", "Buzz" и "FizzBuzz".

Пример 1 Вход: n = 5 Выход: { sequence: ["1","2","Fizz","4","Buzz"], counts: { fizz: 1, buzz: 1, fizzbuzz: 0 } }

Пример 2 Вход: n = 15 Выход (только счётчики): { fizz: 4, buzz: 2, fizzbuzz: 1 }

Примечания и подсказки

  • Ориентир по шагам:

    1. создайте counts = { fizz:0, buzz:0, fizzbuzz:0 } и пустой sequence;
    2. идите циклом for от 1 до n;
    3. сначала удобно проверить «оба делителя» (i % 15 === 0), затем отдельно 3 и 5;
    4. в sequence кладите строку (String(i)), а в counts увеличивайте нужный счётчик.
  • Проверьте крайние случаи: n = 1, n = 3, n = 5, n = 15.

  • При некорректном n (не целое/≤0) бросайте понятную ошибку.

Follow-up Сделайте гибкую версию: fizzbuzzPlus(n, rules), где rules = [{ div: 3, word: "Fizz" }, { div: 5, word: "Buzz" }]. Для каждого i формируйте строку, конкатенируя word всех сработавших правил в порядке правил, и считайте частоты для каждого слова.

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

Реализуйте: function leaderboard(input, k = null) -> { top: { name:string, score:number }[], all: Record<string, number> }

Дана строка с парами вида "Имя:Балл", разделёнными ;. Пробелы до/после имени и балла можно игнорировать. Пример корректного формата: "Аня:10; Борис:7; Ли:12".

Требуется:

  1. распарсить строку в объект all, где ключ — имя, значение — целый неотрицательный балл;
  2. составить top — массив { name, score }, отсортированный по score по убыванию; при равенстве — по имени в алфавитном порядке;
  3. если задано k, вернуть только первые k записей.

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

Пример 1 Вход: input = "Аня:10; Борис:7; Ли:12", k = 2 Выход: { top: [ {name:"Ли",score:12}, {name:"Аня",score:10} ], all: { "Аня":10, "Борис":7, "Ли":12 } }

Пример 2 Вход: input = "Max:5; Max:10; Ann:10" Выход: top = [ {name:"Ann",score:10}, {name:"Max",score:10} ], all = { "Max":10, "Ann":10 }.

Примечания и подсказки

  • Ориентир по шагам:

    1. trim() всей строки → split(";");
    2. для каждого фрагмента: split(":"), отдельно trim() имени и балла;
    3. переведите балл в число и проверьте, что это целое ≥ 0;
    4. сохраните в all[name] = score (так «последнее значение выигрывает»);
    5. Object.entries(all) → отсортируйте: сначала по score убыв., при равенстве — name.localeCompare(...);
    6. соберите top из первых k (если задан).
  • Тест-кейсы: пустая строка; лишняя ; в конце; имена с пробелами; повторяющиеся имена.

Follow-up Добавьте сводку summary = { total, avg, min, max, count } только по корректным парам.

Топ-N слов в тексте (базовая версия)

Реализуйте: function topNWords(text, n) -> Array<[word:string, count:number]>

По данному тексту верните массив из n наиболее частых слов без учёта регистра. Словом считаем последовательность букв и/или цифр (поддержите хотя бы латиницу и кириллицу). Если частоты равны — сортируйте такие слова по алфавиту.

Пример 1 Вход: text = "Привет, мир! Привет мир — мир.", n = 2 Выход: [ ["мир", 3], ["привет", 2] ]

Пример 2 Вход: text = "Tea, TEA? tea!!! coffee.", n = 1 Выход: [ ["tea", 3] ]

Примечания и подсказки

  • Ориентир по шагам:

    1. приведите text к нижнему регистру;
    2. пройдитесь по строке посимвольно, собирая «буквенно-цифровые» символы в currentWord;
    3. при встрече разделителя (пробел/пунктуация) — если currentWord не пуст, добавьте его в массив слов и обнулите сборку;
    4. посчитайте частоты в обычном объекте: counts[word] = (counts[word] || 0) + 1;
    5. преобразуйте counts в массив пар [word, count], отсортируйте по count убыв., при равенстве — по слову;
    6. верните первые n.
  • Полезно завести функцию isWordChar(ch) для диапазонов a..z, A..Z, а..я, А..Я, ё, Ё, 0..9.

  • Проверьте случаи: пустой текст; n больше числа уникальных слов; смешанный регистр.

Follow-up Добавьте параметр stopWords — массив слов, которые нужно игнорировать (например, «и», «в», «на») перед подсчётом частот.

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

Реализуйте: function mergeStats(...arrays) -> { unique:number[], sum:number, avg:number, min:number, max:number }

На вход приходит несколько массивов чисел. Постройте массив unique, добавляя элементы по порядку их первого появления во всех входных массивах слева направо. Любые значения, не являющиеся конечными числами (NaN, Infinity, строки и т. п.), игнорируйте. Затем посчитайте по unique: сумму sum, среднее avg, минимум min и максимум max.

Пример 1 Вход: mergeStats([1,2,2], [2,3,-1, NaN]) Выход: { unique:[1,2,3,-1], sum:5, avg:1.25, min:-1, max:3 }

Пример 2 Вход: mergeStats([], [42], [42, 7, 7], [0]) Выход: { unique:[42,7,0], sum:49, avg:16.333..., min:0, max:42 }

Примечания и подсказки

  • Ориентир по шагам:

    1. подготовьте пустые unique и функцию isFiniteNumber(x);
    2. обходите входные массивы слева направо;
    3. если isFiniteNumber(x) и unique ещё не содержит значение, добавляйте его (unique.includes(x));
    4. после заполнения unique посчитайте sum, min, max одним простым проходом; avg = sum / unique.length.
  • Для самопроверки держите наборы: «всё валидно», «всё невалидно», «повторы», «отрицательные и нули».

  • Если unique пуст, заранее договоритесь о возвращаемых агрегатах (например, sum: 0, avg: NaN, min: NaN, max: NaN) и используйте это правило в тестах.

Follow-up Сделайте обобщённую версию mergeStatsBy(arrays, keyFn), где вход — массивы объектов; уникальность определяется keyFn(x). Дополнительно верните countByKey — сколько раз встретился каждый ключ.

Дополнительные рекомендации

Вообще есть замечательные ресурсы, которые позволят быстрее освоиться и запомнить весь базовый синтаксис:

  • freeCodeCamp курс по JS - набор из множества небольших задач, каждая из которых нацелена на проверку и закрепление одного небольшого конкретного навыка
  • HackerRank — 10 Days of JavaScript - небольшой марафон, состоящий из 10 дней, на каждом из которых вам дается небольшая задачка на освоение основных концепций JS. Ответы на все задания есть на GH.
  • Edabit - ‘LeetCode’ по JS для маленьких