Generator 是 ES6 提出的一种异步编程的解决办法,它与传统的函数完全不同,本章从基础概念和基本用法进行讲解和解析。在此之前也是对 Generator 函数云里雾里,所以通过此次学习,希望能对 Generator 有更深的理解和认识。
1.概念
Generator 中文的意思是'生成器',阮一峰:ECMAScript 6 入门中对 Generator 解释是: Generator 函数有多种理解角度。语法上,首先可以把它理解成,Generator 函数是一个状态机,封装了多个内部状态。
执行 Generator 函数会返回一个遍历器对象,也就是说,Generator 函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。 Generator 有两个特点: 第一个是在定义时,要在 function 关键字和函数名中加一个星号,第二个就是在函数体中运用 yeild 表达式表示不同的状态。
function* foo() {
yield "a";
yield "b";
}
let f = foo();
console.log(f);
和普通函数不同的是,当运行这个函数时,返回不是这个函数的结果,而是一个遍历器对象,或者可以说是一个含有内部状态指针的对象。 如果想输出值得话,要用 next 方法来进行输出。next 方法就是向下移动指针,即每次调用 next 方法,就是从函数头部或者从上一次 yield 表达式移动到下一次 yield 表达式(或者 return 为止)。
function* foo() {
yield "a";
yield "b";
}
let f = foo();
f.next(); // {value: "a", done: false}
f.next(); // {value: "b", done: false}
f.next(); // {value: undefined, done: true}
上述可以看出,调用 next 时输出的是一个对象,即 value 值代表 yield 后面的结果,done 代表遍历还没有结束,当遍历结束时,value 值都为 undefined,done 都为 true。
2.yield 表达式
yield 有个懒惰的特性,即 yield 后面的表达式,如果不调用 next 方法,是不会执行的。
function* foo() {
yield 1 + 1;
}
foo().next(); // 2
只有当 next 指针移动到该 yield 的时候,才会执行后面的表达式。 yield 和 return 是有不同之处的,在 Generator 函数中,可以定义多个 yield 表达式,但是 return 只能定义一个,并且在 yield 中遍历还没有完成,但在遇到 return 时,遍历就终止了。
function* foo() {
yield "a";
yield "b";
return "c";
}
let f = foo();
f.next(); // {value: "a", done: false}
f.next(); // {value: "b", done: false}
f.next(); // {value: "c", done: true}
f.next(); // {value: undefined, done: true}
当遇到 return 时,遍历结束 done 为 true,value 值为 return 后的结果,在此之后的 next 的结果都为{value: undefined, done: true}。 如果在 Generator 函数中没有 yield 表达式:
function* foo() {
console.log("a");
}
let f = foo();
f.next();
foo()返回的依旧是一个含有内部状态指针的对象,只有当 next 方法执行时该函数才会执行。 当 yield 和其他表达式融合时,如果 yield 表达式在左边,要将 yield 表达式用圆括号包裹,否则就会报语法错误。
function* foo() {
console.log('a'+ yield 'b'); // Uncaught SyntaxError: Unexpected identifier
}
function* foo() {
console.log('a'+ (yield 'b')); // {value: "b", done: false}
}
let f = foo();
f.next();
当用作函数参数或放在赋值表达式的右边,可以不加括号。
function* demo() {
foo(yield "a", yield "b"); // OK
let input = yield; // OK
}
3.next 方法
yield 是没有返回值的,它的返回值是 undefined,我们可以通过 next 方法将参数传递给 yield,此参数将会为 yield 的返回值。
function* f() {
for (var i = 0; true; i++) {
var reset = yield i;
if (reset) {
i = -1;
}
}
}
var g = f();
g.next(); // { value: 0, done: false }
g.next(); // { value: 1, done: false }
g.next(true); // { value: 0, done: false }
上面代码先定义了一个可以无限运行的 Generator 函数 f,如果 next 方法没有参数,每次运行到 yield 表达式,变量 reset 的值总是 undefined。当 next 方法带一个参数 true 时,变量 reset 就被重置为这个参数(即 true),因此 i 会等于-1,下一轮循环就会从-1 开始递增。 不用 next 方法是否可以输出值呢?答案是可以的,可以用 for...of...方法遍历 Generator 函数。
function* foo() {
yield 1;
yield 2;
yield 3;
yield 4;
yield 5;
return 6;
}
for (let v of foo()) {
console.log(v); // 1 2 3 4 5
}
值得注意的是,for...of...在 done 为 ture 的时候就会终止执行,所以 return 后的 6 没有输出。
4.yield* 表达式
yield* 表达式是为了解决在一个 Generator 函数中调用另一个 Generator 函数所提供的方法。
function* foo() {
yield 1;
yield 2;
yield 3;
yield* foo1();
}
等同于;
function* foo() {
yield 1;
yield 2;
yield 3;
for (let v of foo1()) {
yield v;
}
}
当 yield*后边的 Generator 函数中没有 return 时,作用就是 for...of...遍历 Generator 函数,如果 Generator 函数中有 return 时,则获取的是 return 值。
function* foo() {
yield 1;
yield 2;
yield 3;
var value = yield* foo1();
console.log(value);
}
function* foo1() {
yield 4;
return 5;
}
let f = foo();
f.next(); // {value: 1, done: false}
f.next(); // {value: 2, done: false}
f.next(); // {value: 3, done: false}
f.next(); // 5 {value: undefined, done: true}
其实 yield*后面只要是带有 Iterator 接口的都会被遍历。
function* foo() {
yield* [1, 2, 3, 4];
}
let f = foo();
f.next(); // {value: 1, done: false}
再举个例子:
function* foo() {
yield "a";
yield "b";
return "END";
}
function* bar(func) {
let result = yield* func();
console.log(result);
}
[...bar(foo)];
// END
// ["a", "b"]
上述中 foo 是拥有 return 表达式的函数,所以 return 后的结果会‘赋值’给 yield*表达式的返回值,所以 result 是'END',并且拓展运算符默认调用 Iterator 接口,所以会先打印出 result,然后再执行 yield。
参考:
后语
相关文章:
- 带你重学ES6 | var、let和const的区别
- 带你重学ES6 | Promsie
- 带你重学ES6 | Generator
- 带你重学ES6 | Async和Await
- 带你重学ES6 | Set和Map
- 带你重学ES6 | Symbol(不仅仅只是一个新的数据类型)
- 带你重学ES6 | Exprort(谨记输出的都是变量)
- 带你重学ES6 | proxy和defineProperty
觉得还可以的,麻烦走的时候能给点个赞,大家一起学习和探讨!
还可以关注我的博客希望能给我的github上点个Start,小伙伴们一定会发现一个问题,我的所有用户名几乎都与番茄有关,因为我真的很喜欢吃番茄❤️!!!
想跟车不迷路的小伙还希望可以关注公众号 前端老番茄 或者扫一扫下面的二维码👇👇👇。
我是一个编程界的小学生,您的鼓励是我不断前进的动力,😄希望能一起加油前进。
本文使用 mdnice 排版