迭代器和生成器
迭代器
内置迭代器
在字符串、数组、类型化数组、映射和集合中都已经内置了迭代器,通过 Symbol.iterator
方法就可以访问。
对象里面没有迭代器。
let str = 'hello';
console.log(str[Symbol.iterator]); // ƒ [Symbol.iterator]() { [native code] }
let iterator = str[Symbol.iterator]();
console.log(iterator.next()); // {value: 'h', done: false}
next()
在获得迭代器之后,就可以通过 next() 方法来返回序列中的对象,每一个对象包含 value 和 done 两个属性,其中,value 属性返回当前的值,而 done 属性则用来表示遍历是否已经结束,如果值为 false,就表示结束,反之,则表示已结束。
let str = 'hello';
let iterator = str[Symbol.iterator]();
console.log(iterator.next()); // {value: 'h', done: false}
console.log(iterator.next()); // {value: 'e', done: false}
console.log(iterator.next()); // {value: 'l', done: false}
console.log(iterator.next()); // {value: 'l', done: false}
console.log(iterator.next()); // {value: 'o', done: false}
console.log(iterator.next()); // {value: undefined, done: true}
对象与迭代器
对象是没有内置迭代器的,要想将对象转换为可迭代的对象,需要自行编写迭代器,又或者使用 Object 的 entries、keys 或 values 方法先提取对象的键值、键或值,再调用 Symbol.iterator
方法,例如,要在对象内添加遍历属性的迭代器,可以为对象添加 Symbol.iterator
方法。
class Car {
constructor(color, passengers) {
this.color = color;
this.passengers = passengers;
};
[Symbol.iterator]() {
return Object.keys(this)[Symbol.iterator]();
}
}
let car = new Car('blue', 5);
let iterator = car[Symbol.iterator]();
console.log(iterator.next()); // {value: 'color', done: false}
console.log(iterator.next()); // {value: 'passengers', done: false}
console.log(iterator.next()); // {value: undefined, done: true}
异步迭代器
由于同步迭代器无法处理异步数据源,因此 ES2018
中引入了异步迭代器,以便处理异步数据,与同步迭代器剧透 Symbol.iterator
方法类似,异步迭代器也有自己的迭代方法 Symbol.asyncIterator
。在调用 next 方法之后,异步迭代器会返回带有迭代结果的 Promise 对象。
生成器
要自定义一个迭代器,最大的麻烦是需要自己维护迭代器的状态,如当前索引。为了简化迭代器的编写,于是就有了生成器,它允许定义一个包含自有迭代算法的函数。同时可自行维护自己的状态。
基本语法
要指定函数生成器,需要在 function 关键字后加以 * ,并使用 yield 关键字来返回结果。
// 迭代器
function* generator() {
yield 1;
yield 2;
yield 3;
}
let gen = generator();
console.log(gen.next()); // {value: 1, done: false}
console.log(gen.next()); // {value: 2, done: false}
console.log(gen.next()); // {value: 3, done: false}
console.log(gen.next()); // {value: undefined, done: true}
返回可迭代对象
当生成器要返回的是数组、字符串等可迭代的对象时,可以使用 yield * 语句来简化书写
function* generator() {
yield* [1, 2, 3];
}
let gen = generator();
console.log(gen.next()); // {value: 1, done: false}
console.log(gen.next()); // {value: 2, done: false}
console.log(gen.next()); // {value: 3, done: false}
console.log(gen.next()); // {value: undefined, done: true}
在类或对象中定义生成器
要在类或对象中定义生成器,可以使用基本语法,也可以使用简写,在方法名前面加星号就可以了。
class Car {
* generator() {
yield 1;
yield 2;
yield 3;
}
}
let car = new Car();
let gen = car.generator();
console.log(gen.next()); // {value: 1, done: false}
// 常规对象中使用生成器
let obj = {
* generator() {
yield 1;
yield 2;
yield 3;
}
}
let gen1 = obj.generator();
console.log(gen1.next());
高级生成器
在调用 next() 方法时,可以通过传递参数给 next 的方式修改生成器内部状态的值。
let getId = function *(prefix, start) {
while(true) {
let stop = yield prefix + start++;
if(stop) break;
}
}
let gen = getId('div', 1);
console.log(gen.next()); // {value: 'div1', done: false}
console.log(gen.next()); // {value: 'div2', done: false}
console.log(gen.next()); // {value: 'div3', done: false}
console.log(gen.next(true));
如果不将 true 传递给 next 方法生成器会一直运作下去,而将 true 传递过去后,生成器的最后一个结果就会变成 true,并赋值给 stop,从而停止生成器的运作。
function* generator() {
let index = 0;
while (true) {
let step = yield index++;
console.log(step);
if (step === 4) index = 0;
}
}
let gen = generator();
console.log(gen.next(4));
console.log(gen.next());
console.log(gen.next(4));
console.log(gen.next());
/*
{value: 0, done: false}
undefined
{value: 1, done: false}
4
{value: 0, done: false}
undefined
{value: 1, done: false}
*/
从输出可以看到,再第一次调用 next 方法时,传递给方法的值会被忽略,不会输出任何有关 step 的值。如果没有传递值给 next 方法,step 的值会为 undefined。若将值传递给方法,则 step 的值为传递过来的值,这时,index 的值就会重置,重新从 0 开始计算。
抛出错误
迭代器带有一个 throw 方法用来抛出一个错误,而这个错误可在生成器内部捕获并进行处理。
function* generator() {
try {
yield 1;
yield 2;
yield 3;
yield 4;
} catch (e) {
yield 99;
}
}
let gen = generator();
console.log(gen.next()); // {value: 1, done: false}
console.log(gen.next()); // {value: 2, done: false}
console.log(gen.throw()); // {value: 99, done: false}
console.log(gen.next()); // {value: undefined, done: true}
从代码看,当使用迭代器调用 throw 方法抛出一个错误的时候,生成器会返回 99,并中止生成器的状态。
return 语法
没有在生成器内使用 return 语句返回数据,并不意味着不能在生成器内使用 return 语句。
虽然 return 语句还是可以使用的,但要注意的是,这会直接退出生成器。
任务队列
在使用 Ajax 获取数据时,有时候需要先获取一个数据,然后根据返回结果获取第二个数据,再根据第二个返回结果获取第三个,这样一直执行到任务结束。
function send(url, callback) {
// 发送请求
}
send('https://localhost/', function(result1) {
let data1 = JSON.parse(result1);
send(`https://localhost/?params=${data1.params}`, function(result2) {
let data2 = JSON.parse(result2);
send(`https://localhost/?params=${data2.params}`, function(result3) {
let data3 = JSON.parse(result3);
//...
})
})
})
如果任务数很多,以上代码的嵌套就会很累赘,可读性也很差;通过生成器可将以上代码简化
class Task {
constructor(tasks) {
var me = this;
console.log(this); // this 指的是 new Task() 实例化后的对象
me.index = 0;
me.tasks = tasks;
me.generator = me.generator();
me.generator.next();
}
*
generator() {
let me = this,
tasks = me.tasks,
result = null,
data = null;
if (Array.isArray(tasks)) {
for (let i = 0; i < tasks.length; i++) {
result = yield me.send(`${tasks[0]}?params=${data ? data.params : ''}`, me.onComplete);
data = JSON.parse(result);
}
}
}
onComplete(result) {
this.generator.next(result)
}
send(url) {
let me = this;
me.index++;
console.log(url);
setTimeout(() => {
me.onComplete(`{"params": ${me.index}`)
}, 10)
}
}
let task = new Task(['https://localhost', 'https://localhost', 'https://localhost']);
异步生成器
异步生成器与同步生成器的主要区别是需要再生成器前面加上 async
语句。
async function* generator() {
try {
yield 1;
yield 2;
yield 3;
yield 4;
} catch (err) {
yield 99;
}
}
let gen = generator();
gen.next().then(arg => console.log(arg)); // {value: 1, done: false}
gen.next().then(arg => console.log(arg)); // {value: 2, done: false}
gen.throw().then(arg => console.log(arg)); // {value: 99, done: false}
gen.next().then(arg => console.log(arg)); // {value: undefined, done: true}
for…of循环
for await … of 循环
只能用在异步函数中使用
async function* generator() {
try {
yield 1;
yield 2;
yield 3;
yield 4;
} catch (err) {
yield 99;
}
}
let gen = generator();
let fn = async function() {
for await (const item of gen) {
console.log(item);
}
}
fn();
/*
1
2
3
4
*/