你是怎么理解ES6中Generator的?使用场景?
问题解析
Generator是ES6引入的一种特殊的函数,它是异步编程的重要解决方案之一。这个问题考察对Generator函数的本质理解(状态机、迭代器)、yield表达式的工作机制、与Promise和async/await的关系,以及实际应用场景。
核心概念
1. Generator函数的本质
- 状态机:封装了多个内部状态的函数
- 迭代器生成器:返回符合Iterator接口的对象
- 可暂停执行:通过yield暂停,通过next()恢复
2. 核心特征
function*声明语法yield表达式暂停执行并返回数据next()方法恢复执行并可传入参数- 返回
{ value, done }对象
3. Generator与异步编程
- 提供同步化的异步代码书写方式
- 是async/await的实现基础
- 与Promise配合实现流程控制
详细解答
Generator函数基本语法
// 1. 声明方式
function* gen() {
yield 1;
yield 2;
return 3;
}
// 2. 调用返回迭代器对象,不立即执行
const g = gen();
console.log(g); // Generator { }
// 3. next()方法执行到下一个yield
console.log(g.next()); // { value: 1, done: false }
console.log(g.next()); // { value: 2, done: false }
console.log(g.next()); // { value: 3, done: true }
console.log(g.next()); // { value: undefined, done: true }
yield表达式详解
function* gen() {
yield 'first';
console.log('继续执行');
yield 'second';
console.log('再次继续');
yield 'third';
}
const g = gen();
g.next(); // 输出: 无,返回 { value: 'first', done: false }
// 暂停在第一个yield后
g.next(); // 输出: '继续执行',返回 { value: 'second', done: false }
g.next(); // 输出: '再次继续',返回 { value: 'third', done: false }
g.next(); // 返回 { value: undefined, done: true }
// yield的返回值
function* genWithReturn() {
const received = yield 'sent';
console.log('received:', received);
yield received;
}
const g2 = genWithReturn();
g2.next(); // { value: 'sent', done: false }
g2.next('hello'); // 输出: 'received: hello',返回 { value: 'hello', done: false }
yield接收next()传入的参数
function* gen() {
const a = yield '第一步';
console.log('a =', a); // 11
const b = yield '第二步';
console.log('b =', b); // 22
const c = yield '第三步';
console.log('c =', c); // 33
return a + b + c;
}
const g = gen();
// 第一个next()的传参会被忽略,因为用于启动生成器
console.log(g.next()); // { value: '第一步', done: false }
console.log(g.next(11)); // { value: '第二步', done: false }
console.log(g.next(22)); // { value: '第三步', done: false }
console.log(g.next(33)); // { value: 66, done: true }
for...of遍历Generator
function* gen() {
yield 1;
yield 2;
yield 3;
return 4; // for...of不会遍历return的值
}
for (const value of gen()) {
console.log(value); // 1, 2, 3
}
// 使用展开运算符
const arr = [...gen()]; // [1, 2, 3]
// 解构赋值
const [a, b, c] = gen(); // a=1, b=2, c=3
为对象部署Iterator接口
// 使用Generator为对象添加遍历能力
const obj = {
data: ['a', 'b', 'c'],
*[Symbol.iterator]() {
for (const item of this.data) {
yield item;
}
}
};
for (const item of obj) {
console.log(item); // 'a', 'b', 'c'
}
// 或者遍历对象的属性
const obj2 = {
name: 'Tom',
age: 25,
job: 'developer',
*[Symbol.iterator]() {
const keys = Object.keys(this);
for (const key of keys) {
yield [key, this[key]];
}
}
};
for (const [key, value] of obj2) {
console.log(key, value);
}
Generator与Promise结合
// 自动执行Generator的辅助函数
function run(generator) {
const gen = generator();
function next(data) {
const result = gen.next(data);
if (result.done) return result.value;
result.value.then(data => {
next(data);
});
}
next();
}
// 使用
function fetchUser(id) {
return fetch(`/api/users/${id}`).then(r => r.json());
}
function* fetchData() {
try {
const user = yield fetchUser(1);
console.log('User:', user);
const posts = yield fetch(`/api/posts?userId=${user.id}`).then(r => r.json());
console.log('Posts:', posts);
return posts;
} catch (error) {
console.error('Error:', error);
}
}
run(fetchData);
Generator的错误处理
function* gen() {
try {
yield 1;
yield 2;
yield 3;
} catch (e) {
console.log('内部捕获:', e);
yield '错误已处理';
}
}
const g = gen();
g.next(); // { value: 1, done: false }
g.throw('错误'); // 内部捕获: 错误,返回 { value: '错误已处理', done: false }
g.next(); // { value: undefined, done: true }
// throw方法被捕获后,会自动执行一次next
function* gen2() {
yield 1;
yield 2;
}
const g2 = gen2();
g2.next(); // { value: 1, done: false }
try {
g2.throw('错误'); // 未被捕获,抛出到外部
} catch (e) {
console.log('外部捕获:', e);
}
深入理解
1. Generator的状态机本质
Generator函数可以看作是一个状态机,封装了多个内部状态:
function* stateMachine() {
// 状态1
yield 'STATE_1';
// 状态2
yield 'STATE_2';
// 状态3
yield 'STATE_3';
// 最终状态
return 'FINAL';
}
// 每次yield对应一个状态转换
// 这种特性使得Generator非常适合实现工作流、状态流转等场景
2. Generator与协程
Generator实现了JavaScript中的"半协程"(semi-coroutine):
// 协程特点:可以暂停和恢复,但只能由调用方恢复
// 对比线程:线程是抢占式多任务,协程是协作式多任务
function* taskA() {
console.log('Task A - Step 1');
yield; // 让出执行权
console.log('Task A - Step 2');
yield;
console.log('Task A - Step 3');
}
function* taskB() {
console.log('Task B - Step 1');
yield;
console.log('Task B - Step 2');
yield;
console.log('Task B - Step 3');
}
// 协作式调度
const a = taskA();
const b = taskB();
a.next(); // Task A - Step 1
b.next(); // Task B - Step 1
a.next(); // Task A - Step 2
b.next(); // Task B - Step 2
// ...
3. Generator与async/await的关系
async/await是Generator的语法糖:
// async/await写法
async function fetchData() {
const user = await fetchUser(1);
const posts = await fetchPosts(user.id);
return posts;
}
// 等价于Generator写法
function* fetchDataGen() {
const user = yield fetchUser(1);
const posts = yield fetchPosts(user.id);
return posts;
}
// async函数本质上就是自动执行的Generator
// async函数 = Generator + 自动执行器 + Promise
4. yield* 委托迭代
// yield* 将迭代委托给另一个Generator或可迭代对象
function* gen1() {
yield 'a';
yield 'b';
}
function* gen2() {
yield 1;
yield* gen1(); // 委托给gen1
yield 2;
yield* [3, 4, 5]; // 委托给数组
yield 6;
}
const arr = [...gen2()]; // [1, 'a', 'b', 2, 3, 4, 5, 6]
// 获取被委托Generator的返回值
function* gen3() {
yield 'start';
const result = yield* gen4();
console.log('gen4 returned:', result);
yield 'end';
}
function* gen4() {
yield 'inner';
return 'done'; // 这个返回值会被yield*捕获
}
const g = gen3();
g.next(); // { value: 'start', done: false }
g.next(); // gen4 returned: done, { value: 'end', done: false }
5. Generator的内存特性
// Generator是惰性求值的,适合处理大数据
function* infiniteSequence() {
let i = 0;
while (true) {
yield i++;
}
}
// 不会占用大量内存,按需生成
const seq = infiniteSequence();
console.log(seq.next().value); // 0
console.log(seq.next().value); // 1
console.log(seq.next().value); // 2
// ...
// 对比数组实现
function infiniteArray() {
const arr = [];
for (let i = 0; i < 1000000; i++) {
arr.push(i);
}
return arr; // 内存占用大
}
最佳实践
1. 异步流程控制
// 使用co库或自行实现自动执行器
const co = require('co');
co(function* () {
const result = yield Promise.resolve(true);
return result;
}).then(value => {
console.log(value);
}, err => {
console.error(err.stack);
});
// 或者使用async/await(推荐)
async function modernAsync() {
const result = await Promise.resolve(true);
return result;
}
2. 数据流处理
// 使用Generator实现管道处理
function* map(iterable, fn) {
for (const item of iterable) {
yield fn(item);
}
}
function* filter(iterable, predicate) {
for (const item of iterable) {
if (predicate(item)) {
yield item;
}
}
}
function* take(iterable, n) {
let count = 0;
for (const item of iterable) {
if (count >= n) return;
yield item;
count++;
}
}
// 惰性求值的管道
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const result = [
...take(
filter(
map(numbers, x => x * x),
x => x % 2 === 0
),
3
)
];
// [4, 16, 36]
3. 实现可迭代对象
// 为二叉树实现遍历
class BinaryTree {
constructor(value, left = null, right = null) {
this.value = value;
this.left = left;
this.right = right;
}
// 中序遍历
*[Symbol.iterator]() {
if (this.left) {
yield* this.left;
}
yield this.value;
if (this.right) {
yield* this.right;
}
}
// 先序遍历
*preOrder() {
yield this.value;
if (this.left) {
yield* this.left.preOrder();
}
if (this.right) {
yield* this.right.preOrder();
}
}
}
const tree = new BinaryTree(
4,
new BinaryTree(2, new BinaryTree(1), new BinaryTree(3)),
new BinaryTree(6, new BinaryTree(5), new BinaryTree(7))
);
console.log([...tree]); // [1, 2, 3, 4, 5, 6, 7]
console.log([...tree.preOrder()]); // [4, 2, 1, 3, 6, 5, 7]
面试要点
-
Generator的本质:
- 状态机,封装多个内部状态
- 返回Iterator对象
- 可暂停和恢复执行的函数
-
核心语法:
function*声明yield暂停并返回数据next()恢复执行,可传参- 返回
{ value, done }对象
-
next()传参机制:
- 第一个next()的参数会被忽略
- 后续next(arg)的参数会成为上一个yield的返回值
-
for...of与Generator:
- for...of会自动遍历Generator直到done为true
- 不会遍历return的值
- 可用于为对象部署Iterator接口
-
与Promise/async的关系:
- Generator是async/await的实现基础
- async函数 = Generator + 自动执行器
- 现代代码优先使用async/await
-
yield*的作用:
- 委托迭代给另一个Generator或可迭代对象
- 可以获取被委托Generator的return值
-
使用场景:
- 异步操作的同步化表达(已被async/await取代)
- redux-saga等中间件
- 为对象部署Iterator接口
- 惰性求值、无限序列
- 协程风格的任务调度
-
注意事项:
- Generator函数不能用作构造函数
- throw()方法会抛出错误到Generator内部
- return()方法可提前终止Generator