(六) Iterator 迭代器
1. 迭代、可迭代对象
1.1 迭代 iteration
维基百科给出的定义是: 迭代是重复反馈过程的活动, 每一次对过程的重复被称为一次迭代
在编程语言中, 我们通过像 for
循环的方式来遍历某个数据, 这种遍历我们称为迭代 (iterator)
for (let i = 0; i < 5; i++) {
// 每一次打印输出就是一次迭代
console.log(i)
}
1.2 可迭代对象
对象
在学习python
的过程中, 我了解到python中的万物皆对象, 后来在刚开始学习js的过程中, 在某些博客/帖子中看到关于js中万物皆对象的描述, 随着学习时间的推移, 对js一些机制的逐步学习了解, 才发现所谓的万物皆对象是错误的说法
所以这里针对js描述的可迭代对象
中的对象
二字, 我们暂且将它看作是一类目标事物, 而不是object类型
可迭代对象
根据上面迭代的概念, 我们暂且将可迭代对象
定义为: 一类可进行遍历的事物, 在es6之前, 他们是
- 字符串
- 数组
- 类数组
2. 迭代器 iterator
2.1 为什么需要迭代器
当我们去遍历一个数组时, 我们可以通过像for循环, forEach等方法, 遍历对象(键)时可以通过for...in , 遍历Map、Set时可以通过forEach
但是, 你有没有发现, 我们遍历不同的数据类型, 要用不同的方式, 是不是有点不够统一, 我遍历个数据,还要转换, 封装, 甚至有可能还要操作原型, 烦不烦? 既然如此, 那为什么不设计一个通用的方式, 让不同的数据结构都可以通过我这一种方式来完成遍历呢 ?这就是ES6设计迭代器的原因 (我猜是)
2.2 什么是迭代器
迭代器 iterator
是为各种不同的数据结构提供的统一访问机制, 是专门用于迭代的对象, 并带有特定接口
- 迭代器是一个对象
- 内部的特定接口专门处理迭代过程
2.3 迭代器的内部接口是什么
es6规定: 所有的迭代器对象都有一个next()
方法, 该方法的返回值是一个结果对象, 结果对象有两个属性: value和done
, 前者表示当前成员值, 后者是一个标识, 用于标识原数据集是否已经遍历完, 这个next()
方法就是接口
也是用到了闭包
// 利用es5的语法来实现一个迭代器, 调用createIterator返回的对象是迭代器
function createIterator(items) {
var i = 0;
return {
next: function () {
var done = (i >= items.length);
var value = !done ? items[i++] : undefined;
return {
done: done,
value: value
}
}
}
}
var iterator = createIterator([1, 2, 3])
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: false }
console.log(iterator.next()); // { value: undefined, done: true }
2.4 这个接口是干什么的
上面提到: 接口是专门用来处理迭代过程的, 通过代码演示也可以发现, 通过不断的调用next()
方法, 可以遍历出数组的成员, 但是显然, 迭代器接口不是让我们手动的一次次的去调用它的, 所以es6定义了一些专门调用这个迭代器接口的方法, 比如: for...of
当我们使用for...of
遍历某种数据结构时, 该方法就会自动去寻找该数据结构上的迭代器接口
- 注意看仔细: 迭代器接口是定义在数据结构上的 !
而一旦某种数据结构上定义了迭代器接口, 我们就称这种数据结构是 可迭代的 (iterable)
知道了这个, 还有一个问题, 那就是迭代器接口是怎么定义在数据结构上的 ?
- es6规定, 默认的迭代器接口是定义在数据结构的
Symbol.iterator
属性(该属性是一个方法)上的,
也就是说, 一个数据结构只要定义了 Symbol.iterator
属性, 它就是 可迭代的
@我们将上面的捋一捋
- 迭代器是一个对象
- 迭代器接口是迭代器内部实现迭代的方法
- for...of方法内部实现原理是调用迭代器接口
- 而迭代器接口被定义在数据结构的Symbol.iterator属性上
那么也就意味着, 只要你有 Symbol.iterator
属性, 那你就是一个可迭代对象
, 那你就可以通过for...of
遍历
所以, 最开始提到的可迭代对象的概念以及哪些是可迭代对象, 我们应该重新定义一下了:
- 可迭代对象是含有
Symbol.iterator
属性的数据结构, 他们分别是- 字符串
- 数组
- 类数组 (arguments / NodeList => DOM)
- Set
- Map
对象不具备迭代器接口
3. for...of
我们来看一看for...of
方法, 上面说了, for...of
通过调用迭代器接口完成遍历操作
示例
let arr = [1, 2, 3]
let str = 'hello world'
let set = new Set([4, 5, 6])
let map = new Map([['key', 'value']])
// 遍历数组
for (const i of arr) {
console.log(i)
}
// 遍历字符串
for (const i of str) {
console.log(i)
}
// 遍历Set
for (const i of set) {
console.log(i);
}
// 遍历Map
for (const i of map) {
console.log(i)
}
// 遍历类数组 DOM的NodeList对象
let domList = document.querySelectorAll('li')
for (const i of domList) {
console.log(i)
}
是不是就是我们一开始所说的那个 统一的方法