迭代器是一种设计模式,可在容器对象如:链表、数组上遍历,无需关心容器对象的内存分配的实现细节。简单的理解就是可以一个一个的依次拿到其中的数据,类似一个移动的指针,但是会告诉我们什么时候结束。这样我们拿到数据之后可以做一些需要做的事情。
一、什么是迭代器
在JAVAScript中迭代器是一个特殊对象,这个迭代器对象有一个next()方法,每次调用都返回一个对象(结果对象)。结果对象有两个属性:一个是value,表示下一个将要返回的值;另一个是done,它是一个布尔类型的值,如果已经迭代到序列中的最后一个值,则它为 true。迭代器还会保存一个内部指针,用来指向当前集合中值的位置,每调用一次next()方法,都会返回下一个可用的值,类似下面这个对象的结构。
{
next: function () {
return {
value:'',
done: true / false
}
}
}
随着JavaScript语言的能力进一步提升,新增了一些新的数据类型 如 Map、Set、WeakMap 等,为了这些不同的数据结构,可以统一的迭代,es6 增加了迭代协议这个东西。
迭代协议并不是新的内置实现或语法,而是协议。这些协议可以被任何遵循某些约定的对象来实现。
迭代协议具体分为两个协议:可迭代协议和迭代器协议。
可迭代协议
要成为可迭代对象, 一个对象必须实现 @@iterator 方法。这意味着对象(或者它原型链上的某个对象)必须有一个键为 @@iterator 的属性,可通过常量 Symbol.iterator 访问该属性:
简单的理解,你想让一个东西可以遍历,那么这个东西要有一个 @@iterator ,这个属性可以通过Symbol.iterator 访问
很多内置类型都实现了 Iterator 接口。包括字符串、数组、Map、Set、arguments 对象。
// 没有实现迭代器工厂函数的类型
const num = 1;
const obj = {};
console.log(num[Symbol.iterator]); // undefined
console.log(obj[Symbol.iterator]); // undefined
// 实现了迭代器工厂函数的类型
const arr = [1, 2, 3];
const set = new Set([1, 2, 3]);
console.log(arr[Symbol.iterator]); // [Function: values]
console.log(set[Symbol.iterator]); // [Function: values]
实际写代码过程中,不需要显式调用这个工厂函数来生成迭代器。实现可迭代协议的所有类型都会自动兼容接收可迭代对象的任何语言特性。接收可迭代对象的原生语言特性包括:for-of 循环、数组解构、扩展操作符、Array.from()、创建集合、创建映射等等。
这些原生的语言结构会在后台调用提供的可迭代对象的这个工厂函数,从而创建一个迭代器。
const arr = [1, 2, 3];
// for-of 循环
for (const item of arr) {
console.log(item); // 1 2 3
}
// 数组解构
const [a, b, c] = arr;
console.log(a, b, c); // 1 2 3
// 扩展操作符
let arr2 = [...arr];
console.log(arr2); // [1, 2, 3]
// 使用 Array.from() 赋值数组
let arr3 = Array.from(arr);
console.log(arr3); // [1, 2, 3]
// 创建集合
let set = new Set(arr);
console.log(set); // Set(3) {1, 2, 3}
// 创建映射
let map = new Map(arr.map((x, i) => [i, x]));
console.log(map);
// Map(3) { 0 => 1, 1 => 2, 2 => 3 }
迭代器协议
迭代器协议定义了产生一系列值(无论是有限个还是无限个)的标准方式。当值为有限个时,所有的值都被迭代完毕后,则会返回一个默认返回值。
只有实现了一个拥有语义的 next() 方法,一个对象才符合迭代器协议。
二、迭代器的使用
通过可迭代对象中的迭代器工厂函数 Symbol.iterator来生成迭代器
const arr = []
console.log(arr)
迭代器的next方法
const arr = [1, 2, 3];
const iter1 = arr[Symbol.iterator](); // 通过迭代器工厂函数` Symbol.iterator`来生成迭代器。
console.log(iter1);
console.log(iter1.next());
console.log(iter1.next());
console.log(iter1.next());
console.log(iter1.next());
可以发现,迭代器是取完最后一个值之后,即迭代器下一个值 value为 undefined时,完成。
但是,上面的说法并不是很准确,并不是迭代器下一个值 value为 undefined时,就完成的。还需要判断是不是真的没有值,还是是可迭代对象里就有一个值为 undefined。如果是可迭代对象里有一个值为 undefined的情况,那么此时还是不会变成完成状态。
const arr = [1, 2, 3, undefined];
const iter1 = arr[Symbol.iterator](); // 通过迭代器工厂函数` Symbol.iterator`来生成迭代器。
console.log(iter1);
console.log(iter1.next());
console.log(iter1.next());
console.log(iter1.next());
console.log(iter1.next());
console.log(iter1.next())
不同迭代器之间互不干扰
可以多次调用迭代器工厂函数来生成多个迭代器,每个迭代器都表示对可迭代对象的一次性有序遍历。不同迭代器之间互不干扰,只会独立地遍历可迭代对象。
const arr = [1, 2, 3];
const iter1 = arr[Symbol.iterator](); // 通过迭代器工厂函数` Symbol.iterator`来生成迭代器。
const iter2 = arr[Symbol.iterator]();
console.log("迭代器1:", iter1.next());
console.log("迭代器2:", iter2.next());
console.log("迭代器1:", iter1.next());
console.log("迭代器2:", iter2.next());
完成并不完成
当我们迭代到 done: true之后,再调用next是不是会报错,或者不返回任何内容呢?
然而,并不是,迭代器会处于一种完成但并不完成的状态, done: true表示已经完成了,但是后续还能一直调用 next,虽然得到的结果一直都会是 { value: undefined, done: true }。这就是为什么说完成但并不完成。
const arr = [1, 2, 3];
const iter = arr[Symbol.iterator]();
console.log(iter.next());
console.log(iter.next());
console.log(iter.next());
console.log(iter.next());
console.log(iter.next());
console.log(iter.next());
三、自定义迭代器
const obj = {
name: "张三",
age: 18,
};
for (const iterator of obj) {
console.log(obj);
}
对象是没有实现迭代器,所以不能遍历对象,为了可以实现对象的遍历,我们需要在对象上实现上面说的迭代器。
const obj = {
name: "张三",
age: 18,
[Symbol.iterator]: () => {
let index = -1,
atrrList = Object.keys(obj);
const objIterator = {
next: () => {
let result = "";
index++;
if (index < atrrList.length) {
result = {
value: atrrList[index],
done: false,
};
} else {
result = {
done: true,
};
}
return result;
},
};
return objIterator;
},
};
for (const iterator of obj) {
console.log("属性名:" + iterator + ", 属性值:" + obj[iterator]);
}
总结
迭代协议可以总结为,一个东西要遍历,必须满足可迭代协议跟迭代器协议
可迭代协议是要求对象必须有@@iterator,可以通过Symbol.iterator 访问,而迭代器协议是一个对象,这个对象的next() 函数返回一个对象,这个对象包括两个属性,一个是value,一个是done(boolean,是否是最后一个元素,done 为 true 时 value 可省略)
也就是说迭代器对象本质上,就是一个指针对象,通过指针对象的next(),用来移动指针。