HTMLCollection は Iterable なのか？

## TL; DR

DOM API 的には `HTMLCollection` は Iterable となっていないが、Web IDL 的には Iterable になっている。

Chrome, Firefox では Iterable として実装されており、Safari は 11 から Iterable になっている。また core-js(babel-polyfill) でも Iterable になる。
一方で MS Edge 40 では `HTMLCollection` は Iterable になっていない。

闇。

## イントロダクション

JavaScript の言語仕様である ECMAScript ではオブジェクトの分類として Iterable と ArrayLike があります。
いずれも `Array.from` で `Array` に変換することが出来ます。

### Iterable について

Iterable は `Symbol.iterator` メソッドを実行すると Iterator を返すオブジェクトのことをいいます。例えば以下のようなオブジェクトは Iterable です。

```js
{
  [Symbol.iterator]() {
    const values = [1, 12, 123];
    let index = 0;
    return {
      next() {
        const value = values[index++];
        return { value, done: value === undefined };
      }
    }
  }
}

// Generator Functions を使うともっと簡単に書ける
{
  * [Symbol.iterator]() {
    yield* [1, 12, 123];
  }
}
```

[JavaScript の イテレータ を極める！](https://qiita.com/kura07/items/cf168a7ea20e8c2554c6) がとてもわかりやすい記事なので、詳しくはそちらを御覧ください。

Iterable は `for...of` や Spread Syntax によって展開することが出来ます。

### ArrayLike について

ここでは ArrayLike の定義として length を持つオブジェクトを ArrayLike と呼ぶことにします[^1]。例えば以下のようなオブジェクトは ArrayLike です。

```js
{
  length: 10
}

// 値を持つ場合
{
  "0": 1,
  "1": 12,
  "2": 123,
  length: 3
}
```

[^1]: 厳密に考え始めると [ToLength](https://www.ecma-international.org/ecma-262/8.0/index.html#sec-tolength) に length を渡すことで、返り値を取得できるオブジェクトが ArrayLike なオブジェクトとみなすことが出来ますが、この場合だと length プロパティを持っていなくても `0` を返してしまったり、length プロパティに `Symbol` を持っている場合は `TypeError` を投げることからややこしくなるためこのような定義としました。

### ECMAScript における Iterable, ArrayLike

`Array` は Iterable と ArrayLike の特徴を両方とも持っているので、Iterable かつ ArrayLike とみなすことが出来ます。

```js
const array = [1, 12, 123];

// Iterable の特徴を持つ
typeof array[Symbol.iterator] === "function"; // true

// ArrayLike の特徴を持つ
array.length !== undefined; // true
```

一方で `Array#keys`, `Array#values`, `Array#entries` が返す `ArrayIterator` は Itarable ではあるものの ArrayLike ではありません。

```js
const arrayIterator = [1, 12, 123].values();

// Iterable の特徴を持つ
typeof arrayIterator[Symbol.iterator] === "function"; // true

// ArrayLike の特徴を持たない
arrayIterator.length !== undefined; // false
```

## DOM API における Iterable, ArrayLike

DOM API では仕様策定の手続きに時間がかかることによってか ArrayLike であるものの Iterable ではないオブジェクトが大量に取り残されています。

`NodeList` については WHATWG と W3C の両方で Iterable ということになっています。

```webidl
interface NodeList {
  getter Node? item(unsigned long index);
  readonly attribute unsigned long length;
  iterable<Node>;
};
```

（https://dom.spec.whatwg.org/#interface-nodelist より引用[^2]）

一方で `HTMLCollection` は Iterable ではありません。

```webidl
interface HTMLCollection {
  readonly attribute unsigned long length;
  getter Element? item(unsigned long index);
  getter Element? namedItem(DOMString name);
};
```


（https://dom.spec.whatwg.org/#interface-htmlcollection より引用[^2]）

これらについては babel-polyfill で使われている core-js でも区別されています。

> ```js
> var DOMIterables = {
>   CSSRuleList: true, // TODO: Not spec compliant, should be false.
>   CSSStyleDeclaration: false,
>   CSSValueList: false,
>   ClientRectList: false,
>   DOMRectList: false,
>   DOMStringList: false,
>   DOMTokenList: true,
>   DataTransferItemList: false,
>   FileList: false,
>   HTMLAllCollection: false,
>   HTMLCollection: false,
>   HTMLFormElement: false,
>   HTMLSelectElement: false,
>   MediaList: true, // TODO: Not spec compliant, should be false.
>   MimeTypeArray: false,
>   NamedNodeMap: false,
>   NodeList: true,
>   PaintRequestList: false,
>   Plugin: false,
>   PluginArray: false,
>   SVGLengthList: false,
>   SVGNumberList: false,
>   SVGPathSegList: false,
>   SVGPointList: false,
>   SVGStringList: false,
>   SVGTransformList: false,
>   SourceBufferList: false,
>   StyleSheetList: true, // TODO: Not spec compliant, should be false.
>   TextTrackCueList: false,
>   TextTrackList: false,
>   TouchList: false
> };
> ```
（https://github.com/zloirock/core-js/blob/0f1c2bffe6e2e26b441908999b8b7469d1ab7a9c/modules/web.dom.iterable.js#L12-L44 より引用）

[^2]: Commit Snapshot: https://dom.spec.whatwg.org/commit-snapshots/fb6638fa3d02985e43782d8857edaa915d499261/

## Web IDL における Iterable, ArrayLike

DOM API では特に Iterable の記載がされていない `HTMLCollection` でしたが、Web IDL の `%Symbol.iterator%` の項目を見ると以下のように記されています。

> If the [interface](https://heycam.github.io/webidl/#dfn-interface) has any of the following: 
>  * an [iterable declaration](https://heycam.github.io/webidl/#dfn-iterable-declaration)
>  * an [indexed property getter](https://heycam.github.io/webidl/#dfn-indexed-property-getter) and an [integer-typed](https://heycam.github.io/webidl/#dfn-integer-type) [attribute](https://heycam.github.io/webidl/#dfn-attribute) named “length”
>  * a [maplike declaration](https://heycam.github.io/webidl/#dfn-maplike-declaration)
>  * a [setlike declaration](https://heycam.github.io/webidl/#dfn-setlike-declaration)
> then a property must exist whose name is the [`%Symbol.iterator%`](https://tc39.github.io/ecma262/#sec-well-known-symbols) symbol, with attributes `{ [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }` and whose value is a [function object](https://tc39.github.io/ecma262/#function-object).

（https://webidl.spec.whatwg.org/#es-iterator より引用）

これによって **ArrayLike であって indexed property によって値を返すオブジェクトはすべて Iterable として実装しなければならない**ことになっています。

前述した通り core-js(babel-polyfill) で区別されており、`keys`/`values`/`entries` メソッドは持たないようになっているものの `Symbol.iterator` メソッドは持つように実装されています[^3]。

[^3]: https://github.com/zloirock/core-js/#iterable-dom-collections

## ブラウザにおける実装

Chrome, Firefox では Web IDL の仕様に踏襲して `HTMLCollection` が Iterable として実装されています。
また Safari は最新の Safari 11 から Iterable となっています。

一方で Edge 40 では Iterable になっていないようです。

## 思ったこと

Web IDL の indexed property によって Iterable にしてしまうのはいいと思います。
`FileList` みたいに Iterable であって然るべきものがなかなか Iterable にならないというのは辛いものがあります。

~~DOM API 側で特に Iterable という記載がしていないのに Web IDL 側で上書きして Iterable っていうことにするのは困惑するからやめろ。~~

しかし `HTMLCollection` は interface を見るとわかりますが indexed property の他にも named property を持っています。この named property を無視して Iterable にしてしまうのはいいんでしょうか。まあこの named property はもう殆ど使われていない感じではありますが。

Iterable にする便利さはわかりますが、なんか後方互換のためにまた仕様が辛いことになってるなぁという印象ですね。