Что можно передать в функцию, чтобы изменения аргумента сохранялись вне функции?
Что проверяют
Понимаете ли вы модель передачи аргументов в JavaScript. Формулировка «объекты передаются по ссылке» звучит в ответах чаще, чем она верна, и вопрос обычно продолжают уточнением, на котором такой ответ разваливается.
Как отвечать
В JavaScript аргументы всегда передаются по значению. Просто для объектов, массивов, функций, Map и Set этим значением является ссылка. Отсюда правило: мутация видна снаружи, переприсваивание нет.
function mutate(o) { o.n = 2; }
function reassign(o) { o = { n: 3 }; }
const obj = { n: 1 };
mutate(obj); // obj.n === 2
reassign(obj); // obj.n по-прежнему 2Значит, чтобы изменение пережило выход из функции, передают объект или массив и меняют содержимое: obj.x = 1, arr.push(1). Тот же приём с обёрткой-контейнером вида { value: ... }, в React это useRef. Примитивы (число, строка, булево, null, undefined, symbol, bigint) так менять нельзя, строки в языке неизменяемы, поэтому остаётся вернуть новое значение и присвоить на месте вызова.
Полезные уточнения: const запрещает переприсваивание переменной, но не мутацию содержимого, а Object.freeze замораживает только верхний уровень. Во фронтенде чаще нужно обратное поведение: в React мутация состояния не вызывает перерисовку, потому что сравнение идёт по ссылке, и новый объект создают через расширение.
Где спотыкаются
Говорят про передачу по ссылке и застревают на встречном вопросе, почему присваивание нового объекта внутри функции снаружи ничего не меняет. Точная формулировка про значение, которым является ссылка, снимает вопрос сразу.
Мутируют входной аргумент в утилите. Функция перестаёт быть предсказуемой, вызывающий код получает изменённые данные, а в React такой объект, уехавший в состояние, ломает перерисовку и мемоизацию.
Путают поверхностное копирование с глубоким. Расширение и Object.assign копируют только верхний уровень, вложенный объект остаётся общим, и правка копии меняет оригинал. Для настоящей копии есть structuredClone.