许多人都说循环,有的时候把迭代都给你忘记了。迭代是按照顺序反复多次执行一段程序,一般会有明确的终止条件。跟循环不是一回事,循环是迭代的基础。
迭代有两个条件
- 迭代之前需要事先知道如何使用数据结构。
- 遍历顺序并不是数据结构固有的。
let collection = ['1', '2', '3'];
collection.forEach((item) => console.log(item));
// 1
// 2
// 3
这个看起来熟悉吧。Java程序程序常用的箭头函数。
迭代器模式
又称为“可迭代对象”实现了正式的 Iterable 接口,并且可以通过迭代器 Iterator 消费
可迭代对象可以理解成数组货集合类型的对象。它们包含是元素有限,并且无歧义的遍历顺序
// 数组的元素是有限的
// 递增索引可以按序访问每个元素
let arr = [3, 1, 4];
// 集合的元素是有限的
// 可以按插入顺序访问每个元素
let set = new Set().add(3).add(1).add(4);
可迭代对象不一定是集合对象,也可以是仅具有类似数组行为的其他数据结构。例如计数循环
。计数循环和集合都有可迭代对象行为。
迭代器是按需创建的一次性对象。每一个迭代器都会关联一个可迭代对象,而迭代器会暴露迭代其关联可迭代对象的API。迭代器无需了解与其关联的可迭代对象的结构,只需要知道如何取得连续的值。
可迭代协议
实现Iterable接口需要具备,支持迭代的自我识别能力和创建Iterator接口对象能力。在实际实现中意味着必须暴露一个默认迭代器,默认迭代器使用Symbol.iterator属性作为键。这个默认迭代器必须引用一个迭代器工厂函数,调用这个工厂函数得到一个新迭代器。
内置实现了Iterable接口:
- 字符串
- 数组
- 映射
- 集合
- arguments 对象
- NodeList 等 DOM 集合类型
检查是否存在默认迭代器属性
let num = 1;
let obj = {};
// 这两种类型没有实现迭代器工厂函数
console.log(num[Symbol.iterator]); // undefined
console.log(obj[Symbol.iterator]); // undefined
let str = 'abc';
let arr = ['a', 'b', 'c'];
let map = new Map().set('a', 1).set('b', 2).set('c', 3);
let set = new Set().add('a').add('b').add('c');
let els = document.querySelectorAll('div');
// 这些类型都实现了迭代器工厂函数
console.log(str[Symbol.iterator]); // f values() { [native code] }
console.log(arr[Symbol.iterator]); // f values() { [native code] }
console.log(map[Symbol.iterator]); // f values() { [native code] }
console.log(set[Symbol.iterator]); // f values() { [native code] }
console.log(els[Symbol.iterator]); // f values() { [native code] }
实现可迭代协议的所有类型都会自动兼容接收可迭代对象的任何语言特性。接收可迭代对象的原生语言特性包括:
- for-of 循环
- 数组解构
- 扩展操作符
- Array.from()
- 创建集合
- 创建映射
- Promise.all()接收由期约组成的可迭代对象
- Promise.race()接收由期约组成的可迭代对象
- yield*操作符
let arr = ['1', '2', '3'];
// for-of 循环
for (let el of arr) {
console.log(el);
}
// foo
// bar
// baz
// 数组解构
let [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 ]
// Set 构造函数
let set = new Set(arr);
console.log(set); // Set(3) {1, 2, 3 }
// Map 构造函数
let pairs = arr.map((x, i) => [x, i]);
console.log(pairs); // [['1', 0], ['2', 1], ['3', 2]]
let map = new Map(pairs);
console.log(map); // Map(3) { '1'=>0, '2'=>1, '3'=>2 }
迭代器协议
上面说的迭代对象每次都会生成一个新的迭代器,用于迭代与其关联的可迭代对象。迭代器API使用next()方法遍历数据。每次调用next()成功,都会返回一个IteratorResult 对象,其中包含迭代器返回的下一个值。不调用next(),无法知道迭代器的当前位置。
IteratorResult 对象包含两个属性:done和value。done:布尔值,是否下次调用next()取得下一个值。value:可迭代对象的下一个值。
注意事项:
迭代器会阻止垃圾回收程序回收可迭代对象
提前终止迭代器
for-of 循环通过 break、continue、return 或 throw 提前退出。
生成器基础
生成器:在一个函数块内暂停和恢复代码执行的能力
函数名称前面加一个星号(*)表示它是一个生成器
// 生成器函数声明
function* generatorFn() {}
注意:箭头函数不能声明生成器
生成器开始处于暂停执行状态,实现了Iterator 接口,调用有 next()方法恢复执行
通过 yield 中断执行
yield 关键字可以让生成器停止,再通过next()恢复执行。yield 关键字只能在接位于生成器函数定
义中 。yield*就是将一个可迭代对象序列化为一连串可以单独产出的值,这跟把 yield放到一个循环里没什么不同。
// 实现递归
function* nTimes(n) {
if (n > 0) {
console.log("n:"+n);
yield* nTimes(n - 1);
console.log("n1:"+n);
yield n - 1;
console.log("n2:"+n);
}
}
for (const x of nTimes(3)) {
console.log("result:"+x);
}
// n:3
// n:2
// n:1
// n1:1
// result:0
// n2:1
// n1:2
// result:1
// n2:2
// n1:3
// result:2
// n2:3
看着我这些log,你可以思考他的执行顺序,你可以清晰看出来yield* nTimes(n - 1);通过递归 将顺序弄反编程 1,2,3 再经过yield n - 1; 暂停执行得到结果0,1,2
生成器作为默认迭代器
生成器对象实现Iterable 接口,并且生成器函数和默认迭代器被调用后都产生迭代器。生成器做成默认迭代器格外合适。
class Foo {
constructor() {
this.values = [1, 2, 3];
}
* [Symbol.iterator]() {
yield* this.values;
}
}
const f = new Foo();
for (const x of f) {
console.log(x);
}
// 1
// 2
// 3
提前终止生成器
return()和 throw()方法都可以用于强制生成器进入关闭状态。