Как понимаешь из кода что значение не могло поменяться?
Комментарии (2)
Ответ сгенерирован нейросетью и может содержать ошибки
Понимание 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 readonly
✓ Object.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
- const + примитивы = гарантированно immutable
- const на объекте = защита только ссылки, не содержимого
- readonly в TypeScript = явное указание что не может меняться
- Object.freeze() = runtime защита
- Spread оператор и методы массива = создание новых значений
- Нет переправки, всегда новые объекты = React счастлив, баги исчезают
Вопрос "из кода понять что не могло поменяться" — смотрим на const/readonly, freeze(), использование spread, методы массива. Если видим прямую мутацию (obj.prop = value, arr.push()) — это может измениться.