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博客

你可能感兴趣的:(2022-08-28 js迭代器和生成器)