通俗的讲 Generators 是可以用来控制迭代器的函数。
// ES5循环写法
function loop() {
for (let i = 0; i < 5; i++) {
console.log(i)
}
}
loop()
// ES6 generator 写法
function * loop() {
for (let i = 0; i < 5; i++) {
yield console.log(i) // 函数内部使用yield暂停程序执行
}
}
const l = loop()
l.next() // 0 函数外部使用next()恢复执行
l.next() // 1
l.next() // 2
l.next() // 3
l.next() // 4
常规的循环只能一次遍历完所有值,Generator 可以通过调用 next 方法拿到依次遍历的值,让遍历的执行变得“可控”。
语法
function * gen () {
yield 1
yield 2
yield 3
}
let g = gen()
// "Generator { }"
以上是 Generator 的定义方法,有几个点值得注意:
- 比普通函数多一个 *
- 函数内部用 yield 来控制程序的执行的“暂停”
- 函数的返回值通过调用 next 来“恢复”程序执行
function* gen() {
let val
val = yield 1
console.log(val)
}
const l = gen()
l.next() // 找到 yield 但没有代码需要执行
l.next() // 未找到yield,没有返回值 undefined
// Generator原理: Generator 函数返回一个对象,对象有next()方法,next()在函数体内找yield或函数结尾,找到一个就结束
Generator基本语法描述:
1、Generator 是一个函数,在定义时比普通函数多一个*
2、在Generator 函数内部,可以通过 yield 控制程序停止执行
3、Generator 函数可以通过在yield 后加*进行嵌套
4、可以用next()恢复执行,同时会返回当前执行的状态和数据{value: 当前的执行结果, done: 是否执行完毕}
Generator 读法扩展:
function* gen() {
let val
val = (yield [1, 2, 3]) + 7
console.log(val)
}
const l = gen()
// next()如果传参数则是作为 yield 表达式的返回值,替换yeld表达式,不传参数则yield 表达式返回 undefined
console.log(l.next(10))
console.log(l.return()) // return 方法可以让 Generator 遍历终止,有点类似 for 循环的 break。
console.log(l.next(20))
Generator 高级语法:
1、next()可以传值,用来修改内部运行数据。如果传参数则是作为 yield 表达式的返回值,替换yeld表达式,不传参数则yield 表达式返回 undefined
2、return() 方法可以让 Generator 遍历提前终止,有点类似 for 循环的 break。
3、可以从外部向内部抛出异常,在内部通过 try{}catch(e){} 捕获异常。程序运行不会受影响
// 通过return() 提前停止迭代
function* gen() {
let val
val = (yield [1, 2, 3]) + 7
console.log(val)
}
const l = gen()
console.log(l.next(10)) // {value: Array(3), done: false}
console.log(l.return(100)) // {value: 100, done: true}
console.log(l.next(20)) // {value: undefined, done: true}
// 函数外部抛出异常,函数内部捕获异常
function* gen() {
while (true) {
try {
yield 1
} catch (e) {
console.log(e.message)
}
}
}
const g = gen()
console.log(g.next()) // {value: 1, done: false}
console.log(g.next()) // {value: 1, done: false}
console.log(g.next()) // {value: 1, done: false}
console.log(g.next()) // {value: 1, done: false}
console.log(g.next()) // {value: 1, done: false}
g.throw(new Error('something error')) // something error
console.log(g.next()) // {value: 1, done: false}
业务场景:年会抽奖模拟 一等奖:一名 二等奖:三名 三等奖:五名
// ES5 实现方法
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', '3H', '3I', '3K', '3J']
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()
for (let value of t) {
console.log(value)
}
// ES6 实现方法
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', '3H', '3I', '3K', '3J']
let count = 0
let random
while (1) {
if (count < first) { // 抽一等奖
random = Math.floor(Math.random() * firstPrize.length)
yield firstPrize[random]
count++ // 抽一次计数器+1
firstPrize.splice(random, 1)
} else if (count < first + second) { // 抽二等奖
random = Math.floor(Math.random() * secondPrize.length)
yield secondPrize[random]
count++ // 抽一次计数器+1
secondPrize.splice(random, 1)
} else if (count < first + second + third) {
random = Math.floor(Math.random() * thirdPrize.length)
yield thirdPrize[random]
count++ // 抽一次计数器+1
thirdPrize.splice(random, 1)
} else { // 如果三个奖项都抽完
return false
}
}
}
let d = draw()
console.log(d.next().value)
console.log(d.next().value)
console.log(d.next().value)
console.log(d.next().value)
console.log(d.next().value)
console.log(d.next().value)
console.log(d.next().value)
console.log(d.next().value)
console.log(d.next().value)
console.log(d.next().value) // false
场景二:输出3的倍数。只要调用就执行并且有值
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)
思考:
1、用 Generator 实现一个斐波那契数列
斐波那契数列 特性:从第三个数开始,值是前两个数之和
// 递归实现方法:
function fibonacci(m) {
if (m < 3) return m
else return fibonacci(m - 1) + fibonacci(m - 2)
// else return arguments.callee(m - 1) + arguments.callee(m - 2)
}
console.log(fibonacci(1)) // 1
console.log(fibonacci(2)) // 2
console.log(fibonacci(3)) // 3
console.log(fibonacci(4)) // 5
console.log(fibonacci(8)) // 34
// generator 实现方法
function* gen() {
let [prev, curr] = [0, 1]
for (;;) { // 如果3个语句全部省略循环体永远循环下去
[prev, curr] = [curr, curr + prev] // 解构赋值,用当前值替换上一个的值,当前值为上一个值+上一个当前值。很绕
yield curr // 输出当前数值
}
}
for (let n of gen()) {
if (n > 1000) {
break
}
console.log(n)
}
// 1
// 2
// 3
// 5
// 8
// 13
// 21
// 34
// 55
// 89
// 144
// 233
// 377
// 610
// 987
2、用 Generator 给自定义数据结构写一个遍历器