Iterator 迭代器
Iterator 是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作。
1. Iterator 的作用
为各种数据结构,提供一个统一的、简便的访问接口
使得数据结构的成员能够按某种次序排列
ES6 创造了一种新的遍历命令 for…of 循环,Iterator 接口主要供 for…of 消费。
2. Iterator 的工作原理
创建一个指针对象,指向当前数据结构的起始位置
第一次调用next方法时,指针指向数据结构的第一个成员
接下来调用next方法,指针后移,直到指向最后一个成员
每次调用 next 方法都会返回一个值,这个值是一个 object,包含两个属性,value 和 done。value表示具体的返回值,done 是布尔类型的,表示集合是否遍历完成,完成则返回 true,否则返回 false。
3. 手写实现 iterator 接口
function myIterator(arr) {
let nextIndex = 0;
return {
next: function () {
return nextIndex < arr.length ? {
value: arr[nextIndex++],
done: false
} : {
value: undefined,
done: true
}
}
}
}
let arr = [1,2,'ljc']
let it = myIterator(arr)
console.log(it.next());
console.log(it.next());
console.log(it.next());
console.log(it.next());
console.log(it.next());
我们来分析一下上面的代码,myIterator 函数是一个遍历器生成函数,它的作用是返回一个遍历器对象,也是指针对象。
我们通过 next 方法来移动指针,next 方法内部通过闭包来保存指针nextIndex的值,每次调用 next 方法 nextIndex都会 +1 ,然后根据nextIndex 的值从数组内取出数据作为 value ,通过索引判断得到 done ,当无数据可用时,超过数组最大索引,无可用数据返回,此时 done 为 true
可迭代对象
了解过了 iterator,并且我们也已经知道了如何创建一个遍历器对象,但是这和我们先前所说的 for…of 循环有什么关系呢
这里首先我们需要了解一下 for…of 的运行机制
当 for…of循环执行时,循环内部会自动调用这个对象上的迭代器方法Symbol.iterator , 依次执行迭代器对象的 next 方法,将 next 方法的返回值赋值给 for …of 内的变量,从而得到具体的值,实现遍历。
4. 手写实现可迭代对象
一个数据结构只要具有 Symbol.iterator 属性,就可以认为是“可遍历的”。
Symbol.iterator 属性本身是一个函数,就是当前数据结构默认的遍历器生成函数,执行这个函数,就会返回一个迭代器对象。
也就是说要实现可迭代对象只要在对象上部署了Symbol.iterator属性,为它创建一个迭代器方法就可以了
let iteratorObj = {
items: [1, 2, 'ljc'],
// 部署Symbol.iterator属性
[Symbol.iterator]: function () {
let self = this
let i = 0
return {
next: function () {
// 类型转化为Boolean
let done = (i >= self.items.length)
// 数据确认
let value = !done ? self.items[i++] : undefined
// 数据返回
return {
done,
value
}
}
}
}
}
for (let item of iteratorObj) {
console.log(item); // 1 2 ljc
}
显然实现了 iterator 接口,可以被 for…of 成功遍历
5. Iterator 原生应用场景
有些对象我们并没有为它们部署 Iterator 接口,但是仍然可以使用 for…of 进行遍历。这是因为在ES6中有些对象已经默认部署了这个接口。
原生具备 Iterator 接口的数据结构:
- Array
- set容器
- map容器
- String
- 函数的 arguments 对象
- NodeList 对象
Array
在数组上成功的找到了 Symbol.iterator 方法,并能够执行返回迭代器对象,同时验证了for…of循环成功执行
let arr = [1, 2, 3]
let it = arr[Symbol.iterator]()//返回迭代器对象
console.log(it.next());
console.log(it.next());
console.log(it.next());
console.log(it.next());
for (let item of arr) {
console.log(item);
}
Q&A
看到这里你可能会想,为什么这么多数据结构都实现了默认部署,为什么偏偏对象没有呢?
当然是有原因的
对象可能有各种各样的属性,不像数组的值是有序的,所以对对象遍历时根本不知道如何确定先后顺序,所以需要我们手动实现
提前退出循环
普通的 for 循环是可以随时中断的,for…of 循环作为 for 和 forEach 的升级版同样是可以的
迭代器对象除了有 next 方法,还有两个可选方法 return 方法和 throw 方法
return 方法的使用场景是,当 for…of 循环提前退出,就会调用 return 方法。
需要特别注意的是,return 方法必须有一个 object 类型的返回值
我们在前面代码的基础上添加上 return 方法,并在 for…of 循环中采用 break 语句来中断循环,循环提前退出,自动调用 return 方法输出提前退出
let iteratorObj = {
items: [1, 2, 'ljc'],
// 部署Symbol.iterator属性
[Symbol.iterator]: function () {
let self = this
let i = 0
return {
next: function () {
// 类型转化为Boolean
let done = (i >= self.items.length)
// 数据确认
let value = !done ? self.items[i++] : undefined
// 数据返回
return {
done,
value
}
},
return () {
console.log('提前退出');
return {
done: true
}
}
}
}
}
for (let item of iteratorObj) {
console.log(item); // 1
break;
}
注意
如果采用抛出异常的方式退出,会先执行 return 方法再抛出异常
关于 throw 方法会在下篇生成器文章中提到
6.Iterator 接口使用场景
除了 for…of 循环会自动调用 iterator 接口之外,还有几个场景也会自动调用
- 解构赋值
对可迭代对象进行解构赋值时,会默认调用 Symbol.iterator 方法
let map = new Set().add('a').add('b');
let [x, y] = map
console.log(x, y, map) // a b Set(2) {"a", "b"}
由于解构赋值适用于可迭代对象,那么我们对自己自定义的可迭代对象解构赋值试试
let iteratorObj = {
items: [1, 2, 'ljc'],
// 部署Symbol.iterator属性
[Symbol.iterator]: function () {
let self = this
let i = 0
return {
next: function () {
// 类型转化为Boolean
let done = (i >= self.items.length)
// 数据确认
let value = !done ? self.items[i++] : undefined
// 数据返回
return {
done,
value
}
}
}
}
}
let [a, b, c] = iteratorObj
console.log(a, b, c)// 1 2 'ljc'
成功的实现了解构赋值
2. 扩展运算符
扩展运算符也会默认调用Symbol.iterator方法,可以将当前数据结构转化为数组
// 阮老师的例子
var str = 'hello';
[...str] // ['h','e','l','l','o']
let arr = ['b', 'c'];
['a', ...arr, 'd']
// ['a', 'b', 'c', 'd']
- yield*
yield*
后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口。
let generator = function* () {
yield 1;
yield* [2,3,4];
yield 5;
}
总结
在 ES6 中新增了新的数据结构,为了提供一种统一的遍历方法,新增了 for…of 方式。而 for…of 执行的时候会自动调用迭代器来取值
只有实现了 Iterator 接口的对象才能采用 for…of
迭代器是一个返回迭代器对象的方法
ES6 中很多场景都采用了 Iterator ,可以多注意一下,新的东西往往是向上的