ES 6-Iterator

15337 ワード

Iterator
紹介する
元の表示セットのデータ構造は、主に配列とオブジェクト、ES 6にMapとSetが追加されています.Iteratorは、異なるデータ構造を処理するための統一されたインターフェース機構です.
  • Iteratorは、様々な異なるデータ構造のための統一アクセス機構
  • を提供するインターフェースである.
  • 任意のデータ構造は、Iteratorインターフェースを配置する限り、このようなデータ構造は巡回可能な
  • であるという.
  • ES 6は、デフォルトのIteratorインターフェースが、データ構造のSymbol.iterator属性に配置されている
  • と規定している.
     //    Symbol.iterator  
     let arr = ['a', 'b', 'c'];
     let iter = arr[Symbol.iterator]();
    
     iter.next() // { value: 'a', done: false }
     iter.next() // { value: 'b', done: false }
     iter.next() // { value: 'c', done: false }
     iter.next() // { value: undefined, done: true }
    
    作用
  • は、様々なデータ構造のための統一的で簡便なアクセスインターフェース
  • を提供する.
  • は、データ構造のメンバがある順序で並べられるようにする.
  • ES 6は、新しいエルゴード命令for...ofサイクルを作成しました.Iteratorインターフェースは、for...of消費量
  • を主に提供します.
    プロセスを経る
  • は、現在のデータ構造の開始位置を指すポインタオブジェクトを作成する.すなわち、エルゴードオブジェクトは、本質的には、ポインタオブジェクト
  • である.
  • ポインタオブジェクトのnextメソッドを初めて呼び出し、ポインタをデータ構造の最初のメンバ
  • に向けることができる.
  • ポインタオブジェクトのnextメソッドを第2回呼び出すと、ポインタはデータ構造の第2のメンバ
  • を指す.
  • ポインタオブジェクトnext方法を継続的に呼び出し、彼がデータ構造の終了位置
  • を指すまで.
    //   next     
    var it = makeIterator(['a', 'b']);
    
    it.next() // { value: "a", done: false }
    it.next() // { value: "b", done: false }
    it.next() // { value: undefined, done: true }
    
    function makeIterator(array) {
      var nextIndex = 0;
      return {
        next: function() {
          return nextIndex < array.length ?
            {value: array[nextIndex++], done: false} :
            {value: undefined, done: true};
        }
      };
    }
    原生はIteratorインターフェースのデータ構造を備えている:
  • Aray
  • Map
  • セット
  • TypedAray
  • 関数のargmentsオブジェクト
  • NodeListオブジェクト
  • オブジェクト(Object)がデフォルトで展開されていない理由は、オブジェクトのどの属性が先に巡回されているか、どの属性が巡回されているかは不明で、開発者が手動で指定する必要があるからです.実際には、エルゴードは線形処理であり、任意の非線形データ構造に対してエルゴードインターフェースを配置することは、線形変換の展開に等しい.
    オブジェクトがfor...of循環呼び出しのためのIteratorインターフェースを備えるには、Symbol.iteratorの属性にエルゴーバー生成方法を配置しなければならない.
    //        Iterator      。Symbol.iterator        ,               
    class RangeIterator {
      constructor(start, stop) {
        this.value = start;
        this.stop = stop;
      }
    
      [Symbol.iterator]() { return this; }
    
      next() {
        var value = this.value;
        if (value < this.stop) {
          this.value++;
          return {done: false, value: value};
        }
        return {done: true, value: undefined};
      }
    }
    
    function range(start, stop) {
      return new RangeIterator(start, stop);
    }
    
    for (var value of range(0, 3)) {
      console.log(value); // 0, 1, 2
    }
    //            
    //               Symbol.iterator  ,             iterator,      next  ,         ,              
    function Obj(value) {
      this.value = value;
      this.next = null;
    }
    
    Obj.prototype[Symbol.iterator] = function() {
      var iterator = { next: next };
    
      var current = this;
    
      function next() {
        if (current) {
          var value = current.value;
          current = current.next;
          return { done: false, value: value };
        } else {
          return { done: true };
        }
      }
      return iterator;
    }
    
    var one = new Obj(1);
    var two = new Obj(2);
    var three = new Obj(3);
    
    one.next = two;
    two.next = three;
    
    for (var i of one){
      console.log(i); // 1, 2, 3
    }
    //      Iterator    
    let obj = {
      data: [ 'hello', 'world' ],
      [Symbol.iterator]() {
        const self = this;
        let index = 0;
        return {
          next() {
            if (index < self.data.length) {
              return {
                value: self.data[index++],
                done: false
              };
            } else {
              return { value: undefined, done: true };
            }
          }
        };
      }
    };
    配列のようなオブジェクト(数値キーとlength属性が存在する)に対しては、Iteratorインターフェースを配置し、Symbol.iteratorメソッドが直接的に配列のIteratorインターフェースを参照するという簡便な方法があります.
    // NodeList           ,         ,      
    NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];
    //   
    NodeList.prototype[Symbol.iterator] = [][Symbol.iterator];
    
    [...document.querySelectorAll('div')] //      
    
    //                   Symbol.iterator     
    let iterable = {
      0: 'a',
      1: 'b',
      2: 'c',
      length: 3,
      [Symbol.iterator]: Array.prototype[Symbol.iterator]
    };
    for (let item of iterable) {
      console.log(item); // 'a', 'b', 'c'
    }
    注:一般的なオブジェクト展開配列のSymbol.iteratorメソッドは、効果がありません.
    let iterable = {
      a: 'a',
      b: 'b',
      c: 'c',
      length: 3,
      [Symbol.iterator]: Array.prototype[Symbol.iterator]
    };
    for (let item of iterable) {
      console.log(item); // undefined, undefined, undefined
    }
    
    Symbol.iteratorメソッドに対応するのがエルゴード生成関数ではない場合、エンジンはエラーとなります.
    var obj = {};
    obj[Symbol.iterator] = () => 1;
    [...obj] // TypeError: [] is not a function
    Iteratorインターフェースを呼び出す場合
  • 構造の割当値:配列とSet構造の値付けを行うと、デフォルトで
  • が呼び出されます.
    Symbol.iterator  
    let set = new Set().add('a').add('b').add('c');
    
    let [x,y] = set;
    // x='a'; y='b'
    
    let [first, ...rest] = set;
    // first='a'; rest=['b','c'];
  • 拡張演算子:あるデータ構造がIteratorインターフェースを展開する限り、拡張演算子を用いて行列
  • に変換することができます.
     //    ,          ,         Iterator        ,    。    ,            Iterator   ,            ,      
     //   
     var str = 'hello';
     [...str] //  ['h','e','l','l','o']
    
     //   
     let arr = ['b', 'c'];
     ['a', ...arr, 'd']
     // ['a', 'b', 'c', 'd']
  • yield:yieldの後に付いているのはエルゴード構造で、インターフェースのエルゴードインターフェース
  • を呼び出すことができます.
    let generator = function* () {
        yield 1;
        yield* [2,3,4];
        yield 5;
    };
    
    var iterator = generator();
    
    iterator.next() // { value: 1, done: false }
    iterator.next() // { value: 2, done: false }
    iterator.next() // { value: 3, done: false }
    iterator.next() // { value: 4, done: false }
    iterator.next() // { value: 5, done: false }
    iterator.next() // { value: undefined, done: true }
  • 他の場合:配列のエルゴードはエルゴードインターフェースを呼び出すので、任意の受け入れ配列がパラメータとして使用される場合、実はエルゴードインターフェースを呼び出しました.
  • for…of
  • Aray.from()
  • Map(),Set(),WeakMap(),WeakSet()
  • Promise.all()
  • Promise.race()
  • 文字列のIteratorインターフェース
     //              ,     Iterator  
     var someString = "hi";
     typeof someString[Symbol.iterator]
     // "function"
    
     var iterator = someString[Symbol.iterator]();
    
     iterator.next()  // { value: "h", done: false }
     iterator.next()  // { value: "i", done: false }
     iterator.next()  // { value: undefined, done:true }
     //        Symbol.iterator  ,            
     var str = new String("hi");
    
    [...str] // ["h", "i"]
    
    str[Symbol.iterator] = function() {
      return {
        next: function() {
          if (this._first) {
            this._first = false;
            return { value: "bye", done: false };
          } else {
            return { done: true };
          }
        },
        _first: true
      };
    };
    //     str  Symbol.iterator      ,       (...)       bye,        hi
    [...str] // ["bye"]
    str // "hi"
    IteratorインターフェースとGenerator関数
    Symbol.iterator方法の一番簡単な実現ですか?それともGenerator関数を使いますか?
    // Symbol.iterator            ,    yield              
    let myIterable = {
      [Symbol.iterator]: function* () {
        yield 1;
        yield 2;
        yield 3;
      }
    }
    [...myIterable] // [1, 2, 3]
    
    //            
    
    let obj = {
      * [Symbol.iterator]() {
        yield 'hello';
        yield 'world';
      }
    };
    
    for (let x of obj) {
      console.log(x);
    }
    // "hello"
    // "world"
    エルゴードオブジェクトのreturn()、throw()
    エルゴードオブジェクトは、nextメソッドの他に、return方法とthrow方法があります.もしあなたが自分でエルゴードオブジェクトを書いて関数を生成するなら、nextメソッドは展開しなければならず、return方法とthrow方法は展開するかどうかを選択します.
    returnメソッドの使用の場合は、for...ofループが早期に終了すると(通常はエラーやbreak文があります)、returnメソッドが呼び出されます.巡回が完了する前に、リソースを整理または解放する必要がある場合は、return方法を展開することができます.
    //   readLinesSync            ,         ,    next  ,    return  
    function readLinesSync(file) {
      return {
        [Symbol.iterator]() {
          return {
            next() {
              return { done: false };
            },
            return() {
              file.close();
              return { done: true };
            }
          };
        },
      };
    }
    //        ,      return  
    //           ,    return  ,      
    for (let line of readLinesSync(fileName)) {
      console.log(line);
      break;
    }
    
    //     return        ,     
    for (let line of readLinesSync(fileName)) {
      console.log(line);
      throw new Error();
    }
    注:return方法は一つのオブジェクトに返さなければなりません.これはGenerator規格によって決められたthrow方法です.主にGenerator関数に合わせて使用されます.一般的なエルゴードオブジェクトはこの方法が使えません.
    for...ofサイクル
  • は、循環配列およびクラス配列のデータ構造
  • を有してもよい.
  • for...of循環内部で呼び出したのは、データ構造のSymbol.iterator方法
  • である.
  • サイクルはキーを取得することを許可します.
  •  var arr = ['a', 'b', 'c', 'd'];
    
     for (let a in arr) {
       console.log(a); // 0 1 2 3
     }
    
     for (let a of arr) {
       console.log(a); // a b c d
     }
  • 配列のエルゴードインターフェースは、デジタルインデックスがある属性
  • だけを返します.
    行列
    配列元はiteratorインターフェースを備えています.すなわちSymbol.iterator属性がデフォルトで配備されています.for...ofサイクルは本質的にこのインターフェースを呼び出して生成されるエルゴードです.
    //    obj     arr Symbol.iterator  ,  obj for...of  ,    arr       
    const arr = ['red', 'green', 'blue'];
    
    for(let v of arr) {
      console.log(v); // red green blue
    }
    
    const obj = {};
    obj[Symbol.iterator] = arr[Symbol.iterator].bind(arr);
    
    for(let v of obj) {
      console.log(v); // red green blue
    }
    for...ofサイクルは、配列例のforEach方法に代わることができます.
    const arr = ['red', 'green', 'blue'];
    arr.forEach(function (element, index) {
      console.log(element); // red green blue
      console.log(index);   // 0 1 2
    });
    JavaScriptの元のfor…inサイクルは、対象のキー名しか獲得できません.直接キーの値を取得することはできません.
    var arr = ['a', 'b', 'c', 'd'];
    
    for (let a in arr) {
      console.log(a); // 0 1 2 3
    }
    
    for (let a of arr) {
      console.log(a); // a b c d
    }
    注:for...ofループは、エルゴードインターフェースを呼び出します.配列のエルゴードインターフェースは、数値索引の属性だけを返します.
    let arr = [3, 5, 7];
    arr.foo = 'hello';
    
    for (let i in arr) {
      console.log(i); // "0", "1", "2", "foo"
    }
    
    for (let i of arr) {
      console.log(i); //  "3", "5", "7"
    }
    for...ofの利点:
  • は同じfor...inのように簡潔な文法を持っていますが、for...inの欠点はありません.
  • はforEach方法と違って、break、continue、returnと一緒に
  • を使うことができます.
  • は、エルゴードのすべてのデータ構造統一動作インターフェース
  • を提供する.
    for…inの欠点
  • 配列のキー名は数字ですが、for...inループは文字列をキーとして0'で、'1'
  • です.
  • for…inサイクルは、数字キーだけでなく、手動で追加された他のキーを巡回し、さらにプロトタイプチェーン上のキー
  • を含む.
  • 場合によっては、for...inサイクルはキー名
  • を任意の順序で巡回します.
    セットとMap構造
    SetとMap構造も元々はIteratorインターフェースを持っています.そのままfor...ofサイクルを使うことができます.
    var engines = new Set(["Gecko", "Trident", "Webkit", "Webkit"]);
    for (var e of engines) {
      console.log(e);
    }
    // Gecko
    // Trident
    // Webkit
    
    var es6 = new Map();
    es6.set("edition", 6);
    es6.set("committee", "TC39");
    es6.set("standard", "ECMA-262");
    for (var [name, value] of es6) {
      console.log(name + ": " + value);
    }
    // edition: 6
    // committee: TC39
    // standard: ECMA-262
    注:
  • エルゴードの順序は、各メンバがデータ構造に追加される順序である
  • .
  • Setは時間を通して構成されています.戻りは1つの値です.一方、Mapは時間をかけて構成されています.戻りは1つの配列です.この配列の2つのメンバーはそれぞれ現在のMapメンバーのキー名とキー値
  • です.
    let map = new Map().set('a', 1).set('b', 2);
    for (let pair of map) {
      console.log(pair);
    }
    // ['a', 1]
    // ['b', 2]
    
    for (let [key, value] of map) {
      console.log(key + ' : ' + value);
    }
    // a : 1
    // b : 2
    生成したデータ構造を計算します.
    いくつかのデータ構造は既存のデータ構造に基づいて計算されて生成される.例えば、ES 6の配列、Set、Mapは、サーキュラータオブジェクトに戻ります.
  • entries()はエルゴードオブジェクトを返し、「キー名、キーパッド値」からなる配列を遍歴するために使用されます.キーパッドの名前はインデックス値です.セットに対して、キーの名前とキーの値は同じです.Map構造のIteratorインターフェースは、デフォルトではentriesメソッド
  • を呼び出します.
  • keys()は、すべてのキーを巡回するためにエルゴードオブジェクトを返します.
  • values()は、全てのキー値
  • を巡回するためのエルゴードオブジェクトを返します.
    これらの3つの方法は起動後に生成されたエルゴードオブジェクトで、エルゴードはすべて計算されて生成されたデータ構造です.
    let arr = ['a', 'b', 'c'];
    for (let pair of arr.entries()) {
      console.log(pair);
    }
    // [0, 'a']
    // [1, 'b']
    // [2, 'c']
    類似配列のオブジェクト
    同様の配列のオブジェクトにはいくつかの種類があります.
    
    //    
    let str = "hello";
    
    for (let s of str) {
      console.log(s); // h e l l o
    }
    
    // DOM NodeList  
    let paras = document.querySelectorAll("p");
    
    for (let p of paras) {
      p.classList.add("test");
    }
    
    // arguments  
    function printArgs() {
      for (let x of arguments) {
        console.log(x);
      }
    }
    printArgs('a', 'b');
    // 'a'
    // 'b'
    
    文字列では、for...ofループは32ビットのUTF-16文字を正しく認識するという特徴があります.
    for (let x of 'a\uD83D\uDC0A') {
      console.log(x);
    }
    // 'a'
    // '\uD83D\uDC0A'
    すべての類似配列のオブジェクトがIteratorインターフェースを持っているわけではなく、簡単な解決方法として、Aray.from法を使って配列に変換することです.
    let arrayLike = { length: 2, 0: 'a', 1: 'b' };
    
    //   
    for (let x of arrayLike) {
      console.log(x);
    }
    
    //   
    for (let x of Array.from(arrayLike)) {
      console.log(x);
    }
    オブジェクト
    普通のオブジェクトに対しては、for...of構造は直接使用できません.エラーが発生します.Iteratorインターフェースを配置してから使用しなければなりません.ただし、for...inサイクルは依然としてキーの名前を遍歴するために使用できます.
    let es6 = {
      edition: 6,
      committee: "TC39",
      standard: "ECMA-262"
    };
    
    for (let e in es6) {
      console.log(e);
    }
    // edition
    // committee
    // standard
    
    for (let e of es6) {
      console.log(e);
    }
    // TypeError: es6[Symbol.iterator] is not a function
    一つの解決方法は、オブジェクトのキー名をObject.keys法を使って配列を作成し、この配列を巡回することです.
    for (var key of Object.keys(someObject)) {
      console.log(key + ': ' + someObject[key]);
    }
    もう一つの方法はGenerator関数を使ってオブジェクトを再包装することです.
    function* entries(obj) {
      for (let key of Object.keys(obj)) {
        yield [key, obj[key]];
      }
    }
    
    for (let [key, value] of entries(obj)) {
      console.log(key, '->', value);
    }
    // a -> 1
    // b -> 2
    // c -> 3
    他のエルゴード文法との比較
    配列を例にとって、JavaScriptは様々な巡回文法を提供します.最初の書き方はforサイクルです.
    //         
    for (var index = 0; index < myArray.length; index++) {
      console.log(myArray[index]);
    }
    したがって、配列は内蔵のforEach方法を提供します.
    //          ,      forEach  ,break   return       
    myArray.forEach(function (value) {
      console.log(value);
    });
    for...inサイクルは配列のキー名を遍歴することができます.
    for (var index in myArray) {
      console.log(myArray[index]);
    }
    for...inサイクルのいくつかの欠点:
  • 配列のキー名は数字ですが、for...inループは文字列をキーとして0'で、'1'
  • です.
  • for…inサイクルは、数字キーだけでなく、手動で追加された他のキーを巡回し、さらにプロトタイプチェーン上のキー
  • を含む.
  • 場合によっては、for...inサイクルはキー名
  • を任意の順序で巡回します.
    つまり、for…inサイクルは主にオブジェクトを遍歴するために設計されています.遍歴配列には適用されません.
    for…ofサイクルは上記のいくつかのやり方に比べて、いくつかの著しい利点があります.
    for (let value of myArray) {
      console.log(value);
    }
  • は同じfor...inのように簡潔な文法を持っていますが、for...inの欠点はありません.
  • はforEach方法と違って、break、continue、returnと一緒に
  • を使うことができます.
  • は、エルゴードのすべてのデータ構造統一動作インターフェース
  • を提供する.
    //    break   ,  for...of  
    //              1000   。        1000,    break    for...of  
    for (var n of fibonacci) {
      if (n > 1000)
        break;
      console.log(n);
    }