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

Map модифицирует исходный массив или создает новый

1.0 Junior🔥 301 комментариев
#JavaScript Core

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

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

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

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.