Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Как запретить мутацию массива
Мутация массива — это прямое изменение содержимого существующего массива (например, arr[0] = 5 или arr.push(1)). Во многих случаях это нежелательно, особенно в React и других фреймворках, где неизменяемость (immutability) критична.
Способ 1: Object.freeze()
Object.freeze() делает объект или массив неизменяемым. Попытка изменить значение будет проигнорирована (в нестрогом режиме) или вызовет ошибку (в строгом режиме).
const arr = [1, 2, 3];
Object.freeze(arr);
arr[0] = 99; // Попытка изменить - игнорируется
arr.push(4); // Ошибка: Cannot add property
console.log(arr); // [1, 2, 3]
Проверка, заморожен ли объект:
const arr = [1, 2, 3];
Object.freeze(arr);
console.log(Object.isFrozen(arr)); // true
Важно: freeze() замораживает только верхний уровень:
const arr = [1, [2, 3], 4];
Object.freeze(arr);
arr[0] = 99; // Не сработает
arr[1][0] = 99; // СРАБОТАЕТ! Вложенный массив не заморожен
console.log(arr); // [1, [99, 3], 4]
Способ 2: Глубокая заморозка (Deep Freeze)
Для защиты всех уровней вложенности нужна рекурсивная заморозка:
function deepFreeze(obj) {
Object.freeze(obj);
Object.getOwnPropertyNames(obj).forEach(prop => {
if (obj[prop] !== null &&
(typeof obj[prop] === 'object' || typeof obj[prop] === 'function') &&
!Object.isFrozen(obj[prop])) {
deepFreeze(obj[prop]);
}
});
return obj;
}
const arr = [1, [2, [3, 4]], 5];
deepFreeze(arr);
arr[1][1][0] = 99; // Ошибка в строгом режиме
console.log(arr); // [1, [2, [3, 4]], 5]
Способ 3: Константы const (не полная защита)
const запрещает переприсваивание переменной, но НЕ запрещает мутацию содержимого:
const arr = [1, 2, 3];
arr = [4, 5, 6]; // Ошибка: Assignment to constant
arr[0] = 99; // OK! Мутация сработает
arr.push(4); // OK! push() также сработает
console.log(arr); // [99, 2, 3, 4]
const + Object.freeze() — полная защита:
const arr = Object.freeze([1, 2, 3]);
arr = [4, 5, 6]; // Ошибка
arr[0] = 99; // Не сработает
Способ 4: Использование Readonly типов в TypeScript
TypeScript позволяет обозначить тип как ReadOnly на уровне типизации:
const arr: readonly number[] = [1, 2, 3];
arr[0] = 99; // Ошибка компилятора
arr.push(4); // Ошибка компилятора
const obj: Readonly<{ name: string }> = { name: 'John' };
obj.name = 'Jane'; // Ошибка компилятора
Readonly для объектов:
interface User {
readonly id: number;
readonly name: string;
email: string; // Этот свойство можно менять
}
const user: User = { id: 1, name: 'John', email: 'john@example.com' };
user.id = 2; // Ошибка компилятора
user.email = 'new@example.com'; // OK
Способ 5: Структурное клонирование (создание копии)
Вместо запрещения мутации можно создавать новые копии и работать с ними:
// Поверхностная копия
const originalArr = [1, 2, 3];
const copiedArr = [...originalArr]; // spread оператор
const copiedArr2 = originalArr.slice(); // метод slice()
const copiedArr3 = Array.from(originalArr); // метод Array.from()
copiedArr[0] = 99;
console.log(originalArr); // [1, 2, 3] - не изменён
console.log(copiedArr); // [99, 2, 3]
Глубокая копия:
// JSON метод (для простых данных)
const original = [1, [2, 3], 4];
const deep = JSON.parse(JSON.stringify(original));
deep[1][0] = 99;
console.log(original); // [1, [2, 3], 4]
// structuredClone (современный способ)
const deep2 = structuredClone(original);
Способ 6: Использование Immer.js
Библиотека Immer позволяет работать с «мутирующим» кодом, но на самом деле создаёт новые неизменяемые структуры:
import produce from 'immer';
const original = [1, 2, 3];
const updated = produce(original, draft => {
draft[0] = 99; // Выглядит как мутация
});
console.log(original); // [1, 2, 3] - не изменён
console.log(updated); // [99, 2, 3] - новый массив
Сравнение методов
| Метод | Преимущества | Недостатки |
|---|---|---|
| Object.freeze() | Простой, встроенный | Только верхний уровень, нет сообщений об ошибке |
| DeepFreeze | Полная защита | Сложнее, медленнее |
| TypeScript Readonly | Ошибка на этапе компиляции | Только в TS, не защита в runtime |
| Копирование | Безопасно | Использует память, медленнее |
| Immer.js | Удобный API | Зависимость, небольшой overhead |
Практический пример в React
import { useState } from 'react';
function Counter() {
// ❌ Плохо: прямая мутация
const [items, setItems] = useState([1, 2, 3]);
const badUpdate = () => {
items[0] = 99; // React не заметит изменение!
setItems(items);
};
// ✅ Хорошо: создание нового массива
const goodUpdate = () => {
setItems([99, ...items.slice(1)]);
// или
setItems(items.map((item, i) => i === 0 ? 99 : item));
};
}
Выводы
- Object.freeze() — простой способ заморозить массив
- deepFreeze() — для защиты всех уровней вложенности
- TypeScript Readonly — для статической типизации
- Структурное клонирование — безопасный способ работы с данными
- Immer.js — элегантный способ для сложных обновлений
- В React всегда создавай новые массивы/объекты, не мутируй существующие