简体   繁体   English

如何将 HTML 元素数组转换为 NodeList?

[英]How to turn an array of HTML elements into a NodeList?

We're trying to replace document.querySelectorAll() with our own function, and we don't want to have to check all the current uses and maybe have to refactor those.我们正在尝试用我们自己的 function 替换document.querySelectorAll() ,并且我们不想检查所有当前的使用,并且可能不得不重构它们。 We're trying to return a NodeList, but that seems to be impossible because there's no apparent way to create one.我们试图返回一个 NodeList,但这似乎是不可能的,因为没有明显的方法来创建它。

So we're trying to return an array of HTML elements making it look like it's a NodeList.所以我们试图返回一个 HTML 元素的数组,使它看起来像一个 NodeList。 It was relatively easy to replicate NodeList's interface, but the problem is: how to overload the brackets ([]) operator?复制 NodeList 的接口相对容易,但问题是:如何重载方括号 ([]) 运算符? Apparently it's impossible to do in JavaScript. 显然在 JavaScript 中是不可能的。

Since NodeList objects are snapshots (they don't track the contents of the DOM the way an HTMLCollection does), the contents of the NodeList are static, which makes supporting [] indexing easy: just assign to the indexes.由于NodeList对象是快照(它们不像HTMLCollection那样跟踪 DOM 的内容), NodeList的内容是 static,这使得支持[]索引变得容易:只需分配给索引。 Arrays are just objects with a couple of additional behaviors (a dynamic length property, adjusting length when you assign via [] , and of course Array.prototype ). Arrays 只是具有一些附加行为的对象(动态length属性,通过[]分配时调整length ,当然还有Array.prototype )。 The same thing that makes array[0] work is what makes anyObject["property name"] work.使array[0]工作的同一件事是使anyObject["property name"]工作的原因。

To make something that looks like a NodeList , offhand I think you need to:要制作看起来像NodeList的东西,我认为您需要:

  1. Put NodeList.prototype in its prototype chain so instanceof worksNodeList.prototype放在它的原型链中,这样instanceof就可以工作了
  2. Support item支持item
  3. Support indexing (in this case, just by assigning to those properties)支持索引(在这种情况下,只需分配给这些属性)
  4. Support length as an accessor with a getter and no setter rather than a non-writable data property (in case anyone looks)支持length作为具有 getter 而没有 setter 的访问器,而不是不可写的数据属性(以防万一)

For instance (see comments):例如(见评论):

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

Live Example:现场示例:

 // 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>

I didn't take the approach of subclassing Array (which would certainly be another way to go) because I didn't want to hide all the array methods that NodeList doesn't have.我没有采用继承Array的方法(这肯定是另一种方法),因为我不想隐藏NodeList没有的所有数组方法。

If you wanted to make the [] indexing dynamic (you don't need to for a NodeList stand-in, since again those are static), you could use a Proxy .如果您想使[]索引动态化(您不需要NodeList替身,因为它们也是静态的),您可以使用Proxy

I've found that this code works as expected:我发现这段代码按预期工作:

function arrayToNodeList(initialArray) {
  const tempArray = Array(initialArray);
  initialArray.__proto__ = {
    get length() {
      return tempArray.length;
    },

    item(position) {
      if (position < 0 || position > tempArray.length - 1) {
        return null;
      }
      return tempArray[position];
    },

    entries() {
      return tempArray.entries();
    },

    forEach(callback, thisArg = initialArray) {
      return tempArray.forEach(callback, thisArg);
    },

    keys() {
      return tempArray.keys();
    },

    values() {
      return tempArray.values();
    },
  };

  return initialArray;
}

It might be a little bit worse, performance wise, but it solves the brackets operator problem and hides the rest of the API for Array objects.在性能方面可能会更糟,但它解决了括号运算符问题并隐藏了 API 的 rest 用于 Array 对象。 The main difference is that console.log() shows that it's not really a NodeList, but otherwise it just works.主要区别在于console.log() 表明它不是真正的NodeList,但除此之外它就可以工作。

NOTE : the tempArray is needed because the prototype of the initialArray is being modified.注意:需要tempArray ,因为正在修改initialArray的原型。 If I rely on initialArray to call functions I get an Uncaught RangeError: Maximum call stack size exceeded because it's recursively calling its own functions.如果我依靠 initialArray 来调用函数,我会得到一个Uncaught RangeError: Maximum call stack size exceeded因为它递归地调用自己的函数。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM