はじめに
この記事は前回の記事の続きです。
前回の記事から読んでいただけると喜びます。よろしくお願いします。
また度々、間違い等が散見されると思いますが、その際はコメント欄でご指摘いただけますと幸いです。
よろしくお願いします。
ジェネレータをfor~of文で回してみよう。
前回の記事でも用いたcolors
ジェネレータをfor~of
文で回してみました。
// colorsジェネレータを用意
function* colors () {
yield 'red';
yield 'blue';
yield 'green';
}
const myColors = []; //空の配列を準備
for (let color of colors()) {
myColors.push(color);
// colorを配列に代入
}
console.log(myColors);
// => ["red", "blue", "green"]
上の結果の通り、ジェネレータをfor~of
文で回すとジェネレータの返り値として定義していたred,blue,green
を返してくれるようになりました。
つまりfor~of
文では内部的にジェネレータに対して返り値毎にnext
メソッドを実行してくれるシンタックスシュガーであることがわかりました。
この今までの流れがわかるようになると今までfor~of
文を使う際には配列の要素を一つ一つ処理していく用途にしか使えませんでしたが用途の幅が一気に広がります。
その用途の一例をみてみましょう。
オブジェクトをジェネレータで処理してみよう
下記のコードを見てください。ある開発チームの構成をオブジェクトで表現しています。解説させていただくと開発チーム(engneeringTeam
)の中にテストチーム(testTeam
)が存在し、このオブジェクトに定義されているメンバーの名前を取得するのにジェネレーターを使用しています。
// テストチーム
const testTeam = {
size:2,
department:'test',
lead:'山田三郎',
member:'加藤貴'
}
// エンジニアチーム
const engneeringTeam = {
size:3,
department:'develop',
lead: '田中太郎',
manager: '佐藤二朗',
member: '鈴木一郎',
testingTeam: testTeam // テストチームと紐付け
};
// テストチームオブジェクトを受け取って処理するGenerator
function* testIterator(team){
yield team.lead;
yield team.member;
}
// チームオブジェクトを受け取って処理するGenerator
function* teamIterator(team){
yield team.lead;
yield team.manager;
yield team.member;
// 紐付けしたTestチームを引数にGenerator生成
const testingTeamGenerator = testIterator(team.testingTeam);
// Testチームのジェネレーターを執行
yield* testingTeamGenerator;
}
const member = [];
for(let name of teamIterator(engneeringTeam)){
member.push(name);
}
console.log(member); // ["田中太郎", "佐藤二朗", "鈴木一郎","山田三郎","加藤貴"]
上記のコードではテストチームを処理するtestIterator
ジェネレータと開発チーム全体を処理するteamIterator
ジェネレータが存在しています。
まず、teamIterator
ジェネレータを見てみましょう。
function* teamIterator(team){
yield team.lead;
yield team.manager;
yield team.member;
// 紐付けしたTestチームを引数にGenerator生成
const testingTeamGenerator = testIterator(team.testingTeam);
// Testチームのジェネレーターを執行
yield* testingTeamGenerator;
}
このジェネレータは引数にteam
を受け取って処理の中でlead
,manager
,member
プロパティの値を取得し出力します。
次に処理の4行目ではconst testingTeamGenerator = testIterator(team.testingTeam);
によってtestIterator
ジェネレータにテストチーム(開発チームオブジェクトのtestingTeam
プロパティを参照)のオブジェクトを渡して定数testingTeamGenerator
を生成し、次のyield* testingTeamGenerator;
で定数に格納されているジェネレータを実行してくれます。
ちなみにtestIterator
ジェネレータは下記のコードです。
function* testIterator(team){
yield team.lead;
yield team.member;
}
これらの一連の処理を言語化すると開発チームのオブジェクトを渡されたジェネレータが開発チームのlead,manager,memberを参照し、開発オブジェクトに紐付けされたテストチームのlead,memberを参照しにいきます。
結果、この一連の処理が定義されたジェネレータをfor~of文で回すと期待の通り、チーム全員のメンバーの名前を出力してくれるわけです。
今までの一連の流れの可読性を上げよう。
ここまでの解説で例のジェネレータの処理の流れを説明しましたが正直、オブジェクトとそのオブジェクトを定義するジェネレータの定義が別々になっていて一々探すのは厄介です。次の段階ではオブジェクトとジェネレータを一遍に定義し可読性を上げていきましょう。
今回の例ではオブジェクトを処理対象にしていますがこれはクラス定義でも同様にできるものなので覚えておくとかなり便利です。
// テストチーム
const testTeam = {
size:2,
department:'test',
lead:'山田三郎',
member:'加藤貴',
// ES6の動的プロパティ 例えば、keyを[1+2]のようにすると計算してkeyを3としてくれる
// つまり[Symbol.iterator]も別の値に動的に変化してkeyとなる
[Symbol.iterator]: function* (){
// testチームのジェネレーターの機能を記述
yield this.lead;
yield this.member;
}
}
// エンジニアチーム
const engneeringTeam = {
size:3,
department:'develop',
lead: '田中太郎',
manager: '佐藤二朗',
member: '鈴木一郎',
testingTeam: testTeam, // テストチームと紐付け
[Symbol.iterator]: function* () {
yield this.lead;
yield this.manager;
yield this.member;
// テストチームのジェネレータを実行
yield* this.testingTeam;
}
};
const member = [];
for(let name of engneeringTeam){
member.push(name);
}
console.log(member); // ["田中太郎", "佐藤二朗", "鈴木一郎","山田三郎","加藤貴"]
今回のコードではオブジェクトの中に定義されている[Symbol.iterator]: function* (){}
に注目してください。先ほどのコードのfunction* teamIterator(team)
,function* testIterator(team)
と処理の中身は同じです。
そしてfor~of
文も回す対象になっているのはジェネレータではなく、engneeringTeam
オブジェクトです。
流れを話すとfor~of
文で回す対象に指定されるとまず対象の[Symbol.iterator]
メソッドをまず探しに行き、あれば実行してくれるというかなり便利な作りになっています。
yield* this.testingTeam;
に到達しても同様に対象となっているthis.testingTeam
、つまりtestTeam
の中に[Symbol.iterator]
メソッドを探しに行き、実行してくれるようになります。
結果は先のコードと同じになります。
私たちがなんとなく使用してきたfor~of
文ですがなぜ期待の通りに動かせるのかを見てみると様々な仕組みがあることがわかります。今回使用した[Symbol.iterator]
メソッドはイテレータを返すメソッドです。イテレータはnext
メソッドを持ち、出力した要素を一つ一つ取得してくれます。
私が以前から使用してきた配列にも内部で[Symbol.iterator]
メソッドを持ち、要素を一つずつ処理するイテレータを生成した後、next
メソッドで取り出す流れだと思います。(ここ曖昧ですいません。)
おまけ
最後にクラスにジェネレータを定義するコードを載せて終わります。
ちなみに*[Symbol.iterator]()
と定義されていますが*
がつかないとエラーになってしまいました。調べましたがクラスでなくオブジェクトに対しても付与さレテいたりいなかったりなのでちょっと違いがわかりませんでした。
どなたかわかる方いれば教えていただけると幸いです。
ちなみに以下のコードはComment
クラスにはコメントを格納するcontent
があり、またそのコメントに対するchildren
が定義されています。
const tree = new Comment('大絶賛', children);
で定義されているtree
をfor~of
で回すと*[Symbol.iterator]()
を探しにいき、まずcontent
を取得し、さらに配列構造になっているchildren
をfor~of
文で回します。一巡目だけみると、child
はnew Comment('賛成',[ new Comment('私もです!',[]) ])
が入り、yield* child;
によってまたクラスのcontent
を取得し、またその子要素を取得していく流れになっています。子要素が見つからなくなるまで無限に子要素を取得しにいきます。
// コメントクラス
class Comment {
constructor(content,children){
this.content = content;
this.children = children;
}
// classのなかにSymbol.iteratorを定義する場合
*[Symbol.iterator](){
yield this.content;
for(let child of this.children){
yield* child;
}
}
}
// 「大絶賛」というコメントの子要素である三つのコメント
const children = [
new Comment('賛成',[ new Comment('私もです!',[]) ]),
new Comment('反対',[]),
new Comment('甘い',[])
];
// childrenの配列を子要素に持つ親要素
const tree = new Comment('大絶賛', children);
const comments = [];
for(let i of tree){
comments.push(i);
}
console.log(comments); // ["大絶賛", "賛成", "私もです!", "反対", "甘い"]
以上です。文章力と理解力の低さの生で乱雑な説明になってしまいましたが間違いのご指摘をいただけると幸いです。