let ary = ['a', 'b', 'c']; //数组
let str = 'str'; //字符串
let set = new Set(); //Set
set.add('s');
set.add('s');
set.add('e');
set.add('t');
let map = new Map(); //Map
let o = {};
map.set('m', 'm');
map.set(o, 'object');
let obj = { //对象
name: 'syj',
age: 24
}
function forOf(list) {
for(var value of list) {
console.log(value);
}
}
forOf(ary); //a b c
forOf(str); // s t r
forOf(set); // s e t
forOf(map); //[ 'm', 'm' ], [ {}, 'object' ]
forOf(obj); //TypeError: list[Symbol.iterator] is not a function
Iterator
背景
ES5中的loop
for循环
这是标准for循环的写法,字符串也支持,定义一个变量i作为索引,以跟踪访问的位置,len是数组的长度,条件就是i不能超过len。
这种写法看起来算是比较简单明了的,只不过for里面要写一大坨,而且当出现嵌套的时候,要定义多个变量去维护不同集合的索引,增加复杂度,容易出错。
for-in
什么是迭代器
ES5模拟一个迭代器
createIterator可以看做一个返回迭代器对象的工厂函数,通过while循环调用返回的iterator.next()会得到result,直到done变为true。用ES5写还是比较麻烦的,而且我们这样写并不支持for...of。很快就会看到ES6真正的写法啦。
迭代器协议(Iteration protocols)
可迭代类型
尝试使用for...of
通过结果可以看出,确实集合类型和字符串都可以用默认的for...of来迭代。但是对象却不可以,内部抛出一个错误,找不到迭代器的接口,证实了上面的言论。
也许你不了解Set,Map,Set通常是类似于数组的,无重复项的集合,Map是类似于对象的,但是他的key可以是任何类型,增强版的“键值对”的数据结构。详情了解可以移步Set-Map。
使对象可迭代
通过defineProperty向obj对象中添加[Symbol.iterator],我们在对应的value做的就是通过Object.keys取出它的key,然后调用一次next就往后找一位,可以通过next()尝试一下。因为obj有了[Symbol.iterator],for...of可以找到,并且调用。
通过直接声明给对象定义一个iterator。这里是iterator经典的用法。当这个数据集合特别大,甚至无限的时候,我们要把它定义好,取出来都是很庞大的操作,这时候iterator的优势就很明显了。只有调用一次next,才返回下一个值,不调用就没有。假如说我们给done没加限制条件,这个迭代器就永远没有done=true,就会永远可以next。为了防止for...of ,调用next的时候,有可能让我的电脑卡死,限制在1000以内。
还有一些值得关注的点
拓展运算符与iterator关系密切
第一次log我们尝试把fibonacci使用... 展开,但是会报错。把这行console注释掉以后,给这个对象添加一个Symbol.iterator属性,再次使用... ,就会得到之前的斐波那契数列了。 由于ES6的数组等集合默认有Symbol.iterator属性,所以我们都是可以直接使用扩展运算符。
类数组
总结
ES6提出迭代器的概念,契合了JS语言发展的趋势,统一处理数据结构。迭代器是ES6中很重要的部分,我们仅仅使用是很方便的,但是自定义一些iterator,或者更复杂的方式运行迭代器,还需要我们继续学习。 而定义一个对象的迭代器,又与Symbol对象有关,它采用了Symbol里面的一个默认属性iterator,用来访问对象的迭代器。最后可迭代的数据类型,我们都可以用for...of方法循环遍历,而且集合和字符串内置迭代器,我们可以轻松方便的访问。拓展运算符也是基于iterator的拓展,通过...我们可以把其他数据类型转化为数组,因为...通过执行迭代器,并读取返回的value。
下一篇我们还有未完成的高级迭代器-生成器。