ECMAScript 2015 2つのものが追加されました。これらは新しい組み込みオブジェクトや構文ではなくプロトコルです。これらのプロトコルは以下のような単純な約束事によって、すべてのオブジェクトで実装することができます。
プロトコルは2つあります。 反復可能プロトコルと 反復子プロトコルです。
反復可能 (iterable) プロトコル
反復可能プロトコルによって、 JavaScript のオブジェクトは反復動作を定義またはカスタマイズすることができます。例えば、 for...of 構造の中でどの値がループに使われるかです。一部の組み込み型は既定の反復動作を持つ組み込み反復可能オブジェクトで、これには Array や Map がありますが、他の型 (Object など) はそうではありません。
反復可能であるために、オブジェクトは@@iterator メソッドを実装する必要があります。これはつまり、オブジェクト (または、プロトタイプチェーン上のオブジェクトの一つ) が 定数にて利用できる @@iterator キーのプロパティを持つ必要があります。Symbol.iterator
| プロパティ | 値 |
|---|---|
[Symbol.iterator] |
反復子プロトコルに準拠するオブジェクトを返す、引数なしの関数。 |
(for...of ループの始まりのように) オブジェクトが反復される必要があるときはいつでも、その @@iterator メソッドが引数なしで呼ばれます。そして、返される反復子は、反復される値を取得するために使用されます。
なお、この引数なしの関数が呼び出されると、反復可能オブジェクト上のメソッドとして呼び出されます。従って関数の中では、 this キーワードを反復可能オブジェクトのプロパティにアクセスするために使用して、反復の間に何を提供するかを決めることができます。
この関数は普通の関数、またはジェネレーター関数にすることができ、そのため呼び出されると、反復子オブジェクトが返されます。このジェネレーター関数の中では yield を使用してそれぞれの項目を提供することができます。
反復子 (iterator) プロトコル
反復子プロトコルは、値のシーケンス (有限でも無限でも) を生成するための標準的な方法と、すべての値が生成された場合の返値を定義します。
以下の意味で next() メソッドを実装していれば、オブジェクトは反復子になります。
| プロパティ | 値 |
|---|---|
next() |
引数なしの関数で、少なくとも以下の二つのプロパティを持つオブジェクトを返します。
|
メモ: 特定のオブジェクトが反復子プロトコルを実装しているかどうかを反射的に知ることはできません。しかし、反復子プロトコルと反復可能プロトコルの両方を満たすオブジェクトを作成するのは簡単です (以下の例にあるように)。
そうすることで、反復可能オブジェクトを期待するさまざまな構文で反復子を使用できます。したがって、反復子プロトコルを実装するには反復可能プロトコルも実装しないと、ほとんど役に立ちません。
// 反復子と反復可能の両プロトコルを満たす
let myIterator = {
next: function() {
// ...
},
[Symbol.iterator]: function() { return this }
}
例: 反復処理プロトコルの使用
String は組み込み反復可能オブジェクトの一例です。
let someString = 'hi' typeof someString[Symbol.iterator] // "function"
String の既定の反復子は文字列のコードポイントを1つずつ返します。
let iterator = someString[Symbol.iterator]()
iterator + '' // "[object String Iterator]"
iterator.next() // { value: "h", done: false }
iterator.next() // { value: "i", done: false }
iterator.next() // { value: undefined, done: true }
一部の組み込みコンストラクター — 例えばスプレッド構文 — は、まったく同じ反復処理プロトコルを使用しています。
[...someString] // ["h", "i"]
自身の @@iterator を提供することによって反復動作を再定義できます。:
let someString = new String('hi') // need to construct a String object explicitly to avoid auto-boxing
someString[Symbol.iterator] = function() {
return {
// this is the iterator object, returning a single element (the string "bye")
next: function() {
if (this._first) {
this._first = false
return { value: 'bye', done: false }
} else {
return { done: true }
}
},
_first: true
}
}
@@iterator を再定義することによって、反復処理プロトコルを使用する組み込みコンストラクターの動作にどれほど影響を与えるか注意してください。
[...someString] // ["bye"] someString + '' // "hi"
反復可能プロトコルの例
組み込み反復可能オブジェクト
String, Array, TypedArray, Map, Set は、すべての組み込み反復可能オブジェクトです。というのも、それらすべてのプロトタイプオブジェクトは @@iterator メソッドをもつからです。
ユーザー定義の反復可能オブジェクト
下記のように反復可能オブジェクトを生成できます。
let myIterable = {}
myIterable[Symbol.iterator] = function* () {
yield 1
yield 2
yield 3
};
[...myIterable] // [1, 2, 3]
反復可能オブジェクトを受け入れる組み込み API
反復可能オブジェクトを受け入れる API はたくさんあります。以下はその例です。
let myObj = {}
new Map([[1, 'a'], [2, 'b'], [3, 'c']]).get(2) // "b"
new WeakMap([[{}, 'a'], [myObj, 'b'], [{}, 'c']]).get(myObj) // "b"
new Set([1, 2, 3]).has(3) // true
new Set('123').has('2') // true
new WeakSet(function* () {
yield {}
yield myObj
yield {}
}()).has(myObj) // true
関連情報
反復可能オブジェクトを期待する構文
いくつかの文や式は反復可能オブジェクトを期待します。例えば、 for...of ループ、スプレッド演算子、yield*、分割代入 などです。
for (let value of ['a', 'b', 'c']) {
console.log(value)
}
// "a"
// "b"
// "c"
[...'abc'] // ["a", "b", "c"]
function* gen() {
yield* ['a', 'b', 'c']
}
gen().next() // { value:"a", done:false }
[a, b, c] = new Set(['a', 'b', 'c'])
a // "a"
非整形反復可能オブジェクト
反復可能オブジェクトの @@iterator メソッドが反復子オブジェクトを返さない場合、それは非整形反復可能オブジェクトと見なされます。
これを使用すると、ランタイムエラーやバグの挙動をもたらす可能性があります。
let nonWellFormedIterable = {}
nonWellFormedIterable[Symbol.iterator] = () => 1
[...nonWellFormedIterable] // TypeError: [] is not a function
反復子の例
簡単な反復子
function makeIterator(array) {
let nextIndex = 0
return {
next: function() {
return nextIndex < array.length ?
{value: array[nextIndex++], done: false} :
{done: true};
}
}
}
let it = makeIterator(['yo', 'ya'])
console.log(it.next().value) // 'yo'
console.log(it.next().value) // 'ya'
console.log(it.next().done) // true
無限の反復子
function idMaker() {
let index = 0
return {
next: function(){
return {value: index++, done: false}
}
}
}
let it = idMaker()
console.log(it.next().value) // '0'
console.log(it.next().value) // '1'
console.log(it.next().value) // '2'
// ...
ジェネレーターで
function* makeSimpleGenerator(array) {
let nextIndex = 0
while (nextIndex < array.length) {
yield array[nextIndex++]
}
}
let gen = makeSimpleGenerator(['yo', 'ya'])
console.log(gen.next().value) // 'yo'
console.log(gen.next().value) // 'ya'
console.log(gen.next().done) // true
function* idMaker() {
let index = 0
while (true) {
yield index++
}
}
let gen = idMaker()
console.log(gen.next().value) // '0'
console.log(gen.next().value) // '1'
console.log(gen.next().value) // '2'
// ...
ES2015 クラスで
class SimpleClass {
constructor(data) {
this.data = data
}
[Symbol.iterator]() {
// Use a new index for each iterator. This makes multiple
// iterations over the iterable safe for non-trivial cases,
// such as use of break or nested looping over the same iterable.
let index = 0;
return {
next: () => {
if (index < this.data.length) {
return {value: this.data[index++], done: false}
} else {
return {done: true}
}
}
}
}
}
const simple = new SimpleClass([1,2,3,4,5])
for (const val of simple) {
console.log(val) //'1' '2' '3' '4' '5'
}
ジェネレーターは反復子か反復可能か
ジェネレーターオブジェクト は、反復子でも反復可能でもあります。
let aGeneratorObject = function* () {
yield 1
yield 2
yield 3
}()
typeof aGeneratorObject.next
// "function", because it has a next method, so it's an iterator
typeof aGeneratorObject[Symbol.iterator]
// "function", because it has an @@iterator method, so it's an iterable
aGeneratorObject[Symbol.iterator]() === aGeneratorObject
// true, because its @@iterator method returns itself (an iterator), so it's an well-formed iterable
[...aGeneratorObject]
// [1, 2, 3]
仕様書
| 仕様書 |
|---|
| ECMAScript Latest Draft (ECMA-262) Iteration の定義 |
関連情報
- ES2015 のジェネレーターの詳細については、
function*のドキュメントを参照してください。