2022-08-28 js迭代器和生成器

近期刷题时碰到迭代Map对象的value值的情景,其中用到了迭代器的原理,于是学习了迭代器和生成器并总结。

一、迭代器

1、可迭代对象

一个对象成为可迭代对象的前提是实现了@@iterator方法,其可以是对象或其原型链上的键为@@iterator的属性,可以通过Symbol.iterator访问。如下述示例

const array1 = [1, 2, 3]
const iterator1 = array1[Symbol.iterator]()
// Node
console.log(iterator1) // [Array Iterator]
// Edge
iterator // Array Iterator {}
// Chrome
console.log(iterator1) // Array Iterator {}

const map2 = new Map()
map2.set('a', 1)
map2.set('b', 2)
const iterator2 = map2[Symbol.iterator]()
const keys = map2.keys()
const values = map2.values()
// Node
console.log(iterator2) // [Map Entries] { [ 'a', 1 ], [ 'b', 2 ] }
console.log(keys) // [Map Iterator] { 'a', 'b' }
console.log(values) // [Map Iterator] { 1, 2 }

上面的例子通过对象Symbol.iterator属性访问对象的迭代器,后文若没有声明,均在Node环境下测试。

可迭代对象可以通过迭代的方式逐一访问元素,如for...of循环访问数组元素。

2、迭代器

一个对象拥有如下语义的next()方法,才能成为迭代器:

next是一个可以接收一个参数的函数,返回一个对象。这个对象必须有以下两个属性:
    `done(boolean)`:如果迭代器还有下一个值,就为false,如果迭代器已经迭代完毕,则为true。在这种情况下,value是可选的,如果它依然存在,即为迭代结束之后的默认返回值。
    `value`:可以是任何JavaScript值,done为true时可省。

 next若不返回满足以上条件的对象,就会抛出错误TypeError

一些内置对象实现了迭代器协议,下面的示例就是通过迭代器迭代数组元素的例子(这样的做法有点多余,纯粹为了演示)。注意区分迭代器可迭代对象

const array3 = [1, 2, 3]
const iterator3 = array3[Symbol.iterator]()

// 方式一
console.log(iterator3.next()) // { value: 1, done: false }
console.log(iterator3.next()) // { value: 2, done: false }
console.log(iterator3.next()) // { value: 3, done: false }
console.log(iterator3.next()) // { value: undefined, done: true }

// 方式二
for(const i of iterator3) {
 console.log(i) // 1, 2, 3
}

// 方式三,混用方式一和方式二
console.log(iterator3.next()) // { value: 1, done: false }
for(const i of iterator3) {
 // 和方式二一样使用for...of循环,但是输出不一样,因为这里for...of迭代一个迭代器,在方法三开头已经迭代了第一个元素了
 console.log(i) // 2, 3
}
// 当再次使用迭代器iterator3时,不会输出结果,已经迭代完成了
for(const i of iterator) {
 console.log('再次迭代', i) // 无输出
}
// 但不影响直接迭代可迭代对象
for(const i of array3) {
 console.log(i) // 1, 2, 3
}

再看一个示例,迭代map的key值

const map4 = new Map()
map4.set('a', 1)
map4.set('b', 2)
const iterator4 = map4.keys()
console.log(iterator4) // [Map Iterator] { 'a', 'b' }

// 方式一
console.log(iterator4.next()) // { value: 'a', done: false }
console.log(iterator4.next()) // { value: 'b', done: false }
console.log(iterator4.next()) // { value: undefined, done: true }

// 方式二
for(const i of iterator4) {
 console.log(i) // a, b
}

我的理解是for...of既能接收可迭代对象(如示例中的array3),也能接收迭代器(如示例中的iterator3、iterator4)。

MDN对for...of的描述如下:

for...of语句在可迭代对象(包括Array,Map,Set,String,TypedArray,arguments对象等等)上创建一个迭代循环,调用迭代钩子,并为每个不同的属性执行语句。

我对这段话的理解是,若传入可迭代对象,则调用可迭代对象的生成器,即Symbol.iterator属性

3、内置可迭代对象

StringArrayTypedArrayMapSet,它们的原型都实现了@@iterator方法,并可以通过Symbol.iterator属性访问。

4、接收可迭代对象的语法

for...of展开语法yield*解构赋值

5、实现可迭代对象

迭代协议 - JavaScript | MDN (mozilla.org)

二、生成器

function*这种方式会定义一个生成器函数,返回一个Generator对象

1、基本用法

定义一个生成器函数,调用它产生一个迭代器。

调用next方法:

首次调用,执行到生成器函数中第一个yield语句的位置停止;
后面的每次调用都到下一个yield语句停止。
function* generator5(i) {
 yield i
 yield i + 10
}

const gen = generator5(10)
console.log(gen) // Object [Generator] {}

console.log(gen.next()) // { value: 10, done: false }
console.log(gen.next()) // { value: 20, done: false }
console.log(gen.next()) // { value: undefined, done: true }

这里用到了第一节迭代器当中迭代方式一,也可以用迭代方式二

2、next方法传递参数

next方法传递参数时,这个参数会赋值给上一次执行yield时等号左边的变量,看下面的例子:

第二次调用next时,传递参数4,由于第一次调用时yield语句等号左边是变量first,所以第二次调用时变量first被赋值为4,从而返回4 + 2 = 6
第三次调用时,参数5被赋值给变量second,所以返回5 + 3 = 8
function* generator6() {
 let first = yield 1
 let second = yield first + 2
 yield second + 3
}

const gen = generator6()

console.log(gen.next()) // { value: 1, done: false }
console.log(gen.next(4)) // { value: 6, done: false }
console.log(gen.next(5)) // { value: 8, done: false }
console.log(gen.next()) // { value: undefined, done: true }
3、生成器函数返回

立刻变为完成状态,且返回值作为当前next方法返回对象的value值

function* generator7() {
 yield 1
 return 2
 yield 3 // 不被执行
}

const gen = generator7()

console.log(gen.next()) // { value: 1, done: false }
console.log(gen.next()) // { value: 2, done: true }
console.log(gen.next()) // { value: undefined, done: true }

三、yield*

yield*表达式用于委托给另一个生成器或可迭代对象

1、委托给其他生成器
function* gen1() {
 yield 10
 yield 11
}

function* gen2() {
 yield 20
 yield* gen1()
 yield 21
}

const g = gen2()

console.log(g.next()) // { value: 20, done: false }
console.log(g.next()) // { value: 10, done: false }
console.log(g.next()) // { value: 11, done: false }
console.log(g.next()) // { value: 21, done: false }
console.log(g.next()) // { value: undefined, done: true }
2、委托给其他可迭代对象
function* gen3() {
 yield* [1, 2]
 yield* '34'
 yield* arguments
}

const g = gen3(5, 6)

// 一堆重复代码
console.log(g.next())
3、yield*是一个表达式

它有自己的值

function* gen4() {
 yield* [1, 2]
 return 'this is the end'
}

let result

function* gen5() {
 result = yield* gen4()
}

const g = gen5()

console.log(g.next()) // 1
console.log(g.next()) // 2
console.log(g.next()) // undefined,但是gen4返回了一个{ value: 'this is the end', done: true }对象
console.log(result) // this is the end

这里的返回同第二节生成器返回同理,都是yield*表达式的值,在这个示例中被赋给了result。

四、生成器的应用

异步操作、逐行读取文本、迭代器等

后记:本文是作者的学习笔记,欢迎学习和交流,如有谬误恳请指正。如能对您有所帮助,不胜荣幸~

迭代协议 - JavaScript | MDN (mozilla.org)

function* - JavaScript | MDN (mozilla.org)

yield* - JavaScript | MDN (mozilla.org)

for...of - JavaScript | MDN (mozilla.org)

es6中生成器Generator的使用场景_前端阳光的博客-CSDN博客

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

推荐阅读更多精彩内容

  • 前言 很多同学在第一次听到生成器这个概念的时候,总觉得是前端高大上的东西,可能现在依然有很多前端同学不理解这个概念...
    Weastsea阅读 313评论 0 0
  • 迭代器和生成器 前置知识:JavaScrip已经提供多个迭代集合的方法,从简单的for循环到map()和filte...
    Daeeman阅读 180评论 0 2
  • 一、迭代器 1.什么是迭代器 迭代器(iterator),是使用户可在容器对象(container,例如链表或数组...
    咸鱼不咸_123阅读 411评论 0 3
  • 在 ES6 标准中,提供了 Generator 函数(即“生成器函数”),它是一种异步编程的解决方案。在前面一篇文...
    越前君阅读 1,974评论 0 7
  • 一、字面量增强 如何理解Javascript中的字面量(literal)?javascript字面量 更安全的二进...
    _ClariS_阅读 215评论 0 0