ES 6シリーズのローズマリーとfor of


起源
標準的なフォーマットの循環コード:
var colors = ["red", "green", "blue"];

for (var i = 0, len = colors.length; i < len; i++) {
    console.log(colors[i]);
}
見たところ簡単ですが、このコードを振り返ると、実際には配列の中の要素の値だけが必要です.しかし、事前に配列長を取得し、インデックス変数などを宣言する必要があります.特に、複数のループがネストされている場合、複数のインデックス変数を使用する必要があります.コードの複雑さは大幅に増加します.
function unique(array) {
    var res = [];
    for (var i = 0, arrayLen = array.length; i < arrayLen; i++) {
        for (var j = 0, resLen = res.length; j < resLen; j++) {
            if (array[i] === res[j]) {
                break;
            }
        }
        if (j === resLen) {
            res.push(array[i]);
        }
    }
    return res;
}
この複雑さを解消し、循環中のエラーを低減するために、ES 6は、他のサイクル中の変数を誤って使用するなど、ディエ代数器とfor ofサイクルの共同解決を提供する.
サンデー
重ね合わせとは、next()メソッドを持つオブジェクトのことで、next()を呼び出すたびに結果オブジェクトが返されます.この結果、オブジェクトは2つの属性があり、valueは現在の値を表し、doneは巡回が終わるかどうかを表します.
私たちは直接ES 5の文法を使ってディエゼルを作成します.
function createIterator(items) {
    var i = 0;
    return {
        next: function() {
            var done = i >= item.length;
            var value = !done ? items[i++] : undefined;

            return {
                done: done,
                value: value
            };
        }
    };
}

// iterator          
var iterator = createIterator([1, 2, 3]);

console.log(iterator.next()); // { done: false, value: 1 }
console.log(iterator.next()); // { done: false, value: 2 }
console.log(iterator.next()); // { done: false, value: 3 }
console.log(iterator.next()); // { done: true, value: undefined }
for of
ローズマリー以外に、私達はもう一つの方法が必要です.ES 6はfor ofステートメントを提供しています.私達は直接for ofを使って、前節で生成したエルゴードオブジェクトを巡回してみます.
var iterator = createIterator([1, 2, 3]);

for (let value of iterator) {
    console.log(value);
}
結果エラーTypeError: iterator is not iterable私たちが生成したiteratorはiterableではないことを示しています.
では何が遍歴できますか?
実際には、データ構造はIteratorインターフェースを展開する限り、私たちはこのようなデータ構造を「エルゴード」と呼びます.
ES 6は、デフォルトのIteratorインターフェースがデータ構造のSymbol.iterator属性に配置されていると規定しています.あるいは、一つのデータ構造はSymbol.iterator属性を持つ限り、「巡回可能」と考えられます.
例を挙げます
const obj = {
    value: 1
};

for (value of obj) {
    console.log(value);
}

// TypeError: iterator is not iterable
私たちは直接for ofがオブジェクトを遍歴しているので、エラーを報告しますが、もし私たちがオブジェクトにSymbol.iteratorの属性を追加すると:
const obj = {
    value: 1
};

obj[Symbol.iterator] = function() {
    return createIterator([1, 2, 3]);
};

for (value of obj) {
    console.log(value);
}

// 1
// 2
// 3
これにより、for ofエルゴードはオブジェクトのSymbol.iterator属性であることがわかった.
デフォルトではオブジェクトを巡回できます.
しかし、私たちが直接的に配列オブジェクトを遍歴する場合:
const colors = ["red", "green", "blue"];

for (let color of colors) {
    console.log(color);
}

// red
// green
// blue
私たちはSymbol.iterator属性を手動で追加していませんが、やはりプロセスを通して成功できます.これはES 6がデフォルトでSymbol.iterator属性を配置したからです.もちろん、私たちも手動でこの属性を修正できます.
var colors = ["red", "green", "blue"];

colors[Symbol.iterator] = function() {
    return createIterator([1, 2, 3]);
};

for (let color of colors) {
    console.log(color);
}

// 1
// 2
// 3
配列の他に、Symbol.iterator属性がデフォルトで展開されているデータ構造があります.
for...ofサイクルが使用できる範囲は以下の通りです.
  • 配列
  • Set
  • Map
  • クラス配列オブジェクト、例えば、argmentsオブジェクト、DOM NodeListオブジェクト
  • Generator対象
  • 文字列
  • シミュレーション実行for of
    実はシミュレーションでfor ofを実現するのも比較的簡単です.基本的にSymbol.iterator属性を通じてサブエージェントのオブジェクトを取得し、whileを使って巡回します.
    function forOf(obj, cb) {
        let iterable, result;
    
        if (typeof obj[Symbol.iterator] !== "function")
            throw new TypeError(result + " is not iterable");
        if (typeof cb !== "function") throw new TypeError("cb must be callable");
    
        iterable = obj[Symbol.iterator]();
    
        result = iterable.next();
        while (!result.done) {
            cb(result.value);
            result = iterable.next();
        }
    }
    ローズマリー
    より良い訪問対象の内容のためには、例えば、ある時は配列の中の値だけが必要ですが、ある時は使用価値だけでなくインデックスも必要です.ES 6は配列、Map、Setのセット内に以下の3つのローズマリーを構築しました.
  • entries()はエルゴードオブジェクトを返し、「キー名、キーパッド値」からなる配列を遍歴するために使用します.配列に対して、キーの名前は索引の値です.
  • keys()は、エルゴードオブジェクトを返して、すべてのキーを巡る.
  • values()は、全てのキーを遍歴するためのエルゴードオブジェクトを返します.
  • 配列を例にとる:
    var colors = ["red", "green", "blue"];
    
    for (let index of colors.keys()) {
        console.log(index);
    }
    
    // 0
    // 1
    // 2
    
    for (let color of colors.values()) {
        console.log(color);
    }
    
    // red
    // green
    // blue
    
    for (let item of colors.entries()) {
        console.log(item);
    }
    
    // [ 0, "red" ]
    // [ 1, "green" ]
    // [ 2, "blue" ]
    Mapタイプは配列と似ていますが、Setタイプについては以下のように注意が必要です.
    var colors = new Set(["red", "green", "blue"]);
    
    for (let index of colors.keys()) {
        console.log(index);
    }
    
    // red
    // green
    // blue
    
    for (let color of colors.values()) {
        console.log(color);
    }
    
    // red
    // green
    // blue
    
    for (let item of colors.entries()) {
        console.log(item);
    }
    
    // [ "red", "red" ]
    // [ "green", "green" ]
    // [ "blue", "blue" ]
    Setタイプのkeys()とvalues()は同じローズマリーを返します.これはSetというデータ構造の中でキーとキーの値が同じであることを意味します.
    また、各セットのタイプにはデフォルトのディケンサがあります.for-ofループでは、明示的な指定がない場合はデフォルトのディケンサが使用されます.配列とセットのデフォルトのディショナーはvalues()メソッドであり、Mapセットのデフォルトのディショナーはentries()メソッドである.
    これは、なぜ直接for ofがSetとMapデータ構造を遍歴して、異なるデータ構造が戻ってくるのかを示しています.
    const values = new Set([1, 2, 3]);
    
    for (let value of values) {
        console.log(value);
    }
    
    // 1
    // 2
    // 3
    const values = new Map([["key1", "value1"], ["key2", "value2"]]);
    for (let value of values) {
        console.log(value);
    }
    
    // ["key1", "value1"]
    // ["key2", "value2"]
    Mapデータ構造を遍歴した場合は、解構成値を結合することができます.
    const valuess = new Map([["key1", "value1"], ["key2", "value2"]]);
    
    for (let [key, value] of valuess) {
        console.log(key + ":" + value);
    }
    
    // key1:value1
    // key2:value2
    Babelはどのようにfor ofをコンパイルしますか?
    BabelのTry it outでコンパイルの結果を確認できます.
    const colors = new Set(["red", "green", "blue"]);
    
    for (let color of colors) {
        console.log(color);
    }
    このようなセグメントコードの場合、コンパイルの結果は以下の通りです.
    "use strict";
    
    var colors = new Set(["red", "green", "blue"]);
    
    var _iteratorNormalCompletion = true;
    var _didIteratorError = false;
    var _iteratorError = undefined;
    
    try {
        for (
            var _iterator = colors[Symbol.iterator](), _step;
            !(_iteratorNormalCompletion = (_step = _iterator.next()).done);
            _iteratorNormalCompletion = true
        ) {
            var color = _step.value;
    
            console.log(color);
        }
    } catch (err) {
        _didIteratorError = true;
        _iteratorError = err;
    } finally {
        try {
            if (!_iteratorNormalCompletion && _iterator.return) {
                _iterator.return();
            }
        } finally {
            if (_didIteratorError) {
                throw _iteratorError;
            }
        }
    }
    少なくともコンパイルの結果からは、使用for ofサイクルの背後には、Symbol.iteratorインターフェースがあります.
    このコンパイルのコードはちょっと複雑なところが二つあります.一段はfor循環ここです.
    for (
        var _iterator = colors[Symbol.iterator](), _step;
        !(_iteratorNormalCompletion = (_step = _iterator.next()).done);
        _iteratorNormalCompletion = true
    ) {
        var color = _step.value;
        console.log(color);
    }
    標準のfor循環の書き方とは少し違っています.for文の文法を見てみます.
    for (initialize; test; increment) statement;
    initialize、test、incrementの3つの表式は、それぞれ番号で分割され、それぞれ および を担当しています.
    for文は実際には次のようなものです.
    initialize;
    while (test) {
        statement;
        increment;
    }
    コードの論理は、まず初期化を行い、それから循環実行の前にtest表現を実行し、その結果、循環体を実行するかどうかを判断します.test計算結果が真正値であれば、循環体のstatementを実行します.最後にincrement式を実行します.
    なお、forサイクルの3つの表式のいずれも無視できますが、セミコロンはまだ書きます.
    例えばfor(;;)・でもこれは死の循環です.
    たとえば:
    var i = 0,
        len = colors.length;
    for (; i < len; i++) {
        console.log(colors[i]);
    }
    たとえば、
    var i = 0,
        len = colors.length;
    for (; i < len; ) {
        i++;
    }
    その後、Babelコンパイルのこのfor循環式を見に来ました.
    for (
        var _iterator = colors[Symbol.iterator](), _step;
        !(_iteratorNormalCompletion = (_step = _iterator.next()).done);
        _iteratorNormalCompletion = true
    ) {
        var color = _step.value;
        console.log(color);
    }
    whileで書くのは、
    var _iterator = colors[Symbol.iterator](),
        _step;
    while (!(_iteratorNormalCompletion = (_step = _iterator.next()).done)) {
        var color = _step.value;
        console.log(color);
        _iteratorNormalCompletion = true;
    }
    多くのことが分かりやすくなったのではないかと思いますが、実際には_iteratorNormalCompletion = trueこの文は全く必要ないです.
    もう一つの段落のちょっと複雑なコードは:
    try {
      ...
    } catch (err) {
      ...
    } finally {
      try {
        if (!_iteratorNormalCompletion && _iterator.return) {
          _iterator.return();
        }
      } finally {
        ...
      }
    }
    _iteratorNormalCompletion = (_step = _iterator.next()).doneので、_iteratoorNormalComplotionとは、完全な反復プロセスが完了しているかどうか、正常な反復が完了していない場合、そして、ディケンサがreturn方法がある場合、この方法を実行します.
    なぜこのようにするのかというと、ローズマリーのreturn方法について話します.
    阮一峰先生のECMAScript 6入門を引用する:
    エルゴードオブジェクトは、nextメソッドの他に、return方法とthrow方法があります.もしあなたが自分でエルゴードオブジェクトを書いて関数を生成するなら、nextメソッドは展開しなければならず、return方法とthrow方法は展開するかどうかを選択します.
    returnメソッドの使用の場合は、for...ofループが早期に終了すると(通常はエラーのため、またはbreak文やcontinue文があります)、returnメソッドが起動されます.巡回が完了する前に、リソースを整理または解放する必要がある場合は、return方法を展開することができます.
    例を挙げてもいいです.
    function createIterator(items) {
        var i = 0;
        return {
            next: function() {
                var done = i >= items.length;
                var value = !done ? items[i++] : undefined;
    
                return {
                    done: done,
                    value: value
                };
            },
            return: function() {
                console.log("    return   ");
                return {
                    value: 23333,
                    done: true
                };
            }
        };
    }
    
    var colors = ["red", "green", "blue"];
    
    var iterator = createIterator([1, 2, 3]);
    
    colors[Symbol.iterator] = function() {
        return iterator;
    };
    
    for (let color of colors) {
        if (color == 1) break;
        console.log(color);
    }
    //     return   
    でも、コンパイルしたコードの中で見たように、return関数がある時にreturn関数を実行しただけです.return関数で返した値は実際には有効ではありません.
    しかし、値を返さないか、基本タイプの値を返すと、またエラーが発生します.
    TypeError: Iterator result undefined is not an object
    これはreturn方法が一つの対象に戻らなければならないからです.これはまたGenerator仕様が決定したからです.
    つまりブラウザで使うなら、return関数の戻り値は実は有効ではありません.T^T
    ES 6シリーズ
    ES 6シリーズのディレクトリアドレス:https://github.com/mqyqingfeng/Blog
    ES 6シリーズは二十編ぐらい書く予定です.ES 6部分の知識点の理解を深めるために、ブロックレベルの作用領域、ラベルテンプレート、矢印関数、Symbol、Set、Map及びPromiseのシミュレーション実現、モジュールロード方案、非同期処理などの内容を重点的に説明します.
    間違いや不備があったら、ぜひ指摘してください.ありがとうございます.好きだったり、何かを啓発したりすれば、starを歓迎し、作者に対しても励みになります.