Вставка элемента в массив по индексу: как работает под капотом в JS

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение: Почему вопрос о вставке элемента в массив так популярен?

Представьте: вы пишете код, и вам нужно добавить новую задачу в середину списка дел, или вставить пользователя в отсортированный массив по алфавиту. Казалось бы, что сложного? Но если вы новичок в JavaScript, то первая мысль — использовать push() или unshift(). Однако они добавляют элемент только в конец или начало. А как быть, если нужен конкретный индекс?

Именно этот вопрос собрал тысячи просмотров на Stack Overflow. В этой статье мы не просто дадим готовый ответ, а заглянем под капот JavaScript-движка и разберем, как на самом деле работает вставка элемента в массив. Вы узнаете не только магию метода splice(), но и поймете, какие процессы происходят в памяти. Поехали!

Прямой ответ: как вставить элемент по индексу

Самый простой и правильный способ — использовать метод splice(). Этот метод изменяет содержимое массива, удаляя, заменяя или добавляя элементы. Вот как выглядит классическая вставка:

let fruits = ['apple', 'banana', 'cherry'];
let index = 1; // Вставляем после 'apple'
let itemToInsert = 'orange';

fruits.splice(index, 0, itemToInsert);
console.log(fruits); // ['apple', 'orange', 'banana', 'cherry']

Разберем синтаксис:

  • Первый аргумент (index) — индекс, с которого начинаем изменять массив.
  • Второй аргумент (0) — количество удаляемых элементов. Ноль означает, что мы ничего не удаляем, а только вставляем.
  • Третий и последующие аргументы — элементы, которые нужно добавить. Можно передать несколько значений.

Этот метод — универсальный швейцарский нож для работы с массивами. Но как он работает внутри? Давайте разберем.

Под капотом: как splice() двигает элементы

Когда вы вызываете splice(), JavaScript-движок (например, V8 в Chrome или SpiderMonkey в Firefox) выполняет несколько шагов:

1. Проверка границ и типа

Движок проверяет, является ли индекс числом и находится ли он в пределах длины массива. Если индекс отрицательный, он преобразуется в length + index. Если вы передаете не число — происходит приведение к числу (часто к 0 или NaN).

2. Создание "дыры" в массиве

Допустим, у нас массив [A, B, C], и мы вставляем X на позицию 1. Движок должен освободить место. Он не просто "записывает" X поверх B. Вместо этого он сдвигает все элементы, начиная с индекса 1, на одну позицию вправо. То есть B становится на место C, C — на новую позицию (индекс 3). Это называется сдвигом.

3. Оптимизация: непрерывный массив vs. sparse array

JavaScript массивы — это не просто последовательность ячеек памяти, как в C. Это объекты с особым поведением. Движок пытается хранить данные в непрерывном буфере (как в типизированных массивах). Если вставлять элементы в середину, движок вынужден перераспределять память: выделять новый блок большего размера и копировать туда все элементы. Это операция O(n) (линейная сложность). Чем больше массив, тем дольше вставка.

Если массив разреженный (sparse), то есть имеет "дыры" (например, [1, , 3]), движок переключается на хранение в виде хеш-таблицы. Вставка в такой массив может быть быстрее, но потребляет больше памяти.

4. Обновление длины

После сдвига и вставки движок обновляет свойство length массива. Если вы вставили один элемент, длина увеличится на 1.

Вот упрощенная визуализация процесса:

// Исходный массив в памяти: [A, B, C]
// Вставляем X на индекс 1
// Шаг 1: Сдвиг: [A, B, C, undefined] (выделяем место)
// Шаг 2: Копирование: [A, B, B, C] (сдвигаем B и C)
// Шаг 3: Вставка: [A, X, B, C]
// Шаг 4: Обновление length: 4

Важно: splice() возвращает массив удаленных элементов. В нашем случае — пустой массив [], так как мы удалили 0 элементов.

Альтернативные способы вставки (и почему они хуже)

Иногда разработчики пытаются изобрести велосипед. Давайте разберем популярные альтернативы и их недостатки.

Использование spread-оператора (ES6)

let arr = [1, 2, 4];
let newArr = [...arr.slice(0, 2), 3, ...arr.slice(2)];
console.log(newArr); // [1, 2, 3, 4]

Плюсы: Не мутирует исходный массив (иммутабельность). Минусы: Создает два новых подмассива и один новый массив. Потребление памяти в 2-3 раза выше, чем у splice(). Для больших массивов (10 000+ элементов) это может вызвать задержки.

Самописный цикл

function insertAt(arr, index, item) {
    for (let i = arr.length; i > index; i--) {
        arr[i] = arr[i - 1];
    }
    arr[index] = item;
}

Это буквально то же самое, что делает splice(), но без оптимизаций движка. Нативный splice() написан на C++ и работает быстрее. Не изобретайте велосипед.

Метод push() + сортировка

Некоторые добавляют элемент в конец, а затем сортируют массив. Это ужасная идея: сортировка в среднем имеет сложность O(n log n), тогда как вставка через splice — O(n). Плюс вы рискуете изменить порядок других элементов, если у вас нет уникального ключа для сортировки.

Продвинутые техники: вставка нескольких элементов

splice() позволяет вставить сразу несколько элементов:

let numbers = [10, 20, 50];
numbers.splice(2, 0, 30, 40);
console.log(numbers); // [10, 20, 30, 40, 50]

Под капотом движок делает сдвиг один раз, а затем последовательно записывает все новые элементы. Это эффективнее, чем вызывать splice() несколько раз.

Иммутабельная вставка: когда нельзя мутировать

В React или Redux мутировать исходный массив — плохая практика. Используйте toSpliced() (ES2023) или spread-оператор

Блоги

Рекомендаций по книгам