Изменение объектов, вложенные структуры и optional chaining
Реальные данные редко бывают плоскими. Пользователь живёт в городе, город — в стране, у адреса есть улица. Учимся работать с объектами внутри объектов и делать это безопасно.
✏️ Добавление и изменение свойств
Объект в JavaScript — это не закрытая структура. После создания можно в любой момент добавить новые свойства или изменить существующие. Синтаксис — тот же, что и для чтения: точка или скобки, плюс оператор присваивания =.
// Начинаем с простого объекта
let user = {
name: "Пётр",
age: 30
};
console.log(user); // { name: "Пётр", age: 30 }
// Изменяем существующее свойство
user.age = 31;
console.log(user.age); // 31
// Добавляем новое свойство (которого раньше не было)
user.email = "petr@example.com";
user.isActive = true;
console.log(user);
// { name: "Пётр", age: 31, email: "petr@example.com", isActive: true }
// Добавляем через скобки (например, если ключ в переменной)
let field = "score";
user[field] = 850;
console.log(user.score); // 850
💡 Объекты — гибкие. В отличие от языков со статической типизацией (Java, C#), где структура объекта фиксирована, в JS можно добавлять и удалять свойства в любой момент. Это мощно, но требует аккуратности.
🪆 Вложенные объекты — объект внутри объекта
Значение свойства объекта может само быть объектом. Это называется вложенностью. В реальных данных это встречается постоянно: данные пользователя содержат адрес, адрес содержит город, город содержит страну.
Представьте, как выглядят данные пользователя в API:
let user = {
id: 1,
name: "Алексей",
age: 25,
address: { // значение — тоже объект!
country: "Россия",
city: "Санкт-Петербург",
street: "Невский проспект",
building: 28
},
contacts: { // ещё один вложенный объект
phone: "+7-900-123-45-67",
email: "alex@email.com",
telegram: "@alex_dev"
}
};
Чтобы добраться до вложенных свойств, используем цепочку точек — каждая точка «идёт вглубь» на один уровень:
// Обращаемся к вложенным свойствам через цепочку точек
console.log(user.name); // "Алексей"
console.log(user.address.city); // "Санкт-Петербург"
console.log(user.address.country); // "Россия"
console.log(user.contacts.email); // "alex@email.com"
// Глубокая вложенность — просто продолжаем цепочку
let company = {
name: "Технологии Будущего",
hq: {
country: {
name: "Россия",
code: "RU"
},
city: "Москва"
}
};
console.log(company.hq.country.name); // "Россия"
console.log(company.hq.country.code); // "RU"
Изменение вложенных свойств — тот же синтаксис, просто с цепочкой:
user.address.city = "Москва"; // переехал
user.contacts.telegram = "@new_handle"; // сменил никнейм
// Добавить новое вложенное свойство
user.address.zipCode = "190000";
console.log(user.address);
// { country: "Россия", city: "Москва", street: "Невский...", building: 28, zipCode: "190000" }
💥 Опасность: обращение к несуществующей вложенности
Вот типичная ловушка при работе с вложенными объектами. Представьте, что данные пришли с сервера, и у части пользователей нет адреса:
let user1 = {
name: "Иван",
address: {
city: "Казань"
}
};
let user2 = {
name: "Мария"
// нет поля address!
};
// Для user1 всё отлично
console.log(user1.address.city); // "Казань"
// Для user2 — ОШИБКА!
console.log(user2.address.city);
// TypeError: Cannot read properties of undefined (reading 'city')
// user2.address → undefined → undefined.city → ОШИБКА
⚠️ Классическая ошибка: «Cannot read properties of undefined». Возникает когда пытаемся обратиться к свойству промежуточного объекта, которого нет. Чтобы этого избежать — раньше писали длинные проверки. Теперь есть элегантное решение.
🛡️ Optional chaining ?. — безопасный доступ
Оператор ?. (optional chaining, «опциональная цепочка») позволяет безопасно обращаться к вложенным свойствам. Если промежуточное значение равно null или undefined — выражение останавливается и возвращает undefined вместо ошибки.
let user1 = { name: "Иван", address: { city: "Казань" } };
let user2 = { name: "Мария" }; // нет address
// Старый способ (до ?.) — громоздкий
let city1 = user1.address && user1.address.city ? user1.address.city : "не указан";
let city2 = user2.address && user2.address.city ? user2.address.city : "не указан";
// Новый способ с ?. — чисто и коротко!
let city3 = user1.address?.city ?? "не указан"; // "Казань"
let city4 = user2.address?.city ?? "не указан"; // "не указан" — без ошибки!
console.log(city3); // "Казань"
console.log(city4); // "не указан"
Как работает ?.: каждый шаг цепочки проверяется. Как только встречается null или undefined — вся цепочка возвращает undefined и вычисление прекращается.
let data = {
user: {
profile: {
avatar: {
url: "https://example.com/avatar.jpg"
}
}
}
};
// Безопасный доступ на любую глубину
console.log(data.user?.profile?.avatar?.url); // "https://..."
console.log(data.user?.settings?.theme); // undefined (без ошибки)
console.log(data.user?.profile?.avatar?.size); // undefined (без ошибки)
// Работает и с методами
let arr = null;
console.log(arr?.length); // undefined (не ошибка)
console.log(arr?.map(x => x)); // undefined (не ошибка)
💡 ?? — оператор nullish coalescing. Часто идёт в паре с ?.. Выражение a ?? b возвращает b только если a равно null или undefined. Удобно для задания значений по умолчанию: user.city ?? "не указан".
📋 Spread оператор {...obj} — копирование объекта
Оператор spread ... «разворачивает» объект — вытаскивает все его свойства наружу. Это самый популярный способ создать поверхностную копию объекта или объединить несколько объектов в один.
// Копирование объекта
let original = { name: "Ольга", age: 28, city: "Уфа" };
let copy = { ...original };
copy.age = 29; // меняем копию
console.log(original.age); // 28 — оригинал не изменился!
console.log(copy.age); // 29
// Добавление новых свойств при копировании
let extended = { ...original, email: "olga@mail.ru", score: 100 };
console.log(extended);
// { name: "Ольга", age: 28, city: "Уфа", email: "olga@mail.ru", score: 100 }
// Перезапись свойств (новое значение "выигрывает")
let updated = { ...original, age: 30, city: "Москва" };
console.log(updated);
// { name: "Ольга", age: 30, city: "Москва" }
// Слияние двух объектов
let defaults = { theme: "light", lang: "ru", fontSize: 14 };
let userPrefs = { theme: "dark", fontSize: 16 };
let settings = { ...defaults, ...userPrefs }; // userPrefs перезаписывает defaults
console.log(settings);
// { theme: "dark", lang: "ru", fontSize: 16 }
⚠️ Поверхностная копия: spread копирует только первый уровень. Если значение свойства само является объектом — скопируется ссылка, а не объект. Изменение вложенного объекта затронет и оригинал. Для глубокого копирования используют другие методы (JSON.parse/JSON.stringify, structuredClone).
🧪 Практика: работа с профилем пользователя
// Данные приходят с сервера — иногда адрес есть, иногда нет
function displayUserCity(user) {
// Безопасно достаём город через optional chaining
// Если нет — показываем "Город не указан"
return user?.address?.city ?? "Город не указан";
}
let userA = { name: "Коля", address: { city: "Новосибирск" } };
let userB = { name: "Таня" };
let userC = { name: "Саша", address: null };
console.log(displayUserCity(userA)); // "Новосибирск"
console.log(displayUserCity(userB)); // "Город не указан"
console.log(displayUserCity(userC)); // "Город не указан"
console.log(displayUserCity(null)); // "Город не указан" — даже null не ломает!
// Обновляем профиль через spread
function updateProfile(profile, changes) {
return { ...profile, ...changes, updatedAt: new Date().toISOString() };
}
let profile = { name: "Коля", age: 22, city: "Новосибирск" };
let updated = updateProfile(profile, { age: 23, city: "Москва" });
console.log(profile); // { name: "Коля", age: 22, city: "Новосибирск" } — не изменился!
console.log(updated); // { name: "Коля", age: 23, city: "Москва", updatedAt: "..." }
✅ Итоги урока
- Свойства объекта добавляются и изменяются через
obj.key = valueилиobj[key] = value - Вложенные объекты — когда значение свойства само является объектом
- Доступ к вложенным свойствам через цепочку точек:
user.address.city - Обращение к несуществующему промежуточному объекту → TypeError
?.(optional chaining) — безопасный доступ: возвращаетundefinedвместо ошибки??— значение по умолчанию если результатnullилиundefined{...obj}— поверхностная копия объекта; можно объединять и перезаписывать свойства
Следующий урок: перебираем коллекции с помощью for...of и for...in. 🚀
Тест: вложенные объекты и optional chaining
7 вопросов