由于是关于Iterator的文章,所以前文不得不提起 JavaScript 表示“集合”的数据结构,主要是数组(Array)和 对象(Object),ES6又添加了 Map 和 Set,这样就有了四种数据集合,用户还可以组合使用它们,定义自己的数据结构,这样就需要一种统一的接口机制,来处理所有不同的数据结构。 ---本文摘选阮一峰《ECMAScript 6 标准入门》
Iterator(遍历器)的概念
说明:遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署Iterator接口,就可以完成遍历操作。
Iterator的作用有三个:
- 为各种数据结构,提供统一 简便的访问接口
- 使数据结构的成员能够按某种次序排列
- ES6创造了一种新的遍历 for...of 循环,Iterator接口主要供 for...of 消费。
Iterator的遍历过程大概是这样的:
- 创建一个指针对象,指向当前数据结构的起始位置。
- 第一次调用指针对象的
- 第二次调用指针对象的 next 方法,指针就指向数据结构的第二个成员。
- 不断调用指针对象的 next 方法,直到它指向数据结构的结束位置。
PS:每一次调用 next方法,都会一个包含 value和 done两个属性的对象。
value 属性是当前成员的值, done 属性是一个布尔值,表示遍历是否结束,来看代码:JS code:var it = makeIterator(['a', 'b']);it.next() // { value: "a", done: false }it.next() // { value: "b", done: false }it.next() // { value: undefined, done: true }function makeIterator(array) { var nextIndex = 0; return { next: function() { return nextIndex < array.length ? {value: array[nextIndex++], done: false} : {value: undefined, done: true}; } };}复制代码
说明:makeIterator 是遍历器生成函数,作用就是返回一个遍历器对象。对数组 ['a', 'b'] 执行这个函数,就会返回该数组的遍历器对象 ( 指针对象 ) it,指针对象的 next 方法,用来移动指针,next 方法返回一个对象,对象具有 value
数据结构的默认Iterator接口
说明:Iterator接口的目的:为所有数据结构提供统一的访问机制( for...of循环 ) ,当使用 for...of 循环遍历某种数据结构时,该循环会自动去寻找Iterator接口。
ES6规定,数据结构只要具有 Symbol.iterator 属性,就是可遍历的
Symbol.iterator 属性本身是一个函数,是当前数据结构默认的遍历器生成函数,执行这个函数,就会返回一个遍历器。
PS:Symbol.iterator 是一个表达式,返回
JS code:const obj = { [Symbol.iterator]: function () { return { next: function () { return { value: 1, done: true }; } }; }};复制代码
说明:对象 obj 是可遍历的,因为有 Symbol.iterator
在ES6中,有三类数据结构原生具备Iterator接口:数组、某些类似数组的对象、Set和Map结构,来看代码:
JS code:let arr = ['a', 'b', 'c'];let iter = arr[Symbol.iterator]();iter.next() // { value: 'a', done: false }iter.next() // { value: 'b', done: false }iter.next() // { value: 'c', done: false }iter.next() // { value: undefined, done: true }复制代码
说明:变量arr是一个数组,原生具有遍历器接口,部署在 arr 的 Symbol.iterator 属性上面,所以,调用这个属性,就得到遍历器对象。
上面提到三类数据结构原生具备Iterator接口,有可能会问为什么没有我们常见的对象Object,那现在为大家解答,对象之所以没有默认部署Iterator接口,是因为对象的哪个属性先遍历,哪个属性后遍历是不确定的,需要开发者手动指定,所以一个对象如果要有可被
JS code:class RangeIterator { constructor(start, stop) { this.value = start; this.stop = stop; } [Symbol.iterator]() { return this; } next() { var value = this.value; if (value < this.stop) { this.value++; return { done: false, value: value}; } else { return { done: true, value: undefined}; } }}function range(start, stop) { return new RangeIterator(start, stop);}for (var value of range(0, 3)) { console.log(value);}复制代码
说明:Symbol.iterator 属性对应一个函数,执行后返回当前对象的遍历器对象。
下面是另一个为对象添加Iterator接口的例子,来看代码:
JS code:let obj = { data: [ 'hello', 'world' ], [Symbol.iterator]() { const self = this; let index = 0; return { next() { if (index < self.data.length) { return { value: self.data[index++], done: false }; } else { return { value: undefined, done: true }; } } }; }};复制代码
PS:对于类似数组的对象(存在数值键名和length属性)部署Iterator接口,有一个简便方法,就是 Symbol.iterator 方法直接引用数组的Iterator接口,来看代码:
JS code:NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];// 或者NodeList.prototype[Symbol.iterator] = [][Symbol.iterator];[...document.querySelectorAll('div')] // 可以执行了复制代码
如果是类似数组的对象调用数组的 Symbol.iterator 方法呢?来看代码
JS code:let iterable = { 0: 'a', 1: 'b', 2: 'c', length: 3, [Symbol.iterator]: Array.prototype[Symbol.iterator]};for (let item of iterable) { console.log(item); // 'a', 'b', 'c'}复制代码
注意:普通对象部署数组的 Symbol.iterator 方法,并无效果。
调用Iterator接口的场合
有一些场合会默认调用Iterator接口(即
- 解构赋值对数组和Set结构进行解构赋值时,会默认调用 Symbol.iterator 方法。
JS code: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'];复制代码
- 扩展运算符扩展运算符(...)也会调用默认的iterator接口。
JS code:// 例一var str = 'hello';[...str] // ['h','e','l','l','o']// 例二let arr = ['b', 'c'];['a', ...arr, 'd']// ['a', 'b', 'c', 'd']复制代码说明:提供了一种简便机制,可以将任何部署了Iterator接口的数据结构 转为数组,即只要某个数据结构部署了Iterator接口,就可以对它使用扩展运算符,将其转为数组。
- yield*yield*后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口。
JS code: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 }复制代码
- 其他场合由于数组的遍历会调用遍历器接口,所以任何接受数组作为参数的场合,其实都调用了遍历器接口,如: 1. for...of 2. Array.from() 3. Map(), Set(), WeakMap(), WeakSet() 4. Promise.all() 5. Promise.race()
字符串的Iterator接口
字符串是一个类似数组的对象,也原生具有Iterator接口,来看代码:
JS code:var someString = "hi";typeof someString[Symbol.iterator]// "function"var iterator = someString[Symbol.iterator]();iterator.next() // { value: "h", done: false }iterator.next() // { value: "i", done: false }iterator.next() // { value: undefined, done: true }复制代码
说明:调用
当然还可以覆盖原生的
JS code:var str = new String("hi");[...str] // ["h", "i"]str[Symbol.iterator] = function() { return { next: function() { if (this._first) { this._first = false; return { value: "bye", done: false }; } else { return { done: true }; } }, _first: true };};[...str] // ["bye"]str // "hi"复制代码
说明:字符串 str 的
Iterator接口与Generator函数
Symbol.iterator 方法的最简单实现,还是使用Generator函数,来看代码:
JS code:var myIterable = {};myIterable[Symbol.iterator] = function* () { yield 1; yield 2; yield 3;};[...myIterable] // [1, 2, 3]// 或者采用以下简单写法let obj = { * [Symbol.iterator]() { yield 'hello'; yield 'world'; }};for (let x of obj) { console.log(x);};// hello// world复制代码
说明:Symbol.iterator 方法几乎不用部署代码,仅用 yield 命令给出每一步的返回值即可。
遍历器对象的return(),throw()
说明:遍历器对象除了具有
return 适用场景:如果 for...of 循环提前退出(通常是因为出错,或有 break 语句 或 continue 语句),就会调用
JS code:function readLinesSync(file) { return { next() { if (file.isAtEndOfFile()) { file.close(); return { done: true }; } }, return() { file.close(); return { done: true }; }, };}复制代码
说明:函数 readLinesSync 接受一个文件对象作为参数,返回一个遍历器对象,其中除了
throw 方法主要是配合Generator函数使用,一般的遍历器对象用不到这个方法。
好了,关于 Iterator 混子前端就总结到这里,老规矩,欢迎点赞和纠错!
最后,祝大家工作愉快!
( 内心os:明天终于周五了 )