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]));
jairajs89
fonte
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);
gblazex
fonte
??? 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)
webdif
fonte
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
103

Aqui está uma nova maneira interessante de fazer isso usando o operador de spread ES6 :

let arr = [...nl];
Alexander Olsson
fonte
7
Não funcionou para mim em texto datilografado. ERROR TypeError: el.querySelectorAll(...).slice is not a function
Alireza Mirian
1
Vem em 3 mais rápido, atrás dos 3 métodos unshift, usando o Chrome 71: jsperf.com/nodelist-to-array/90
BrianFreud
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];
}
tailandês
fonte
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:

CMS
fonte
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
14

O navegador mais rápido e cruzado é

for(var i=-1,l=nl.length;++i!==l;arr[i]=nl[i]);

Como eu comparei em

http://jsbin.com/oqeda/98/edit

* Obrigado @CMS pela ideia!

Chromium (semelhante ao Google Chrome) Raposa de fogo Ópera

Felipe Buccioni
fonte
1
o link parece estar errado, deve ser 91, em vez de 89, para incluir o teste que você mencionou. E 98 parece ser o mais completo.
Yaroslav Yakovlev
9

No ES6, você pode usar:

  • Array.from

    let array = Array.from(nodelist)

  • Operador de propagação

    let array = [...nodelist]

Isabella
fonte
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 () ...)

John Williams
fonte
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] );
anônimo
fonte
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.

Vivin Paliath
fonte
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;
}
Web designer
fonte
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.

TomSlick
fonte
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 .

Udo E.
fonte