目录
1、迭代器是什么
2、迭代器实现遍历的原理
3、如何让对象实现for-of遍历
4、内置迭代器——keys()、values()、entries()
迭代器(Iterator)是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署Iterator接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。
Iterator 的作用有三个:一是为各种数据结构,提供一个统一的、简便的访问接口;二是使得数据结构的成员能够按某种次序排列;三是ES6创造了一种新的遍历命令for...of循环,Iterator接口主要供for...of消费。
Iterator接口就是对象里面的一个属性,这个属性叫做Symbol.iterator。而原生具备 Iterator 接口的数据结构有Array、Map、Set、String、TypedArray、arguments、NodeList ,它们都可以使用for-of来遍历。
举个:
我们使用for-of来遍历一下数组:
let arr = [23, 90, 45, 12, 'hello']
for (let i of arr) {
console.log(i);
}
返回结果如下:
数组可以使用for-of实现遍历,是因为数组对象里面有一个Symbol.iterator属性。我们可以打印看一下:
当我们使用for-of来遍历对象时:
let obj = {
name: 'zhangsan',
age: 19
}
for (let i of obj) {
console.log(i);
}
结果报错了:obj不可迭代
这是因为对象没有Iterator 接口,即对象原型上没有Symbol.iterator属性,所以无法使用for-of来遍历对象。
(1)创建一个指针对象(由Symbol.iterator来创建),指向当前数据结构的起始位置
(2)第一次调用对象的next方法,指针自动指向数据结构的第一个成员
(3)接下来不断调用next方法,指针一直往后移动,直到指向最后一个成员
(4)每次调用next方法都会返回一个包含value和done属性的对象(value 表示当前的值,done 表示遍历是否结束,true表示结束了,false表示还没有结束)
这实际上也是for-of实现遍历的原理。
let arr = [23, 90, 45, 12, 'hello']
for (let i of arr) {
console.log(i);
}
上面的数组能够实现遍历的原理如下:
let arr = [23, 90, 45, 12, 'hello']
// for (let i of arr) {
// console.log(i);
// }
// 1.使用Symbol.iterator创建一个迭代器对象
let iterator = arr[Symbol.iterator]()
// 2.调用迭代器对象的next方法,每次调用next方法,指针都会往后移动,直到指向最后一个成员
//每次调用next方法都会返回一个包含value和done属性的对象
console.log(iterator.next()); //{ value: 23, done: false }
console.log(iterator.next()); //{ value: 90, done: false }
console.log(iterator.next()); //{ value: 45, done: false }
console.log(iterator.next()); //{ value: 12, done: false }
console.log(iterator.next()); //{ value: "hello", done: false }
console.log(iterator.next()); //{ value: undefined, done: true }
console.log(iterator.next()); //{ value: undefined, done: true }
for-of 实现原理就是调用迭代器的next()方法,第一次调用将指针指向数据结构的第一个成员,依次调用依次指向,直到没有成员可以指向,done为true。
要想让对象也能够使用for-of来遍历,那么我们就需要给对象添加一个Iterator 接口,也就是在对象的原型上手动添加一个Symbol.iterator属性。
简单实现思路如下:
1、给对象添加一个iterator接口,即Symbol.iterator属性
2、Symbol.iterator创建一个指针对象,因此我们返回一个对象
3、指针对象里面有一个next方法
4、每次调用next方法返回一个包含value和done属性的对象
let obj = {
name: "zhangsan",
age: 19,
gender: 'male',
// 1.给对象添加一个iterator接口,即Symbol.iterator属性
[Symbol.iterator]() {
// 2.Symbol.iterator创建一个指针对象,因此我们返回一个对象
return {
// 3.指针对象里面有一个next方法
next: function () {
// 4.每次调用next方法返回一个包含value和done属性的对象
return {
value: '123',
done: false
}
}
}
}
}
for (let i of obj) {
console.log(i);
}
5、控制指针往后移动,并添加结束条件
let obj = {
name: "zhangsan",
age: 19,
gender: 'male',
// 1.给对象添加一个iterator接口,即Symbol.iterator属性
[Symbol.iterator]() {
// 设置索引变量
let index = 0
// 2.Symbol.iterator创建一个指针对象,因此我们返回一个对象
return {
// 3.指针对象里面有一个next方法
//这里需要使用箭头函数,否则this指向的就不是obj当前对象了
next: () => {
// 4.每次调用next方法返回一个包含value和done属性的对象
if (index < Object.values(this).length) {
// Object.values()方法返回对象的属性值构成的数组
let result = {value: Object.values(this)[index], done: false}
// 下标自增,让指针往后移动
index++
return result
} else {
//遍历结束
return {value: undefined, done: true}
}
}
}
}
}
for (let i of obj) {
console.log(i);
}
运行结果如下:
为了更好地访问对象的内容,ES6为数组、Set、Map集合内置了三个API:keys()、values()、和entries() ,它们都返回一个迭代器对象,因为它们都返回一个迭代器对象,因此我们可以使用它们的返回值去调用next()方法,从而实现遍历。其中:
keys() 用来遍历所有的键名;
values() 用来遍历所有的键值;
entries() 用来遍历[键名, 键值]组成的数组。(对于数组,键名就是索引值)
我们以数组为例,使用一下这些方法来实现遍历:
let arr = ['zhangsan', 'lisi', 'hello']
let keys = arr.keys();
let values = arr.values();
let entries = arr.entries();
console.log(keys.next()); //{ value: 0, done: false }
console.log(keys.next()); //{ value: 1, done: false }
console.log(keys.next()); //{ value: 2, done: false }
console.log(keys.next()); //{ value: undefined, done: true }
console.log(values.next()); //{ value: 'zhangsan', done: false }
console.log(values.next()); //{ value: 'lisi', done: false }
console.log(values.next()); //{ value: 'hello', done: false }
console.log(values.next()); //{ value: undefined, done: true }
console.log(entries.next()); //{ value: [ 0, 'zhangsan' ], done: false }
console.log(entries.next()); //{ value: [ 1, 'lisi' ], done: false }
console.log(entries.next()); //{ value: [ 2, 'hello' ], done: false }
console.log(entries.next()); //{ value: undefined, done: true }
for (let k of keys) {
console.log(k);
}
/*
上面的for-of输出结果:
0
1
2
*/
for (let k of values) {
console.log(k);
}
/*
上面的for-of输出结果:
zhangsan
lisi
hello
*/
for (let k of entries) {
console.log(k);
}
/*
上面的for-of输出结果:
[ 0, 'zhangsan' ]
[ 1, 'lisi' ]
[ 2, 'hello' ]
*/
// 手动调用next 遍历迭代器对象 {value:0 ,done:false}
let result;
while (!(result = values.next()).done) {
console.log(result);
}
/*
上面的while循环输出结果:
{ value: 'zhangsan', done: false }
{ value: 'lisi', done: false }
{ value: 'hello', done: false }
*/