Как написать функцию для глубокого сравнения двух объектов в JavaScript?
Что проверяют
Умеете ли вы написать рекурсию и помнить о краевых случаях JavaScript: NaN, массив против объекта, Date, циклические ссылки. После первой версии обычно просят поддержать Map и Set или объяснить, чем плох JSON.stringify.
Как отвечать
Сначала договоритесь о правилах: сравниваем собственные перечисляемые ключи, учитываем прототип, функции сравниваем по ссылке. Затем каркас:
function deepEqual(a, b, seen = new WeakMap()) {
if (Object.is(a, b)) return true;
if (!a || !b || typeof a !== "object" || typeof b !== "object") return false;
if (Object.getPrototypeOf(a) !== Object.getPrototypeOf(b)) return false;
if (a instanceof Date) return a.getTime() === b.getTime();
if (seen.get(a) === b) return true;
seen.set(a, b);
const keys = Object.keys(a);
if (keys.length !== Object.keys(b).length) return false;
return keys.every(k => Object.hasOwn(b, k) && deepEqual(a[k], b[k], seen));
}Object.is: в отличие от===считаетNaNравнымNaN, но различает0и-0, это стоит оговорить;- прототип: без проверки
[1, 2]и{0: 1, 1: 2}окажутся равны; Object.hasOwn: вместоkeys.includes(k), который даёт O(n²), и вместоin, который смотрит в прототип;- циклы:
WeakMapпомнит уже сравниваемые пары, иначеa.self = aуйдёт в бесконечную рекурсию; Map,Set,RegExp: нужны отдельные ветки, их содержимоеObject.keysне видит.
В проекте такую функцию берут из библиотеки (isEqual из lodash), но на собеседовании ждут свою.
Где спотыкаются
Предлагают JSON.stringify(a) === JSON.stringify(b). Результат зависит от порядка ключей, теряет undefined и функции, превращает NaN в null, а на цикле бросает TypeError.
Сравнивают примитивы через ===, и объект с NaN внутри никогда не равен своей копии.
Не проверяют тип контейнера. У наивной версии две разные Date всегда равны, ведь собственных ключей у них нет.