Iterator概念及用法

一、由来及意义#
Javascript中表示“集合”的数据结构,主要是 Array、Object、Map、Set 这四种数据集合,除此之外,它们相互之间还可以组合使用,例如Array的成员是Map,Map的成员是Object等。因此Javascript的需要一种统一的接口机制,来处理所有不同的数据结构。
遍历器(Iterator)就是这样一种机制。它是一种接口,可以为各种不同的数据结构提供一种访问机制(访问接口),任何数据结构部署Iterator接口,就可以完成该数据解构成员的遍历操作(Iterator 接口主要供for...of使用)。
二、具体实现流程#
Iterator的遍历过程:
- 创建一个指针对象,指向数据解构的起始位置。
- 第一次调用指针对象的
next()方法,指针指向数据结构的第一个成员。 - 第二次调用指针对象的
next()方法,指针指向数据结构的第二个成员。 - 不停调用指针对象的
next()方法,直到它指向数据结构结束的位置。(类似于C语言中的链表)
每一次调用next方法,都会返回数据结构中被指针指向的成员的信息。该信息为一个对象,其中包含value和done两个属性的对象{ value: something , done: false },value属性是当前成员的值,done属性是一个布尔值,表示遍历是否结束(done:false:表示循环还没有结束,done:true :表示循环结束了)。
模拟next方法返回值例子:
下面代码定义了一个makeIterator函数,它是一个遍历器生成函数,作用就是返回一个遍历器对象。对数组['前端','收割','机']执行这个函数,就会返回该数组的遍历器对象(即指针对象)goodjob。
由于 Iterator 只是把接口规格加到数据结构之上,所以,遍历器与它所遍历的那个数据结构,实际上是分开的,完全可以写出没有对应数据结构的遍历器对象,或者说用遍历器对象模拟出数据结构。
三、具有默认 Iterator 接口的数据结构#
Iterator 接口的目的,就是为所有数据结构,提供了一种统一的访问机制,当使用for...of循环遍历某种数据结构时,该循环会自动去寻找 Iterator 接口。
因此,当某种数据结构具有Iterator 接口,即表示该数据结构是可遍历的(iterable)。
默认的 Iterator 接口部署在数据结构的Symbol.iterator属性,或者说,一个数据结构只要具有Symbol.iterator属性,就可以认为是“可遍历的”(iterable)。Symbol.iterator属性本身是一个函数,就是当前数据结构默认的遍历器生成函数。执行这个函数,就会返回一个遍历器。至于属性名Symbol.iterator,它是一个表达式,返回Symbol对象的iterator属性,这是一个预定义好的、类型为 Symbol 的特殊值,所以要放在方括号内。
对象object1是可遍历的(iterable),因为具有Symbol.iterator属性。执行这个属性,会返回一个遍历器对象。该对象的根本特征就是具有next方法。每次调用next方法,都会返回一个代表当前成员的信息对象,具有value和done两个属性。
凡是部署了Symbol.iterator属性的数据结构,就称为部署了遍历器接口。调用这个接口,就会返回一个遍历器对象。即不用任何处理,就可以被for...of循环遍历。
原生具备 Iterator 接口的数据结构:
- Array
- Map
- Set
- String
- TypedArray
- NodeList 对象
- 函数的 arguments 对象
数组的Symbol.iterator属性:
数组通过for of调用iterator接口生成的遍历器
Map通过for of调用iterator接口生成的遍历器
Set 通过for of调用iterator接口生成的遍历器
类数组对象通过for of调用iterator接口生成的遍历器
对于不具备Iterator接口的数据结构(主要是对象)都需要自己在Symbol.iterator属性上面部署,这样才会被for...of循环遍历。
原因:对象(Object)之所以没有默认部署 Iterator 接口,是因为对象的哪个属性先遍历,哪个属性后遍历是不确定的,需要开发者手动指定。本质上,遍历器是一种线性处理,对于任何非线性的数据结构,部署遍历器接口,就等于部署一种线性转换。
下面是为对象添加 Iterator 接口的例子:
对于类似数组的对象(存在数值键名和length属性),部署 Iterator 接口,有一个简便方法,就是Symbol.iterator方法直接引用数组的 Iterator 接口。
注意,普通对象部署特定数据结构的Symbol.iterator方法,并无效果。例如普通对象部署数组的Symbol.iterator方法。
如果Symbol.iterator方法对应的不是遍历器生成函数(即会返回一个遍历器对象),解释引擎将会报错。
四、调用 Iterator 接口的场合#
除了for...of循环,某些场景会默认调用 Iterator 接口(即Symbol.iterator方法)
解构赋值
扩展运算符
扩展运算符内部就调用 Iterator 接口,这提供了一种简便机制,可以将任何部署了 Iterator 接口的数据结构,通过扩展运算符,转为数组。
yield*
yield*后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口。
Array.from()
往Array.from()函数以类数组形式输入值,Array.from()函数调用Iterator接口,将输入的类数组转化成数组
Map(), Set(), WeakMap(), WeakSet()
往Map构造函数以数组形式输入键值对,新建Map对象时,Map构造函数调用Iterator接口,遍历存入键值对
Promise.all()
往Promise.all()函数以Promise数组形式输入值,Promise.all()函数调用Iterator接口,将promise请求遍历执行。
Promise.all()方法接受一个数组作为参数,p1、p2、p3都是 Promise 实例,如果不是,就会先调用Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。另外,Promise.all()方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。
Promise.race()
往Promise.race()函数以Promise数组形式输入值,Promise.race()函数调用Iterator接口,将promise请求遍历执行。
Promise.race()方法接受一个数组作为参数,p1、p2、p3都是 Promise 实例,如果不是,就会先调用Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。另外,Promise.race()方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。
补充:Generator 函数与 Iterator 接口的关系:
对象的Symbol.iterator方法,等于该对象的遍历器生成函数,调用该函数会返回该对象的一个遍历器对象。由于 Generator 函数就是遍历器生成函数,因此可以把 Generator 赋值给对象的Symbol.iterator属性,从而使得该对象具有 Iterator 接口。
五、总结#
- 遍历器(
Iterator)可以为各种不同的数据结构提供一种访问机制(访问接口),任何数据结构部署Iterator接口,就可以完成该数据解构成员的遍历操作(Iterator 接口主要供for...of使用)。 Iterator的遍历过程:创建一个指针对象,指向数据解构的起始位置。不停调用指针对象的next()方法,指针往后移动,直到它指向数据结构结束的位置。每一次调用next方法,都会返回数据结构中被指针指向的成员的信息{ value: something , done: false }。Array,Map,Set,String,TypedArray,NodeList 对象,函数的arguments对象为原生具备Iterator接口的数据结构。- 调用
Iterator接口的场合:for...of循环、解构赋值、扩展运算符、yield*、Array.from()、Map()、Set()、WeakMap()、WeakSet()、Promise.all()、Promise.race()。