ES6 迭代器与生成器

537 阅读6分钟

前言

迭代器是一个特殊的对象,具有专门为迭代过程设计的专有接口,所有迭代器对象都有一个 next() 方法,每次调用返回一个结果对象。迭代器保存一个内部指针,用于指向当前集合中值的位置,调用 next() ,返回下一个可用的值。

结果对象包含两个属性:value 和 done,value 表示下一个返回值,done 是一个布尔值,当没有更多可返回数据时返回 true。
结果对象两种形式:

  • value 下一个返回值(不为undefined),done 为 false
  • value 为 undefined ,done 为 true

ES6 中迭代器编写规则复杂,但 ES6 同时引入生成器对象,它使创建迭代器对象的过程变得更简单。下面先介绍生成器:

生成器

生成器是一种返回迭代器的函数,生成器调用方式与普通函数相同,只不过返回的是一个迭代器。

function *createIterator(){
    yield 1;
    yield 2;
}
var iterator = createIterator();
console.log(iterator.next());   //  Object { value: 1, done: false }
console.log(iterator.next());   //  Object { value: 2, done: false }
console.log(iterator.next());   //  Object { value: undefined, done: true }

上述示例中,注意:

  1. createIterator() 前的星号表明它是一个生成器,我们知道函数还有一种 new 方式调用,通过 new 调用生成器的话会抛出错误,生成器是没有 [Constructor] 属性。
  2. yield 关键字是 ES6 新特性,下面来介绍它。

yield 使用

yield 关键字,通过它来指定调用迭代器 next() 返回值及返回顺序。每执行完一条 yield 语句后函数就会自动停止执行,直到再次调用迭代器的 next() 或 throw() 方法才会继续执行。

function *createIterator (array) {
    for(var i = 0; i < array.length; i++)
        yield array[i];
}
var iterator = createIterator([1,2]);
console.log(iterator.next().value);     //  1
console.log(iterator.next().value);     //  2

yield 使用限制:只能在生成器内部使用,在其他地方使用或生成器内部的函数使用都会抛出语法错误。

yield 1;    //  抛出语法错误    

function *fun (array) {
    //  可以使用
    
    array.forEach(function () {
        //  不可以使用
        yield 1;    //抛出语法错误
    })
}

生成器表达式

注意不能用箭头函数来创建生成器。

//  函数表达式
var createIterator = function *(array) {
    for(var i = 0; i < array.length; i++)
        yield array[i];
}

//  对象的生成器
var object = {
    array : [1,2],
    //  第一种方式:
    createIterator: function *() {
        for(var i = 0, len = this.array.length; i < len; i++)
            yield this.array[i];
    }
    
    //  第二种方式:简写方式
    *createIterator () {
        for(var i = 0, len = this.array.length; i < len; i++)
            yield this.array[i];
    }
}



迭代器

可迭代对象

可迭代对象具有 Symbol.iterator 属性,是一种与迭代器密切相关的对象。 Symbol.iterator 通过指定函数可以返回一个作用于附属对象的迭代器。在 ES6 的所有集合对象和字符串都是可迭代对象,它们都有默认的迭代器,就是通过 Symbol.iterator() 来获取的。
for-of 循环中,首先使用对象默认迭代器[Symbol.iterator],然后每执行一次都会调用迭代器的next(),从其返回对象的value属性读取值并存储在变量中。

var array = [1,2,3];
//  for-of
for (var num of array) {
    console.log( num );         
}
//依次:1, 2, 3

//  模拟
var iterator = array[Symbol.iterator]();
var num = iterator.next().value;
while ( num !== undefined ) {
    console.log(num);
    num = iterator.next().value;
}
//依次:1, 2, 3

通过上述代码,明白在 JS 引擎中执行 for-of 循环语句中,通过 Symbol.iterator 获取数组 array 的默认迭代器,并用它遍历数组元素。
下面利用 Symbol.iterator 创建一个可迭代对象。

//  创建一个可迭代对象
var object = {
    array: [1,2,3],
    *[Symbol.iterator] () {
        for( var number of this.array){
            yield number;
        }
    }
}

for ( var number of object ) {
    console.log( number );
}

默认情况下,对象是不可迭代的,但如果给 Symbol.iterator 属性添加一个生成器,则该对象将变为可迭代对象。

内建迭代器

ES6 中有 3 中类型的集合对象:数组、Set集合和Map集合。这3中对象都内建了以下三种迭代器:

  • entries() 返回一个迭代器,其值为多个键值对
  • values() 返回一个迭代器,其值为集合的值
  • keys() 返回一个迭代器,其值为集合中所有键名
var array = [1,2,3];
//  entries()
for( var item of array.entries() ) {
    console.log(item);
}
//  依次打印:Array [ 0, 1 ], Array [ 1, 2 ], Array [ 2, 3 ]

//  values()
for( var item of array.values() ) {
    console.log(item);
}
//  依次打印:1, 2, 3

//  keys()
for( var item of array.keys() ) {
    console.log(item);
}
//  依次打印:0, 1, 2

每个集合类型都有一个默认的迭代器,在 for-of 循环中,数组和Set集合调用 values() 迭代器,Map集合调用 entries() 迭代器。

//  数组
var array = [1,2,3];
for ( var item of array ) {
    console.log(item);      //  依次打印:1,2,3
}

//  Set集合
var set = new Set([1,2,3]);
for ( var item of set ) {
    console.log(item);      //  依次打印:1,2,3
}

//  Map集合
var map = new Map([["key1", "value1"], ["key2", "value2"]]);
for ( var item of map ) {
    console.log(item);  
    //依次打印:Array [ "key1", "value1" ],Array [ "key2", "value2" ]
}



展开运算符与迭代器

ES6 新增加的展开运算符(...),它可以操作所有可迭代对象,根据默认迭代器来选取要引用的值,从迭代器读取所有值,然后按顺序返回。

var object = {
    array: [1,2],
    *[Symbol.iterator] () {
        for ( let item of this.array ) {
            yield item;
        }
    }
}
var array = [...object];
console.log(array);     //  Array [ 1, 2 ]



生成器与迭代器高级功能

生成器返回语句

由于生成器也是函数,因此也可以通过 return 语句提前退出函数执行。

function *fun () {
    yield 1;
    return 2;
    yield 3;
}
var iterator = fun();
console.log(iterator.next());   //  Object { value: 1, done: false }
console.log(iterator.next());   //  Object { value: 2, done: true }
console.log(iterator.next());   //  Object { value: undefined, done: true }

可以看到 return 提前终止迭代,返回结果对象{ value: 2, done: true },如果没有 return 值,则返回结果对象value: undefined



委托生成器

某种情况下,需要将多个迭代器合为一,创建一个新的生成器。通过yield 语句添加一个星号(*),就可以将生成数据的过程委托给其他迭代器。

function *createIterator1 () {
    yield 1;
    yield 2;
}
function *createIterator2 () {
    yield 3;
    yield 4;
}
function *createIterator3 () {
    yield *createIterator1();
    yield *createIterator2();
    yield 5;
}
for ( let num of createIterator3()) {
    console.log(num);
}
//  依次打印:1,2,3,4,5



迭代器传递参数

yield 关键字可以在生成器内部生成值传递给迭代器中,也可以接收迭代器传入的参数来赋值。通过迭代器的 next() 方法来传递参数。

function *createIterator () {
    var a = yield 1;
    var b = yield a + 10;
    yield b + 20;
}
let iterator = createIterator();
console.log( iterator.next() );       //  Object { value: 1, done: false }
console.log( iterator.next(2) );      //  Object { value: 12, done: false }
console.log( iterator.next(3) );      //  Object { value: 23, done: false }
console.log( iterator.next() );       //  Object { value: undefined, done: true }

执行过程:

  • 第一次next():执行 yield 1 ,然后停止执行,返回值为 1;
  • 第二次next(2):yield 1 接受 next 方法传入的参数,var a = 2,并执行 yield a + 10,停止执行,返回值为 12;
  • 第三次next(3):yield a + 10 接受 next 方法传入的参数,var b = 3,并执行 yield b + 20,停止执行,返回值为 23;

注意:第一次调用next()无论传入什么参数都会被丢弃。

throw()

除了向迭代器传递数据,还可以向迭代器中抛出错误,通过 throw() 方法,该方法接受一个错误对象的参数,当迭代器恢复执行时令其抛出一个错误,然后在生成器中使用 try-catch 代码块来处理错误,从而增强生成器内部的编码弹性。

function *createIterator () {
    yield 10;
    try {
        yield 20;
    } catch (error) {
        return ;
    }
    yield 30;
}
var iterator = createIterator();
console.log( iterator.next() );     //  Object { value: 10, done: false }
console.log( iterator.next() );     //  Object { value: 20, done: false }
console.log( iterator.throw(new Error("错误")) );   //Object { value: undefined, done: true }
console.log( iterator.next() );     //  Object { value: undefined, done: true }

上面代码执行两次 next() 方法后,代码执行完 yield 20 语句。使用 throw() 方法在迭代器中抛出错误,迭代器恢复代码执行并在 try 中捕捉到错误,提前终止迭代器。