Generator
英文翻译是 生产者,产生者
.
定义方式如下.
function *gen() {
//xxxxxx
yield xxxx
//xxxxx
let a = yield xxxx.
return xxx
}
- 使用
function *
来定义 - 在此方法内部可以使用
yield
&return
Generator 函数和普通函数的区别
一个普通函数,如果一旦执行了,就会从头走到尾,不会中止.
function normalFn() {
console.log('start')
console.log('middle')
console.log('end')
}
normalFn()
普通函数从头走到尾,不会中断
同样的代码放在generator
函数里面
function *gen() {
console.log('start')
yield
console.log('middle')
yield
console.log('end')
}
// 返回一个枚举器
let g = gen()
g.next() // 输出 'start'
g.next() // 输出 'middle'
g.next() // 输出 'end'
发现代码必须手动的调用gen()
返回的枚举器的next()
方法,代码才会执行.
其中有 yield
这个关键字,yield
的英文意思是:放弃
用一张图去理解的话:
感性的理解yield也就是说,每次调用 next()
方法,gen
函数里的代码就会往下走一步,每次走到一个 yield
就停止执行.
generator内部通过
yield
关键字,将代码中断,除非你调用next(),否则,generator函数不会继续往下执行.
关于 yield 关键字
generator
方法,返回一个枚举器.
每次调用 next()
在碰到 yield
的时候,会停止运行,执行 yield
后面的表达式,并返回.
function *__gen() {
yield 'hello'
yield 'world'
}
let g = __gen()
console.log(g.next()) // {value:'hello',done:false}
console.log(g.next()) // {value:'world',done:false}
console.log(g.next()) // {value:undefined,done:true}
next()方法返回了一个对象,包含value&done两个属性
那第一个例子,单纯的使用 yield
,后面没有接任何表达式,返回的对象的value
是undefined
吗?
function *gen() {
console.log('start')
yield
console.log('middle')
yield
console.log('end')
}
// // 返回一个枚举器
let g = gen()
// g.next() // 输出 'start'
// g.next() // 输出 'middle'
// g.next() // 输出 'end'
console.log(g.next()) //{value:undefined,done:false}
console.log(g.next()) //{value:undefined,done:false}
console.log(g.next()) //{value:undefined,done:true}
yield后面没跟表达式 value就=undefined
也就是说,
next()
会返回一个对象,对象的.value
属性就是yield
后面表达式返回的值.
yield可以返回值:next()==>{value:yield表达式,done:boolean}
yield
关键字,将 generator
中断,并返回yield
后面表达式的值赋值给next(){value属性}
(如果有),
并等待下一次的 next()
关于 generator 里写 return 关键字
我们发现在 generator
中,如果有 n
个 yield
,我们就需要手动的调用 n+1
次 next()
才能得到 done:true
的结果.
但是最后一次的next()
返回的对象中 .value=undefined
..
我们也可以使用在generator 中使用 return
关键字的方式来告知generator迭代结束,并将 return
返回的给过,给最后一个 next()
返回对象的value
属性.
function *__gen() {
yield 'hello'
yield 'world'
yield '!!!'
}
let g = __gen()
console.log(g.next()) // {value:'hello',done:false}
console.log(g.next()) // {value:'world',done:false}
console.log(g.next()) // {value:'!!!',done:true}
[图片上传失败...(image-2417dc-1544290199232)]
如果不想让最后一次 next() 的 value 是 undefined ,而是想具体的返回一个值,那么就在 generator 里使用 return 关键字.
两个作用
- 告知 generator 迭代结束
- 把return的值,给最后一个迭代返回对象的.value属性.
模拟一下 generator 返回对象的next()方法.
我是使用 next()
方法,来每次执行一段一直到 yield
的代码段.
next()
方法每次回返回一个对象,包括 value
和 done
两个属性.
其中,value
是 yield
后面表达式的值. done
表示迭代是否结束.
generator
很高级,在代码级别实现了中断.
但我们也可以使用数组,模拟一个 next()
执行过程,来加深对迭代
的理解.
准备在数组上模拟一个generator中的next()方法.
核心就两个
- 每一次返回对应的元素
value
- 迭代是否完成
done
Array.prototype.next = function () {
this.index = this.index || 0
return {
value: (this.index < this.length ? this[this.index] : undefined),
done: this.index++ >= this.length ? true : false
}
}
;
let arr = [1, 2, 3]
console.log (arr.next())
console.log (arr.next())
console.log (arr.next())
console.log (arr.next())
用数组模拟generator的next()方法
关于 next() 方法的参数问题.
我们现在知道了,每次调用 next()
方法
-
都会执行到下一个
yield
停止 -
返回停止
yield
后面的表达式结果.
function *__gen() {
console.log('第一阶段')
yield 'hello'
console.log('第二阶段')
yield 'world'
return '!!!' // 告知 generator 迭代结束,并把 !!! 传递给最后一次next() 返回对象的 .value 属性
}
let g = __gen()
var res1 = g.next()
res1.value === 'hello' // true
var res2 = g.next()
res2.value === 'world' // true
var res3 = g.next()
res3.value === '!!!' // true & {done:true}
那么既然 next() 是一个方法,方法是可以传递参数的.
就像在定义 generator
方法传递参数一样.
function *__gen(p1,p2) {
console.log(p1,p2)
}
let g = __gen(1,2)
g.next() // 输出 1,2 {value:undefined,done:true}
next()
方法的参数是如何传递的呢?
可以先看一段代码.
function *__gen() {
let a = yield 'hello'
console.log(`a:${a}`)
let b = yield 'world'
console.log(`b:${b}`)
return '!!!'
}
let g = __gen()
g.next()
g.next()
g.next()
先感性上猜测一下:
let a = yield 'hello'
a
是 hello
吗?
我们之前说过,yield
后面返回的值,给next()
返回的对象的.value
了.
所以,这里的 a
是不是 hello
不好说.
同理 b
是不是 world
也不好说.
按照我们以前的理解, let xx = xxx
都是把表达式右边的结果返回给左边.
在这里也会是这样吗?
undefined & undefined突然发现,之前的想法全是错的.出来的是
undefined,undefined
那 hello
,world
哪去了?
记得之前说的吗?
yield
的返回值,给对象了.
console.log(g.next())
console.log(g.next())
console.log(g.next())
yield返回的数据在next()方法返回对象的.value身上
所以,let a = yield 'hello'
这里的 a
和后面的 hello
没有一毛钱关系.
最终结果输出的是两个 undefined
.
那generator里写的 let a,let b
有啥意义啊?
yield
后面的值给的又不是它们.
next 也是一个函数,既然是函数,我们就可以给它传递参数.
js 里的人一个函数,我们可以传递参数,而不管之前的函数是如何定义的.
function noExplicitParam() {
console.log(arguments[0])
console.log(arguments[1])
}
noExplicitParam(1,2)
js的函数参数很灵活
所以,先不管 generator
的返回对象的 next()
方法是如何定义的.
我们就传参数试试.
function *__gen() {
let a = yield 'hello'
console.log(`a:${a}`)
let b = yield 'world'
console.log(`b:${b}`)
return '!!!'
}
let g = __gen()
g.next(1)
g.next(2)
g.next(3)
还是先猜一下.
- 当执行第一个
g.next(1)
碰到了yield 'hello'
, 代码终止,hello
给了对象的.value
,1
给了 变量a
, - 当执行第二个
g.next(2)
,碰到了yield 'world'
,代码终止,world
给了对象的.value
,2
给了b
-
3
由于后面没有let c
去接收,所以不会输出.
所以,输出应该是 a:1 b:2
查看结果:
输出结果 a:2 b:3居然输出的是a:2 b:3,我滴妈啊.那第一个g.next(1)传递的1哪去了?????
总是有惊喜.....
然而实际情况是:
-
每次调用next()
方法,都会执行到一个yield
结束.- 返回
yield
后面的表达式给对象的.value
(如果有) - 代码暂停,等待下一次的
next()
- 返回
上面只说了表达式右边的yield
情况,对左边的let x
没有丝毫提起.
其实这也是为什么 1
消失了的原因.
使用大神们的博客说明:
上面介绍的next方法均没有参数,其实可以为它提供一个参数。参数会被当作上一个yield语句的返回值。如果没有参数,那默认上一个yield语句的返回值为undefined。
反正我是看起来比较费劲.
一张我自己理解的图来说明为什么 1
消失了.
回忆以下之前说的,每一次执行到next()程序都会中止,等待下一个next()
-
当执行到红色矩形的
g.next(1)
时, 执行的是上述红色范围的区域.- 由于
let a =
不属于第一个yield
的区域,所以g.next(1)
参数1
压根就没有变量去接收.
- 由于
-
当执行到蓝色矩形的
g.next(2)
时,执行的是上述蓝色范围的代码段.- 由于
let a =
属于蓝色代码的区域,属于第二个yield
的有效范围,所以2
就被赋值到a
.输出结果是a:2
- 由于
-
当执行到紫色矩形
g.next(3)
时,执行的是上述紫色代码的区域.- 由于
let b =
属于紫色代码的有效区域,属于第三个yield
的有效范围,所以3
赋值给了b
,最终输出结果是b:3
- 由于
所以,最终输出结果是 a:2,b:3
而不是 a:1,b:2
.
每一次next()的执行,真真正正的只到yield而已.(知道yield的右边,yield的左边数与下一个yield的有效范围)
只要正确的识别了 yield
的有效区域,就能很快的知道 next()
是如何传递参数的.
如果写这么一代码,就很好理解了.
function *__gen() {
console.log('第一个yield的有效范围开始')
yield '第一个yield的有效范围结束'
console.log('第二个yield的有效范围开始')
yield '第二个yield的有效范围结束'
console.log('第三个yield的有效范围开始')
yield '第三个yield的有效范围结束'
console.log('我在第四个yield的有效范围开始')
yield '第四个yield的有效范围结束'
}
不包括yield这一行代码的左边。
网友评论