???????? ??? ???????
На самом деле, тест производительности на jsperf и проверка некоторых вещей в консоли выполняются. Для исследования используется сайт irt.org . Ниже собрана коллекция всех этих источников, а также пример функции внизу.
╔═══════════════╦══════╦═════════════════╦═══════════════╦═════════╦══════════╗
║ Method ║Concat║slice&push.apply ║ push.apply x2 ║ ForLoop ║Spread ║
╠═══════════════╬══════╬═════════════════╬═══════════════╬═════════╬══════════╣
║ mOps/Sec ║179 ║104 ║ 76 ║ 81 ║28 ║
╠═══════════════╬══════╬═════════════════╬═══════════════╬═════════╬══════════╣
║ Sparse arrays ║YES! ║Only the sliced ║ no ║ Maybe<sup>2<sub> </sub></sup> ║no ║
║ kept sparse ║ ║array (1st arg) ║ ║ ║ ║
╠═══════════════╬══════╬═════════════════╬═══════════════╬═════════╬══════════╣
║ Support ║MSIE 4║MSIE 5.5 ║ MSIE 5.5 ║ MSIE 4 ║Edge 12 ║
║ (<a href="https://www.irt.org/xref/core_objects.htm" rel="nofollow noreferrer">source</a>) ║NNav 4║NNav 4.06 ║ NNav 4.06 ║ NNav 3 ║<strike>MSIE</strike> <strike>NNav</strike> ║
╠═══════════════╬══════╬═════════════════╬═══════════════╬═════════╬══════════╣
║Array-like acts║no ║Only the pushed ║ YES! ║ YES! ║If have ║
║like an array ║ ║array (2nd arg) ║ ║ ║iterator<sup>1<sub> </sub></sup>║
╚═══════════════╩══════╩═════════════════╩═══════════════╩═════════╩══════════╝
<sup>1</sup> If the array-like object does not have a <i>Symbol.iterator</i> property, then trying
to spread it will throw an exception.
<sup>2</sup> Depends on the code. The following example code "YES" preserves sparseness.
function mergeCopyTogether(inputOne, inputTwo){
var oneLen = inputOne.length, twoLen = inputTwo.length;
var newArr = [], newLen = newArr.length = oneLen + twoLen;
for (var i=0, tmp=inputOne[0]; i !== oneLen; ++i) {
tmp = inputOne[i];
if (tmp !== undefined || inputOne.hasOwnProperty(i)) newArr[i] = tmp;
}
for (var two=0; i !== newLen; ++i, ++two) {
tmp = inputTwo[two];
if (tmp !== undefined || inputTwo.hasOwnProperty(two)) newArr[i] = tmp;
}
return newArr;
}
Как видно из вышеизложенного, я бы сказал, что Concat - это почти всегда способ достижения как производительности, так и способности сохранять редкость запасных массивов. Затем для лайков массива (таких как DOMNodeLists, как document.body.children
) я бы рекомендовал использовать цикл for, поскольку он является вторым по производительности и единственным другим методом, который сохраняет разреженные массивы. Ниже мы быстро рассмотрим, что имеется в виду под разреженными массивами и подобными массивам, чтобы устранить путаницу.
??? ??????
Вначале некоторые люди могут подумать, что это случайность, и что производители браузеров в конечном итоге смогут оптимизировать Array.prototype.push, чтобы быть достаточно быстрыми, чтобы превзойти Array.prototype.concat. НЕПРАВИЛЬНО! Array.prototype.concat всегда будет быстрее (в принципе, по крайней мере), потому что это просто копирование и вставка данных. Ниже приведена упрощенная наглядная диаграмма того, как может выглядеть реализация 32-битного массива (обратите внимание, что реальные реализации намного сложнее)
Byte ║ Data here
═════╬═══════════
0x00 ║ int nonNumericPropertiesLength = 0x00000000
0x01 ║ ibid
0x02 ║ ibid
0x03 ║ ibid
0x00 ║ int length = 0x00000001
0x01 ║ ibid
0x02 ║ ibid
0x03 ║ ibid
0x00 ║ int valueIndex = 0x00000000
0x01 ║ ibid
0x02 ║ ibid
0x03 ║ ibid
0x00 ║ int valueType = JS_PRIMITIVE_NUMBER
0x01 ║ ibid
0x02 ║ ibid
0x03 ║ ibid
0x00 ║ uintptr_t valuePointer = 0x38d9eb60 (or whereever it is in memory)
0x01 ║ ibid
0x02 ║ ibid
0x03 ║ ibid
Как видно выше, все, что вам нужно сделать, чтобы скопировать что-то подобное, почти так же просто, как копировать его байт за байтом. С Array.prototype.push.apply это намного больше, чем просто копирование и вставка данных. «.Apply» должен проверить каждый индекс в массиве и преобразовать его в набор аргументов, прежде чем передать его в Array.prototype.push. Затем Array.prototype.push должен дополнительно выделять больше памяти каждый раз, и (для некоторых реализаций браузера) может даже пересчитывать некоторые данные поиска позиции для разреженности.
Альтернативный способ думать об этом - это. Исходный массив один - это большая стопка бумаг, сшитых вместе. Массив источника два - это еще одна большая стопка бумаг. Это будет быстрее для вас
- Пойдите в магазин, купите достаточно бумаги, необходимой для копирования каждого исходного массива. Затем пропустите каждую пачку бумаги из каждого исходного массива через копировальный аппарат и скрепите получающиеся две копии вместе.
- Идите в магазин, купите достаточно бумаги для одной копии первого исходного массива. Затем вручную скопируйте исходный массив на новую бумагу, чтобы заполнить все пустые редкие пятна. Затем вернитесь в магазин и купите достаточно бумаги для второго источника. Затем просмотрите второй исходный массив и скопируйте его, не оставляя пробелов в копии. Затем скрепите все скопированные бумаги вместе.
В приведенной выше аналогии, опция # 1 представляет Array.prototype.concat, в то время как # 2 представляет Array.prototype.push.apply. Давайте проверим это с помощью аналогичного JSperf, отличающегося только тем, что этот тестирует методы с разреженными массивами, а не с массивными массивами. Его можно найти прямо здесь .
Поэтому я остановлюсь на том, что будущее производительности для этого конкретного варианта использования лежит не в Array.prototype.push, а скорее в Array.prototype.concat.
??????????????
????? ??????
Когда некоторые члены массива просто отсутствуют. Например:
// This is just as an example. In actual code,
// do not mix different types like this.
var mySparseArray = [];
mySparseArray[0] = "foo";
mySparseArray[10] = undefined;
mySparseArray[11] = {};
mySparseArray[12] = 10;
mySparseArray[17] = "bar";
console.log("Length: ", mySparseArray.length);
console.log("0 in it: ", 0 in mySparseArray);
console.log("arr[0]: ", mySparseArray[0]);
console.log("10 in it: ", 10 in mySparseArray);
console.log("arr[10] ", mySparseArray[10]);
console.log("20 in it: ", 20 in mySparseArray);
console.log("arr[20]: ", mySparseArray[20]);
Кроме того, javascript позволяет легко инициализировать запасные массивы.
var mySparseArray = ["foo",,,,,,,,,,undefined,{},10,,,,,"bar"];
?????-?????
Подобный массиву объект - это объект, обладающий как минимум свойством length
, но не инициализированный с помощью new Array
или []
; Например, перечисленные ниже объекты классифицируются как массивы.
{0: "foo", 1: "bar", length:2}
document.body.children
new Uint8Array(3)
- Это похоже на массив, потому что, хотя это (n) (типизированный) массив, приведение его к массиву изменяет конструктор.
(function(){return arguments})()
Наблюдайте, что происходит, используя метод, который does приводит массивы-лайки в массивы типа slice.
var slice = Array.prototype.slice;
// For arrays:
console.log(slice.call(["not an array-like, rather a real array"]));
// For array-likes:
console.log(slice.call({0: "foo", 1: "bar", length:2}));
console.log(slice.call(document.body.children));
console.log(slice.call(new Uint8Array(3)));
console.log(slice.call( function(){return arguments}() ));
- ПРИМЕЧАНИЕ: плохая практика - разрезать аргумент функции из-за производительности.
Наблюдайте за тем, что происходит, используя метод, который не принудительно обращается к массивам в массивы, такие как concat.
var empty = [];
// For arrays:
console.log(empty.concat(["not an array-like, rather a real array"]));
// For array-likes:
console.log(empty.concat({0: "foo", 1: "bar", length:2}));
console.log(empty.concat(document.body.children));
console.log(empty.concat(new Uint8Array(3)));
console.log(empty.concat( function(){return arguments}() ));