for...of とスプレッドが使えるかどうかは Symbol.iterator を持つかで決まります。自分で作る方法、ジェネレータ、1度きりしか回せないこと、無限の列を扱う形まで実行して確かめます。
for...of とスプレッドが使えるかどうかは、たった1つの決まりで決まります。
example.js ▶ 実行↺ 戻す
for (const value of [[1 ], 'ab' , new Set ([1 ]), new Map ([['a' , 1 ]])]) {
console.log (typeof value[Symbol .iterator]);
}
console.log (typeof ({})[Symbol .iterator]);出力 書き換えて実行できます
function
function
function
function
undefined
javascript
for ( const value of { a : 1 }) { console .log (value); }
TypeError: {(intermediate value)} is not iterable
Symbol.iterator という名前の関数を持っていれば回せます。
素のオブジェクトはこれを持っていません。だから落ちます。
example.js ▶ 実行↺ 戻す
for (const [key, value] of Object .entries ({ a: 1 , b: 2 })) {
console.log (key, value);
}出力 書き換えて実行できます
a 1
b 2
example.js ▶ 実行↺ 戻す
const iterator = [10 , 20 ][Symbol .iterator]();
console.log (iterator.next ());
console.log (iterator.next ());
console.log (iterator.next ());出力 書き換えて実行できます
{ value: 10, done: false }
{ value: 20, done: false }
{ value: undefined, done: true }
回せるもの
回せないもの
配列 / 文字列 / Set / Map
素のオブジェクト
arguments
{ length: 3 } のような形だけ似たもの
ジェネレータ
数値・真偽値
素のオブジェクトを回すなら、いったん配列にします。
example.js ▶ 実行↺ 戻す
const range = {
from : 1 ,
to: 3 ,
*[Symbol .iterator]() {
for (let i = this .from ; i <= this .to; i++) {
yield i;
}
},
};
console.log ([...range]);
console.log (Array .from (range));出力 書き換えて実行できます
[ 1, 2, 3 ]
[ 1, 2, 3 ]
example.js ▶ 実行↺ 戻す
function * gen () {
yield 1 ;
yield 2 ;
return 99 ;
}
const g = gen ();
console.log (g.next (), g.next (), g.next (), g.next ());
console.log ([...gen ()]);出力 書き換えて実行できます
{ value: 1, done: false } { value: 2, done: false } { value: 99, done: true } { value: undefined, done: true }
[ 1, 2 ]
→ Object.keys() / values() / entries()
中身は「次を返す関数」だけ
Symbol.iterator を呼ぶとイテレータ が返り、それに next() を聞きます。
javascript
function* gen () { yield 1 ; yield 2 ; } const g = gen (); console .log ([ ... g]); console .log ([ ... g]);
[ 1, 2 ]
[]
example.js ▶ 実行↺ 戻す
const array = [1 , 2 ];
console.log ([...array], [...array]);出力 書き換えて実行できます
[ 1, 2 ] [ 1, 2 ]
done が true になるまで呼び続ける。 for...of がやっているのはこれだけです。
自分で作る
Symbol.iterator を持たせれば、素のオブジェクトでも回せます。
example.js ▶ 実行↺ 戻す
function * gen () {
yield 1 ;
yield 2 ;
}
const values = [...gen ()];
console.log (values, values);出力 書き換えて実行できます
[ 1, 2 ] [ 1, 2 ]
example.js ▶ 実行↺ 戻す
function * naturals () {
let n = 1 ;
while (true ) {
yield n++;
}
}
function * take (iterable, count) {
let i = 0 ;
for (const value of iterable) {
if (i++ >= count) return ;
yield value;
}
}
console.log ([...take (naturals (), 5 )]);出力 書き換えて実行できます
[ 1, 2, 3, 4, 5 ]
function* と yield で書くと、next() を自分で用意しなくて済みます。
これをジェネレータ と呼びます。
ジェネレータ
example.js ▶ 実行↺ 戻す
function * gen () {
try {
yield 1 ;
yield 2 ;
yield 3 ;
} finally {
console.log ('後始末' );
}
}
for (const n of gen ()) {
console.log (n);
if (n === 2 ) break ;
}出力 書き換えて実行できます
1
2
後始末
example.js ▶ 実行↺ 戻す
function * gen () {
yield 1 ;
yield 2 ;
}
const [first, ...rest] = gen ();
console.log (first, rest);
console.log (Math .max (...gen ()));
console.log (new Set (gen ()));
console.log (Object .fromEntries ([['a' , 1 ]]));出力 書き換えて実行できます
1 [ 2 ]
2
Set(2) { 1, 2 }
{ a: 1 }
yield で止まって値を返し 、次に呼ばれたら続きから動く
return した値は done: true と一緒に来る
for...of とスプレッドは return の値を拾いません ([1, 2] だけ)
return に意味のある値を入れないでください。 多くの書き方で消えます。
1度回すと空になる
ここがいちばん事故になります。
配列は何度でも回せますが、イテレータは使い切りです。
2回使うなら、先に配列にしてください。
終わらない列も書ける
return するまで作られないので、無限に続く列を書いても止まりません。
必要になったときにだけ計算されます。これを遅延評価 と呼びます。
[...naturals()] と書くと止まりません 。必ず打ち切りを挟んでください。
途中で抜けたときの後始末
break で抜けても、finally は動きます。
ファイルや接続を閉じる処理を、安心して書けます。
→ Error と try / catch / finally / throw
どこで効いてくるか
Symbol.iterator を持っていれば、言語のいろいろな場所がそのまま使えます。
for...of・スプレッド・分割代入・Array.from()・new Set()・new Map()。
入口が1つなので、対応する場所が一気に増えます。
まとめ
回せるかどうかはSymbol.iterator を持つか だけで決まる
素のオブジェクトは持っていない。Object.entries() を挟む
中身は next() が { value, done } を返すだけ
function* と yield で短く書ける
イテレータは使い切り。 2回使うなら先に配列にする
終わらない列も書ける。ただし打ち切りを忘れると止まらない
break で抜けても finally は動く