Объект в JavaScript - набор пар «ключ - значение». Почти всё в языке либо объект, либо ведёт себя как объект: массивы, функции, даты. Разберём, как объекты создавать, читать и изменять, как перебирать и проверять свойства, как правильно копировать и почему два одинаковых объекта не равны друг другу.
Создание
Чаще всего объект создают литералом:
const user = {
name: 'Иван',
age: 30,
'favorite color': 'синий',
};
Ключи - строки (или символы). Кавычки нужны, только если ключ не является допустимым идентификатором, например содержит пробел или дефис.
Сокращённая запись свойств - если имя переменной совпадает с ключом:
const name = 'Иван';
const age = 30;
const user = { name, age }; // то же, что { name: name, age: age }
Вычисляемые ключи - имя свойства берётся из выражения:
const field = 'email';
const user = { [field]: 'ivan@example.com', [`${field}Verified`]: true };
Другие способы: Object.create(proto) создаёт объект с заданным прототипом (Object.create(null) - объект совсем без прототипа, удобен как словарь), Object.fromEntries(pairs) - объект из массива пар, а классы - об этом ниже.
Доступ к свойствам
user.name // точечная запись
user['favorite color'] // квадратные скобки: для любых ключей и ключей из переменных
user[field]
user.city = 'Бишкек'; // добавить или изменить
delete user.city; // удалить
Обращение к несуществующему свойству возвращает undefined, а не ошибку. Ошибка возникает, когда пытаются прочитать свойство у undefined или null. От неё защищает опциональная цепочка:
const city = user.address?.city; // undefined, если address нет
const first = user.orders?.[0];
user.notify?.(); // вызвать, только если метод есть
Значение по умолчанию удобно задавать оператором ?? - он срабатывает только на null и undefined, в отличие от ||, который заменяет и 0, и пустую строку:
const limit = settings.limit ?? 10;
Методы и this
Функция в свойстве объекта - метод. Внутри метода this указывает на объект, у которого метод вызван:
const counter = {
value: 0,
increment() {
this.value++;
return this;
},
};
counter.increment().increment();
counter.value; // 2
this определяется в момент вызова, а не при создании. Если передать метод как отдельную функцию, связь потеряется:
const inc = counter.increment;
inc(); // this не counter - ошибка или неверный результат
const bound = counter.increment.bind(counter); // так связь сохраняется
У стрелочных функций своего this нет, они берут его из окружающего кода. Поэтому стрелочные функции не подходят для методов, но удобны внутри методов - например, в обработчиках и колбэках.
Геттеры и сеттеры
Свойство может вычисляться при чтении и проверять значение при записи:
const user = {
firstName: 'Иван',
lastName: 'Петров',
get fullName() {
return `${this.firstName} ${this.lastName}`;
},
set fullName(value) {
[this.firstName, this.lastName] = value.split(' ');
},
};
user.fullName; // 'Иван Петров'
user.fullName = 'Пётр Сидоров';
user.firstName; // 'Пётр'
Перебор свойств
Object.keys(user) // ['firstName', 'lastName', ...] - собственные перечисляемые ключи
Object.values(user) // значения
Object.entries(user) // пары [ключ, значение]
for (const [key, value] of Object.entries(user)) {
console.log(key, value);
}
Цикл for...in тоже перебирает ключи, но вместе с унаследованными перечисляемыми свойствами из прототипа. В обычном коде удобнее и безопаснее Object.keys и Object.entries.
Порядок ключей: сначала целочисленные ключи по возрастанию, затем строковые в порядке добавления.
Преобразовать объект, например умножить все цены:
const prices = { apple: 100, pear: 150 };
const doubled = Object.fromEntries(
Object.entries(prices).map(([key, value]) => [key, value * 2])
);
Проверка свойств
'name' in user // true, если свойство есть (в том числе в прототипе)
Object.hasOwn(user, 'name') // true, только если это собственное свойство
user.name !== undefined // не надёжно: свойство может существовать со значением undefined
Object.hasOwn - современная замена user.hasOwnProperty('name'), которая работает и для объектов без прототипа.
Деструктуризация
const { name, age = 18, address: { city } = {} } = user;
age = 18- значение по умолчанию, если свойства нет;address: { city }- вложенная деструктуризация,= {}защищает от отсутствияaddress.
Переименование и остаток:
const { name: userName, ...rest } = user; // rest - все остальные свойства
В параметрах функции деструктуризация заменяет длинные списки аргументов:
function createUser({ name, role = 'user', active = true }) { /* ... */ }
createUser({ name: 'Иван', role: 'admin' });
Объекты передаются по ссылке

Переменная хранит не сам объект, а ссылку на него. Присваивание копирует ссылку:
const a = { x: 1 };
const b = a;
b.x = 2;
a.x; // 2 - это один и тот же объект
Поэтому функция, изменившая полученный объект, меняет его и для вызывающего кода. И поэтому сравнение === проверяет, один ли это объект, а не одинаковое ли содержимое:
{ x: 1 } === { x: 1 } // false
Сравнить содержимое простых объектов можно своей функцией или через JSON.stringify (только если порядок ключей совпадает и в объектах нет функций, дат и undefined).
const запрещает переназначить переменную, но не менять объект: const user = {}; user.name = 'Иван'; работает.
Копирование

Поверхностная копия
const copy = { ...user };
const copy2 = Object.assign({}, user);
Копируются свойства первого уровня. Вложенные объекты остаются общими:
const original = { name: 'Иван', address: { city: 'Бишкек' } };
const copy = { ...original };
copy.address.city = 'Ош';
original.address.city; // 'Ош' - вложенный объект общий
Spread удобен и для «изменения» объекта без мутации - это основа работы с состоянием во многих фреймворках:
const updated = { ...user, age: 31 };
Глубокая копия
const deep = structuredClone(original);
structuredClone встроен в браузеры и Node.js и копирует вложенные объекты, массивы, даты, Map, Set и даже циклические ссылки. Не копирует функции и DOM-элементы, а у экземпляров классов теряется прототип - получится обычный объект.
Старый способ JSON.parse(JSON.stringify(obj)) до сих пор встречается, но теряет undefined, функции, превращает даты в строки и падает на циклических ссылках. В новом коде используйте structuredClone.
Защита от изменений
Object.freeze(config); // нельзя добавлять, удалять и менять свойства
Object.seal(config); // нельзя добавлять и удалять, менять можно
Object.isFrozen(config);
Заморозка поверхностная: вложенные объекты остаются изменяемыми, если не заморозить и их.
Тонкая настройка отдельного свойства - Object.defineProperty:
Object.defineProperty(user, 'id', {
value: 42,
writable: false, // нельзя изменить
enumerable: false, // не видно в Object.keys и JSON
});
Прототипы и классы
У каждого объекта есть прототип - объект, у которого он «наследует» свойства и методы. Когда свойства нет в самом объекте, JavaScript ищет его в прототипе, потом в прототипе прототипа и так далее. Именно поэтому у любого массива есть map, а у любого объекта - toString.
Классы - удобный синтаксис для создания объектов с общими методами в прототипе:
class User {
#password; // приватное поле
constructor(name, password) {
this.name = name;
this.#password = password;
}
checkPassword(value) {
return this.#password === value;
}
static fromJSON(json) {
const data = JSON.parse(json);
return new User(data.name, data.password);
}
}
class Admin extends User {
isAdmin = true;
}
const admin = new Admin('Иван', 'secret');
admin.checkPassword('secret'); // true
admin instanceof User; // true
Поля с # недоступны снаружи класса: admin.#password - синтаксическая ошибка.
Объект или Map
Для словарей с часто меняющимися или произвольными ключами (идентификаторы, строки от пользователя) лучше подходит Map: ключи любого типа, свой метод size, понятный порядок и нет унаследованных свойств вроде toString, которые могут совпасть с ключом. Объект удобнее для структур с заранее известными полями и для обмена данными через JSON.





Комментарии
Комментариев пока нет - будьте первым.