【问题标题】:Create node list from a single node in JavaScript从 JavaScript 中的单个节点创建节点列表
【发布时间】:2012-11-01 08:43:54
【问题描述】:

这是其中一个看起来很简单,但我想不出一个好的方法来解决它。

我有一个节点,可能是nodelist = document.getElementById("mydiv"); - 我需要将它标准化为一个节点列表。也不是一个数组:一个真实的、真正的 nodeList 对象。

不是 nodelist = [document.getElementById("mydiv")];

请不要使用库。

【问题讨论】:

  • Creating a DOM NodeList的可能重复
  • 非常相似......实现略有不同。很高兴在这里有这个参考。
  • 鉴于我的回答存在问题,您能否取消接受我的回答以便我删除它?

标签: javascript nodes nodelist


【解决方案1】:

如果您的目标浏览器支持document.querySelectorAll,它将始终返回NodeList。所以:

var nodelist = document.querySelectorAll("#mydiv");

【讨论】:

  • 好主意,但是这并不总是我获得节点的方式。为其他人可能认为相关且有用的想法 +1!
【解决方案2】:
var nodeList = document.createDocumentFragment();
nodeList.appendChild(document.getElementById("myDiv"));

【讨论】:

  • 但是nodeList instanceof NodeList 返回false。
  • 你可以像这样追加后返回nodeList:console.log(nodeList.childNodes instanceof NodeList); // returns true
  • 这样做的问题是,将节点添加到片段中会将其从文档 DOM 中删除。
  • 这对于编写单元测试很方便
【解决方案3】:

获取 JavaScript 中已经引用的任何元素,给它一个我们可以使用选择器找到的属性,将其作为列表查找,删除属性,返回列表。

function toNodeList(elm){
    var list;
    elm.setAttribute('wrapNodeList','');
    list = document.querySelectorAll('[wrapNodeList]');
    elm.removeAttribute('wrapNodeList');
    return list;
}

延伸自 bfavaretto 的回答。


function toNodeList(elm, context){
    var list, df;
    context = context // context provided
           || elm.parentNode; // element's parent
    if(!context && elm.ownerDocument){ // is part of a document
        if(elm === elm.ownerDocument.documentElement || elm.ownerDocument.constructor.name === 'DocumentFragment'){ // is <html> or in a fragment
            context = elm.ownerDocument;
        }
    }
    if(!context){ // still no context? do David Thomas' method
        df = document.createDocumentFragment();
        df.appendChild(elm);
        list = df.childNodes;
        // df.removeChild(elm); // NodeList is live, removeChild empties it
        return list;
    }
    // selector method
    elm.setAttribute('wrapNodeList','');
    list = context.querySelectorAll('[wrapNodeList]');
    elm.removeAttribute('wrapNodeList');
    return list;
}

我最近想到了另一种方法

var _NodeList = (function () {
    var fragment = document.createDocumentFragment();
    fragment.appendChild(document.createComment('node shadows me'));
    function NodeList (node) {
        this[0] = node;
    };
    NodeList.prototype = (function (proto) {
        function F() {} // Object.create shim
        F.prototype = proto;
        return new F();
    }(fragment.childNodes));
    NodeList.prototype.item = function item(i) {
        return this[+i || 0];
    };
    return NodeList;
}());

现在

var list = new _NodeList(document.body); // note **new**
list.constructor === NodeList; // all these are true
list instanceof NodeList;
list.length === 1;
list[0] === document.body;
list.item(0) === document.body;

【讨论】:

  • 你是说你不能用任何 CSS 选择器访问你想要的元素?
  • 是的。该节点可能是从文档中动态创建或选择的。
  • @RandyHall edit 几乎可以在任何情况下工作(elm.nodeType !== 1 除外)
  • 所以基本上,如果它在文档或上下文容器中,请执行您的方法,如果不是,David Thomas 的方法将是安全的。不错。
  • 在大多数情况下,您是完全正确的。但是我必须使用其他人编写的函数,但不能影响该需求并专门检查节点列表。
【解决方案4】:

重提这个是因为我最近想起了一些关于 JavaScript 的事情。这取决于如何检查 NodeList,但是..

const singleNode = ((nodeList) => (node) => {
  const layer = { // define our specific case
    0: { value: node, enumerable: true },
    length: { value: 1 },
    item: {
      value(i) {
        return this[+i || 0];
      }, 
      enumerable: true,
    },
  };
  return Object.create(nodeList, layer); // put our case on top of true NodeList
})(document.createDocumentFragment().childNodes); // scope a true NodeList

现在,如果你这样做了

const list = singleNode(document.body); // for example

list instanceof NodeList; // true
list.constructor === NodeList; // true

and list 具有属性 length 1 和 0 作为您的节点,以及从 NodeList 继承的任何内容。

如果你不能使用Object.create,你可以做同样的事情,除了使用原型nodelist的构造函数并设置this['0'] = node;,this['length'] = 1;并使用new创建。


ES5 版本

var singleNode = (function () {
    // make an empty node list to inherit from
    var nodelist = document.createDocumentFragment().childNodes;
    // return a function to create object formed as desired
    return function (node) {
        return Object.create(nodelist, {
            '0': {value: node, enumerable: true},
            'length': {value: 1},
            'item': {
                "value": function (i) {
                    return this[+i || 0];
                }, 
                enumerable: true
            }
        }); // return an object pretending to be a NodeList
    };
}());

【讨论】:

  • 还要指出,如果你想使用list.item,你必须隐藏它以避免非法调用(可能使用两级原型)。
  • 我很感激它已经好几年了,为什么singleNode返回一个自执行函数?在创建空的DocumentFragment 后返回Object.create... 似乎工作正常?是不是只创建一次文档片段,不管它被调用多少次?
  • @RegularJo 是的,因为我们将文档片段放在原型中而不是修改它,如果我们在范围内捕获它,我们可以多次重复使用相同的片段
  • 为 ES6 更新
【解决方案5】:

基于 Reflect.construct 的另一种方法:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/construct

与其他情况一样,它需要修补 NodeList.prototype.item 才能调用此函数。

NodeList.prototype.item = function item(i) {
    return this[+i || 0];
};
let nl = Reflect.construct(Array, [], NodeList);

要使用节点创建它,传递节点数组作为第二个参数。 此方法通过检查:

list instanceof NodeList; // true
list.constructor === NodeList; // true

使用它创建的数组可以使用for..of、forEach 和其他标准方法进行迭代,您可以使用简单的nl[n] = node; 向其中添加元素。

【讨论】:

  • 我不确定项目声明和+i 语法(以前从未见过),但 Reflect.construct() 效果很好。
  • 我收回它......在 Safari 12 中,我们得到“TypeError: Reflect.construct requires the third argument to be a constructor if present”。
  • 好吧,你可以试试这个: div.parentNode.querySelectorAll(:scope &gt; :nth-child(${Array.from(div.parentNode.children).indexOf(div)+1}))实际的节点列表。但是,元素必须有一个父级。可以通过临时将其添加到另一个元素中来解决,以防 parentNode 为空。
  • 你甚至不需要再修补原型了!
猜你喜欢
  • 1970-01-01
  • 2021-08-03
  • 2019-02-08
  • 2020-03-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-10-04
相关资源
最近更新 更多