※スマホ対応はしてません。

タグ: Iterator

反復処理の中身、IterableとIteratorについて仕様書を調べてみた。(配列とかおれおれAdvent Calendar2018 – 19日目)

カテゴリー: JavaScript

LINDORのAdvent Calendar(本物)の19日目を開けたところ。
配列とかおれおれAdvent Calendar2018 – 19日目

ここまで何度か出てきた反復可能 (iterable) と反復子 (iterator) のお話です。やっと。

先にまとめ

  • Iterableオブジェクトは
    • Iteratorを返すメソッド [Symbol.iterator] を持つ
    • for-of で使える
  • Iteratorオブジェクトは
    • IteratorResultを返すメソッド next() を持つ
  • IteratorResultオブジェクトは
    • プロパティ done, value を持つ
  • 配列は
    • Iterableである
    • Iteratorではない
  • arr.values() は
    • Iterableである
    • Iteratorである
  • Generatorオブジェクトは
    • Iterableであり、かつIteratorである
  • Generator関数は
    • function*(){} のやつ
    • Generatorオブジェクトを返す

自作例

// Iteratorはnext()を持つ
class MyIterator {
  constructor () {
    this.index = 0;
    this.values = [11, 22, 33];
  }

  next () {
    const value = this.values[this.index];
    this.index += 1;
    const result = { done: !value, value };
    return result;
  }
}

// Iterableは[Symbol.iterator]()を持つ
class MyIterable {
  [Symbol.iterator] () {
    return new MyIterator();
  }
}

const iterable = new MyIterable();
for (const value of iterable) {
  console.log(value);
}
// 11
// 22
// 33

反復 (iteration)

IterableとIteratorは、実在するコンストラクターではなく概念的な「インターフェイス」として定義されています。仕様だけが示されて、それを満たせば「これはIterableである」とかなんとか言って良いということです。

仕様書のIterationの章は、インターフェイスについての説明から始まります。

An interface is a set of property keys whose associated values match a specific specification. Any object that provides all the properties as described by an interface’s specification conforms to that interface. An interface is not represented by a distinct object. There may be many separately implemented objects that conform to any interface. An individual object may conform to multiple interfaces.

  • インターフェイスとは、関連する値が特定の仕様に合致するプロパティキーの組み合わせである。
  • インターフェイスの仕様で述べられるすべてのプロパティを提供するあらゆるオブジェクトは、そのインターフェイスに準拠している。
  • インターフェイスは個別のオブジェクトとしては存在しない。
  • あるインターフェイスに準拠する、別々に実装されている複数のオブジェクトがあってもよい。
  • 個々のオブジェクトが複数のインターフェイスに準拠してもよい。

(※訳注: 勝手に1文ごとに箇条書きへ変換しました。内容はそのまま。)

ちなみに isIterable() や isIterator() のようなものは仕様にはありません。まあメソッド実行して戻り値が正しいことまで確認しないといけないので、JavaScriptで綺麗につくるのはちょっと難しいっすかね。

定義されているインターフェイス

5種あります。

  • 反復可能 Iterable
  • 反復子 Iterator
  • 非同期反復可能 AsyncIterable
  • 非同期反復子 AsyncIterator
  • 反復結果 IteratorResult

反復可能 (Iterable)

[Symbol.iterator]() メソッドが反復子を返すもの。

for-of で使えるやつ。配列とか、 Set とか、配列とか、文字列とか、あと配列とか。

仕様が短い。

Property Value Requirements
@@iterator A function that returns an Iterator object. The returned object must conform to the Iterator interface.

プロパティ 値 要求
@@iterator Iterator オブジェクトを返す関数。 返却されるオブジェクトは Iterator インターフェイスに準拠しなければならない。

[Symbol.iterator]() メソッド

仕様書では @@iterator という形で表現される名前のメソッドです。特別なものなので、名前が文字列ではなくシンボルになっています。知らんけど。

この [Symbol.iterator]() という名前でIteratorを返すオブジェクトをIterableと呼べるという話だけど、それ以外のメソッドがIteratorを返すのは自由です。例えば配列はこの [Symbol.iterator]() メソッドを持ちますが、他にも values() というIteratorを返すメソッドも持っています。

ちなみに arr[Symbol.iterator] === arr.values です。

実装例

Iteratorが何かは次項に譲りつつ、それを返す [Symbol.iterator]() メソッドを持つオブジェクト(のクラス)です。

class MyIterable {
  [Symbol.iterator] () {
    return new MyIterator();
  }
}

反復子 (Iterator)

next() メソッドを持ち、それを使って反復できるもの。

Object.values() とか。for-of を for に分解したときに出てくるやつ。

Property Value Requirements
next A function that returns an IteratorResult object. The returned object must conform to the IteratorResult interface. If a previous call to the next method of an Iterator has returned an IteratorResult object whose done property is true, then all subsequent calls to the next method of that object should also return an IteratorResult object whose done property is true. However, this requirement is not enforced.

Note 1

Arguments may be passed to the next function but their interpretation and validity is dependent upon the target Iterator. The for-of statement and other common users of Iterators do not pass any arguments, so Iterator objects that expect to be used in such a manner must be prepared to deal with being called with no arguments.

プロパティ 値 要求
next IteratorResult オブジェクトを返す関数。 返却されるオブジェクトは IteratorResult インターフェイスに準拠しなければならない。もし Iterator の前回の next メソッド呼び出しが返した IteratorResult の done プロパティが true である場合、以降の next メソッド呼び出しが返す IteratorResult オブジェクトも done プロパティが true になるだろう (should) 。ただし、この要求は強制されない。

ノート1

引数を next 関数へ与えても良いが、それらの解釈や妥当性は対象 Iterator に依る。 for-of 構文やその他の一般的な Iterator の利用者は引数を何も与えないため、そのような作法での利用が想定される Iterator オブジェクトは、引数なしで呼ばれた場合も対処するようになっていなければならない。

実装例

内包する値が固定だけど。

class MyIterator {
  constructor () {
    this.index = 0;
    this.values = [11, 22, 33];
  }

  next () {
    const value = this.values[this.index];
    this.index += 1;
    const result = { done: !value, value };
    return result;
  }
}

任意のプロパティ

next() は必須だけど、他に return() と throw() を実装しても良いそうです。

Property Value Requirements
return A function that returns an IteratorResult object. The returned object must conform to the IteratorResult interface. Invoking this method notifies the Iterator object that the caller does not intend to make any more next method calls to the Iterator. The returned IteratorResult object will typically have a done property whose value is true, and a value property with the value passed as the argument of the return method. However, this requirement is not enforced.
throw A function that returns an IteratorResult object. The returned object must conform to the IteratorResult interface. Invoking this method notifies the Iterator object that the caller has detected an error condition. The argument may be used to identify the error condition and typically will be an exception object. A typical response is to throw the value passed as the argument. If the method does not throw, the returned IteratorResult object will typically have a done property whose value is true.

Note 2

Typically callers of these methods should check for their existence before invoking them. Certain ECMAScript language features including for-of, yield*, and array destructuring call these methods after performing an existence check. Most ECMAScript library functions that accept Iterable objects as arguments also conditionally call them.

プロパティ 値 要求
return IteratorResult オブジェクトを返す関数。 返却されるオブジェクトは IteratorResult インターフェイスに準拠しなければならない。このメソッドの呼び出しは Iterator オブジェクトへ、呼び出し側がこれ以上 next メソッド呼び出しを行う意図がないことを通知する。返却される IteratorResult の done プロパティは、一般に true になり、 value プロパティは return メソッドの引数に与えられた値となる。ただし、この要求は強制されない。
throw IteratorResult オブジェクトを返す関数。 返却されるオブジェクトは IteratorResult インターフェイスに準拠しなければならない。このメソッドの呼び出しは Iterator オブジェクトへ、呼び出し者がエラー状態を検出したことを通知する。引数はエラー状態特定のために使ってもよく、また多くの場合は例外オブジェクトになる。代表的な目的は引数として渡された値を throw することである。このメソッドが throw しない場合、返却される IteratorResult の done プロパティは、一般に true になる。

ノート2

一般的にこれらのメソッドの呼び出し側は、その存在を確認してから呼び出すことになるだろう (should) 。 for-of や yield* 、配列の分解 (destructuring) といった、信頼できるECMAScript言語の機能は存在確認を実行した後にこれらのメソッドを呼び出している。 Iterable オブジェクトを受け付けるほとんどのECMAScriptライブラリー関数もまた、条件次第でこれらを呼ぶようになっている。

反復可能な反復子

ECMAScriptでIteratorとして記述されているものは、いずれも %IteratorPrototype% という秘密のプロトタイプを継承するようになっています。

このプロトタイプは単に this を返すだけの [Symbol.iterator]() を持つとされます。

つまり標準のIteratorは反復可能であり、Iteratorが作るIteratorは自身である (it === it[Symbol.iterator]()) というわけですね。

と、そうわけで配列はもちろんのこと、配列から明示的に生成したIteratorもまた for-of で使えるのです。

const arr = [11, 22, 33];

for (const value of arr) {
  console.log('arr', value);
}
// arr 11
// arr 22
// arr 33

const it = arr.values();
for (const value of it) {
  console.log('it', value);
}
// it 11
// it 22
// it 33

非同期の反復可能、反復子

for-await-of で使えるやつ。

基本的に同期のものと同じっぽい。

違うのは、 next() が返すものが「 IteratorResult オブジェクト」から「 IteratorResult オブジェクトのプロミス」になってる点。

反復結果 (IteratorResult)

いろいろ書いてあるけど、持ってるのは値だけなんで、実際そんなにややこしくはないね。

Property Value Requirements
done Either true or false. This is the result status of an iterator next method call. If the end of the iterator was reached done is true. If the end was not reached done is false and a value is available. If a done property (either own or inherited) does not exist, it is consider to have the value false.
value Any ECMAScript language value. If done is false, this is the current iteration element value. If done is true, this is the return value of the iterator, if it supplied one. If the iterator does not have a return value, value is undefined. In that case, the value property may be absent from the conforming object if it does not inherit an explicit value property.

プロパティ 値 要求
done true か false 。 これは iterator の next メソッド呼び出しの結果状態である。iteratorが最後まで到達していれば done は true になる。iteratorが最後まで到達していなければ done は false になり、 value は有効になる。 done プロパティ(自身のものでも継承したものでも)が存在しない場合は false 値を持つものとみなす。
value ECMAScript言語の任意の値。 done が false の場合、これは現在の反復要素の値である。 done が true の場合、これは iterator の戻り値である。(あれば。) iterator が戻り値を持たない場合、 value は undefined になる。その場合、確認中のオブジェクトが明確な value プロパティを継承していなければ value プロパティはそのオブジェクトから欠落してもよい (may) 。

ジェネレーター

反復子でありながら反復可能であるもの

A Generator object is an instance of a generator function and conforms to both the Iterator and Iterable interfaces.

Generator instances directly inherit properties from the object that is the value of the prototype property of the Generator function that created the instance. Generator instances indirectly inherit properties from the Generator Prototype intrinsic, %GeneratorPrototype%.

Generatorオブジェクトはgenerator関数のインスタンスであり、 Iterator と Iterable インターフェイスの両方に準拠するものである。

Generatorインスタンスはそのインスタンスを生成したGenerator関数の prototype プロパティの値であるオブジェクトから直接的にプロパティを継承する。Generatorインスタンスは固有のGeneratorプロトタイプ、%GeneratorPrototype%から非直接的にプロパティを継承する。

Generator関数 (function*(){}) の正体がわかりましたね!

function* f () {
  yield 1;
}
f.prototype.sayHello = () => console.log('Hello!');

const it = f();
it.sayHello();
// Hello!

for-of とIterable

GetIterator() という内部処理があって、こいつで of 右側のオブジェクトから例の [Symbol.iterator]() を通してIteratorを取得してます。取得できなければ TypeError 。

第3引数に任意の method を渡せるようになってるけど、仕様書見た感じ全部 @@iterator か @@asyncIterator みたい。

その他

Object.values() vs arr.values()

Object.values(obj) はただの配列を返して、その中身が obj のプロパティ値になります。

arr.values() は arr の項目を順に返すIteratorを返します。

おまけ: 英単語 iteration vs repetition

反復と繰り返し、どちらも同じような意味だけど、どう違うんだろうか。

これの回答がわかりやすい。(合ってるのかどうかは判断しかねるが。)

Iteration uses an iterator to count through a list of (usually different) items:

Today, I must:
1: go shopping
2: clean the house
3: mow the lawn

Repetition does the same thing again, and again, etc…:

One sheep, two sheep, three sheep, …

漫画の単行本を続けて読むのが iterate で、同じ本を何度も読むのが repeat てな感じみたい。

おしまい

ほとんどただの翻訳になってしまった。

まあ仕様書が一番わかりやすいから仕方ない。

関連

参考

ジェネレータと自作イテレータで各種オブジェクトもぐーるぐる。(現代的JavaScriptおれおれアドベントカレンダー2017 – 23日目)

カテゴリー: JavaScript

現代的JavaScriptおれおれアドベントカレンダー2017 – 23日目

概要

function* で、イテレータを返すジェネレータ関数を作成できます。中で yield が使えます。

function* createIdGenerator() {
  let currentId = 100;
  while (true) {
    yield currentId++;
  }
}

const iterator = createIdGenerator();
console.log(iterator.next().value);
console.log(iterator.next().value);
console.log(iterator.next().value);

これはオブジェクトを反復可能(イテラブル)にするのに便利です。

const obj = {
  *[Symbol.iterator]() {
    const max = 3;
    for (let i = 0; i < max; i++) {
      yield i;
    }
  }
};

for (let item of obj) {
  console.log(item);
}

イテレータ

著名なデザインパターンのひとつです。配列でないオブジェクトでも、共通のインターフェイスで反復処理を行えるようにします。

JavaScript (ES2015+) では(たぶん一般的な定義とは異なり)イテレータは next() メソッドを持つオブジェクトです。具体的なインターフェイスは後述します。

JavaScriptでのイテレータ

イテラブルなオブジェクトで [Symbol.iterator]() というすごい名前のメソッドを実行すると、イテレータを得られます。

const iterable = new Set([100, 200, 300]);

// イテレータ生成
const it = iterable[Symbol.iterator]();

while (true) {
  // 反復処理を進め、現段階の状態を得る
  const result = it.next();

  // 繰り返し条件を確認
  if (result.done) {
    break;
  }
  
  // 項目取得
  const item = result.value;

  console.log(item);
}

Set オブジェクトはイテラブルなオブジェクトなので、 [Symbol.iterator]() を実行するとイテレータ it を生成して返してくれます。

そのイテレータ it は next() メソッドを持ちます。このメソッド呼び出しを繰り返して反復 (iterate) していきます。

next() メソッドは実行するたびに、なんだろ、結果オブジェクト? result を生成して返します。結果オブジェクトって呼び方で良いかな。 IteratorResult という名前のインターフェイス(後述)を満たすオブジェクトです。

この結果オブジェクトはまた、二つのプロパティを持ちます。 value が反復途中の現段階の値で、 done がもう反復し終えたかが格納される真偽値です。

これらを使って配列でも何でもぐるぐるできます。

for-of で使う

オブジェクトがイテラブルである、つまり前述の各要素を満たす場合、 for-of を使って簡単に反復処理を実現できます。

const iterable = new Set([100, 200, 300]);

for (let item of iterable) {
  console.log(item);
}

わあ短い。

ジェネレータで生成する

[Symbol.iterator]() をちゃんと自作すればどんなオブジェクトでもイテラブルにできます。

next() メソッドを自前で実装しても良いんだけど、面倒なので、ジェネレータ関数というのを使って作ると簡単です。

ジェネレータ

function* を使ってジェネレータ関数を宣言できます。ジェネレータ関数を実行するとジェネレータオブジェクトを得られます。

function* createGenerator() {
  const values = [100, 200, 300];

  for (let i = 0; i < values.length; i++) {
    const item = values[i];
    yield item;
  }
}

const generator = createGenerator();

ジェネレータオブジェクトは加えてイテラブルでもあるで for-of で使えます。わーい。

for (let item of generator) {
  console.log(item);
}

generator[Symbol.iterator]() の結果は元のオブジェクト generator になります。

また同時にイテレータでもあるので next() を持ちます。コードは書かなくても良いかね。

yield

さらっと使ったけど、ジェネレータ関数の中では yield という式を使えます。この式は右辺を value 、また done: false という設定で、 next() で返す IteratorResult オブジェクトを生成します。

必ずしも for 文で使う必要はないです。

function* oneTwoThree() {
  yield 1;
  yield 2;
  yield 3;
}

const it = oneTwoThree();

let result;
result = it.next();
console.log(result.value);  // => 1
result = it.next();
console.log(result.value);  // => 2
result = it.next();
console.log(result.value);  // => 3

[Symbol.iterator]() を実装する

いよいよ [Symbol.iterator]() です。 * を付けて、ジェネレータ関数というかジェネレータメソッドになります。

const obj = {
  *[Symbol.iterator]() {
    const max = 3;
    for (let i = 0; i < max; i++) {
      yield i;
    }
  }
};

for (let item of obj) {
  console.log(item);
}

他のイテレータ生成メソッドを作る

obj じゃなくて obj.iterator() のようにメソッドを呼んでやる必要があるけれど、他の名前でも大丈夫。

目的別に何通りか用意しても良いかもね。

const obj = {
  *iterator() {
    const max = 3;
    for (let i = 0; i < max; i++) {
      yield i;
    }
  }
};

for (let item of obj.iterator()) {
  console.log(item);
}

イテレータのインターフェイス

いっぱい出てきたのでまとめ。

  • 反復可能(イテラブル)なオブジェクト … [Symbol.iterator]() をもつオブジェクト( Iterable )
  • [Symbol.iterator]() … イテレータを返すメソッド
  • イテレータ … next() をもつオブジェクト( Iterator )
  • next()value 、 done を持つオブジェクト( IteratorResult )を返し、内部状態を進めるメソッド
  • value … 反復処理各段階における値
  • done … 反復処理が終了しているかどうか

[Symbol.iterator]()

Symbol.iterator というシンボルが、名前というか何だろ、識別子?になってるメソッドです。 [] は動的にプロパティ名を決めるやつです。別稿参照。あとシンボル Symbol についても。

なんならどこかで Symbol.iterator = 'iterate' とか定義されてると考えてください。

Iterable インターフェイス

オブジェクトがこのインターフェイスを満たしていると、 for-of とか ... とかが使えます。

  • [Symbol.iterator]() … イテレータオブジェクトを返す

Iterator インターフェイス

このインターフェイスを満たすオブジェクトをイテレータオブジェクトと呼びます。

  • next() … イテレータオブジェクトを返す
  • return() (optional) … イテレータオブジェクトを返す
  • throw() (optional) … イテレータオブジェクトを返す

return() を呼び出すと「もう終了するぞ」と、 throw() を呼び出すと「なんかおかしいぞ」を、呼び出し先となるオブジェクトに伝えることが、できる、そう、です。単純に配列みたいな情報を扱う上ではいらなさそうだけど、何か外部から情報を引っ張ってくるようなやつで使うときに便利なんだろか。わかんない。

IteratorResult インターフェイス

next() 他のメソッドはこのインターフェイスを満たすオブジェクトを返す必要があります。

  • done
  • value

分割代入やスプレッド演算子 ...

これら実はイテラブルオブジェクトが対象です。配列じゃなくても自作のオブジェクトでも、 [Symbol.iterator]() を備えていれば使えます。

function* oneTwoThree() {
  yield 1;
  yield 2;
  yield 3;
}

// 分割代入
const [one, two, three] = oneTwoThree();
console.log(one);
console.log(two);
console.log(three);

// スプレッド演算子
function say(one, two, three) {
  console.log(one);
  console.log(two);
  console.log(three);
}
say(...oneTwoThree());

ES2018では普通のオブジェクトも分割代入したりスプレッド演算子で展開したりできるようになるっぽいんだけど、じゃあ普通のオブジェクトも全部イテラブルになるのかな? 仕様策定の様子は追ってないので思っただけだけど。

分割代入と ... の使い方は別稿参照。

その他

[Symbol.iterator]() て何だよ

なんで普通の名前じゃないんだ Symbol なんだ、 toString() みたいにしなかったんだ。

だいたいイテレータはイテラブル

「イテレータ」と「イテラブル」は別インターフェイスなので同時に満たす必要はないんだけど、内部で %IteratorPrototype% というイテレータ共通のプロトタイプがありまして、こいつがイテラブルだったりします。

配列とかのイテレータはこのプロトタイプを継承しているので、JavaScriptネイティブなイテレータはだいたいイテラブルになるっぽい。 TypedArray は専用のものを持たないが、普通の配列 Array の処理を利用している。

ちなみに %IteratorPrototype% の [Symbol.iterator]() は this を返します。

イテラブルではないイテレータ

の例。

class MyIterator {
  constructor(values) {
    this.values = values;
    this.index = 0;
  }

  // 自前で実装するぞ
  next() {
    const value = this.values[this.index];
    this.index += 1;
    return {
      value: value,
      done: this.index >= this.values.length,
    };
  }
};

const values = [100, 200, 300];

// イテレータとして利用
const it = new MyIterator(values);
let result;
result = it.next();
console.log(result.value);

// イテラブルとして利用(できない)
// Exception: TypeError: (new MyIterator(...)) is not iterable
for (let item of new MyIterator(values)) {
  console.log(item);
}

普通のイテレータ

Java方面の話で聞いたときは next() で次に移動しつつ値を戻り値でそのまま得て、続きがあるかどうかは hasNext() という別のメソッドを使うみたいな流れだったと思うんだけど、なんで違うのかしらん。

時代が変わってイテレータパターン自体が変わった?

インターフェイス vs プロトコル

ちなみにMDNだとイテレータとかで「プロトコル (protocol)」という表現が用いられているけれど、仕様書だと「インターフェイス (interface)」です。まあ同じものでしょ。

たしかSwiftはprotocolという表現してたよね。

function と * の間で改行できる

適当に空白文字を置ける様子。

function
* goo() {
}

async はだめだったのに~。

参考

更新履歴

  • 2017/12/23 TypedArray がイテラブルじゃないみたいな勘違いしてたのを修正

イテレータとfor-of文で配列以外もぐーるぐる。(現代的JavaScriptおれおれアドベントカレンダー2017 – 22日目)

カテゴリー: JavaScript

現代的JavaScriptおれおれアドベントカレンダー2017 – 22日目

概要

配列とか Map とかは for-of 文を使って、添え字アクセス? [] を使わずにぐるぐるできます。

const arr = ['Hello', 'World', '!'];

for (let item of arr) {
  console.log(item);
}

普通のオブジェクトでは使えないけど、インターフェイスを追加すれば使えるようにもなります。

使い方

普通の for 文を使って配列で arr[i] する代わりに for-of でいきなり値を取ってこれます。

const arr = ['Hello', 'World', '!'];

for (let item of arr) {
  console.log(item);
}

配列以外でも反復可能なオブジェクト(後述)で使えます。

const map = new Map([[1, 11], [2, 22], [3, 33]]);

for (let [key, value] of map) {
  console.log(`${key}: ${value}`);
}

文字列と絵文字

文字列でも使えます。絵文字とかのサロゲートペアを上手に扱ってくれます。やったね。

const str = '#sushi🍣';

console.log('--- for ---');
for (let i = 0; i < str.length; i++) {
  console.log(str[i]);
}

console.log('--- for-of ---');
for (let item of str) {
  console.log(item);
}

--- for ---
#
s
u
s
h
i
�
�
--- for-of ---
#
s
u
s
h
i
🍣

反復可能なオブジェクト

for-of 文は反復可能なオブジェクトに対して利用することができます。

対応してないオブジェクトだとエラーに。

const obj = {};

for (let item of obj) {  // TypeError: obj[Symbol.iterator] is not a function
    console.log(item);
}

反復可能なオブジェクト

この「反復可能(イテラブル)なオブジェクト (iterable object)」とは、「イテレータ」を作成するインターフェイスを整えたオブジェクトのことです。配列 Array や Map 等が該当します。

インターフェイスが整っていれば何でも良いので、自作することもできます。

// 反復可能なオブジェクト
const iterable = {
  // for-ofに必要なインターフェイス
  [Symbol.iterator]() {
    const max = 10;
    let n = 0;

    const iterator = {
      next() {
        return { value: n++, done: n > max };
      },
    };

    return iterator;
  },
};

// よっしゃー使うぞー
for (let item of iterable) {
  console.log(item);
}

詳細は別稿参照。

その他

for-in と for-of

もちろん別物なんだけど、同じ項 (13.7.5) でまとめて定義されてるので、兄弟みたいなものらしい。

for-in はカンマ , 区切りの値を置ける

なんでOKなんだろ。

const arr1 = [100];
const arr2 = [200];

arr1.foo = 123;
arr2.bar = 123;
for (let index in arr1, arr2) {
  console.log(index);
}

もちろん末尾のもの arr2 が有効になります。

0
bar

for-of でやると構文エラーです。

const arr1 = [100];
const arr2 = [200];

for (let item of arr1, arr2) {  // SyntaxError: Unexpected token ,
  console.log(item);
}

for-in で初期値を書ける場面が

"use strict" の厳格モードでない場面で、 var をここで使用する場合のみ、初期値を置ける。 let を使ったり、別の場所で var 宣言してからの代入だとだめ。

const arr = [1, 2, 3];

for (var index = 'wow' in arr) {
  console.log(index);
}

といってもその初期値が適用される場面てあるんだろか。

謎仕様だなー。後方互換のためなのかな。

参考

  • ECMAScript® 2017 Language Specification
    • 13.7.5 The for-in and for-of Statements
    • 13.7.5.12 Runtime Semantics: ForIn/OfHeadEvaluation ( TDZnames, expr, iterationKind ) … イテレータの作成。初期処理的な
    • 13.7.5.13 Runtime Semantics: ForIn/OfBodyEvaluation ( lhs, stmt, iterator, iterationKind, lhsKind, labelSet ) … イテレータを使って反復処理する
    • 13.7.5.15 EnumerateObjectProperties ( O ) … for-in 用のイテレータ
    • 6.1.5.1 Well-Known Symbols … Symbole.iterator
    • 7.4.1 GetIterator ( obj [ , method ] )
    • 7.4.5 IteratorStep ( iterator ) … イテレータで反復
    • 25.1 Iteration
    • B.3.6 Initializers in ForIn Statement Heads … for-in で初期値使える
  • for…of – JavaScript | MDN
  • for…in – JavaScript | MDN