📜 名词解释
🎈 迭代
在软件开发领域,“迭代”的意思是按照顺序反复多次执行一段程序。
🎈 可迭代对象(iterable)
可迭代对象就是指一个包含可以在其值上迭代的迭代器的对象。
从一个可迭代对象中提取迭代器的方法是:可迭代对象必须支持一个函数,其名称是专门的ES6符号值Symbol.iterator
。调用这个函数时,它会返回一个迭代器。
比如,下面代码中的a就是可迭代对象:
const a = [1,3,5,6]
const it = a[Symbol.iterator]()
it.next().value // 1
it.next().value // 3
it.next().value // 5
it.next().value // 6
it.next().value // undefined
🧮 迭代器
🤓 什么是迭代器
如果一个对象有next()
方法,调用此方法会返回具有value
(任意类型)和done
(布尔类型)的结果对象,那它就是个迭代器对象。
function createIterator(limit) {
let value = 1
return {
next() {
if (value < limit) {
return {
value: value++,
done: false,
}
} else {
return {
value: undefined,
done: true,
}
}
},
}
}
const it = createIterator(3)
it.next() // { value: 1, done: false }
it.next() // { value: 2, done: false }
it.next() // { value: undefined, done: true }
it.next() // { value: undefined, done: true }
🤔 为什么会有迭代器
下面的代码是个简单的迭代操作:
const arr = ['a', 'b', 'c', 'd']
for (let i = 0, len = arr.length; i < len; i++) {
console.log(arr[i])
}
上面的代码通过变量i
来跟踪arr
数组的索引,虽然简单,但如果将多个循环嵌套则需要追踪多个变量,代码的复杂度会大大增加。迭代器的出现旨在消除这种复杂性并减少循环中的错误。
🤯 你应该知道的
👣 迭代器的工作流程
每次调用迭代器的next()
方法,都返回一个结果对象,此结果对象有两个属性:一个是value
,表示下一个将要返回的值;另一个是done
,是一个布尔类型的值,当没有可返回的数据时,其值为true
,否则为false
。
如果在最后一个值返回后,再调用next()
方法,结果对象的value属性值就不是数据集的一部分,跟函数类似,如果没有明确指定,则默认返回undefined
。
🦈 迭代器和可迭代对象是分离的
迭代器就像个指针对象,它的next()
方法用于移动指针。
迭代器和可迭代对象是分开的,执行迭代器的next()
方法,其实是去可迭代对象中找数据,如果可迭代对象在迭代期间被修改了,那么迭代器也会反映相应的变化。
let arr = ['foo', 'baz']
const it = arr[Symbol.iterator]()
it.next() // { value: "foo", done: false }
// 在数组中间插入值
arr.splice(1, 0, 'bar')
it.next() // { value: "bar", done: false }
it.next() // { value: "baz", done: false }
it.next() // { value: undefined, done: true }
it.next() // { value: undefined, done: true }
⛔ 若迭代提前关闭,要如何处理
可选的return()
方法用于指定在迭代器提前关闭时执行的逻辑。
提前关闭可能的情况包括:
for-of
循环通过break
、continue
、return
或throw
提前退出。
class Test {
limit = 1
constructor(limit) {
this.limit = limit
}
[Symbol.iterator]() {
let i = 1
let limit = this.limit
return {
next() {
let done = i < limit ? false : true
return { value: i++, done }
},
return() {
console.log('提前终止喽')
return { done: true }
},
}
}
}
const t = new Test(5)
for (let item of t) {
if (item === 3) {
break
}
console.log(item)
}
// 1
// 2
// 提前终止喽
- 解构操作并未消费所有值。
🐎 用生成器可以让创建迭代器的过程变简单
通过前面的示例代码可以看出迭代器的编写规则是较复杂的。
ES6中引入了生成器对象,它可以让创建迭代器的过程变得简单。因为生成器就是一种返回迭代器的函数,它的调用方式与普通函数相同,只不过返回的是个迭代器。
function* createIterator(limit) {
for (let i = 1; i < limit; i++) {
yield i
}
}
const it = createIterator(3)
console.log(it.next().value) // 1
console.log(it.next().value) // 2
console.log(it.next().value) // undefined
📦 内建迭代器
可迭代对象具有Symbol.iterator
属性,通过该属性就可以获取到对象的默认迭代器。
const values = [1, 2, 3]
const it = values[Symbol.iterator]()
it.next() // { value: 1, done: false }
it.next() // { value: 2, done: false }
it.next() // { value: 3, done: false }
it.next() // { value: undefined, done: true }
那要这么说的话,有默认的迭代器,对应的就得有非默认的迭代器。事实还真是这样,在ES6中已经为许多内建类型提供了内建迭代器。
集合迭代器
ES6中有三种集合对象:数组、Set
、Map
。这三种对象都内建了三种迭代器。
entries()
返回一个迭代器,其值为多个键值对。
const arr = ['red', 'green', 'blue']
const set = new Set([123, 456, 789])
let map = new Map()
map.set('name', '张三')
map.set('age', 18)
// 数组返回值的第一个元素是数字型索引
for (let entry of arr.entries()) {
console.log(entry) // [ 0, "red" ], [ 1, "green" ], [ 2, "blue" ]
}
// Set集合中的值被同时作为键和值使用
for (let entry of set.entries()) {
console.log(entry) // [ 123, 123 ], [ 456, 456 ], [ 789, 789 ]
}
for (let entry of map.entries()) {
console.log(entry) // [ "name", "张三" ], [ "age", 18 ]
}
注意:数组返回值的第一个元素是数字型索引,Set
集合中的值被同时作为键和值使用。
values()
返回一个迭代器,其值为集合的值。
const arr = ['red', 'green', 'blue']
const set = new Set([123, 456, 789])
let map = new Map()
map.set('name', '张三')
map.set('age', 18)
for (let value of arr.values()) {
console.log(value) // "red", "green", "blue"
}
for (let value of set.values()) {
console.log(value) // 123, 456, 789
}
for (let value of map.values()) {
console.log(value) // "张三", 18
}
keys()
返回一个迭代器,其值为集合中的所有键名。
const arr = ['red', 'green', 'blue']
const set = new Set([123, 456, 789])
let map = new Map()
map.set('name', '张三')
map.set('age', 18)
for (let key of arr.keys()) {
console.log(key) // 0, 1, 2
}
for (let key of set.keys()) {
console.log(key) // 123, 456, 789
}
for (let key of map.keys()) {
console.log(key) // "name", "age"
}
重点来了:数组和Set
集合的默认迭代器是values()
,Map
集合的默认迭代器是entries()
。
字符串迭代器
自ES5发布后,字符串越来越像数组啦,比如,可以通过方括号访问字符串中的字符。
const str = 'hello'
console.log(str[0]) // "h"
console.log(str[1]) // "e"
可以使用for-of
语句迭代字符串的每个字符。
const str = 'hello'
for (const item of str) {
console.log(item) // "h", "e", "l", "l", "o"
}
NodeList
迭代器
DOM
定义中的NodeList
类型(定义在HTML标准而不是ES6标准中),也有默认迭代器,其与数组的默认迭代器完全一致。
<p id="p1">张三</p>
<p id="p2">李四</p>
<p id="p3">王五</p>
<script>
const nodes = document.querySelectorAll('p')
for (const item of nodes) {
console.log(item.id) // p1, p2, p3
}
</script>
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。