前言
迭代器是一个特殊的对象,具有专门为迭代过程设计的专有接口,所有迭代器对象都有一个 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 }
上述示例中,注意:
- createIterator() 前的星号表明它是一个生成器,我们知道函数还有一种 new 方式调用,通过 new 调用生成器的话会抛出错误,生成器是没有 [Constructor] 属性。
- 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 中捕捉到错误,提前终止迭代器。