迭代器
遍历器(Iterator)是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。
(1) Iterator
的作用
- 为各种数据结构,提供一个统一的、简便的访问接口
- 使得数据结构的成员能够按某种次序排列
- ES6 创造了一种新的遍历命令for...of循环,Iterator 接口主要供for...of消费。
(2) Iterator
的迭代过程
所有的迭代器对象都会有一个next
方法,每次调用都会返回一个对象:{done: boolean, value: any}
。value
表示当前成员的值,done
表示是否还有更多的数据。迭代器内部会维护一个指针,指向当前成员的位置,每次调用next
都会指向下一个成员。
// es5实现迭代器
function creatIterator(arr){
let index = 0;
return {
next: () =>{
return {
done: index > arr.length - 1,
value: this.done ? undefined : arr[index++]
}
}
}
}
const iterator = creatIterator([1, 2, 3])
console.log(a.next()) // { done: false, value: 1 }
console.log(a.next()) // { done: false, value: 2 }
console.log(a.next()) // { done: false, value: 3 }
console.log(a.next()) // { done: true, value: undefined }
// 之后的调用都会返回相同的内容
console.log(a.next()) // { done: true, value: undefined }
生成器
生成器是一种返回迭代器的函数。通过function
后面的*
来表明它是一个生成器。
yield
关键字是es6
的新特性,它可以指定调用next
方法时的返回值以及调用顺序。- 每当执行完
yield
语句,函数就会停止执行,直到再次调用next
方法才会继续执行yield
关键字只能在生成器内部使用,其他地方会导致语法错误
function * createIterator(){
yield 1
yield 2
}
// 生成器返回的是一个迭代器
const iterator = createIterator()
console.log(iterator .next()) // { done: false, value: 1 }
console.log(iterator .next()) // { done: false, value: 2 }
console.log(iterator.next()) // { done: true, value: undefined }
使用函数表达式:const createIterator = function *(){ yield 1 }
,但不能使用箭头函数来创建生成器。
这里仅仅介绍了生成器可以返回迭代器,但它主要的作用是流程管理,因此可以使用同步的方式书写异步代码,generator详情
可迭代对象
(1) Symbol.iterator
属性
一个数据结构只要具有Symbol.iterator
属性,就可以认为是可迭代的
- 所有的集合对象
array、set、map
以及string
都是可迭代对象,都有默认的迭代器,即Symbol.iterator
属性- 生成器生成的对象就是可迭代的,生成器会默认为他们的
Symbol.iterator
属性赋值- 通过执行
Symbol.iterator
方法 来获取对象的迭代器
访问数组的默认迭代器:
const arr = [1, 2, 3]
// 通过执行数组的 `Symbol.iterator`方法来获取 `arr` 的迭代器
const arrIterator = arr[Symbol.iterator]()
console.log(arrIterator.next()) // { value: 1, done: false }
console.log(arrIterator.next()) // { value: 2, done: false }
console.log(arrIterator.next()) // { value: 3, done: false }
console.log(arrIterator.next()) // { value: undefined, done: true }
(2) for...of
for...of
封装了next
的重复调用过程,来自动执行迭代器,遍历访问可迭代对象的成员。它通过对象的Symbol.iterator
方法来获取迭代器,并在每次循环中调用可迭代对象的next
方法,将返回值根据迭代器的规则赋给中间变量,一直循环到done
属性为true
。即
const arr = [1, 2, 3]
for(let item of arr){
console.log(item)
}
// 1 2 3
(3) 创建可迭代对象
可以通过给一个不可迭代对象设置Symbol.iterator
属性,使它变成可迭代的。Symbol.iterator
必须是迭代器生成函数,否则使用for...of
会报错。
// es5
const list = {
items: [1,2,3],
[Symbol.iterator](){
let index = 0;
return {
next: () =>{
return {
done: index > this.items.length - 1,
value: this.done ? undefined : this.items[index++]
}
}
}
}
}
for (const item of list) {
console.log(item)
}
// 1 2 3
// es6
const list = {
items: [1,2,3],
*[Symbol.iterator](){
for(item of this.items){
yield item
}
}
}
for (const item of list) {
console.log(item)
}
// 1 2 3
(3) 内置迭代器
entries、values、keys
都是es6
新增的迭代器。下表为各种集合类型的各种迭代器在for...of
循环中的中间变量:
集合类型/迭代器 | entries |
values |
keys |
---|---|---|---|
array |
[[index, value]] |
[value] ✔ |
[index] |
map |
[[key, value]] ✔ |
[value] |
[key] |
set |
[[value, value]] |
[value] ✔ |
[value] |
每个集合类型都有默认的迭代器,在for... of
循环中,如果没有显式的指定,则使用默认的迭代器。上表中的✔表示各集合类型的for... of
默认迭代器
const arr = [1,2,3]
// 没有显示指定迭代器,则使用array默认的迭代器,即values迭代器
for (const item of arr) {
console.log(item)
}
// 1 2 3
// 使用显示指定的entries迭代器
for (const item of arr.entries()) {
console.log(item)
}
// [0,1] [1,2] [2,3]
需要注意的是:
entries、values、keys
请勿与Object.entries、Object.values、Obejct.keys
混淆。前者返回的是迭代器,用于for...of
循环;后者返回的是根据原对象格式化后的数组。
(4) 字符串迭代器
在es5
中也可以使用for
循环遍历字符串,但却以编码为单元而非字符,因此无法正确访问双字节字符。es6
中全面支持unicode
,所以for...of
循环就可以正确遍历字符串中的双字节字符
const str = '1'
for (let i = 0; i < text.length; i++) {
console.log(text[i]);
}
// " "
// " "
// "1"
// 可以正确识别双字节字符
for (let i of text) {
console.log(i);
}
// ""
// "1"
(5) NodeList
迭代器
DOM
标准中有一个NodeList
类型,用于表示页面文档元素的集合。它含有length属性;可以通过[number]
访问元素,跟数组的格式和操作方法很类似,但其实它是对象,也就是我们俗称的伪数组,比如:{0: domObject, 1: domObject, 2: domObject, length:3}
。
es6
中NodeList
类型也拥有了默认迭代器,可以使用for...of
迭代
const nodeList = document.getElementsByClassName('abc')
for (const node of nodeList) {
console.log(node)
}
(6) 展开运算符
展开运算符可以操作所有可迭代对象,并根据默认迭代器来选取要引用的值,然后读取所有值
const nodeList = document.getElementsByClassName('abc')
console.log([...nodeList])
// [node, node, node]
const map = new Map()
map.set('name','li yang')
map.set('age', 18)
console.log([...map]) // 调用了map的默认迭代器 entries
// [["name", "li yang"], ["age", 18]]
由于展开运算符可以作用于任意可迭代对象,因此要将可迭代对象装换成数组,这是最简单的方法