Массивы и объекты: как хранят много данных сразу
Зачем это нужно
Одна переменная хранит одно значение. Транзакция — это уже десяток значений сразу: идентификатор, отправитель, получатель, сумма, комиссия, метка времени, подпись. Ответ ноды — список из сотни таких транзакций. Заводить сто переменных никто не будет.
Для этого есть две структуры, и почти весь код на JavaScript и TypeScript состоит из них.
- Массив — упорядоченный список: «много одинаковых по смыслу вещей», доступ по номеру.
- Объект — набор именованных полей: «одна вещь с разными характеристиками», доступ по имени.
Ответ любого блокчейн-узла — это массив объектов. Конфигурация — объект. Список подписей — массив. Разобраться с этими двумя структурами важнее, чем выучить любой фреймворк: они встретятся в первой же строке настоящего кода.
Глава разбирает их полностью — включая то место, где спотыкаются все: массивы и объекты передаются по ссылке, и копия, сделанная неправильно, оказывается тем же самым объектом. Все выводы сняты с живого запуска Node.js 24 и TypeScript 6.
- Зачем это нужно
- Массив: список с номерами
- Как массив меняют
- Копии кусков:
slice,concat, распространение - Поиск в массиве
- Сортировка и главный сюрприз
sort - Ссылки: главная ловушка этой главы
- Объект: набор именованных полей
- Как объект меняют
- Вложенность и безопасное обращение
- Перебор полей объекта
- Деструктуризация: разбор на переменные
- JSON: как данные путешествуют по сети
MapиSet: когда объекта и массива мало- Заморозка: когда содержимое менять нельзя
- Типы массивов в TypeScript
- Типы объектов:
type, необязательные поля,Record - Типичные ошибки: текст, перевод, причина, починка
- Тренажёр: коллекции вслепую
- Символы этой главы
- Что должен уметь
- Куда дальше: проверенные ресурсы
Массив: список с номерами
Массив создают квадратными скобками, элементы разделяют запятыми:
const a = [10, 20, 30];
console.log(a[0], a[2], a.length); // 10 30 3
Индексы начинаются с нуля. Первый элемент — a[0], последний — a[a.length - 1]. Это единственная причина, по которой в циклах пишут i < a.length, а не i <= a.length.
Обращение за границу массива не даёт ошибки. Живой запуск:
console.log(a[3], a[-1]); // undefined undefined
Ни исключения, ни предупреждения — просто undefined. И падение случится позже, когда с этим undefined попробуют что-то сделать. В других языках выход за границу массива — это немедленная ошибка; здесь придётся быть внимательнее самому.
Как массив меняют
Основные методы, меняющие массив на месте:
const a = [10, 20, 30];
a.push(40); // [10, 20, 30, 40] — добавить в конец
a.pop(); // вернёт 40, массив снова [10, 20, 30]
a.unshift(5); // [5, 10, 20, 30] — добавить в начало
a.shift(); // вернёт 5, массив снова [10, 20, 30]
push и pop работают с концом массива, unshift и shift — с началом. Первая пара быстрая, вторая медленнее: при вставке в начало все остальные элементы сдвигаются.
splice вырезает кусок по месту:
const b = [1, 2, 3, 4, 5];
b.splice(1, 2);
console.log(b); // [ 1, 4, 5 ]
Первый аргумент — с какого индекса, второй — сколько штук удалить. Массив изменился сам.
const не запрещает менять массив. Он запрещает только переприсваивать переменную:
const a = [1, 2];
a.push(3); // можно: массив тот же, содержимое другое
a = [9]; // TypeError: Assignment to constant variable
То же с объектами. const защищает имя, а не содержимое, — и это ровно то, что нужно в девяти случаях из десяти.
Копии кусков: slice, concat, распространение
В отличие от предыдущих, эти методы не меняют исходный массив, а возвращают новый:
const a = [10, 20, 30];
console.log(a.slice(1)); // [ 20, 30 ]
console.log(a.slice(0, 2)); // [ 10, 20 ]
console.log(a); // [ 10, 20, 30 ] — не тронут
slice(from, to) берёт кусок: с индекса from включительно до to не включительно. Именно поэтому slice(0, 2) даёт два элемента, а не три.
Склеить массивы можно двумя способами — старым и современным:
console.log([1, 2].concat([3])); // [ 1, 2, 3 ]
console.log([...[1, 2], 3]); // [ 1, 2, 3 ]
Три точки — оператор распространения (spread): «высыпь содержимое массива сюда». Из него же получается самый короткий способ скопировать массив: [...a].
Различать «меняет на месте» и «возвращает новый» приходится постоянно. Мнемоника: slice — «отрезать себе кусочек», исходное блюдо цело; splice — «вырезать из», блюдо испорчено. Похожие имена, разный смысл, одна буква разницы.
Поиск в массиве
Живой запуск всех способов найти что-нибудь:
const a = [10, 20, 30];
console.log(a.includes(20)); // true — есть ли такой элемент
console.log(a.indexOf(20)); // 1 — на каком он месте
console.log(a.indexOf(999)); // -1 — не найден
const txs = [{ id: 'a', amt: 5 }, { id: 'b', amt: 1 }];
console.log(txs.find((t) => t.amt < 3)); // { id: 'b', amt: 1 }
console.log(txs.findIndex((t) => t.amt < 3)); // 1
console.log(txs.some((t) => t.amt > 4)); // true — хоть один подходит
console.log(txs.every((t) => t.amt > 4)); // false — все ли подходят
Разница между парами:
includesиindexOfищут конкретное значение, сравнивая строго. Для объектов бесполезны: два одинаковых с виду объекта — это разные значения.findиfindIndexищут по условию, которое ты описываешь функцией. Для массивов объектов нужны именно они.someиeveryотвечают на вопрос «да или нет».
indexOf возвращает -1, когда не нашёл, — не null и не undefined. Отсюда идиома if (a.indexOf(x) !== -1), которую в новом коде заменяют на понятную if (a.includes(x)).
find не нашёл — вернёт undefined, и TypeScript об этом обязательно напомнит:
error TS18048: 'found' is possibly 'undefined'.
Сортировка и главный сюрприз sort
console.log([10, 9, 1].sort()); // [ 1, 10, 9 ]
console.log([10, 9, 1].sort((x, y) => x - y)); // [ 1, 9, 10 ]
Первая строка — не опечатка. sort без аргументов сортирует как строки: сравниваются символы, а '10' меньше '9', потому что символ '1' идёт раньше '9'. Для чисел нужна функция сравнения.
Функция сравнения получает два элемента и должна вернуть отрицательное число, ноль или положительное — «первый раньше», «всё равно», «первый позже». Для чисел это записывается как (x, y) => x - y по возрастанию и (x, y) => y - x по убыванию. Для объектов — по любому полю:
txs.sort((x, y) => x.amount - y.amount); // по сумме
txs.sort((x, y) => x.id.localeCompare(y.id)); // по идентификатору, по-человечески
Второй сюрприз: sort меняет исходный массив и возвращает его же. Если исходный порядок нужен, сначала копию: [...txs].sort(...).
Ссылки: главная ловушка этой главы
Числа и строки хранятся в переменной сами. Массивы и объекты — нет: в переменной лежит ссылка, то есть адрес. Присваивание копирует адрес, а не содержимое. Живой запуск:
const c1 = [1, 2, 3];
const c2 = c1;
c2.push(4);
console.log(c1, c1 === c2); // [ 1, 2, 3, 4 ] true
Мы добавили элемент в c2, а изменился и c1, — потому что это один и тот же массив под двумя именами.
Настоящая копия делается распространением:
const c3 = [...c1];
c3.push(5);
console.log(c1, c3, c1 === c3); // [1,2,3,4] [1,2,3,4,5] false
Отсюда же следует, что массивы и объекты нельзя сравнивать через ===:
console.log([1, 2] === [1, 2]); // false
console.log(JSON.stringify([1, 2]) === JSON.stringify([1, 2])); // true
=== сравнивает адреса, а адреса разные. Сравнение через JSON.stringify работает, но грубо: порядок полей влияет на результат, а функции и undefined теряются. Для настоящего сравнения структур берут готовую функцию из библиотеки.
И последнее — копия бывает поверхностной:
const nested = [{ v: 1 }];
const shallow = [...nested];
shallow[0].v = 99;
console.log(nested[0].v); // 99 — внутренний объект остался общим
Распространение скопировало внешний массив, но внутри лежали ссылки — их оно и скопировало. Для полной копии есть встроенная функция:
const deep = structuredClone(nested);
deep[0].v = 7;
console.log(nested[0].v, deep[0].v); // 99 7
structuredClone — встроенная функция, отдельная библиотека не нужна. Она копирует массивы, объекты, Map, Set, даты и вложенные структуры любой глубины, но не умеет копировать функции — на них она выбросит ошибку. Для данных, пришедших от ноды, этого достаточно с запасом.
Объект: набор именованных полей
Объект создают фигурными скобками, поля пишут парами «имя: значение»:
const tx = { id: '3P', amount: 5, sender: { address: '3PABC' } };
console.log(tx.id, tx['amount']); // 3P 5
Два способа обратиться к полю:
- Через точку —
tx.amount. Короче и читается лучше; годится, когда имя поля известно заранее и записано буквами. - Через квадратные скобки —
tx['amount']. Нужны, когда имя лежит в переменной или содержит пробелы и дефисы:
const key = 'amount';
console.log(tx[key]); // 5
Отсутствующее поле даёт undefined, а не ошибку — ровно как выход за границу массива:
console.log(tx.fee); // undefined
Как объект меняют
Поля добавляют присваиванием, удаляют оператором delete, наличие проверяют через in. Живой запуск:
tx.fee = 0.001;
console.log(Object.keys(tx)); // [ 'id', 'amount', 'sender', 'fee' ]
delete tx.fee;
console.log(Object.keys(tx)); // [ 'id', 'amount', 'sender' ]
console.log('amount' in tx, 'fee' in tx); // true false
Обрати внимание: новое поле создаётся молча. Опечатка в имени не даёт ошибки — вместо изменения существующего поля появляется новое:
tx.amonut = 10; // опечатка: создано новое поле, старое не тронуто
Ошибки нет, программа работает, число не то. Это один из самых убедительных аргументов за TypeScript, где такая строка сразу даёт TS2339.
Копия объекта делается тем же распространением, что и у массива, — и заодно позволяет что-то переопределить:
const copy = { ...tx, amount: 9 };
console.log(copy.amount, tx.amount); // 9 5
Порядок важен: поля, написанные после распространения, перекрывают пришедшие из него.
Вложенность и безопасное обращение
Поля объектов бывают объектами — и обращение к полю поля падает, если промежуточного звена нет:
console.log(tx.recipient.address);
TypeError: Cannot read properties of undefined (reading 'address')
Разбор сообщения: tx.recipient дало undefined, а у undefined нет поля address. Ошибка называет последнее имя в цепочке, а виновато предпоследнее звено, — эту деталь стоит запомнить, она экономит время при отладке.
Лечится опциональной цепочкой — вопросительный знак перед точкой:
console.log(tx.recipient?.address); // undefined
console.log(tx.recipient?.address ?? 'нет получателя'); // нет получателя
?. означает «если слева null или undefined, останови цепочку и верни undefined, не пытаясь читать дальше». В паре с ?? из главы про значения получается короткая и безопасная запись, которой пишут разбор любого ответа от сети.
Работает ?. и с массивами (arr?.[0]), и с вызовами (callback?.()) — во втором случае функция вызывается, только если она есть.
Перебор полей объекта
Три функции превращают объект в массивы, а дальше работают уже знакомые методы:
console.log(Object.keys({ a: 1, b: 2 })); // [ 'a', 'b' ]
console.log(Object.values({ a: 1, b: 2 })); // [ 1, 2 ]
console.log(Object.entries({ a: 1, b: 2 })); // [ [ 'a', 1 ], [ 'b', 2 ] ]
entries даёт массив пар, и его удобно перебирать с деструктуризацией:
for (const [key, value] of Object.entries(tx)) {
console.log(`${key} = ${value}`);
}
Это современная замена for..in: перебираются только собственные поля, ключи приходят строками (что для объекта естественно), а весь арсенал методов массива остаётся доступен.
Деструктуризация: разбор на переменные
Запись, которая уже встречалась в главах про циклы и функции, — теперь целиком.
const { id, amount: amt, missing = 'по умолчанию' } = tx;
console.log(id, amt, missing); // 3P 5 по умолчанию
У массивов то же самое, но квадратными скобками и по порядку:
const [first, second] = [10, 20];
const [head, ...rest] = [1, 2, 3]; // head = 1, rest = [2, 3]
Три точки в левой части собирают остаток — та же идея, что у остаточных параметров функции.
Деструктуризация особенно полезна в двух местах: в списке параметров функции (получается вызов с именованными аргументами) и при разборе ответа от сети, где из большого объекта нужны два поля.
JSON: как данные путешествуют по сети
Нода отвечает не объектами, а текстом в формате JSON. Две функции переводят туда и обратно:
console.log(JSON.stringify({ a: 1, b: [1, 2] })); // {"a":1,"b":[1,2]}
console.log(JSON.parse('{"a":1}').a); // 1
JSON похож на литерал объекта, но строже: имена полей всегда в двойных кавычках, строки — тоже, одинарные кавычки запрещены, запятая после последнего поля запрещена, комментариев нет. Нарушение даёт живую ошибку:
JSON.parse('{a:1}');
SyntaxError: Expected property name or '}' in JSON at position 1 (line 1 column 2)
Сообщение называет позицию в тексте — по ней и ищут опечатку.
При переводе в JSON часть данных теряется:
console.log(JSON.stringify({ f: () => 1, u: undefined, n: null, d: new Date(0) }));
// {"n":null,"d":"1970-01-01T00:00:00.000Z"}
Функция и undefined исчезли молча, null сохранился, дата превратилась в строку — и обратно объектом даты уже не станет, её придётся создавать заново. Это не поломка, а свойство формата: JSON умеет хранить только числа, строки, логические значения, null, массивы и объекты.
Приём для отладки: console.log(JSON.stringify(obj, null, 2)) печатает объект с отступами в два пробела — читать вложенный ответ ноды так гораздо легче, чем одной строкой. Второй аргумент (null) — фильтр полей, он нужен редко.
Map и Set: когда объекта и массива мало
Две встроенные структуры, которые решают то, что объектом и массивом делается неудобно.
Map — словарь «ключ → значение», где ключом может быть что угодно, а не только строка:
const m = new Map();
m.set('a', 1);
m.set('b', 2);
console.log(m.get('a'), m.has('c'), m.size, [...m.keys()]); // 1 false 2 [ 'a', 'b' ]
Отличия от обычного объекта: ключи любого типа (в том числе объекты), размер берётся свойством size, порядок вставки гарантирован, а случайных ключей из прототипа не бывает. Когда набор ключей заранее неизвестен и приходит из данных — берут Map.
Set — множество без повторов:
const s = new Set([1, 1, 2, 3]);
console.log(s.size, [...s], s.has(2)); // 3 [ 1, 2, 3 ] true
Дубликаты отброшены при создании. Отсюда самый короткий способ убрать повторы из массива: [...new Set(arr)]. Проверка has в Set работает быстро независимо от размера — в отличие от includes у массива, который перебирает всё подряд.
Обе структуры перебираются через for..of и разворачиваются через ....
Заморозка: когда содержимое менять нельзя
Object.freeze запрещает менять поля объекта. Тонкость в том, как именно он это делает:
const frozen = Object.freeze({ x: 1 });
frozen.x = 2;
console.log(frozen.x); // 1 — присваивание не сработало
В обычном режиме присваивание молча ничего не делает. В строгом режиме (в модулях он включён всегда) — выбрасывает ошибку:
TypeError: Cannot assign to read only property 'x' of object
Заморозка тоже поверхностная: вложенные объекты остаются изменяемыми. Применяют её нечасто — обычно к конфигурации и константам, которые не должны меняться в течение работы программы. В TypeScript ту же задачу на этапе компиляции решают readonly и as const, и это дешевле: проверка происходит до запуска.
Типы массивов в TypeScript
Тип массива записывают двумя способами — они полностью равнозначны:
const nums: number[] = [1, 2, 3];
const nums2: Array<number> = [1, 2, 3];
Форма с квадратными скобками короче и встречается чаще; форма с угловыми нужна для сложных типов элементов.
Что это даёт:
nums.push('4');
error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.
Массив разнотипных элементов описывают объединением: (number | string)[]. Круглые скобки обязательны — без них number | string[] означает «либо число, либо массив строк», совсем другое.
Кортеж — массив фиксированной длины с типом на каждой позиции:
const pair: [string, number] = ['a', 1, 2];
error TS2322: Type '[string, number, number]' is not assignable to type '[string, number]'.
Source has 3 element(s) but target allows only 2.
Кортежи нужны там, где массив на самом деле не список, а пара: координаты, Object.entries, возврат двух значений из функции.
readonly запрещает изменение на этапе компиляции:
const ro: readonly number[] = [1, 2];
ro.push(3);
error TS2339: Property 'push' does not exist on type 'readonly number[]'.
Обрати внимание на формулировку: метода push у такого типа просто нет. Это дешевле Object.freeze — ничего не происходит во время работы программы, вся проверка сделана до запуска.
Типы объектов: type, необязательные поля, Record
Форму объекта описывают псевдонимом типа:
type Tx = { id: string; amount: number; fee?: number };
Поля разделяют точкой с запятой (запятая тоже допускается), ? делает поле необязательным. Четыре живые ошибки, которые начинает ловить компилятор:
error TS2741: Property 'amount' is missing in type '{ id: string; }' but required in type 'Tx'.
error TS2353: Object literal may only specify known properties, and 'extra' does not exist in type 'Tx'.
error TS2339: Property 'sender' does not exist on type 'Tx'.
error TS18048: 't.fee' is possibly 'undefined'.
Построчно:
- Забыли обязательное поле. «Свойство amount отсутствует, но требуется в типе Tx».
- Добавили лишнее. «Литерал объекта может задавать только известные свойства». Это и есть защита от опечатки в имени поля: в чистом JavaScript такая строка молча создала бы новое поле.
- Прочитали несуществующее поле. «Свойства sender нет в типе Tx».
- Использовали необязательное поле без проверки. «t.fee, возможно, undefined». Лечится проверкой,
??или?..
Для объекта-словаря, где ключи заранее неизвестны, есть Record:
const rec: Record<string, number> = { a: 1 };
rec.b = 'x';
error TS2322: Type 'string' is not assignable to type 'number'.
Читается так: «объект, у которого ключи — строки, а значения — числа».
Для описания формы объекта есть два инструмента: type и interface. В девяти случаях из десяти разницы нет, и можно спокойно писать type — он умеет ещё и объединения, и кортежи, и всё остальное. interface пригодится, когда описание нужно расширять из нескольких мест; такое бывает в библиотеках. Главное — не смешивать оба стиля в одном проекте без причины.
Типичные ошибки: текст, перевод, причина, починка
| Сообщение | Перевод | Что произошло | Как чинить |
|---|---|---|---|
TypeError: Cannot read properties of undefined (reading 'address') | Чтение поля у undefined | Промежуточное звено цепочки отсутствует | a?.b?.c или проверка перед обращением |
TypeError: Assignment to constant variable | Присваивание константе | Переменной с const присвоили новое значение | Менять содержимое, а не переменную; либо let |
TypeError: Cannot assign to read only property 'x' | Запись в замороженное поле | Объект пропущен через Object.freeze | Работать с копией { ...frozen, x: 2 } |
SyntaxError: Expected property name or '}' in JSON at position 1 | Неверный JSON | В тексте одинарные кавычки, имя без кавычек или лишняя запятая | Проверить текст по позиции из сообщения |
error TS2345: Argument of type 'string' is not assignable to parameter of type 'number' | Не тот тип аргумента | В типизированный массив кладут элемент другого типа | Исправить значение или тип массива |
error TS2741: Property 'amount' is missing … but required in type 'Tx' | Не хватает обязательного поля | Объект собран не полностью | Добавить поле или пометить его ? |
error TS2353: Object literal may only specify known properties | Лишнее поле в литерале | Опечатка в имени поля или поле забыли в типе | Исправить имя или дополнить тип |
error TS2339: Property 'sender' does not exist on type 'Tx' | Такого поля нет | Читают поле, не описанное в типе | Проверить имя, дополнить тип |
error TS18048: 't.fee' is possibly 'undefined' | Возможно, отсутствует | Необязательное поле используют без проверки | Проверка, ?? или значение по умолчанию |
error TS2322: Source has 3 element(s) but target allows only 2 | Лишний элемент кортежа | Длина не совпала с типом кортежа | Убрать элемент или расширить тип |
error TS2339: Property 'push' does not exist on type 'readonly number[]' | Массив только для чтения | Пытаются изменить readonly-массив | Создать новый массив вместо изменения |
| Массив изменился «сам» | — | Скопировали ссылку вместо содержимого | [...a], а для вложенных — structuredClone(a) |
| Сортировка чисел дала неверный порядок | — | sort() без функции сравнения | sort((x, y) => x - y) |
| Поле объекта не изменилось | — | Опечатка в имени: создано новое поле | Описать тип объекта — TypeScript покажет опечатку |
Тренажёр: коллекции вслепую
Цель: скорость ≥ 110 зн/мин, точность ≥ 90%
const confirmed = txs.filter((t) => t.status === "confirmed");
Символы этой главы
| Знак | Название | Что делает |
|---|---|---|
[ ] в объявлении | литерал массива | создаёт массив из перечисленных элементов |
[ ] после имени | обращение по индексу или ключу | достаёт элемент массива или поле объекта по вычисленному имени |
{ } справа от = | литерал объекта | создаёт объект из пар «имя: значение» |
{ } слева от = | деструктуризация | разбирает объект на переменные |
. | точка | обращение к полю по известному имени |
?. | опциональная цепочка | останавливает цепочку, если слева null или undefined |
... в правой части | распространение | высыпает содержимое массива или объекта |
... в левой части | сбор остатка | собирает оставшиеся элементы в новый массив |
: в литерале объекта | разделитель | отделяет имя поля от значения |
number[] | тип массива | массив чисел; равнозначно Array<number> |
[string, number] | кортеж | массив фиксированной длины с типом на каждой позиции |
? после имени поля | необязательное поле | поле может отсутствовать; только в TypeScript |
readonly | только для чтения | запрещает изменение на этапе компиляции |
Record<K, V> | тип словаря | объект с ключами типа K и значениями типа V |
Что должен уметь
- Создавать массивы и обращаться к элементам, помня, что индексы начинаются с нуля.
- Объяснять, почему
a[3]у массива из трёх элементов даётundefined, а не ошибку. - Различать методы, меняющие массив (
push,splice,sort), и возвращающие новый (slice,concat,map). - Помнить, что
constне запрещает менять содержимое массива и объекта. - Искать в массиве:
includes,indexOf,find,findIndex,some,every— и выбирать подходящий метод. - Сортировать числа с функцией сравнения и объяснять, почему
sort()без неё даёт неверный порядок. - Объяснять, что в переменной лежит ссылка, и делать настоящую копию через
[...a]. - Отличать поверхностную копию от глубокой и пользоваться
structuredClone. - Помнить, что массивы и объекты нельзя сравнивать через
===. - Создавать объекты, обращаться к полям через точку и через скобки, знать, когда нужны скобки.
- Добавлять и удалять поля, проверять наличие через
in, перебирать черезObject.entries. - Безопасно читать вложенные поля через
?.и??. - Читать
TypeError: Cannot read properties of undefinedи находить виноватое звено цепочки. - Разбирать объекты и массивы деструктуризацией, включая переименование и значения по умолчанию.
- Переводить данные в JSON и обратно, зная, что при этом теряется.
- Понимать, зачем нужны
MapиSet, и убирать повторы через[...new Set(arr)]. - Описывать типы:
number[], кортежи,typeдля объекта, необязательные поля,Record,readonly. - Читать ошибки
TS2741,TS2353,TS2339,TS18048и понимать, что именно не сошлось.
Углубиться
Массив — это объект. Технически typeof [1,2] даёт 'object', а индексы внутри хранятся как строковые ключи. Отсюда две особенности: проверять массив надо через Array.isArray, а не через typeof, и for..in по массиву выдаёт строковые индексы — ровно то, что мы видели в главе про циклы.
Разреженные массивы. new Array(3) создаёт массив из трёх «дырок» ([ <3 empty items> ]), а не из трёх undefined. Такие массивы ведут себя странно: map дырки пропускает, for..of — нет. Правильный способ создать массив заданной длины — Array.from({ length: 3 }, (_, i) => i), дающий [0, 1, 2].
Стоимость операций. push и pop работают за постоянное время, shift и unshift сдвигают весь массив. Поиск через includes или find просматривает элементы по одному: для массива в сто тысяч записей это заметно. Если по одним и тем же данным ищут многократно, их один раз перекладывают в Map или Set — дальше поиск становится почти мгновенным. Это тот самый приём, который отличает решение, укладывающееся в лимит времени, от решения, которое не укладывается.
Опциональная цепочка не ловит ошибки. a?.b защищает только от null и undefined слева. Если a — число, а b не существует, получится undefined без всякой ошибки; но если слева вообще нет переменной, будет ReferenceError, и ?. тут не поможет.
Структурная типизация. TypeScript сравнивает типы по форме, а не по имени: объект с нужными полями подойдёт под тип Tx, даже если про Tx он «не знает». Строгая проверка лишних полей включается только для литералов, написанных прямо в месте присваивания, — отсюда ошибка TS2353 там и её отсутствие при передаче переменной.
as const. Приписка as const к литералу превращает его в неизменяемый и максимально узкий тип: массив становится кортежем из литеральных типов, а поля — только для чтения. Приём часто используют для списков допустимых статусов, чтобы из них тут же получить union-тип.
Дальше по теме:
- MDN: индексируемые коллекции — массивы целиком
- MDN: работа с объектами — объекты от первоисточника
- MDN: деструктуризация — все формы записи
- TypeScript Handbook: Object Types — типы объектов, кортежи, readonly
Сводная проверка по всей главе: 15 вопросов, 5:00 на всё, без подсказок по ходу. Вопросы показываются по одному, ответ изменить нельзя. Если время выйдет — экзамен завершится с тем, что успел ответить. Пересдавать можно сколько угодно раз.
Куда дальше: проверенные ресурсы
-
MDN: индексируемые коллекции — массивы целиком, с примерами.
-
MDN: Array — справочник — все методы списком, с пометкой, какие меняют массив.
-
MDN: работа с объектами — создание, доступ, перебор.
-
MDN: деструктуризация — объекты, массивы, параметры функций.
-
MDN: опциональная цепочка — включая вызовы и индексы.
-
MDN: распространение — три точки во всех контекстах.
-
MDN: JSON — stringify, parse и их аргументы.
-
MDN: structuredClone — глубокое копирование без библиотек.
-
TypeScript Handbook: Object Types — типы объектов, необязательные поля, readonly, кортежи.
-
Видео и материалы сообщества — ниже — ролики по теме и ссылки от студентов и преподавателей смотри в самом низу страницы.
Комментарии
Обсуждение живёт в GitHub Discussions и грузится с чужого домена — поэтому только по нажатию.