Вы разработчик Javascript, работающий над внешним интерфейсом или node.js, который использовал array.map для преобразования элементов в существующем массиве? Тогда это для вас. Вы делаете это самым неэффективным способом. Позвольте мне рассказать вам, почему.

Javascript не является строго типизированным, и многие внутренние структуры данных являются производными от других структур данных. Array под капотом в JavaScript — это Object. Их ключи — числа, а их значения — элементы.

Существует два типа массивов. Плотные массивы и разреженные массивы. Плотные массивы — это ваши обычные массивы с индексом, начинающимся с 0 и последовательно заканчивающимся длиной массива. С другой стороны, разреженные массивы не являются последовательными, и их индексы не обязательно являются числами. Или мы можем сказать, что индекс может быть любой парой ключ-значение, и могут быть пустые индексы.

let array = [];
array[10] = "value";
array.length // 11, but only 1 element
console.log(array)
=> (11) [empty × 10, 'value']
array.length // still 11 because non numbers are not included in determining length
console.log(array)
=> (11) [empty × 10, 'value', str: 'string index']

Теперь вернемся к нашему сценарию, в котором мы хотели преобразовать элементы массива. Пусть преобразование добавляет 2 к значениям элементов, предполагая, что все элементы являются целыми числами. Обычно мы используем встроенный массив array.map.

let array=[1,2,3]
console.log(array.map(el=>el+2))
=> (3) [1, 2, 3]

Это очень лаконично и читабельно. Но Array.map реализован таким образом, что обрабатывает разреженные массивы. Другими словами, в нем есть несколько функций, которые вызываются для проверки разреженных значений, и он также перебирает их. Это снижает производительность, когда вы можете ожидать только плотные массивы в качестве входных данных. Этот подход занимает ~ 1000 мс для 5 миллионов элементов.

let array=[1,2,,3]
console.log(array.map(el=>el+2))
=> (4) [1, 2, empty, 3]

Альтернативные подходы

  1. Запись значений в изначально пустой массив
const newArr = [];
    for (let i = 0; i < arr.length; ++i) {
        newArr[i] = arr[i]+2;
    }

Запись в несуществующий индекс обычно не рекомендуется, поскольку это может привести к снижению производительности и непредсказуемости, а также может привести к путанице. Например, этот подход занимает примерно 250 мс для 5 миллионов элементов.

2. Для... в цикле

const newArr = new Array(arr.length);
    for (const i in arr) {
        newArr[i] = arr[i]+2;
    }

Циклы for…in обычно используются для перебора ключей объекта в JavaScript. Однако их также можно использовать для перебора индексов массива и учета разреженных массивов. Этот подход похож на то, как работает встроенный Array.map.

3. Поместить значения в массив

 const newArr = [];
    for (let i = 0; i < arr.length; ++i) {
        newArr.push(arr[i]+2);
    }

Традиционно массивы имеют фиксированный размер. Однако в JavaScript мы можем добавлять элементы в массив с помощью метода array.push(). Этот метод добавляет новые элементы в конец массива и возвращает новую длину. Это самый простой способ добавить новый элемент в массив и считается наиболее эффективным способом добавления элементов в массив в JavaScript. Но для нашего случая мы знаем длину входного массива. Когда вы помещаете значение в массив, в массиве может быть недостаточно памяти, выделенной для него, и его размер необходимо будет изменить. Это дорогостоящая операция, которая занимает ~ 200 мс для 5 миллионов элементов.

4. Предварительно выделить память

 const newArr = new Array(arr.length);
    for (let i = 0; i < arr.length; ++i) {
        newArr[i] = arr[i]+2;
    }

Предварительная инициализация памяти может привести к гораздо лучшей производительности, поскольку мы уже знаем размер массива, который нужно преобразовать. Этот подход занимает ~ 40 мс для 5 миллионов элементов.

5. 32-битный целочисленный массив

const newArr = new Int32Array(arr.length);
    for (let i = 0; i < arr.length; ++i) {
        newArr[i] = arr[i]+2;
    }

Традиционные массивы могут занимать значительный объем памяти, поскольку они не имеют фиксированного размера и могут хранить произвольные данные. Традиционные массивы могут занимать значительный объем памяти, поскольку они не имеют фиксированного размера и могут хранить произвольные данные. Это полезный инструмент, когда вы хотите хранить много данных с эффективным использованием памяти. Этот подход занимает ~ 20 мс для 5 миллионов элементов.

6. Преобразование в памяти

for (let i = 0; i < arr.length; ++i) {
        arr[i] = arr[i]+2;
    }

Для достижения оптимальной производительности можно повторно использовать память, уже выделенную для первого массива. Обычно не рекомендуется, чтобы функция изменяла переданные ей значения, потому что это может привести к неожиданным ошибкам, когда пользователь функции не ожидал этого как побочного эффекта. Встроенный Array.map не изменяет исходный массив. Этот подход занимает примерно 10 мс для 5 миллионов элементов.

Итак, это была часть сравнения производительности при преобразовании массива JavaScript. Есть также еще один случай, когда я видел, как некоторые разработчики использовали array.map для выполнения некоторых операций, которые не обязательно требуют преобразованного массива.

let positiveVals=[];
[1,-1,2,-6].map(el=>{if(el>0)positiveVals.push(el)})
console.log(positiveVals)
=> (2) [1, 2]

Это можно было бы сделать с помощью простого метода array.filter. Вот и все. Удачного кодирования!

Показатели производительности любезно предоставлены: Leet code