迭代器——Iterator
迭代器的描述
迭代器(iterator),是确使用户可在容器对象(container,例如链表或数组)上遍访的对象[1][2][3],设计人员使用此接口无需关心容器对象的内存分配的实现细节。其行为很像数据库技术中的光标(cursor),迭代器最早出现在1974年设计的CLU编程语言中。
在JavaScript中,迭代器需要符合迭代器协议(Iterator Protocol),迭代器是一个具体的对象,该对象需要有next()
方法。next()
返回一个对象,该对象含有以下两个属性
- done:该属性是布尔类型, 如果迭代器可以产生序列中的下一个值,则为
**false**
,如果迭代器不能产生下一个值,则为**true**
。当done为true时,value属性是可选的。 - value:该属性是任何类型,表示当前序列的值。
示例:
let i = 0;
const iterator = {
next() {
if (i++ < 10) {
return { done: false, value: i };
} else {
return { done: true, value: undefined };
}
},
};
可迭代对象
当一个对象实现了iterable protocol协议时,它就是一个可迭代对象; 这个对象的要求是必须实现 @@iterator方法,在代码中我们使用 Symbol.iterator
访问该属性;当一个对象变成一个可迭代对象的时候,进行某些迭代操作,比如 for...of
操作时,其实就会调用它的 @@iterator 方法。
:::info
@@iterator方法返回一个迭代器
:::
示例:
const names = {
values: ["a", "b", "c", "d", "e"],
[Symbol.iterator]: function () {
let i = 0;
const iterator = {
next: () => {
if (i < this.values.length) {
return { done: false, value: this.values[i++] };
} else {
return { done: true, value: undefined };
}
},
};
return iterator;
},
};
for (const name of names) {
console.log(name);
}
事实上平时创建的很多原生对象已经实现了可迭代协议,会生成一个迭代器对象的。 比如: String、Array、Map、Set、arguments对象、NodeList集合; 可以通过
[Symbol.iterator]
获取。
迭代器的应用场景
- JavaScript中语法:for …of、展开语法(spread syntax)、yield*(后面讲)、解构赋值(Destructuring_assignment);
- 创建对象时:new Map([Iterable])、new WeakMap([iterable])、new Set([iterable])、new WeakSet([iterable]);
- 一些方法的调用:Promise.all(iterable)、Promise.race(iterable)、Array.from(iterable);
迭代器的中断
迭代器在某些情况下可以中断,比如在遍历过程中使用break,return、throw等或在解构时,没有解构所有的值时。
迭代器的间断可以被监听:
需要在返回的迭代器中添加return()
方法 ```javascript const names = { values: [“a”, “b”, “c”, “d”, “e”], [Symbol.iterator]: function () { let i = 0; const iterator = {
}; return iterator; }, };next: () => { if (i < this.values.length) { return { done: false, value: this.values[i++] }; } else { return { done: true }; } }, return() { console.log("迭代器被中断了"); return { done: true }; },
for (const name of names) { console.log(name); if (name === “c”) { break; } }
:::warning
`**return()**`方法也需要返回包含done的对象
:::
<a name="W5jqk"></a>
## 生成器——Generator
生成器是ES6中新增的一种函数控制、使用的方案,它可以让我们更加灵活的控制函数内部的执行、暂停等操作。<br />**生成器**对象是由一个 [generator function](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/function*) 返回的,并且它符合[可迭代协议](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Iteration_protocols#iterable)和[迭代器协议](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Iteration_protocols#iterator)。
<a name="FP19a"></a>
### 生成器函数的定义
**function*** 这种声明方式function关键字后跟一个星号会定义一个**_生成器函数_ (**_generator function_**)**,它返回一个`Generator`对象。
:::info
*** 生成器(Generator)对象其实是一个特殊的迭代器。**
:::
<a name="gSO6s"></a>
### 生成器的执行
**生成器函数**在执行时能暂停,后面又能从暂停处继续执行。<br />调用一个**生成器函数**并不会马上执行它里面的语句,而是返回一个这个生成器的 **迭代器** **( **[iterator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#iterator)** )对象**。<br />当这个迭代器的 `next()`方法被首次(后续)调用时,其内的语句会执行到第一个(后续)出现`**yield**`的位置为止,`**yield**`后紧跟迭代器要返回的值。或者如果用的是 [yield*](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/yield*)(多了个星号),则表示将执行权移交给另一个生成器函数(当前生成器暂停执行)。<br />`next()`方法返回一个对象,这个对象包含两个属性:value 和 done,value 属性表示本次 `**yield**`表达式的返回值,done 属性为布尔类型,表示生成器后续是否还有 `**yield**`语句,即生成器函数是否已经执行完毕并返回。<br />调用 `next()`方法时,如果传入了参数,那么这个参数会传给**上一条执行的 yield语句左边的变量。**<br />示例:
```javascript
function* foo(a) {
console.log("statement-1", a);
const b = yield 1;
console.log("statement-2", b);
const c = yield 2;
console.log("statement-3", c);
}
const fooGenerator = foo("abc");
console.log("ret", fooGenerator.next()); // 执行到 yield 1;
console.log("ret", fooGenerator.next("cba")); // 执行到 yield 2;
console.log("ret", fooGenerator.next("xxx")); // 执行完毕
console.log("ret", fooGenerator.next("xxx"));
生成器的return方法
**return()**
方法返回给定的值并结束生成器。
return传值后这个生成器函数就会结束,之后调用next不会继续生成值了;
function* foo(a) {
console.log("statement-1", a);
const b = yield 1;
console.log("statement-2", b);
const c = yield 2;
console.log("statement-3", c);
return 999
}
const fooGenerator = foo("abc");
console.log("ret", fooGenerator.next());
console.log("ret", fooGenerator.return("cba"));
console.log("ret", fooGenerator.next("xxx"));
console.log("ret", fooGenerator.next());
console.log("ret", fooGenerator.next());
:::info
- 如果对已经处于“完成”状态的生成器调用
return(value)
,则生成器将保持在“完成”状态。 - 如果没有提供参数,则返回对象的value属性为undefined。
- 如果提供了参数,则参数将被设置为返回对象的
value
属性的值。 :::生成器抛出异常
**throw()**
方法用来向生成器抛出异常,并恢复生成器的执行,返回带有done
及value
两个属性的对象。 ```javascript function* foo(a) { console.log(“statement-1”, a); let b = undefined; try { b = yield 1; } catch (error) { console.log(error); } console.log(“statement-2”, b); const c = yield 2; console.log(“statement-3”, c); return 1000; }
const fooGenerator = foo(“abc”);
console.log(“ret”, fooGenerator.next()); console.log(“ret”, fooGenerator.throw(“some error”)); console.log(“ret”, fooGenerator.next()); console.log(“ret”, fooGenerator.return(100));
![image.png](https://cdn.nlark.com/yuque/0/2021/png/21450449/1639117412307-9353c7b6-134b-4b3f-82db-b8d14e0c69c3.png#clientId=ub6f6b52d-ea44-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=270&id=u12af975b&margin=%5Bobject%20Object%5D&name=image.png&originHeight=270&originWidth=832&originalType=binary&ratio=1&rotation=0&showTitle=false&size=27925&status=done&style=none&taskId=ua24bfe14-31e5-4c91-9af2-6015c274e0d&title=&width=832)
<a name="QctXk"></a>
### 使用生成器代替迭代器
```javascript
function* foo() {
let i = 0;
while (i < 10) {
yield i++;
}
}
const gen = foo();
for (const i of gen) {
console.log(i);
}
类中方法变成迭代器方法
class GenClass {
constructor() {
this.arr = ["a", "b", "c"];
}
*[Symbol.iterator]() {
// 如果用的是 yield*,则表示将执行权移交给另一个生成器函数(当前生成器暂停执行)。
yield* this.arr;
}
}
async/await
异步代码同步执行——生成器方案
// 模拟网络请求一
function requestToken() {
return new Promise((resolve) => {
console.log("requestToken is pending...");
setTimeout(() => {
console.log("requestToken resolved.");
resolve(`ABCDEFG`);
}, 1500);
});
}
// 模拟网络请求二
function requestData(token) {
return new Promise((resolve, reject) => {
console.log("requestData is pending...");
setTimeout(() => {
if (token === "ABCDEFG") {
const dataList = ["a", "b", "c", "d"];
resolve(dataList);
} else {
reject("error.");
}
}, 2500);
});
}
// 生成器模拟异步函数
function* getData() {
const token = yield requestToken();
yield requestData(token);
}
// 执行
const gen = getData();
gen.next().value.then((res) => {
gen.next(res).value.then((res) => {
console.log(res);
});
});
自动执行生成器函数的封装
function execAsyncGenerator(generator, param) {
function _recursiveGen(param) {
const result = generator.next(param);
if (result.done) return result.value;
result.value.then((res) => {
_recursiveGen(res);
});
}
_recursiveGen(param);
}
asnyc函数
async函数是使用**async**
关键字声明的函数。 async函数是AsyncFunction构造函数的实例, 并且其中允许使用await关键字。async
和await
关键字让我们可以用一种更简洁的方式写出基于Promise的异步行为,而无需刻意地链式调用promise。
function resolveAfter2Seconds() {
return new Promise(resolve => {
setTimeout(() => {
resolve('resolved');
}, 2000);
});
}
async function asyncCall() {
console.log('calling');
const result = await resolveAfter2Seconds();
console.log(result);
}
asyncCall();
异步函数的内部代码执行过程和普通的函数是一致的,默认情况下也是会被同步执行。
异步函数与普通函数返回值的区别
- 普通值:异步函数的返回值会被包裹到
Promise.resolve
中; - Promise:如果异步函数的返回值是Promise,Promise.resolve的状态会由Promise决定;
thenable obj:如果异步函数的返回值是一个对象并且实现了
thenable
,那么会由对象的then方法来决定;await关键字
await
操作符用于等待一个Promise
对象。它只能在异步函数 async function 中使用。await等待值的四种情况
普通值:返回该值本身
- fulfilled Promise:其回调的resolve函数参数作为 await 表达式的值
- rejected Promise:await 表达式会把 Promise 的异常原因抛出。
- thenable obj: 根据对象的then方法调用来决定后续的值。