Мутирующие методы изменяют исходный массив: push()
, pop()
, shift()
, unshift()
, splice()
, sort()
, reverse()
, fill()
. Немутирующие методы возвращают новый массив, не изменяя исходный: map()
, filter()
, slice()
, concat()
, join()
, find()
, includes()
, indexOf()
. Понимание этого различия критически важно для предотвращения неожиданных побочных эффектов в коде.
Ключевые принципы:
Мутация массива — это изменение исходного массива “на месте”. Когда метод мутирует массив, он изменяет содержимое оригинального объекта, а не создает новый.
// Пример с мутацией
const originalArray = [1, 2, 3];
const reference = originalArray;
originalArray.push(4); // Мутирующий метод
console.log(originalArray); // [1, 2, 3, 4]
console.log(reference); // [1, 2, 3, 4] — тоже изменился!
// Пример без мутации
const originalArray2 = [1, 2, 3];
const reference2 = originalArray2;
const newArray = originalArray2.concat(4); // Немутирующий метод
console.log(originalArray2); // [1, 2, 3] — не изменился
console.log(reference2); // [1, 2, 3] — не изменился
console.log(newArray); // [1, 2, 3, 4] — новый массив
const fruits = ['яблоко', 'банан'];
const length = fruits.push('апельсин');
console.log(fruits); // ['яблоко', 'банан', 'апельсин'] — изменился!
console.log(length); // 3 — возвращает новую длину
// Добавление нескольких элементов
fruits.push('груша', 'киви');
console.log(fruits); // ['яблоко', 'банан', 'апельсин', 'груша', 'киви']
Особенности:
const numbers = [1, 2, 3, 4, 5];
const removed = numbers.pop();
console.log(removed); // 5 — удаленный элемент
console.log(numbers); // [1, 2, 3, 4] — изменился!
// Удаление из пустого массива
const empty = [];
const result = empty.pop();
console.log(result); // undefined
console.log(empty); // [] — остался пустым
const colors = ['красный', 'зеленый', 'синий'];
const first = colors.shift();
console.log(first); // 'красный'
console.log(colors); // ['зеленый', 'синий'] — изменился!
const animals = ['кот', 'собака'];
const length = animals.unshift('птица');
console.log(animals); // ['птица', 'кот', 'собака'] — изменился!
console.log(length); // 3
// Добавление нескольких элементов
animals.unshift('рыба', 'хомяк');
console.log(animals); // ['рыба', 'хомяк', 'птица', 'кот', 'собака']
const letters = ['a', 'b', 'c', 'd', 'e'];
// Удаление элементов
const removed = letters.splice(1, 2); // С позиции 1 удалить 2 элемента
console.log(removed); // ['b', 'c'] — удаленные элементы
console.log(letters); // ['a', 'd', 'e'] — изменился!
// Добавление элементов
letters.splice(1, 0, 'x', 'y'); // В позицию 1 добавить 'x', 'y'
console.log(letters); // ['a', 'x', 'y', 'd', 'e']
// Замена элементов
letters.splice(1, 2, 'z'); // В позиции 1 заменить 2 элемента на 'z'
console.log(letters); // ['a', 'z', 'd', 'e']
const numbers = [3, 1, 4, 1, 5, 9];
const sorted = numbers.sort();
console.log(numbers); // [1, 1, 3, 4, 5, 9] — изменился!
console.log(sorted); // [1, 1, 3, 4, 5, 9] — та же ссылка
console.log(numbers === sorted); // true
// Сортировка чисел по возрастанию
const nums = [10, 5, 40, 25, 1000, 1];
nums.sort((a, b) => a - b);
console.log(nums); // [1, 5, 10, 25, 40, 1000]
// Сортировка строк
const words = ['банан', 'яблоко', 'апельсин'];
words.sort();
console.log(words); // ['апельсин', 'банан', 'яблоко']
const original = [1, 2, 3, 4, 5];
const reversed = original.reverse();
console.log(original); // [5, 4, 3, 2, 1] — изменился!
console.log(reversed); // [5, 4, 3, 2, 1] — та же ссылка
console.log(original === reversed); // true
const arr = [1, 2, 3, 4, 5];
// Заполнение всего массива
arr.fill(0);
console.log(arr); // [0, 0, 0, 0, 0] — изменился!
// Заполнение части массива
const arr2 = [1, 2, 3, 4, 5];
arr2.fill('x', 1, 4); // С позиции 1 до 4 (не включительно)
console.log(arr2); // [1, 'x', 'x', 'x', 5]
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(x => x * 2);
console.log(numbers); // [1, 2, 3, 4, 5] — не изменился!
console.log(doubled); // [2, 4, 6, 8, 10] — новый массив
// Преобразование объектов
const users = [{name: 'Иван', age: 25}, {name: 'Мария', age: 30}];
const names = users.map(user => user.name);
console.log(users); // Исходный массив не изменился
console.log(names); // ['Иван', 'Мария']
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const even = numbers.filter(x => x % 2 === 0);
console.log(numbers); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] — не изменился!
console.log(even); // [2, 4, 6, 8, 10] — новый массив
// Фильтрация объектов
const products = [
{name: 'Хлеб', price: 30},
{name: 'Молоко', price: 60},
{name: 'Мясо', price: 500}
];
const cheap = products.filter(product => product.price < 100);
console.log(products); // Исходный массив не изменился
console.log(cheap); // [{name: 'Хлеб', price: 30}, {name: 'Молоко', price: 60}]
const fruits = ['яблоко', 'банан', 'апельсин', 'груша', 'киви'];
const part = fruits.slice(1, 4);
console.log(fruits); // ['яблоко', 'банан', 'апельсин', 'груша', 'киви'] — не изменился!
console.log(part); // ['банан', 'апельсин', 'груша'] — новый массив
// Копирование всего массива
const copy = fruits.slice();
console.log(copy); // ['яблоко', 'банан', 'апельсин', 'груша', 'киви']
console.log(fruits === copy); // false — разные объекты
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const combined = arr1.concat(arr2);
console.log(arr1); // [1, 2, 3] — не изменился!
console.log(arr2); // [4, 5, 6] — не изменился!
console.log(combined); // [1, 2, 3, 4, 5, 6] — новый массив
// Объединение с отдельными элементами
const withElements = arr1.concat(7, 8, arr2);
console.log(withElements); // [1, 2, 3, 7, 8, 4, 5, 6]
const words = ['Привет', 'мир', 'JavaScript'];
const sentence = words.join(' ');
console.log(words); // ['Привет', 'мир', 'JavaScript'] — не изменился!
console.log(sentence); // 'Привет мир JavaScript' — строка
console.log(typeof sentence); // 'string'
// Разные разделители
const csv = words.join(', ');
console.log(csv); // 'Привет, мир, JavaScript'
const numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
// find() — поиск элемента
const found = numbers.find(x => x > 3);
console.log(numbers); // [1, 2, 3, 4, 5, 4, 3, 2, 1] — не изменился!
console.log(found); // 4
// indexOf() — поиск индекса
const index = numbers.indexOf(4);
console.log(index); // 3
// includes() — проверка наличия
const hasThree = numbers.includes(3);
console.log(hasThree); // true
// findIndex() — поиск индекса по условию
const foundIndex = numbers.findIndex(x => x > 4);
console.log(foundIndex); // 4
Метод | Мутирует | Возвращает | Назначение |
---|---|---|---|
push() | ✅ Да | Новая длина | Добавление в конец |
pop() | ✅ Да | Удаленный элемент | Удаление с конца |
shift() | ✅ Да | Удаленный элемент | Удаление с начала |
unshift() | ✅ Да | Новая длина | Добавление в начало |
splice() | ✅ Да | Массив удаленных | Универсальное изменение |
sort() | ✅ Да | Тот же массив | Сортировка |
reverse() | ✅ Да | Тот же массив | Переворот |
fill() | ✅ Да | Тот же массив | Заполнение |
map() | ❌ Нет | Новый массив | Преобразование |
filter() | ❌ Нет | Новый массив | Фильтрация |
slice() | ❌ Нет | Новый массив | Извлечение части |
concat() | ❌ Нет | Новый массив | Объединение |
join() | ❌ Нет | Строка | Преобразование в строку |
find() | ❌ Нет | Элемент/undefined | Поиск элемента |
indexOf() | ❌ Нет | Индекс/-1 | Поиск индекса |
includes() | ❌ Нет | Boolean | Проверка наличия |
// ❌ Неправильно — мутация состояния
function TodoList() {
const [todos, setTodos] = useState(['Задача 1', 'Задача 2']);
const addTodo = (newTodo) => {
todos.push(newTodo); // Мутация!
setTodos(todos); // React не обнаружит изменения
};
return /* JSX */;
}
// ✅ Правильно — создание нового массива
function TodoList() {
const [todos, setTodos] = useState(['Задача 1', 'Задача 2']);
const addTodo = (newTodo) => {
setTodos([...todos, newTodo]); // Новый массив
// или
// setTodos(todos.concat(newTodo));
};
const removeTodo = (index) => {
setTodos(todos.filter((_, i) => i !== index)); // Новый массив
};
return /* JSX */;
}
// ❌ Императивный стиль с мутациями
function processUsers(users) {
const result = [];
for (let i = 0; i < users.length; i++) {
if (users[i].active) {
const user = users[i];
user.displayName = `${user.firstName} ${user.lastName}`; // Мутация!
result.push(user);
}
}
result.sort((a, b) => a.age - b.age); // Мутация!
return result;
}
// ✅ Функциональный стиль без мутаций
function processUsers(users) {
return users
.filter(user => user.active)
.map(user => ({
...user,
displayName: `${user.firstName} ${user.lastName}`
}))
.slice() // Создаем копию перед сортировкой
.sort((a, b) => a.age - b.age);
}
// ❌ Опасно — функция изменяет входные данные
function addItemToCart(cart, item) {
cart.push(item); // Мутация входного параметра!
return cart;
}
const myCart = ['товар1', 'товар2'];
const updatedCart = addItemToCart(myCart, 'товар3');
console.log(myCart); // ['товар1', 'товар2', 'товар3'] — изменился!
// ✅ Безопасно — функция не изменяет входные данные
function addItemToCart(cart, item) {
return [...cart, item]; // Новый массив
// или
// return cart.concat(item);
}
const myCart2 = ['товар1', 'товар2'];
const updatedCart2 = addItemToCart(myCart2, 'товар3');
console.log(myCart2); // ['товар1', 'товар2'] — не изменился!
console.log(updatedCart2); // ['товар1', 'товар2', 'товар3']
function performanceTest() {
const size = 100000;
const testArray = Array.from({length: size}, (_, i) => i);
// Тест 1: Мутирующий push vs немутирующий concat
console.time('Мутирующий push');
const arr1 = [];
for (let i = 0; i < size; i++) {
arr1.push(i);
}
console.timeEnd('Мутирующий push');
console.time('Немутирующий concat');
let arr2 = [];
for (let i = 0; i < size; i++) {
arr2 = arr2.concat(i); // Создает новый массив каждый раз!
}
console.timeEnd('Немутирующий concat');
// Тест 2: Мутирующий sort vs немутирующий slice+sort
const unsorted = [...testArray].reverse();
console.time('Мутирующий sort');
const sorted1 = [...unsorted];
sorted1.sort((a, b) => a - b);
console.timeEnd('Мутирующий sort');
console.time('Немутирующий slice+sort');
const sorted2 = unsorted.slice().sort((a, b) => a - b);
console.timeEnd('Немутирующий slice+sort');
}
performanceTest();
Операция | Мутирующий | Немутирующий | Разница |
---|---|---|---|
Добавление элементов | push() ~1мс | concat() ~500мс | 500x медленнее |
Сортировка | sort() ~10мс | slice()+sort() ~12мс | 1.2x медленнее |
Удаление элемента | splice() ~1мс | filter() ~5мс | 5x медленнее |
// ✅ Используйте мутирующие методы для производительности
function buildLargeArray() {
const result = [];
for (let i = 0; i < 1000000; i++) {
result.push(i); // Быстро
}
return result;
}
// ✅ Используйте немутирующие методы для безопасности
function processUserData(users) {
return users
.filter(user => user.active)
.map(user => ({...user, processed: true})); // Безопасно
}
// ✅ Создавайте копию перед мутацией
function sortSafely(array) {
return [...array].sort(); // Копия + мутация
// или
// return array.slice().sort();
}
const users = [
{id: 1, name: 'Иван', active: true},
{id: 2, name: 'Мария', active: false},
{id: 3, name: 'Петр', active: true}
];
// ❌ Неправильно — мутация объектов
function activateUser(users, userId) {
const user = users.find(u => u.id === userId);
if (user) {
user.active = true; // Мутация объекта!
}
return users;
}
// ✅ Правильно — создание новых объектов
function activateUser(users, userId) {
return users.map(user =>
user.id === userId
? {...user, active: true} // Новый объект
: user // Тот же объект
);
}
// ✅ Безопасная цепочка немутирующих методов
const result = users
.filter(user => user.age >= 18)
.map(user => ({...user, adult: true}))
.slice(0, 10)
.sort((a, b) => a.name.localeCompare(b.name));
// ❌ Опасная цепочка с мутирующими методами
const result2 = users
.filter(user => user.age >= 18)
.sort((a, b) => a.name.localeCompare(b.name)) // Мутирует отфильтрованный массив
.slice(0, 10);
// Вместо мутирующих методов
const arr = [1, 2, 3];
// push() → spread
const withPush = [...arr, 4]; // [1, 2, 3, 4]
// unshift() → spread
const withUnshift = [0, ...arr]; // [0, 1, 2, 3]
// splice() для добавления → spread
const withInsert = [...arr.slice(0, 1), 'new', ...arr.slice(1)];
// [1, 'new', 2, 3]
// concat() → spread
const arr2 = [4, 5, 6];
const combined = [...arr, ...arr2]; // [1, 2, 3, 4, 5, 6]
// toSorted() — немутирующая сортировка (ES2023)
const numbers = [3, 1, 4, 1, 5];
const sorted = numbers.toSorted(); // Новый массив
console.log(numbers); // [3, 1, 4, 1, 5] — не изменился
console.log(sorted); // [1, 1, 3, 4, 5]
// toReversed() — немутирующий переворот (ES2023)
const reversed = numbers.toReversed(); // Новый массив
console.log(numbers); // [3, 1, 4, 1, 5] — не изменился
console.log(reversed); // [5, 1, 4, 1, 3]
// with() — немутирующая замена элемента (ES2023)
const updated = numbers.with(0, 999); // Заменить элемент по индексу 0
console.log(numbers); // [3, 1, 4, 1, 5] — не изменился
console.log(updated); // [999, 1, 4, 1, 5]
// Immutable.js
import { List } from 'immutable';
const list = List([1, 2, 3]);
const newList = list.push(4); // Всегда возвращает новый объект
console.log(list.toArray()); // [1, 2, 3]
console.log(newList.toArray()); // [1, 2, 3, 4]
// Immer
import produce from 'immer';
const state = {items: [1, 2, 3]};
const newState = produce(state, draft => {
draft.items.push(4); // Выглядит как мутация, но создает новый объект
});
console.log(state.items); // [1, 2, 3]
console.log(newState.items); // [1, 2, 3, 4]
// Что выведет этот код?
const arr1 = [1, 2, 3];
const arr2 = arr1;
const arr3 = arr1.slice();
arr1.push(4);
arr2.unshift(0);
arr3.pop();
console.log(arr1);
console.log(arr2);
console.log(arr3);
console.log(arr1); // [0, 1, 2, 3, 4]
console.log(arr2); // [0, 1, 2, 3, 4] — та же ссылка, что и arr1
console.log(arr3); // [1, 2] — независимая копия
// Создайте функцию, которая удаляет элемент по индексу БЕЗ мутации
function removeAtIndex(array, index) {
// Ваш код
}
console.log(removeAtIndex([1, 2, 3, 4, 5], 2)); // [1, 2, 4, 5]
const original = [1, 2, 3, 4, 5];
const result = removeAtIndex(original, 2);
console.log(original); // [1, 2, 3, 4, 5] — не должен измениться
function removeAtIndex(array, index) {
return array.filter((_, i) => i !== index);
// или
// return [...array.slice(0, index), ...array.slice(index + 1)];
// или
// return array.slice(0, index).concat(array.slice(index + 1));
}
// Исправьте функцию, чтобы она не мутировала входной массив
function processNumbers(numbers) {
numbers.sort((a, b) => a - b);
numbers.reverse();
return numbers.slice(0, 3);
}
const nums = [5, 2, 8, 1, 9, 3];
const top3 = processNumbers(nums);
console.log(nums); // Должен остаться [5, 2, 8, 1, 9, 3]
console.log(top3); // [9, 8, 5]
function processNumbers(numbers) {
return numbers
.slice() // Создаем копию
.sort((a, b) => a - b)
.reverse()
.slice(0, 3);
// или более эффективно:
// return numbers
// .slice()
// .sort((a, b) => b - a) // Сразу по убыванию
// .slice(0, 3);
// или с spread:
// return [...numbers]
// .sort((a, b) => b - a)
// .slice(0, 3);
}
Понимание различий между мутирующими и немутирующими методами массивов критически важно для написания предсказуемого и безопасного кода. Мутирующие методы быстрее, но могут вызывать побочные эффекты. Немутирующие методы безопаснее, но могут быть медленнее для больших объемов данных.
Основные принципы:
Хотите больше статей для подготовки к собеседованиям? Подписывайтесь на EasyAdvice(@AleksandrEmolov_EasyAdvice), добавляйте сайт в закладки и совершенствуйтесь каждый день 💪