javascript - 如何将节点数组转换为静态 NodeList?

标签 javascript dom nodelist

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


我们都知道将 NodeList 转换为 Array 很容易,并且有很多方法可以做到这一点:

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

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


我为什么要这样做?

在不深入的情况下,我正在创建一种新方法来查询页面上的元素,即:

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

为了忠实于 querySelectorAll 的工作原理,我想返回一个 static collection NodeList而不是数组。


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

尝试 1:

创建文档片段

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

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

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


尝试 2:

试图“模拟”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;
})();

它将以下列方式使用:

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 中删除元素,但会导致其他错误,例如将其转换为数组时:

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

以上每个都会产生以下错误:Uncaught TypeError: Illegal invocation

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


尝试 3:

将临时属性附加到元素以重新查询它们

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

这工作得很好,直到我发现它不适用于某些元素,例如 SVG。它不会附加属性(尽管我只在 Chrome 中测试过)。


看起来这应该是一件容易的事,为什么我不能使用 NodeList 构造函数来创建一个 NodeList,为什么我不能以类似于 NodeLists 转换为数组的方式将数组转换为 NodeList ?

如何以正确的方式将节点数组转换为 NodeList?


答案不适合我的类似问题:

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

How can I convert an Array of elements into a NodeList?这个问题的答案使用了克隆节点的方法。这行不通,因为我需要访问原始节点。

Create node list from a single node in JavaScript使用文档片段方法(尝试 1)。其他答案在尝试 2 和 3 中尝试了类似的事情。

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

最佳答案

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

因为 DOM specification for the NodeList interface未指定 the WebIDL [Constructor] attribute , 所以它不能直接在用户脚本中创建。

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

在您的情况下,这肯定是一个有用的函数,但 DOM 规范中没有指定存在这样的函数。因此,不可能从 Node 数组直接填充 NodeList

虽然我严重怀疑您会称之为“正确的方式”来处理事情,但一个丑陋的解决方案是找到 CSS 选择器来唯一地选择您想要的元素,并将所有这些路径传递到 querySelectorAll作为逗号分隔的选择器:

// 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) ...。也就是说,每个元素都可以理解为作为子元素(等等)的子元素一直存在于根元素之上。通过在节点的祖先路径中找到每个 child 的索引,我们可以唯一地标识它。

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

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

关于javascript - 如何将节点数组转换为静态 NodeList?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38440492/

相关文章:

javascript - Nextjs - 在服务器端访问 url 参数

javascript - fqdn 后缺少正斜杠

python - 从 CSV 文件中提取列以用作 NetworkX 中的节点列表

Javascript 对象列表返回 null 或 undefined 但不是

javascript - 如何从 Amcharts-Stockchart 中删除 Thousand 'k' 后缀

javascript - 无法使用accept属性在Chrome中上传zip格式的文件

javascript - 从 HIGHCHART 中删除零

javascript - Rows-Element,这些空格从哪里来?

Java XML Dom 文档 getElementsByTagNameNS 返回空 NodeList

Java 将 NodeList 转换为 String 以检查 xml 注释