Копирование и сравнение
Примитивы
Копируются по значению, сравниваются по значению:
let a = 5;
let b = a;
b = 10;
a; // 5
5 === 5; // true
Объекты и массивы
Копируются по ссылке:
const a = { x: 1 };
const b = a;
b.x = 2;
a.x; // 2 — тот же объект
Сравниваются по ссылке:
{} === {}; // false
[] === []; // false
const a = {};
const b = a;
a === b; // true
Поверхностная копия
Массив
const copy = [...arr];
const copy2 = arr.slice();
const copy3 = Array.from(arr);
Объект
const copy = { ...obj };
const copy2 = Object.assign({}, obj);
Проблема с вложенными:
const original = { user: { name: 'Алиса' } };
const copy = { ...original };
copy.user.name = 'Боб';
original.user.name; // 'Боб' — вложенный тот же
Глубокая копия
structuredClone
Современный способ:
const deep = structuredClone(obj);
Работает с Date, Map, Set, ArrayBuffer. Не работает с функциями и DOM.
JSON
Быстро и грязно:
const deep = JSON.parse(JSON.stringify(obj));
Проблемы:
- Теряет функции
- Теряет
undefined Dateстановится строкой- Не работает с циклами
- Не работает с
Map,Set
Ручная рекурсия
function deepClone(value) {
if (value === null || typeof value !== 'object') return value;
if (value instanceof Date) return new Date(value);
if (Array.isArray(value)) return value.map(deepClone);
if (value instanceof Map) return new Map([...value].map(([k, v]) => [deepClone(k), deepClone(v)]));
if (value instanceof Set) return new Set([...value].map(deepClone));
const result = {};
for (const [key, v] of Object.entries(value)) {
result[key] = deepClone(v);
}
return result;
}
Сравнение
Примитивы
1 === 1; // true
'а' === 'а'; // true
true === true; // true
NaN === NaN; // false
Объекты
По ссылке:
const a = { x: 1 };
const b = { x: 1 };
a === b; // false
a === a; // true
Структурное сравнение
Простые случаи:
function shallowEqual(a, b) {
const ka = Object.keys(a);
const kb = Object.keys(b);
if (ka.length !== kb.length) return false;
return ka.every((k) => a[k] === b[k]);
}
Глубокое:
function deepEqual(a, b) {
if (a === b) return true;
if (typeof a !== typeof b) return false;
if (a === null || b === null) return false;
if (typeof a !== 'object') return false;
const ka = Object.keys(a);
const kb = Object.keys(b);
if (ka.length !== kb.length) return false;
return ka.every((k) => deepEqual(a[k], b[k]));
}
Готовые библиотеки
- lodash —
isEqual,cloneDeep - fast-deep-equal — быстрая проверка
- rfdc — быстрый клон
import { isEqual, cloneDeep } from 'lodash';
isEqual({ a: 1 }, { a: 1 }); // true
cloneDeep(obj);
Проблема с React и другими
React использует === для проверки изменений. Мутация объекта не вызывает ререндер:
const [user, setUser] = useState({ name: 'Алиса' });
user.name = 'Боб'; // не сработает
setUser({ ...user, name: 'Боб' }); // ок
Всегда создавай новые объекты.
Иммутабельные обновления
// Массив — добавить
setItems((prev) => [...prev, item]);
// Массив — удалить
setItems((prev) => prev.filter((i) => i.id !== id));
// Объект — обновить
setUser((prev) => ({ ...prev, name: 'Боб' }));
// Вложенный объект
setUser((prev) => ({
...prev,
address: { ...prev.address, city: 'Питер' },
}));
Итоги
- Примитивы — по значению
- Объекты — по ссылке
spread— поверхностная копияstructuredClone— глубокаяJSON— грязный способlodash.isEqualдля глубокого сравнения- Мутация не сработает в React