概述
本次文章主要探讨Iterator (遍历器/迭代器)循环
Iterator
Iterator(遍历器)的概念
遍历器(Iterator)就是一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作。简单理解就是可以一次拿到容器对象中的数据,这样就方便我们进行后续的操作。 (Iterator(遍历器)也称迭代器)
Iterator 的作用
Iterator 的作用有三个:一是为各种数据结构,提供一个统一的、简便的访问接口;二是使得数据结构的成员能够按某种次序排列;三是 ES6 创造了一种新的遍历命令 for...of 循环,Iterator 接口主要供 for...of 消费 。
Iterator 的工作原理
- 创建一个指针对象,指向当前数据结构的起始位置(遍历器对象本质上就是一个指针对象)。
- 第一次调用对象的 next 方法,指针自动指向数据结构的第一个成员。
- 接下来不断调用 next 方法,指针一直往后移动,直到指向最后一个成员。
- 每调用 next 方法返回一个包含 value 和 done 属性的对象 ,其中value属性是当前成员的值,done属性是一个布尔值,表示遍历是否结束。
案例:
//声明一个数组
const xiyou = ['唐僧', '孙悟空', '猪八戒', '沙僧']
//使用 for...of 遍历数组
for(let v of xiyou){
console.log(v);
}
let iterator = xiyou[Symbol.iterator]()
//调用对象的next方法
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
运行结果:
Iterator协议及使用规则
ES6 规定,默认的 Iterator 接口部署在数据结构的 Symbol.iterator 属性,或者说,一个数据结构只要具有 Symbol.iterator 属性,就可以认为是“可遍历的”(iterable)。 Symbol.iterator 属性本身是一个函数,就是当前数据结构默认的遍历器生成函数。执行这个函数,就会返回一个遍历器。至于属性名 Symbol.iterator ,它是一个表达式,返回 Symbol 对象的 iterator 属性,这是一个预定义好的、类型为 Symbol 的特殊值,所以要放在方括号内。
ES6 的有些数据结构原生具备 Iterator 接口(比如数组),即不用任何处理,就可以被 for...of 循环遍历。原因在于,这些数据结构原生部署了Symbol.iterator 属性,另外一些数据结构没有(比如对象)。凡是部署了 Symbol.iterator 属性的数据结构,就称为部署了遍历器接口。调用这个接口,就会返回一个遍历器对象。
原生具备 Iterator 接口的数据结构如下:
- Array
- Map
- Set
- String
- TypedArray
- 函数的 arguments 对象
- NodeList 对象
案例:
let arr = ['a', 'b', 'c']
let iter = arr[Symbol.iterator]()
iter.next() // { value: 'a', done: false }
iter.next() // { value: 'b', done: false }
iter.next() // { value: 'c', done: false }
iter.next() // { value: undefined, done: true }
案例解读:
上面代码中,变量 arr 是一个数组,原生就具有遍历器接口,部署在 arr 的 Symbol.iterator 属性上面。所以,调用这个属性,就得到遍历器对象。
对于原生部署 Iterator 接口的数据结构,不用自己写遍历器生成函数, for...of 循环会自动遍历它们。除此之外,其他数据结构(主要是对象)的Iterator 接口,都需要自己在 Symbol.iterator 属性上面部署,这样才会被 for...of 循环遍历。
自定义遍历器(迭代器)
对象是没有实现迭代器,所以不能遍历对象,为了可以实现对象的遍历,我们需要在对象上实现上面说的迭代器。
案例:
//声明一个对象
const banji = {
name: '终极一班',
stus: ['xiaoming', 'xiaoning', 'xiaotian', 'knight'],
[Symbol.iterator]() {
//索引变量
let index = 0
//保存this指向
let _this = this
return {
next: function () {
if (index < _this.stus.length) {
const result = { value: _this.stus[index], done: false }
//下标自增
index++
//返回结果
return result
} else {
return { value: undefined, done: true }
}
}
}
}
}
//遍历这个对象
for (let v of banji) {
console.log(v)
}
运行结果:
调用 Iterator 接口的场合
有一些场合会默认调用 Iterator 接口(即 Symbol.iterator 方法)。
(1)解构赋值
对数组和 Set 结构进行解构赋值时,会默认调用 Symbol.iterator 方法。
(2)扩展运算符
扩展运算符(...)也会调用默认的 Iterator 接口。
(3)其他场合(还有yield*,此处不做深究)
由于数组的遍历会调用遍历器接口,所以任何接受数组作为参数的场合,其实都调用了遍历器接口。下面是一些例子:
- for...of
- Array.from()
- Map(), Set(), WeakMap(), WeakSet()(比如 new Map([['a',1],['b',2]]) )
- Promise.all()
- Promise.race()