Comment convertir un tableau de noeuds en NodeList statique?

NOTE: avant que cette question soit considérée comme un double, il y a une section au bas de cette question qui traite des raisons pour lesquelles quelques questions semblables ne fournissent pas la réponse que je cherche.


nous savons tous qu'il est facile de convertir un NodeList en un tableau et qu'il existe de nombreuses façons de le faire:

[].slice.call(someNodeList)
// or
Array.from(someNodeList)
// etc...

ce que je suis après est l'inverse; Comment puis-je convertir un un réseau de noeuds dans une NodeList statique?


Pourquoi est-ce que je veux faire ça?

sans entrer trop dans les choses, je crée une nouvelle méthode pour interroger des éléments sur la page I. e:

Document.prototype.customQueryMethod = function (...args) {...}

essayer de rester fidèle à la façon dont querySelectorAll fonctionne, je veux retourner un collection statique NodeList au lieu d'un tableau.


j'ai abordé le problème de trois façons différentes:

tentative 1:

création D'un Fragment de Document

function createNodeList(arrayOfNodes) {
    let fragment = document.createDocumentFragment();
    arrayOfNodes.forEach((node) => {
        fragment.appendChild(node);
    });
    return fragment.childNodes;
}

bien que cela renvoie un NodeList, cela ne fonctionne pas car appeler appendChild enlève le noeud de son emplacement actuel dans le DOM (où il devrait rester).

une autre variante de ceci implique cloning les noeuds et le retour clone. Cependant, maintenant vous retournez les noeuds clonés, qui n'ont aucune référence aux noeuds réels dans le DOM.


tentative 2:

tentative de "moquerie" du constructeur 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;
})();

et il serait utilisé de la manière suivante:

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

bien que cette approche particulière ne supprime pas les éléments du DOM, elle entraîne d'autres erreurs, telles que comme lors de sa conversion en tableau:

let arr = [].slice.call(nodeList);
// or
let arr = Array.from(nodeList);

chacune des réponses ci-dessus produit l'erreur suivante: Uncaught TypeError: Illegal invocation

j'essaie aussi d'éviter" d'imiter " un nodeList avec un faux nodeList constructor car je pense que cela aura probablement des conséquences involontaires futures.


tentative 3:

Fixation d'un attribut temporaire à des éléments de ré-interroger

function createNodeList(arrayOfNodes) {
    arrayOfNodes.forEach((node) => {
        node.setAttribute('QUERYME', '');
    });
    let nodeList = document.querySelectorAll('[QUERYME]');
    arrayOfNodes.forEach((node) => {
        node.removeAttribute('QUERYME');
    });
    return nodeList;
}

ça marchait bien, jusqu'à ce que je découvre que ça ne marche pas pour certains éléments, comme SVG 's . Il n'attachera pas l'attribut (bien que je n'ai testé cela que dans Chrome).


Il semble que ce devrait être une chose facile à faire, pourquoi ne puis-je pas utiliser la NodeList constructeur pour créer une NodeList, et pourquoi ne puis-je pas jeté un tableau à une NodeList une manière similaire que l'élément nodelists sont exprimés à les tableaux?

Comment puis-je convertir un tableau de noeuds en NodeList, de la bonne manière?


questions similaires qui ont des réponses qui ne fonctionnent pas pour moi:

les questions suivantes sont similaires à celle-ci. Malheureusement, ces questions / réponses ne résolvent pas mon problème particulier pour les raisons suivantes.

Comment puis-je convertir un tableau de éléments dans une NodeList? la réponse à cette question utilise une méthode qui clone les noeuds. Cela ne fonctionnera pas parce que j'ai besoin d'avoir accès aux noeuds originaux.

créer une liste de noeuds à partir d'un seul noeud en JavaScript utilise l'approche de fragment de document (tentative 1). Les autres réponses tentent des choses semblables aux tentatives 2 et 3.

créer un DOM NodeList utilise E4X , et ne s'applique donc pas. Et même s'il utilise cela, il enlève tout de même les éléments du DOM.

32
demandé sur Community 2016-07-18 18:22:12

4 réponses

pourquoi ne puis-je pas utiliser la NodeList constructeur pour créer une NodeList

parce que la spécification DOM pour NodeList interface ne spécifie pas l'attribut WebIDL [constructeur] , il ne peut donc pas être créé directement dans les scripts d'utilisateur.

pourquoi ne puis-je pas lancer un tableau à un NodeList de la même manière que les nodelists sont lancés sur des tableaux?

ce serait certainement une fonction utile à avoir dans votre cas, mais aucune fonction de ce genre n'est spécifiée pour exister dans la spécification DOM. Ainsi, il n'est pas possible de remplir directement un NodeList à partir d'un tableau de Node .

bien que je doute sérieusement que vous appelleriez cela "la bonne façon" de faire les choses, une solution moche est de trouver des sélecteurs CSS qui sélectionnent uniquement vos éléments désirés, et passer tous ces chemins dans querySelectorAll en tant que sélecteur séparé par des virgules:

// 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, ...]);

cela fonctionne en trouvant des chaînes CSS pour sélectionner uniquement chaque élément, où chaque sélecteur est de la forme html > :nth-child(x) > :nth-child(y) > :nth-child(z) ... . C'est, chaque élément peut être entendu à exister en tant qu'enfant d'un enfant, d'un enfant (etc.) tout le chemin jusqu'à l'élément racine. En trouvant l'index de chaque enfant dans le chemin de l'ancêtre du noeud, nous pouvons l'identifier de façon unique.

noter que cela ne préservera pas le type Text les noeuds, parce que querySelectorAll (et les chemins CSS en général) ne peuvent pas sélectionner les noeuds de texte.

Je n'ai aucune idée si cela sera suffisamment performant pour vos fins, cependant.

18
répondu apsillers 2016-07-19 15:55:14

Voici mes deux cents:

  • le Document est un objet natif et en l'étendant peut-être pas une bonne idée.
  • NodeList est un objet natif avec un constructeur privé et aucune méthode publique pour ajouter des éléments, et il doit y avoir une raison pour cela.
  • à moins que quelqu'un soit capable de fournir un hack, il n'y a aucun moyen de créer et de peupler une NodeList sans modifier le document actuel.
  • NodeList is comme un tableau, mais ayant la méthode item qui fonctionne tout comme l'utilisation de crochets, à l'exception de retourner null au lieu de undefined quand vous êtes hors de portée. Vous pouvez simplement retourner un tableau avec la méthode Item implémentée:

myArray.item= function (e) { return this[e] || null; }

PS: peut-être que vous prenez la mauvaise approche et votre méthode de requête personnalisée pourrait simplement envelopper un appel document.querySelectorAll qui renvoie ce que vous recherchez.

4
répondu Pablo Lozano 2016-07-18 16:19:29

comme il semble que la création d'un vrai NodeList à partir d'un tableau ait de graves conséquences, peut-être pourriez-vous utiliser un objet JS régulier avec un prototype fait par vous-même pour émuler un NodeList à la place. Comme ceci:

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);

cette solution présente encore quelques inconvénients. L'opérateur instanceof ne peut pas reconnaître L'objet retourné comme un NodeList. De plus, les loggings et les dirrings de la console sont affichés différemment d'une NodeList.

(* = Un for loop est utilisé pour itérer le tableau passé, de sorte que la fonction peut accepter un NodeList passé aussi. Si vous préférez une boucle forEach , celle-ci peut être utilisée aussi longtemps qu'un tableau seulement sera passé.)

Une démonstration en direct à jsFiddle .

4
répondu Teemu 2016-07-18 22:16:09

vous pouvez utiliser la propriété outerHTML de chaque élément, et l'ajouter à un élément parent (qui créera par document.createElement() , le type d'élément n'a pas d'importance). Par exemple, dans 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;
}
1
répondu Chayim Friedman 2018-02-27 16:46:45