Learning Book

Ссылки и копирование

Ссылки и копирование объектов

Примитивы хранятся по значению — объекты по ссылке. Это различие порождает неожиданное поведение при копировании и сравнении.

Значение vs ссылка

// Примитивы — копируются по значению
let a = 5;
let b = a;
b = 10;
console.log(a); // 5 — не изменилось

// Объекты — копируются по ссылке
const user = { name: 'Иван' };
const copy = user; // copy указывает на ТОТ ЖЕ объект
copy.name = 'Мария';
console.log(user.name); // 'Мария' — изменилось!

copy и user — это две переменные, но обе указывают на один объект в памяти.

Сравнение объектов

const a = { x: 1 };
const b = { x: 1 };
const c = a;

console.log(a === b); // false — разные объекты, хотя содержимое одинаково
console.log(a === c); // true  — одна и та же ссылка
Два объекта равны === только если они — один и тот же объект (одна ссылка). Для сравнения содержимого нужна ручная проверка или JSON.stringify (с ограничениями).

Поверхностное копирование

Создаёт новый объект, но вложенные объекты всё ещё передаются по ссылке.

const original = {
  name: 'Иван',
  address: { city: 'Москва' }
};

// Способ 1: spread
const shallow1 = { ...original };

// Способ 2: Object.assign
const shallow2 = Object.assign({}, original);

// Поверхностные свойства скопированы
shallow1.name = 'Мария';
console.log(original.name); // 'Иван' — не изменилось ✓

// Вложенные объекты — всё ещё ссылки
shallow1.address.city = 'Питер';
console.log(original.address.city); // 'Питер' — изменилось! ✗

Глубокое копирование

Копирует весь граф объекта, включая вложенные объекты.

structuredClone (современный стандарт)

const original = {
  name: 'Иван',
  address: { city: 'Москва' },
  tags: ['js', 'ts'],
  createdAt: new Date()
};

const deep = structuredClone(original);

deep.address.city = 'Питер';
console.log(original.address.city); // 'Москва' ✓

deep.tags.push('vue');
console.log(original.tags); // ['js', 'ts'] ✓

// Поддерживает Date, Map, Set, ArrayBuffer и другие
console.log(deep.createdAt instanceof Date); // true ✓

JSON.parse/JSON.stringify (старый способ)

const deep = JSON.parse(JSON.stringify(original));
// Ограничения:
// — теряет Date (становится строкой)
// — теряет undefined
// — теряет функции
// — не работает с циклическими ссылками
structuredClone не поддерживает функции, DOM-узлы и объекты с прототипами (классы). Для таких случаев — библиотека lodash.cloneDeep.

Заморозка объектов

const config = Object.freeze({
  apiUrl: 'https://api.example.com',
  timeout: 5000,
  nested: { retries: 3 }
});

config.apiUrl = 'другой'; // Молча игнорируется (TypeError в strict mode)
console.log(config.apiUrl); // 'https://api.example.com'

// freeze — поверхностная: вложенные объекты изменяемы
config.nested.retries = 10; // Работает!

Для глубокой заморозки нужно рекурсивно обойти все вложенные объекты:

function deepFreeze(obj) {
  Object.getOwnPropertyNames(obj).forEach(name => {
    const value = obj[name];
    if (typeof value === 'object' && value !== null) {
      deepFreeze(value); // рекурсия для вложенных объектов
    }
  });
  return Object.freeze(obj);
}

const frozen = deepFreeze({ nested: { x: 1 } });
frozen.nested.x = 2; // TypeError в strict mode
variables