什么是JavaScript Generator呢?通俗的讲Generators是可以用来控制迭代器的函数。它们可以暂停,然后在任何时候恢复。
for(let i = 0; i< 5; i++){
console.log(i) // 0,1,2,3,4
}
function * generatorForLoop(){
for(let i = 0; i < 5; i++){
yield console.log(i)
}
}
const genForLoop = generatorForLoop()
console.log(genForLoop.next()) //0
console.log(genForLoop.next()) //1
console.log(genForLoop.next()) //2
console.log(genForLoop.next()) //3
console.log(genForLoop.next()) //4
对比代码,常规的循环只能一次遍历完所有的值,Generator可以通过调用next方法拿到依次遍历的值,让遍历的执行变得‘可控’。
function * gen(){
yield 1
yield 2
yield 3
}
let g = gen()
// Generator{ }
这个是Generator的定义方法,有几个点值得注意:
1.比普通函数多一个*
2.函数内部用yield来控制程序的执行的“暂停”
3.函数的返回值通过调用next来“恢复”程序的执行
yield关键字用来暂停和恢复一个生成器函数
关于yield表达式,要熟记几个知识点:
1.yield表达式的返回值是undefined,但是遍历器对象的next方法可以修改这个默认值。
function * gen (){
let val;
val = yield 1
console.log(`1:${val}`) // 1: undefined
val = yield 2
console.log(`2:${val}`) // 2: undefined
val = yield 3
console.log(`3:${val}`) // 3: undefined
}
var g = gen()
console.log(g.next()) //{ value: 1, done: false}
console.log(g.next()) //{ value: 2, done: false}
console.log(g.next()) //{ value: 3, done: false}
console.log(g.next()) //{ value: undefined, done: true}
从这个代码可以看出来,yield表达式的返回值是undefined。
2.yield *是委托给另一个遍历器对象或者可遍历对象
function * gen(){
let val;
val = yield 1
console.log(`1:${val}`) // 1: undefined
val = yield 2
console.log(`2:${val}`) // 2: undefined
val = yield 3
console.log(`3:${val}`) // 3: undefined
}
3.Generator对象的next方法,遇到yield就暂停,并返回一个对象,这个对象包括两个属性:value和done。
方法:
Generator对象有几个方法:next、return、throw。
–next([value])
Generator对象通过next方法来获取每一次遍历的结果,这个方法返回一个对象,这个对象包含两个属性:value和done。value是指当前程序的运行结果,done表示遍历是否结束。
其实next是可以接收参数的,这个参数可以让你在Generator外部给内部传递数据,而这个参数就是作为yield的返回值。
function * gen (){
var val = 100;
while(true){
console.log(`before${val}`)
val = yield val
console.log(`return${val}`)
}
}
var g = gen()
console.log(g.next(20).value)
//before 100
//100
console.log(g.next(30).value)
//before 30
//return 30
//30
console.log(g.next(20).value)
//before 40
//return 40
//40
如果对上面的话和代码不理解,可以把console.log(g.next(30).value)和console.log(g.next(40).value)注释掉。会发现只输出before 100和100,这是为什么呢?
1.g.next(20)这句代码会执行gen内部的代码,遇到第一个yield暂停。所以console.log(before${val}
)执行输出了before 100,此时的val是100,所以执行到yield返回了100,注意yield val并没有赋值给val。
2.g,next(30)这句代码会继续执行gen内部的代码,也就是val = yield val这句,因为next传入了30,所以yield val这个返回值就是30,执行了console.log(return${val}
)输出了30,此时没有遇到yield代码继续执行。
3.g.next(40) 重复步骤2
–return()
return方法可以让Generator遍历终止,有点类似for循环的break。
function * gen (){
yield 1
yield 2
yield 3
}
var g = gen()
console.log(g.next()) //{value: 1, done:false}
console.log(g.return()) //{value: undefined, done:true}
console.log(g.next()) //{value: undefined, done:false}
从done可以看出代码执行已经结束。
当然return也可以传入参数,作为返回值的value值。
function * gen(){
yield 1
yield 2
yield 3
}
var g = gen()
console.log(g.next()) //{value: 1, done:false}
console.log(g.return(100)) //{value: 100, done:true}
console.log(g.next()) //{value: undefined, done:false}
–throw()
function * gen(){
while(true){
try{
yield 42
} catch(e){
console.log(e.message)
}
}
}
let g = gen()
console.log(g.next()) //{value: 42, done:false}
console.log(g.next()) //{value: 42, done:false}
console.log(g.next()) //{value: 42, done:false}
//中断操作
g.throw(new Error('break'))
console.log(g.next()) //{value: undefined, done: true}
Scene Practice
场景1:
我们在商品搞活动的时候,通常会有抽奖的环节。回忆下抽奖的流程:
1.满1000积分的可以抽一等奖(一名)
2.满500积分的可以抽一等奖(三名)
3.满50积分的可以抽一等奖(五名)
function * draw (first = 1, second = 3, third = 5) {
let firstPrize = ['1A', '1B', '1C', '1D', '1E']
let secondPrize = ['2A', '2B', '2C', '2D', '2E', '2F', '2G', '2H', '2I']
let thirdPrize = ['3A', '3B', '3C', '3D', '3E', '3F', '3G', '3K', '3O', '3P']
let count = 0
let random
while (1) {
if (count < first) {
random = Math.floor(Math.random() * firstPrize.length)
yield firstPrize[random]
count++
firstPrize.splice(random, 1)
} else if (count < first + second) {
random = Math.floor(Math.random() * secondPrize.length)
yield secondPrize[random]
count++
secondPrize.splice(random, 1)
} else if (count < first + second + third) {
random = Math.floor(Math.random() * thirdPrize.length)
yield thirdPrize[random]
count++
thirdPrize.splice(random, 1)
} else {
return false
}
}
}
从这个代码可以看出来,每次执行都是临时random,如果是普通函数就只能用三个循环,依次random所有名单,这个函数返回所有中将的名单,而不是每次返回单个名单。
function draw (first = 1, second = 3, third = 5) {
let firstPrize = ['1A', '1B', '1C', '1D', '1E']
let secondPrize = ['2A', '2B', '2C', '2D', '2E', '2F', '2G', '2H', '2I']
let thirdPrize = ['3A', '3B', '3C', '3D', '3E', '3F', '3G', '3K', '3O', '3P']
let result = []
let random
// 抽一等奖
for (let i = 0; i < first; i++) {
random = Math.floor(Math.random() * firstPrize.length)
result = result.concat(firstPrize.splice(random, 1))
}
// 抽二等奖
for (let i = 0; i < second; i++) {
random = Math.floor(Math.random() * secondPrize.length)
result = result.concat(secondPrize.splice(random, 1))
}
// 抽三等奖
for (let i = 0; i < third; i++) {
random = Math.floor(Math.random() * thirdPrize.length)
result = result.concat(thirdPrize.splice(random, 1))
}
return result
}
let t = draw()
对比普通函数和Generator的两种写法,哪个体验更好呢?
function * count (x = 1) {
while (1) {
if (x % 3 === 0) {
yield x
}
x++
}
}
let num = count()
console.log(num.next().value)
console.log(num.next().value)
console.log(num.next().value)
console.log(num.next().value)
console.log(num.next().value)
console.log(num.next().value)
console.log(num.next().value)
console.log(num.next().value)
console.log(num.next().value)
通过Generator就能轻松实现,只要调用num.next就知道下一个数是什么了,而使用普通函数却没法做到。