40 votes

Comment convertir un tableau de noeuds en liste de noeuds statique?

REMARQUE: Avant de cette question suppose un doublon, il y a une section au bas de cette question qui aborde pourquoi quelques questions similaires ne fournissent pas la réponse, je suis à la recherche d'.


Nous savons tous qu'il est facile de convertir une NodeList à un Tableau et il y a beaucoup de façons de le faire:

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

Ce que je suis après, c'est l'inverse; comment puis-je convertir un tableau de nœuds dans un statique NodeList?


Pourquoi ai-je envie de faire?

Sans entrer trop profondément dans les choses, je suis en création d'une nouvelle méthode pour interroger les éléments sur la page.e:

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

En essayant de rester fidèle à combien de querySelectorAll fonctionne, je veux un retour statique de la collection NodeList au lieu d'un tableau.


J'ai abordé le problème de trois façons différentes à ce jour:

Tentative 1:

La création d'un Fragment de Document

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

Alors que ce n'est le retour d'une NodeList, cela ne fonctionne pas parce que l'appelant appendChild supprime le nœud à partir de son emplacement actuel dans les DOM (où il doit le rester).

Une autre variation de cette implique cloning les nœuds et le retour de l'clones. Cependant, maintenant vous êtes de retour clonés de nœuds, qui n'ont aucune référence à la réalité de nœuds dans les DOM.


Tentative 2:

Tenter de "se moquer de" la NodeList constructeur

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 peut être 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

Si cette approche ne permet pas de supprimer les éléments du DOM, il entraîne d'autres erreurs, comme lors de la conversion d'un tableau:

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

Chacun des ci-dessus génère l'erreur suivante: Uncaught TypeError: Illegal invocation

Je suis aussi en train d'essayer d'éviter les "imiter" une nodeList avec un faux nodelist constructeur comme je le crois, et susceptible d'avoir des conséquences inattendues.


Tentative 3:

Fixation temporaire d'attribut à 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;
}

Cela fonctionnait bien, jusqu'à ce que j'ai découvert que cela ne fonctionne pas pour certains éléments, comme SVGs'. Il ne se fixe pas l'attribut (bien que je n'ai fait que tester cela 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 à des tableaux?

Comment puis-je convertir un tableau de nœuds à un NodeList, de la bonne façon?


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

Les questions suivantes sont similaires à celui-ci. Malheureusement, les questions/réponses ne permettent pas de résoudre mon problème en particulier pour les raisons suivantes.

Comment puis-je convertir un Tableau d'éléments dans une NodeList? La réponse à cette question utilise une méthode que les clones de nœuds. Cela ne fonctionnera pas parce que j'ai besoin d'avoir accès à l'original de nœuds.

Créer la liste des nœuds à partir d'un seul nœud en JavaScript utilise le fragment de document d'approche (Tentative 1). Les autres réponses essayer des choses similaires au Tente 2 et 3.

La création d'un DOM NodeList est à l'aide de E4X, et ne s'applique donc pas. Et même si c'est à l'aide, on supprime les éléments du DOM.

20voto

apsillers Points 29372

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

Parce que le DOM cahier des charges pour l' NodeList interface ne permet pas de spécifier la WebIDL [Constructeur] attribut, de sorte qu'il ne peut pas être créé directement dans les scripts utilisateurs.

pourquoi ne puis-je pas jeté un tableau à une NodeList une manière similaire que l'élément nodelists sont exprimés à des tableaux?

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

Bien que je doute sérieusement que vous appelez "la bonne façon" de faire les choses, un laid solution c'est de trouver des sélecteurs CSS qui permet de sélectionner des éléments, et de passer l'ensemble de ces chemins en querySelectorAll séparée par des virgules sélecteur:

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

Cette recherche des CSS chaînes 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. Par trouver l'index de chaque enfant dans le nœud de l'ancêtre de chemin, nous pouvons identifier de manière unique.

Notez que ce ne sera pas préserver Text-type de nœuds, car querySelectorAll (et le CSS chemins en général) ne peut pas sélectionner les nœuds de texte.

Je n'ai aucune idée si ce sera suffisamment performant pour vos besoins, mais.

4voto

Pablo Points 3168

Voici mes deux cents:

  • Le Document est un objet natif et en l'étendant peut-être pas une bonne idée.
  • NodeList est un natif de l'objet avec un constructeur privé et non public des méthodes pour ajouter des éléments, et il doit y avoir une raison à cela.
  • À moins que quelqu'un est en mesure de fournir un hack, il n'existe aucun moyen pour créer et remplir une NodeList, sans modifier le document en cours.
  • NodeList est comme un Tableau, mais aussi d'avoir l' item méthode qui fonctionne à l'aide de crochets, à l'exception de revenir null au lieu de undefined lorsque vous êtes hors de portée. Vous pouvez simplement retourner un tableau avec la méthode item mis en œuvre:

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

PS: Peut-être vous prenez pas la bonne approche et votre requête personnalisée méthode pourrait juste envelopper un document.querySelectorAll appel qui renvoie à ce que vous recherchez.

4voto

Teemu Points 11613

Depuis, il semble que la création d'un véritable NodeList à partir d'un tableau est d'avoir de graves réserves, vous pourriez peut-être utiliser un objet JS avec un self-made prototype pour émuler une NodeList à la place. Comme suit:

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

Il y a encore quelques réserves à cette solution. instanceof opérateur ne peut pas reconnaître l'objet retourné comme une NodeList. Aussi, la console de mémorisations et dirrings sont présentés différemment à partir d'une NodeList.

(* = A for boucle est utilisé pour itérer le tableau transmis, de sorte que la fonction accepte un passé NodeList trop. Si vous préférez un forEach boucle, qui peut être utilisé aussi bien comme un tableau sera transmis).

Une démo en live à jsFiddle.

2voto

Chayim Friedman Points 946

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X