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

Как понимаешь из кода что значение не могло поменяться?

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

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

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

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

Понимание Immutability в JavaScript: когда значение не изменится

Это критический навык для написания надёжного кода. Immutability (неизменяемость) — когда значение не может быть изменено после создания — делает код предсказуемым и предотвращает множество ошибок. Разбираю, как это определить и использовать.

1. Примитивные типы (по умолчанию неизменяемы)

В JavaScript примитивы (string, number, boolean, bigint, symbol) — всегда immutable:

// Примитивы всегда immutable
const str = "hello";
const newStr = str.toUpperCase();
// str остался "hello" - методы создают новые значения

const num = 42;
num = 43; // ❌ Ошибка, const запрещает переассигнмент

// Правило: примитивы + const = гарантированно immutable
const USERNAME = "john"; // не изменится
const USER_ID = 123;     // не изменится
const IS_ADMIN = false;  // не изменится

// ❌ Даже если используем let - значение всё равно примитив
let temp = 10;
temp = 20; // Переассигнили, но это новое значение, старое не изменилось

2. Объекты и массивы: const защищает ссылку, не содержимое

Это главная ошибка: const объект НЕ значит, что его содержимое immutable.

// ❌ Плохо понимание immutability
const user = { name: "john", age: 30 };
user.age = 31; // ✓ Это работает! const защищает только ссылку

// ✓ const защищает переассигнмент
const user = { name: "john" };
user = {}; // ❌ Ошибка - пытаемся переассигнить

// Но содержимое изменяется:
const user = { name: "john" };
user.name = "jane"; // ✓ Работает - содержимое изменилось

// Правило: const на объекте = shallow immutability только на уровне ссылки

3. Object.freeze() для true immutability

Для настоящей неизменяемости используем Object.freeze():

// Freezing на уровне объекта
const config = Object.freeze({
  apiUrl: "https://api.example.com",
  timeout: 5000
});

config.timeout = 10000; // ❌ Молча падает (или ошибка в strict mode)
console.log(config.timeout); // 5000 - не изменилось

// Проверяем, заморожен ли объект
Object.isFrozen(config); // true

// Но это shallow freeze - вложенные объекты могут изменяться
const shallow = Object.freeze({
  user: { name: "john" }
});

shallow.user.name = "jane"; // ✓ Работает - вложенный объект не заморожен

// Deep freeze для полной защиты
const 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 config = deepFreeze({
  api: {
    url: "https://api.example.com",
    retries: { max: 3 }
  }
});

config.api.retries.max = 5; // ❌ Не сработает

4. Типизация в TypeScript для явного указания immutability

TypeScript помогает выразить intent - что не должно меняться:

// ✅ Явно указываем: это значение не может быть изменено
interface User {
  readonly name: string;      // не может меняться после создания
  readonly age: number;       // не может меняться
  readonly email: string;     // не может меняться
  mutableStatus?: string;     // может быть изменено
}

const user: User = { name: "john", age: 30, email: "j@e.com" };
user.name = "jane"; // ❌ TS ошибка: Cannot assign to 'name' because it is a readonly property

// Readonly для массивов
const numbers: readonly number[] = [1, 2, 3];
numbers.push(4); // ❌ TS ошибка: Property 'push' does not exist on type 'readonly number[]'
numbers[0] = 10; // ❌ TS ошибка

// Readonly для всего объекта рекурсивно
type Immutable<T> = {
  readonly [K in keyof T]: T[K] extends object ? Immutable<T[K]> : T[K];
};

const config: Immutable<{ api: { url: string } }> = {
  api: { url: "https://api.example.com" }
};

config.api.url = "new"; // ❌ TS ошибка

// Const assertion (as const) для литеральных типов
const ROLES = ["admin", "user", "guest"] as const;
// ROLES: readonly ["admin", "user", "guest"]

const status = { code: 200, message: "OK" } as const;
// status: { readonly code: 200; readonly message: "OK"; }

5. Неизменяемость в React

В React это критично для оптимизации рендеринга:

// ❌ Плохо: мутируем состояние
const [user, setUser] = useState({ name: "john", age: 30 });

const updateAge = () => {
  user.age = 31; // Мутация! React не заметит изменение
  setUser(user); // React думает, что это тот же объект
};

// ✅ Хорошо: создаём новый объект
const updateAge = () => {
  setUser({ ...user, age: 31 }); // Новый объект = React заметит
};

// ✅ Альтернатива с readonly
interface User {
  readonly name: string;
  readonly age: number;
}

const [user, setUser] = useState<User>({ name: "john", age: 30 });

const updateAge = () => {
  // user.age = 31; // ❌ TS ошибка - readonly
  setUser({ ...user, age: 31 }); // ✓ Единственный способ
};

// Для массивов
const [items, setItems] = useState<readonly Item[]>([]);

const addItem = (item: Item) => {
  // items.push(item); // ❌ TS ошибка
  setItems([...items, item]); // ✓ Новый массив
};

const removeItem = (id: string) => {
  setItems(items.filter(item => item.id !== id)); // ✓ Новый массив
};

6. Паттерны для immutable обновлений

// Объекты
const updateUser = (user, updates) => ({
  ...user,      // spread старые значения
  ...updates    // перезаписываем нужные
});

const user = { name: "john", age: 30 };
const updated = updateUser(user, { age: 31 });
// user: { name: "john", age: 30 } - неизменён
// updated: { name: "john", age: 31 } - новый объект

// Массивы
const items = [1, 2, 3];
const added = [...items, 4];           // добавить
const removed = items.filter(x => x !== 2); // удалить
const mapped = items.map(x => x * 2);  // изменить
const updated = items.map((x, i) => i === 1 ? 20 : x); // обновить по индексу

// Вложенные обновления
const state = {
  user: { profile: { name: "john", settings: { theme: "dark" } } }
};

const updated = {
  ...state,
  user: {
    ...state.user,
    profile: {
      ...state.user.profile,
      settings: {
        ...state.user.profile.settings,
        theme: "light"
      }
    }
  }
};

// Или с immer библиотекой (проще)
import produce from 'immer';

const updated = produce(state, draft => {
  draft.user.profile.settings.theme = "light";
});

7. Как читать код и понимать immutability

// Сигналы неизменяемости:const variable;          // const = не переассигнится
✓ readonly property;       // TypeScript readonlyObject.freeze(obj);      // заморожено
✓ ...spread оператор;      // создаёт новый объект/массив
✓ .map(), .filter();       // создают новые массивы
✓ {...obj, prop: value};   // создаёт новый объект

// Сигналы мутации (опасно):let variable;           // может переассигниться
❌ obj.prop = value;       // прямая мутация
❌ array.push(), .pop();   // мутируют исходный
❌ array[0] = value;       // мутируют исходныйObject.assign(obj);     // может мутировать

// Быстрая оценка функции
function processUser(user) {
  const updatedUser = { ...user, age: user.age + 1 }; // ✓ immutable
  return updatedUser; // ✓ новый объект
}

const user = { name: "john", age: 30 };
const result = processUser(user);
// user: { name: "john", age: 30 } - не изменился
// result: { name: "john", age: 31 } - новый

Итоговые Rules of Thumb

  1. const + примитивы = гарантированно immutable
  2. const на объекте = защита только ссылки, не содержимого
  3. readonly в TypeScript = явное указание что не может меняться
  4. Object.freeze() = runtime защита
  5. Spread оператор и методы массива = создание новых значений
  6. Нет переправки, всегда новые объекты = React счастлив, баги исчезают

Вопрос "из кода понять что не могло поменяться" — смотрим на const/readonly, freeze(), использование spread, методы массива. Если видим прямую мутацию (obj.prop = value, arr.push()) — это может измениться.

Как понимаешь из кода что значение не могло поменяться? | PrepBro