A maneira mais rápida de converter JavaScript NodeList para Array?


251

As perguntas anteriormente respondidas aqui disseram que esse era o caminho mais rápido:

//nl is a NodeList
var arr = Array.prototype.slice.call(nl);

Nos testes comparativos no meu navegador, descobri que é mais de três vezes mais lento que isso:

var arr = [];
for(var i = 0, n; n = nl[i]; ++i) arr.push(n);

Ambos produzem a mesma saída, mas acho difícil acreditar que minha segunda versão seja a maneira mais rápida possível, principalmente porque as pessoas disseram o contrário aqui.

Isso é uma peculiaridade no meu navegador (Chromium 6)? Ou existe uma maneira mais rápida?

EDIT: Para quem se importa, decidi o seguinte (que parece ser o mais rápido em todos os navegadores que testei):

//nl is a NodeList
var l = []; // Will hold the array of Node's
for(var i = 0, ll = nl.length; i != ll; l.push(nl[i++]));

EDIT2: Encontrei uma maneira ainda mais rápida

// nl is the nodelist
var arr = [];
for(var i = nl.length; i--; arr.unshift(nl[i]));

2
arr[arr.length] = nl[i];pode ser mais rápido do que arr.push(nl[i]);porque evita uma chamada de função.
Luc125

9
Esta página JSPerf é manter o controle de todas as respostas nessa página: jsperf.com/nodelist-to-array/27
pilau

Observe que o "EDIT2: Encontrei uma maneira mais rápida" é 92% mais lento no IE8.
Camilo Martin

2
Como você já sabe quantos nós você tem:var i = nl.length, arr = new Array(i); for(; i--; arr[i] = nl[i]);
mems 30/10

@ Luc125 Depende do navegador, já que a implementação push pode ser otimizada, estou pensando no chrome porque a v8 é boa com esse tipo de coisa.
axelduch

Respostas:


197

O segundo tende a ser mais rápido em alguns navegadores, mas o ponto principal é que você precisa usá-lo porque o primeiro não é apenas um navegador cruzado. Mesmo que os tempos estejam mudando

@kangax ( pré-visualização do IE 9 )

Agora, o Array.prototype.slice pode converter determinados objetos do host (por exemplo, NodeList) em arrays - algo que a maioria dos navegadores modernos consegue fazer há um bom tempo.

Exemplo:

Array.prototype.slice.call(document.childNodes);

??? Ambos são compatíveis com vários navegadores - Javascript (pelo menos se ele afirma ser compatível com a especificação ECMAscript) é Javascript; Matriz, protótipo, fatia e chamada são todos os recursos do idioma principal + tipos de objetos.
Jason S

6
mas não pode ser usado em NodeLists em IE (eu sei que é chato, mas hey ver o meu update)
gblazex

9
porque NodeLists não são parte da linguagem, eles são parte da API DOM, que é conhecido por ser buggy / imprevisível especialmente no IE
gblazex

3
Array.prototype.slice não é um navegador cruzado, se você levar o IE8 em conta.
Lajos Meszaros

1
Sim, foi sobre isso que minha resposta foi basicamente :) Embora fosse mais relevante em 2010 do que em hoje (2015).
gblazex

224

Com o ES6, agora temos uma maneira simples de criar uma matriz a partir de uma NodeList: a Array.from()função

// nl is a NodeList
let myArray = Array.from(nl)

como esse novo método ES6 se compara em velocidade aos outros mencionados acima?
user5508297

10
@ user5508297 Melhor do que o truque das fatias. Mais lento que os loops for, mas não é exatamente isso que queremos ter uma matriz sem atravessá-la. E a sintaxe é linda, simples e fácil de lembrar!
Webdif 03/08/19

O legal de Array.from é que você pode usar o argumento map:console.log(Array.from([1, 2, 3], x => x + x)); // expected output: Array [2, 4, 6]
honzajde


19

Algumas otimizações:

  • salve o comprimento do NodeList em uma variável
  • defina explicitamente o comprimento da nova matriz antes da configuração.
  • acessar os índices, em vez de pressionar ou não mudar.

Código ( jsPerf ):

var arr = [];
for (var i = 0, ref = arr.length = nl.length; i < ref; i++) {
 arr[i] = nl[i];
}

Você pode economizar um pouco mais de tempo usando Array (length) em vez de criar a matriz e, em seguida, dimensioná-la separadamente. Se você usar const para declarar o array e seu comprimento, com um let dentro do loop, ele será 1,5% mais rápido que o método acima: const a = Array (comprimento nl), c = a.length; for (seja b = 0; b <c; b ++) {a [b] = nl [b]; } consulte jsperf.com/nodelist-to-array/93
BrianFreud 27/04/19

15

Os resultados dependerão completamente do navegador, para dar um veredicto objetivo, temos que fazer alguns testes de desempenho, aqui estão alguns resultados, você pode executá-los aqui :

Chrome 6:

Firefox 3.6:

Firefox 4.0b2:

Safari 5:

Prévia da plataforma IE9 3:


1
Gostaria de saber como o loop for reverso se compara a esses ... for (var i=o.length; i--;)... o 'loop for' nesses testes reavalia a propriedade length em todas as iterações?
Dagg Nabbit


9

No ES6, você pode usar:

  • Array.from

    let array = Array.from(nodelist)

  • Operador de propagação

    let array = [...nodelist]


Não adiciona nada de novo ao que já foi escrito nas respostas anteriores.
Stefan Becker

7
NodeList.prototype.forEach = Array.prototype.forEach;

Agora você pode fazer document.querySelectorAll ('div'). ForEach (function () ...)


Boa ideia, obrigado @John! No entanto, NodeListnão está funcionando, mas Objecté: # Object.prototype.forEach = Array.prototype.forEach; document.getElementsByTagName("img").forEach(function(img) { alert(img.src); });
Ian Campbell

3
Não use Object.prototype: ele quebra o JQuery e várias outras coisas, como sintaxe literal do dicionário.
Nate Symer

Claro, evite estender funções internas nativas.
Roland

5

mais rápido e mais curto:

// nl is the nodelist
var a=[], l=nl.length>>>0;
for( ; l--; a[l]=nl[l] );

3
Por que o >>>0? E por que não colocar as atribuições na primeira parte do loop for?
Camilo Martin

5
Além disso, isso é buggy. Quando lé 0, o ciclo termina, por conseguinte, o 0elemento th não será copiado (Remeber há um elemento no índice 0)
Camilo Martin

1
Adoro esta resposta, mas ... Qualquer um que esteja se perguntando: o >>> pode não ser necessário aqui, mas é usado para garantir que o comprimento do modelista adira às especificações da matriz; garante que seja um número inteiro de 32 bits não assinado. Confira aqui ecma-international.org/ecma-262/5.1/#sec-15.4 Se você gosta de código ilegível, use este método com as sugestões de @ CamiloMartin!
Todd

Em resposta a @CamiloMartin - É arriscado varinserir a primeira parte de um forloop por causa de 'içamento variável'. A declaração de varserá 'içada' na parte superior da função, mesmo que a varlinha apareça em algum lugar abaixo, e isso pode causar efeitos colaterais que não são óbvios na sequência de código. Por exemplo um código na a mesma função que ocorre antes de o loop pode depender em ae lsendo não declarado. Portanto, para maior previsibilidade, declare seus vars na parte superior da função (ou, no ES6, use constou letnão, que não sejam içados).
brennanyoung

3

Confira este post aqui que fala sobre a mesma coisa. Pelo que entendi, o tempo extra pode ter a ver com subir a cadeia de escopo.


Interessante. Acabei de fazer alguns testes semelhantes agora e o Firefox 3.6.3 não mostra aumento de velocidade, de qualquer maneira, enquanto o Opera 10.6 tem um aumento de 20% e o Chrome 6 tem um aumento de 230% (!), Fazendo o modo iterate-push manual.
precisa saber é o seguinte

@ jairajs89 bastante estranho. Parece que Array.prototype.slicedepende do navegador. Gostaria de saber qual algoritmo cada navegador está usando.
Vivin Paliath 07/07

3

Esta é a função que eu uso no meu JS:

function toArray(nl) {
    for(var a=[], l=nl.length; l--; a[l]=nl[l]);
    return a;
}

1

Aqui estão os gráficos atualizados na data desta postagem (o gráfico "plataforma desconhecida" é o Internet Explorer 11.15.16299.0):

Safari 11.1.2 Firefox 61.0 Chrome 68.0.3440.75 Internet Explorer 11.15.16299.0

A partir desses resultados, parece que o método de pré-alocação 1 é a aposta mais segura entre navegadores.


1

Supondo que nodeList = document.querySelectorAll("div")esta seja uma forma concisa de conversão nodelistem matriz.

var nodeArray = [].slice.call(nodeList);

Veja-me usá-lo aqui .

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.