JavaScript 迭代器、生成器与调试技巧
1. 迭代器与生成器
在 JavaScript 中,迭代器和生成器是强大的工具,它们为处理数据序列提供了灵活的方式。
1.1 生成器消费值
简单的迭代器仅用于生成值,而生成器还能消费值。我们可以使用
next
方法向生成器传递值,该值会成为
yield
表达式的值。以下是一个生成器消费值的代码示例:
function* myGenerator() {
const name = yield "What is your name?";
yield `Hello ${name}!`;
}
const gen = myGenerator();
console.log(gen.next().value); // What is your name?
console.log(gen.next("John").value); // Hello John!
这个示例展示了如何通过
next
方法向生成器传递值。第一次调用
next
方法时,生成器会暂停并返回
What is your name?
。第二次调用
next
方法并传递
"John"
时,该值会成为第一个
yield
表达式的值,进而生成
Hello John!
。
需要注意的是,第一次调用
next
方法时传递的任何值都会被忽略,因为第一次调用无法为第一个
yield
表达式提供值。第一个
yield
表达式的值由第二次调用
next
方法提供,依此类推。
下面是一个生成无限随机数的生成器示例,它允许我们传递随机数生成范围的上限(该上限不包含在范围内):
function* generatorRandomNumber(limit) {
while (true) {
const randomNumber = Math.floor(Math.random() * limit);
limit = yield randomNumber;
}
}
const randomNumGenerator = generatorRandomNumber(10);
console.log(randomNumGenerator.next());
console.log(randomNumGenerator.next(20));
console.log(randomNumGenerator.next(40));
console.log(randomNumGenerator.next(60));
console.log(randomNumGenerator.next(80));
console.log(randomNumGenerator.next(100));
1.2 委托给其他迭代器
生成器的另一个强大特性是允许我们将生成值的责任委托给其他迭代器,例如其他生成器。我们可以使用
yield*
运算符来实现这一点,该运算符会从指定的迭代器中生成值,直到迭代器结束。
以下是一个使用
yield*
运算符将生成偶数或奇数的责任委托给相应生成器函数的示例:
function* evens() {
yield 2;
yield 4;
yield 6;
}
function* odds() {
yield 1;
yield 3;
yield 5;
}
function* printNums(isEven) {
if (isEven) {
yield* evens();
} else {
yield* odds();
}
}
for (const num of printNums(false)) {
console.log(num);
}
// 输出:
// 1
// 3
// 5
在这个示例中,
printNums
生成器根据
isEven
参数的值将生成值的责任委托给
evens
或
odds
生成器函数。
1.3 同步与异步迭代器
在之前的学习中,我们了解了同步迭代器,它包含一个
next
方法,调用该方法会返回一个包含
done
和
value
属性的对象。JavaScript 还提供了异步迭代器,其
next
方法返回一个 Promise。
对象可以通过实现
Symbol.iterator
方法来实现可迭代协议,通过实现
Symbol.asyncIterator
方法来实现异步可迭代协议。
以下是一个使用异步迭代器从 API 获取用户数据的示例:
function fetchUsers(userCount) {
if (userCount > 10) {
userCount = 10;
}
const BASE_URL = "https://jsonplaceholder.typicode.com/users";
let userId = 1;
const userAsyncIterator = {
async next() {
if (userId > userCount) {
return { value: undefined, done: true };
}
const response = await fetch(`${BASE_URL}/${userId++}`);
if (response.ok) {
const userData = await response.json();
return { value: userData, done: false };
} else {
throw new Error("failed to fetch users");
}
},
[Symbol.asyncIterator]() {
return this;
}
};
return userAsyncIterator;
}
async function getData() {
const usersAsyncIterator = fetchUsers(3);
let userIteratorResult = await usersAsyncIterator.next();
while (!userIteratorResult.done) {
console.log(userIteratorResult.value);
userIteratorResult = await usersAsyncIterator.next();
}
}
getData();
在这个示例中,
fetchUsers
函数返回一个异步迭代器,
getData
函数使用该迭代器从 API 获取用户数据。
同步迭代器和异步迭代器的主要区别在于,异步迭代器的
next
方法返回一个 Promise。
1.4 异步生成器
异步生成器是异步函数和生成器的结合,它可以像普通生成器一样方便地实现异步迭代器。异步生成器使用
async function*
定义,其
yield
表达式会返回一个 Promise。
以下是将上述异步迭代器示例重写为使用异步生成器的示例:
async function* fetchUsers(userCount) {
if (userCount > 10) {
userCount = 10;
}
const BASE_URL = "https://jsonplaceholder.typicode.com/users";
for (let userId = 1; userId <= userCount; userId++) {
const response = await fetch(`${BASE_URL}/${userId}`);
if (response.ok) {
const userData = await response.json();
yield userData;
} else {
throw new Error("failed to fetch users");
}
}
}
async function getData() {
const usersAsyncGenerator = fetchUsers(3);
let userGeneratorResult = await usersAsyncGenerator.next();
while (!userGeneratorResult.done) {
console.log(userGeneratorResult.value);
userGeneratorResult = await usersAsyncGenerator.next();
}
}
getData();
在
fetchUsers
函数中,我们可以直接
yield response.json()
,因为从异步函数中返回 Promise 会将异步函数的 Promise 解析为内部返回的 Promise。同样的原理适用于异步生成器,如果我们
yield
一个 Promise,异步生成器的 Promise 会解析为内部
yield
的 Promise。
异步生成器使得实现异步迭代器变得非常简单,这也是通常实现异步迭代器的方式。
1.5
for await...of
循环
到目前为止,我们通过直接调用
next
方法来消费异步迭代器。
for...of
循环用于迭代可迭代对象,而
for await...of
循环则用于迭代异步可迭代对象。
以下是将
getData
函数重写为使用
for await...of
循环的示例:
async function getData() {
for await (const user of fetchUsers(3)) {
console.log(user);
}
}
for await...of
循环只能在可以使用
await
关键字的上下文中使用,例如在异步函数或模块中。
2. JavaScript 调试技巧
调试是查找和修复代码中问题或错误的过程。对于软件开发人员来说,调试是一项必备技能,因为编写无错误的代码几乎是不可能的,学会有效地调试自己或他人的代码可以大大提高开发效率。
以下是几种有效的 JavaScript 调试方法:
-
使用
debugger
语句
-
在浏览器开发者工具中使用断点
-
使用 VS Code 调试器
这些调试策略都依赖于调试器来帮助我们有效地调试代码。
2.1 使用
debugger
语句
debugger
语句允许我们在代码中设置一个点,调试器会在该点暂停代码执行,就像设置断点一样,这样我们可以检查代码中不同变量的值。
以下是一个示例代码,在浏览器中运行该代码并输入值
"18"
,代码看似正常,但对于该值会给出错误的输出:
function isOldEnoughToDrive() {
const age = prompt("What is your age?");
let result;
debugger;
if (age === 18) {
result = "You are just about the right age to drive!";
} else if (age < 18) {
result = "Not allowed to drive";
} else if (age > 18) {
result = "Allowed to drive!";
} else {
result = "invalid age value provided";
}
const resultElm = document.querySelector("#result");
resultElm.innerHTML = result;
}
isOldEnoughToDrive();
<body>
<h2 id="result"></h2>
<script src="index.js"></script>
</body>
当输入
"18"
时,输出为
"invalid age value provided"
,这是因为
prompt
函数返回的是字符串,而我们在比较时使用的是
age === 18
,字符串与数字使用严格相等运算符比较总是返回
false
。
要使
debugger
语句生效,我们需要打开浏览器的开发者工具,然后刷新浏览器窗口,代码执行会在
debugger
语句处暂停。此时,我们可以关注调试器的两个区域:调试器控件和当前作用域中不同变量的值。
调试器控件允许我们逐行执行代码,方便我们观察每一行代码的执行过程以及对不同变量值的影响。我们还可以查看调用栈,了解当前函数的调用方式,并且可以通过悬停在变量上查看其值,甚至可以通过双击“Scopes”部分中的值来更改变量的值,以观察代码的行为。
找到问题后,我们可以通过将
age
转换为数字来修复代码:
const age = Number(prompt("What is your age?"));
这个简单的示例展示了如何使用
debugger
语句调试代码。浏览器内置的调试器功能非常强大,值得我们探索其每一个特性以提高调试技能。
2.2 使用浏览器开发者工具中的断点
除了使用
debugger
语句,我们还可以在浏览器的开发者工具中设置断点来暂停代码执行。与
debugger
语句不同的是,我们不需要在代码中编写特殊的关键字,而是在浏览器开发者工具中打开 JavaScript 代码并设置断点。
在 Chrome 浏览器中,对应的调试面板名为“Sources”,其功能与 Firefox 浏览器的“Debugger”面板类似。
以下是设置断点的步骤:
1. 在浏览器中打开代码。
2. 打开浏览器的开发者工具。
3. 打开“Sources”(Chrome)或“Debugger”(Firefox)面板。
4. 在代码中点击行号设置断点。
5. 刷新浏览器窗口,当代码执行到断点时,会暂停执行,我们可以使用浏览器调试器的各种功能来调试代码。
2.3 使用 VS Code 调试器
VS Code 是目前常用的编辑器之一,它提供了许多功能和灵活性,其中包括内置的调试器,允许我们在 VS Code 中调试代码。
以下是使用 VS Code 调试代码的步骤:
1. 在 VS Code 中打开要调试的代码示例。
2. 在包含代码(HTML 和 JavaScript 文件)的文件夹中创建一个名为
.vscode
的文件夹。
3. 在
.vscode
文件夹中创建一个名为
launch.json
的文件,并粘贴以下 JSON 内容:
{
"version": "0.2.0",
"configurations": [
{
"type": "chrome",
"request": "launch",
"name": "Launch Chrome against localhost",
"file": "${workspaceFolder}/index.html"
}
]
}
-
设置断点。我们可以在代码中使用
debugger语句,或者在 VS Code 中打开的 JavaScript 代码文件中点击行号设置断点。 - 打开 VS Code 的“Run and Debug”选项。
- 点击“Run and Debug”窗口顶部的绿色播放按钮,这将打开 Chrome 浏览器,当代码执行到断点时,调试器会暂停执行。
调试时,我们可以看到 VS Code 顶部的调试器控件,其功能与浏览器调试器的控件类似。高亮显示的行表示代码执行暂停的位置,左侧边栏显示调用栈和当前作用域中的变量。我们可以使用调试器控件推进或恢复调试,观察代码的执行流程和不同变量的值。
VS Code 调试器还有很多其他功能,例如可以通过点击按钮自动创建
launch.json
文件,这些功能在 VS Code 文档中有详细解释。
总结
本文介绍了 JavaScript 中的迭代器、生成器以及调试技巧。迭代器和生成器为处理数据序列提供了强大的工具,包括生成器消费值、委托给其他迭代器、同步与异步迭代器、异步生成器以及
for await...of
循环等特性。同时,我们还学习了几种有效的 JavaScript 调试方法,包括使用
debugger
语句、浏览器开发者工具中的断点以及 VS Code 调试器。掌握这些知识和技巧可以帮助我们更好地编写和调试 JavaScript 代码。
JavaScript 迭代器、生成器与调试技巧
3. 总结与对比
为了更好地理解迭代器、生成器以及调试技巧,我们可以通过表格的形式对它们进行总结和对比。
| 类别 | 特点 | 示例代码 | 使用场景 |
|---|---|---|---|
| 生成器消费值 |
可通过
next
方法向生成器传递值,该值成为
yield
表达式的值
|
function* myGenerator() {
const name = yield "What is your name?";
yield `Hello ${name}!`;
}
const gen = myGenerator();
console.log(gen.next().value);
console.log(gen.next("John").value);
``` | 需要动态生成值并根据外部输入进行处理的场景 |
| 委托给其他迭代器 | 使用 `yield*` 运算符将生成值的责任委托给其他迭代器 |
```javascript
function* evens() {
yield 2;
yield 4;
yield 6;
}
function* odds() {
yield 1;
yield 3;
yield 5;
}
function* printNums(isEven) {
if (isEven) {
yield* evens();
} else {
yield* odds();
}
}
for (const num of printNums(false)) {
console.log(num);
}
``` | 需要根据不同条件生成不同序列值的场景 |
| 同步迭代器 | `next` 方法返回包含 `done` 和 `value` 属性的对象 | 一般用于处理本地数据序列,数据获取速度快且不需要异步操作的场景 |
| 异步迭代器 | `next` 方法返回一个 Promise | 用于从网络、数据库等异步数据源获取数据的场景 |
| 异步生成器 | 结合了异步函数和生成器,`yield` 表达式返回一个 Promise | 简化异步迭代器的实现,处理异步数据序列的场景 |
| `for await...of` 循环 | 用于迭代异步可迭代对象 |
```javascript
async function getData() {
for await (const user of fetchUsers(3)) {
console.log(user);
}
}
``` | 在异步函数或模块中迭代异步可迭代对象的场景 |
| 使用 `debugger` 语句 | 在代码中设置 `debugger` 语句,打开浏览器开发者工具后代码执行会在该语句处暂停 |
```javascript
function isOldEnoughToDrive() {
const age = prompt("What is your age?");
let result;
debugger;
if (age === 18) {
result = "You are just about the right age to drive!";
} else if (age < 18) {
result = "Not allowed to drive";
} else if (age > 18) {
result = "Allowed to drive!";
} else {
result = "invalid age value provided";
}
const resultElm = document.querySelector("#result");
resultElm.innerHTML = result;
}
isOldEnoughToDrive();
``` | 快速定位代码执行过程中某个位置的变量值和代码状态的场景 |
| 使用浏览器开发者工具中的断点 | 在浏览器开发者工具中打开代码并设置断点,代码执行到断点处会暂停 | 设置步骤:1. 在浏览器中打开代码;2. 打开浏览器的开发者工具;3. 打开“Sources”(Chrome)或“Debugger”(Firefox)面板;4. 在代码中点击行号设置断点;5. 刷新浏览器窗口。 | 需要灵活设置断点,观察代码执行过程的场景 |
| 使用 VS Code 调试器 | 在 VS Code 中设置断点,配置 `launch.json` 文件后可在 VS Code 中调试代码 | 设置步骤:1. 在 VS Code 中打开要调试的代码示例;2. 在包含代码的文件夹中创建 `.vscode` 文件夹;3. 在 `.vscode` 文件夹中创建 `launch.json` 文件并粘贴配置内容;4. 设置断点;5. 打开 VS Code 的“Run and Debug”选项;6. 点击绿色播放按钮。 | 集成开发环境中调试代码,方便与代码编辑结合的场景 |
#### 4. 流程图展示
下面通过 mermaid 格式的流程图来展示异步生成器获取用户数据的流程:
```mermaid
graph TD;
A[开始] --> B[调用 fetchUsers 函数并传入用户数量];
B --> C[判断用户数量是否大于 10];
C -- 是 --> D[将用户数量设为 10];
C -- 否 --> E[继续];
D --> E;
E --> F[初始化用户 ID 为 1];
F --> G[进入 for 循环];
G --> H[发送请求获取用户数据];
H --> I[判断响应是否成功];
I -- 是 --> J[解析响应数据并 yield 数据];
I -- 否 --> K[抛出错误];
J --> L[判断是否达到用户数量上限];
L -- 否 --> G;
L -- 是 --> M[结束循环];
M --> N[结束];
K --> N;
这个流程图清晰地展示了异步生成器
fetchUsers
函数从开始到结束的整个执行过程,包括对用户数量的处理、请求的发送、响应的处理以及循环的结束条件等。
5. 实践建议
在实际开发中,我们可以根据具体的需求和场景选择合适的迭代器、生成器和调试方法。
-
迭代器与生成器的选择
- 如果需要处理本地数据序列且不需要异步操作,可使用同步迭代器。
- 当需要动态生成值并根据外部输入进行处理时,使用生成器消费值的特性。
- 对于需要根据不同条件生成不同序列值的场景,可使用委托给其他迭代器的方式。
- 处理异步数据源时,优先考虑异步迭代器和异步生成器,特别是异步生成器能简化异步迭代器的实现。
-
在异步函数或模块中迭代异步可迭代对象时,使用
for await...of循环。
-
调试方法的选择
-
当需要快速定位代码执行过程中某个位置的变量值和代码状态时,使用
debugger语句。 - 需要灵活设置断点,观察代码执行过程时,使用浏览器开发者工具中的断点。
- 在集成开发环境中调试代码,方便与代码编辑结合时,使用 VS Code 调试器。
-
当需要快速定位代码执行过程中某个位置的变量值和代码状态时,使用
通过合理运用这些工具和技巧,我们可以提高代码的质量和开发效率,更好地应对各种复杂的开发场景。
总结
JavaScript 中的迭代器、生成器和调试技巧为开发者提供了强大的工具和方法。迭代器和生成器让我们能够更灵活地处理数据序列,无论是同步还是异步的数据。而调试技巧则帮助我们快速定位和解决代码中的问题,提高开发效率。通过本文的介绍和总结,希望开发者能够更好地掌握这些知识和技巧,在实际开发中发挥它们的最大作用。
超级会员免费看
1125

被折叠的 条评论
为什么被折叠?



