← Назад к вопросам

Как запретить мутацию массива?

2.0 Middle🔥 131 комментариев
#JavaScript Core

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как запретить мутацию массива

Мутация массива — это прямое изменение содержимого существующего массива (например, 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 всегда создавай новые массивы/объекты, не мутируй существующие