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

Как мутировать массив?

1.7 Middle🔥 191 комментариев
#JavaScript Core

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

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

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

Мутирование массивов в JavaScript

Это вопрос о различии между мутирующими и немутирующими методами работы с массивами. Важно понимать обе подходы для правильного управления состоянием, особенно в React.

1. Что такое мутирование

Мутирование — это изменение исходного массива, а не создание нового. В функциональном программировании и React это часто считается нежелательным, но иногда необходимым.

// Мутирование - изменяет исходный массив
const arr = [1, 2, 3];
arr[0] = 10;  // arr теперь [10, 2, 3]
console.log(arr);  // [10, 2, 3]

// Без мутирования - создает новый массив
const arr = [1, 2, 3];
const newArr = [...arr];
newArr[0] = 10;
console.log(arr);  // [1, 2, 3] - оригинал не изменился
console.log(newArr);  // [10, 2, 3]

2. Мутирующие методы массивов

Эти методы изменяют исходный массив.

const arr = [1, 2, 3, 4, 5];

// push() - добавляет элемент в конец
arr.push(6);  // [1, 2, 3, 4, 5, 6]

// pop() - удаляет последний элемент
arr.pop();  // [1, 2, 3, 4, 5]

// shift() - удаляет первый элемент
arr.shift();  // [2, 3, 4, 5]

// unshift() - добавляет элемент в начало
arr.unshift(1);  // [1, 2, 3, 4, 5]

// splice() - удаляет/заменяет элементы
arr.splice(2, 1, 'new');  // [1, 2, 'new', 4, 5]

// reverse() - разворачивает массив
arr.reverse();  // [5, 4, 'new', 2, 1]

// sort() - сортирует массив
arr.sort();  // изменяет исходный массив

// fill() - заполняет массив значением
arr.fill(0);  // [0, 0, 0, 0, 0]

// copyWithin() - копирует часть массива
arr.copyWithin(0, 3, 5);  // копирует элементы с индекса 3 на 0

3. Немутирующие (иммutable) методы

Эти методы создают новый массив, оставляя оригинал нетронутым.

const arr = [1, 2, 3, 4, 5];

// concat() - объединяет массивы
const newArr = arr.concat([6, 7]);  // [1, 2, 3, 4, 5, 6, 7]

// slice() - создает копию части массива
const newArr = arr.slice(1, 3);  // [2, 3]

// map() - трансформирует элементы
const newArr = arr.map(x => x * 2);  // [2, 4, 6, 8, 10]

// filter() - фильтрует элементы
const newArr = arr.filter(x => x > 2);  // [3, 4, 5]

// reduce() - сводит к одному значению
const sum = arr.reduce((acc, x) => acc + x, 0);  // 15

// spread operator - создает копию
const newArr = [...arr];  // [1, 2, 3, 4, 5]

// find() - находит элемент (не мутирует)
const found = arr.find(x => x > 3);  // 4

// includes() - проверяет наличие (не мутирует)
const has = arr.includes(3);  // true

4. Мутирование vs Иммутабельность в React

В React обычно нужно избегать прямого мутирования состояния.

// ПЛОХО - мутирует состояние напрямую
function MyComponent() {
  const [items, setItems] = useState([1, 2, 3]);
  
  const addItem = () => {
    items.push(4);  // Не работает! React не заметит изменения
    setItems(items);  // React видит, что reference тот же
  };
  
  return <button onClick={addItem}>Add</button>;
}

// ХОРОШО - создает новый массив
function MyComponent() {
  const [items, setItems] = useState([1, 2, 3]);
  
  const addItem = () => {
    setItems([...items, 4]);  // Новый массив
    // или
    setItems(items.concat(4));
    // или
    setItems([...items.slice(0, items.length), 4]);
  };
  
  return <button onClick={addItem}>Add</button>;
}

5. Примеры иммутабельных операций

// Добавить элемент
const arr = [1, 2, 3];
const newArr = [...arr, 4];  // [1, 2, 3, 4]

// Удалить элемент по индексу
const arr = [1, 2, 3, 4, 5];
const index = 2;
const newArr = [...arr.slice(0, index), ...arr.slice(index + 1)];  // [1, 2, 4, 5]

// Заменить элемент
const arr = [1, 2, 3, 4, 5];
const index = 2;
const newArr = [...arr.slice(0, index), 10, ...arr.slice(index + 1)];  // [1, 2, 10, 4, 5]

// Обновить элемент по условию
const arr = [1, 2, 3, 4, 5];
const newArr = arr.map(x => x === 3 ? 30 : x);  // [1, 2, 30, 4, 5]

// Удалить элемент по условию
const arr = [1, 2, 3, 4, 5];
const newArr = arr.filter(x => x !== 3);  // [1, 2, 4, 5]

// Вставить элемент в позицию
const arr = [1, 2, 4, 5];
const index = 2;
const value = 3;
const newArr = [...arr.slice(0, index), value, ...arr.slice(index)];  // [1, 2, 3, 4, 5]

// Сортировать без мутирования
const arr = [3, 1, 4, 1, 5];
const newArr = [...arr].sort((a, b) => a - b);  // [1, 1, 3, 4, 5]
const original = [3, 1, 4, 1, 5];  // не изменился

6. Мутирование с splice()

Когда мутирование необходимо, используй splice().

// splice() мутирует исходный массив
const arr = [1, 2, 3, 4, 5];

// Удалить 2 элемента, начиная с индекса 1
arr.splice(1, 2);  // arr теперь [1, 4, 5]

// Вставить элементы без удаления
const arr = [1, 2, 3];
arr.splice(1, 0, 'a', 'b');  // arr теперь [1, 'a', 'b', 2, 3]

// Заменить элементы
const arr = [1, 2, 3, 4, 5];
arr.splice(1, 2, 'a', 'b');  // arr теперь [1, 'a', 'b', 4, 5]

// splice() возвращает массив удаленных элементов
const arr = [1, 2, 3, 4, 5];
const removed = arr.splice(1, 2);  // removed = [2, 3], arr = [1, 4, 5]

7. Глубокое копирование (для вложенных структур)

// Поверхностное копирование (shallow copy)
const original = [{ id: 1, name: 'John' }, { id: 2, name: 'Jane' }];
const copy = [...original];
copy[0].name = 'Changed';
console.log(original[0].name);  // 'Changed' - мутировал!

// Глубокое копирование (deep copy)
const copy = JSON.parse(JSON.stringify(original));
copy[0].name = 'Changed';
console.log(original[0].name);  // 'John' - не мутировал

// structuredClone (новый способ)
const copy = structuredClone(original);
copy[0].name = 'Changed';
console.log(original[0].name);  // 'John'

// С использованием map для объектов
const copy = original.map(obj => ({ ...obj }));
copy[0].name = 'Changed';
console.log(original[0].name);  // 'John'

8. Библиотеки для иммутабельности

// Immer - самая популярная библиотека для иммутабельных обновлений
import produce from 'immer';

const arr = [1, 2, 3, 4, 5];

// Пишешь как будто мутируешь, но создается новый массив
const newArr = produce(arr, draft => {
  draft[0] = 10;
  draft.push(6);
  draft.splice(1, 2);
});

// В React с useState
const [items, setItems] = useState([1, 2, 3]);

const addItem = () => {
  setItems(produce(items, draft => {
    draft.push(4);
  }));
};

9. Когда мутировать нельзя

// ПЛОХО в React/Redux
function reducer(state = [], action) {
  switch (action.type) {
    case 'ADD':
      state.push(action.payload);  // НЕПРАВИЛЬНО!
      return state;
    
    case 'REMOVE':
      state.splice(action.index, 1);  // НЕПРАВИЛЬНО!
      return state;
  }
}

// ХОРОШО в React/Redux
function reducer(state = [], action) {
  switch (action.type) {
    case 'ADD':
      return [...state, action.payload];
    
    case 'REMOVE':
      return state.filter((_, i) => i !== action.index);
    
    default:
      return state;
  }
}

10. Таблица методов

МетодМутируетСоздает новый
push()ДаНет
pop()ДаНет
shift()ДаНет
unshift()ДаНет
splice()ДаНет
reverse()ДаНет
sort()ДаНет
concat()НетДа
slice()НетДа
map()НетДа
filter()НетДа
reduce()НетНет (возвращает значение)
[...arr]НетДа

Лучшие практики

  1. В React - всегда используй иммутабельные методы
  2. В Redux - создавай новые массивы, никогда не мутируй state
  3. Для производительности - используй структурное копирование [...arr]
  4. Для сложных случаев - используй Immer для читаемости
  5. Глубокое копирование - используй structuredClone или JSON методы для вложенных структур

В современной разработке фреймворки (React, Vue) полагаются на иммутабельность для отслеживания изменений. Поэтому хорошее понимание мутирующих и немутирующих методов критично для разработчика.