Pertanyaan Bagaimana saya bisa mengkonversi Array dari node ke NodeList statis?


CATATAN: Sebelum pertanyaan ini diasumsikan duplikat, ada bagian di bagian bawah pertanyaan ini yang membahas mengapa beberapa pertanyaan serupa tidak memberikan jawaban yang saya cari.


Kita semua tahu bahwa mudah untuk mengonversi NodeList ke Array dan ada banyak cara untuk melakukannya:

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

Apa yang saya cari adalah kebalikannya; bagaimana saya bisa mengubah array node menjadi NodeList statis?


Mengapa saya ingin melakukan ini?

Tanpa terlalu dalam hal, saya membuat metode baru untuk menanyakan elemen pada halaman yaitu:

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

Mencoba untuk tetap setia pada bagaimana caranya querySelectorAll bekerja, saya ingin mengembalikan koleksi statis NodeList bukannya sebuah array.


Saya telah mendekati masalah dalam tiga cara berbeda sejauh ini:

Mencoba 1:

Membuat Fragmen Dokumen

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

Sementara ini mengembalikan NodeList, ini tidak berfungsi karena memanggil appendChild menghapus node dari lokasinya saat ini di DOM (di mana ia harus tinggal).

Variasi lain dari ini melibatkan cloning simpul dan mengembalikan klon. Namun, sekarang Anda mengembalikan node kloning, yang tidak memiliki referensi ke node sebenarnya di DOM.


Mencoba 2:

Mencoba untuk "mengejek" konstruktor 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;
})();

Dan itu akan digunakan dengan cara berikut:

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

Meskipun pendekatan khusus ini tidak menghapus elemen dari DOM, ini menyebabkan kesalahan lain, seperti saat mengonversinya ke larik:

let arr = [].slice.call(nodeList);
// or
let arr = Array.from(nodeList);

Setiap hal di atas menghasilkan kesalahan berikut: Uncaught TypeError: Illegal invocation

Saya juga mencoba untuk menghindari "meniru" nodeList dengan konstruktor nodelist palsu karena saya percaya bahwa kemungkinan akan memiliki konsekuensi yang tidak diinginkan di masa depan.


Mencoba 3:

Melampirkan atribut sementara ke elemen untuk meminta kembali mereka

function createNodeList(arrayOfNodes) {
    arrayOfNodes.forEach((node) => {
        node.setAttribute('QUERYME', '');
    });
    let nodeList = document.querySelectorAll('[QUERYME]');
    arrayOfNodes.forEach((node) => {
        node.removeAttribute('QUERYME');
    });
    return nodeList;
}

Ini bekerja dengan baik, sampai saya menemukan itu itu tidak berfungsi untuk elemen tertentu, seperti SVG's. Ini tidak akan melampirkan atribut (meskipun saya hanya menguji ini di Chrome).


Sepertinya ini seharusnya menjadi hal yang mudah dilakukan, mengapa saya tidak dapat menggunakan konstruktor NodeList untuk membuat NodeList, dan mengapa saya tidak bisa memberikan array ke NodeList dengan cara yang sama seperti NodeLists dilemparkan ke array?

Bagaimana saya bisa mengkonversi array node ke NodeList, dengan cara yang benar?


Pertanyaan serupa yang memiliki jawaban yang tidak berhasil untuk saya:

Pertanyaan-pertanyaan berikut serupa dengan yang satu ini. Sayangnya, pertanyaan / jawaban ini tidak menyelesaikan masalah khusus saya karena alasan berikut.

Bagaimana saya bisa mengonversi Array elemen menjadi NodeList? Jawaban dalam pertanyaan ini menggunakan metode yang mengkloning node. Ini tidak akan berfungsi karena saya harus memiliki akses ke node asli.

Buat daftar node dari satu node di JavaScript menggunakan pendekatan fragmen dokumen (Attempt 1). Jawaban yang lain mencoba hal yang sama di Attempts 2, dan 3.

Membuat NodeList DOM menggunakan E4X, dan karena itu tidak berlaku. Dan meskipun menggunakan itu, ia masih menghapus elemen-elemen dari DOM.


32
2017-07-18 15:22


asal


Jawaban:


mengapa saya tidak dapat menggunakan konstruktor NodeList untuk membuat NodeList

Karena Spesifikasi DOM untuk NodeList antarmuka tidak menentukan atribut WebIDL [Constructor], sehingga tidak dapat dibuat langsung di skrip pengguna.

mengapa saya tidak bisa memberikan array ke NodeList dengan cara yang sama dengan NodeLists yang dilemparkan ke array?

Ini tentunya akan menjadi fungsi yang bermanfaat untuk dimiliki dalam kasus Anda, tetapi tidak ada fungsi yang ditetapkan untuk ada dalam spesifikasi DOM. Dengan demikian, tidak mungkin untuk secara langsung mengisi a NodeList dari array Nodes.

Sementara saya benar-benar meragukan Anda akan menyebutnya "cara yang benar" untuk melakukan banyak hal, satu solusi jelek adalah menemukan pemilih CSS yang secara unik memilih elemen yang Anda inginkan, dan meneruskan semua jalur tersebut ke querySelectorAll sebagai pemilih yang dipisahkan koma:

// 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, ...]);

Ini bekerja dengan menemukan string CSS untuk secara unik memilih setiap elemen, di mana setiap pemilih berada dalam formulir html > :nth-child(x) > :nth-child(y) > :nth-child(z) .... Artinya, setiap elemen dapat dipahami ada sebagai anak seorang anak kecil (dll.) Sepanjang jalan naik elemen root. Dengan menemukan indeks setiap anak di jalur leluhur node, kita dapat mengidentifikasi secara unik.

Perhatikan bahwa ini tidak akan dipertahankan Text-jenis node, karena querySelectorAll (dan jalur CSS secara umum) tidak dapat memilih node teks.

Saya tidak tahu apakah ini akan cukup berkinerja untuk tujuan Anda.


18
2017-07-18 16:39



Ini dua sen saya:

  • Dokumen adalah objek asli dan memperluasnya mungkin bukan ide yang baik.
  • NodeList adalah objek asli dengan konstruktor pribadi dan tidak ada metode publik untuk menambahkan elemen, dan pasti ada alasan untuk itu.
  • Kecuali seseorang mampu memberikan hack, tidak ada cara untuk membuat dan mengisi NodeList tanpa memodifikasi dokumen saat ini.
  • NodeList seperti Array, tetapi memiliki item metode yang berfungsi seperti menggunakan tanda kurung siku, dengan pengecualian mengembalikan null dari pada undefined ketika Anda berada di luar jangkauan. Anda hanya dapat mengembalikan array dengan metode item yang diterapkan:

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

PS: Mungkin Anda mengambil pendekatan yang salah dan metode permintaan khusus Anda hanya bisa membungkus document.querySelectorAll panggilan yang mengembalikan apa yang Anda cari.


4
2017-07-18 16:19



Karena tampaknya membuat NodeList asli dari array mengalami fallback yang parah, mungkin Anda bisa menggunakan objek JS biasa dengan prototipe buatan sendiri untuk meniru NodeList sebagai gantinya. Seperti ini:

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);

Masih ada beberapa fallback dengan solusi ini. instanceof operator tidak dapat mengenali objek yang dikembalikan sebagai NodeList. Juga, konsol logging dan dirrings ditampilkan berbeda dari NodeList.

(* = A for loop digunakan untuk mengiterasi larik yang diteruskan, sehingga fungsi tersebut dapat menerima NodeList yang dilewatkan juga. Jika Anda lebih suka a forEach loop, yang bisa digunakan juga, asalkan hanya array yang dilewati.)

Demo langsung di jsFiddle.


4
2017-07-18 21:33



Kamu dapat memakai outerHTML milik setiap elemen, dan tambahkan ke elemen induk (yang akan dibuat oleh document.createElement(), tipe elemen tidak masalah). Misalnya, di 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
2018-02-27 16:46