近期刷题时碰到迭代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、内置可迭代对象
String
、Array
、TypedArray
、Map
、Set
,它们的原型都实现了@@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)