Сравнение объектов: ссылки vs значения

Примитивы (числа, строки, булевы) в JavaScript сравниваются по значению, а объекты (массивы, функции, объекты) — по ссылке: два объекта равны только если они ссылаются на один и тот же участок памяти.

Зачем нужно

Это одна из самых распространённых причин ошибок у начинающих: {} === {} даёт false, а мутация одного объекта неожиданно меняет «другой» — потому что оба ссылаются на один. Понимание этого различия критично для корректного сравнения, копирования и управления состоянием.

Где используется

  • Сравнение объектов в условиях (if, ===)
  • Обнаружение изменений состояния в React, Vue
  • Копирование объектов и массивов (shallow vs deep)
  • Работа с Set и Map (ключи объектов по ссылке)

Примитивы — по значению

let a = 5;
let b = a; // копия значения

b = 10;
console.log(a); // 5 — a не изменилась

// Сравнение
console.log(5 === 5);         // true
console.log('hello' === 'hello'); // true

Объекты — по ссылке

const obj1 = { name: 'Иван' };
const obj2 = obj1; // обе переменные указывают на ОДИН объект

obj2.name = 'Анна';
console.log(obj1.name); // 'Анна' — obj1 тоже изменился!

// Сравнение
const a = { x: 1 };
const b = { x: 1 };
console.log(a === b); // false — разные объекты в памяти
console.log(a === a); // true — та же ссылка

Массивы тоже по ссылке

const arr1 = [1, 2, 3];
const arr2 = arr1;

arr2.push(4);
console.log(arr1); // [1, 2, 3, 4] — тот же массив!

console.log([1, 2] === [1, 2]); // false — разные массивы

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

// 1. Через JSON (простые объекты без методов и undefined)
const deepEqual = (a, b) => JSON.stringify(a) === JSON.stringify(b);

deepEqual({ x: 1, y: 2 }, { x: 1, y: 2 }); // true
deepEqual([1, 2, 3], [1, 2, 3]);            // true

// Ограничения: порядок ключей важен, нет поддержки Date, функций, undefined

// 2. Рекурсивная проверка
function isEqual(a, b) {
  if (a === b) return true;
  if (typeof a !== 'object' || typeof b !== 'object') return false;
  if (a === null || b === null) return false;

  const keysA = Object.keys(a);
  const keysB = Object.keys(b);
  if (keysA.length !== keysB.length) return false;

  return keysA.every(key => isEqual(a[key], b[key]));
}

isEqual({ a: 1, b: { c: 2 } }, { a: 1, b: { c: 2 } }); // true

Копирование объектов

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

// Поверхностная копия (shallow copy)
const shallow1 = { ...original };
const shallow2 = Object.assign({}, original);

shallow1.name = 'Анна'; // не влияет на original
console.log(original.name); // 'Иван'

shallow1.address.city = 'СПб'; // ВЛИЯЕТ! address — вложенный объект (ссылка)
console.log(original.address.city); // 'СПб'

// Глубокая копия (deep copy)
const deep1 = JSON.parse(JSON.stringify(original)); // простые объекты
const deep2 = structuredClone(original);             // ES2022, работает с Date, Map, Set

deep2.address.city = 'Казань';
console.log(original.address.city); // 'СПб' — original не затронут

Set и Map: ключи объектов по ссылке

const map = new Map();
const key1 = { id: 1 };
const key2 = { id: 1 }; // другой объект

map.set(key1, 'первый');
map.set(key2, 'второй');

console.log(map.size); // 2 — разные ссылки = разные ключи
console.log(map.get(key1)); // 'первый'

// Set с объектами
const set = new Set();
set.add({ x: 1 });
set.add({ x: 1 });
console.log(set.size); // 2 — одинаковые объекты, но разные ссылки

Частые ошибки

1. Мутация объекта-аргумента

function addRole(user) {
  user.role = 'admin'; // мутирует оригинал!
  return user;
}

const ivan = { name: 'Иван' };
addRole(ivan);
console.log(ivan.role); // 'admin' — оригинал изменён

// Лучше возвращать новый объект:
function addRole(user) {
  return { ...user, role: 'admin' };
}

2. Сравнение массивов через ===

const result = fetchData;
if (result === ) { /* никогда не выполнится */ }

// Правильно:
if (result.length === 0) {}
if (Array.isArray(result) && result.length === 0) {}

Связанные темы

Ресурсы