Иммутабельность и функции
Иммутабельность — подход, при котором данные не изменяются после создания: вместо мутации исходного объекта/массива создаётся новый с нужными изменениями.
Зачем нужно
Мутация данных — частый источник трудноуловимых багов: функция изменила переданный объект, другая часть кода получила неожиданный результат. Иммутабельный подход делает поток данных предсказуемым, упрощает отладку, позволяет сравнивать состояния по ссылке (===) и является основой реактивных систем (React, Redux).
Где используется
- Функции без побочных эффектов (pure functions)
- Redux/состояние в React — reducer-паттерн
- Обновление вложенных объектов без мутации
- Массивы: создание новых вместо изменения существующих
Чистые функции (Pure Functions)
// Нечистая: мутирует аргумент
function addItem(cart, item) {
cart.items.push(item); // изменяет переданный объект!
return cart;
}
// Чистая: возвращает новый объект
function addItem(cart, item) {
return {
...cart,
items: [...cart.items, item],
};
}
Иммутабельная работа с объектами
const user = { name: 'Иван', age: 30, address: { city: 'Москва' } };
// Изменить поверхностное свойство
const updated = { ...user, age: 31 };
console.log(user.age); // 30 — оригинал не изменён
console.log(updated.age); // 31
// Изменить вложенное свойство (deep update)
const movedUser = {
...user,
address: { ...user.address, city: 'Питер' },
};
console.log(user.address.city); // 'Москва'
console.log(movedUser.address.city); // 'Питер'
Иммутабельная работа с массивами
const arr = [1, 2, 3, 4, 5];
// Добавить элемент (вместо push)
const withNew = [...arr, 6];
// Удалить элемент (вместо splice)
const without3 = arr.filter(n => n !== 3);
// Изменить элемент по индексу
const withUpdated = arr.map((n, i) => i === 1 ? 99 : n);
// Вставить в середину
const withInserted = [...arr.slice(0, 2), 99, ...arr.slice(2)];
console.log(arr); // [1, 2, 3, 4, 5] — оригинал не изменён
Object.freeze для глубокой иммутабельности
const config = Object.freeze({
host: 'localhost',
port: 3000,
db: Object.freeze({ name: 'mydb' }), // нужно замораживать вложенное тоже
});
config.port = 8080; // молча игнорируется (TypeError в strict mode)
config.db.name = 'other'; // тоже не изменится — заморожено
console.log(config.port); // 3000
Reducer-паттерн
// Redux-стиль: (state, action) => newState
function cartReducer(state = { items: , total: 0 }, action) {
switch (action.type) {
case 'ADD_ITEM':
return {
...state,
items: [...state.items, action.payload],
total: state.total + action.payload.price,
};
case 'REMOVE_ITEM':
return {
...state,
items: state.items.filter(item => item.id !== action.payload),
};
default:
return state;
}
}
Частые ошибки
- Поверхностная копия объекта не защищает вложенные данные —
{ ...obj }копирует только первый уровень; вложенные объекты остаются общими ссылками. Object.freezeне рекурсивна — замораживает только сам объект, не его вложенные свойства.- Перфоманс-опасения — создание копий через
spreadимеет O(n) стоимость, но для типичных объектов состояния это незначительно.
Связанные темы
- _MOC Функции
- _MOC Паттерны
- Деструктуризация объектов
- Деструктуризация массивов
- Методы массивов -- map, filter, reduce