Cho-Ching's Blog

[ES6] ECMAScript 6 筆記(二) -- iteration, generator

參考Babel的learn-es2015MDN- iteration protocols的解釋。

ES6定義iteratable protocoliterator protocol 兩個協定。

The iterable protocol

iterable protocol 允許 JavaScript物件定義或是客製化他們自己的迭代行為(iteration behavior)。例如使用for..of結構來loop所有的值。

ArrayMap已經有預設的iteration behavior, 有的沒有(例如Object)

為了要可以iterable, 物件必須要實作 iterator method, 就是說該物件要有一個屬性有Symbol.iterator key, 其值為一個不帶參數的函式, 這個函式傳回一個符合 iterator protocol的物件。

當這個物件需要被遍歷(iteratored), 例如使用for..of 迴圈, 其 iterator method就會被呼叫傳回iterator, 我們就可以利用這個iterator來loop取值。

The iterator protocol

iterator protocol 定義一個標準的方法來產生一個值的序列(無限或有限)。

一個物件只要實作了next() method, 就被當作是一個iterator。next()是一個無參數的函式, 傳回帶有兩個以下兩個屬性的物件:

Example

例如String就有內建的iterable物件了, 預設的iteraor會一個一個傳回字串的字元:

'use strict';
var s = 'hi';
var it = s[Symbol.iterator]();
console.log(it + '');  // [object String Iterator]
console.log(it.next()); //  { value: "h", done: false }
console.log(it.next()); //  { value: "i", done: false }
console.log(it.next()); //   { value: undefined, done: true }

定義我們自己的iterator:

'use strict';
function makeIterator(array){
  var nextIndex = 0;
  return {
    next: function(){
      return nextIndex < array.length?
        {value: array[nextIndex++], done: false}:
        {done: true};
    }
  };
}
var it = makeIterator(['yo', 'ya']);
console.log(it.next().value); // yo
console.log(it.next().value) // ya;
console.log(it.next().done); // true

for...of

for...offor...in loop的差別在於, for...in是遍歷(iterate)屬性名(property names), 那for...of是遍歷屬性值(property values):

let arr = [3, 5, 7];
arr.foo = "hello";

for (let i in arr) {
  console.log(i); // logs "0", "1", "2", "foo"
}

for (let i of arr) {
  console.log(i); // logs "3", "5", "7"
}

babel的範例:

let fibonacci = {
  [Symbol.iterator]() {
    let pre = 0, cur = 1;
    return {
      next() {
        [pre, cur] = [cur, pre + cur];
        return { done: false, value: cur }
      }
    }
  }
}

for (var n of fibonacci) {
  // truncate the sequence at 1000
  if (n > 1000)
    break;
  console.log(n);
}

generator function + yield

function宣告前面加上*號, 就表示我們定義了一個genrator function. 這個函式會傳回一個Generator物件。

function * name([param[, param[, ... param]]]) {
  //statements
}

Generators are functions which can be exited and later re-entered. Their context (variable bindings) will be saved across re-entrances.

呼叫generator function不會立刻執行,會先回傳一個iterator物件, 當iterator物件的next() method被呼叫, 這個函式的主體就會開始執行,直到遇到第一個yield運算式為止。

yield 會從iterator指定一個值回傳, 或是連同yield*發送值給其他的generator function。

接下來next() method傳回一個物件, 這個物件帶有一個包含被送出(yielded)的值的屬性,以及一個done屬性指出是否這個generator已經發送出其最新的值。

例如:

function* idMaker(){
  var index = 0;
  while(index < 3)
    yield index++;
}

var gen = idMaker();

console.log(gen.next()); // { value: 0, done: false }
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: undfined, done: true }

使用 yield* 呼叫其他的generator function:

function* anotherGenerator(i) {
  yield i + 1;
  yield i + 2;
  yield i + 3;
}

function* generator(i){
  yield i;
  yield* anotherGenerator(i);
  yield i + 10;
}

var gen = generator(10);

console.log(gen.next().value); // 10
console.log(gen.next().value); // 11
console.log(gen.next().value); // 12
console.log(gen.next().value); // 13
console.log(gen.next().value); // 20

babel範例, 改寫:

'use strict';
require("babel/polyfill");
var fibonacci = {
  [Symbol.iterator]: function*() {
    var pre = 0, cur = 1;
    for (;;) {
      var temp = pre;
      pre = cur;
      cur += temp;
      yield cur;
    }
  }
}                                                                                                                                       
for (var n of fibonacci) {
  // truncate the sequence at 1000                                                                                                      
  if (n > 1000)
    break;
  console.log(n);
}

More

iteration Iteration_protocols -- MDN

for-of -- MDN

function * -- MDN

<< 回到文章列表