首页 > 解决方案 > 如何将 HTML 元素数组转换为 NodeList?

问题描述

我们正在尝试document.querySelectorAll()用我们自己的函数替换,我们不想检查所有当前的使用,也许不得不重构这些。我们试图返回一个 NodeList,但这似乎是不可能的,因为没有明显的方法来创建它。

所以我们试图返回一个 HTML 元素数组,使它看起来像一个 NodeList。复制 NodeList 的接口相对容易,但问题是:如何重载方括号 ([]) 运算符? 显然在 JavaScript 中是不可能的。

标签: javascriptarrays

解决方案


由于NodeList对象是快照(它们不会像 anHTMLCollection那样跟踪 DOM 的内容),其内容NodeList是静态的,这使得支持[]索引变得容易:只需分配给索引。数组只是具有几个附加行为的对象(一个动态length属性,length在您分配时进行调整 via [],当然还有Array.prototype)。创造工作的东西就是创造array[0]工作的东西anyObject["property name"]

要制作看起来像 的东西,NodeList我认为您需要:

  1. 放入NodeList.prototype它的原型链,这样就instanceof可以了
  2. 支持item
  3. 支持索引(在这种情况下,只需分配给这些属性)
  4. 支持length作为具有 getter 而没有 setter 的访问器,而不是不可写的数据属性(以防万一)

例如(见评论):

// Constructor
function PseudoNodeList(arrayLike) {
    const length = arrayLike.length;
    // Define `length` -- slight difference with `NodeList` here, this is
    // defined on the object itself, but `NodeList` has it on the prototype
    Object.defineProperty(this, "length", {
        get() {
            return length;
        },
        enumerable: true, // Oddly, it is on `NodeList.prototype`
        configurable: true,
    });
    // Copy the indexed entries
    Object.assign(this, Array.from(arrayLike));
    // (Instead of the statement above, you could use a `for` loop, which
    // would likely be faster -- you did mention performance)
}
// Make `instanceof` work, and inherit the implementations of
// [Symbol.iterator] and other methods -- though you'll want to test that
// Safari and Firefox are okay with inheriting them, I only checked on
// Chromium-based browsers (Chromium, Chrome, Brave, Edge, Opera I think).
// They may be more picky about `this`.
PseudoNodeList.prototype = Object.create(NodeList.prototype);
// Fix `constructor` property
PseudoNodeList.prototype.constructor = PseudoNodeList;
// Add item method
Object.defineProperty(PseudoNodeList.prototype, "item", {
    value(index) {
        if (index < 0 || index >= this.length) {
            return null;
        }
        return this[index];
    },
    enumerable: true, // Oddly, it is on `NodeList.prototype`
    configurable: true,
});

现场示例:

// Constructor
function PseudoNodeList(arrayLike) {
    const length = arrayLike.length;
    // Define `length` -- slight difference with `NodeList` here, this is
    // defined on the object itself, but `NodeList` has it on the prototype
    Object.defineProperty(this, "length", {
        get() {
            return length;
        },
        enumerable: true, // Oddly, it is on `NodeList.prototype`
        configurable: true,
    });
    // Copy the indexed entries
    Object.assign(this, Array.from(arrayLike));
    // (Instead of the statement above, you could use a `for` loop, which
    // would likely be faster -- you did mention performance)
}
// Make `instanceof` work, and inherit the implementations of
// [Symbol.iterator] and other methods -- though you'll want to test that
// Safari and Firefox are okay with inheriting them, I only checked on
// Chromium-based browsers (Chromium, Chrome, Brave, Edge, Opera I think).
// They may be more picky about `this`.
PseudoNodeList.prototype = Object.create(NodeList.prototype);
// Fix `constructor` property
PseudoNodeList.prototype.constructor = PseudoNodeList;
// Add item method
Object.defineProperty(PseudoNodeList.prototype, "item", {
    value(index) {
        if (index < 0 || index >= this.length) {
            return null;
        }
        return this[index];
    },
    enumerable: true, // Oddly, it is on `NodeList.prototype`
    configurable: true,
});

// ======= Using it:

const p = new PseudoNodeList(
    document.querySelectorAll(".example")
);
console.log(`p instanceof NodeList? ${p instanceof NodeList}`);
console.log(`p.length = ${p.length}`);
console.log(`p.keys():`, [...p.keys()]);
console.log(`p.values():`, [...p.values()]);
console.log(`p.entries():`, [...p.entries()]);

// Turn all of them green via iteration
for (const el of p) {
    el.style.color = "green";
}
// Use `item` method to make the first match `font-size: 20px`
p.item(0).style.fontSize = "20px";
// Use indexed property to make the first match `font-style: italic`
p[1].style.fontStyle = "italic";
<div>one</div>
<div class="example">two</div>
<div>one</div>
<div class="example">three</div>

我没有采用子类化的方法Array(这肯定是另一种方法),因为我不想隐藏所有没有的数组方法NodeList

如果您想使[]索引动态化(您不需要NodeList替身,因为它们也是静态的),您可以使用Proxy.


推荐阅读