内部迭代器:本身是函数,该函数内部定义好迭代规则,完全接受整个迭代过程,外部只需要一次调用。例如Array.prototype.forEach方法、jQuery.each都是内部迭代器。
外部迭代器:本身是函数,执行返回迭代对象,迭代下一个元素必须显式调用。使用forEach遍历,只可以一次性把数据全部拉取消耗,而迭代器可以用于以一次一步的方式控制行为,使得迭代过程更加灵活可控。
迭代器使用
实现迭代器接口后,如何进行使用?
let arr = ['a', 'b']; let iter = arr[Symbol.iterator](); iter.next() // { value: 'a', done: false } iter.next() // { value: 'b', done: false } iter.next() // { value: undefined, done: true }
除了像上述代码这样单独使用外,实现该接口的目的,就是为所有数据结构,提供一种统一的访问机制。实现了该接口,就可以调用ES6中新增的通过调用Iterator 接口实现的API,例如for..of就是典型的消耗迭代器的API。下面具体看看for..of的实现原理:
let arr = [1,2,3]; for(let num of arr){ console.log(num); }
输出结果为:1,2,3
for-of 循环首先会调用 arr 数组中Symbol.iterator 属性对象的函数,就会获取到该数组对应的迭代器,接下来 iterator.next()被调用,迭代器结果对象的 value 属性会被放入到变量 num 中。数组中的数据项会依次存入到变量num 中,直到迭代器结果对象中的 done 属性变成 true 为止,循环就结束。
for-of 循环完全删除了for循环中追踪集合索引的需要,更能专注于操作集合内容。
ES6 规定,默认的 Iterator 接口部署在数据结构的Symbol.iterator属性,或者说,一个数据结构只要具有Symbol.iterator属性,就可以认为是“可遍历的”(iterable)。就可以使用上述默认会调用Iterator函数的API,而如果该数据结构没有提供实现这个接口(例如对象)又该怎么样达到最大化的互操作性呢?那么就可以自己构建符合这个标准的迭代器。
下面是一个为对象添加 Iterator 接口的例子:
let obj = { 0: 'a', 1: 'b', 2: 'c', length: 3, [Symbol.iterator]: function () { let curIndex = 0; let next = () => { return { value: this[curIndex], done: this.length == curIndex++ } } return { next } } } for (let item of obj) { console.log(item) }
如果把该对象的[Symbol.iterator]属性删除,那么就会报错Uncaught TypeError: obj is not iterable,告诉我们obj是不可被遍历。
除了上面展示的for..of循环可以一个一个的消耗迭代器之外,还有其它ES6结构也可以用来消耗迭代器。例如spread运算符:
function f(x, y, z) { console.log(x, y, z) } f(...[2, 3, 1])
以及结构赋值也可以部分或者完全消耗一个迭代器:
let arr = [1, 2, 3, 4, 5] var it = arr[Symbol.iterator]() //部分消耗 var [x, y] = it console.log(x, y) //打印1 2 //完全消耗 var [y, ...z] = it console.log(y, z) //打印3 [4,5]
JavaScript 默认产生迭代器的API
产生迭代器对象,我们可以通过定义迭代器函数来生产迭代器对象,还可以调用JavaScript在内置数据结构中定义好的迭代器函数来生产。除此之外,对于数组以及ES6新增的几个新的数据结构MAP、Set,这些集合不仅本身已部署迭代器接口,还提供了API方法来产生迭代器对象。ES6 的数组、Set、Map 都部署了以下三个方法,调用后都返回遍历器对象。
entries() 返回一个遍历器对象,用来遍历[键名, 键值]组成的数组。
keys() 返回一个遍历器对象,用来遍历所有的键名。
values() 返回一个遍历器对象,用来遍历所有的键值。
数组的迭代器使用实例
下面是数组的迭代器接口使用:
let arr = [1,2,3,4] let arrEntires = arr.entries() arrEntires.next() //{value: [0, 1], done: false} let arrKeys = arr.keys() //对于数组,索引值就是键值 arrKeys.next() //{value: 0, done: false} let arrValues = arr.values() arrValues.next() //{value: 1, done: false}
下面代码可以看出数组的for…of 遍历的默认迭代器接口是values
for(let item of [1,2,3]) { console.log(item)// [1,2,3] }
Set的迭代器使用实例
下面是Set的迭代器接口使用:
let set = new Set([1,2,3,4]) let setEntires = set.entries()//对于 Set,键名与键值相同。 setEntires.next() //{value: [1, 1], done: false} let setKeys = set.keys() setKeys.next() //{value: 1, done: false} let setValues = set.values() setValues.next() //{value: 1, done: false}
如下可以看出Set的默认迭代器接口[Symblo.iterator]是values
for(let item of new Set([1,2,3,4])){ console.log(item)// [1,2,3,4] }
Map的迭代器使用实例
下面是Map的迭代器接口使用: