Самый эффективный способ конвертировать HTMLCollection в массив

392

Есть ли более эффективный способ преобразовать HTMLCollection в массив, кроме как перебирать содержимое указанной коллекции и вручную помещать каждый элемент в массив?

Том
источник
10
Что подразумевается под «эффективным»? В случае наилучшего выполнения цикл for обычно выполняется быстрее, чем Array.prototype.slice . Цикл также работает в более широком разнообразии браузеров (то есть все), так что по этим критериям она является «наиболее эффективным способом». И это очень мало кода: for (var a=[], i=collection.length; i;) a[--i] = collection[i];так что не так много «против» там :-)
RobG
@RobG Спасибо - я бы дал тебе + 59k, если бы мог! ;-)
Slashback
1
Если посмотреть на текущую производительность браузера , slice в основном догнал циклы с точки зрения производительности, за исключением Chrome. При использовании большего количества элементов и небольшой оптимизации цикла результаты практически идентичны , за исключением Chrome, где цикл выполняется намного быстрее.
RobG
Я создал тест jsperf, который рассматривает оба метода, упомянутых @harpo, а также тест jquery на производительность. Я обнаружил, что jquery немного медленнее, чем оба метода javascript, и максимальная производительность варьируется в зависимости от js-теста. Chrome 59.0.3071 / Mac OS X 10.12.5 предпочитает использовать, Array.prototype.slice.callи Brave (на основе Chrome 59.0.3071) практически не имеет различий между двумя тестами javascript на нескольких запусках. См. Jsperf.com/htmlcollection-array-vs-jquery-children
NuclearPeon
jsben.ch/h2IFA => тест производительности для наиболее распространенных способов сделать это
EscapeNetscape

Ответы:

699
var arr = Array.prototype.slice.call( htmlCollection )

будет иметь тот же эффект, используя "родной" код.

редактировать

Так как это получает много просмотров, обратите внимание (на комментарий @ oriol), что следующее более краткое выражение эффективно эквивалентно:

var arr = [].slice.call(htmlCollection);

Но обратите внимание на комментарий @ JussiR, который, в отличие от «многословной» формы, создает в процессе пустой, неиспользуемый и действительно неиспользуемый экземпляр массива. То, что компиляторы делают с этим, находится за пределами возможностей программиста.

редактировать

Начиная с ECMAScript 2015 (ES 6) существует также Array.from :

var arr = Array.from(htmlCollection);

редактировать

ECMAScript 2015 также предоставляет оператор распространения , который функционально эквивалентен Array.from(хотя обратите внимание, что Array.fromв качестве второго аргумента поддерживается функция отображения).

var arr = [...htmlCollection];

Я подтвердил, что оба вышеперечисленных работают NodeList.

Сравнение производительности для упомянутых методов: http://jsben.ch/h2IFA

Харпо
источник
7
Это терпит неудачу в IE6.
Хит Бордерс
29
Ярлык [].slice.call(htmlCollection)тоже работает.
Ориол
1
@ChrisNielsen Да, я был дезинформирован об этом. Извините за распространение этого вокруг. Я не понимал, что я сказал это и здесь. Комментарий был удален, чтобы избежать путаницы, но для контекста, который я где-то читал (или неправильно читал), что разделение HTMLCollection заставляет его вести себя как массив и коллекция. Совершенно неверно.
Эрик Реппен
3
Ярлык [] .slice не эквивалентен, поскольку он также создает неиспользуемый пустой экземпляр массива. Однако не уверен, что компиляторы смогут его оптимизировать.
JussiR
3
Array.from, То есть from, не поддерживается IE11.
Фрэнк Конейн
86

не уверен, что это наиболее эффективно, но краткий синтаксис ES6 может быть таким:

let arry = [...htmlCollection] 

Редактировать: еще один, из комментария Chris_F:

let arry = Array.from(htmlCollection)
Мидо
источник
9
Кроме того, ES6 добавляетArray.from()
Chris_F
4
Остерегайтесь первого, есть небольшая ошибка при переносе с babel, когда [... htmlCollection] возвращает массив с htmlCollection, поскольку это единственный элемент.
Марсель М.
3
Оператор распределения массива не работает на htmlCollection. Это применимо только к NodeList.
Бобби
1
Array.from, То есть from, не поддерживается IE11.
Фрэнк Конейн
Тест производительности Похоже, что оператор спреда быстрее из этих 2.
RedSparr0w
20

Я видел более краткий метод получения Array.prototypeметодов в целом, который работает так же хорошо. Преобразование HTMLCollectionобъекта в Arrayобъект показано ниже:

[] .slice.call (yourHTMLCollectionObject);

И, как упоминалось в комментариях, для старых браузеров, таких как IE7 и более ранних версий, вам просто нужно использовать функцию совместимости, например:

function toArray(x) {
    for(var i = 0, a = []; i < x.length; i++)
        a.push(x[i]);

    return a
}

Я знаю, что это старый вопрос, но я чувствовал, что принятый ответ был немного неполным; так что я решил выбросить это туда, FWIW.

CodeSmith
источник
6

Для кросс-браузерной реализации я бы предложил посмотреть на функцию prototype.js $A

скопировано с 1.6.1 :

function $A(iterable) {
  if (!iterable) return [];
  if ('toArray' in Object(iterable)) return iterable.toArray();
  var length = iterable.length || 0, results = new Array(length);
  while (length--) results[length] = iterable[length];
  return results;
}

Вероятно, он не используется, Array.prototype.sliceпотому что он доступен не во всех браузерах. Я боюсь, что производительность довольно плохая, так как откат назад - это цикл javascript iterable.

Гарет Дэвис
источник
2
ОП запросил другой способ, кроме «перебора содержимого указанной коллекции и ручного помещения каждого элемента в массив», но это именно то, что $Aфункция делает большую часть времени.
Luc125
1
Я думаю, что смысл, который я пытался сделать, заключается в том, что нет хорошего способа сделать это, код prototype.js показывает, что вы можете искать метод 'toArray', но провалив эту итерацию по самому безопасному маршруту
Гарет Дэвис,
1
Это создаст новые неопределенные члены в разреженных массивах. Перед назначением должен быть тест hasOwnProperty .
RobG
3

Это мое личное решение, основанное на информации здесь (эта тема):

var Divs = new Array();    
var Elemns = document.getElementsByClassName("divisao");
    try {
        Divs = Elemns.prototype.slice.call(Elemns);
    } catch(e) {
        Divs = $A(Elemns);
    }

Где $ A был описан Гаретом Дэвисом в его посте:

function $A(iterable) {
  if (!iterable) return [];
  if ('toArray' in Object(iterable)) return iterable.toArray();
  var length = iterable.length || 0, results = new Array(length);
  while (length--) results[length] = iterable[length];
  return results;
}

Если браузер поддерживает лучший способ, хорошо, иначе будет использовать кросс-браузер.

Gustavo
источник
В целом, я не ожидаю, что try / catch будет эффективным способом управления потоком управления. Вы можете проверить, существует ли функция сначала, а затем запустить один или другой немного дешевле.
Патрик
2
Как и в случае с ответом Гарета Дэвиса, это создает новых неопределенных членов в разреженных массивах, так что [,,]становится [undefined, undefined].
RobG
У меня еще не было таких проблем. Он объединяет результаты из 3 элементов в массив из 2 элементов. Что касается пустого становится неопределенным, это немного ограничения JavaScript, я думаю, вы ожидали нулевой вместо неопределенного, верно?
Густаво
3

Это работает во всех браузерах, включая более ранние версии IE.

var arr = [];
[].push.apply(arr, htmlCollection);

Поскольку jsperf все еще не работает в данный момент, вот jsfiddle, который сравнивает производительность различных методов. https://jsfiddle.net/qw9qf48j/

Николас
источник
попробуйvar args = (htmlCollection.length === 1 ? [htmlCollection[0]] : Array.apply(null, htmlCollection));
Шахар Шокрани
3

Для эффективного преобразования массива в массив мы можем использовать jQuery makeArray :

makeArray: конвертировать массивоподобный объект в истинный массив JavaScript.

Применение:

var domArray = jQuery.makeArray(htmlCollection);

Немного больше:

Если вы не хотите сохранять ссылку на объект массива (большую часть времени HTMLCollections динамически изменяется, поэтому лучше скопировать их в другой массив, в этом примере обратите особое внимание на производительность:

var domDataLength = domData.length //Better performance, no need to calculate every iteration the domArray length
var resultArray = new Array(domDataLength) // Since we know the length its improves the performance to declare the result array from the beginning.

for (var i = 0 ; i < domDataLength ; i++) {
    resultArray[i] = domArray[i]; //Since we already declared the resultArray we can not make use of the more expensive push method.
}

Что такое массив?

HTMLCollection является "array-like"объектом, массив подобных объектов похож на объект массива , но отсутствует много его определение функционально:

Подобные массиву объекты выглядят как массивы. Они имеют различные пронумерованные элементы и свойство длины. Но на этом сходство заканчивается. Подобные массиву объекты не имеют функций Array, и циклы for-in даже не работают!

Шахар Шокрани
источник