ES6 Generator 函数
1. 异步编程
Javascript 的执行环境是“单线程”的,如果没有异步编程,根本无法使用,容易造成卡死。
什么是异步呢?
所谓“异步”,简单来说就是一个任务不是连续完成的,该任务被人为分成两段。先执行第一段,然后转而执行其他任务,等做好准备后再回过头执行第二段。
比如,有一个任务是读取文件进行处理,任务的第一段是向操作系统发出请求,要求读取文件。然后,程序执行其他任务,等到操作系统返回文件后再接着执行任务的第二段(处理文件)。这种不连续的执行就叫做异步
相应地,连续执行叫作同步。由于是连续执行,不能插入其他任务,所以操作系统从硬盘读取文件的这段时间,程序只能等待
。
在Generator函数出现前,异步编程的方法大概有4种:
- 回调函数
- 事件监听
- 发布/订阅
- Promise对象
Promise对象是为了解决回调地狱而出现的。它不是新的语法功能,而是一种新的写法,允许将回调函数的嵌套改写成链式调用。
Promise
的写法只是回调函数的改进,使用 then 方法以后,异步任务的两段执行更清楚了。其最大问题是代码冗余,原来的任务被 Promise包装之后,无论什么操作,一眼看去都是许多 then 的堆积,原来的语义变得很不清楚。
在这个背景下,Generator函数出现了!
Generator 函数最大特点就是可以交出函数的执行权(即暂停执行)。
整个 Generator 函数就是一个封装的异步任务,或者说是异步任务的容器 ,异步操作需要暂停的地方都用 yield
语句注明。
yield命令
表示执行到此处时,执行权交给其他协程。
也就是说,yield命令是异步两个阶段的分界线。协程遇到yield命令就暂停,等到执行权返回,再从暂停的地方继续往后执行。
什么是异步任务
? 在下面这个例子中,A就是异步任务,因为分成两段(或以上)执行。
2. Generator 函数
1.Generator
有两个区分于普通函数的部分:
- 在 function 后面,函数名之前有个
*
。*
用来表示函数为 Generator 函数 - 函数内部有 yield 表达式。
yield
用来定义函数内部的状态。
function* func(){
console.log("one");
yield '1';
console.log("two");
yield '2';
console.log("three");
return '3';
}
2.执行机制
- 调用 Generator 函数和调用普通函数一样,在函数名后面加上()即可
- 但是 Generator 函数不会像普通函数一样立即执行,而是 返回一个指向内部状态对象的指针
Iterator
,所以要调用遍历器对象Iterator 的 next 方法,指针就会从函数头部或者上一次停下来的地方开始执行。
示例:
-
第一次调用 next 方法时,从 Generator 函数的头部开始执行,先是打印了 one ,执行到 yield 就停下来,并将yield 后边表达式的值 ‘1’,作为返回对象的 value 属性值,此时函数还没有执行完, 返回对象的 done 属性值是 false。
-
第二次调用 next 方法时,同上步 。
-
第三次调用 next 方法时,先是打印了 three ,然后执行了函数的返回操作,并将 return 后面的表达式的值,作为返回对象的 value 属性值,此时函数已经结束,多以 done 属性值为true 。
注意
:若再次调用 next
方法时, 此时函数已经执行完了,所以返回 value 属性值是 undefined ,done 属性值是 true 。
function* func(){
console.log("one");
yield '1';
console.log("two");
yield '2';
console.log("three");
return '3';
}
let iterator = func() // 调用 Generator函数,函数并没有执行,返回的是一个Iterator对象
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
3.一般情况下,
next
方法不传入参数的时候,yield
表达式的返回值是 undefined。
function* func(){
console.log("start");
console.log("---------------------------------");
var x = yield '2';
console.log("one:" + x);
console.log("---------------------------------");
var y = yield '3';
console.log("two:" + y);
console.log("total:" + (x + y));
console.log("---------------------------------");
}
let iterator = func() // 调用 Generator函数,函数并没有执行,返回的是一个Iterator对象
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
next
传入参数的时候,该参数会作为上一步yield的返回值。
function* func(){
console.log("start");
console.log("---------------------------------");
var x = yield '2';
console.log("one:" + x);
console.log("---------------------------------");
var y = yield '3';
console.log("two:" + y);
console.log("total:" + (x + y));
console.log("---------------------------------");
}
let iterator = func() // 调用 Generator函数,函数并没有执行,返回的是一个Iterator对象
console.log(iterator.next(10));
console.log(iterator.next(20));
console.log(iterator.next(30));
再来看一个例子,
function *g(a){
const b=2*(yield(a-1))
// b=12
const c=yield(b/4)
// c=4
return a-b+c
// a-b+c=2-12+4=-6
}
const o=g(2)
console.log(o.next()); // {value:1,done:false}
console.log(o.next(6)); // {value:3,done:false}
console.log(o.next(4)); //{value:-6,done:true}
首先调用g(2)得到的是一个内部指针(遍历器)。
- 调用第一个
next()
,返回值是一个对象。其中value:1
是第一个yield计算的结果a-1=2-1=1
。 - 再调下一个next(6),这个6会替换掉上一次停下来的yield表达式。所以
b=2*6=12
剩下的可以类似分析
4. 我们可以使用for循环遍历输出Generator 函数。
function* g(){
console.log("one");
yield '1';
console.log("two");
yield '2';
console.log("three");
yield '3';
}
for(let v of g()){
console.log(v);
}
还有使用yield* 表达式
在一个Generator 函数中调用另外一个Generator 函数
function* g1(){
yield '1';
yield '2';
yield '3';
}
function* g2(){
yield '11';
yield '22';
yield* g1();
yield '33';
}
for(let v of g2()){
console.log(v);
}
再来一个例子说明正确使用yield* 表达式
在一个Generator 函数中调用另外一个Generator 函数
function* g1(){
yield 'one';
yield 'two'
}
function* g2(){
yield '11';
yield* g1();
yield '22';
}
const o=g2(2)
console.log(o.next()); // {value:11,done:false}
console.log(o.next()); // {value:one,done:false}
console.log(o.next()); //{value:two,done:false}
对于这个例子,如果yield* g1();
写为yield g1();
那会得到什么呢。结果如图所示,第二个next只能得到一个遍历器。