Сравнение объектов в JavaScript: почему {} !== {} и как правильно сравнивать?
Почему объекты сравниваются по ссылке, а не по значению?
В JavaScript объекты — это ссылочный тип данных. Когда вы создаете объект, переменная хранит не сам объект, а ссылку (указатель) на область памяти, где этот объект находится. Поэтому операторы === и == сравнивают не содержимое объектов, а ссылки — указывают ли они на один и тот же объект в памяти.
const obj1 = { a: 1 };
const obj2 = { a: 1 };
const obj3 = obj1; // копируем ссылку
console.log(obj1 === obj2); // false — разные объекты
console.log(obj1 === obj3); // true — один и тот же объект
Как правильно сравнивать объекты по содержимому?
Есть несколько подходов. Рассмотрим их от простого к сложному.
1. Поверхностное сравнение (shallow compare)
Подходит, если объект имеет только примитивные свойства (строки, числа) и не имеет вложенных объектов:
function shallowEqual(obj1, obj2) {
const keys1 = Object.keys(obj1);
const keys2 = Object.keys(obj2);
if (keys1.length !== keys2.length) return false;
for (let key of keys1) {
if (obj1[key] !== obj2[key]) return false;
}
return true;
}
const a = { x: 1, y: 2 };
const b = { x: 1, y: 2 };
console.log(shallowEqual(a, b)); // true
2. Глубокое сравнение с помощью JSON.stringify (с ограничениями)
Самый простой, но ненадежный способ:
const obj1 = { name: 'Анна', date: new Date(2023, 0, 1) };
const obj2 = { name: 'Анна', date: new Date(2023, 0, 1) };
console.log(JSON.stringify(obj1) === JSON.stringify(obj2)); // false! Потому что Date преобразуется в строку по-разному
Недостатки:
- Не работает для функций, undefined, Symbol, Infinity, NaN
- Чувствителен к порядку ключей (хотя в современных движках порядок обычно сохраняется)
- Медленный для больших объектов
3. Рекурсивное глубокое сравнение
Надежный способ, который проверяет все уровни вложенности:
function deepEqual(obj1, obj2) {
// Проверка на примитивы и одинаковые ссылки
if (obj1 === obj2) return true;
// Если хотя бы один не объект или null
if (typeof obj1 !== 'object' || obj1 === null ||
typeof obj2 !== 'object' || obj2 === null) {
return false;
}
// Специальная обработка для Date
if (obj1 instanceof Date && obj2 instanceof Date) {
return obj1.getTime() === obj2.getTime();
}
// Специальная обработка для RegExp
if (obj1 instanceof RegExp && obj2 instanceof RegExp) {
return obj1.toString() === obj2.toString();
}
const keys1 = Object.keys(obj1);
const keys2 = Object.keys(obj2);
if (keys1.length !== keys2.length) return false;
for (let key of keys1) {
if (!keys2.includes(key)) return false;
if (!deepEqual(obj1[key], obj2[key])) return false;
}
return true;
}
// Пример использования
const user1 = {
name: 'Анна',
age: 25,
address: { city: 'Москва', street: 'Тверская' }
};
const user2 = {
name: 'Анна',
age: 25,
address: { city: 'Москва', street: 'Тверская' }
};
console.log(deepEqual(user1, user2)); // true
4. Использование готовых библиотек
В реальных проектах лучше не писать свои велосипеды, а использовать проверенные решения:
- Lodash —
_.isEqual(obj1, obj2)— самая популярная утилита - Ramda —
R.equals(obj1, obj2) - Fast-deep-equal — легковесная библиотека
// Установка: npm install lodash
const _ = require('lodash');
const obj1 = { a: { b: 2 } };
const obj2 = { a: { b: 2 } };
console.log(_.isEqual(obj1, obj2)); // true
Когда нужно сравнивать объекты?
Чаще всего это требуется в React/Vue для оптимизации ре-рендеров (React.memo, useMemo), в тестах (Jest — toEqual), или при проверке изменений состояния в Redux.
Запомните главное: операторы сравнения работают с объектами как со ссылками. Для сравнения по содержимому всегда используйте специальные функции.
<<Альтернативный подход: сравнение через Map и Set
Иногда нужно сравнивать не просто объекты, а их структуру или наличие определенных ключей. В этом случае удобно использовать коллекции Map и Set.
Сравнение набора ключей
function hasSameKeys(obj1, obj2) {
const keys1 = new Set(Object.keys(obj1));
const keys2 = new Set(Object.keys(obj2));
if (keys1.size !== keys2.size) return false;
for (let key of keys1) {
if (!keys2.has(key)) return false;
}
return true;
}
const a = { name: 'Иван', age: 30 };
const b = { name: 'Петр', age: 25 };
console.log(hasSameKeys(a, b)); // true — одинаковый набор ключей
Сравнение с игнорированием порядка свойств
Если вам не важен порядок ключей (хотя в ES2015+ он гарантированно сохраняется), можно использовать сортировку:
function sortedStringify(obj) {
const sortedKeys = Object.keys(obj).sort();
const sortedObj = {};
for (let key of sortedKeys) {
sortedObj[key] = obj[key];
}
return JSON.stringify(sortedObj);
}
const x = { b: 2, a: 1 };
const y = { a: 1, b: 2 };
console.log(sortedStringify(x) === sortedStringify(y)); // true
Когда это полезно?
- При сравнении конфигурационных объектов
- В тестах, где важен только набор полей, а не их значения
- При работе с формами и проверке изменений
Помните: для сложных вложенных структур всегда лучше использовать _.isEqual из Lodash — это сэкономит вам часы отладки и покрывает 99% edge-кейсов.
Ответов пока нет.
Войдите чтобы оставить ответ