前言
在 JavaScript 中,迭代器和生成器是两个非常重要的概念,它们不仅在语言的底层实现中扮演着关键角色,还为许多高级功能(如 for...of 循环、async/await 等)提供了支持。本文将从一个全新的角度出发,深入探讨迭代器和生成器的原理、实现以及它们在实际开发中的应用。
一、迭代器
背景知识
什么是迭代?
迭代是从一个数据集合中按照一定的顺序,不断地取出数据的过程。类比产品研发的迭代,产品的迭代是一次次做出来的,不确定做多少个迭代(有可能做到中途项目废弃等因素),只能知道依次迭代的动作。迭代强调的是一个过程。
迭代和遍历的区别
迭代:强调的是过程,依次取出数据的动作,不保证能取完数据。
遍历:强调的是结果,把整个数据依次取完。
JavaScript 中的迭代器
在 JavaScript 中,迭代器是一种特殊的对象,它必须具备以下两个特征:
-
具有
next()方法。 -
next()方法返回一个对象,该对象包含两个属性:value:当前迭代的值。done:布尔值,表示迭代是否完成。
例如,以下是一个简单的迭代器实现:
const obj = {
next() {
return {
value: "some value",
done: false
};
}
};
迭代器的实际应用
数组的迭代器实现
以数组为例,我们可以将数组的遍历过程改写为迭代器模式:
const array = [1, 2, 3, 4, 5];
//for循环
for (let i = 0; i < array.length; i++) {
console.log(array[i]);
}
function arrayIterator(arr) {
let i = 0;
return {
next() {
console.log(i);
return {
value: arr[i++],
done: i > arr.length,
};
},
};
}
//迭代器便利数组改造
let iterator = arrayIterator(array);
let data = iterator.next();
console.log(data);
while (!data.done) {
console.log(data);
data = iterator.next();
}
console.log("迭代完成");
虽然迭代器的代码看起来比传统的 for 循环更复杂,但它有一个显著的优点:隔离了调用者与数据源之间的直接操作,使得数据的处理更加灵活。
斐波那契数列的迭代器实现
迭代器还可以实现一些传统循环难以完成的需求,例如生成无限的斐波那契数列:
function FbIterator() {
let prev1 = 1,
prev2 = 1,
n = 1;
return {
next() {
console.log(n);
let value;
if (n <= 2) {
value = 1;
} else {
value = prev1 + prev2;
}
const result = {
value,
done: false,
};
prev2 = prev1;
prev1 = result.value;
n++;
return result;
},
};
}
const fbIterator = FbIterator();
上面例子的FbIterator和arrayIterator都称为迭代器创建函数
可迭代协议
es6规定,如果一个对象具有知名符号属性 Symbol.iterator,并且属性值是一个迭代器创建函数,则该对象是一个可迭代的(iterable)
const obj = {
[Symbol.iterator]() {
return {
next() {
return { value: "some value", done: false };
}
};
}
};
检测对象是否可迭代
可以通过检测对象是否具有 Symbol.iterator 属性来判断它是否是可迭代的
const arr = [1, 2, 3, 4];
console.log(typeof arr[Symbol.iterator]); // "function"
常用的数组就是一个可迭代对象,
很多的伪数组也是一个可迭代对象
遍历可迭代对象
可以通过调用对象的 Symbol.iterator 方法来获取其迭代器,然后通过迭代器的 next() 方法逐个获取值:
const arr = [1, 2, 3, 4];
const iterator = arr[Symbol.iterator]();
let result = iterator.next();
while (!result.done) {
console.log(result.value);
result = iterator.next();
}
for of
for...of 循环是 ES6 引入的一种语法糖,专门用于遍历可迭代对象。它的底层实现就是通过调用对象的 Symbol.iterator 方法来获取迭代器,然后逐个获取值:
for (let item of iterable){
//iterable 可迭代对象
// item 每次迭代得到的数据
}
//这个语法糖实现原理其实就是上面遍历可迭代对象的过程
二、生成器(Generator)
生成器是 JavaScript 中一种特殊的函数,它返回一个生成器对象,该对象实现了迭代器协议。生成器的出现主要是为了简化迭代器的编写过程,但它也带来了一些新的特性。
什么是生成器
生成器是一个通过构造函数 Generator 创建的对象,但它不能直接通过 new Generator() 创建,而是通过生成器函数(Generator Function)来创建。
生成器的特点
1.生成器是一个迭代器,一定有 next() 方法。
2.生成器是一个可迭代对象,一定有 Symbol.iterator 属性。
3.生成器可以使用 for...of 循环遍历。
生成器的创建
生成器函数的定义方式如下:
function *method() {} //该函数一定返回一个生成器
在函数名前加上 *,表示这是一个生成器函数。调用生成器函数会返回一个生成器对象:
生成器函数的执行逻辑
生成器函数内部的代码不会在函数调用时立即执行,而是等待生成器的 next() 方法被调用时才会逐步执行。每次调用 next() 方法,生成器函数会运行到下一个 yield 关键字的位置,并返回一个对象,包含两个属性:
1、value:当前迭代的值。
2、 done:布尔值,表示迭代是否完成。
例如:
function* method() {
console.log("第一次运行");
yield 1;
console.log("第二次运行");
yield 2;
console.log("第三次运行");
}
const generator = method();
console.log(generator.next()); // { value: 1, done: false }
console.log(generator.next()); // { value: 2, done: false }
console.log(generator.next()); // { value: undefined, done: true }
function *method() { //该函数一定返回一个生成器
console.log("test") //直接调用,不会执行 因为这里并没有迭代
}
生成器的使用示例
使用生成器简化数组迭代器
const array = [1, 2, 3, 4, 5];
// 迭代器实现
function arrayIterator(arr) {
let i = 0;
return {
next() {
return {
value: arr[i++],
done: i > arr.length
};
}
};
}
// 使用生成器实现
function* arrayGenerator(arr) {
for (let item of arr) {
yield item;
}
}
const generator = arrayGenerator(array);
for (let item of generator) {
console.log(item);
}
生成器的高级特性
生成器的返回值
生成器函数可以有返回值,返回值出现在第一次 done 为 true 时的 value 属性中:
function* method() {
yield 1;
yield 2;
return 10;
}
const generator = method();
console.log(generator.next()); // { value: 1, done: false }
console.log(generator.next()); // { value: 2, done: false }
console.log(generator.next()); // { value: 10, done: true }
向生成器传递参数
调用生成器的 next() 方法时,可以传递参数,传递的参数可以交给 yield 的表达式:
function* method() {
//该函数一定返回一个生成器
let num = yield 1;
console.log(num); //这个num就是next参数传进来的参数
num = yield 2 + num;
}
const generator = method();
其他 API
return() :调用该方法可以提前结束生成器函数,从而提前结束整个迭代过程。
function* method() {
yield 1;
yield 2;
yield 3;
}
const generator = method();
generator.next()
generator.return()//直接结束迭代
throw() :调用该方法可以在生成器中产生一个错误。
生成器的实际应用
异步流程控制
在 ES6 刚出来的时候,还没有 async/await 关键字,使用 Promise 写异步代码比较麻烦。当时,有人利用生成器来解决这个问题:
function* task() {
const d = yield 1;
const a = yield "abc";
const resp = yield fetch("....");
const result = yield resp.json();
}
function run(generatorFunc) {
const generator = generatorFunc();
let result = generator.next();
handleResult();
function handleResult() {
if (result.done) {
return;
}
if (typeof result.value.then === "function") {
result.value.then(
(data) => {
result = generator.next(data);
handleResult();
},
(error) => {
result = generator.throw(error);
handleResult();
}
);
} else {
result = generator.next(result.value);
handleResult();
}
}
}
run(task);
通过这种方式,生成器可以用来简化异步代码的编写,使得异步流程更加清晰。
四、总结
本文从迭代器和生成器的基本概念出发,详细介绍了它们的实现原理、特点以及在实际开发中的应用。迭代器和生成器是 JavaScript 中非常强大的工具,它们不仅能够简化代码的编写,还能实现一些传统方法难以完成的功能。希望本文能够帮助你更好地理解和使用迭代器与生成器,提升你的 JavaScript 编程能力。