
JavaScriptにおけるES6イテレータのシンプルなガイド(例付き
今回の記事では、イテレータを分析していきます。
イテレータは、JavaScriptで任意のコレクションをループするための新しい方法です。
ES6で導入され、便利で様々な場所で使用されているため、とても人気があります。
イテレータとは何か、どこで使うのかを例を挙げて概念的に理解していきます。
また、JavaScriptでの実装もいくつか見ていきます。
[st-minihukidashi fontawesome=”” fontsize=”” fontweight=”” bgcolor=”#FFB74D” color=”#fff” margin=”0 0 20px 0″ radius=”” add_boxstyle=””]この記事は以下の記事を翻訳し要点だけまとめたものです[/st-minihukidashi]
はじめに
次のような配列があるとします。
const myFavouriteAuthors = [
'Neal Stephenson',
'Arthur Clarke',
'Isaac Asimov',
'Robert Heinlein'
];
これを、画面に表示したり、操作したり、何らかのアクションを実行するために、
配列内の個々の値をすべて取得したいと思うとします。
どうやってそんなことをするのかと尋ねたら、あなたはこう言うでしょう。
「簡単だよ、for、while、for-ofなどのループメソッドを使って、配列をループさせるだけだ」と。
それで、これまでの配列の代わりに、
すべての著者を格納するカスタムデータ構造があったとします。
次のようになります。
allAuthors には fiction, scienceFiction, fantasy というキーを持つ 3 つの配列があります。
myFavouriteAuthors をループしてすべての著者を取得しろというと、
あなたはどのようなアプローチをとるでしょうか。
for (let author of myFavouriteAuthors) {
console.log(author)
}
// TypeError: {} is not iterable
オブジェクトが反復可能ではないという TypeError が発生します。
イテレート可能とは何か、どうすればオブジェクトをイテレート(反復)可能にできるかを見てみましょう。
ここでは myFavouriteAuthors を対象としています。
イテレートとイテレータ
前のセクションで問題が発生し、カスタムオブジェクトからすべての作者を取得する簡単な方法がありませんでした。
何らかのメソッドを使って、すべての内部データを順次公開することができるか考えてみます。
myFavouriteAuthors にすべての著者を返すメソッド getAllAuthors を追加しましょう。
次のようにします。
これはシンプルなアプローチです。
すべての著者を取得するという現在のタスクを達成しています。
しかし、この実装ではいくつかの問題が発生する可能性があります。
そのうちのいくつかは –
- getAllAuthorsという名前は非常に特殊です。もし誰かが自分のmyFavouriteAuthorsを作っていたら、retrieveAllAuthorsという名前にするかもしれません。
- 開発者である私たちは、すべてのデータを返す特定のメソッドについて常に知っておく必要があります。この場合は getAllAuthors という名前です。
- getAllAuthorsは、すべての著者の文字列の配列を返します。他の開発者が、次のような形式でオブジェクトの配列を返してきたらどうしますか?
[ {name: 'Agatha Christie'}, {name: 'J. K. Rowling'}, ... ]
開発者は、すべてのデータを返すメソッドの正確な名前と戻り値の型を知る必要があります。
Dr. Axel Rauschmayerの著書『Exploring JS』によると
イテレート可能なデータ構造は、その要素を一般に公開することを目的としています。
Symbol.iteratorをキーとするメソッドを実装することでそれを実現しています。
このメソッドはイテレータのファクトリです、つまり、イテレータを作成します。
イテレータは、データ構造の要素を走査するためのポインタです。
オブジェクトをイテレート可能にする
つまり、Symbol.iteratorというメソッドを実装すればいいわけです。
このキーを設定するためにcomputed property構文を使用します。簡単な例を挙げると
4行目では、イテレータを作成しています。
これはnextメソッドが定義されたオブジェクトです。
nextメソッドはstep変数に応じた値を返します。
25行目ではイテレータを取得しています。
27行目ではnextを呼び出しています。
doneがtrueになるまでnextを呼び続けます。
これはまさにfor-ofループで起こることです。
for-ofループはイテレートを受け取り、そのイテレータを作成します。
そして、doneがtrueになるまで、next()を呼び続けます。
JavaScriptにおける反復可能なもの
JavaScriptでは多くのものが反復可能です。
すぐには見えないかもしれませんが、よく調べてみると反復可能なものが見えてきます。
- 配列と型付き配列
- 文字列 – 各文字やUnicodeのコードポイントを反復して処理します。
- マップ – キーと値のペアを繰り返し処理します。
- セット – その要素を繰り返し処理する
- 引数 – 関数内の配列のような特殊変数
- DOM 要素 (作業中)
イテレートを使用するJSの他の構成要素としては、以下のものがあります。
- for-of ループ – for-of ループはイテレート可能なものを必要とします。そうでない場合は、TypeErrorが発生します。
for (const value of iterable) { ... }
配列のデストラクションは反復可能であるために起こります。その方法を見てみましょう。
const array = ['a', 'b', 'c', 'd', 'e'];
const [first, ,third, ,last] = array;
これに相当します。
const array = ['a', 'b', 'c', 'd', 'e'];
const iterator = array[Symbol.iterator]();
const first = iterator.next().value
iterator.next().value // Since it was skipped, so it's not assigned
const third = iterator.next().value
iterator.next().value // Since it was skipped, so it's not assigned
const last = iterator.next().value
スプレッド演算子(…)
const array = ['a', 'b', 'c', 'd', 'e'];
const newArray = [1, ...array, 2, 3];
次のように書くこともできます。
const array = ['a', 'b', 'c', 'd', 'e'];
const iterator = array[Symbol.iterator]();
const newArray = [1];
for (let nextValue = iterator.next(); nextValue.done !== true; nextValue = iterator.next()) {
newArray.push(nextValue.value);
}
newArray.push(2)
newArray.push(3)
myFavouriteAuthorsの反復可能化
myFavouriteAuthorsを反復可能にする実装を紹介します。
const myFavouriteAuthors = {
allAuthors: {
fiction: [
'Agatha Christie',
'J. K. Rowling',
'Dr. Seuss'
],
scienceFiction: [
'Neal Stephenson',
'Arthur Clarke',
'Isaac Asimov',
'Robert Heinlein'
],
fantasy: [
'J. R. R. Tolkien',
'J. K. Rowling',
'Terry Pratchett'
],
},
[Symbol.iterator]() {
// Get all the authors in an array
const genres = Object.values(this.allAuthors);
// Store the current genre and author index
let currentAuthorIndex = 0;
let currentGenreIndex = 0;
return {
// Implementation of next()
next() {
// authors according to current genre index
const authors = genres[currentGenreIndex];
// doNotHaveMoreAuthors is true when the authors array is exhausted.
// That is, all items are consumed.
const doNothaveMoreAuthors = !(currentAuthorIndex < authors.length);
if (doNothaveMoreAuthors) {
// When that happens, we move the genre index to the next genre
currentGenreIndex++;
// and reset the author index to 0 again to get new set of authors
currentAuthorIndex = 0;
}
// if all genres are over, then we need tell the iterator that we
// can not give more values.
const doNotHaveMoreGenres = !(currentGenreIndex < genres.length);
if (doNotHaveMoreGenres) {
// Hence, we return done as true.
return {
value: undefined,
done: true
};
}
// if everything is correct, return the author from the
// current genre and incerement the currentAuthorindex
// so next time, the next author can be returned.
return {
value: genres[currentGenreIndex][currentAuthorIndex++],
done: false
}
}
};
}
};
for (const author of myFavouriteAuthors) {
console.log(author);
}
console.log(...myFavouriteAuthors)
この記事で得た知識を使えば、イテレータがどのように動作しているかを簡単に理解することができます。
ロジックの説明は少し難しいかもしれませんので、コードと一緒にコメントを書いています。
しかし、コンセプトを理解するための最良の方法は、ブラウザやノードでコードを操作することです。
ぜひコンセプトがわかったら自分でコーディングしてみてください。