web-dev-qa-db-ja.com

ノードの配列を静的NodeListに変換するにはどうすればよいですか?

注:この質問が重複していると見なされる前に、この質問の下部に、いくつかの同様の質問が私が探している答えを提供しない理由を説明するセクションがあります。


NodeListを配列に変換するのは簡単であり、それを行うには多くの方法があることは誰もが知っています。

[].slice.call(someNodeList)
// or
Array.from(someNodeList)
// etc...

私が求めているのはその逆です。 ノードの配列を静的NodeListに変換するにはどうすればよいですか?


なぜ私はこれをしたいのですか?

物事に深く入り込むことなく、ページ上の要素をクエリするための新しいメソッドを作成しています。

Document.prototype.customQueryMethod = function (...args) {...}

querySelectorAllの動作に忠実であり続けようとして、配列の代わりに 静的コレクションNodeList を返したいと思います。


私はこれまでに3つの異なる方法で問題に取り組みました。

試行1:

ドキュメントフラグメントの作成

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

これは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;
})();

そして、それは次のように使用されます:

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を「模倣」することを避けようとしています。これは、将来意図しない結果が生じる可能性があるためです。


試行3:

要素に一時属性を付加して、要素を再クエリします

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に変換するにはどうすればよいですか?


私にはうまくいかない答えがある同様の質問:

次の質問はこれに似ています。残念ながら、これらの質問/回答は、次の理由で私の特定の問題を解決しません。

要素の配列をNodeListに変換するにはどうすればよいですか? この質問の答えは、ノードのクローンを作成するメソッドを使用しています。元のノードにアクセスする必要があるため、これは機能しません。

JavaScriptで単一ノードからノードリストを作成する ドキュメントフラグメントアプローチを使用します(試行1)。他の回答は、試行2、および3で同様のことを試みます。

DOM NodeListの作成E4Xを使用しているため、適用されません。そして、それを使用していても、DOMから要素を削除します。

34
KevBot

nodeListコンストラクターを使用してNodeListを作成できないのはなぜですか

NodeListインターフェイスのDOM仕様WebIDL [コンストラクタ]属性 を指定していないため、ユーザースクリプトで直接作成することはできません。

nodeListsが配列にキャストされるのと同様の方法で配列をNodeListにキャストできないのはなぜですか?

これは確かにあなたの場合に役立つ関数ですが、そのような関数はDOM仕様に存在するように指定されていません。したがって、NodeListsの配列から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タイプのノードは保持されないことに注意してください。

しかし、これがあなたの目的にとって十分に機能するかどうかはわかりません。

20
apsillers

配列から実際の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ループが必要な場合は、これも使用できます。配列のみが渡される限り。)

jsFiddleでのライブデモ

4
Teemu

これが私の2セントです:

  • ドキュメントはネイティブオブジェクトであり、それを拡張することはお勧めできません。
  • NodeListは、プライベートコンストラクターを持ち、要素を追加するためのパブリックメソッドを持たないネイティブオブジェクトであり、それには理由があるはずです。
  • 誰かがハックを提供できない限り、現在のドキュメントを変更せずにNodeListを作成してデータを入力する方法はありません。
  • NodeListは配列に似ていますが、範囲外の場合にitemの代わりにnullを返すことを除いて、角括弧を使用するのと同じように機能するundefinedメソッドがあります。 itemメソッドが実装された配列を返すことができます。

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

PS:おそらくあなたは間違ったアプローチを取っているので、カスタムクエリメソッドはあなたが探しているものを返すdocument.querySelectorAll呼び出しをラップするだけかもしれません。

4
Pablo Lozano

各要素の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;
}
1
Chayim Friedman