JavaScript - Generator

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 ,后面没有接任何表达式,返回的对象的valueundefined吗?


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 中,如果有 nyield,我们就需要手动的调用 n+1next() 才能得到 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() 方法每次回返回一个对象,包括 valuedone 两个属性.

其中,valueyield 后面表达式的值. 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'

ahello 吗?

我们之前说过,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 没有一毛钱关系.

let a 和 yield '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 消失了.

yield的有效范围

回忆以下之前说的,每一次执行到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这一行代码的左边。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,504评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,434评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,089评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,378评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,472评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,506评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,519评论 3 413
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,292评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,738评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,022评论 2 329
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,194评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,873评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,536评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,162评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,413评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,075评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,080评论 2 352

推荐阅读更多精彩内容