今回はジェネレーターやってみようと思います。
お世話になるサイト
https://hakuhin.jp/js/generator.html
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/yield
https://qiita.com/kura07/items/d1a57ea64ef5c3de8528
##ジェネレーターって?
ジェネレーター・・・データを順に生成する。Iteratorと互換性あり。
ジェネレーターを作るには、
function*{...}
で定義を行います。
そして、yield
で値を返します。
function* myGenerator(){
yield 1;
yield 2;
yield 3;
}
for(let t of myGenerator()){
console.log(t);
}
1,2,3がコンソールに表示されました。
なんだか不思議な関数ですね。
yield
・・・returnと同じように関数の値を呼び戻し元に返す。
ただし、関数を終了せず一時停止するだけ!
→次に呼び出された時は続きから処理が行われる。
なんだかおもしろそうな関数です。
余談ですが、アスタリスクを見るとCのポインタを思い出してクラクラします。
##ジェネレーターの使い道
function* myGenerator(){
let num = 2;
while(true){
if(isPrime(num)){ yield num;}
num++;
}
}
function isPrime(value){
let prime = true;
for(let i = 2;i<=Math.floor(Math.sqrt(value));i++){
if(value % i === 0){
prime = false;
break;
}
}
return prime;
}
for(let value of myGenerator()){
if(value > 100){break;}
console.log(value);
}
これはジェネレーターを使った便利な例として、JavaScript本格入門の本の中で紹介されていた、素数をコンソールするプログラムです。ゆっくり読み解いていきます。
function* myGenerator(){
let num = 2;
while(true){
if(isPrime(num)){ yield num;}
num++;
}
}
ここではジェネレーター関数が定義されていて、numは2から始まっています。(素数は2からのスタート)
whileは無限に回るようになっていて、isPrime(num)
がtrueの時numを出力しています。
yield
は一時停止なので、直呼び出されたらnumはプラス1されますね。
function isPrime(value){
let prime = true;
for(let i = 2;i<=Math.floor(Math.sqrt(value));i++){
if(value % i === 0){
prime = false;
break;
}
}
return prime;
}
もし割り切れる数が現れた場合、
戻り値をfalseに、割り切れなかった場合はtrueを返す関数です。
forのループ制限に<Math.floor(Math.sqrt(value))
が使われています。
つまり、value
の平方根(小数点切り落とし)の回数を繰り返しています。
これは約数を求める時を考えれば簡単ですが、素数は平方根で十分です。なぜなら、
平方根×平方根以上の数値が現れた場合は、その数値と対になる数は平方根以下であることが確実だからですね。
for(let value of myGenerator()){
if(value > 100){break;}
console.log(value);
}
そして最後に関数の実行文です。
引数が100以上にならないように調整しています。
ジェネレーターのいいところ・・・値がその都度返されるので、上限がわからない場合などはメモリ消費が最小限で済む
##ジェネレーターとイテレーターとか
大前提:ジェネレータ関数の戻り値は、なんとイテレータです。for ..of
を使えていたことからも明確。
前の学習記録参考
###yield*
yield*
という書き方もできて、これにイテラブルなオブジェクトを与える。
すると、そのオブジェクトから順番に値を取り出し、それぞれにyield
を行なってくれる!
function* myGenerator(){
yield* [1,2,3];
}
for(let n of myGenerator()){
console.log(n);
}
yield*
はfor(let n of iterableObject) yield n
の
糖衣構文ってことですね。
##for..ofじゃなくてnext()使えるよね
イテレータということはそういうことです。
function* generator(){
let num = yield 0;
yield*[1,4,6];
}
let g = generator(); //ここでは実行されていない
console.log(g.next()); //value:0,done: false
console.log(g.next()); //value:1,done:false
console.log(g.next()); //value:4,done:false
console.log(g.next()); //value:6,done:false
console.log(g.next()); //value:undefined,done:true
generatorを使えばIteratorを簡単につくることができます。
class MyIterator {
constructor(data){
this.data = data;
this[Symbol.iterator] =function*(){
let current = 0;
let that = this;
while(current<that.data.length){
yield that.data[current++];
}
};
}
}
let i = new MyIterator(['irico','niboshi','zyako']);
for(let value of i){
console.log(value);
}
//irico,niboshi,zyakoが表示される
以前のイテレーターと比べるとかなりスッキリしました!
##余談
Web制作のために日々JavaScriptを勉強していますが、
非同期の仕組みやfor..ofの挙動について勉強になった他に
イテレーターやジェネレーターを実務でどう活かすか?についてはいまいちピンと来ていません。
そういうことも意識しつつ勉強した方がいいかな、と思いました。
(もし利用できそうな場面ありましたらお教えて欲しいです!)