HTMLCollections 可以用 for...of (Symbol.iterator) 迭代吗?

Can HTMLCollections be iterated with for...of (Symbol.iterator)?

DOM4 使 NodeLists 可迭代:

interface <i>NodeList</i> {
  getter <a href="https://www.w3.org/TR/dom/#node" rel="noreferrer">Node</a>? <a href="https://www.w3.org/TR/dom/#dom-nodelist-item" rel="noreferrer">item</a>(unsigned long <i>index</i>);
  readonly attribute unsigned long <a href="https://www.w3.org/TR/dom/#dom-nodelist-length" rel="noreferrer">length</a>;
  iterable<<a href="https://www.w3.org/TR/dom/#node" rel="noreferrer">Node</a>>;
};

根据WebIDL,这意味着

Objects implementing an interface that is declared to be iterable support being iterated over to obtain a sequence of values.

Note: In the ECMAScript language binding, an interface that is iterable will have “entries”, “forEach”, “keys”, “values” and @@iterator properties on its interface prototype object.

所以以下是可能的:

for (var el of document.querySelectorAll(selector)) ...

我注意到 HTMLCollections 似乎同样适用于 Firefox 和 Chrome:

for (var el of document.getElementsByTagName(tag)) ...

其实我明白了

HTMLCollection.prototype[Symbol.iterator] === [][Symbol.iterator]; // true

但是,HTMLCollection 未定义为可迭代的:

interface <i>HTMLCollection</i> {
  readonly attribute unsigned long <a href="https://www.w3.org/TR/dom/#dom-htmlcollection-length" rel="noreferrer">length</a>;
  getter <a href="https://www.w3.org/TR/dom/#element" rel="noreferrer">Element</a>? <a href="https://www.w3.org/TR/dom/#dom-htmlcollection-item" rel="noreferrer">item</a>(unsigned long <i>index</i>);
  getter <a href="https://www.w3.org/TR/dom/#element" rel="noreferrer">Element</a>? <a href="https://www.w3.org/TR/dom/#dom-htmlcollection-nameditem" rel="noreferrer">namedItem</a>(DOMString <i>name</i>);
};

我也检查了 WHATWG DOM spec 并且它也不是可迭代的。

那么,这种行为规范与否? HTMLCollection 应该在原型中有一个 @@iterator 吗?

在 JavaScript 中,几乎所有具有 iterable 结构的东西都可以通过一些迭代引擎操作,例如:for..of...spread

任何东西都是可迭代的,如果它 returns 一个针对它存储 @@iterator 符号的 属性 操作的迭代器 [[Get]] 在这种情况下显然 HTMLCollection returns这样的对象。

如果一个迭代器本身有一个:next() {method}:,以及其他两个可选方法

return() {method}: stops iterator and returns IteratorResult
throw() {method}: signals error and returns IteratorResult

这是可迭代的完整自定义对象的示例。

    const calculations = {
      counting: 1,
      [Symbol.iterator]: function(){ return this; },
      next: function(){

        if(this.counting <= 3){

          return {
            value: this.counting++,
            done: false
          }
        }

        return { value: this.counting, done: true}
      }
    };

const data = ...calculations; // [1,2,3]

所以在你的情况下,只要 HTMLCollection returns 一个合适的迭代器你就可以应用 for..of...spread,但是如果你担心这是否符合规格比我必须告诉你我还没有计算机科学学士学位:P

我找到了,在WebIDL中有解释:

If the interface has any of the following:

then a property must exist whose name is the @@iterator symbol, with attributes { [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true } and whose value is a function object. [...]

If the interface defines an indexed property getter, then the Function object is %ArrayProto_values%.

在这种情况下,HTMLCollections 有一个索引 属性 getter:

getter <a href="https://www.w3.org/TR/dom/#element" rel="nofollow noreferrer">Element</a>? <a href="https://www.w3.org/TR/dom/#dom-htmlcollection-item" rel="nofollow noreferrer">item</a>(unsigned long <i>index</i>);

和一个名为“length”的 integer-typed 属性:

readonly attribute unsigned long <a href="https://www.w3.org/TR/dom/#dom-htmlcollection-length" rel="nofollow noreferrer">length</a>;

因此,是的,它应该有效。事实上,它也适用于 NodeLists,即使它们没有声明为可迭代的,但它们不会有 entriesforEachkeysvalues特性。正如@lonesomeday 提到的,很可能 HTMLCollection 没有被定义为可迭代的,因为添加这些方法不会是 backwards-compatible,因为 namedItem getter 接受任意字符串。