Iterator(遍历器)
概念
是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构(array,object,map和set)只要部署Iterator接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)
作用
- 为各种数据结构,提供一个统一的。简便的访问接口;
- 使得数据结构的成员能够按照某种次序排列
- ES6创造了一种新的遍历命令for…of循环,Iterator接口主要提供for…of消费
遍历过程
- 创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历对象本质上,就是一个指针对象
- 第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员
- 第二次调用指针对象的next方法,指针就指向数据结构的第二个成员
- 不断调用指针对象的next方法,直到它指向数据结构的结束位置
每一次调用next方法,都会返回数据结构的当前成员的信息——返回一个包含value和done两个属性的对象。其中,value属性是当前成员的值,done属性是一个布尔值,表示遍历是否结束。
例子
//模拟next方法返回值的例子
var it = makeIterator(['a','b'])
let r1 = it.next()
let r2 = it.next()
let r3 = it.next()
console.log(r1,r2,r3)
function makeIterator(array){
var nextIndex = 0;
return {
next: function() {
return nextIndex < array.length ? {
value: array[nextIndex++],done:false
}:{
value: undefined,
done: true
}
}
}
}
对于遍历对象来说,done:false和value:undefined属性是可以省略的,因此可简写:
//模拟next方法返回值的例子
var it = makeIterator(['a','b'])
let r1 = it.next()
let r2 = it.next()
let r3 = it.next()
console.log(r1,r2,r3)
function makeIterator(array){
var nextIndex = 0;
return {
next: function() {
return nextIndex < array.length ? {
value: array[nextIndex++]
}:{
done: true
}
}
}
}
默认Iterator接口
一种数据结构只要部署了Iterator接口,就可以称这种数据结构使“可遍历的”(iterable)
ES6规定,默认的Iterator接口部署在数据结构的Symbol.iterator属性,或者说,一个数据结构只要具有Symbol.iterator属性,就可以认为是“可遍历的”(iterable)
Symbol.iterable属性本身就是一个函数,就是当前数据结构默认的遍历器生成的函数。执行这个函数,就会返回一个遍历器。属性名Symbol.iterator是一个表达式,返回Symbol对象的iterator属性,这是一个预定好的,类型为Symbol的特殊值。例如下面这个obj
const obj = {
[Symbol.iterator]: function (){
return {
next: function (){
return {
value: 1,
done: true
}
}
}
}
}
原生具备Iterator接口的数据结构:
array、map、set、string、typedArray、函数的arguments对象、nodeList对象
对于原生部署Iterator接口的数据结构,不用自己写遍历器生成函数,for…of循环会自动遍历它们。其他函数都要自己在Symbo.iterator属性上面部署,才可以被for…of循环遍历。(原型链上的对象具有该方法也可)
class RangeIterator{
constructor(start, stop){
this.value = start;
this.stop = stop;
}
[Symbol.iterator]() {return this;}
next(){
var value = this.value;
if(value < this.stop){
this.value++;
return {done: false,value:value};
}
return {done: true,value:undefined};
}
}
function range(start,stop){
return new RangeIterator(start, stop);
}
for(var value of range(0,3)){
console.log(value)
}
为对象添加Iterator接口
let obj = {
data:['hello','world'],
[Symbol.iterator](){
const self = this;
let index = 0;
return {
next() {
if(index < self.data.length){
return {
value:self.data[index++],
done:false
};
}else{
return {value:undefined,done:true};
}
}
}
}
}
for (let item of obj){
console.log(item)
}
对于类似数组的对象(存在数值键名和length属性),部署Iterator接口,有一个简便的方法——Symbol.Interator方法直接引用数组的Iterator接口
NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator]
//或者
NodeList.prototype[Symbol.iterator] = [][Symbol.iterator]
[...document.querySelectorAll('div')]//可以执行
let iterable = {
0: 'a',
1:'b',
2:'c',
length:3,
[Symbol.iterator]:Array.prototype[Symbol.iterator]
};
for(let item of iterable){
console.log(item)
}
NodeList对象是类似数组的对象,本身就具有遍历接口,可直接遍历。但上述代码将其遍历接口替换成Symbol。iterator属性,没有发生任何变化
对于普通对象,部署数组的Symbol。iterator方法无效果
调用Iterator接口的场合
- 解构赋值
对数组和set结构进行解构赋值时,会默认调用Symbol.iterator方法
let set = new Set().add('a').add('b').add('c')
let [first,...rest] = set
console.log(first)
console.log(rest)
2. 扩展运算符
只要某个数据结构部署了Iterator接口,就可以对其使用扩展运算符,将其转为数组
var str = 'hello'
console.log([...str])
3. yield*
yield*后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口
let generator = function* () {
yield 1;
yield* [2,3,4];
yield 5;
};
var iterator = generator()
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
4. 其他场合
5. 由于数组的遍历会调用遍历器接口,所以任何接受数组作为参数的场合,其实都调用了遍历器接口
例如:
- for…of
- Array.from()
- Map() Set() WeakMap() WeakSet()
- Promise.al()
- Promise.race()
字符串的Iterator接口
字符串是一个类似数组的对象,也原生具有Iterator接口
var someString = 'hi'
console.log(typeof someString[Symbol.iterator])
var iterator = someString[Symbol.iterator]()
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
也可以覆盖原生的Symbol.iterator方法,达到修改遍历器行为的目的(log出来还是有点不一样)
var str = new String('hi')
console.log([...str])
console.log(str)
str[Symbol.iterator] = function(){
return {
next: function() {
if(this._first){
this._first = false;
return {value:'bye',done:false};
}else{
return {done: true};
}
},
_first:true
}
}
console.log([...str])
console.log(str)
遍历器对象的return(),throw()
return()
使用场合: 如果for…of循环提前退出(通常是因为出错,或者有break语句),就会调用return方法。如果一个对象在完成遍历前,需要清理或释放资源,就可以部署return方法。
for…of与array.forEach()
for…of可以用break、continue和return配合使用,但是forEach()不可以
for (var n of fibonacci) {
if (n > 1000)
break;
console.log(n);
}