注:この質問が重複していると見なされる前に、この質問の下部に、いくつかの同様の質問が私が探している答えを提供しない理由を説明するセクションがあります。
NodeListを配列に変換するのは簡単であり、それを行うには多くの方法があることは誰もが知っています。
[].slice.call(someNodeList)
// or
Array.from(someNodeList)
// etc...
私が求めているのはその逆です。 ノードの配列を静的NodeListに変換するにはどうすればよいですか?
物事に深く入り込むことなく、ページ上の要素をクエリするための新しいメソッドを作成しています。
Document.prototype.customQueryMethod = function (...args) {...}
querySelectorAll
の動作に忠実であり続けようとして、配列の代わりに 静的コレクションNodeList
を返したいと思います。
私はこれまでに3つの異なる方法で問題に取り組みました。
function createNodeList(arrayOfNodes) {
let fragment = document.createDocumentFragment();
arrayOfNodes.forEach((node) => {
fragment.appendChild(node);
});
return fragment.childNodes;
}
これはNodeListを返しますが、appendChild
を呼び出すと、ノードがDOM内の現在の場所(ノードが留まるはずの場所)から削除されるため、これは機能しません。
これの別のバリエーションには、ノードをcloning
し、クローンを返すことが含まれます。ただし、ここでは、DOM内の実際のノードへの参照がないクローンノードを返しています。
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コンストラクターでnodeListを「模倣」することを避けようとしています。これは、将来意図しない結果が生じる可能性があるためです。
function createNodeList(arrayOfNodes) {
arrayOfNodes.forEach((node) => {
node.setAttribute('QUERYME', '');
});
let nodeList = document.querySelectorAll('[QUERYME]');
arrayOfNodes.forEach((node) => {
node.removeAttribute('QUERYME');
});
return nodeList;
}
これは、SVG
'sなどの特定の要素では機能しないことがわかるまではうまく機能していました。属性は添付されません(ただし、これはChromeでのみテストしました)。
これは簡単なことのようです。NodeListコンストラクターを使用してNodeListを作成できないのはなぜですか。また、NodeListが配列にキャストされるのと同様の方法で配列をNodeListにキャストできないのはなぜですか。
私にはうまくいかない答えがある同様の質問:
次の質問はこれに似ています。残念ながら、これらの質問/回答は、次の理由で私の特定の問題を解決しません。
要素の配列をNodeListに変換するにはどうすればよいですか? この質問の答えは、ノードのクローンを作成するメソッドを使用しています。元のノードにアクセスする必要があるため、これは機能しません。
JavaScriptで単一ノードからノードリストを作成する ドキュメントフラグメントアプローチを使用します(試行1)。他の回答は、試行2、および3で同様のことを試みます。
DOM NodeListの作成 はE4X
を使用しているため、適用されません。そして、それを使用していても、DOMから要素を削除します。
nodeListコンストラクターを使用してNodeListを作成できないのはなぜですか
NodeList
インターフェイスのDOM仕様 は WebIDL [コンストラクタ]属性 を指定していないため、ユーザースクリプトで直接作成することはできません。
nodeListsが配列にキャストされるのと同様の方法で配列をNodeListにキャストできないのはなぜですか?
これは確かにあなたの場合に役立つ関数ですが、そのような関数はDOM仕様に存在するように指定されていません。したがって、NodeList
sの配列からNode
を直接入力することはできません。
これを「正しい方法」と呼んで物事を進めることは真剣に疑っていますが、1つの醜い解決策は、目的の要素を一意に選択する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) ...
の形式です。つまり、各要素は、ルート要素のずっと上にある子の子(など)の子として存在すると理解できます。ノードの祖先パスで各子のインデックスを見つけることにより、それを一意に識別できます。
Text
(および一般的なCSSパス)はテキストノードを選択できないため、これではquerySelectorAll
タイプのノードは保持されないことに注意してください。
しかし、これがあなたの目的にとって十分に機能するかどうかはわかりません。
配列から実際の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として認識できません。また、コンソールのログとディレクトリは、NodeListとは異なる方法で表示されます。
(* = for
ループは渡された配列を反復するために使用されるため、関数は渡されたNodeListも受け入れることができます。forEach
ループが必要な場合は、これも使用できます。配列のみが渡される限り。)
これが私の2セントです:
item
の代わりにnull
を返すことを除いて、角括弧を使用するのと同じように機能するundefined
メソッドがあります。 itemメソッドが実装された配列を返すことができます。myArray.item= function (e) { return this[e] || null; }
PS:おそらくあなたは間違ったアプローチを取っているので、カスタムクエリメソッドはあなたが探しているものを返すdocument.querySelectorAll
呼び出しをラップするだけかもしれません。
各要素のouterHTML
プロパティを使用して、それを親要素に追加できます(document.createElement()
によって作成されます。要素の種類は関係ありません)。たとえば、ES6では次のようになります。
function getNodeList(elements) {
const parentElement = document.createElement('div');
// This can be a differnet element type, too (but only block (display: block;) element, because it impossible to put block element in inline element, and maybe 'elements' array contains a block element).
let HTMLString = '';
for (let element of elements) {
HTMLString += element.outerHTML;
}
parentElement.innerHTML = HTMLString;
return parentElement.childNodes;
}