迭代器

可迭代对象(Iterables)是可以使用 for..of 进行迭代的对象。

从技术上讲,可迭代对象必须实现 Symbol.iterator 方法。

理解迭代:

循环是迭代的基础
迭代会在一个有序集合上进行(有序可以理解为集合中所有项都可以按照既定的顺序被遍历到,特别是开始和结束项都有明确的定义)

数组是有序集合中最典型的例子

内置可迭代对象(实现Iterable接口,暴露Symbol.iterator属性作为默认迭代器,在es6中):

  • 字符串
  • 数组
  • 映射
  • 集合
  • arguments对象
  • NodeList等DOM集合类型

Symbol.iterator这个默认迭代器必须引用一个迭代器工厂函数,调用这个工厂函数必须返回一个新的迭代器

检查是否存在默认迭代器属性可以暴露这个工厂函数:

1
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
let num = 1
let obj = {}

// 这两种类型都没有实现迭代器工厂函数
console.log(num[Symbol.iterator]) // undefined
console.log(obj[Symbol.iterator]) // undefined

// -----------------------


let str = 'abc'
let arr = ['a', 'b', 'c']
let map = new Map().set('a', 1).set('b', 2).set('c', 3)
let set = new Set().add('a').add('b').add('c')
let divs = document.querySelectorAll('div')

// 这些类型都实现迭代器工厂函数
console.log(str[Symbol.iterator]) // ƒ values() { [native code] }
console.log(arr[Symbol.iterator]) // ƒ values() { [native code] }
console.log(map[Symbol.iterator]) // ƒ values() { [native code] }
console.log(set[Symbol.iterator]) // ƒ values() { [native code] }
console.log(divs[Symbol.iterator]) // ƒ values() { [native code] }

// 调用这个工厂函数会生成一个迭代器
console.log(str[Symbol.iterator]()) // StringInterator {}
console.log(arr[Symbol.iterator]()) // Array Iterator {}
console.log(map[Symbol.iterator]()) // MapInterator {}
console.log(set[Symbol.iterator]()) // SetInterator {}
console.log(divs[Symbol.iterator]()) // Array Iterator {}

实际写代码过程不需要显示调用这个工厂函数来生产迭代器

实现可迭代协议的所有类型都会自动兼容接收可迭代对象的任何语言特性。

接收可迭代对象的原生语言特性包括:

  • for-of循环
  • 数组解构
  • 扩展操作符
  • Array.from()
  • 创建集合
  • 创建映射
  • Promise.all()接收由promise组成的可迭代对象
  • Promise.race()接收由promise组成的可迭代对象
  • yeild*操作法,在生成器中使用

迭代器协议定义了如何从一个对象中生成一系列的值
当一个对象实现了next()方法时,他就成为了一个迭代器。
next()方法必须返回一个带有2个属性的对象:

  • value(下一个值,有值done为false,undefined:done为true)
  • done(true或false)

数组演示:

1
2
3
4
5
6
7
8
9
10
11
const arr = ['foo', 'bar']
console.log(arr[Symbol.iterator]) // ƒ values() { [native code] }

let iter = arr[Symbol.iterator]()
console.log(iter) // Array Iterator()

console.log(iter.next()) // {value: 'foo', done: false}
console.log(iter.next()) // {value: 'bar', done: false}
console.log(iter.next()) // {value: undefined, done: true}
console.log(iter.next()) // {value: undefined, done: true}

只要迭代器到达done:true状态后续调用next()就一直返回同样的值了

迭代器并不与可迭代对象的某个时刻的快照绑定,而仅仅是使用游标来记录遍历历程。
如果可迭代对象在迭代期间被修改了,那么迭代器也会变化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const arr = ['foo', 'bar']
console.log(arr[Symbol.iterator]) // ƒ values() { [native code] }

let iter = arr[Symbol.iterator]()
console.log(iter) // Array Iterator()

console.log(iter.next()) // {value: 'foo', done: false}

arr.splice(1, 0, 'test')

console.log(iter.next()) // {value: 'test', done: false}
console.log(iter.next()) // {value: 'bar', done: false}
console.log(iter.next()) // {value: undefined, done: true}

自定义的可迭代对象:

当每次调用 next() 时, 这个可迭代对象会无限返回:10、20、30、40……

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 自制的可迭代对象
function myNumbers() {
let n = 0;
return {
next: function() {
n += 10;
return {value:n, done:false};
}
};
}

// 创建可迭代对象
const n = myNumbers();
n.next(); // Returns {value: 10, done: false}
n.next(); // Returns {value: 20, done: false}
n.next(); // Returns {value: 30, done: false}

注意:自制的可迭代对象存在问题,它不支持 JavaScript 中的 for..of 语句。

JavaScript 中的可迭代对象是具有 Symbol.iterator 属性的对象。

Symbol.iterator 是一个返回 next() 函数的函数。

只能被迭代一次的可迭代对象:

1
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
class Counter {
constructor(limit) {
this.limit = limit;
this.count = 1
}
next() {
if (this.count <= this.limit) {
return { done: false, value: this.count++ }
} else {
return { done: true, value: undefined }
}
}
[Symbol.iterator]() {
return this
}
}

let counter = new Counter(3)
for(let i of counter) {
console.log(i)
}
// 1
// 2
// 3
for(let i of counter) {
console.log(i)
}
// 未输出console

为让一个可迭代对象可以创建多个迭代器,必须每创建一个迭代器就对应一个新的计数器,为此可以将计数器让在闭包里。通过闭包返回迭代器

1
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
class Counter {
constructor(limit) {
this.limit = limit;
}

[Symbol.iterator]() {
let count = 1
let limit = this.limit
return {
next() {
if (count <= limit) {
return { done: false, value: count++ }
} else {
return { done: true, value: undefined }
}
}
}
}
}

let counter = new Counter(3)
for(let i of counter) {
console.log(i)
}
// 1
// 2
// 3
for(let i of counter) {
console.log(i)
}
// 1
// 2
// 3

提前终止迭代器:

可选的return方法可以用于指定在迭代器提前关闭时的逻辑。执行迭代的结构要想让迭代器知道它不想可迭代对象耗尽时,就可以“关闭”迭代器。可能的情况有:

  • for-of循环通过break,continue,return,throw提前退出
  • 解构操作并未消费所有值

return方法必须返回一个有效的IteratorResult对象。简单情况下可以只返回{done: true}

1
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
class Counter {
constructor(limit) {
this.limit = limit;
}

[Symbol.iterator]() {
let count = 1
let limit = this.limit
return {
next() {
if (count <= limit) {
return { done: false, value: count++ }
} else {
return { done: true, value: undefined }
}
},
// 可选的return方法
return() {
console.log('exiting early')
return {done: true}
}
}
}
}
let counter = new Counter(3)
for(let i of counter) {
if (i >2) {
break
}
console.log(i)
}
// 1
// 2
// exiting early

// 再次迭代重新开始
for(let i of counter) {
console.log(i)
}
// 1
// 2
// 3

如果迭代器没有关闭,则还可以继续上次离开的地方继续迭代。比如,数组的迭代器就是不能关闭的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const arr = [1,2,3,4,5]
let iter = arr[Symbol.iterator]()
for (let i of iter) {
console.log(i)
if (i > 3) break
}
// 1
// 2
// 3
// 4
for (let i of iter) {
console.log(i)
}
// 5

因为return方法是可选的,所以并非所有迭代器都是可以关闭的.