迭代器与生成器
总览
迭代器与生成器笔记总结
# Iterator
使用户在容器对象(container,例如链表或数组)上遍访的对象,使用该接口无需关心对象的内部实现细节。是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作。即for...of
循环。
当使用for...of
循环遍历某种数据结构时,该循环会自动去寻找 Iterator 接口。一种数据结构只要部署了 Iterator 接口,我们就称这种数据结构是“可遍历的”(iterable)
ES6 的有些数据结构原生具备 Iterator 接口(比如数组),即不用任何处理,就可以被for...of
循环遍历。原因在于,这些数据结构原生部署了Symbol.iterator
属性
let arr = ['a', 'b', 'c'];
console.log(Object.getPrototypeOf(arr)); // 在原型链上可以发现部署了Symbol.iterator
let iter = arr[Symbol.iterator]();
console.log(iter.next()); // { value: 'a', done: false }
console.log(iter.next()); // { value: 'b', done: false }
console.log(iter.next()); // { value: 'c', done: false }
console.log(iter.next() ); // { value: undefined, done: true }
2
3
4
5
6
7
普通对象部署数组的Symbol.iterator
方法,并无效果。
对象(Object)之所以没有默认部署 Iterator 接口,是因为对象的哪个属性先遍历,哪个属性后遍历是不确定的,需要开发者手动指定。本质上,遍历器是一种线性处理,对于任何非线性的数据结构,部署遍历器接口,就等于部署一种线性转换
let iterable = {
a: 'a',
b: 'b',
c: 'c',
length: 3,
[Symbol.iterator]: Array.prototype[Symbol.iterator]
};
for (let item of iterable) {
console.log(item); // undefined, undefined, undefined
}
---------------------------------------------------------
// 实现遍历对象
const infos = {
name: 'rio',
age: 20,
height: 1.75,
[Symbol.iterator](){
const keys = Object.keys(this)
const entries = Object.entries(this)
let index = 0
const iterator = {
next:()=>{
if (index<keys.length) {
return { done: false, value: entries[index++]}
} else {
return { done: true, value: undefined}
}
}
}
return iterator
}
}
for (const item of infos) {
const [key,value] = item
console.log(key,value);
}
// name rio
// age 20
// height 1.75
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
32
33
34
35
36
37
38
39
40
# 可迭代对象
当一个对象实现了iterable protocol协议时,它就是一个可迭代对象;
这个对象的要求是必须实现@@iterator
方法,在代码中我们使用Symbol.iterator访问该属性;
const infos = {
friends: ["abc", "qwe", 'rio'],
[Symbol.iterator]:function(){
let index = 0
const infoIterator = {
next:function(){
if (index<infos.friends.length) {
return { done:false, value: infos.friends[index++]}
} else {
return { done: true}
}
}
}
return infoIterator
}
}
const iterator = infos[Symbol.iterator]()
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
for (const item of infos) {
console.log(item);
}
------------------------------------------
// 将infos替换成this
const infos = {
friends: ["abc", "qwe", 'rio'],
[Symbol.iterator]:function(){
let index = 0
const infoIterator = {
next:()=>{
if (index<this.friends.length) {
return { done:false, value: this.friends[index++]}
} else {
return { done: true}
}
}
}
return infoIterator
}
}
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
32
33
34
35
36
37
38
39
40
41
42
43
44
# 迭代器的中断
中断情况:比如遍历的过程中通过break、return、throw中断了循环操作;在解构的时候,没有解构所有的值; 想要监听中断的话,可以添加return方法:
迭代器对象除了具有next
方法,还可以具有return
方法和throw
方法。next
方法是必须部署的,return
方法和throw
方法是否部署是可选的。
return
方法的使用场合是,如果for...of
循环提前退出(通常是因为出错,或者有break
语句),就会调用return
方法。如果一个对象在完成遍历前,需要清理或释放资源,就可以部署return
方法。return
方法必须返回一个对象
const infos = {
name: 'rio',
age: 20,
height: 1.75,
[Symbol.iterator]() {
const keys = Object.keys(this)
const entries = Object.entries(this)
let index = 0
const iterator = {
next: () => {
if (index < keys.length) {
return { done: false, value: entries[index++] }
} else {
return { done: true, value: undefined }
}
},
return: ()=>{
console.log('中断了');
return { done: true}
}
}
return iterator
}
}
for (const item of infos) {
console.log(item);
if (item[0]=='age') {
break;
}
}
// ['name', 'rio']
// ['age', 20]
// 中断了
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
32
33
34
35
# 生成器
生成器是函数控制、使用的方案,它可以让我们更加灵活的控制函数什么时候继续执行、暂停执行
- 生成器函数的返回值是一个Generator (生成器)∶
- 生成器函数需要在function的后面加一个符号*
- 生成器是一个特殊的迭代器
- 生成器函数可以通过yield关键字来控制函数的执行流程
Generator 函数是 ES6 提供的一种异步编程解决方案
语法上,首先可以把它理解成,Generator 函数是一个状态机,封装了多个内部状态。
执行 Generator 函数会返回一个遍历器对象
function* foo(y0) {
console.log('111',y0);
console.log('222',y0);
const y1 = yield 'aaa'
console.log('333',y1);
const y2 = yield
console.log('444',y2);
return 'bbb'
}
const generator = foo('第一次执行next')
console.log(generator.next());
console.log(generator.next("第二次执行next"));
console.log(generator.next('第三次执行next'));
console.log(generator[Symbol.iterator]() == generator);
// 111 第一次执行next
// 222 第一次执行next
// {value: 'aaa', done: false}
// 333 第二次执行next
// {value: undefined, done: false}
// 444 第三次执行next
// {value: 'bbb', done: true}
// true
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
# yield
yield
表达式只能用在 Generator 函数里面。yield*
后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口。
function* helloWorldGenerator() {
yield* 'hello';
yield 'world';
return 'ending';
}
let hw = helloWorldGenerator();
console.log(hw.next()); // {value: h, done: false}
console.log(hw.next()); // {value: e, done: false}
console.log(hw.next()); // {value: l, done: false}
console.log(hw.next()); // {value: l, done: false}
console.log(hw.next()); // {value: o, done: false}
console.log(hw.next()); // {value: world, done: false}
console.log(hw.next()); // {value: ending, done: true}
console.log(hw.next()); // {value: undefined, done: true}
2
3
4
5
6
7
8
9
10
11
12
13
14
yield
表达式后面的表达式,只有当调用next
方法、内部指针指向该语句时才会执行,因此等于为 JavaScript 提供了手动的“惰性求值”(Lazy Evaluation)的语法功能。
function* gen() {
yield 123 + 456;
}
// yield后面的表达式123 + 456,不会立即求值,只会在next方法将指针移到这一句时,才会求值
2
3
4
Generator 函数可以不用yield
表达式,这时就变成了一个单纯的暂缓执行函数
function* f() {
console.log('执行了!')
}
var generator = f();
setTimeout(function () {
generator.next()
}, 2000);
// 函数f如果是普通函数,在为变量generator赋值时就会执行 console.log('执行了!')。
// 但是,函数f是一个 Generator 函数,就变成只有调用next方法时,函数f才会执行。
2
3
4
5
6
7
8
9
10
# Generator 与 Iterator 接口
由于 Generator 函数就是遍历器生成函数,因此可以把 Generator 赋值给对象的Symbol.iterator
属性,从而使得该对象具有 Iterator 接口。
var myIterable = {};
myIterable[Symbol.iterator] = function* () {
yield 1;
yield 2;
yield 3;
};
[...myIterable] // [1, 2, 3]
-----------------------------------------
// 利用 Generator 函数,可以在对象上部署 Iterator 接口。
function* iterEntries(obj) {
let keys = Object.keys(obj);
for (let i=0; i < keys.length; i++) {
let key = keys[i];
yield [key, obj[key]];
}
}
let myObj = { foo: 3, bar: 7 };
for (let [key, value] of iterEntries(myObj)) {
console.log(key, value); // foo 3 bar 7
}
----------------------------------------------------
class Person {
constructor(name,age,friends){
this.name = name
this.age = age
this.friends = friends
}
*[Symbol.iterator](){
yield* this.friends
// yield* Object.entries(this)
}
}
const p = new Person('rio',20,['a','b','c'])
for (const item of p) {
console.log(item);
}
const pIterator = p[Symbol.iterator]()
console.log(pIterator.next());
// a
// b
// c
// {value: 'a', done: false}
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
Generator 函数赋值给Symbol.iterator
属性,从而使得myIterable
对象具有了 Iterator 接口,可以被...
运算符遍历了。
# Generator 与状态机
var clock = function* () {
while (true) {
console.log('Tick!');
yield;
console.log('Tock!');
yield;
}
};
let c = clock();
console.log(c.next()); // Tick! {value: undefined, done: false}
console.log(c.next()); // Tock! {value: undefined, done: false}
console.log(c.next()); // Tick! {value: undefined, done: false}
2
3
4
5
6
7
8
9
10
11
12