Как преобразовать массив узлов в статический NodeList?
ПРИМЕЧАНИЕ. Прежде чем этот вопрос станет дублированным, в нижней части этого вопроса есть раздел, в котором рассматривается, почему несколько подобных вопросов не дают ответа, который я ищу.
Мы все знаем, что легко преобразовать NodeList в массив и есть много способов сделать это:
[].slice.call(someNodeList)
// or
Array.from(someNodeList)
// etc...
То, чем я являюсь, - это обратное; Как преобразовать массив узлов в статический NodeList?
Почему я хочу это сделать?
Не углубляясь в вещи, я создаю новый метод для запроса элементов на странице i.e:
Document.prototype.customQueryMethod = function (...args) {...}
Пытаясь оставаться верным тому, как работает querySelectorAll
, я хочу вернуть статический сбор NodeList
вместо массива.
Я подошел к проблеме тремя различными способами:
Попытка 1:
Создание фрагмента документа
function createNodeList(arrayOfNodes) {
let fragment = document.createDocumentFragment();
arrayOfNodes.forEach((node) => {
fragment.appendChild(node);
});
return fragment.childNodes;
}
Пока это возвращает NodeList, это не работает, потому что вызов appendChild
удаляет node из его текущего местоположения в 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;
})();
И он будет использоваться следующим образом:
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
Я также стараюсь избегать "подражания" nodeList с помощью поддельного конструктора нодлистов, поскольку я считаю, что, вероятно, будущие непреднамеренные последствия.
Попытка 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, и почему я не могу наложить массив на NodeList так же, как NodeLists, которые будут переданы в массивы
Как преобразовать массив узлов в NodeList, правильный путь?
Аналогичные вопросы, которые не отвечают на меня:
Следующие вопросы похожи на следующие. К сожалению, эти вопросы/ответы не решают мою конкретную проблему по следующим причинам.
Как преобразовать массив элементов в NodeList? Ответ в этом вопросе использует метод, который клонирует узлы. Это не сработает, потому что мне нужно иметь доступ к исходным узлам.
Создайте node список из одного node в JavaScript, используя подход фрагмента документа (попытка 1). Другие ответы аналогичны попыткам при попытках 2 и 3.
Создание DOM NodeList использует E4X
и поэтому не применяется. И хотя он использует это, он по-прежнему удаляет элементы из DOM.
Ответы
Ответ 1
почему я не могу использовать конструктор NodeList для создания NodeList
Поскольку спецификация DOM для интерфейса NodeList
не указывает атрибут WebIDL [конструктор], поэтому он не может быть создан непосредственно в пользовательских сценариях.
почему я не могу наложить массив на NodeList так же, как NodeLists отбрасываются в массивы?
Это, безусловно, будет полезной функцией в вашем случае, но такая функция не указана в спецификации DOM. Таким образом, невозможно напрямую заполнить NodeList
из массива Node
s.
В то время как я серьезно сомневаюсь, что вы бы назвали это "правильным способом", чтобы разобраться в вещах, одно уродливое решение - это поиск селекторов 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 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) ...
. То есть каждый элемент может быть понят, что он существует как дочерний элемент дочернего элемента ребенка (и т.д.), Вплоть до корневого элемента. Найдя индекс каждого дочернего элемента в пути node предка, мы можем его однозначно идентифицировать.
Обратите внимание, что это не сохранит узлы Text
-type, потому что querySelectorAll
(и пути CSS вообще) не может выбирать текстовые узлы.
Я понятия не имею, достаточно ли это для ваших целей.
Ответ 2
Вот мои два цента:
- Документ - это родной объект, и его расширение может быть не очень хорошим.
- NodeList - это собственный объект с частным конструктором и не содержит общедоступных методов для добавления элементов, и для этого должна быть причина.
- Если кто-то не может обеспечить взлома, нет способа создать и заполнить NodeList без изменения текущего документа.
- NodeList похож на массив, но имеет метод
item
, который работает так же, как и квадратные скобки, за исключением возврата null
вместо undefined
, когда вы находитесь за пределами допустимого диапазона. Вы можете просто вернуть массив с помощью метода item:
myArray.item= function (e) { return this[e] || null; }
PS: Возможно, вы используете неправильный подход, и ваш пользовательский метод запроса может просто обернуть вызов document.querySelectorAll
, который возвращает то, что вы ищете.
Ответ 3
Так как кажется, что создание реального NodeList из массива имеет серьезные спады, возможно, вы можете использовать обычный JS-объект с самодельным прототипом для эмуляции NodeList. Например:
var nodeListProto = Object.create({}, {
item: {
value: function(x) {
return (Object.getOwnPropertyNames(this).indexOf(x.toString()) > -1) ? this[x] : null;
},
enumerable: true
},
length: {
get: function() {
return Object.getOwnPropertyNames(this).length;
},
enumerable: true
}
}),
getNodeList = function(nodes) {
var n, eN = nodes.length,
list = Object.create(nodeListProto);
for (n = 0; n < eN; n++) { // *
Object.defineProperty(list, n.toString(), {
value: nodes[n],
enumerable: true
});
}
return (list.length) ? list : null;
};
// Usage:
var nodeListFromArray = getNodeList(arrayOfNodes);
Есть еще некоторые резервы с этим решением. Оператор instanceof
не может распознать возвращаемый объект как NodeList. Кроме того, консольные записи и dirrings показаны иначе, чем NodeList.
Цикл (* = A for
используется для итерации переданного массива, так что функция может принимать и переданный NodeList. Если вы предпочитаете цикл forEach
, который также может использоваться, если массив будет передан только.)
Живая демонстрация в jsFiddle.