𝗥𝗲𝘀𝗲𝗮𝗿𝗰𝗵 𝗔𝗻𝗱 𝗥𝗲𝘀𝘂𝗹𝘁𝘀
Для фактів проводиться перевірка працездатності на jsperf та перевірка деяких речей у консолі. Для проведення досліджень використовується веб-сайт irt.org . Нижче наводиться збірка всіх цих джерел разом із прикладом функції внизу.
╔═══════════════╦══════╦═════════════════╦════════ ═══════╦═════════╦══════════╗
║ метод ║Concat║slice & push.apply ║ push.apply x2 ║ ForLoop pread розповсюдження ║
╠═══════════════╬══════╬═════════════════╬════════ ═══════╬═════════╬══════════╣
║ mOps / Sec ║179 ║104 ║ 76 ║ 81 ║28 ║
╠═══════════════╬══════╬═════════════════╬════════ ═══════╬═════════╬══════════╣
║ Рідкі масиви ║ ТАК! Тольки нарізаний ║ ні ║ Можливо, 2 ║но ║
║ збережений рідкий ray ║array (1-й аргумент) ║ ║ ║ ║
╠═══════════════╬══════╬═════════════════╬════════ ═══════╬═════════╬══════════╣
║ Підтримка ║MSIE 4║MSIE 5.5 ║ MSIE 5.5 ║ MSIE 4 ║Edege 12 ║
║ ( джерело ) ║NNav 4║NNav 4.06 ║ NNav 4.06 ║ NNav 3 ║ MSIE NNav ║
╠═══════════════╬══════╬═════════════════╬════════ ═══════╬═════════╬══════════╣
║Различні дії║но ║Тольки натиснуті ║ ТАК! ║ ТАК! FЯкщо є ║
Подібно до масиву ║ ║array (2nd arg) ║ ║ ║iterator 1 ║
╚═══════════════╩══════╩═════════════════╩════════ ═══════╩═════════╩══════════╝
1 Якщо об’єкт, схожий на масив, не має властивості Symbol.iterator , то спробуйте
щоб поширити його, викине виняток.
2 Залежить від коду. У наведеному нижче прикладі код "ТАК" зберігає рідкість.
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 завжди буде швидше (в принципі принаймні), тому що це проста копія-n-вставка над даними. Нижче наводиться спрощена переконливо-візуальна схема того, як може виглядати 32-розрядна реалізація масиву (зверніть увагу на те, що реальні реалізації є багато складнішими)
Байт ║ Дані тут
═════╬═══════════
0x00 ║ int nonNumericPropertiesLength = 0x00000000
0x01 ║ там само
0x02 ║ там само
0x03 ║ там само
0x00 ║ int довжина = 0x00000001
0x01 ║ там само
0x02 ║ там само
0x03 ║ там само
0x00 ║ int valueIndex = 0x00000000
0x01 ║ там само
0x02 ║ там само
0x03 ║ там само
0x00 ║ int valueType = JS_PRIMITIVE_NUMBER
0x01 ║ там само
0x02 ║ там само
0x03 ║ там само
0x00 ║ uintptr_t valuePointer = 0x38d9eb60 (або де б це не було в пам'яті)
0x01 ║ там само
0x02 ║ там само
0x03 ║ там само
Як видно з вище, все, що вам потрібно зробити, щоб скопіювати щось подібне майже так само просто, як і скопіювати його в байт. З Array.prototype.push.apply це набагато більше, ніж проста копія-n-вставка над даними. ".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", довжина: 2}
document.body.children
новий Uint8Array (3)
- Це схоже на масив, оскільки хоча це (n) (набраний) масив, примушування до масиву змінює конструктор.
(функція () {повернути аргументи}) ()
Поспостерігайте за тим, що відбувається за допомогою методу, який примушує масив подібних до масивів, як фрагмент.
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}() ));