分享更有价值
被信任是一种快乐

JavaScript中Generator函数和yield表达式怎么使用

文章页正文上

这篇“JavaScript中Generator函数和yield表达式怎么使用”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“JavaScript中Generator函数和yield表达式怎么使用”文章吧。在Javascript中,一个函数一旦开始执行,就会运行到最后或遇到return时结束,运行期间不会有其它代码能够打断它,也不能从外部再传入值到函数体内而Generator函数(生成器)的出现使得打破函数的完整运行成为了可能,其语法行为与传统函数完全不同Generator函数是ES6提供的一种异步编程解决方案,形式上也是一个普通函数,但有几个显著的特征:function关键字与函数名之间有一个星号 “*” (推荐紧挨着function关键字)函数体内使用 yield 表达式,定义不同的内部状态 (可以有多个yield)直接调用 Generator函数并不会执行,也不会返回运行结果,而是返回一个遍历器对象(Iterator Object)依次调用遍历器对象的next方法,遍历 Generator函数内部的每一个状态

//传统函数
functionfoo(){
return'helloworld'
}
foo()//'helloworld',一旦调用立即执行
//Generator函数
function*generator(){
yield'statusone'//yield表达式是暂停执行的标记
return'helloworld'
}
letiterator=generator()//调用Generator函数,函数并没有执行,返回的是一个Iterator对象
iterator.next()//{value:"statusone",done:false},value表示返回值,done表示遍历还没有结束
iterator.next()//{value:"helloworld",done:true},value表示返回值,done表示遍历结束

上面的代码中可以看到传统函数和Generator函数的运行是完全不同的,传统函数调用后立即执行并输出了返回值;Generator函数则没有执行而是返回一个Iterator对象,并通过调用Iterator对象的next方法来遍历,函数体内的执行看起来更像是“被人踢一脚才动一下”的感觉

function*gen(){
yield'hello'
yield'world'
return'ending'
}
letit=gen()
it.next()//{value:"hello",done:false}
it.next()//{value:"world",done:false}
it.next()//{value:"ending",done:true}
it.next()//{value:undefined,done:true}

上面代码中定义了一个 Generator函数,其中包含两个 yield 表达式和一个 return 语句(即产生了三个状态)每次调用Iterator对象的next方法时,内部的指针就会从函数的头部或上一次停下来的地方开始执行,直到遇到下一个 yield 表达式或return语句暂停。换句话说,Generator 函数是分段执行的,yield表达式是暂停执行的标记,而 next方法可以恢复执行第四次调用next方法时,由于函数已经遍历运行完毕,不再有其它状态,因此返回 {value: undefined, done: true}。如果继续调用next方法,返回的也都是这个值yield 表达式只能用在 Generator 函数里面,用在其它地方都会报错

function(){
yield1;
})()
//SyntaxError:Unexpectednumber
//在一个普通函数中使用yield表达式,结果产生一个句法错误

yield 表达式如果用在另一个表达式中,必须放在圆括号里面

function*demo(){
console.log('Hello'+yield);//SyntaxError
console.log('Hello'+yield123);//SyntaxError
console.log('Hello'+(yield));//OK
console.log('Hello'+(yield123));//OK
}

yield 表达式用作参数或放在赋值表达式的右边,可以不加括号

function*demo(){
foo(yield'a',yield'b');//OK
letinput=yield;//OK
}

相似:都能返回紧跟在语句后面的那个表达式的值区别:每次遇到 yield,函数就暂停执行,下一次再从该位置继续向后执行;而 return 语句不具备记忆位置的功能一个函数只能执行一次 return 语句,而在 Generator 函数中可以有任意多个 yield如果在 Generator 函数里面调用另一个 Generator 函数,默认情况下是没有效果的

function*foo(){
yield'aaa'
yield'bbb'
}
function*bar(){
foo()
yield'ccc'
yield'ddd'
}
letiterator=bar()
for(letvalueofiterator){
console.log(value)
}
//ccc
//ddd

上例中,使用 for…of 来遍历函数bar的生成的遍历器对象时,只返回了bar自身的两个状态值。此时,如果想要正确的在bar 里调用foo,就需要用到 yield* 表达式yield* 表达式用来在一个 Generator 函数里面 执行 另一个 Generator 函数

function*foo(){
yield'aaa'
yield'bbb'
}
function*bar(){
yield*foo()//在bar函数中**执行**foo函数
yield'ccc'
yield'ddd'
}
letiterator=bar()
for(letvalueofiterator){
console.log(value)
}
//aaa
//bbb
//ccc
//ddd

yield表达式本身没有返回值,或者说总是返回undefined。next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值

function*gen(x){
lety=2*(yield(x+1))//注意:yield表达式如果用在另一个表达式中,必须放在圆括号里面
letz=yield(y/3)
returnx+y+z
}
letit=gen(5)
/***正确的结果在这里***/
console.log(it.next())//首次调用next,函数只会执行到“yield(5+1)”暂停,并返回{value:6,done:false}
console.log(it.next())//第二次调用next,没有传递参数,所以y的值是undefined,那么y/3当然是一个NaN,所以应该返回{value:NaN,done:false}
console.log(it.next())//同样的道理,z也是undefined,6+undefined+undefined=NaN,返回{value:NaN,done:true}

如果向next方法提供参数,返回结果就完全不一样了

{
function*gen(x){
lety=2*(yield(x+1))//注意:yield表达式如果用在另一个表达式中,必须放在圆括号里面
letz=yield(y/3)
returnx+y+z
}
letit=gen(5)
console.log(it.next())//正常的运算应该是先执行圆括号内的计算,再去乘以2,由于圆括号内被yield返回5+1的结果并暂停,所以返回{value:6,done:false}
console.log(it.next(9))//上次是在圆括号内部暂停的,所以第二次调用next方法应该从圆括号里面开始,就变成了lety=2*(9),y被赋值为18,所以第二次返回的应该是18/3的结果{value:6,done:false}
console.log(it.next(2))//参数2被赋值给了z,最终x+y+z=5+18+2=25,返回{value:25,done:true}
}

ES6 规定,默认的 Iterator 接口部署在数据结构的Symbol.iterator属性,或者说,一个数据结构只要具有Symbol.iterator属性,就可以认为是“可遍历的”(iterable)。Symbol.iterator属性本身是一个函数,就是当前数据结构默认的遍历器生成函数。执行这个函数,就会返回一个遍历器。由于执行 Generator 函数实际返回的是一个遍历器,因此可以把 Generator 赋值给对象的Symbol.iterator属性,从而使得该对象具有 Iterato免费云主机、域名r 接口。

{
letobj={}
function*gen(){
yield4
yield5
yield6
}
obj[Symbol.iterator]=gen
for(letvalueofobj){
console.log(value)
}
//4
//5
//6
console.log([...obj])//[4,5,6]
}

传统对象没有原生部署 Iterator接口,不能使用 for…of 和 扩展运算符,现在通过给对象添加Symbol.iterator 属性和对应的遍历器生成函数,就可以使用了由于 Generator 函数运行时生成的是一个 Iterator 对象,因此,可以直接使用 for…of 循环遍历,且此时无需再调用 next() 方法这里需要注意,一旦 next() 方法的返回对象的 done 属性为 true,for…of 循环就会终止,且不包含该返回对象

{
function*gen(){
yield1
yield2
yield3
yield4
return5
}
for(letitemofgen()){
console.log(item)
}
//1234
}

Generator 函数返回的遍历器对象,还有一个 return 方法,可以返回给定的值(若没有提供参数,则返回值的value属性为 undefined),并且 终结 遍历 Generator 函数

{
function*gen(){
yield1
yield2
yield3
}
letit=gen()
it.next()//{value:1,done:false}
it.return('ending')//{value:"ending",done:true}
it.next()//{value:undefined,done:true}
}

应用一:假定某公司的年会上有一个抽奖活动,总共6个人可以抽6次,每抽一次,抽奖机会就会递减按照常规做法就需要声明一个全局的变量来保存剩余的可抽奖次数,而全局变量会造成全局污染,指不定什么时候就被重新赋值了,所以往往并不被大家推荐

{
letcount=6//声明一个全局变量
//具体抽奖逻辑的方法
functiondraw(){
//执行一段抽奖逻辑
//...
//执行完毕
console.log(`剩余${count}次`)
}
//执行抽奖的方法
functionstartDrawing(){
if(count>0){
count--
draw(count)
}
}
letbtn=document.createElement('button')
btn.id='start'
btn.textContent='开始抽奖'
document.body.appendChild(btn)
document.getElementById('start').addEventListener('click',function(){
startDrawing()
},false)
}[objectObject]

事实上,抽奖通常是每个人自己来抽,每抽一次就调用一次抽奖方法,而不是点一次就一次性就全部运行完,是可暂停的,这个不就是 Generator 函数的意义所在吗?

//具体抽奖逻辑的方法
functiondraw(count){
//执行一段抽奖逻辑
//...
console.log(`剩余${count}次`)
}
//执行抽奖的方法
function*remain(count){
while(count>0){
count--
yielddraw(count)
}
}
letstartDrawing=remain(6)
letbtn=document.createElement('button')
btn.id='start'
btn.textContent='开始抽奖'
document.body.appendChild(btn)
document.getElementById('start').addEventListener('click',function(){
startDrawing.next()
},false)

事实上,抽奖通常是每个人自己来抽,每抽一次就调用一次抽奖方法,而不是点一次就一次性就全部运行完,是可暂停的,这个不就是 Generator 函数的意义所在吗?
应用二:由于HTTP是一种无状态协议,执行一次请求后服务器无法记住是从哪个客户端发起的请求,因此当需要实时把服务器数据更新到客户端时通常采用的方法是长轮询和Websocket。这里也可以用 Generator 函数来实现长轮询

{
//请求的方法
function*ajax(){
yieldnewPromise((resolve,reject)=>{
//此处用一个定时器来模拟请求数据的耗时,并约定当返回的json中code为0表示有新数据更新
setTimeout(()=>{
resolve({code:0})
},200)
})
}
//长轮询的方法
functionupdate(){
letpromise=ajax().next().value//返回的对象的value属性是一个Promise实例对象
promise.then(res=>{
if(res.code!=0){
setTimeout(()=>{
console.log('2秒后继续查询.....')
update()
},2000)
}else{
console.log(res)
}
})
}
update()
}

以上就是关于“JavaScript中Generator函数和yield表达式怎么使用”这篇文章的内容,相信大家都有了一定的了解,希望小编分享的内容对大家有帮助,若想了解更多相关的知识内容,请关注云技术行业资讯频道。

相关推荐: es6 class是不是语法糖

这篇文章主要介绍了es6 class是不是语法糖的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇es6 class是不是语法糖文章都会有所收获,下面我们一起来看看吧。 class是语法糖。原因:class是基于原型继承的实现,对语言…

文章页内容下
赞(0) 打赏
版权声明:本站采用知识共享、学习交流,不允许用于商业用途;文章由发布者自行承担一切责任,与本站无关。
文章页正文下
文章页评论上

云服务器、web空间可免费试用

宝塔面板主机、支持php,mysql等,SSL部署;安全高速企业专供99.999%稳定,另有高防主机、不限制内容等类型,具体可咨询QQ:360163164,Tel同微信:18905205712

主机选购导航云服务器试用

登录

找回密码

注册