Перейти к основному содержимому
Блокчейн24ГЛАВА 24Массивы и объекты: как хранят много данных сразу012keyvalue

Массивы и объекты: как хранят много данных сразу

Прочитано0%
Квизы0/5
Тренажёрыне пройден0/1
Уровень 1 · Новичок

Зачем это нужно​

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

Для этого есть две структуры, и почти весь код на JavaScript и TypeScript состоит из них.

  • Массив — упорядоченный список: «много одинаковых по смыслу вещей», доступ по номеру.
  • Объект — набор именованных полей: «одна вещь с разными характеристиками», доступ по имени.

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

Глава разбирает их полностью — включая то место, где спотыкаются все: массивы и объекты передаются по ссылке, и копия, сделанная неправильно, оказывается тем же самым объектом. Все выводы сняты с живого запуска Node.js 24 и TypeScript 6.

Массив: список с номерами​

Массив создают квадратными скобками, элементы разделяют запятыми:

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.

СЧЁТ НАЧИНАЕТСЯ С НУЛЯconst coins = ['btc', 'eth', 'ton'];'btc'0индекс'eth'1индекс'ton'2индексcoins[3]undefinedза границей —не ошибка, а пустотаcoins.length === 3последний индекс = length - 1
Массив из трёх элементов: индексы 0, 1, 2, длина 3. Последний индекс всегда на единицу меньше длины — отсюда классическая ошибка на единицу.

Обращение за границу массива не даёт ошибки. Живой запуск:

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(...).

Отвечено вопросов: 0 из 5
Чему равен a[3] для массива [10, 20, 30]?
Почему [10, 9, 1].sort() даёт [1, 10, 9]?
Чем slice отличается от splice?
Какой метод искать транзакцию по условию в массиве объектов?
Что запрещает const у массива?
Отвечено верно: 0 из 5

Ссылки: главная ловушка этой главы​

Числа и строки хранятся в переменной сами. Массивы и объекты — нет: в переменной лежит ссылка, то есть адрес. Присваивание копирует адрес, а не содержимое. Живой запуск:

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
ССЫЛКА ПРОТИВ КОПИИconst b = aскопировали ссылкуab[1, 2, 3]b.push(4)a тоже стал [1, 2, 3, 4]a === b → trueconst c = [...a]создали новый массивac[1, 2, 3][1, 2, 3]c.push(4)a остался прежнимa === c → 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
ПОВЕРХНОСТНАЯ И ГЛУБОКАЯ КОПИЯ{ ...wallet }дублируется только верхний уровеньwalletcopylimits: { … }вложенный объект — общий на двоихstructuredClone(wallet)повторяется вся структура целикомwalletdeeplimitslimitsу каждого свой вложенный объект
Поверхностная копия дублирует только верхний уровень: вложенные объекты остаются общими. Глубокая копия повторяет всю структуру целиком.
Интересный факт

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
ПАРЫ «ИМЯ — ЗНАЧЕНИЕ»const wallet = {owner'Олег'balance152340currency'RUB'activetrue}wallet.balanceточка: имя известно заранеепишется без кавычекwallet['balance']скобки: имя вычисляетсявнутри — строка или переменнаяопечатка в имени не ошибка: чтение даст 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) — фильтр полей, он нужен редко.

Отвечено вопросов: 0 из 5
const c2 = c1 для массива — что произойдёт при c2.push(4)?
Почему [1, 2] === [1, 2] даёт false?
Что делает [...nested] с массивом объектов?
TypeError: Cannot read properties of undefined (reading "address") — какое звено виновато?
Что теряется при JSON.stringify?
Отвечено верно: 0 из 5

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, и это дешевле: проверка происходит до запуска.

Отвечено вопросов: 0 из 4
Что делает const [head, ...rest] = [1, 2, 3]?
Чем Map лучше обычного объекта, когда ключи приходят из данных?
Как убрать повторы из массива addresses?
Что произойдёт при frozen.x = 2 в обычном (нестрогом) режиме?
Отвечено верно: 0 из 4

Типы массивов в 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'.

Построчно:

  1. Забыли обязательное поле. «Свойство amount отсутствует, но требуется в типе Tx».
  2. Добавили лишнее. «Литерал объекта может задавать только известные свойства». Это и есть защита от опечатки в имени поля: в чистом JavaScript такая строка молча создала бы новое поле.
  3. Прочитали несуществующее поле. «Свойства sender нет в типе Tx».
  4. Использовали необязательное поле без проверки. «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 пригодится, когда описание нужно расширять из нескольких мест; такое бывает в библиотеках. Главное — не смешивать оба стиля в одном проекте без причины.

Отвечено вопросов: 0 из 4
Чем number[] отличается от Array<number>?
Что означает error TS2353: Object literal may only specify known properties?
Что описывает тип [string, number]?
Почему ro.push(3) для readonly number[] даёт ошибку про несуществующее свойство?
Отвечено верно: 0 из 4

Типичные ошибки: текст, перевод, причина, починка​

СообщениеПереводЧто произошлоКак чинить
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");
клавиша ``клавиша 11клавиша 22клавиша 33клавиша 44клавиша 55клавиша 66клавиша 77клавиша 88клавиша 99клавиша 00клавиша --клавиша ==клавиша ⌫⌫клавиша TabTabклавиша QQклавиша WWклавиша EEклавиша RRклавиша TTклавиша YYклавиша UUклавиша IIклавиша OOклавиша PPклавиша [[клавиша ]]клавиша \\клавиша Caps LockCaps Lockклавиша AAклавиша SSклавиша DDклавиша FFклавиша GGклавиша HHклавиша JJклавиша KKклавиша LLклавиша ;;клавиша ''клавиша EnterEnterклавиша ShiftShiftклавиша ZZклавиша XXклавиша CCклавиша VVклавиша BBклавиша NNклавиша MMклавиша ,,клавиша ..клавиша //клавиша ShiftShiftклавиша CtrlCtrlклавиша WinWinклавиша AltAltклавиша ПробелПробелклавиша AltAltклавиша WinWinклавиша MenuMenuклавиша CtrlCtrl
следующая: C — средний левой руки

Символы этой главы​

ЗнакНазваниеЧто делает
[ ] в объявлениилитерал массивасоздаёт массив из перечисленных элементов
[ ] после имениобращение по индексу или ключудостаёт элемент массива или поле объекта по вычисленному имени
{ } справа от =литерал объектасоздаёт объект из пар «имя: значение»
{ } слева от =деструктуризацияразбирает объект на переменные
.точкаобращение к полю по известному имени
?.опциональная цепочкаостанавливает цепочку, если слева 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-тип.

Дальше по теме:

Сводная проверка по всей главе: 15 вопросов, 5:00 на всё, без подсказок по ходу. Вопросы показываются по одному, ответ изменить нельзя. Если время выйдет — экзамен завершится с тем, что успел ответить. Пересдавать можно сколько угодно раз.

Куда дальше: проверенные ресурсы​

Видео по теме

ВидеоМетоды массивов JavaScript — forEach, map, filter, reduce, some, every, sort, includes, indexOfАлександр Ламков — Friendly Frontend
ВидеоУрок 12. JavaScript. Методы массивов (forEach, map, filter, reduce, find, findIndex)Владилен Минин
ВидеоДеструктуризация объектов. Фундаментальный JavaScriptМихаил Непомнящий
ВидеоДеструктуризация массивов. Фундаментальный JavaScriptМихаил Непомнящий
ВидеоДеструктуризация в JavaScriptprofrontend | Екатерина Нанивская

Комментарии

Обсуждение живёт в GitHub Discussions и грузится с чужого домена — поэтому только по нажатию.