Iteratorについて

前回は「Symbol」について説明しました
今回は「Iterator」についてです
Iterator」、「Iterable」の順番で説明します

Iterator

Iteratorとは・・

  • 次の要素(IteratorResult)へ1つずつアクセスする方法を備えたオブジェクト
  • nextメソッドを持っていて、nextメソッドがIteratorResultを返す
  • IteratorResultはvalueプロパティとdoneプロパティを持っているオブジェクト

文章だとよくわかりませんね汗

上記の定義をコードにするとこんな感じになります

var index = 0;
var array = [1, 2, 3];

var iterator = {}; // Iteratorはオブジェクト
iterator.next = function () { // nextメソッドを持っている
    // valueプロパティとdoneプロパティを持っているIteratorResult
    var iteratorResult =  { value: array[index++], done: index > array.length };
    return iteratorResult; // nextメソッドはIteratorResultを返す
};

コードにするとたったこれだけです
Iteratorは次の要素へのアクセスする方法を提供するだけで 実際の繰り返し処理は外部に任せます

IteratorResultの各プロパティですが

  • value: Iteratorから順番に取り出した値
  • done: Iteratorから値を取り出し終えたかどうか

Iterable

Iterableとは・・

  • 反復処理の挙動が定義されたオブジェクト
  • [Symbol.iterator]メソッドを実行するとIteratorを返すオブジェクト

これも文章だとさっぱりですねw
コードで表現してみます

var iterable = {}; // Iterableはオブジェクト
iterable[Symbol.iterator] = function() { // [Symbol.iterator]メソッドを持ってる
    return iterator; // Iteratorを返す
};

前回説明したSymbol.iteratorを使ってますね
それ以外は簡単なコードです

では実際にIteratorを使ってみます

実践

var obj = {};
obj[Symbol.iterator] = function() {
    var index = 0;
    var array = [1, 2, 3];
    var iterator = {};
    iterator.next = function () {
        var iteratorResult =  { value: array[index++], done: index > array.length };
        return iteratorResult;
    };

    return iterator;
};

var iterator = obj[Symbol.iterator]();
console.log(iterator.next()); // Object {value: 1, done: false}
console.log(iterator.next()); // Object {value: 2, done: false}
console.log(iterator.next()); // Object {value: 3, done: false}
console.log(iterator.next()); // Object {value: undefined, done: true}

/* whileを使った場合
var iterator = obj[Symbol.iterator]();
var iteratorResult;
while (true) {
    iteratorResult = iterator.next();
    if (iteratorResult.done) {
        break;
    }
    console.log(iteratorResult.value);
}
*/

上記を実際に実行してみると、順番に値を取り出すことができます
ただ、この書き方だと値の取り出し方が冗長でめんどくさいですね
そこでES2015で用意されたのがfor (var v of iterable)構文です

上記の繰り返し処理はこんな感じで書けます

for (let v of obj) {
    console.log(v);
}

スッキリしましたね
Iterableの定義さえ守っていれば、自作のオブジェクトもfor (var v of iterable)構文に渡せます

内部でwhileを使った場合の処理が行われています

  1. iteratorを取得
  2. nextメソッドを呼び出し、IteratorResultを取得
  3. doneが偽の場合繰り返し、真なら終了する

ビルトインのString、Array、TypedArray、Map、Setなども反復処理が可能なオブジェクトです

for (let v of [0, 1, 2]) console.log(v);
/*
0
1
2
*/

for (let v of 'abc') console.log(v);
/*
a
b
c
*/

まとめ

そして、ジェネレータ関数で作成されるジェネレータがIterableなオブジェクトです
詳しくは次の回

Symbolについて

ES2015(ES6)で追加されたgenerators(yield)
generatorsを使うにはSymbol、Iteratorを理解しておいた方がいいので順番に説明していきます
今回は「Symbol」についてです

概要

ES2015で追加された新しいプリミティブです
数値でも文字列でも真偽値でもない値ですが
文字列のようにオブジェクトのプロパティのキーとして使えます
またObject.keys、Object.getOwnPropertyNamesでも列挙できないという特徴を持っています

作り方

var s = Symbol();
var s = Symbol('foo'); // 引数に文字列を渡すことでSymbolの説明を追加することができる

念のためtypeof演算子で確認するとSymbolであることがわかります

typeof s; // "symbol"

またnew演算子付きで呼ぶとType Errorとなるので注意

var s = new Symbol(); // Type Error

使い方

Symbolはオブジェクトのプロパティのキーにすることができます

var obj = {};
var s = Symbol();
obj[s] = 'hoge';
console.log(obj[s]); // hoge

ちなみに、ES2015のComputed Property Namesを使えば下記のように書けます

var s = Symbol();
var obj = {
    [s]: 'hoge'
};
console.log(obj[s]); // hoge

シンボルは暗黙的に文字列変換されるわけではないので、下記の場合undefinedになります

console.log(obj[s.toString()]); // undefined

特徴

Symbolは毎回異なるシンボルが作成されます

Symbol('foo') === Symbol('foo'); // false

この特徴を活かして、ES2015は既存のコードに影響がでないように
機能を追加できるようにしたようです

例えば、

function test(obj, value) {
    obj._value = value;
}

上記のようなコードの場合、外部から勝手にobjのプロパティを上書きされて全く別のものになったり
キーがシンボルではないので普通に外部から参照できてしまいます

↓このようにすることで、外部からいじられる心配も、参照される心配もありません

var obj = {};
var _value = Symbol();
function test(obj, value) {
    obj[_value] = value;
}
test(obj, 123);

キーがシンボルなので同じキーを外部から作れないためです

ただし、Object.getOwnPropertySymbolsでシンボルを取得できてしまうので
完全に隠蔽できるわけではないです

console.log(obj[Object.getOwnPropertySymbols(obj)[0]]); // 123

登録

シンボルは毎回異なるシンボルが作成されますが
Symbol.forを使ってシンボルを登録すると共有することが可能です

Symbol.for

引数で与えられたキーでランタイム全体のシンボルレジストリ内に存在しているシンボルを検索し、見つかった場合それを返します。さもなければ、新しいシンボルがこのキーでグローバルシンボルレジストリ内に生成されます。

↓こんな感じでシンボルを共有できます

var s1 = Symbol.for('foo');  // ここでは新しく作成される
var s2 = Symbol.for('foo'); // 作成されているシンボルを参照
var s3 = Symbol('foo'); // これはグローバルシンボルレジストリに生成されてないシンボルの作成
s1 === s2; // true
s1 === s3; // false

Well-Known Symbols

ウェルノウンシンボルという特別なシンボルがあります
「特別なシンボル」と書くと、何かすごい機能があるように思えますが、ごく普通のシンボルです
JavaScriptの内部処理で参照されるシンボルです

Well-Known Symbols

このウェルノウンシンボルの中にSymbol.iteratorというシンボルがあります
IteratorはこのSymbol.iteratorを使うことで実現します

Iteratorについての説明は次の回

ブログ開設します

ブログの名前「function」はJavaScriptのfunctionからとってます

JavaScriptのfunctionが

  • 変数に代入できる
  • 関数に渡せる
  • 関数の戻り値にできる
  • 実行時に関数の中身を変えられる

こんな感じでいろいろできることに習って
自分も何でもできるエンジニアになりたいという願望から名付けました

ブログの内容的にはWeb技術全般について書いていこうかなって思ってます