Map модифицирует исходный массив или создает новый
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Map: модифицирует или создает новый массив?
Map создает новый массив и НЕ модифицирует исходный. Это критически важное поведение для функционального программирования в JavaScript и одна из основных причин, почему map столь популярен. За 10+ лет разработки я вижу, что правильное понимание этого различия - основа чистого и безошибочного кода.
Основное поведение
Array.prototype.map() создает новый массив:
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(num => num * 2);
console.log(doubled); // [2, 4, 6, 8, 10]
console.log(numbers); // [1, 2, 3, 4, 5] (НЕ изменился!)
console.log(doubled === numbers); // false (разные массивы)
Это чистая функция - она не имеет побочных эффектов.
Почему это важно: Immutability (Неизменяемость)
Функциональное программирование предпочитает неизменяемость:
// ❌ Плохо: mutating the array (side effects)
const users = [{id: 1, name: 'Alice'}, {id: 2, name: 'Bob'}];
for (let i = 0; i < users.length; i++) {
users[i].name = users[i].name.toUpperCase();
}
console.log(users); // Изменилась исходная переменная!
// [{id: 1, name: 'ALICE'}, {id: 2, name: 'BOB'}]
✅ Хорошо: используем map (immutable)
const users = [{id: 1, name: 'Alice'}, {id: 2, name: 'Bob'}];
const uppercasedUsers = users.map(user => ({
...user,
name: user.name.toUpperCase()
}));
console.log(users); // Исходный массив не изменился
console.log(uppercasedUsers); // Новый массив с изменениями
Второй подход безопаснее и предсказуемее.
Детальные примеры
1. Примитивные значения
const numbers = [1, 2, 3];
const mapped = numbers.map(x => x * 2);
// Эти массивы совершенно независимы
numbers[0] = 999;
console.log(mapped); // [2, 4, 6] (не изменился)
2. Объекты и ссылки (важно!)
const users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' }
];
// Map создает новый массив, но объекты внутри - ТЕ ЖЕ ссылки!
const mapped = users.map(user => user);
// Это работает!
users[0].name = 'Charlie';
console.log(mapped[0].name); // 'Charlie' (ИЗМЕНИЛСЯ!)
console.log(users === mapped); // false (разные массивы)
console.log(users[0] === mapped[0]); // true (один и тот же объект)
Решение: создать новые объекты
const mapped = users.map(user => ({
...user // Spread operator создает новый объект!
}));
// Теперь всё независимо
users[0].name = 'Charlie';
console.log(mapped[0].name); // 'Alice' (не изменился)
3. Глубокое копирование
const complex = [
{ id: 1, tags: ['js', 'react'] },
{ id: 2, tags: ['python', 'django'] }
];
// Поверхностное копирование (spread создает shallow copy)
const mapped = complex.map(item => ({ ...item }));
// Массивы tags всё ещё указывают на исходные!
mapped[0].tags.push('typescript');
console.log(complex[0].tags); // ['js', 'react', 'typescript'] ИЗМЕНИЛСЯ!
// Решение: глубокое копирование
const deepMapped = complex.map(item => ({
...item,
tags: [...item.tags] // Копируем и вложенный массив
}));
mapped[0].tags.push('typescript');
console.log(complex[0].tags); // ['js', 'react'] (не изменился)
Сравнение с другими методами
forEach - модифицирует, не возвращает значение
const numbers = [1, 2, 3];
numbers.forEach((num, index) => {
numbers[index] = num * 2; // Модифицируем исходный массив!
});
console.log(numbers); // [2, 4, 6] (ИЗМЕНИЛСЯ)
filter - создает новый массив (как map)
const numbers = [1, 2, 3, 4, 5];
const even = numbers.filter(x => x % 2 === 0);
console.log(even); // [2, 4]
console.log(numbers); // [1, 2, 3, 4, 5] (не изменился)
reduce - может модифицировать или нет
// Может создать новый объект (хорошо)
const result = numbers.reduce((acc, num) => {
return { ...acc, [num]: num * 2 }; // Новый объект
}, {});
// Или модифицировать (плохо)
const bad = numbers.reduce((acc, num) => {
acc[num] = num * 2; // Модифицируем accumulator
return acc;
}, {});
Практические примеры из реальной работы
1. React компонент с обновлением списка
function UserList() {
const [users, setUsers] = useState([...initialUsers]);
const handleNameChange = (id, newName) => {
// ❌ НЕПРАВИЛЬНО (React не заметит изменение)
// users[users.findIndex(u => u.id === id)].name = newName;
// setUsers(users);
// ✅ ПРАВИЛЬНО (создаем новый массив)
const updated = users.map(user =>
user.id === id ? { ...user, name: newName } : user
);
setUsers(updated);
};
return (
<ul>
{users.map(user => (
<li key={user.id}>
{user.name}
<button onClick={() => handleNameChange(user.id, 'NewName')}>
Update
</button>
</li>
))}
</ul>
);
}
2. Трансформация данных из API
async function fetchAndTransform() {
const response = await fetch('/api/products');
const products = await response.json();
// Создаем новый массив с трансформированными данными
const uiProducts = products.map(product => ({
id: product.id,
displayName: product.title.toUpperCase(),
displayPrice: `$${product.price.toFixed(2)}`,
available: product.stock > 0
}));
return uiProducts;
}
3. Чейнинг операций
const users = [
{ id: 1, name: 'Alice', age: 25 },
{ id: 2, name: 'Bob', age: 17 },
{ id: 3, name: 'Charlie', age: 30 }
];
const result = users
.filter(user => user.age >= 18) // Создает новый массив
.map(user => ({ ...user, adult: true })) // Создает новый массив
.map(user => user.name) // Создает новый массив
.sort(); // Создает новый массив
console.log(result); // ['Alice', 'Charlie']
console.log(users); // (исходный не изменился)
Производительность
Создание новых массивов - cost
const large = Array.from({length: 1000000}, (_, i) => i);
// Это медленнее, чем модификация
const mapped1 = large.map(x => x * 2); // Новый массив в памяти
// Но часто это приемлемо благодаря оптимизациям движка
// Для huge arrays используй типизированные массивы
const typed = new Float64Array(1000000);
for (let i = 0; i < typed.length; i++) {
typed[i] = i * 2;
}
Best Practices
// 1. Используй map когда трансформируешь элементы
const doubled = numbers.map(x => x * 2);
// 2. Используй filter когда выбираешь элементы
const positive = numbers.filter(x => x > 0);
// 3. Комбинируй map и filter для сложных операций
const result = numbers
.filter(x => x > 0)
.map(x => x * 2);
// 4. Копируй объекты внутри map для полной независимости
const copied = objects.map(obj => ({ ...obj }));
// 5. Избегай побочных эффектов в map
// ❌ array.map((x) => {
// global.counter++; // side effect!
// return x * 2;
// });
Резюме
Map создает новый массив и НЕ модифицирует исходный. Это чистая функция, идеальная для функционального программирования. Однако, нужно помнить, что при работе с объектами map создает shallow copy - объекты внутри всё ещё указывают на исходные ссылки. Для полной независимости нужно копировать объекты через spread оператор или глубокое копирование. Это поведение делает map одним из самых важных методов массива в современном JavaScript.