迭代器
什么是迭代
我的理解迭代就是在一个有序集合上按顺序操集合中每一个集合元素,下面就是最简单的迭代
for (let i = 0; i < 10; i++) {
}
但是这种迭代并没有普适性,因为再迭代前我们需要事先知道如何使用数据结构和特定的遍历顺序。因此在ES6中提出了迭代器模式,让我们在迭代前不必先知道该如何迭代。
迭代器
迭代器(Iterator)就是一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作。
工作原理
- 创建一个指针对象,指向当前数据结构的起始位置
- 第一次调用对象的 next 方法,指针自动指向数据结构的第一个成员
- 接下来不断调用 next 方法,指针一直往后移动,直到指向最后一个成员
- 每调用 next 方法返回一个包含 value 和 done 属性的对象
可迭代对象
他们实现了 Iterable 接口,而且可以通过迭代器 Iterator 消费。
可迭代对象有两个特点:
- 包含的元素都是有限个;
- 具有唯一的迭代顺序;
迭代器是一次性对象(用完就销毁),每个迭代器都会关联一个可迭代对象(下面的代码调用一次for of就会创建一个迭代器,两个迭代器互不影响)。迭代器会暴露迭代器关联的可迭代对象的 API,这样就使得迭代器无需了解可迭代对象的结构,只用调用 API 就能操作可迭代对象的每个元素。
const arr = [1, 2, 3];
for (let item of arr) {
console.log(item);
}
for (let item of arr) {
console.log(item);
}
在 JS 中暴露了一个属性Symbol.iterator
作为默认迭代器,它的值是一个函数,调用它就会返回一个新迭代器。JS 中如下内置对象实现了 Itrerable 接口:
- 字符串
- 数组
- 映射
- 集合
- arguments 对象
- NodeList 等 DOM 对象
const str = '123';
const arr = [1, 2, 3];
const map = new Map();
const set = new Set();
// 都有Symbol.iterator 属性,且都是函数
console.log(str[Symbol.iterator]);
console.log(arr[Symbol.iterator]);
console.log(map[Symbol.iterator]);
console.log(set[Symbol.iterator]);
//调用该方法得到对应迭代器
console.log(str[Symbol.iterator]());
console.log(arr[Symbol.iterator]());
console.log(map[Symbol.iterator]());
console.log(set[Symbol.iterator]());
迭代器的应用
在实际开发中并不需要我们显示的调用Symbol.iterator
工厂函数生成迭代器,以下原生语言接收会在后台调用该工厂函数创建迭代器:
- for-of
- 数组解构
- 扩展操作符
- Array.from
- 创建集合
- 创建映射
- Promise.all()接收期约组成的可迭代对象
- Promise.race()接收期约组成的可迭代对象
- yield *在生成器中使用
const arr = [1, 2, 3];
for (let item of arr) {
console.log(item);
}
// 1
// 2
// 3
let [a, b, c] = arr;
console.log(a, b, c);//1 2 3
console.log(...arr);//1 2 3
// ···
如果对象原型链上的父类实现了 Iterable 接口,那么这个对象也就相当于实现了 Iterable 接口。
class test extends Array { }
let t = new test(1, 2, 3);
console.log(...t);
迭代器API
迭代器 API 使用 next 方法,每次调用 next 方法,都会返回一个 IteratorResult 对象,它包含两个属性:
- done:bool 值,flase 表示还可以再次调用 next 方法取得下一个值,true 表示已耗尽
- value:可迭代对象的元素值。
耗尽之后再次调用 next 方法会永远返回 {value: undefined, done: true}
。
const arr = [1, 2, 3];
const arrIterator = arr[Symbol.iterator]();
console.log(arrIterator.next());//{value: 1, done: false}
console.log(arrIterator.next());//{value: 2, done: false}
console.log(arrIterator.next());//{value: 3, done: false}
console.log(arrIterator.next());//{value: undefined, done: true}
迭代器的简单实现
任何实现了 Iterator 接口的对象都可作为迭代器使用。
class myIterator {
constructor(limit) {
this.limit = limit;
}
[Symbol.iterator]() {
let start = 0, end = this.limit;
return {
next() {
if (start < end) {
return { done: false, value: start++ };
}
return { done: true, value: undefined };
}
}
}
}
const MyIterator = new myIterator(2);
const Ite = MyIterator[Symbol.iterator]();
console.log(Ite.next());
console.log(Ite.next());
console.log(Ite.next());
console.log(Ite.next());
for (let item of MyIterator) {
console.log(item);
}
提前终止迭代器
在开发中我们往往会遇到这样的情况,当我们在循环过程中,满足一定的条件后就需要结束循环,迭代器如何处理呢?这就需要一个可选方法 return。它在遇到如下情况时会被调用(但是它不会强制迭代器进入关闭状态):
- for-of 循环通过break、continue、return、throw提前退出。
- 解构操作并未消费所有值。
class myIterator {
constructor(limit) {
this.limit = limit;
}
[Symbol.iterator]() {
let start = 0, end = this.limit;
return {
next() {
if (start < end) {
return { done: false, value: start++ };
} else {
return { done: false, value: undefined };
}
},
return() {
console.log('提前退出')
return { done: false, value: undefined };
}
}
}
}
const MyIterator = new myIterator(2);
const Ite = MyIterator[Symbol.iterator]();
console.log(Ite.next());
console.log(Ite.next());
console.log(Ite.next());
console.log(Ite.next());
for (let item of MyIterator) {
if (item > 1) {
break;
}
console.log(item);
}
如果迭代器没有关闭,还可以从上次退出的地方接着迭代,例如数组。并且由于return方法是可选的,所以并非所有的迭代器都是可关闭的。
const arr = [1, 2, 3];
const Ite = arr[Symbol.iterator]();
for (let item of Ite) {
if (item > 1) {
console.log('退出', item);
break;
}
console.log(item);
}
for (let item of Ite) {
console.log(item);
}
结语
迭代器这部分内容还是比较简单的,主要是需要理解一些概念。我是孤城浪人,一名正在前端路上摸爬滚打的菜鸟,欢迎你的关注。