Как добавить элемент в начало массива в JavaScript: разбор под капотом

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

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

Начать курс

Введение: почему этот вопрос собрал тысячи просмотров на Stack Overflow

Каждый, кто хоть раз писал на JavaScript, сталкивался с массивами. Они повсюду: списки товаров, очереди задач, история действий пользователя. И рано или поздно встает вопрос: «Как добавить элемент в начало массива в JavaScript?» На Stack Overflow этот вопрос набрал сотни тысяч просмотров, а ответов — десятки. Почему? Потому что за кажущейся простотой скрывается целый пласт знаний: от производительности до внутреннего устройства движка.

В этой статье мы не просто покажем unshift(). Мы заглянем «под капот»: как движок V8 обрабатывает вставку в начало, почему unshift может быть медленнее push, и какие альтернативы существуют для разных сценариев. Поехали!

Базовый способ: метод unshift()

Самый очевидный и распространенный способ — использовать встроенный метод unshift(). Он добавляет один или несколько элементов в начало массива и возвращает новую длину массива.

let fruits = ['banana', 'orange'];
let newLength = fruits.unshift('apple');
console.log(fruits); // ['apple', 'banana', 'orange']
console.log(newLength); // 3

Можно добавить сразу несколько элементов:

let numbers = [3, 4, 5];
numbers.unshift(1, 2);
console.log(numbers); // [1, 2, 3, 4, 5]

На этом можно было бы и закончить. Но давайте разберемся, что происходит внутри, когда мы вызываем unshift().

Как работает unshift() под капотом: взгляд на V8

Массив в JavaScript — это не просто непрерывный блок памяти, как в C. В современных движках (V8 в Chrome/Node.js, SpiderMonkey в Firefox) массивы могут быть реализованы двумя способами:

  • Fast elements (упакованные) — непрерывный буфер, как в C-стиле. Оптимальны для чтения и записи по индексу.
  • Dictionary elements (разреженные) — хеш-таблица. Используются, когда массив становится «дырявым» (пропуски индексов) или слишком большим.

Когда вы вызываете unshift() на fast-массиве, движок делает следующее:

  1. Выделяет новую память (или сдвигает существующую) на количество добавляемых элементов.
  2. Сдвигает все существующие элементы вправо на n позиций.
  3. Записывает новые элементы в освободившиеся ячейки в начале.

Почему unshift медленнее push?

Метод push() добавляет элемент в конец — ему не нужно ничего сдвигать. unshift() же переписывает индексы для каждого существующего элемента. Если в массиве 1000 элементов, unshift вынужден сдвинуть все 1000. push просто дописывает в конец.

В V8 есть оптимизация: если массив пустой или почти пустой, сдвиг почти бесплатен. Но для больших массивов разница в производительности может быть в десятки раз. Особенно если делать unshift в цикле.

// Плохо: O(n²) из-за постоянного сдвига
let arr = [];
for (let i = 0; i < 10000; i++) {
  arr.unshift(i);
}

// Хорошо: O(n) — добавляем в конец, потом разворачиваем
let arr2 = [];
for (let i = 0; i < 10000; i++) {
  arr2.push(i);
}
arr2.reverse();

Альтернатива: spread-оператор (ES6+)

Современный и элегантный способ — использовать spread-оператор ... для создания нового массива:

let oldArray = [2, 3, 4];
let newArray = [1, ...oldArray];
console.log(newArray); // [1, 2, 3, 4]

Этот подход не мутирует исходный массив, что часто удобно в функциональном программировании (React, Redux). Но помните: создается новый массив, а старые элементы копируются. Для гигантских массивов это может быть дорого по памяти.

Добавление нескольких элементов в начало

let base = ['world', '!'];
let result = ['Hello', ', ', ...base];
console.log(result); // ['Hello', ', ', 'world', '!']

Метод splice(): универсальный, но опасный

splice() позволяет добавлять элементы в любую позицию. Для вставки в начало используйте индекс 0:

let colors = ['green', 'blue'];
colors.splice(0, 0, 'red', 'yellow');
console.log(colors); // ['red', 'yellow', 'green', 'blue']

Первый аргумент — индекс вставки, второй — сколько элементов удалить (0 — ничего не удаляем). Далее — элементы для добавления.

splice тоже сдвигает элементы, как и unshift. Более того, он еще и проверяет, нужно ли что-то удалять. Поэтому он медленнее unshift для чистой вставки в начало. Используйте его, когда нужно совместить удаление и добавление.

Сравнение производительности: unshift vs spread vs splice

Давайте посмотрим на реальные цифры. Запустите этот код в консоли браузера или Node.js:

const size = 100000;
let arr1 = Array.from({length: size}, (_, i) => i);
let arr2 = [...arr1];
let arr3 = [...arr1];

console.time('unshift');
arr1.unshift(-1);
console.timeEnd('unshift'); // ~0.5ms (для 100k)

console.time('spread');
let newArr = [-1, ...arr2];
console.timeEnd('spread'); // ~2ms (создание нового массива)

console.time('splice');
arr3.splice(0, 0, -1);
console.timeEnd('splice'); // ~1.5ms

Выводы:

  • unshift — самый быстрый для мутации существующего массива.
  • spread — удобен, если нужна иммутабельность, но создает копию.
  • splice — универсален, но чуть медленнее для простой вставки.

Когда что использовать: практические рекомендации

Выбор метода зависит от контекста:

  • Нужно мутировать массив (изменить существующий): используйте unshift(). Это самый прямой путь.
  • Нужна иммутабельность (не менять исходный): используйте spread-оператор [newElement, ...arr]. Идеально для Redux reducers или React state.
  • Массив очень большой (сотни тысяч элементов): подумайте, можно ли вообще избежать вставки в начало. Возможно, стоит хранить

Блоги

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