array - nodelist javascript
¿Cómo puedo convertir una matriz de nodos en una NodeList estática? (4)
¿Por qué no puedo usar el constructor NodeList para crear una NodeList
Debido a que la especificación DOM para la interfaz NodeList
no especifica el atributo WebIDL [Constructor] , por lo que no se puede crear directamente en las secuencias de comandos del usuario.
¿Por qué no puedo lanzar una matriz a una NodeList de manera similar a las listas de nodos que se lanzan a las matrices?
Sin duda, esta sería una función útil en su caso, pero no se especifica que exista dicha función en la especificación DOM. Por lo tanto, no es posible completar directamente una NodeList
partir de una matriz de Node
s.
Aunque tengo serias dudas de que llamaría a esto "la manera correcta" de hacer las cosas, una solución fea es encontrar selectores de CSS que seleccionen únicamente los elementos deseados, y pasar todas esas rutas a querySelectorAll
como un selector separado por comas:
// find a CSS path that uniquely selects this element
function buildIndexCSSPath(elem) {
var parent = elem.parentNode;
// if this is the root node, include its tag name the start of the string
if(parent == document) { return elem.tagName; }
// find this element''s index as a child, and recursively ascend
return buildIndexCSSPath(parent) + " > :nth-child(" + (Array.prototype.indexOf.call(parent.children, elem)+1) + ")";
}
function toNodeList(list) {
// map all elements to CSS paths
var names = list.map(function(elem) { return buildIndexCSSPath(elem); });
// join all paths by commas
var superSelector = names.join(",");
// query with comma-joined mega-selector
return document.querySelectorAll(superSelector);
}
toNodeList([elem1, elem2, ...]);
Esto funciona mediante la búsqueda de cadenas de CSS para seleccionar de forma única cada elemento, donde cada selector tiene la forma html > :nth-child(x) > :nth-child(y) > :nth-child(z) ...
Es decir, se puede entender que cada elemento existe como hijo de un hijo de un niño (etc.) en todo el elemento raíz. Al encontrar el índice de cada niño en la ruta del ancestro del nodo, podemos identificarlo de manera única.
Tenga en cuenta que esto no preservará los nodos de tipo Text
, porque querySelectorAll
(y las rutas CSS en general) no pueden seleccionar nodos de texto.
Sin embargo, no tengo idea si esto será lo suficientemente bueno para sus propósitos.
NOTA: Antes de que esta pregunta se considere duplicada, hay una sección al final de esta pregunta que explica por qué algunas preguntas similares no brindan la respuesta que estoy buscando.
Todos sabemos que es fácil convertir una NodeList en una matriz y hay muchas maneras de hacerlo:
[].slice.call(someNodeList)
// or
Array.from(someNodeList)
// etc...
Lo que estoy buscando es el reverso; ¿cómo puedo convertir una matriz de nodos en una NodeList estática?
¿Por qué quiero hacer esto?
Sin profundizar en las cosas, estoy creando un nuevo método para consultar elementos en la página, es decir:
Document.prototype.customQueryMethod = function (...args) {...}
Al tratar de mantenerme fiel a cómo funciona querySelectorAll
, quiero devolver una colección estática NodeList
lugar de una matriz.
He abordado el problema de tres maneras diferentes hasta ahora:
Intento 1:
Crear un fragmento de documento
function createNodeList(arrayOfNodes) {
let fragment = document.createDocumentFragment();
arrayOfNodes.forEach((node) => {
fragment.appendChild(node);
});
return fragment.childNodes;
}
Si bien esto devuelve un NodeList, esto no funciona porque llamar a appendChild
elimina el nodo de su ubicación actual en el DOM (donde debería permanecer).
Otra variación de esto implica cloning
los nodos y devolver los clones. Sin embargo, ahora está devolviendo los nodos clonados, que no tienen ninguna referencia a los nodos reales en el DOM.
Intento 2:
Intentando "burlarse" del constructor NodeList
const FakeNodeList = (() => {
let fragment = document.createDocumentFragment();
fragment.appendChild(document.createComment(''create a nodelist''));
function NodeList(nodes) {
let scope = this;
nodes.forEach((node, i) => {
scope[i] = node;
});
}
NodeList.prototype = ((proto) => {
function F() {
}
F.prototype = proto;
return new F();
})(fragment.childNodes);
NodeList.prototype.item = function item(idx) {
return this[idx] || null;
};
return NodeList;
})();
Y se usaría de la siguiente manera:
let nodeList = new FakeNodeList(nodes);
// The following tests/uses all work
nodeList instanceOf NodeList // true
nodeList[0] // would return an element
nodeList.item(0) // would return an element
Si bien este enfoque particular no elimina los elementos del DOM, causa otros errores, como al convertirlo en una matriz:
let arr = [].slice.call(nodeList);
// or
let arr = Array.from(nodeList);
Cada uno de los anteriores produce el siguiente error: Uncaught TypeError: Illegal invocation
Error: Uncaught TypeError: Illegal invocation
También estoy tratando de evitar "imitar" una lista de nodos con un constructor de lista de nodos falso, ya que creo que probablemente tendrá futuras consecuencias imprevistas.
Intento 3:
Adjuntar un atributo temporal a los elementos para volver a consultarlos
function createNodeList(arrayOfNodes) {
arrayOfNodes.forEach((node) => {
node.setAttribute(''QUERYME'', '''');
});
let nodeList = document.querySelectorAll(''[QUERYME]'');
arrayOfNodes.forEach((node) => {
node.removeAttribute(''QUERYME'');
});
return nodeList;
}
Esto estaba funcionando bien, hasta que descubrí que no funciona para ciertos elementos, como SVG
. No adjuntará el atributo (aunque solo probé esto en Chrome).
Parece que esto debería ser algo fácil de hacer, ¿por qué no puedo usar el constructor NodeList para crear una NodeList y por qué no puedo convertir una matriz en una NodeList de manera similar a las listas NodeLists en las matrices?
¿Cómo puedo convertir una matriz de nodos en una NodeList, de la manera correcta?
Preguntas similares que tienen respuestas que no me funcionan:
Las siguientes preguntas son similares a esta. Desafortunadamente, estas preguntas / respuestas no resuelven mi problema particular por las siguientes razones.
¿Cómo puedo convertir una matriz de elementos en una NodeList? La respuesta en esta pregunta usa un método que clona nodos. Esto no funcionará porque necesito tener acceso a los nodos originales.
Crear lista de nodos desde un solo nodo en JavaScript usa el enfoque de fragmentos de documento (intento 1). Las otras respuestas intentan cosas similares en los intentos 2 y 3.
La creación de un DOM NodeList usa E4X
y, por lo tanto, no se aplica. Y a pesar de que está usando eso, aún elimina los elementos del DOM.
Aquí están mis dos centavos:
- El documento es un objeto nativo y extenderlo puede no ser una buena idea.
- NodeList es un objeto nativo con un constructor privado y sin métodos públicos para agregar elementos, y debe haber una razón para ello.
- A menos que alguien pueda proporcionar un hack, no hay forma de crear y completar una NodeList sin modificar el documento actual.
- NodeList es como una matriz, pero tiene el método de
item
que funciona igual que el uso de corchetes, con la excepción de devolvernull
lugar deundefined
cuando está fuera de rango. Puede devolver una matriz con el método de elemento implementado:
myArray.item= function (e) { return this[e] || null; }
PD: Tal vez esté tomando el enfoque equivocado y su método de consulta personalizado podría simplemente ajustar un document.querySelectorAll
llamada que devuelva lo que está buscando.
Dado que parece que la creación de una NodeList real a partir de una matriz está teniendo graves inconvenientes, tal vez podría utilizar un objeto JS común con un prototipo hecho a sí mismo para emular una NodeList en su lugar. Al igual que:
var nodeListProto = Object.create({}, {
item: {
value: function(x) {
return (Object.getOwnPropertyNames(this).indexOf(x.toString()) > -1) ? this[x] : null;
},
enumerable: true
},
length: {
get: function() {
return Object.getOwnPropertyNames(this).length;
},
enumerable: true
}
}),
getNodeList = function(nodes) {
var n, eN = nodes.length,
list = Object.create(nodeListProto);
for (n = 0; n < eN; n++) { // *
Object.defineProperty(list, n.toString(), {
value: nodes[n],
enumerable: true
});
}
return (list.length) ? list : null;
};
// Usage:
var nodeListFromArray = getNodeList(arrayOfNodes);
Todavía hay algunos inconvenientes con esta solución. instanceof
operador instanceof
no puede reconocer el objeto devuelto como NodeList. Además, los inicios de sesión de la consola y los directorios se muestran de forma diferente a los de NodeList.
(* = A for
loop se usa para iterar la matriz pasada, de modo que la función también puede aceptar una NodeList pasada. Si prefiere un bucle forEach
, también se puede usar, siempre que se pase una matriz únicamente).
Puede usar la propiedad outerHTML
de cada elemento y agregarla a un elemento primario (que creará mediante document.createElement()
, el tipo de elemento no importa). Por ejemplo, en ES6:
function getNodeList(elements) {
const parentElement = document.createElement(''div'');
// This can be a differnet element type, too (but only block (display: block;) element, because it impossible to put block element in inline element, and maybe ''elements'' array contains a block element).
let HTMLString = '''';
for (let element of elements) {
HTMLString += element.outerHTML;
}
parentElement.innerHTML = HTMLString;
return parentElement.childNodes;
}