JavaScriptの新しいAPI、ジェネレータの使い方
ES6で新しく追加されたJavaScript APIにジェネレータがあります。使い方次第で新しいJavaScriptの書き方が可能です。ES6 GeneratorはIE11を除くモダンなブラウザではサポートされていますので、ES6が利用できるのであれば使わない手はないでしょう。
ジェネレータの基本形
ジェネレータは function* という形で記述します。そしてyieldという形で値を返却します。このyieldが呼ばれる度にジェネレータの呼び出し元に値が送られます。
function* gen(i) { | |
i++; | |
yield i; | |
i++; | |
yield i; | |
i++; | |
yield i; | |
} |
呼び出す際には関数を実行します。
const g = gen(3); |
さらに このジェネレータ(g)のnextメソッドを呼び出すとyieldと書かれた部分まで処理が進みます。返り値はオブジェクトで、valueキーに値が、doneキーにyieldが終了したかどうかが入ってきます。
console.log(g.next()); | |
–> {done: false, value: 4} | |
console.log(g.next()); | |
–> {done: false, value: 5} | |
console.log(g.next()); | |
–> {done: false, value: 6} | |
console.log(g.next()); | |
–> {done: true, value: undefined} |
nextメソッドではなく、for of を使うこともできます。この場合はvがvalueキーの内容になります。
for (let v of g) { | |
console.log(v); | |
–> 4、5、6が順番に出力されます | |
} |
オブジェクトを返せば複数の値を渡すこともできます。
function* gen(i) { | |
yield {o: i, n: ++i}; | |
yield {o: i, n: ++i}; | |
yield {o: i, n: ++i}; | |
} | |
let g = gen(3); | |
for (let {o, n} of g) { | |
console.log(o, n); | |
–> 3, 4 | |
–> 4, 5 | |
–> 5, 6 | |
} |
nextを使う場合、yieldに対して引数を送ることができます。この値は yield の返り値として受け取れるものになります。
function* gen(i) { | |
let m = yield {o: i, n: ++i}; | |
m = yield {o: m, n: ++m}; | |
m = yield {o: m, n: ++m}; | |
} | |
let g = gen(3); | |
let {o, n} = g.next().value; | |
console.log(o, n); | |
–> 3, 4 | |
let {o: a, n: b} = g.next(n).value; | |
console.log(a, b); | |
–> 4, 5 | |
let {o: c, n: d} = g.next(n).value; | |
console.log(c, d); | |
–> 5, 6 |
配列にeachWithIndexを実装する
例えば配列を順番に処理する場合、for を使います。
const ary = []; | |
ary.push(1); | |
ary.push(2); | |
ary.push(3); | |
for (let i = 0; i < ary.length; i += 1) { | |
let value = ary[i]; | |
} |
この毎回 value = ary[i] というのを書くのが面倒です。逆に for of を使った場合にはインデックスがないので不便な時もあります(回避する方法としてentriesを使う手もあります)。
for (let value of ary) { | |
// インデックスがない | |
} |
そこでeachWithIndexメソッドを作って、インデックスも返ってくるイテレータを作ってみます。これはArrayのprototypeに作ります。
Array.prototype.eachWithIndex = function* () { | |
for (let i = 0; i < this.length; i += 1) { | |
yield {index: i, value: this[i]}; | |
} | |
}; |
後はこのメソッドをfor ofの中で呼び出します。 eachWithIndex は関数であり、それを実行すると処理が開始されるので注意してください。
const ary = []; | |
ary.push(1); | |
ary.push(2); | |
ary.push(3); | |
for (let {index, value} of ary.eachWithIndex()) { | |
console.log(index, value); | |
} |
クラスにイテレーションを追加する
もし自作クラスにイテレーションを追加したい場合には、 Symbol.iterator
を定義します。
class Hello { | |
constructor() { | |
this.ary = ['a', 'b', 'c']; | |
} | |
* [Symbol.iterator](){ | |
for (let v of this.ary) { | |
yield v + v; | |
} | |
} | |
}; |
これを実行します。
const hello = new Hello(); | |
const ary = []; | |
for (let v of hello) { | |
console.log(v); | |
–> aa, bb, ccと出ます | |
} |
こちらはJSFiddleにデモを掲載しています。
イテレーションを使うことで、ループ処理であったり、段階を踏んで処理するようなところが書きやすくなります。JavaScriptはシングルスレッドで非同期処理が面倒ですが、イテレーションを使えば処理が完了してから次の処理に移動すると言ったことも書けるでしょう。
コメントは受け付けていません。