JavaScript: значения, функции и ссылки
Задача: превратить список событий в список подписей и понять, что изменяется
при выполнении программы. Нужны множества и отображения; синтаксис вводим здесь.
Все данные ниже учебные. Сохраните первый пример как lesson.mjs в своей
учебной ветке и запустите node lesson.mjs из каталога файла.
Выражение и последовательность действий
Выражение вычисляет значение: 2 + 3 даёт число, "Привет" — строку,
count > 0 — логическое значение. Инструкция задаёт действие: объявляет имя,
выбирает ветку или возвращает результат функции.
const events = [
{ title: "Открытие", location: "Площадь" },
{ title: "Мастерская", location: "Шатёр" },
];
function label(event) {
return `${event.title} — ${event.location}`;
}
for (const event of events) {
console.log(label(event));
}
events — имя массива, { ... } создаёт объект с полями, event.title
читает поле. function объявляет функцию; параметр получает значение аргумента
при вызове. return завершает вызов и отдаёт результат. for...of перебирает
элементы массива. Шаблонная строка в обратных кавычках вставляет значения
выражений ${...}. Здесь console.log — наблюдаемый эффект: запись в консоль.
Имя, объявленное через const, нельзя связать с другим значением.
let позволяет присваивание: let count = 0; count = count + 1.
Объявления внутри блока { ... } видны в этом блоке и вложенных блоках.
Предпочитайте const, пока переприсваивание не нужно.
Значения и операции
В JavaScript есть примитивы: undefined, null, boolean, number, bigint,
string, symbol — и объекты. Массивы и функции также объекты; функцию можно
передать другой функции. Это классификация языка, а не типы нашего предметного мира.
Спецификация ECMAScript.
undefined часто означает отсутствие значения свойства или результата,
null — явно выбранное отсутствие. Их смысл в API нужно договорить.
typeof null исторически возвращает "object"; одной этой проверки недостаточно
для работы с объектом. Для массивов есть Array.isArray(value).
console.log(0.1 + 0.2 === 0.3); // false
console.log("2" + 3); // "23"
console.log(2 + 3); // 5
console.log(null === undefined); // false
number не моделирует точные вещественные числа: округление меняет привычные
алгебраические законы. === не выполняет преобразование строк в числа;
оператор + может складывать числа или соединять строки. Поэтому внешний
ввод сначала разбирают, а не полагаются на неявное преобразование.
Для ветвления используйте if (условие) { ... } else { ... }.
В условии значения преобразуются к boolean: пустая строка, 0, null,
undefined ложны, а пустой массив и пустой объект истинны.
value ?? fallback выбирает запасное значение только для null/undefined;
value || fallback — для любого ложного значения. Если ноль допустим,
подмена его запасным значением через || может быть ошибкой.
Выбор варианта и короткие выражения
switch сравнивает значение с вариантами case. Здесь каждая ветка завершает
функцию через return, поэтому выполнение не переходит в следующий case:
function statusLabel(status) {
switch (status) {
case "draft":
return "Черновик";
case "published":
return "Опубликовано";
default:
return "Неизвестный статус";
}
}
console.log(statusLabel("published")); // Опубликовано
default обрабатывает остальные значения. Без return или break выполнение
может продолжиться в соседней ветке. Проверка типов never в ADT — дополнительный
слой TypeScript, а не свойство обычного JavaScript switch.
condition ? yes : no выбирает одно из двух выражений. Destructuring извлекает
поля или элементы: const { title, location } = events[0], const [first] = events.
Это короткая форма чтения, она не копирует вложенные объекты.
Два имени могут указывать на один объект
const original = { title: "Открытие", venue: { name: "Площадь" } };
const alias = original;
alias.title = "Начало";
console.log(original.title); // "Начало"
console.log(alias === original); // true
const copy = { ...original };
copy.title = "Другой заголовок";
copy.venue.name = "Шатёр";
console.log(original.title); // "Начало"
console.log(original.venue.name); // "Шатёр"
Присваивание объекта копирует ссылку, не весь граф данных. Spread { ...original }
создаёт новый внешний объект, но вложенный venue остаётся общим.
const запрещает переприсваивание имени, а не изменение полей.
Два отдельно созданных объекта с одинаковыми полями не равны через ===.
Объекты и их сравнение в MDN.
Исходник схемы
flowchart LR Original["original"] --> Object["Объект события"] Alias["alias"] --> Object Copy["copy"] --> New["Новый внешний объект"] Object --> Venue["Общий объект venue"] New --> Venue
Стрелка означает ссылку. Копирование внешнего объекта не разрывает последнюю
общую ссылку. Для изменения названия площадки без изменения исходника создайте
и новый вложенный объект: { ...original, venue: { ...original.venue, name: "Шатёр" } }.
Преобразование массива
map применяет функцию к каждому элементу и собирает результаты.
filter оставляет элементы, для которых предикат истинен.
find возвращает первый подходящий элемент или undefined.
Методы Array.
const onSquare = events.filter((event) => event.location === "Площадь");
const titles = onSquare.map((event) => event.title);
console.log(titles); // ["Открытие"]
console.log(events.find((event) => event.title === "Неизвестное")); // undefined
(event) => event.title — стрелочная функция; это сокращённый способ записать
вычисление результата. Callback получает элемент массива. Новые внешние массивы
не означают новые объекты элементов: onSquare[0] === events[0] здесь истинно.
sort меняет массив, на котором вызван; [...events].sort(compare) сортирует
копию внешнего массива. map тоже может менять вход, если callback мутирует объект.
Сводный итог и коллекция ключей
reduce последовательно передаёт накопитель следующему вызову callback.
Начальное значение делает поведение пустого массива явным:
const totalLetters = events.reduce((sum, event) => sum + event.title.length, 0);
const byLocation = new Map();
for (const event of events) {
const previous = byLocation.get(event.location) ?? 0;
byLocation.set(event.location, previous + 1);
}
console.log(totalLetters, [...byLocation]);
Map хранит пары ключ/значение: get читает, set записывает. Это отдельная
коллекция, а map массива преобразует каждый элемент. ...byLocation перебирает
пары в порядке добавления ключей. Локальная запись в новую коллекцию не меняет
исходный массив; это правило пригодится в FP-главе.
Замыкание и модуль
Функция сохраняет доступ к лексическому окружению, в котором создана. Такое сочетание функции и окружения называют замыканием.
function atLocation(location) {
return (event) => event.location === location;
}
const isOnSquare = atLocation("Площадь");
console.log(events.filter(isOnSquare).length); // 1
location остаётся доступен после возврата из atLocation.
Замыкание может также захватить изменяемое значение, поэтому само наличие
замыкания не означает чистую функцию.
Модуль отделяет публичные имена от внутренних. В Node файл .mjs — ESM-модуль:
// labels.mjs
export function label(event) {
return `${event.title} — ${event.location}`;
}
// lesson.mjs, рядом с labels.mjs
import { label } from "./labels.mjs";
console.log(label({ title: "Открытие", location: "Площадь" }));
В браузере нет всего API Node, в Node нет браузерного DOM. Язык общий, окружение предоставляет разные возможности. Правила модулей и сборки проекта разбираются вместе с инструментами.
Где это видно в Atmanki
Группировка расписания использует цикл,
массивы, Map и локальные объекты. Она сортирует копию входа и создаёт структуру
дней/площадок. Сам Map — коллекция пар ключ/значение, не метод массива map.
Файл написан на TypeScript; аннотации разберём в следующей главе.
Практика и самопроверка
Напишите titlesAt(events, location) на учебных данных первого примера.
Функция возвращает массив заголовков нужной площадки и не меняет вход.
Проверьте обычный случай, неизвестную площадку и пустой массив.
Затем измените элемент результата: исходный заголовок должен сохраниться,
ведь результат содержит строки, а не объекты событий.
Ожидается: titlesAt(events, "Площадь") → ["Открытие"],
titlesAt(events, "Нет") → []. Объясните, почему копия массива событий
не позволяла бы так же безопасно менять поля его элементов.