A maneira mais eficiente de converter uma coleção HTMLC em uma matriz

392

Existe uma maneira mais eficiente de converter uma HTMLCollection em uma matriz, além de iterar pelo conteúdo da coleção e empurrar manualmente cada item em uma matriz?

Tom
fonte
10
O que se entende por "eficiente"? Se o melhor desempenho, uma para circuito é geralmente mais rápido do que Array.prototype.slice . Um loop também funciona em uma variedade maior de navegadores (ou seja, todos), portanto, por esses critérios, é a "maneira mais eficiente". E é muito pouco código: for (var a=[], i=collection.length; i;) a[--i] = collection[i];não muito de um "con" há :-)
RobG
@RobG Obrigado - eu daria a você + 59k, se pudesse! ;-)
Slashback
11
Analisando o desempenho atual do navegador , o slice alcançou loops em termos de desempenho, exceto no Chrome. Usando um número maior de elementos e uma leve otimização do loop, os resultados são quase idênticos , exceto no Chrome, onde um loop é muito mais rápido.
RobG
Criei um teste jsperf que analisa os dois métodos mencionados pelo @harpo, bem como um teste jquery para desempenho. Descobri que o jquery é um pouco mais lento que os dois métodos javascript e o desempenho superior varia entre os casos de teste do js. O Chrome 59.0.3071 / Mac OS X 10.12.5 prefere o uso Array.prototype.slice.calle o Brave (baseado no Chrome 59.0.3071) praticamente não tem diferença entre os dois testes de javascript em várias execuções. Veja jsperf.com/htmlcollection-array-vs-jquery-children
NuclearPeon
jsben.ch/h2IFA => teste de desempenho para as maneiras mais comuns de fazer isso
EscapeNetscape

Respostas:

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

terá o mesmo efeito usando o código "nativo".

Editar

Como isso gera muitas visualizações, observe (por comentário de @ oriol) que a seguinte expressão mais concisa é efetivamente equivalente:

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

Mas observe pelo comentário do @ JussiR, que, diferentemente da forma "detalhada", ele cria uma instância de matriz vazia, não utilizada e de fato inutilizável no processo. O que os compiladores fazem sobre isso está fora do alcance do programador.

Editar

Desde o ECMAScript 2015 (ES 6), também existe o Array.from :

var arr = Array.from(htmlCollection);

Editar

O ECMAScript 2015 também fornece o operador de spread , que é funcionalmente equivalente a Array.from(embora observe que Array.fromsuporta uma função de mapeamento como o segundo argumento).

var arr = [...htmlCollection];

Confirmei que ambos os trabalhos acima funcionam NodeList.

Uma comparação de desempenho para os métodos mencionados: http://jsben.ch/h2IFA

harpo
fonte
7
Isso falha no IE6.
21309 Heath Borders
29
O atalho [].slice.call(htmlCollection)também funciona.
Oriol
11
@ChrisNielsen Sim, eu estava mal informado sobre isso. Desculpe por espalhar isso por aí. Eu não sabia que tinha afirmado isso aqui também. Excluí o comentário para evitar confusão, mas por contexto eu tinha lido (ou lido mal) em algum lugar que cortar uma HTMLCollection fazia com que ele se comportasse como uma matriz e uma coleção. Totalmente incorreto.
Erik Reppen
3
O atalho [] .slice não é equivalente, pois também cria uma instância de matriz vazia não utilizada. Porém, não tenho certeza se os compiladores são capazes de otimizá-lo.
JussiR
3
Array.from, ou seja from, não é suportado pelo IE11.
Frank Conijn
86

não tenho certeza se essa é a mais eficiente, mas uma sintaxe concisa do ES6 pode ser:

let arry = [...htmlCollection] 

Edit: Outro, do comentário Chris_F:

let arry = Array.from(htmlCollection)
mido
fonte
9
Além disso, o ES6 adicionaArray.from()
Chris_F 13/06
4
Cuidado com o primeiro, há um bug sutil ao transpilar com babel, onde [... htmlCollection] retornará uma matriz com o htmlCollection, pois é seu único elemento.
Marcel M.
3
O operador de propagação de matriz não funciona em htmlCollection. É aplicável apenas ao NodeList.
Bobby
11
Array.from, ou seja from, não é suportado pelo IE11.
precisa
Índice de referência se parece com o operador de disseminação é mais rápida fora destes 2.
RedSparr0w
20

Eu vi um método mais conciso de obter Array.prototypemétodos em geral que também funciona. A conversão de um HTMLCollectionobjeto em um Arrayobjeto é demonstrada abaixo:

[] .slice.call (yourHTMLCollectionObject);

E, como mencionado nos comentários, para navegadores antigos como o IE7 e versões anteriores, basta usar uma função de compatibilidade, como:

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

    return a
}

Sei que essa é uma pergunta antiga, mas achei que a resposta aceita era um pouco incompleta; então pensei em lançar isso lá fora, FWIW.

Codesmith
fonte
6

Para uma implementação entre navegadores, sugiro que você analise a função prototype.js $A

copiado de 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;
}

Provavelmente não é usado Array.prototype.sliceporque não está disponível em todos os navegadores. Receio que o desempenho seja muito ruim, pois há um retorno de javascript sobre o iterable.

Gareth Davis
fonte
2
O OP pediu uma maneira diferente de "iterar o conteúdo da coleção e colocar manualmente cada item em uma matriz", mas é exatamente isso que a $Afunção faz na maioria das vezes.
Luc125
11
Eu acho que o ponto que eu estava tentando enfatizar é que não existe uma boa maneira de fazer isso, o código prototype.js mostra que você pode procurar um método 'toArray', mas falhando nessa iteração da rota mais segura
Gareth Davis
11
Isso criará membros novos e indefinidos em matrizes esparsas. Deve haver um teste hasOwnProperty antes da atribuição.
RobG 4/16
3

Esta é a minha solução pessoal, com base nas informações aqui (este tópico):

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

Onde $ A foi descrito por Gareth Davis em seu post:

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;
}

Se o navegador suportar da melhor maneira, ok, caso contrário, usará o navegador cruzado.

Gustavo
fonte
Em geral, não espero que o try / catch seja uma maneira eficiente de gerenciar o fluxo de controle. Você pode verificar se a função existe primeiro e executar um ou outro um pouco mais barato.
Patrick
2
Tal como acontece com resposta Gareth Davis', isso cria novos membros indefinidos em matrizes esparsas, então [,,]torna-se [undefined, undefined].
RobG 4/16
Ainda não tive esse tipo de problema. Costura uma coleção de 3 elementos resulta em uma matriz com 2 elementos. Quanto ao vazio se tornar indefinido, há algumas limitações de JavaScript, suponho que você estava esperando nulo em vez de indefinido, certo?
Gustavo
3

Isso funciona em todos os navegadores, incluindo versões anteriores do IE.

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

Como o jsperf ainda está inativo no momento, aqui está um jsfiddle que compara o desempenho de diferentes métodos. https://jsfiddle.net/qw9qf48j/

Nicholas
fonte
tente #var args = (htmlCollection.length === 1 ? [htmlCollection[0]] : Array.apply(null, htmlCollection));
Shahar Shokrani
3

Para converter de matriz para matriz de maneira eficiente, podemos usar o jQuery makeArray :

makeArray: converte um objeto semelhante a um array em um verdadeiro array JavaScript.

Uso:

var domArray = jQuery.makeArray(htmlCollection);

Um pouco mais:

Se você não deseja manter a referência ao objeto da matriz (na maioria das vezes, as HTMLCollections são alteradas dinamicamente, portanto é melhor copiá-las para outra matriz. Este exemplo presta muita atenção ao desempenho:

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.
}

O que é semelhante a um array?

HTMLCollection é um "array-like"objeto, os objetos do tipo matriz são semelhantes ao objeto da matriz, mas perdem grande parte de sua definição funcional:

Objetos semelhantes a matrizes parecem matrizes. Eles têm vários elementos numerados e uma propriedade length. Mas é aí que a semelhança para. Os objetos do tipo matriz não possuem nenhuma das funções da matriz e os loops de entrada nem funcionam!

Shahar Shokrani
fonte