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.