定义
Generator 本意是生成器,普通函数一旦被执行,那么在结束之前是不再执行其他的JS代码。但是Generator函数可以在函数执行的过程中暂停,又可以在暂停的地方继续执行
Generator函数的特点是
- 声明函数的时候在函数名前有星号
- 里面有
yield关键字,本意是生产、产出的意思 - Generator需要我们手动的去执行,使用
f.next() - Generator函数并不会立即执行,而是返回一个生成器的迭代器对象
- 当迭代器函数调用next方法的时候,内部就会执行到
yield后面的语句为止 - Generator函数不能当做构造函数去使用,它只能返回生成器的对象
yield关键字只能在Generator函数里面去使用,不能在其他地方使用
function* foo(args) {
args.forEach(item => {
yield item + 1
})
}
基础例子
例1:普通函数调用同Generator函数调用的区别
普通的函数
function foo() {
for(let i=0; i< 3; i++) {
console.log(i); // 0 1 2
}
}
foo()
Generator函数 / 生成器函数
function* foo() {
for(let i = 0; i < 3; i++){
yield i
}
}
let f = foo()
console.log(f.next())
console.log(f.next())
console.log(f.next())
console.log(f.next())
让函数有点像在调试的时候使用的单步执行一样,一步一步的手动的控制进行到哪一步
备注:
next()方法返回一个包含属性done和value的对象,value表示当前这一次yield的返回值done表示当前的Generator是否有执行完成或者说后续是否还有yield语句,当函数执行完成则done的值就变成true
例 2 : next()中没有传参
function* gen(x) {
let y = 2 * (yield(x + 1))
let z = yield(y / 3)
return x + y + z
}
let g = gen(5)
console.log(g.next()) // 6
console.log(g.next()) // NaN
console.log(g.next()) // NaN
分析下结果为什么是这样?
- next()中可以传参数,它表示关于generator内部的上一条yield语句的返回值
- 第一次调用next()是执行到yield后面的x + 1,x=5,结果是6
- next()中的参数表示上一条yield的返回值,当第二次调用next()的时候没有传参,因此
(yield(x + 1))返回的结果是undefined,因此y是NaN.第二次next()执行到yield后面的代码(y / 3)也是NaN,因此第二次的next的执行结果是NaN - 最后一次的next中也没有传参,因此上一次yield的返回值 y / 3 也是 undefined,z是undefined,因此结果是NaN
例 3 : next()中传参
function* gen(x) {
let y = 2 * (yield(x + 1))
let z = yield(y / 3)
return x + y + z
}
let g = gen(5)
console.log(g.next()) // 6
console.log(g.next(12)) // 8
console.log(g.next(13)) // 42
备注:
- next()执行的结果是yield后面的表达式
- 第一次next执行的值是
x+1,等于6 - next传递参数表示上一次yield表达式返回的值
- next中的参数12的意思就是
(yield(x + 1))= 12,y=24,但是next表示下一次yield结束的位置才停止也就是y/3,因此得到的结果是8 - 当第三次调用next,13表示的上一次yield表达式的返回值,
yield(y / 3)= 13, z = 13 - x = 5,y = 24,z = 13,x+y+z = 42
- 注意:要把计算xyz的值同g.next()的返回值分开分析
通过上面的例子我们知道:next()是如何执行的、next()的参数是如何使用的、yield的作用
例4 :敲7游戏,逢7的倍数跳过
使用generator函数求出7的倍数
function* count(x = 1){
while (true) {
if(x % 7 === 0){
yield x
}
x++
}
}
let n = count()
console.log(n.next().value);
console.log(n.next().value);
console.log(n.next().value);
console.log(n.next().value);
console.log(n.next().value);
如果没有Generator那么我们写的函数就陷入死循环,正因为有Generator可以让我们在中途停止,当前才不会是死循环
Generator如何对异步的状态进行管理呢?
还是用之前发送ajax请求来获取static/a.json、static/b.json 、static/c.json、,它们之间存在依赖关系,先请求a再请求b最后请求c
前2次分别使用了回调深渊和Promise实现,这里将使用Generator的方式对异步操作进行管理
function ajax(url,callback) {
// 1. 创建XMLHttpRequest对象
var xmlhttp
if(window.XMLHttpRequest) {
xmlhttp = new XMLHttpRequest()
// 兼容早期浏览器
} else {
xmlhttp = new ActiveXObject('Microsoft.XMLHTTP')
}
// 2. 发送请求
xmlhttp.open('GET',url,true)
xmlhttp.send()
// 3. 接收服务端相应
xmlhttp.onreadystatechange = function() {
if(xmlhttp.readyState === 4 && xmlhttp.status === 200){
var obj = JSON.parse(xmlhttp.responseText)
callback(obj);
}
}
}
function request(url) {
ajax(url,(res) => {
// 下面这句很难理解
getData.next(res) // next参数代表上一次yield的返回值,这样就把请求的结果返回到了generator函数中yield的request的请求结果
})
}
function* gen(){
let res1 = yield request('static/a.json')
console.log(res1);
let res2 = yield request('static/b.json')
console.log(res2);
let res3 = yield request('static/c.json')
console.log(res3);
}
let getData = gen()
getData.next()
备注:
- 第一次调用next的时候会执行到yield的位置停止,request发送请求a.json,进入request,当进入
getData.next(res)执行next的时候,参数res,就是上一次yield的返回值,也就是yield request('static/a.json')它的返回值,就得到了res1的结果 - 同理可以得到res2、res3的结果