Урок 16 из 49 · Месяц 2. Git и введение в JavaScript
Деструктуризация, spread, rest, ?. и ??
Содержание урока
- Деструктуризация объектов
- Переименование и значения по умолчанию
- Вложенная деструктуризация
- Деструктуризация в параметрах функции
- Деструктуризация массивов
- Spread — «развернуть»
- Spread с массивами
- Spread с объектами
- Rest — «собрать остальное»
- Rest в деструктуризации
- Rest в параметрах функции
- Optional chaining ?.
- Оператор нулевого слияния ??
- Чем ?? отличается от ||
- ?. и ?? вместе
- Пример: всё вместе
- Типичные ошибки
- Практика
- Итоги
Современный JavaScript полон коротких записей с тремя точками, фигурными скобками слева от знака = и странными знаками ?. и ??. В этом уроке разберём их по очереди: деструктуризацию (быстро достать значения из объекта или массива), spread (развернуть), rest (собрать), optional chaining (безопасно добраться до вложенного свойства) и оператор нулевого слияния (подставить значение по умолчанию). Без этих приёмов не обходится ни один React-проект.
Деструктуризация объектов
Деструктуризация — способ одной строкой «распаковать» свойства объекта в отдельные переменные. Аналогия: вы открыли посылку и сразу разложили вещи по полкам.
Без деструктуризации:
const user = { name: "Айдана", age: 23, city: "Бишкек" };
const name = user.name;
const age = user.age;
const city = user.city;
С деструктуризацией:
const user = { name: "Айдана", age: 23, city: "Бишкек" };
const { name, age, city } = user;
console.log(name); // "Айдана"
console.log(age); // 23
Как это читать: «создай переменные name, age, city и положи в них одноимённые свойства из user». Порядок не важен — значения ищутся по имени ключа.
Переименование и значения по умолчанию
const product = { title: "Ноутбук", price: 65000 };
const { title: productName, price, discount = 0 } = product;
console.log(productName); // "Ноутбук"
console.log(discount); // 0 — свойства нет, взяли значение по умолчанию
// console.log(title); // ReferenceError — переменная называется productName
title: productName— взять свойствоtitle, но назвать переменнуюproductName;discount = 0— если свойства нет (оноundefined), используется0.
Вложенная деструктуризация
const order = {
id: 77,
customer: { name: "Тимур", phone: "+996 555 000 000" },
};
const {
id,
customer: { name, phone },
} = order;
console.log(id, name, phone); // 77 "Тимур" "+996 555 000 000"
Здесь customer: { ... } значит «зайди в свойство customer и распакуй его». Переменной customer при этом не создаётся.
Деструктуризация в параметрах функции
Самый частый случай на практике — функция получает объект и сразу его распаковывает:
function renderCard({ title, price, inStock = true }) {
return `${title} — ${price} сом ${inStock ? "" : "(нет в наличии)"}`;
}
console.log(renderCard({ title: "Кружка", price: 350 }));
// "Кружка — 350 сом "
Так в React описывают пропсы компонентов: function Card({ title, price }) { ... }.
Деструктуризация массивов
У массивов значения берутся по позиции, поэтому используются квадратные скобки:
const colors = ["red", "green", "blue"];
const [first, second] = colors;
console.log(first); // "red"
console.log(second); // "green"
const [, , third] = colors; // пропуск элементов запятыми
console.log(third); // "blue"
Удобный трюк — поменять значения двух переменных местами:
let a = 1;
let b = 2;
[a, b] = [b, a];
console.log(a, b); // 2 1
Вы ещё встретите это в React: const [count, setCount] = useState(0) — именно деструктуризация массива.
Spread — «развернуть»
Spread (..., «разворот») раскладывает массив или объект на отдельные элементы. Аналогия: высыпать содержимое одной коробки в другую.
Spread с массивами
const fruits = ["яблоко", "банан"];
const vegetables = ["морковь", "огурец"];
const all = [...fruits, ...vegetables];
console.log(all); // ["яблоко", "банан", "морковь", "огурец"]
const copy = [...fruits]; // копия массива
const withKiwi = [...fruits, "киви"]; // новый массив + элемент
const nums = [4, 18, 7];
console.log(Math.max(...nums)); // 18 — передали элементы как отдельные аргументы
Spread с объектами
const user = { name: "Руслан", age: 30 };
const userCopy = { ...user };
const updated = { ...user, age: 31, city: "Ош" };
console.log(updated); // { name: "Руслан", age: 31, city: "Ош" }
console.log(user); // { name: "Руслан", age: 30 } — исходный не изменился
Правило: если ключ повторяется, побеждает тот, что правее. Поэтому { ...user, age: 31 } перезаписывает возраст, а { age: 31, ...user } — нет.
Это основной способ «изменить» данные, не трогая оригинал. В React так обновляют состояние:
const settings = { theme: "light", lang: "ru", notifications: true };
const newSettings = { ...settings, theme: "dark" };
const original = { name: "Ali", address: { city: "Бишкек" } };
const shallow = { ...original };
shallow.address.city = "Ош";
console.log(original.address.city); // "Ош" — вложенный объект общий!
const deep = structuredClone(original);
deep.address.city = "Талас";
console.log(original.address.city); // "Ош" — не изменился
Rest — «собрать остальное»
Rest записывается теми же тремя точками, но делает обратное: собирает несколько значений в один массив или объект. Различить просто:
...справа от=или при вызове функции — это spread (разворачиваем);...слева от=или в параметрах функции — это rest (собираем).
Rest в деструктуризации
const [winner, ...others] = ["Аня", "Бека", "Влад", "Гуля"];
console.log(winner); // "Аня"
console.log(others); // ["Бека", "Влад", "Гуля"]
const { password, ...safeUser } = { login: "admin", password: "123", role: "owner" };
console.log(safeUser); // { login: "admin", role: "owner" }
Второй пример — популярный приём: убрать из объекта лишнее поле (например, пароль перед выводом на страницу), не изменяя исходный объект.
Rest в параметрах функции
Функция, которая принимает любое количество аргументов:
function sum(...numbers) {
return numbers.reduce((total, n) => total + n, 0);
}
console.log(sum(1, 2)); // 3
console.log(sum(5, 10, 15, 20)); // 50
...numbers собирает все переданные аргументы в настоящий массив, поэтому у него есть reduce и другие методы из урока Методы перебора массива.
Rest-параметр может быть только последним: function f(first, ...rest) — можно, function f(...rest, last) — ошибка.
Optional chaining ?.
Представьте, что данные пришли с сервера, и у одного пользователя нет адреса:
const user = { name: "Ислам" };
console.log(user.address.city); // TypeError: Cannot read properties of undefined (reading 'city')
Ошибка останавливает весь скрипт. Optional chaining (?., «необязательная цепочка») проверяет: если слева null или undefined, дальше не идти и вернуть undefined:
console.log(user.address?.city); // undefined — без ошибки
console.log(user?.name); // "Ислам"
Работает и с массивами, и с вызовом методов:
const data = { items: [] };
console.log(data.items?.[0]?.title); // undefined — первого элемента нет
console.log(user.sayHi?.()); // undefined — метода нет, но ошибки тоже нет
obj?.prop— свойство;obj?.[key]— свойство по ключу или индексу;obj.method?.()— вызвать метод, только если он существует.
Пример с DOM: элемента может не быть на странице.
document.querySelector("#promo")?.classList.add("visible");
Оператор нулевого слияния ??
?? возвращает правую часть, только если левая — null или undefined. Это способ задать значение по умолчанию.
const userName = null;
console.log(userName ?? "Гость"); // "Гость"
const city = "Бишкек";
console.log(city ?? "Не указан"); // "Бишкек"
Чем ?? отличается от ||
|| подставляет значение по умолчанию для любого falsy значения: 0, "", false, null, undefined, NaN. Иногда это ошибка:
const settings = { volume: 0, nickname: "" };
console.log(settings.volume || 50); // 50 — а пользователь специально поставил 0!
console.log(settings.volume ?? 50); // 0 — правильно
console.log(settings.nickname || "Аноним"); // "Аноним"
console.log(settings.nickname ?? "Аноним"); // ""
Сравнение на разных значениях слева:
Значение x x || "default" x ?? "default"
null "default" "default"
undefined "default" "default"
0 "default" 0
"" "default" ""
false "default" false
Правило: если 0, пустая строка или false — нормальные значения, используйте ??.
Есть и короткая форма присваивания: x ??= 10 — записать 10, только если x равен null или undefined.
?. и ?? вместе
Эти операторы отлично работают в паре: безопасно достать значение и подставить запасное:
const response = { user: { profile: null } };
const avatar = response.user?.profile?.avatar ?? "default-avatar.png";
console.log(avatar); // "default-avatar.png"
Пример: всё вместе
Обработаем данные, похожие на ответ сервера:
const apiResponse = {
status: "ok",
data: {
users: [
{ id: 1, name: "Айгуль", contacts: { email: "aigul@example.com" }, rating: 0 },
{ id: 2, name: "Эрлан", contacts: null },
],
},
};
const { data: { users = [] } = {} } = apiResponse;
const cards = users.map(({ id, name, contacts, rating, ...rest }) => ({
id,
title: name,
email: contacts?.email ?? "нет почты",
rating: rating ?? "без оценки",
...rest,
}));
console.log(cards);
// [
// { id: 1, title: "Айгуль", email: "aigul@example.com", rating: 0 },
// { id: 2, title: "Эрлан", email: "нет почты", rating: "без оценки" }
// ]
Разбор:
const { data: { users = [] } = {} } = apiResponse— достаёмusersиз вложенногоdata; еслиdataилиusersнет, получим пустой массив вместо ошибки.- В
mapдеструктурируем каждого пользователя прямо в параметрах. contacts?.email ?? "нет почты"— у Эрланаcontactsравенnull, поэтому?.вернётundefined, а??подставит запасной текст.rating ?? "без оценки"— у Айгуль рейтинг0сохранился (с||он бы потерялся)....restсобирает остальные поля, а в новом объекте...restих разворачивает — ничего не потеряется, если сервер добавит новые поля.
Типичные ошибки
- Путают фигурные и квадратные скобки:
{}— для объектов (по имени),[]— для массивов (по позиции). - Деструктуризация
undefined:const { a } = undefined— ошибка. Задавайте значение по умолчанию:= {}. - Ожидание глубокой копии от spread — вложенные объекты остаются общими.
- Rest не последним в параметрах или деструктуризации — синтаксическая ошибка.
||вместо??там, где0или""— допустимые значения.- Смешивание
??с||или&&без скобок — синтаксическая ошибка: пишите(a || b) ?? c.
Практика
- Распаковка. Дан
const book = { title: "JS для всех", author: "Иванов", year: 2024 }. Деструктурируйте его, переименовавtitleвbookTitleи добавивpages = 300по умолчанию. Ожидаемый результат: все четыре переменные выводятся в консоль. - Слияние. Объедините два массива
[1, 2, 3]и[4, 5]через spread, добавив0в начало, и найдите максимум черезMath.max(...). Ожидаемый результат:[0, 1, 2, 3, 4, 5]и5. - Обновление профиля. Напишите функцию
updateUser(user, changes), которая возвращает новый объект через spread, не меняя исходный. Ожидаемый результат: исходный объект прежний, новый — с изменениями. - Средняя оценка. Напишите
average(...grades)с rest-параметром. Если оценок нет, верните0. Ожидаемый результат:average(5, 4, 3)→4,average()→0. - Безопасные данные. Создайте массив из трёх «ответов сервера», где у некоторых нет
profile,profile.cityилиstats.likesравен0. Выведите для каждого строку «Имя — город — лайки», используя?.и??. Ожидаемый результат: нигде нет ошибки, вместо пустых данных — «не указан»,0лайков сохраняется.
Итоги
- Деструктуризация объекта
{ a, b } = objберёт значения по имени, массива[x, y] = arr— по позиции. - При деструктуризации можно переименовывать (
a: newName) и задавать значения по умолчанию (a = 0). - Spread
...разворачивает массив или объект — для копий и объединения; копия поверхностная. - Rest
...собирает «остальное» в массив или объект — в деструктуризации и параметрах функции. ?.безопасно обращается к свойствам, которых может не быть.??подставляет значение по умолчанию только дляnullиundefined, в отличие от||.