概要
ES6のgeneratorを利用することで、列挙可能なオブジェクトをより簡単に実装することができる。generatorを実装するのに必要なのは
1 function* {} で定義する。
2 yield 命令で値を返す。
3 next() で開始/再開
書き方としては、
function* print(){}
function *print(){}
のどちらでもよいが、後者のほうがオブジェクトリテラルでの簡潔な書き方に親しみやすい。
const print = {
*printDatas(){
//コード
}
}
yieldはreturnと似ているが、yieldはその値をいったん呼び出し元に返した後、その位置から処理を再開させます。yieldで返されたオブジェクトは停止している状態であり、next()メソッドが呼び出されるまで何もしない。
//列挙可能なオブジェクト
function* print() {
yield 'Hello!'
yield 'ES6'
yield 'generator!'
}
//next()で毎回呼び出す
console.log(print.next()) //Hello
console.log(print.next()) //ES6
console.log(print.next()) //generator!
//next()を内部的に含んでいるfor...ofで呼び出す
for(let t of print()){
console.log(t)
}
next()メソッドを呼び出すたびに、iteratorオブジェクトと同様なvalueとdoneの2つのプロパティを持つオブジェクトが返される。for...ofを使うことでprint()をすべて列挙することができる。yieldによってその時々で必要な処理をすることができるので、一度に列挙してからそれぞれに処理するよりもメモリ消費を抑えられるのが特徴だ。
next()に引数を入れて呼び出した場合かなり複雑になる。
print.next(1) -> let A = yield 'a' //前で実行したyieldから次のyieldまで実行
console.log(A) // Aオブジェクトのvalueプロパティとして 1 が出力される!
yield 'b'
一つ前のnext()においてyieldで 'a' を返したが、次のnext()で引数を添えて呼び出した場合、オブジェクト(今回ではAオブジェクト)にその引数(今回は1)がvalueプロパティとし格納される。
iterator -> generator
自作classを作ったときに、iteratorを実装したがgeneratorでも同じことを実装してみる。
class Name {
constructor(name){
this.name = name
//yieldは内部的にiteratorを返している。
this[Symbol.iterator] = function*() {
let current = 0
let that = this
while(current < that.name.length){
yield that.data[current++]
}
}
}
let itr = new Nmae(['1', '2', '3'])
for(let value of itr){
console.log(value)
}
for...of命令は内部的には「iteratorの取得、doneメソッドによる判定、valueプロパティによる値の取り出し」を行うので、generatorによって内部的なiteratorオブジェクトを設定しているのである。