ES6语法(十一)Generator

Generator

什么是JavaScript Generator呢?通俗的讲Generators是可以用来控制迭代器的函数。它们可以暂停,然后在任何时候恢复。

  • 1.常规循环
for(let i = 0; i< 5; i++){
	console.log(i)  // 0,1,2,3,4
}
  • 2.利用Generator
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方法拿到依次遍历的值,让遍历的执行变得‘可控’。

  • Basic Syntax
    语法:
function * gen(){
	yield 1
	yield 2
	yield 3
}
let g = gen()
// Generator{ }

这个是Generator的定义方法,有几个点值得注意:
1.比普通函数多一个*
2.函数内部用yield来控制程序的执行的“暂停”
3.函数的返回值通过调用next来“恢复”程序的执行

  • yield表达式

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的两种写法,哪个体验更好呢?

  • 如果用普通函数来写,主持人说现在获得一等奖的名单时,其实二等奖和三等奖已经确定了,如果观众知道内幕就不好了
  • 如果用Generator来写,主持人每宣布一个名单时,下一个的中奖人是不知道的,这才是真正意义的抽奖。
    场景2
    小游戏,比如数数字,遇到3的倍数就要跳过,从1一直往下数:
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就知道下一个数是什么了,而使用普通函数却没法做到。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值