迭代器
我们经常使用 for 循环来遍历数组
let arr = ["red", "green", "blue"] |
上述代码是使用变量 i
作为下标来访问数组,但是对于一些无序的集合,例如 Map 我们便不能通过下标的形式进行访问,所以有必要提供统一的方式来访问集合,这就是迭代器。
所谓的迭代器那当然是用来迭代用的,迭代的对象可以是数组,集合以及对象。迭代器含有一个方法 next(),该方法的作用就是从数组 (集合、对象) 取出一个值,它返回一个对象,对象中有两个属性:
- done:为布尔值,false 表示集合中还有元素可遍历,true 表示集合中没有元素可遍历
- value: 如果 done 为 false,value 的值为集合中的一个元素值,如果 done 为 true,value 的值为 undefined
function getIterator() { |
getIterator 返回一个具有 next 方法的对象,next 方法返回一个具有 done 和 value 属性的对象,每次调用 next 方法,都会对 i
进行 ++
,当 i > 3
时,done 的值就为 true,且返回的 value 值为 undefined。
生成器
生成器的作用是来生成一个迭代器的,上面我们自定义的迭代器需要自己维护 done 和 value 属性,使用起来还是比较麻烦的。生成器的语法就是在 function 关键字后加一个 *
号,通过 yield 返回被迭代的元素
function *getIterator() { |
当我们调用 getIterator 方法时,并不会执行该方法,而是会返回一个迭代器。每当该迭代器调用 next 方法时,就会开始执行函数,直至遇到 yield 语句,它会把 yield 后的表达式作为 value 属性的值返回,例如遇到yield 1
时,返回对象 {done: false, value: 1}
。
当返回对象后,不会继续执行函数,而是会挂起该函数,直到迭代器再次调用 next 方法时,就会从上次挂起的地方开始执行,直到遇到 yield 语句或者执行完函数。
当执行完函数时会返回 {done: true, value: undefined}
,如果是通过 return 语句结束函数的,例如 return 10
,那么这时会返回 {done: true, value: 10}
。
因为生成器只是一个函数,所以可以有函数表达式的形式,它的函数表达式形式如下
let getIterator = function *() { |
同样生成器也可以是对象的方法
let o = { |
当生成器作为对象的方法时,*
要放在方法名前,如 *getIterator
。
可迭代与 for…of 循环
可迭代的概念是针对对象提出,如果对象含有 Symbol.iterator 属性,那么就说它是可迭代的。Symbol.iterator 是一个函数,它返回一个迭代器,因此我们可以通过下面的方式去迭代一个对象
let obj = { |
上面遍历对象还是比较麻烦,对于任意一个对象都需要写一些模板代码,如
let iterator = obj[Symbol.iterator]() |
所以为了简化代码的编写,ES6 提出一个新的 for … of 语法糖来帮我们遍历可迭代的对象,上面遍历对象的代码可以写为如下
for(value of obj) { |
代码简洁了很多,但是我们要明白 for … of 不是黑魔法,for … of 所做的事情同上面写的模板代码做的事情是一样的。
所有由生成器生成的迭代器都是可迭代的,因为生成器会默认为迭代器的 Symbol.iterator 属性赋值。
let getIterator = function *() { |
我们可以通过判断对象的 Symbol.iteraor 是否为 function 来判断该对象是否可迭代
function isIterable(obj) { |
内置的迭代器
集合迭代器
通过上面的代码我们知道数组、Map 以及 Set 这三个几个都是可迭代的,下面我们就将学习它们内置的迭代器。每一个集合都有如下三个方法:
- entries
- keys
- values
这三个方法都会返回一个迭代器,entries 迭代器中的元素为键值对组成数组,keys 中的元素为集合中的键,values 中的元素为集合的值。
其实对于数组和 Set 来说没有键的概念,但是为了接口的统一,我们在这里定义一下数组与 Set 的键:数组的键就是下标 (0, 1, …),Set 的键与它的值相同。
定义如下集合
let arr = ["red", "green", "blue"] |
entries
for(let entry of arr.entries()) { |
keys
for(let key of arr.keys()) { |
values
for(let value of arr.values()) { |
现在还有一个问题就是集合它们的 Symbol.iterator 是什么,对于数组和 Set 来说,它们的 Symbol.iterator 与 values 相同,对于 Map 来讲,它们的 Symbol.iterator 与 entries 相同
for(let item of arr){ |
字符串迭代器
字符串越来越像数组了,例如我们可以通过下标去访问字符串中的字符,所以我们可以通过下面的方式遍历字符串
const str = "Hi 𠮷" |
但是因为 JavaScript 不识别 4 个字节表示的字符,所以打印如下
H |
所幸 ES6 对 Unicode 提供全套支持,所以 ES6 提供的字符串迭代器解决了这个问题
for(let c of str) { |
H |
展开运算符
看下面一个例子
function removeDuplicate(array) { |
上面这个函数是对去除数组中的重复元素,在其中我们使用了 […set] 展开运算符,将 Set 集合展开为一个数组返回,那么 JavaScript 内部是怎么做的,其实内部默认调用了 Set 集合迭代器,然后将 Set 集合中的元素添加到数组中的
let arr = [...set] |
迭代器高级用法
为迭代器传入参数
我们在调用迭代器的 next 方法时,还可以向其中传入一个参数
function *getIterator() { |
上面的动图说明了代码的执行过程:当我们调用第一个 next 时,方法开始执行,当执行到 yield 1
后停止执行,挂起函数并将值返回,当我们第二次调用 next 时,这时我们传递一个参数,这个参数会被传递给 first,接着继续执行,当执行到 yield first + 2
挂起函数,并返回值。
抛出错误
迭代器还有一个 throw 方法,它可以抛出一个错误,具体的应用场景我还不清楚
function *getIterator() { |
委托生成器
有的时候希望能够组合两个迭代器,我们可以通过 yield* 的语句来组合两个迭代器
function *generator01() { |
yield *gengerator01()
表示委托 generator01
这个生成器来做事情,当执行到这条语句时,接下来会进入 generator01
内部去执行,直到遇到 yield 语句,则会将函数挂起,然后将值返回,当下次调用 next 方法时则从上次挂起的地方重新开始执行。