Вернёт ли метод map в JavaScript новый массив с новыми объектами или с теми же старыми объектами при добавлении свойства к объектам?
Что проверяют
Понимаете ли вы разницу между новым массивом и новыми объектами, то есть как в JavaScript работают ссылки. Следом обычно просят переписать код без мутации исходных данных и объяснить, почему это важно в React.
Как отвечать
map всегда возвращает новый массив той же длины, это новая ссылка: arr.map(x => x) !== arr. Но в него попадает ровно то, что вернул колбэк. Дальше всё зависит от кода:
- мутация и возврат того же объекта:
arr.map(o => { o.age = 30; return o; })даёт новый массив со старыми объектами. Проверкаresult[0] === arr[0]вернётtrue, иageпоявится в исходном массиве тоже; - возврат копии:
arr.map(o => ({ ...o, age: 30 }))создаёт новые объекты, исходные не меняются. Скобки вокруг литерала обязательны, иначе фигурные скобки прочитаются как тело функции; - поверхностная копия: spread и
Object.assignкопируют только первый уровень. Вложенныйo.addressостанется общим, и его мутация снова протечёт в оригинал; - глубокая копия:
structuredClone(o), если действительно нужна полная независимость. Функции и DOM-узлы он не клонирует и бросает ошибку.
Почему это важно на фронтенде. React, React.memo, зависимости useMemo и useEffect, селекторы Redux сравнивают по ссылке. Мутация объекта внутри состояния не меняет ссылку, и компонент не перерисуется или покажет устаревшие данные. Поэтому состояние обновляют иммутабельно, а мутирующие методы массива заменяют на toSorted, toReversed и with.
Где спотыкаются
Отвечают «map не мутирует, значит, исходные объекты в безопасности». map не трогает сам массив, но колбэк свободно меняет его элементы.
Пишут o => { ...o, age: 30 } без круглых скобок и получают синтаксическую ошибку, а вариант o => { o.age = 30 } вернёт массив из undefined.
Считают spread глубоким копированием и удивляются, что вложенные поля продолжают меняться в обоих массивах.