Объекты в JavaScript: от базовых принципов до неочевидных кейсов

#javascript#objects#prototypes#es6

Когда впервые сталкиваешься с объектами в JS, кажется, что это просто словари или хэш-мапы из других языков. Но на практике оказывается, что под капотом — целая вселенная с прототипами, дескрипторами и тонкостями мутаций. Давайте разбираться без учебникового пафоса.

Литералы vs конструкторы: что реально используют

Создать объект можно минимум четырьмя способами, но в продакшене чаще всего встречаются два:

// 99% случаев — литералы
const project = {
  name: 'Vibe Coding App',
  stack: ['Next.js', 'Tailwind'],
  isActive: true
};

// Редкий legacy-кейс
const user = new Object();
user.name = 'Agent';

Литеральная нотация выигрывает по читаемости и лаконичности. Конструктор Object() почти не используют — разве что при работе с динамическими ключами в очень старом коде.

Интереснее вариант с Object.create(), который часто всплывает в вопросах на собесах:

const baseConfig = { env: 'dev' };
const serviceConfig = Object.create(baseConfig);
console.log(serviceConfig.env); // 'dev' (но это прототипное свойство!)

Здесь важно понимать, что serviceConfig не содержит собственного свойства env — оно тянется из прототипа. Это может неожиданно всплыть при попытке Object.keys() или hasOwnProperty.

Когда Object.assign() стреляет в ногу

Метод для копирования свойств выглядит безобидно:

const defaults = { theme: 'dark', logs: false };
const userPrefs = { logs: true };
const config = Object.assign({}, defaults, userPrefs);

Но подводные камни начинаются, когда копируешь вложенные объекты:

const original = { nested: { value: 1 } };
const copy = Object.assign({}, original);
copy.nested.value = 2; // Мутирует original.nested тоже!

Это происходит потому, что Object.assign() делает shallow copy. Для глубокого клонирования сегодня чаще используют spread-оператор с JSON или специальные утилиты типа Lodash.

Прототипы: наследование без классов

JS-объекты — это не классическая ООП-модель, а прототипная цепочка. На практике это означает, что даже у пустого объекта {} уже есть методы типа toString() в прототипе.

Когда создаешь объект через литерал, его прототипом становится Object.prototype. Но можно подменить:

const agent = {
  __proto__: SomeBasePrototype,
  codename: 'Neo'
};

Важно: __proto__ — deprecated, хотя и работает. Вместо него рекомендуют Object.getPrototypeOf() и Object.setPrototypeOf().

Дескрипторы: тонкая настройка свойств

Объекты в JS — это не просто key-value пары. Каждое свойство имеет дескриптор с метаинформацией:

const secret = {};
Object.defineProperty(secret, 'apiKey', {
  value: '123-xyz',
  writable: false,
  enumerable: false
});

secret.apiKey = 'new'; // Тихий fail в non-strict режиме
console.log(Object.keys(secret)); // []

Этот механизм используется в:

Выводы для практики

  1. Для обычных DTO — литералы + spread для копирования
  2. Для иммутабельностиObject.freeze() или библиотеки типа Immer
  3. Для тонкого контроля — дескрипторы и прокси
  4. Наследование — лучше классы (синтаксический сахар над прототипами) или композиция

Объекты в JS — это одновременно и просто, и сложно. Главное — не переоценивать их схожесть с классическими ООП-моделями и помнить про прототипную магию.


Попробуй сам: Cursor — AI-редактор для разработчиков.


Источник: https://dev.to/sraghul_fe17c80d6b8c8023e/object-in-javascript-2797