通俗的讲 Generators 是可以用来控制迭代器的函数。它们可以暂停,然后在任何时候恢复。经常配合Iterator(迭代器)使用。
生成器是一种返回迭代的函数,(其实生成器函数也就是返回了一个iterator对象)。通过function关键字后边的星号(*)来表示,函数中存在新的关键字yield。星号可以紧挨着function关键字,也可以在中间添加一个空格。
Generator方法调用返回的是一个对象,对用拥有next方法,next方法调用返回一个对象,对用拥有done和value。
其实它返回的这种结构就是和Iterator一样的(通过next方法调用),也就是说Generator的返回值就是一个Iterator的迭代器对象。
所以定义[Symbol.iterator]方法的时候,可以自己手动实现next方法,也可配置Generator方法和yield关键字返回的Iterator对象进行实现。(生成器函数返回的对象已经根据yield为我们写好next方法)。
// 生成器函数
function *createIterator() {
yield 1;
yield 2;
yield 3;
}
// 生成器函数和普通函数调用一致,只不过返回的是一个迭代器
let iterator = createIterator()
console.log(iterator.next().value) // 1
console.log(iterator.next().value) // 2
console.log(iterator.next().value) // 3
console.log(iterator.next().value) // undefined
复制代码
生成器函数每当执行一次next方法才会继续执行下一次yield。
也就是说第一次调用next方法,会执行到第一次yield语法结束。next方法第一次yield后的表达式。第二次调用next方法顺着上次结束的地方开始执行。
Generator 对象通过 next方法来获取每一次遍历的结果,这个方法返回一个对象,这个对象包含两个属性:value 和 done。value 是指当前程序的运行结果,done 表示遍历是否结束。 其实 next 是可以接受参数的,这个参数可以让你在 Generator 外部给内部传递数据,而这个参数就是作为 yield 的返回值。 也就是 next 方法接收的参数是作为上一次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) // 第一次调用 传入的20是上一次yield表达式的返回值,没什么用
// before 100
// 100
// 第二次调用next 第一次的30作为了yield val 表达式的返回值
// 所以打印出来了30
console.log(g.next(30).value)
// return 30
// before 30
// 30
// 同理
console.log(g.next(40).value)
// return 40
// before 40
// 40
```
> 如果对上面的话和代码不理解,可以把 console.log(g.next(30).value) 和 console.log(g.next(40).value) 注释掉。你会发现 只输出了 before 100 和 100,这是为什么呢?下面我们来还原下这段代码的执行过程:
> g.next(20) 这句代码会执行 gen 内部的代码,遇到第一个 yield 暂停。所以 console.log( before ${val} ) 执行输出了 before 100 ,此时的 val 是 100,所以执行到 yield val 返回了 100,注意 yield val 并没有赋值给 val。
> g.next(30) 这句代码会继续执行 gen 内部的代码,也就是 val = yield val 这句,因为 next 传入了 30,所以 yield val 这个返回值就是 30,因此 val 被赋值 30,执行到 console.log( return ${val} ) 输出了 30,此时没有遇到 yield 代码继续执行,也就是 while 的判断,继续执行 console.log( before ${val} ) 输出了 before 30 ,再执行遇到了 yield val 程序暂停。
> g.next(40) 重复步骤 2。
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: true}
```
从 done 可以看出代码执行已经结束。
当然 return 也可以传入参数,作为返回的 value 值。(注意return当时done已经是true,所以无法被迭代。)
复制代码
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: true}
复制代码
3. throw()
可以通过 throw 方法在 Generator 外部控制内部执行的“终断”。
复制代码
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}
复制代码
(Generator生成的其实就一个可迭代的对象,说白了就是满足可迭代协议的对象。) 配合Iterator这讲去理解可迭代协议和迭代器协议。
深入浅出ES6,等完善Generator和Iterator的笔记。
// 解决异步问题
// 第一次调用next方法开始执行到第一次request对象发送请求a
// a成功之后会再次调用getData的next方法
// 这时next方法传入的参数res就是上一次得到的异步的数据
// 同时传递给next方法作为上一步yield request('static/a.json')表达式的返回值,使用了res1接收
// request的成功函数中继续调用next方法,会接下来去执行到yield request('static/b.json')
// 只有成功才会继续调用next
// 这就是解决异步的方式
function request(url) {
ajax(url, res => {
getData.next(res)
})
}
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()
// 我们经常玩一些小游戏, 比如数数字, 敲7, 到7和7的倍数, 无限循环转圈去数数
function* count(x = 1) {
while (true) {
if (x % 7 === 0) {
yield x
}
x++
}
}
// es5中就是个死循环 因为es5的循环需要有个终止值,但我们这个需求没有终止,一直在数数
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)
console.log(n.next().value)
复制代码