Создать список узлов из одного узла в JavaScript

Это один из тех, которые кажутся такими простыми, но я не могу придумать хороший способ сделать это.

У меня есть узел, возможно, nodelist = document.getElementById("mydiv"); — мне нужно нормализовать его в список узлов. И не массив: настоящий, добросовестный nodeList объект.

Нет nodelist = [document.getElementById("mydiv")];

Никаких библиотек, пожалуйста.


person Randy Hall    schedule 12.11.2012    source источник
comment
возможный дубликат создания DOM NodeList   -  person Gabriele Petrioli    schedule 13.11.2012
comment
Очень похоже... немного другая реализация. Хотя хорошо, что эта ссылка здесь.   -  person Randy Hall    schedule 13.11.2012
comment
Учитывая проблему с моим ответом, не могли бы вы отменить мой ответ, чтобы я мог его удалить?   -  person David says reinstate Monica    schedule 28.07.2016


Ответы (5)


Возрождаю, потому что недавно вспомнил кое-что о 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

и 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
    };
}());
person Paul S.    schedule 04.07.2013
comment
Также хочу отметить, что если вы хотите использовать list.item, вам придется скрыть его, чтобы избежать нелегального вызова (возможно, используйте двухуровневое прототипирование). - person Paul S.; 04.07.2013
comment
Я понимаю, что прошло несколько лет, почему singleNode возвращает самовыполняющуюся функцию? Кажется, он отлично работает, просто возвращая Object.create... после создания пустого DocumentFragment? Просто так фрагмент документа создается один раз, независимо от того, сколько раз он вызывается? - person Regular Jo; 09.06.2021
comment
@RegularJo да, поскольку мы помещаем фрагмент документа в прототип и не модифицируем его, мы можем повторно использовать один и тот же фрагмент столько раз, сколько захотим, если мы зафиксируем его в области видимости. - person Paul S.; 11.06.2021
comment
Обновлено для ES6 - person Paul S.; 11.06.2021

Возьмите любой элемент, на который уже есть ссылка в 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;
person Paul S.    schedule 13.11.2012
comment
Вы говорите, что не обязательно можете получить доступ к нужному элементу с помощью любого селектора CSS? - person Paul S.; 13.11.2012
comment
Да. Узел мог быть создан динамически или выбран из документа. - person Randy Hall; 13.11.2012
comment
Редактирование @RandyHall должно работать практически в любой ситуации (за исключением elm.nodeType !== 1) - person Paul S.; 13.11.2012
comment
Таким образом, в основном, если это в документе или контейнере контекста, используйте свой метод, если это не так, метод Дэвида Томаса будет безопасным. Хороший. - person Randy Hall; 13.11.2012
comment
Я не знаю, почему вы недовольны массивом. Кроме как умственный вызов. Если вас не волнует, как создается NodeList, то его живость не имеет большого значения, и почти все методы и свойства такие же, как и у массивов. (Вы можете добавить элемент с помощью Array.prototype.item = function(i){return this[i];};) - person Paul S.; 13.11.2012
comment
В большинстве случаев вы будете совершенно правы. Но есть функции, написанные другими, которые я должен использовать, но не могу повлиять на это, требуя и проверяя список узлов. - person Randy Hall; 13.11.2012
comment
@RandyHall Недавно меня это вдохновило. Я не уверен, как была сделана проверка, но я почти уверен, что это пройдет. редактировать о, я вижу, я опубликовал еще один ответ, чтобы сделать то же самое, что ниже, лол .. ну, по крайней мере, то, как я написал это, на этот раз работает в старых браузерах и имеет немного меньше для каждой конструкции Д: - person Paul S.; 30.03.2014
comment
Не работает в Firefox и Chrome. Третье условие вызывает ошибку Invalid Invocation в Chrome. 4-е и 5-е условия оцениваются как false в Firefox и true в Chrome. - person Joyce Babu; 17.11.2015
comment
Мне больше понравился подход tag, fetch и untag, но я использовал атрибут данных, чтобы гарантировать отсутствие конфликтов с чем-либо еще в элементе: gist.github.com/GuyPaddock/93fbe863389a88fc9f2d495446c2598c - person GuyPaddock; 29.02.2020

Если вы ориентируетесь на браузеры, поддерживающие document.querySelectorAll, всегда будет вернуть NodeList. Так:

var nodelist = document.querySelectorAll("#mydiv");
person bfavaretto    schedule 12.11.2012
comment
Хорошая мысль, однако это не ВСЕГДА, как я получаю свой узел. +1 за идею, которая может оказаться актуальной и полезной для других! - person Randy Hall; 13.11.2012

Еще один способ сделать это на основе 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;.

person Lain inVerse    schedule 06.09.2017
comment
Я не уверен в объявлении элемента и синтаксисе +i (никогда такого раньше не видел), но Reflect.construct() работает хорошо. - person GuyPaddock; 31.01.2020
comment
Я беру это обратно ... в Safari 12 мы получаем TypeError: Reflect.construct требует, чтобы третий аргумент был конструктором, если он присутствует. - person GuyPaddock; 29.02.2020
comment
Что ж, тогда вы можете попробовать поиграть с этим: div.parentNode.querySelectorAll(:scope > :nth-child(${Array.from(div.parentNode.children).indexOf(div)+1})) Не уверен в совместимости с Safari 12, и он точно не будет работать в IE11, но он возвращает фактический NodeList. Однако у элемента должен быть родитель. Можно решить, временно добавив его в другой элемент, если parentNode имеет значение null. - person Lain inVerse; 01.03.2020

person    schedule
comment
Но nodeList instanceof NodeList возвращает false. - person Qian Chen; 05.04.2018
comment
Вы можете вернуть nodeList после добавления, например: console.log(nodeList.childNodes instanceof NodeList); // returns true - person lao; 30.07.2018
comment
Проблема в том, что добавление узла к фрагменту удаляет его из DOM документа. - person GuyPaddock; 29.02.2020