イテレーターについて
「イテレーター」は反復操作のためのオブジェクトです
2つのプロパティ(value・done)を返すnext()
メソッドを持ちます
valueプロパティ:次の値
doneプロパティ:最後の値が使われたら「true 」となります
const iterator = {
next: function(){
return {
done: false, //true
value: 値,
}
};
反復可能(iterable)オブジェクトとは[Symbol.iterator]
メソッドを実行するとイテレーターを返すオブジェクトのことです
*ビルドインオブジェクトでは「String・Array ・ Map・Set・NodeListなど」が反復可能(iterable)オブジェクトです
//objは反復可能(iterable)オブジェクト
const obj = {
[Symbol.iterator]:function(){
return iterator;
}
「for of」ループ
- 反復可能(iterable)オブジェクトに対してループ処理をします(値が出力されます)
*[Symbol.iterator]
で実装されています
ジェネレーター
ジェネレーター関数を使うとイテレーターをシンプルに作成できます
ジェネレーター関数と呼び出し側とで双方向のコミュニケーションが可能です
*アロー関数は使用できません
ジェネレーター関数は通常の関数と異なり、呼ばれたても即コードは実行されません
- ジェネレーター関数を呼ぶとまずはイテレーターが返されます
- イテレーターの
next()
メソッドを呼び出すたびに実行が進みます - ジェネレータ関数で「
yield
」を使用して実行を「一時停止」し「停止した場所」を記憶 next()
メソッドで「停止した場所」から実行されます
*イテレーターのnext()
メソッドを実行するとvalueプロパティとdoneプロパティを持つオブジェクトを返します
*yieldキーワードを検出するまで実行し、終了以降のnext()
の呼び出しでは「valueはundefined・doneはture」になります
function* num() {
yield 1;
yield 2;
yield 3;
}
//ジェネレーター関数を実行
const genIterator = num();
//next()メソッドを呼び出して実行
console.log(genIterator.next())
console.log(genIterator.next())
console.log(genIterator.next())
console.log(genIterator.next()) //ジェネレーターが終了以降のnext()の呼び出し
// { value: 1, done: false }
// { value: 2, done: false }
// { value: 3, done: false }
// { value: undefined, done: true }
ジェネレーターで 「return 」文が実行されるとジェネレーターが終了します
値が返された場合valueプロパティとして設定されます
function* num() {
yield 1;
return '終わり!'
}
const genIterator = num();
console.log(genIterator.next())
console.log(genIterator.next())
//{value: 1, done: false}
//{value: '終わり!', done: true}
ジェネレーター関数を実行して返されたイテレーターを使いfor ofループします
//ジェネレーター関数 numを作成
function* num() {
yield 1;
yield 2;
yield 3;
}
//ジェネレーター関数を実行して返されたイテレーターを使ってfor ofループ
for(i of num()){
console.log(i)
}
//1
//2
//3
オブジェクトを反復可能にする
オブジェクトは反復可能(iterable)オブジェクトではないので通常「for of」ループはできません
ジェネレーター関数の引数にオブジェクトを渡し、ジェネレーター関数を呼び出してイテレーターを受け取り「for of」ループします
const user = {
id: 1,
name: "花子",
email: "hoge@april.biz",
}
function* userIterator(arg) {
yield arg.id
yield arg.name
yield arg.email
}
//userIterator()を実行=イテレーターが返されます)
for(i of userIterator(user)){
console.log(i)
}
//1
//花子
//hoge@april.biz
別のジェネレーターに委任する(yield*
)
*もし「yield*
」があればそのジェネレーターに委任します
下記のコードではオブジェクトを合体させて一度に「for of」ループしています
const obj1={
name:'花子',
age: 20
}
const obj2={
email: 'hoge@april.biz',
phone: '090-000-000'
}
//obj1のイテレーター
function* gen1(arg) {
yield arg.name
yield arg.age
}
//obj2とobj1を委任されたイテレーター
function* gen2(arg) {
yield* gen1(obj1);
yield arg.email
yield arg.phone
}
for(i of gen2(obj2)){
console.log(i)
}
//花子
//20
//hoge@april.biz
//090-000-000
[Symbol.iterator]
を実装して反復可能(iterable)オブジェクトにします
よりシンプルになります
*オブジェクト自身なのでthisキーワードを使います
*配列扱いでスプレッド演算子が使えます
const user = {
id: 1,
name: "花子",
email: "hoge@april.biz",
[Symbol.iterator]:function* () {
yield this.id
yield this.name
yield this.email
}
}
for(i of user){
console.log(i)
}
//1
//花子
//hoge@april.biz
console.log([...user])
// [1, '花子', 'hoge@april.biz']
const obj1={
name:'花子',
age: 20,
[Symbol.iterator]:function* (){
yield this.name
yield this.age
}
}
const obj2={
email: 'hoge@april.biz',
phone: '090-000-000',
[Symbol.iterator]:function* (){
yield* obj1
yield this.email
yield this.phone
}
}
for(i of obj2){
console.log(i)
}
//花子
//20
//hoge@april.biz
//090-000-000
yieldのループ
ジェネレーター関数内でのループはforまたはwhileを使います
function* step(min, max, step) {
for (let i = min; i <= max; i += step) {
yield i;
}
}
//または
function* step(min, max, step) {
let i = min
while (i <= max) {
yield i
i += step
}
}
for(let i of step(1, 10, 2)){
console.log(i)
}
// 1
// 3
// 5
// 7
// 9
function* obj() {
for (let i = 0; i <= 5; i++) {
const data = {
id: i,
}
yield data
}
}
for (const data of obj()) {
console.log(data)
}
// { id: 0 }
// { id: 1 }
// { id: 2 }
// { id: 3 }
// { id: 4 }
// { id: 5 }
*「yield*
」は「もしあれば」なのでTree構造になっている反復可能オブジェクト(Array ・NodeListなど)の再帰に応用できると思います^^;
class Parent {
constructor(text, children){
this.text = text;
this.children = children;
}
//クラスのメソッドに[Symbol.iterator]
*[Symbol.iterator]() {
yield this.text;
for (let child of this.children) {
//もしあれば
yield* child
}
}
}
const children = [
new Parent('aa', []),
new Parent('bb',
[new Parent('ccc', [ new Parent('dddd', [])]) ],
),
];
const tree = new Parent('a', children)
console.dir(tree, { depth: null });
// Parent {
// text: 'a',
// children: [
// Parent { text: 'aa', children: [] },
// Parent {
// text: 'bb',
// children: [
// Parent {
// text: 'ccc',
// children: [ Parent { text: 'dddd', children: [] } ]
// }
// ]
// }
// ]
// }
for(let v of tree){
console.log(v)
}
// a
// aa
// bb
// ccc
// dddd
yieldとnext
ジェネレーター関数と呼び出し側との双方向のコミュニケーション
next()
再開時の引数はyield
式の結果として取得します
*最初の呼び出しのnext() は引数なしです
function* genFunc( args ){
console.log(args); //引数
const result1 = yield "a";
console.log(result1); // "あ"
const result2 = yield "b";
console.log(result2); // "い"
const result3 = yield "c";
console.log(result3); // "う"
return [result1, result2, result3]
}
const genIt = genFunc('引数');
const v1 = genIt.next();
console.log(v1); // { value: 'a', done: false }
const v2 = genIt.next("あ");
console.log(v2); // { value: 'b', done: false }
const v3 = genIt.next("い");
console.log(v3); // { value: 'c', done: false }
const v4 = genIt.next("う");
console.log(v4); // { value: [ 'あ', 'い', 'う' ], done: true }
next()
の引数をyieldの結果として取得して、それをもとに条件分岐
function* count() {
let index = 1
while (true) {
//next()引数をyield式の結果として取得
const step = yield index
if(step != null){
index += step
} else {
index ++
}
}
}
const countGen = count()
console.log(countGen.next())
console.log(countGen.next(10))
console.log(countGen.next(100))
console.log(countGen.next())
//
// { value: 1, done: false }
// { value: 11, done: false }
// { value: 111, done: false }
// { value: 112, done: false }
return(値)
メソッド:値を返してジェネレーター自身の処理を終了します
console.log(countGen.next())
console.log(countGen.return(1))
// { value: 1, done: false }
// { value: 1, done: true }