迭代器(Iterator)和生成器(Generator)
Iterator
什么是迭代器
迭代器是一种一次性使用的对象,可以用来遍历某个数据结构。该对象需符合迭代器协议。
迭代器协议
迭代器API使用next()方法,在可迭代对象中遍历数据。
next()方法返回的迭代器对象IteratorResult包含done和value两个属性:
-
done(boolean):是否还可以再次调用next()取得下一值;
- done: false 表示可以取得下一值,即所对应的value值;
- done: true 表示状态为“耗尽”,无法再取得下一值,value为undefined
-
value:包含可迭代对象的下一值(done: false),或为undefined(done: true)
// 创建一个迭代器对象来访问数组
const names = ["kobe", "zj", "cate"];
let index = 0;
const namesIterator {
next: function() {
if(index < names.length) {
return { done: false, value: names[index++] }
} else {
return { done: true, value: undefined }
}
}
}
// 使用迭代器来访问数组元素,当迭代器到达done: true状态时,后续调用next()就一直返回同样值
console.log(namesIterator.next());
生成迭代器的函数
创建一个有限的迭代器
function createArrayIterator(arr) {
let index = 0;
return {
next: function() {
if(index < arr.length) {
return { done: false, value: arr[index++] }
} else {
return { done: true, value: undefined }
}
}
}
}
创建一个无限的迭代器:done永远不为true
function createNumberIterator() {
let index = 0;
return {
next: function() {
return { done: false, value: index++ }
}
}
}
什么是可迭代对象
一个对象实现了可迭代协议时,为可迭代对象。该对象必须实现@@iterator方法。
可迭代协议
必须暴露一个使用Symbol.iterator作为键的属性,来作为“默认迭代器”。该默认迭代器属性要引用一个迭代器工厂函数,调用该工厂函数时,返回一个新迭代器。
// 创建一个可迭代对象,相当于对创建迭代器代码的封装
const iterableObj = {
names: ["kobe", "zj", "cate"],
[Symbol.iterator]: function() { // 迭代器工厂函数
let index = 0;
return {
// 这里返回一个迭代器
next: () => { // 箭头函数不绑定this
if(inedx < this.names.length) {
return { done: false, value: this.names[index++] }
} else {
return { done: true, value: undefined }
}
}
}
}
}
console.log(iterableObj[Symbol.iterator]); // [Function: [Symbol.iterator]]
console.log(iterableObj[Symbol.iterator]().next()); // {done: false, value: "kobe"}
内置创建可迭代对象
平时创建的很多原生对象已经实现了可迭代协议,会生成一个迭代器对象,包括:
String、Array、Map、Set、arguments对象、NodeList集合等DOM集合类型
可迭代对象的应用场景
- for...of场景:相当于每次遍历可迭代对象中的迭代器,done: false时,将value取出赋值给item;done: true,自动停止
for(const item of iterableObj) {
console.log(item);
}
- 展开语法
console.log([...iterableObj]); // ['kobe', 'zj', 'cate']
- 数组解构
const names = ["lsy", "zj"];
const [name1, name2] = names;
- 创建一些其他对象时
// Array.from()
const arr = Array.from(arguments);
// Set构造函数
const set = new Set(iterableObj);
// Map构造函数
let pairs = names.map((x, i) => [x, i]); // names见3中
console.log(pairs); // [['lsy', 0], ['zj', 1]]
let map = new Map(pairs);
console.log(map); // Map(2) {'lsy'=>0, 'zj'=>1}
- Promise.all():接收由期约组成的可迭代对象
Promise.all(iterableObj).then(res => {
console.log(res); // ['kobe', 'zj', 'cate']
}
自定义类的可迭代性,以及提前终止迭代器
创建一个教室类,创建出来的对象都是可迭代对象
class Classroom {
constructor(address, name, students) {
this.address = address;
this.name = name;
this.students = students;
}
entry(newStudent) {
this.students.push(newStudent);
}
[Symbol.iterator]() {
let index = 0;
return {
next: () => {
if(index < this.students.length) {
return { done: false, value: this.students[index++] }
} else {
return { done: true, value: undefined }
}
}
},
// 监听迭代器是否提前终止了
return: () => {
console.log("迭代器提前终止了");
return { done: true, value: undefined }
}
}
}
const classroom = new Classroom("3栋5楼205", "计算机教室", ["james", "kobe", "curry", "lsy"]);
classroom.entry("zj");
// 这样创建出来的对象为可迭代对象,可以使用for...of遍历
for(const stu of classroom) {
console.log(stu);
if(stu === "why") break; // 强制性将迭代器停掉
}
Generator
什么是生成器
生成器的形式是一个函数:(1)它需要在函数名称前加*;(2)生成器函数可以通过yield关键字控制函数的执行流程;(3)调用生成器函数并不会执行函数代码,但是会产生一个生成器对象
// 生成器函数声明
function* generatorFn() {}
// 生成器函数表达式
let generatorFn = function* () {}
// 作为对象字面量方法的生成器函数
let foo = {
* generatorFn() {}
}
// 作为类实例方法的生成器函数
class Foo {
* generatorFn()
}
// 作为类静态方法的生成器函数
class Bar {
static * generatorFn() {}
}
生成器函数的执行
生成器函数遇到yield之前会正常执行,遇到yield后,停止执行,函数作用域的状态会被保留
当生成器对象继续调用next()方法后,又恢复执行,相当于继续执行下一段代码
迭代器的next是会返回值的,yield后面的内容会作为next()中的value值来返回,此时done: false;return后的内容也会作为next()中的value值返回,此时done: true
function* foo() {
console,log("函数开始执行");
const value1 = 100;
console.log("第一段代码:", value1);
yield value1
const value2 = 200;
console.log("第二段代码:", value2);
yield value2
console.log("函数执行结束");
return 123
}
const generator = foo(); // 得到生成器对象
generator.next(); // 第一次调用next(),执行到第一个yield停止,则相当于执行第一段代码(第一个yield以前的代码){done: false, value: 100}
generator.next(); // 执行第二段代码 {done: false, value: 200}
generator.next(); // 执行第三段代码,函数执行结束 {done: true, value: 123}
在调用next函数时,可以给它传递参数,这个参数会作为上一个yield语句的返回值,相当于为本次的函数代码块执行提供了一个值
function* foo(initial) {
console.log("函数开始执行");
const value1 = yield initial + "aaa";
const value2 = yield value1 + "bbb";
const value3 = yield value2 + "ccc";
}
const generator = foo("zj");
const result1 = generator.next();
console.log("result1:", result1); // result1: { value: 'zjaaa', done: false }
const result2 = generator.next(result1.value);
console.log("result2:", result2); // result2: { value: 'zjaaabbb', done: false }
const result3 = generator.next(result2.value);
console.log("result3:", result3); // result3: { value: 'zjaaabbbccc', done: false }
生成器替代迭代器使用
function* createArrayIterator(arr) {
// 写法一
/*for(const item of arr) {
yield item
}*/
// 写法二:为写法一的语法糖
// yield*后面跟上一个可迭代对象,每次迭代可迭代对象中的一个值
yield* arr
}
自定义类迭代-生成器实现
class Foo() {
constructor() {
this.values = [1, 2, 3];
}
*[Symbol.iterator]() {
yield* this.values;
}
}
提前终止生成器
return()
当生成器对象调用return()时,则提前终止迭代器,比如生成器对象generator,generator.return(15)则表明{value: 15, done: true}
throw()
该方法将传入的参数作为错误向生成器对象内部注入,若错误未被处理,则生成器关闭;处理,则不会关闭且恢复执行。错误处理会跳过对应的yield。
function* foo() {
for(const x of [1, 2, 3]) {
try {
yield x;
} catch(err) {}
}
}
const generator = foo();
console.log(generator.next()); // {done: false, value: 1}
generator.throw('error message');
console.log(generator.next()); // {done: false, value: 3}
// 生成器在try/catch块中的yield处暂停执行,yield抛出了错误,生成器不再产生值2