如何将节点数组转换为静态NodeList? [英] How can I convert an Array of nodes to a static NodeList?

查看:86
本文介绍了如何将节点数组转换为静态NodeList?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

注意:在假设此问题重复之前,此问题底部有一个部分解决了为什么一些类似的问题无法提供我正在寻找的答案。

我们都知道将NodeList转换为数组很容易,有很多方法可以做到:

We all know that it is easy to convert a NodeList to an Array and there are many ways to do it:

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

我所追求的是相反的; 如何将节点数组转换为静态NodeList?

What I am after is the reverse; how can I convert an array of nodes into a static NodeList?

我没有深入研究,我正在创建一种新方法来查询页面上的元素,即:

Without getting too deep into things, I am creating a new method to query elements on the page i.e:

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

试图保持 querySelectorAll 如何工作,我想返回一个静态集合 NodeList 而不是数组。

Trying to stay true to how querySelectorAll works, I want to return a static collection NodeList instead of an array.

到目前为止,我已经以三种不同的方式解决了这个问题:

I have approached the problem in three different ways so far:

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

虽然这确实返回了NodeList,但这不起作用,因为调用 appendChild 从DOM中的当前位置移除节点(它应该留在哪里)。

While this does return a NodeList, this does not work because calling appendChild removes the node from its current location in the DOM (where it should stay).

另一种变体涉及克隆节点并返回克隆。但是,现在返回克隆节点,这些节点没有引用DOM中的实际节点。

Another variation of this involves cloning the nodes and returning the clones. However, now you are returning the cloned nodes, which have no reference to the actual nodes in the DOM.

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

它将按以下方式使用:

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

虽然这种特殊方法不会从DOM中删除元素,但它导致其他错误,例如将其转换为数组时:

While this particular approach does not remove the elements from the DOM, it causes other errors, such as when converting it to an array:

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

以上各项都会产生以下错误:未捕获TypeError:非法调用

Each of the above produces the following error: Uncaught TypeError: Illegal invocation

我也试图避免使用伪节点列表构造函数模仿nodeList,因为我相信这可能会产生未来意想不到的后果。

I am also trying to avoid "mimicking" a nodeList with a fake nodelist constructor as I believe that will likely have future unintended consequences.

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

这很好用,直到我发现它没有适用于某些元素,例如 SVG 。它不会附加属性(虽然我只在Chrome中测试过这个)。

This was working well, until I discovered that it doesn't work for certain elements, like SVG's. It will not attach the attribute (although I did only test this in Chrome).

这似乎应该很容易要做的事情,为什么我不能使用NodeList构造函数来创建NodeList,为什么我不能以类似于NodeLists转换为数组的方式将数组转换为NodeList?

It seems this should be an easy thing to do, why can't I use the NodeList constructor to create a NodeList, and why can't I cast an array to a NodeList in a similar fashion that NodeLists are cast to arrays?

类似问题的答案对我不起作用:

以下问题与此类似。不幸的是,由于以下原因,这些问题/答案无法解决我的特定问题。

The following questions are similar to this one. Unfortunately, these questions/answers don't solve my particular problem for the following reasons.

如何将元素数组转换为NodeList?这个问题的答案使用克隆节点的方法。这不起作用,因为我需要访问原始节点。

How can I convert an Array of elements into a NodeList? The answer in this question uses a method that clones nodes. This will not work because I need to have access to the original nodes.

从JavaScript中的单个节点创建节点列表使用文档片段方法(尝试1)。其他答案在Attempts 2和3尝试类似的事情。

Create node list from a single node in JavaScript uses the document fragment approach (Attempt 1). The other answers try similar things at Attempts 2, and 3.

创建DOM NodeList 使用 E4X ,因此不适用。即使它正在使用它,它仍然从DOM中删除元素。

Creating a DOM NodeList is using E4X, and therefore does not apply. And even though it is using that, it still removes the elements from the DOM.

推荐答案


为什么我不能使用NodeList构造函数来创建NodeList

why can't I use the NodeList constructor to create a NodeList

因为 NodeList 界面的DNS规范未指定 WebIDL [Constructor]属性,因此无法直接在用户脚本中创建。

Because the DOM specification for the NodeList interface does not specify the WebIDL [Constructor] attribute, so it cannot be created directly in user scripts.


<为什么我不能以类似于NodeLists转换为数组的方式将数组转换为NodeList?

why can't I cast an array to a NodeList in a similar fashion that NodeLists are cast to arrays?

这肯定会在你的情况下是一个有用的函数,但在DOM规范中没有指定这样的函数。因此,无法直接从节点的数组中填充 NodeList

This would certainly be a helpful function to have in your case, but no such function is specified to exist in the DOM specification. Thus, it is not possible to directly populate a NodeList from an array of Nodes.

虽然我严重怀疑你会称之为正确的方法,但是一个丑陋的解决方案是找到唯一选择所需元素的CSS选择器,并将所有这些路径传递给 querySelectorAll 作为逗号分隔选择器:

While I seriously doubt you would call this "the right way" to go about things, one ugly solution is find CSS selectors that uniquely select your desired elements, and pass all of those paths into querySelectorAll as a comma-separated selector:

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

这可以通过查找CSS字符串来唯一选择每个元素,其中每个选择器的格式为 html> :nth-​​child(x)> :nth-​​child(y)> :nth-​​child(z)... 。也就是说,每个元素可以被理解为作为子元素的子元素(等等)一直存在于根元素之上。通过在节点的祖先路径中找到每个子节点的索引,我们可以唯一地识别它。

This works by finding CSS strings to uniquely select each element, where each selector is of the form html > :nth-child(x) > :nth-child(y) > :nth-child(z) .... That is, each element can be understood to exist as a child of a child of a child (etc.) all the way up the root element. By finding the index of each child in the node's ancestor path, we can uniquely identify it.

请注意,这不会保留 Text -type节点,因为 querySelectorAll (和一般的CSS路径)无法选择文本节点。

Note that this will not preserve Text-type nodes, because querySelectorAll (and CSS paths in general) cannot select text nodes.

我不知道这是否足以满足您的目的。

I have no idea if this will be sufficiently performant for your purposes, though.

这篇关于如何将节点数组转换为静态NodeList?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆