阅读 2872

理解ES6的 Iterator 、Iterable 、 Generator

image

什么是迭代器(Iterator)?

满足迭代器协议的对象。
迭代器协议: 对象的next方法是一个无参函数,它返回一个对象,该对象拥有donevalue两个属性:

  • done(boolean):
    • 如果迭代器已经经过了被迭代序列时为true。这时value可能描述了该迭代器的返回值。
    • 如果迭代器可以产生序列中的下一个值,则为false。这等效于连同done属性也不指定。
  • value: 迭代器返回的任何 JavaScript值。donetrue时可省略。

ES5实现一个简单的迭代器:

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
            };
        }
    };
}

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

console.log(iterator.next());           // "{ value: 1, done: false }"
console.log(iterator.next());           // "{ value: 2, done: false }"
console.log(iterator.next());           // "{ value: 3, done: false }"
console.log(iterator.next());           // "{ value: undefined, done: true }"
// 之后的所有调用
console.log(iterator.next());           // "{ value: undefined, done: true }"复制代码

什么是可迭代对象(Iterable)?

满足可迭代协议的对象是可迭代对象。
可迭代协议: 对象的[Symbol.iterator]值是一个无参函数,该函数返回一个迭代器。

在ES6中,所有的集合对象(ArraySetMap)以及Stringarguments都是可迭代对象,它们都有默认的迭代器。

可迭代对象可以在以下语句中使用:

for (let value of ['a', 'b', 'c']) {
  console.log(value);
}
// "a"
// "b"
// "c"复制代码
[...'abc'];   // ["a", "b", "c"]
console.log(...['a', 'b', 'c']);   // ["a", "b", "c"]复制代码
function* gen() {
  yield* ['a', 'b', 'c'];
}

gen().next(); // { value: "a", done: false }复制代码
let [a, b, c] = new Set(['a', 'b', 'c']);
a;   // 'a'复制代码

理解 for...of 循环

for...of接受一个可迭代对象(Iterable),或者能被强制转换/包装成一个可迭代对象的值(如'abc')。遍历时,for...of会获取可迭代对象的[Symbol.iterator](),对该迭代器逐次调用next(),直到迭代器返回对象的done属性为true时,遍历结束,不对该value处理。

for...of循环实例:

var a = ["a","b","c","d","e"];

for (var val of a) {
    console.log( val );
}
// "a" "b" "c" "d" "e"复制代码

转换成普通for循环示例,等价于上面for...of循环:

var a = ["a","b","c","d","e"];

for (var val, ret, it = a[Symbol.iterator]();
    (ret = it.next()) && !ret.done;
) {
    val = ret.value;
    console.log( val );
}
// "a" "b" "c" "d" "e"复制代码

使迭代器可迭代

什么是迭代器部分,我们自定义了一个简单的生成迭代器的函数createIterator,但并该函数生成的迭代器并没有实现可迭代协议,所以不能在for...of等语法中使用。可以为该对象实现可迭代协议,在[Symbol.iterator]函数中返回该迭代器自身。

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
            };
        },
        [Symbol.iterator]: function () { return this }
    };
}

var iterator = createIterator([1, 2, 3]);
console.log(...iterator)复制代码

什么是生成器(Generator)?

生成器函数

生成器函数(GeneratorFunction)是能返回一个生成器(generator)的函数。生成器函数由放在 function 关键字之后的一个星号( * )来表示,并能使用新的 yield 关键字。

function *aGeneratorfunction(){
  yield 1
  yield 2
  yield 3
};

var aGeneratorObject = aGeneratorfunction()
// 生成器对象
aGeneratorObject.toString()   // "[object Generator]"复制代码

生成器对象既是迭代器,又是可迭代对象

function *aGeneratorfunction(){
  yield 1
  yield 2
  yield 3
};

var aGeneratorObject = aGeneratorfunction()

// 满足迭代器协议,是迭代器
aGeneratorObject.next()   // {value: 1, done: false}
aGeneratorObject.next()   // {value: 2, done: false}
aGeneratorObject.next()   // {value: 3, done: false}
aGeneratorObject.next()   // {value: undefined, done: true}

// [Symbol.iterator]是一个无参函数,该函数执行后返回生成器对象本身(是迭代器),所以是可迭代对象
aGeneratorObject[Symbol.iterator]() === aGeneratorObject   // true

// 可以被迭代
var aGeneratorObject1 = aGeneratorfunction()
[...aGeneratorObject1]   // [1, 2, 3]复制代码

在生成器中return

遍历返回对象的done值为true时迭代即结束,不对该value处理。

function *createIterator() {
  yield 1;
  return 42;
  yield 2;
}

let iterator = createIterator();
iterator.next();   // {value: 1, done: false}
iterator.next();   // {value: 42, done: true}
iterator.next();   // {value: undefined, done: true}复制代码

done值为true时迭代即结束,迭代不对该value处理。所以对这个迭代器遍历,不会对值42处理。

let iterator1 = createIterator();
console.log(...iterator);   // 1复制代码

添加[Symbol.iterator]使Object可迭代

根据可迭代协议,给Object的原型添加[Symbol.iterator],值为返回一个对象的无参函数,被返回对象符合迭代器协议。

Object.prototype[Symbol.iterator] = function () {
  var i = 0
  var items = Object.entries(this)
  return {
    next: function () {
      var done = (i >= items.length);
      var value = !done ? items[i++] : undefined;

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

var a = {
  name: 'Jimmy',
  age: 18,
  job: 'actor'
}

console.log(...a)   // [ 'name', 'Jimmy' ] [ 'age', 18 ] [ 'job', 'actor' ]复制代码

使用生成器简化代码:

Object.prototype[Symbol.iterator] = function* () {
  for (const key in this) {
    if (this.hasOwnProperty(key)) {
      yield [key, this[key]];
    }
  }
}

var a = {
  name: 'Jimmy',
  age: 18,
  job: 'actor'
}

console.log(...a)   // [ 'name', 'Jimmy' ] [ 'age', 18 ] [ 'job', 'actor' ]复制代码

生成器委托 yield*

function* g1() {
  yield 1;
  yield 2;
}

function* g2() {
  yield* g1();
  yield* [3, 4];
  yield* "56";
  yield* arguments;
}

var generator = g2(7, 8);
console.log(...generator);   // 1 2 3 4 "5" "6" 7 8复制代码

最后一个例子

分析下面这段代码:

function* fibs() {
  var a = 0;
  var b = 1;
  while (true) {
    yield a;
    [a, b] = [b, a + b];
  }
}

var [first, second, third, fourth, fifth, sixth] = fibs();
console.log(first, second, third, fourth, fifth, sixth);复制代码

在这段代码里,fibs是一个生成无限长的斐波那契数列的生成器,[a, b] = [b, a + b]是利用解构赋值的交换赋值写法(=赋值是从右到左计算,所以先计算右侧a+b,然后才结构,所有有交换赋值的效果),写成生成有限长的数组的ES5写法如下:

function fibs1(n) {
  var a = 0;
  var b = 1;
  var c = 0;
  var result = []
  for (var i = 0; i < n; i++) {
    result.push(a);
    c = a;
    a = b;
    b = c + b;
  }

  return result;
}

console.log(fibs1(6))   // [0, 1, 1, 2, 3, 5]复制代码

而第一段代码里,就是从fibs()迭代器(生成器是迭代器的子集)中解构出前六个值,代码示例如下:

function* fibs2(n) {
  var a = 0;
  var b = 1;
  for (var i = 0; i < n; i++) {
    yield a;
    [a, b] = [b, a + b];
  }
}

console.log(...fibs2(6))复制代码

为什么要使用迭代器、生成器,有什么好处?

...还没想清楚

以上,有很多个人理解的部分,欢迎纠错(* ̄︶ ̄)

参考: