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

Kev*_*Bot 34 javascript dom nodelist

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


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

[].slice.call(someNodeList)
// or
Array.from(someNodeList)
// etc...
Run Code Online (Sandbox Code Playgroud)

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


我为什么要这样做?

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

Document.prototype.customQueryMethod = function (...args) {...}
Run Code Online (Sandbox Code Playgroud)

为了坚持如何querySelectorAll工作,我想要返回静态集合NodeList而不是数组.


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

尝试1:

创建文档片段

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

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

另一种变化涉及cloning节点并返回克隆.但是,现在您将返回克隆节点,这些节点没有引用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;
})();
Run Code Online (Sandbox Code Playgroud)

它将以下列方式使用:

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
Run Code Online (Sandbox Code Playgroud)

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

let arr = [].slice.call(nodeList);
// or
let arr = Array.from(nodeList);
Run Code Online (Sandbox Code Playgroud)

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

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


尝试3:

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

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

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


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

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


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

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

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

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

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

aps*_*ers 19

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

由于接口DOM规范NodeList未指定WebIDL [Constructor]属性,因此无法直接在用户脚本中创建.

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

在你的情况下,这肯定是一个有用的函数,但是在DOM规范中没有指定这样的函数.因此,不可能直接NodeListNodes 数组中填充a .

虽然我严重怀疑你会称这种"正确的方式"去处理事情,但是一个丑陋的解决方案是找到唯一选择所需元素的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, ...]);
Run Code Online (Sandbox Code Playgroud)

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

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

不过,我不知道这对你的目的是否足够高效.


Pab*_*ano 4

这是我的两分钱:

  • Document 是一个本机对象,扩展它可能不是一个好主意。
  • NodeList 是一个原生对象,有私有构造函数,没有公共方法来添加元素,这肯定是有原因的。
  • 除非有人能够提供 hack,否则无法在不修改当前文档的情况下创建和填充 NodeList。
  • NodeList 就像一个数组,但它item的方法就像使用方括号一样,除了返回null而不是undefined超出范围时返回。您可以只返回一个实现了 item 方法的数组:

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

PS:也许您采用了错误的方法,并且您的自定义查询方法可能只是包装了一个document.querySelectorAll返回您正在查找的内容的调用。