¿Por qué document.querySelectorAll devuelve un StaticNodeList en lugar de un Array real?

103

Me molesta que no pueda hacerlo document.querySelectorAll(...).map(...)ni siquiera en Firefox 3.6, y todavía no puedo encontrar una respuesta, así que pensé en hacer una publicación cruzada en SO la pregunta de este blog:

http://blowery.org/2008/08/29/yay-for-queryselectorall-boo-for-staticnodelist/

¿Alguien sabe de una razón técnica por la que no obtienes una matriz? O por qué un StaticNodeList no hereda de un Array de tal manera que podría usarmap , concatetc?

(Por cierto, si es solo una función lo que desea, puede hacer algo como NodeList.prototype.map = Array.prototype.map;... pero nuevamente, ¿por qué esta funcionalidad (¿intencionalmente?) Está bloqueada en primer lugar?)

Kev
fuente
3
En realidad, también getElementsByTagName no devuelve un Array, sino una colección, y si desea usarlo como un Array (con métodos como concat, etc.) debe convertir dicha colección en un Array haciendo un bucle y copiando cada elemento del colección en una matriz. Nadie se quejó nunca de esto.
Marco Demaio

Respuestas:

81

Creo que es una decisión filosófica del W3C. El diseño del DOM de W3C [especificación] es bastante ortogonal al diseño de JavaScript, ya que el DOM se entiende a ser una plataforma y un lenguaje neutro.

Decisiones como " getElementsByFoo()devuelve un pedido NodeList" o " querySelectorAll()devuelve un StaticNodeList" son muy intencionales, por lo que las implementaciones no tienen que preocuparse por alinear su estructura de datos devueltos según las implementaciones dependientes del lenguaje (como .mapestar disponible en Arrays en JavaScript y Ruby, pero no en listas en C #).

El W3C apunta bajo: dirán que a NodeListdebería contener una propiedad de solo lectura .lengthde tipo unsigned long porque creen que cada implementación puede al menos admitir eso , pero no dirán explícitamente que el []operador de índice debería estar sobrecargado para admitir la obtención de elementos posicionales, porque no quieren obstaculizar un pequeño lenguaje deficiente que aparece y que quiere implementar getElementsByFoo()pero no puede soportar la sobrecarga del operador. Es una filosofía predominante presente en gran parte de la especificación.

John Resig ha expresado una opción similar a la suya, a la que agrega :

Mi argumento no es tanto que NodeIteratorno sea muy parecido a DOM, sino que no es muy parecido a JavaScript. No aprovecha las características presentes en el lenguaje JavaScript y las usa lo mejor que puede ...

Siento cierta empatía. Si el DOM se escribió específicamente con las funciones de JavaScript en mente, sería mucho menos incómodo y más intuitivo de usar. Al mismo tiempo, comprendo las decisiones de diseño del W3C.

Creciente fresco
fuente
Gracias, eso me ayuda a entender la situación.
Kev
@Kev: Vi su comentario en la página del artículo de ese blog cuestionando cómo haría para convertir el StaticNodeListen una matriz. Apoyaría la respuesta de @ mck89 como el camino a seguir para convertir un NodeList/ StaticNodeLista un Array nativo, pero eso fallará en IE (8 obv) con un error de JScript, ya que esos objetos están alojados / "especiales".
Crescent Fresh
Es cierto, por eso lo voté a favor. Sin embargo, alguien más canceló mi +1. ¿A qué te refieres con alojado / especial?
Kev
1
@Kev: las variables alojadas son cualquier variable proporcionada por el entorno "anfitrión" (por ejemplo, un navegador web). Por ejemplo document, window, etc. IE menudo implementa estos "especial" (por ejemplo, como objetos COM) que a veces no son conformes con el uso normal, en formas pequeñas y sutiles, tales como Array.prototype.slice.callel bombardeo cuando se les da una StaticNodeList;)
Media Luna fresca
200

Puede utilizar el operador de propagación ES2015 (ES6) :

[...document.querySelectorAll('div')]

convertirá StaticNodeList en una matriz de elementos.

A continuación, se muestra un ejemplo de cómo utilizarlo.

[...document.querySelectorAll('div')].map(x => console.log(x.innerHTML))
<div>Text 1</div>
<div>Text 2</div>

Vlad Bezden
fuente
24
Otra forma es usar Array.from () :Array.from(document.querySelectorAll('div')).map(x => console.log(x.innerHTML))
Michael Berdyshev
42

No sé por qué devuelve una lista de nodos en lugar de una matriz, tal vez porque, como getElementsByTagName, actualizará el resultado cuando actualice el DOM. De todos modos, un método muy simple para transformar ese resultado en una matriz simple es:

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

y luego puedes hacer:

Array.prototype.slice.call(document.querySelectorAll(...)).map(...);
mck89
fuente
3
En realidad, no actualiza el resultado cuando actualiza el DOM, por lo tanto, 'estático'. Debe volver a llamar manualmente a qSA para actualizar el resultado. Sin sliceembargo, +1 para la línea.
Kev
1
Sí, como dijo Kev: el conjunto de resultados qSA es estático, el conjunto de resultados getElementsByTagName () es dinámico.
joonas.fi
IE8 solo admite querySelectorAll () en modo estándar
mbokil
13

Solo para agregar a lo que dijo Crescent,

si solo desea una función, puede hacer algo como NodeList.prototype.map = Array.prototype.map

¡No hagas esto! No está garantizado que funcione.

Ningún estándar JavaScript o DOM / BOM especifica que la NodeListfunción de constructor existe incluso como una windowpropiedad / global , o que la NodeListdevuelta por querySelectorAllheredará de ella, o que su prototipo es modificable, o que la funciónArray.prototype.map realmente funcionará en una NodeList.

Una NodeList puede ser un 'objeto de host' (y es uno, en IE y en algunos navegadores más antiguos). Los Arraymétodos se definen como permitidos para operar en cualquier 'objeto nativo' de JavaScript que exponga numérico ylength propiedades, pero no es necesario que funcionen en objetos host (y en IE, no lo hacen).

Es molesto que no obtenga todos los métodos de matriz en las listas DOM (todos ellos, no solo StaticNodeList), pero no hay una forma confiable de evitarlo. Tendrá que convertir todas las listas DOM que obtenga a una matriz manualmente:

Array.fromList= function(list) {
    var array= new Array(list.length);
    for (var i= 0, n= list.length; i<n; i++)
        array[i]= list[i];
    return array;
};

Array.fromList(element.childNodes).forEach(function() {
    ...
});
bobince
fuente
1
Dispara, no pensé en eso. ¡Gracias!
Kev
Estoy de acuerdo +1. Solo un comentario, creo que hacer "var array = []" en lugar de "var array = new Array (list.length)" hará que el código sea aún más corto. Pero me interesa si sabe que podría haber un problema al hacer esto.
Marco Demaio
@MarcoDemaio: No, no hay problema. new Array(n)simplemente le da al terp de JS una pista sobre cuánto tiempo va a terminar la matriz. Eso podría permitirle asignar esa cantidad de espacio por adelantado, lo que podría resultar en una aceleración, ya que algunas reasignaciones de memoria podrían evitarse a medida que la matriz crece. Sin embargo, no sé si realmente ayuda en los navegadores modernos ... sospecho que no es mensurable.
Bobince
2
Ahora está implementado en Array.from ()
Michael Berdyshev
2

Creo que simplemente puedes seguir

Array.prototype.map.call(document.querySelectorAll(...), function(...){...});

Funciona perfecto para mi

Max Leps
fuente
0

Esta es una opción que quería agregar a la gama de otras posibilidades sugeridas por otros aquí. Está destinado únicamente a la diversión intelectual y no se recomienda .


Solo por diversión , aquí hay una manera de "forzar" querySelectorAlla arrodillarse e inclinarse ante usted:

Element.prototype.querySelectorAll = (function(QSA){
    return function(){
        return [...QSA.call(this, arguments[0])]
    }
})(Element.prototype.querySelectorAll);

Ahora se siente bien pasar por encima de esa función, mostrándole quién es el jefe. Ahora no sé qué es mejor, crear un envoltorio de función con nombre completamente nuevo y luego hacer que todo su código use ese nombre extraño (casi al estilo jQuery) o anule la función como la anterior una vez para que el resto de su código aún pueda para usar el nombre del método DOM original querySelectorAll.

No recomendaría esto de ninguna manera, a menos que honestamente no le des un [ya sabes qué].

vsync
fuente