前言
for.of是ES6为遍历所有数据结构而引入的统一方法。这里的所有数据结构仅指具有迭代器接口的数据。 如果在单个数据中引入了Symbol.iterator,则有一个iterator界面,可以使用for.of循环遍历成员。 也就是说,在for.of循环内部调用的数据结构是Symbol.iterator方法。
for.of循环的可用范围包括数组、Set和Map结构、arguments对象、类似于数组的对象(如DOM NodeList对象)、生成器对象和字符串。 也就是说,上述数据类型本机具有迭代器接口。
所以,不要误以为for.of只是用来遍历数组的。
Iterator
为什么引入 Iterator
为什么会引入迭代器,是因为ES6中添加了Map、Set,再加上原始数组、对象,一共形成了4种表示“集合”的数据结构。 在没有Map和Set之前,我们知道for.in通常用于遍历对象,并且有一个for循环经常用于遍历数据,但是现在引入的Map、Set有必要单独引入适合各自遍历的方法吗? 聪明的你一定能提供我们遍历所有数据结构的方法吗? 这种方法能够遍历所有的数据结构,这无疑是这些数据结构的共同特征。 然后,这个共同的方法基于这些共同的特征进行遍历。
可以理解为迭代器是上文所述的共同特征。
让我们看看官方是如何解释迭代器的。 扫描器(Iterator )就是这样的机制。 这是一个为各种数据结构提供统一访问机制的接口。 对于任何数据结构,都可以通过部署迭代器接口来完成遍历操作。 也就是说,按顺序处理该数据结构的所有成员。 一般来说,为了解决不同数据结构的扫描问题,引入了迭代器
在
Iterator 是什么样子的呢
模拟中,您将实现以下目标:functionmakeiterator{
var nextIndex=0;
返回
next :功能()
return nextindex阵列.长度?
{
value :阵列、
done:法尔斯
}
:
{
value :无定义的,
done :真
(;
}
(;
}
constit=makeiterator(['a ',' b ' );
it.next ()。
//{值: ' a ',完成:假}
it.next ()。
//{值: ' b ',完成:假}
it.next ()。
//{ value: undefined,done: true }上的阵列的含义简单说明如下:
如果nextIndex现在为0,则nextIndex的意思返回1.0.值增加(nextIndex现在为1 )。 以前遇到的问题是考察I和I
let编号=0
控制台日志(编号) )。
控制台日志(编号) )。
console.log(number )输出什么?
答案是0、2、2;
一元后自增运算符:
返回值(返回0 )的自增加) number现在是1 )一元前自增加运算符。
自我增加(number现在为2 )返回值)2)结果为0 )2)。
现在,让我们来看一下迭代器的整个遍历过程:
可以创建指向当前数据开头并首次调用指针对象的next方法的指针对象(上面代码的it ),然后将指针指向数据结构的第一个成员(上面代码的a )。 第二次调用指针对象的next方法可以将指针指向数据结构的第二个成员(上面代码中的b )。 继续调用指针对象的next方法,并在每次调用next方法直到指向数据结构的结束位置时返回数据结构的当前成员的信息。 具体来说,它返回包含两个属性: value和done的对象。 其中,value属性是当前成员的值,done属性是布尔值,指示遍历是否结束,即是否需要再次调用。
迭代器的特征
通过提供各种数据结构、统一的简单访问接口,数据结构的成员可以按某个顺序排列ES6,从而创建新的扫描命令for.of循环、Iterator连接
口主要供 for...of 消费默认 Iterator 接口
部署在 Symbol.iterator 属性,或者说,一个数据结构只要具有 Symbol.iterator 属性,就认为是"可遍历的"。
原生具备 Iterator 接口的数据结构如下。
ArrayMapSetString :字符串是一个类似数组的对象,也原生具有 Iterator 接口。TypedArray :通俗理解:ArrayBuffer是一片内存空间,不能直接引用里面的数据,可以通过TypedArray类型引用,用户只能通过TypedArray使用这片内存,不能直接通过ArrayBuffer使用这片内存函数的 arguments 对象NodeList 对象除了原生具备 Iterator 接口的数据之外,其他数据结构(主要是对象)的 Iterator 接口,都需要自己在 Symbol.iterator 属性上面部署,这样才会被 for...of 循环遍历。
对象( Object )之所以没有默认部署 Iterator 接口,是因为对象的哪个属性先遍历,哪个属性后遍历是不确定的,需要开发者手动指定。本质上,遍历器是一种线性处理,对于任何非线性的数据结构,部署遍历器接口,就等于部署一种线性转换。不过,严格地说,对象部署遍历器接口并不是很必要,因为这时对象实际上被当作 Map 结构使用, ES5 没有 Map 结构,而 ES6 原生提供了。
一个对象如果要具备可被 for...of 循环调用的 Iterator 接口,就必须在 Symbol.iterator 的属性上部署遍历器生成方法(原型链上的对象具有该方法也可)。
class RangeIterator { constructor(start, stop) { this.value = start; this.stop = stop; } [Symbol.iterator]() { return this; } next() { let value = this.value; if (value < this.stop) { this.value++; return { done: false, value: value }; } return { done: true, value: undefined }; } } function range(start, stop) { return new RangeIterator(start, stop); } for (let value of range(0, 3)) { console.log(value); // 0, 1, 2 }如果 Symbol.iterator 方法对应的不是遍历器生成函数(即会返回一个遍历器对象),解释引擎将会报错。
const obj = {}; obj[Symbol.iterator] = () => 1; // TypeError: Result of the Symbol.iterator method is not an object console.log([...obj] )字符串是一个类似数组的对象,也原生具有 Iterator 接口。
const someString = "hi"; typeof someString[Symbol.iterator] // "function"调用Iterator的场景
除了 for...of ,还有下面几个场景
解构赋值:对数组和 Set 结构进行解构赋值时,会默认调用Symbol.iterator方法。扩展运算符:扩展运算符内部就调用 Iterator 接口。yield* : yield* 后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口。接受数组作为参数的场合Array.from() Map(), Set(), WeakMap(), WeakSet()(比如new Map([['a',1],['b',2]])) Promise.all() Promise.race()Iterator的实现思想
看到 next 这个你有没有感到很熟悉,链表中 每个元素由一个存储元素本身的节点和一个指向下一个元素的引用(即next属性)组成。是不是很类似,不错, Iterator 的实现思想就是来源于单向链表。
下面来简单介绍一下单向链表。
单向链表
链表存储有序的元素集合,但不同于数组,链表中每个元素在内存中并不是连续放置的。每个元素由一个存储元素本身的节点和一个指向下一个元素的节点(也称为指针或链接)组成,下图展示了一个链表的结构。
和数组相比较,链表的一个好处已在于,添加或移除元素的时候不需要移动其他元素。然而,链表需要指针,因此实现链表时需要额外注意。数组的另一个细节是可以直接访问任何位置的任何元素,而 想要访问链表中间的一个元素,需要从起点(表头)开始迭代列表知道找到所有元素 。
现实生活中也有一些链表的例子,比如说寻宝游戏。你有一条线索,这条线索是指向寻找下一条线索的地点的指针。你顺着这条链接去到下一个地点,得到另一条指向再下一处的线索,得到列表中间的线索的唯一办法,就是从起点(第一条线索)顺着列表寻找。
具体怎么实现一个单向链表,这里就不展开讲了,推荐看 《学习JavaScript数据结构与算法》(第二版) 。
for...of 循环
关于 for...of 的原理,相信你看完上面的内容已经掌握的差不多了,现在我们以数组为例,说一下, for...of 和之前我们经常使用的其他循环方式有什么不同。
最原始的写法就是 for 循环。
for (let i = 0; i < myArray.length; index++) { console.log(myArray[i]); }这种写法比较麻烦,因此数组提供内置的 forEach 方法。
myArray.forEach((value) => { console.log(value); });这种写法的问题在于,无法中途跳出 forEach 循环, break 命令或 return 命令都不能奏效。
for...in 循环可以遍历数组的键名。
const arr = ['red', 'green', 'blue']; for(let v in arr) { console.log(v); // '0', '1', '2 }for...in 循环有几个缺点:
for...in for...in for...infor...in 循环主要是为遍历对象而设计的,不适用于遍历数组。
for...of 和上面几种做法( for 循环, forEach , for...in )相比,有一些显著的优点
有着同 for...in 一样的简洁语法,但是没有 for...in 那些缺点。不同于 forEach 方法,它可以与 break 、 continue 和 return 配合使用。提供了遍历所有数据结构的统一操作接口。