前言
ES6新增了两个协议:
可迭代协议:对象必须具有Symbol.Iterator属性,属性值为一个函数,当这个对象被迭代时,就会调用该函数,返回一个迭代器。
迭代器协议:描述了迭代器对象的具体规则。
迭代器
迭代器,它是用于访问集合类的标准访问方法,它可以把访问逻辑从不同类型集合中抽象出来,从而避免向外部暴露集合内部的结构。
比如我们访问一个数组可能使用for循环或者map,foreach,filter等for(int i=0; i<array.size(); i++) { ... get(i) ... }, 但是当我们想要遍历链表(linkedlist)的时候就得使用while循环while((e=e.next())!=null) { ... e.data() ... }, 以上两种方式我们都必须知道集合的内部结构是怎么样的我们才可以使用对应的循环方式去循环整个集合,那么这样就造成了很大的耦合度,当我们把一个集合的类型从Arrarlist变成Linkedlist的时候,那么原来客户端的代码必须重写,因为我们集合变了,遍历的方式也必须改成对应的方式。
在js中,当我们要迭代集合(指数组)的时候,我们利用ES6为Array类增加的迭代器属性(iterator),该属性的值为一个函数,该函数返回一个迭代器对象。而Array类的iterator属性可以通过Symbol.iterator来访问。我们执行这个函数来生成该数组的迭代器。
const arr = ['a','b','c','d']
const arrIterator = arr[Symbol.iterator]();
关于什么是Symbol,可以看本文附录。
我们已经得到了数组的迭代器,如果我们需要依次获得数组中的值,我们需要不断地调用迭代器的next方法。
arrIterator.next();
// {value: 'a', done: false}
可以看见执行next方法后返回了数据对象,value键对应的是数组的元素,done代表的时候迭代器是否已经迭代到了数组长度的最后下标。
当迭代器迭代到数组长度以外之后,返回了{value: undefined, done: true}
我们也可以这么写:
for (const n of arrIterator) {
console.log(n)
}
// a
// b
// c
// d
// e
总结一下:
在Js中迭代器对象实现了可迭代协议,迭代器对象由Symbol.iterator属性的值返回。
Symbol.iterator属性的值是一个函数,它返回一个迭代器对象。
迭代器指的是拥有next方法的对象。
该next方法必须返回一个带有value和done的对象。
除了Symbol.iterator,ES6中另外提供三种获取数组迭代器的方法
var arr = ['a','b','c']
entries
const iterator = arr.entries();
iterator.next(); // {value: [0,'a'], done: false}
...
iterator.next(); // {value: undefined, done: true}
可以看到entries提供的迭代器返回的结构与Symbol.iterator相似。只是value键对应的值是,包含当前迭代的数组元素的下标和值的数组。
keys
const iterator = arr.keys()
iterator.next()
// {value: 0, done: false}
...
iterator.next()
// {value: undefined, done: true}
value值是当前迭代的数组元素的下标。
values
const iterator = arr.values()
undefined
iterator.next()
// {value: 'a', done: false}
...
iterator.next()
// {value: undefined, done: true}
value值是当前迭代的数组元素。
附录-Symbol
Symbol是es6新增的类型,它是一个基本类型。
symbol属性值对应的值是唯一的,这解决了命名冲突的问题,类似于id的作用。
symbol值不能与其他数据进行计算,包括与字符串拼接。
for/in ,for/of遍历时不会遍布symbol属性
我们知道symbol可以作为属性值存在。并且它具有唯一的特性,举个栗子:直接let s = Symbol();测试s就是Symbol类型了。怎么说他是唯一的呢?
let s = Symbol(); let ss = Symbol(); s == ss ; 结果是false;或者
let s = Symbol('a'); let ss = Symbol('a'); s == ss ; 结果是false
有人会好奇Symbol('a')里面的参数a又是怎么回事呢?字符串a表示一种修饰,对你当前创建的Symbol类型的一种修饰,作为区分使用,否则当你创建多个Symbol数据时,容易混淆。
现在我们回过头说Iterotor是symbol的内置符号,而字符串a是自定义的符号。
附录-for of
条件语句,可以理解为JavaScript解释器在源代码中会经过不同分支路径。而循环语句则是把这些路径弯曲又折回起点,以重复执行代码中的某部分。
es6定义了一个新循环语句:for/of。
for of 与 for in不相同 (in输出的是下标,of输出的是元素)
for/of循环是专门用于可迭代对象的,什么是可迭代对象呢?
我们前文提到具有symbol.iterator属性的对象就是可以迭代的。而这个对象就是可迭代对象。
对象本身默认是不可迭代的。运行时尝试对常规对象使用for/of会抛出TypeError:
附录-JS中 可迭代的数据类型
Array
Set
Map
WeakMap
WeakSet
可以使用for of或new Set(传入参数的数据类型)检验