for...of
是ES6引入用来遍历全部数据结构的统一方法。前端
这里的全部数据结构只指具备iterator接口
的数据。一个数据只要部署了 Symbol.iterator
,就具备了 iterator
接口,就可使用 for...of
循环遍历它的成员。也就是说,for...of循环内部调用的数据结构为Symbol.iterator
方法。面试
for...of
循环可使用的范围包括数组、Set 和 Map 结构、某些相似数组的对象(好比arguments
对象、DOM NodeList
对象)、 Generator
对象,以及字符串。也就是说上面提到的这些数据类型原生就具有了 iterator
接口。算法
因此千万不要错误地认为
for...of
只是用来遍历数组的。数组
为何会有 会引入 Iterator
呢,是由于 ES6添加了 Map
, Set
,再加上原有的数组,对象,一共就是4种表示 “集合”的数据结构。没有 Map
和 Set
以前,咱们都知道 for...in
通常是经常使用来遍历对象,for
循环 经常使用来遍历数据,如今引入的 Map
, Set
,难道还要单独为他们引入适合用来遍历各自的方法么。聪明的你确定能想到,咱们能不能提供一个方法来遍历全部的数据结构呢,这个方法能遍历全部的数据结构,必定是这些数据结构要有一些通用的一些特征,而后这个公共的方法会根据这些通用的特征去进行遍历。微信
Iterator
就能够理解为是上面咱们所说的通用的特征。markdown
咱们来看看官方对 Iterator
是怎么解释的:遍历器(Iterator
)就是这样一种机制。它是一种接口,为各类不一样的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator
接口,就能够完成遍历操做(即依次处理该数据结构的全部成员)。通俗点理解就是为了解决不一样数据结构遍历的问题,引入了Iterator
.数据结构
咱们来模拟实现如下:函数
function makeIterator(array) { var nextIndex = 0; return { next: function() { return nextIndex < array.length ? { value: array[nextIndex++], done: false } : { value: undefined, done: true }; } }; } const it = makeIterator(['a', 'b']); it.next() // { value: "a", done: false } it.next() // { value: "b", done: false } it.next() // { value: undefined, done: true } 复制代码
简单解释一下上面
array[nextIndex++]
是什么意思, 假如nextIndex
当前为0,则nextIndex++
的意思为1.返回0 2. 值自增(nextIndex
如今为1)。以前遇到一道面试题就是考察i++
和++i
oop
let number = 0 console.log(number++) console.log(++number) console.log(number) 复制代码
输出什么?学习
答案是: 0, 2, 2;
一元后自增运算符 ++:
一元前自增运算符 ++:
结果是 0 2 2.
好了,接着来看 Iterator
的整个的遍历过程:
it
),指向当前数据的起始位置next
方法,能够将指针指向数据结构的第一个成员(上面代码中的a
)。next
方法,能够将指针指向数据结构的第二个成员(上面代码中的b
)。next
方法,直到它指向数据结构的结束位置每一次调用next方法,都会返回数据结构的当前成员的信息。具体来讲,就是返回一个包含value
和done
两个属性的对象。其中,value
属性是当前成员的值,done
属性是一个布尔值,表示遍历是否结束,便是否要有必要再一次调用。
Iterator
的特色
for...of
循环,Iterator
接口主要供for...of
消费部署在 Symbol.iterator
属性,或者说,一个数据结构只要具备 Symbol.iterator
属性,就认为是"可遍历的"。
原生具有 Iterator 接口的数据结构以下。
Array
Map
Set
String
:字符串是一个相似数组的对象,也原生具备 Iterator 接口。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" 复制代码
除了 for...of
,还有下面几个场景
yield*
:yield*
后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口。Array.from()
Map(), Set(), WeakMap(), WeakSet()(好比new Map([['a',1],['b',2]]))
Promise.all()
Promise.race()
看到next
这个你有没有感到很熟悉,链表中 每一个元素由一个存储元素自己的节点和一个指向下一个元素的引用(即next属性)组成。是否是很相似,不错,Iterator
的实现思想就是来源于单向链表。
下面来简单介绍一下单向链表。
链表存储有序的元素集合,但不一样于数组,链表中每一个元素在内存中并非连续放置的。每一个元素由一个存储元素自己的节点和一个指向下一个元素的节点(也称为指针或连接)组成,下图展现了一个链表的结构。
和数组相比较,链表的一个好处已在于,添加或移除元素的时候不须要移动其余元素。然而,链表须要指针,所以实现链表时须要额外注意。数组的另外一个细节是能够直接访问任何位置的任何元素,而想要访问链表中间的一个元素,须要从起点(表头)开始迭代列表知道找到全部元素。
现实生活中也有一些链表的例子,好比说寻宝游戏。你有一条线索,这条线索是指向寻找下一条线索的地点的指针。你顺着这条连接去到下一个地点,获得另外一条指向再下一处的线索,获得列表中间的线索的惟一办法,就是从起点(第一条线索)顺着列表寻找。
具体怎么实现一个单向链表,这里就不展开讲了,推荐看
《学习JavaScript数据结构与算法》(第二版)
。
关于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
循环是以字符串做为键名“0”、“1”、“2”等等。for...in
循环不只遍历数字键名,还会遍历手动添加的其余键,甚至包括原型链上的键for...in
循环会以任意顺序遍历键名。for...in
循环主要是为遍历对象而设计的,不适用于遍历数组。
for...of
和上面几种作法(for
循环,forEach
, for...in
)相比,有一些显著的优势
for...in
同样的简洁语法,可是没有for...in
那些缺点。forEach
方法,它能够与break
、continue
和return
配合使用。for...of
能够用来遍历全部具备iterator
接口的数据结构。(一个数据结构只要部署了Symbol.iterator
属性,就被视为具备 iterator
接口)。也就是说 for...of
循环内部调用是数据结构的 Symbol.iterator
iterator
的实现思想来源于 单向链表
forEach
循环中没法用break
命令或return
命令终止。而for...of
能够。for...in
遍历数组遍历的是键名,全部适合遍历对象,for...of
遍历数组遍历的是键值。最近发起了一个100天前端进阶计划,主要是深挖每一个知识点背后的原理,欢迎关注 微信公众号「牧码的星星」,咱们一块儿学习,打卡100天。