23
17

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

HTMLCollection は Iterable なのか?

Last updated at Posted at 2017-11-12

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.fromArray に変換することが出来ます。

Iterable について

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

{
  [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 の イテレータ を極める! がとてもわかりやすい記事なので、詳しくはそちらを御覧ください。

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

ArrayLike について

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

{
  length: 10
}

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

ECMAScript における Iterable, ArrayLike

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

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 ではありません。

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 ということになっています。

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 ではありません。

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 でも区別されています。

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 より引用)

Web IDL における Iterable, ArrayLike

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

If the interface has any of the following:

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

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

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

ブラウザにおける実装

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

  1. 厳密に考え始めると ToLength に length を渡すことで、返り値を取得できるオブジェクトが ArrayLike なオブジェクトとみなすことが出来ますが、この場合だと length プロパティを持っていなくても 0 を返してしまったり、length プロパティに Symbol を持っている場合は TypeError を投げることからややこしくなるためこのような定義としました。

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

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

23
17
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
23
17

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?