一、迭代器专用循环 for…of
for…of 用来遍历实现了迭代器相关接口的数据结构或对象。
1. 使用 for … of 迭代数组对象:
<script>
let array = [1, 2, 3, 4, 5]
for (let item of array) {
console.log(item)
}
// 输出:
// 1
// 2
// 3
// 4
// 5
</script>
2. 使用 for … of 迭代字符串对象:
<script>
let str = '遍历字符串'
for (let char of str) {
console.log(char)
}
// 输出:
// 遍
// 历
// 字
// 符
// 串
</script>
3. 使用 for … of 迭代 Set 对象:
<script>
let set = new Set([1, 2, 3, 4, 5])
for (let item of set) {
console.log(item)
}
// 输出:
// 1
// 2
// 3
// 4
// 5
</script>
4. 使用 for … of 迭代 Map 对象:
<script>
let maps = new Map([['1', 2], ['3', 4]])
for (let [key, val] of maps) {
console.log(`${key}:${val}`)
}
// 输出:
// 1:2
// 3:4
</script>
5. 使用 for … of 迭代自定义对象: ( 遍历失败 )
<script>
let obj = {number: [1, 2, 3, 4, 5]}
for (let item of obj) {
console.log(item)
}
// 输出:
// Uncaught TypeError: obj is not iterable
</script>
二、迭代器接口
上面几个例子中,数组、字符串、Map、Set 之所以能够使用 for…of 遍历,是因为它们内部都定义了一个
函数 Symbol.iterator,而自定义对象使用 for…of 时会出现错误,正是因为它没有定义 Symbol.iterator 函数。
证明上述观点:
<script>
// 数组
let array = [1, 2, 3, 4, 5]
console.dir(array[Symbol.iterator]) // 输出:函数对象
// 字符串
let str = '遍历字符串'
console.dir(str[Symbol.iterator]) // 输出:函数对象
// set
let set = new Set([1, 2, 3, 4, 5])
console.dir(set[Symbol.iterator]) // 输出:函数对象
// map
let maps = new Map([['1', 2], ['3', 4]])
console.dir(maps[Symbol.iterator]) // 输出:函数对象
// 自定义对象
let obj = {number: [1, 2, 3, 4, 5]}
console.dir(obj[Symbol.iterator]) // 输出:undefined
</script>
其实想正确的被 for…of 遍历,只定义 Symbol.iterator 函数是不够的, 还需要两个重要接口的实现 Iterator 和 IteratorResult。
※ 接口是抽象的,其目的是定义标准,具体逻辑由对象自行实现。
1. Iterator 接口:
方法 | 返回值 | 描述 |
---|---|---|
next | 实现 IteratorResult 接口的对象 ( 每轮迭代的结果 ) | 具体的迭代逻辑 |
2. IteratorResult 接口:
属性 | 取值 | 描述 |
---|---|---|
done | true / false | 是否可以继续迭代 |
value | 任意值 | 返回给 for…of 中的数据 |
3. for…of 的正确运行需要3个接口搭配完成 Symbol.iterator、Iterator、
IteratorResult 三个缺一不可,通过这三个接口的描述,我们可以推断 for…of 的大致执行流程如下:
1. 执行 Symbol.iterator 函数,并得到返回的 Iterator 接口对象。
2. 执行 Iterator 接口对象的 next 方法,并得到返回的 IteratorResult 接口对象。
3. 判断 IteratorResult 接口对象的 done 属性,done 为 false 时, 将 value 属性赋值给 for…of 定义的
变量, 并执行 for…of 方法体,然后重复步骤 2 和 3,当 done 属性为 true 时退出 for…of 循环。
三、为对象自定义迭代器
有了上面对 for…of 运行机制的理解,那么就可以遵照这个规则,为对象自定义迭代器。
假设需求:
现有 person 对象,其有 name(姓名) 和 technology(技术) 两个属性,因为一个人会有多个技能,所以
technology是个数组类型,现在要求,让该对象可以被 for…of 遍历,并且遍历时把所有的 technology 罗列出来。
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>iterator</title>
</head>
<body>
<script>
// 定义对象
let person = {name: 'ares5k', technology: ['h5c3', 'vue', 'java', 'mysql', 'python']}
// 为对象自定义迭代器
person[Symbol.iterator] = () => {
let index = 0
let length = person.technology.length
return { // 此处返回的是 Iterator 接口的实现
next() {
return index < length ? { // 此处返回的是 IteratorResult 接口的实现
// 此处是先用 index 取值, 然后再对其进行自增( i++, ++i 相关理论)
value: person.technology[index++],
done: false
} : {
done: true
}
}
}
}
// 测试
for (let item of person) {
console.log(item)
}
// 输出:
// h5c3
// vue
// java
// mysql
// python
</script>
</body>
</html>