一、什么是迭代器?
生成器概念在Java,Python等语言中都是具备的,ES6也添加到了JavaScript中。Iterator可以使我们不需要初始化集合,以及索引的变量,而是使用迭代器对象的 next 方法,返回集合的下一项的值,偏向程序化。
迭代器是带有特殊接口的对象。含有一个next()方法,调用返回一个包含两个属性的对象,分别是value和done,value表示当前位置的值,done表示是否迭代完,当为true的时候,调用next就无效了。
ES5中遍历集合通常都是 for循环,数组还有 forEach 方法,对象就是 for-in,ES6 中又添加了 Map 和 Set,而迭代器可以统一处理所有集合数据的方法。迭代器是一个接口,只要你这个数据结构暴露了一个iterator的接口,那就可以完成迭代。ES6创造了一种新的遍历命令for...of循环,Iterator接口主要供for...of消费。
二、如何使用迭代器?
1、默认 Iterator 接口
数据结构只要部署了 Iterator 接口,我们就成这种数据结构为“可遍历”(Iterable)。ES6 规定,默认的 Iterator 接口部署在数据结构的 Symbol.iterator 属性,或者说,一个数据结构只要具有 Symbol.iterator 数据,就可以认为是“可遍历的”(iterable)。
可以供 for...of 消费的原生数据结构
Array
Map
Set
String
TypedArray(一种通用的固定长度缓冲区类型,允许读取缓冲区中的二进制数据)
函数中的 arguments 对象
NodeList 对象
可以看上面的原生数据结构中并没有对象(Object),为什么呢?
那是因为对象属性的遍历先后顺序是不确定的,需要开发者手动指定。本质上,遍历器是一种线性处理,对于任何非线性的数据结构,部署遍历器接口就等于部署一种线性变换。
做如下处理,可以使对象供 for...of 消费:
// code1 function Obj(value) { this.value = value; this.next = null; } Obj.prototype[Symbol.iterator] = function() { var iterator = { next: next }; var current = this; function next() { if (current) { var value = current.value; current = current.next; return { done: false, value: value }; } else { return { done: true }; } } return iterator; } var one = new Obj(1); var two = new Obj(2); var three = new Obj(3); one.next = two; two.next = three; for (var i of one) { console.log(i); } // 1 // 2 // 3
2、调用 Iterator 接口的场合
(1) 解构赋值
// code2 let set = new Set().add('a').add('b').add('c'); let [x,y] = set; // x='a'; y='b' let [first, ...rest] = set; // first='a'; rest=['b','c'];
(2) 扩展运算符
// code3 // 例一 var str = 'hello'; [...str] // ['h','e','l','l','o'] // 例二 let arr = ['b', 'c']; ['a', ...arr, 'd'] // ['a', 'b', 'c', 'd']
(3)Generator 函数中的 yield* 表达式(下一章介绍)
// code4 let generator = function* () { yield 1; yield* [2,3,4]; yield 5; }; var iterator = generator(); iterator.next() // { value: 1, done: false } iterator.next() // { value: 2, done: false } iterator.next() // { value: 3, done: false } iterator.next() // { value: 4, done: false } iterator.next() // { value: 5, done: false } iterator.next() // { value: undefined, done: true }
(4)其它场合
for..of
Array.from
Map()、Set()、WeakMap()、WeakSet()
Promise.all()
Promise.race()
3、for...of 循环的优势
先看看,数组 forEach 方法的缺点:
// code5 myArray.forEach(function (value) { console.log(value); });
这个写法的问题在于,无法中途跳出 forEach 循环,break 命令或 return 命令都不能生效。
再看看,对象 for...in 的循环的缺点:
for (var index in myArray) { console.log(myArray[index]); };
数组的键名是数字,但是 for...in 循环是以字符串作为键名,“0”、“1”、“2”等。
for...in 循环不仅可以遍历数字键名,还会遍历手动添加的期推荐,甚至包括原型链上的键。
某些情况下,for...in 循环会议任意顺序遍历键名
for...in 遍历主要是为遍历对象而设计的,不适用于遍历数组
那么,for...of 有哪些显著的优点呢?
有着同 for...in 一样的简洁语法,但是没有 for...in 那些缺点
不同于 forEach 方法,它可以与 break、continue 和 return 配合使用
提供了遍历所有数据结构的统一操作接口
for (var n of fibonacci) { if (n > 1000) { break; console.log(n); } }
4、各数据类型如何使用 for...of 循环?
(1)数组
for...of 循环允许遍历数组获得键值
var arr = ['a', 'b', 'c', 'd']; for (let a in arr) { console.log(a); // 0 1 2 3 } for (let a of arr) { console.log(a); // a b c d }
for...of 循环调用遍历器接口,数组的遍历器接口只返回具有数字索引的值
let arr = [3, 5, 7]; arr.foo = 'hello'; for (let i in arr) { console.log(i); // "0", "1", "2", "foo" } for (let i of arr) { console.log(i); // "3", "5", "7" }
(2)Map 和 Set 结构