ES6 中的 iterator
【简介】
遍历器/迭代器。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作。这种数据结构是“可遍历的”(iterable)。
如何判断是否可遍历?
typeof target[Symbol.iterator] // function
【作用】
1. 为各种数据结构,提供一个统一的、简便的访问接口;
2. 使得数据结构的成员能够按某种次序排列;
3. ES6 创造了一种新的遍历命令for...of 循环,Iterator 接口主要供for...of消费。
【遍历】
const colors = ["red", "green", "blue"] for (var i = 0, len = colors.length; i < len; i++) { console.log(colors[i]) }
- 追踪下标位置,
- 判断循环何时停止。
【自定义 iterator】
function createIterator(items) { var i = 0 return { next: function () { var done = (i >= items.length) var value = !done ? items[i++] : undefined return { done: done, value: value, } } } } var iterator = createIterator([1, 2, 3]) console.log(iterator.next()) console.log(iterator.next()) console.log(iterator.next()) console.log(iterator.next())
Iterator 的遍历过程:
1、创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。
2、第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。
3、不断调用指针对象的next方法,直到它指向数据结构的结束位置。
虽然是比 for 循环简单了些,但手动写个 iterator 太麻烦了,所以ES6 推出 generator ,方便创建 iterator。也就是说,generator 就是一个返回值为 iterator 的函数。
【generator 和 iterator】
function* createIterator() { yield 1 yield 2 yield 3 } let iterator = createIterator() console.log(iterator.next().value) console.log(iterator.next().value) console.log(iterator.next().value)
Generator:ES6 提供的一种异步编程解决方案。
执行 Generator 函数会返回一个 iterator 对象,通过这个对象可以依次遍历 Generator 函数内部的每一个状态。
【for…of 和 iterator】
const colors = ["red", "green", "blue"]; for (let color of colors) { console.log(color); }
当使用for…of循环遍历某种数据结构时,该循环会自动去寻找 Iterator 接口,并调用Symbol.iterator方法,返回该对象的默认遍历器。
for…of循环可以使用的范围包括数组、Set 和 Map、类似数组对象(arguments、DOM NodeList、 Generator、字符串)。
【内置的 iterator】
ES6 的有些数据结构原生具备 Iterator 接口(比如数组),即不用任何处理,就可以被 for…of 循环遍历。
原因在于,这些数据结构原生部署了Symbol.iterator属性,另外一些数据结构没有(比如对象)。
凡是部署了Symbol.iterator属性的数据结构,就称为部署了遍历器接口。调用这个接口,就会返回一个遍历器对象。
原生具备 Iterator 接口的数据结构如下。
Array、Map、Set、String、arguments 、NodeList
// Array iterator const heros = ['Tony', 'Steve', 'Natasha', 'Banner', 'Thor'] for (const hero of heros) { console.log(hero) } // Map iterator const sex = 'male' const userMap = new Map([ ['name', 'Stark'], [48, 'age' ], ['gender', sex] ]) for (let entry of userMap.entries()) { console.log(entry) } for (let key of userMap.keys()) { console.log(key) } for (let value of userMap.values()) { console.log(value) } const numberSet = new Set([1, 2, 2, 3, 4, 4, 5]) for (let entry of numberSet.entries()) { console.log(entry) } for (let key of numberSet.keys()) { console.log(key) } for (let value of numberSet.values()) { console.log(value) } // String iterator const str = 'ca 𠮷 r'for (const c of str) { console.log(c) } // arguments iterator function getUserInfo (name, male, age, hobby) { console.log(arguments.length) for (const arg of arguments) { console.log(arg) } } getUserInfo('code monkey', 'male', '28', 'make money')
一个类数组对象如果要具备可被 for...of 循环调用的 Iterator 接口,就必须在Symbol.iterator的属性上部署遍历器生成方法。
// Array alike iterator const arrAlike = { 0: 'a', 1: 'b', 2: 'c', length: 3, [Symbol.iterator]: Array.prototype[Symbol.iterator] } for (const item of arrAlike) { console.log(item) // a b c } // 普通对象部署数组的Symbol.iterator方法,并无效果。 const obj = { a: 'a', b: 'b', c: 'c', length: 3, [Symbol.iterator]: Array.prototype[Symbol.iterator] } for (const item of obj) { console.log(item) // undefined undefined undefined }
注:普通对象部署数组的Symbol.iterator方法,并无效果。因为数组,Map和类数组对象等结构中的成员都是有顺序的,
即都是线性的结构,而对象各成员并没有一个确定的顺序,所以遍历时先遍历谁后遍历谁并不确定。
【解构、扩展运算 和 Iterator】
对数组和 Set 结构进行解构赋值时,会默认调用Symbol.iterator
方法。
扩展运算符(...)也会调用默认的 Iterator 接口。
const set = new Set().add('a').add('b').add('c') const [x, y] = set console.log(x, y) const [first, ...rest] = set console.log(first, rest) const str = 'hello' console.log([...str]) const arr = ['b', 'c']; console.log(['a', ...arr, 'd'])
【小结】
1. Iterator 就是为了提供一种统一的接口机制。任何的数据结构,只要部署了Iterator接口,便可以使用 for…of 来遍历。
2. es6中有三类结构生来就具有Iterator接口:数组、类数组对象、Map和Set结构。
3. String、类数组对象、函数的arguments对象和nodeList 对象的遍历以及generator的使用、扩展运算符和解构赋值的操作也会调用到 iterator。
4. 对象不具备 iterator 接口,但是可以通过部署 Symbol.iterator 属性来使对象可遍历。