迭代器生成器
# 什么是迭代器
在JavaScript中,迭代器是一个具体的对象,这个对象需要符合迭代器协议
- 迭代器协议定义了产生一系列值的标准方法
- 在JavaScript中这个标准就是一个特定的next方法
next方法的要求
- 一个无参数或者一个参数的函数,返回一个应当拥有一下两个属性的对象
- done(Boolean类型)
- 如果迭代器可以产生序列中的下一个值,则为false。(相当于没有指定done这个属性)
- 如果迭代器已将序列迭代完毕,则为true。这种情况下value是可选的,如果它依然存在,即为迭代结束之后的默认返回值
- value:迭代器返回的任何JavaScript值,done为true时可以省略
const arr = ["ls", "zs", "ws"];
let index = 0;
const arrIterator = {
next: function () {
if (index < arr.length) {
return {
done: false,
value: arr[index++],
};
} else {
return { done: true, value: undefined };
}
},
};
console.log(arrIterator.next());
console.log(arrIterator.next());
console.log(arrIterator.next());
console.log(arrIterator.next());
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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 };
}
},
};
}
const arr = ["ls", "zs", "ws"];
const arrIterator = createArrayIterator(arr);
console.log(arrIterator.next());
console.log(arrIterator.next());
console.log(arrIterator.next());
console.log(arrIterator.next());
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 可迭代对象
当我们获取一个数组的时候,需要自己创建一个index变量,再创建一个所谓的迭代器对象;事实上可以进一步封装让一个不可迭代对象变成一个可迭代对象
# 什么是可迭代对象
- 和迭代器是不同的概念
- 当一个对象实现了iterator protocol协议时,它就是一个可迭代对象
- 这个对象的要求是必须实现iterator方法,在代码中使用Symbol.iterator访问该属性
转换成为可迭代对象的好处
- 当一个对象变成一个可迭代对象的时候,就可以进行某些迭代操作
- 例如
for...of
操作时,其实就是在调用它的iterator方法。
const info = {
friends: ["ls", "Zs", "we"],
[Symbol.iterator]: function () {
let index = 0;
return {
next: () => {
// 这里使用箭头函数是因为里面需要使用this,为了不改变this的指向
if (index < this.friends.length) {
return { done: false, value: this.friends[index++] };
} else {
return { done: true, value: undefined };
}
},
};
},
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
可迭代对象中函数名[Symbol.iterator]
是不能为其它名称的
# 原生迭代对象
是平时开发中很多原生对象已经实现可迭代协议,会生成一个迭代对象。
String,Array,Map,Set,arguments对象, NodeList集合
const str = "Hello World";
for (const s of str) {
console.log(s);
}
const arr = ["abc", "cba", "nba"];
for (const item of arr) {
console.log(item);
}
function foo(x, y, z) {
for (const arg of arguments) {
console.log(arg);
}
}
foo(20, 30, 40);
const set = Array.from(str);
console.log(set);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const names = ["abc", "cba", "nba"];
console.log(names[Symbol.iterator]);
const iterator = names[Symbol.iterator]();
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
2
3
4
5
6
7
# 可迭代对象应用
可迭代对象应用场景
- 在JavaScript中:
for...of
,展开语法,yield,解构赋值 - 创建对象时:new Map([Iterator]),new WeakMap([Iterator]),new Set([Iterator]),new WeakSet([Iterator])
- 一些对象调用:Promise.all(iterator),Promise.race(iterator),Array.from(iterator)
const info = ["abc", "cba"];
for (const item of info) {
console.log(item);
}
console.log([...info, "bnf"]);
const [name1, name2] = info;
console.log(name1, name2);
console.log(new Set(info));
console.log(Array.from(info));
Promise.all(info).then((res) => {
console.log(res);
});
2
3
4
5
6
7
8
9
10
11
12
# 自定义类的迭代
在面向对象开发中,可以通过class定义一个自己的类,这个类可以创建很多对象;如果希望自己的类创建出来的对象是可迭代的,那么在设计类的时候可以添加@@iterator
方法
class Person {
constructor(name, age, friends) {
this.age = age;
this.name = name;
this.friends = friends;
}
[Symbol.iterator]() {
let index = 0;
return {
next: () => {
if (index < this.friends.length) {
return { done: false, value: this.friends[index++] };
} else {
return { done: true };
}
},
};
}
}
const p1 = new Person("zs", 20, ["sd", "ls"]);
const p2 = new Person("ls", 20, ["ww", "ws"]);
for (const f1 of p1) {
console.log(f1);
}
for (const f2 of p2) {
console.log(f2);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 迭代器中断
迭代器在某些情况下会在没有完全迭代的情况下中断;比如遍历的过程中通过break
,return
,throw
中断了循环操作;比如在结构的时候,没有解构所有的值。
如果想要监听中断,可以添加return方法
class Person {
constructor(name, age, friends) {
this.age = age;
this.name = name;
this.friends = friends;
}
[Symbol.iterator]() {
let index = 0;
return {
next: () => {
if (index < this.friends.length) {
return { done: false, value: this.friends[index++] };
} else {
return { done: true };
}
},
return() {
console.log("迭代器中断");
return { done: true };
},
};
}
}
const p1 = new Person("zs", 20, ["sd", "ls"]);
for (const f1 of p1) {
console.log(f1);
if (f1 === "ls") {
break;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
## 生成器
生成器是ES6中新增的一种函数控制,使用的方案,可以让我们更加灵活的控制函数什么时候继续执行、暂停执行等
生成器函数也是一个函数,但是和普通函数有些些区别
- 生产器函数需要在function后面添加一个
*
- 生成器函数可以通过
yield
关键字来控制函数的执行流程 - 生成器函数的返回值是一个生成器;生成器是一种特殊的迭代器
- 要想执行函数内部的代码。需要生成器对象,调用
next
操作 - 遇到
yield
时,中断操作
- 要想执行函数内部的代码。需要生成器对象,调用
function* foo(){
console.log('1111')
console.log('2222')
yield
console.log('3333')
console.log('4444')
yield
console.log('5555')
console.log('6666')
console.log('7777')
const generator = foo() // 这里函数不会执行
generator.next() // 执行到第一个yield后面,第一个yield后面的代
}码也会执行
generator.next() // 继续执行到第二个yield后面的代码
2
3
4
5
6
7
8
9
10
11
12
13
14
# 生成器函数执行
直接调用生成器函数名并没有执行,只是返回了一个生成器对象;如果想要执行函数中的东西需要调用next函数;next是有返回值的;很多时候不希望next返回的是一个undefined,这个时候可以通过yield
来返回结果
function* foo() {
console.log("函数执行~");
const value1 = 100;
console.log(value1);
yield value1;
const value2 = 200;
console.log(value2);
yield value2;
const value3 = 300;
console.log(value3);
yield value3;
console.log("函数执行完毕");
}
const generator = foo();
// 执行到第一个yield,并暂停
console.log(generator.next()); // {value: 100, done: false}
// 执行到第二个yield,并暂停
console.log(generator.next()); // {value: 200, done: false}
// 执行到第三个yield,暂停
console.log(generator.next()); // {value: 300, done: false}
// 函数执行完毕
console.log(generator.next()); // {value: undefined, done: true}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 生成器传递参数
# next函数
生成器函数可以暂停分段来执行,因此也可以分段来传递参数。在调用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("zs");
const result1 = generator.next();
console.log("result1: ", result1); // result1: {value: 'zsaaa', done: false}
const result2 = generator.next(result1.value);
console.log("result2: ", result2); // result2: {value: 'zsaaabbb', done: false}
const result3 = generator.next(result2.value);
console.log("result3: ", result3); // result3: {value: 'zsaaabbbccc', done: false}
2
3
4
5
6
7
8
9
10
11
12
13
14
# 生成器提前结束---return函数
还可以通过return函数给生成器函数传递参数,return传值后这个生成器函数就会结束,之后调用next函数不会继续生成值了
function* foo() {
const value1 = yield "sdd";
console.log("value1: ", value1);
const value2 = yield value1;
const value3 = yield value2;
}
const generator = foo();
console.log(generator.next()); // {value: 'sdd', done: false}
console.log(generator.return("123")); // {value: '123', done: true}
console.log(generator.next()); // {value: undefined, done: true}
2
3
4
5
6
7
8
9
10
# 生成器抛出异常---throw函数
除了给生成器函数内部传递参数之外,也可以给生成器函数内部抛出异常;抛出异常后可以在生成器函数中捕获异常,但是在catch语句中不能继续yield新的值了,但是可以在catch语句外使用yield继续中断函数的执行。
function* foo() {
console.log("函数开始执行");
try {
yield "zs";
} catch (err) {
console.log("函数内部捕获异常:", err);
}
yield 2222;
console.log("函数执行结束");
}
const generator = foo();
const result = generator.next();
console.log(result); // {value: 'zs', done: false}
const result2 = generator.throw("throw error"); // 函数内部捕获异常: throw error
console.log(result2); // {value: 2222, done: false}
console.log(generator.next());
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 生成器代替迭代器
生成器是一种特殊的迭代器,在某些情况下可以使用生成器来代替迭代器
// 迭代器
const arr = ["abc", "cba", "cnc"];
function createArrIterator(arr) {
let index = 0;
return {
next: () => {
if (index < arr.length) {
return { done: false, value: arr[index++] };
} else {
return { done: true, value: undefined };
}
},
};
}
const arrIterator = createArrIterator(arr);
/* console.log(arrIterator.next());
console.log(arrIterator.next());
console.log(arrIterator.next());
console.log(arrIterator.next()); */
// 生成器
function* createArrIteratorGen(arr){
for(const item of arr){
yield arr
}
}
const arrGenerator = createArrIteratorGen(arr)
console.log(arrGenerator.next());
console.log(arrGenerator.next());
console.log(arrGenerator.next());
console.log(arrGenerator.next());
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
function* createRangeIterator(start, end) {
for (let i = start; i < end; i++) {
yield i;
}
}
const rangeIterator = createRangeIterator(10, 20);
console.log(rangeIterator.next());
console.log(rangeIterator.next());
console.log(rangeIterator.next());
console.log(rangeIterator.next());
console.log(rangeIterator.next());
console.log(rangeIterator.next());
console.log(rangeIterator.next());
2
3
4
5
6
7
8
9
10
11
12
13
也可以使用yield*
来产生一个可迭代对象,相当于是一个语法糖,只不过会依次迭代这个可迭代对象,每次迭代其中的一个值。
function* createArrIteratorGen(arr){
yield* arr
}
2
3
# 自定义类迭代--生成器实现
class Classroom {
constructor(name, address, initialStudent) {
this.name = name;
this.address = address;
this.students = initialStudent || [];
}
push(student) {
this.students.push(student);
}
*[Symbol.iterator]() {
yield* this.students;
}
}
const classRoom = new Classroom("zs", "sd", ["as", "cd", "fg"]);
const generator = classRoom[Symbol.iterator]();
console.log(generator.next());
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 生成器操作
生成器是一个迭代器,那么可以对生成器做一些操作
const nameIterator1 = createArrayIterator(names)
for(const item of nameIterator1){
console.log(item)
}
const namesIterator2 = createArrayIterator(names)
const set = new Set(nameIterator2)
console.log(set)
const nameIterator3 = createArrayIterator(names)
Promise.all(nameIterator3).then(res => {
console.log(res)
})
2
3
4
5
6
7
8
9
10
11
12
13
# 异步处理方法
假设一个场景,需要向服务器发送网络请求获取数据,一共需要发送三次请求
function requestData(url) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(url)
}, 2000)
})
}
2
3
4
5
6
7
方案一
function getData(){
requestData('zs').then(res1 => {
requestData(res1+ 'aaa').then(res2 => {
requestData(res2+'bbb').then(res3 => {
console.log('res3: ', res3)
})
})
})
}
2
3
4
5
6
7
8
9
方案二
function getData(){
requestData('zs').then(res1 => {
return requestData(res1+'aaa')
}).then(res2 => {
return requestData(res2+'bbb')
}).then(res3 => {
console.log('res3: ', res3)
})
}
2
3
4
5
6
7
8
9
# 生成器的解决方案
上面的代码阅读性很差,因此需要进一步对代码进行优化
function* getData(){
const res1 = yield requestData('zs')
const res2 = yield requestData(res1+'aaa')
const res3 = yield requestData(res2+'bbb')
const res4 = yield requestData(res3+'ccc')
console.log(res4)
}
const generator = getData()
generator.next().value.then(res => {
generator.next(res).value.then(res => {
generator.next(res).value.then(res => {
generator.next(res)
})
})
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 自动执行generator函数
上面的方案中仍然存在问题
- 不能确定需要调用多少层Promise关系
- 如果还有其它需要这样执行的函数,如何操作
因此需要封装一个工具函数用于自动执行生成器函数
function execGenerator(genFn) {
const generator = genFn()
function exec(res) {
const result = generator.next(res)
if(result.done) return result.value
result.value.then(res => {
exec(res)
})
}
}
2
3
4
5
6
7
8
9
10