用循环语句迭代数据时,必须要初始化一个变量来记录每一次迭代在数据集合中的位置,于是 ES6 向 JS 中添加了这个迭代器特性。新的数组方法和新的集合类型 ( 例如 Set 集合与 Map 集合 ) 都依赖迭代器实现。
你也会发现在语言的其他特性中也都有迭代器的身影:新的 for / of 循环、展开运算符 ( … ) ,甚至连异步编程都可以使用迭代器。
什么是迭代器
迭代器是一种特殊对象,它具有专门为迭代过程设计的接口,所有的迭代器对象都有一个next()方法,每次调用都返回一个结果对象。
该对象有两个属性:value => 表示下一个将要返回的值;另一个是 done => 当没有更多可返回数据时返回 true。
迭代器还会保存一个内部指针,用来指向当前集合中值得位置,每调用一次 next() 方法。都会返回下一个可用的值。
迭代器需要靠生成器生成
什么是生成器
生成器是生成一个迭代器的函数,由于生成器默认会为 Symbol.iterator 赋值,因此所有通过生成器创建的迭代器都是可迭代对象
通过给 function 关键字后添加 * 号来表示该函数为生成器,函数中会通过一个关键字 yield 用来标识每次调用迭代器的 next() 方法时返回的值
每当执行完一条 yield 语句后函数就会自动停止执行,直到再次调用迭代器的 next() 方法才会继续执行 下一个 yield 语句
如果生成器中所有的 yield 执行完毕,再调用迭代器的 next() 方法,都会返回相同的内容:{ value: undefind , done: true }
// 使用函数声明一个生成器
function *createIterator(){
yield 1;
yield 2;
yield 3;
}
// 调用生成器,返回一个迭代器给 iterator ,此时 iterator 具有了 next() 方法
let iterator = createIterator();
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: undefind , done: true }
// 使用函数表达式声明一个生成器
let createIterator = function *(items){
for(let item of items){
yield item;
}
}
let iterator = createIterator([1, 2, 3]);
iterator.next(); // { value: 1 , done: false }
// 在对象内添加生成器的属性
let o = {
num: 0,
*createIterator(items){
for(let item of items){
yield item + this.num;
}
}
}
let iterator = o.createIterator([1, 2, 3]);
iterator.next(); // { value: 1 , done: false }
可迭代对象和 for / of 循环
可迭代对象具有 Symbol.iterator 属性,是一种与迭代器密切相关的对象。Symbol.iterator 通过指定的函数可以返回一个作用域附属对象的迭代器。
ES6 中所有的集合对象 ( 数组、Set集合、Map集合 ) 和字符串都是可迭代对象,这些对象都有默认的迭代器 ( 即内部存在 yield )
前面提到过循环内部索引跟踪的相关问题,要解决这个问题,需要两个工具:一个是迭代器,另一个是for / of 循环。如此一来,便不再需要跟踪整个集合的索引,只需关注集合中要处理的内容。
for / of 循环每次执行一次都会调用可迭代对象的 next() 方法,并将迭代器返回的结果对象的 value 属性存储在一个变量中,循环将持续执行这一过程直到返回对象的 done 属性的值为 true 。
访问默认迭代器
可以通过 Symbol.iterator 来访问对象默认的迭代器
let arr = [1, 2, 3];
let iterator = arr[Symbol.iterator]();
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: undefind , done: true}
创建可迭代对象
默认情况下,开发者定义的对象都是不可迭代对象,但如果给 Symbol.iterator 属性添加一个生成器,则可以将其变为可迭代对象,该方法适用于对象转数组
let collection = {
items: [1, 2, 3],
*[Symbol.iterator](){
for( let item of this.items ){
yield item;
}
}
}
// 给 collection 添加了 Symbol.iterator 属性后,可以使用 for / of 来迭代了
for( let x of collection ){
console.log( x ) // 将输出 1 2 3
}
// 下面是对象转数组的使用方式
let collection = {
"葡萄干": 1,
"荔枝干": 2,
"芒果干": 3,
*[Symbol.iterator]() {
for (let key of Object.keys(this)) {
yield {
name:key,
value:this[key]
}
}
},
};
let arr = Array.from(collection);
关于迭代器还有以下高级使用方式,内容太多就不写了。
内建迭代器 ( 集合对象迭代器、字符串迭代器、NodeList迭代器 )
高级迭代器功能 ( 给迭代器传递参数、在迭代器中抛出错误、生成器 return 返回语句、委托生成器 )
异步任务执行 ( 简单任务执行器、向任务执行器传递数据、异步任务执行器 )
在生成器和迭代器的所有应用场景中,最令人兴奋的是用来创建更简洁的异步代码。这种方式无须在所有地方定义回调函数,其代码看起来像是同步代码,但实际上使用了 yield 生成的特性来等待异步操作最终完成。