步遥——Iterator对象和for...of循环

1:Iterator(遍历器)概念
集合数据结构:Array,Object,Map,Set
用户可以组合使用,定义自己的数据结构。比如:数组中有Map,Map中有对象。这样就需要一种统一的接口机制。遍历器就是这样一种机制,来处理所有不同的数据结构。

遍历器是一种接口。
遍历器为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署Iterator接口,就可以完成遍历操作: for ...of循环

作用:
1:为各种数据结构,提供统一的,简便的访问接口
2:使得数据结构的成员能够按某种次序排列
3:ES6创造了一种新的遍历命令for...of循环,Iterator接口主要提供for...of消费

遍历过程:
1:创建一个指针对象,指向当前数据结构的起始位置。遍历器对象本质上是一个指针对象。{next:function(){} }
2:第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。
3:第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。
4:不断调用指针对象的next方法,直到它指向数据结构的结束位置。

每一次调用next方法,都会返回数据结构的当前成员的信息。具体来说,就是返回一个包含value和done两个属性的对象。其中value是当前成员的值,done是一个布尔值,表示遍历是否结束。

{value:'someValue',done:false}

指针对象的next方法,用来移动指针。开始时指针指向数组的开始位置,然后每次调用next方法,指针就会指向数组的下一个成员:

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} : //i++来移动指针
        {value: undefined, done: true}; //最后以为done为true
    }
  };}

Iterator只是把接口规格加到数据结构之上,所以,遍历器与它所遍历的那个数据结构,实际上是分开的,完全可以写出没有对应数据结构的遍历器对象,或者说用遍历器对象模拟出数据结构。

for...of循环会自动寻找Iterator接口

一种数据结构只要部署了 Iterator 接口,我们就称这种数据结构是“可遍历的”(iterable)。
ES6 规定,默认的 Iterator 接口部署在数据结构的Symbol.iterator属性,或者说,一个数据结构只要具有Symbol.iterator属性,就可以认为是“可遍历的”(iterable)

interface Iterable {
  [Symbol.iterator]() : Iterator, // 部署了iterator接口,Iterator是一个函数,就是当前数据结构默认的遍历器生成函数。
}

遍历器接口其实就是一个方法,方法返回一个指针对象,指针对像中有next方法,next方法返回一个含有value,done属性的对象

const obj = {
[Symbol.iterator]:function(){
    return { //指针对象
        next:function(){
            return {
            value:i++,
            done:false
            }
        }
    }
}
}

凡是部署了Symbol.iterator属性的数据结构,就称为部署了遍历器接口。调用这个接口,就会返回一个遍历器对象。

原生具有Iterator接口的数据结构如下:Array,Map,Set,String,TypedArray,arguments对象,NodeList对象

对于原生部署 Iterator 接口的数据结构,不用自己写遍历器生成函数,for...of循环会自动遍历它们。除此之外,其他数据结构(主要是对象)的 Iterator 接口,都需要自己在Symbol.iterator属性上面部署,这样才会被for...of循环遍历。

一个对象如果要具备可被for...of循环调用的 Iterator 接口,就必须在Symbol.iterator的属性上部署遍历器生成方法(原型链上的对象具有该方法也可)。

如果Symbol.iterator方法对应的不是遍历器生成函数(即会返回一个遍历器对象),解释引擎将会报错。

2:调用Iterator接口的场合:
2.1:解构赋值
对数组和Set结构进行解构赋值时,会默认调用Symbol.iterator方法
2.2:扩展运算符
只要某个数据结构部署了 Iterator 接口,就可以对它使用扩展运算符,将其转为数组。
2.3:yield*
yield*后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口。
2.4:其他
由于数组的遍历会调用遍历器接口,所以任何接受数组作为参数的场合,其实都调用了遍历器接口。
* for...of
* Array.from()
* Map(), Set(), WeakMap(), WeakSet()(比如new Map([['a',1],['b',2]]))
* Promise.all()
* Promise.race()

3:字符串也有Iterator接口
可以覆盖原生的Symbol.iterator方法,达到修改遍历器行为的目的。(可以偷偷改掉遍历输出的内容)
对于字符串来说,for...of循环还有一个特点,就是会正确识别 32 位 UTF-16 字符。

4:Iterator接口和Generator函数
Symbol.iterator()方法几乎不用部署任何代码,只要用 yield 命令给出每一步的返回值即可

5:遍历器对象的return,throw方法
遍历器对象除了具有next方法,还可以具有return和throw方法。 如果你自己写遍历器对象生成函数,那么next()方法是必须部署的,return()方法和throw()方法是否部署是可选的。

return()方法的使用场合是:
4.1:如果for...of循环提前退出(通常是因为出错,或者有break语句),就会调用return()方法。
4.2:如果一个对象在完成遍历前,需要清理或释放资源,就可以部署return()方法。

return()方法必须返回一个对象,这是 Generator 语法决定的。

throw()方法的使用场合是:
主要是配合 Generator 函数使用,一般的遍历器对象用不到这个方法。请参阅《Generator 函数》一章

6:for...of循环
引入了for...of循环,作为遍历所有数据结构的统一的方法。

let arr = new Array(5).fill({a:1})
for(i of arr){console.log(i)} // 获得键值
for(i in arr){console.log(i)}  //获取键名

for...in循环读取键名,for...of循环读取键值。如果要通过for...of循环,获取数组的索引,可以借助数组实例的entries方法和keys方法(参见《数组的扩展》一章)。

7: Set 和 Map 结构也原生具有 Iterator 接口,可以直接使用for...of循环。
值得注意的地方有两个,首先,遍历的顺序是按照各个成员被添加进数据结构的顺序。其次,Set 结构遍历时,返回的是一个值,而 Map 结构遍历时,返回的是一个数组,该数组的两个成员分别为当前 Map 成员的键名和键值。

并不是所有类似数组的对象都具有 Iterator 接口,一个简便的解决方法,就是使用Array.from方法将其转为数组。

8:对象:
对于普通的对象,for...of结构不能直接使用,会报错,必须部署了 Iterator 接口后才能使用。但是,这样情况下,for...in循环依然可以用来遍历键名。
一种解决方法是,使用Object.keys方法将对象的键名生成一个数组,然后遍历这个数组。
另一个方法是使用 Generator 函数将对象重新包装一下。

9:与其他遍历语法的比较
for : 比较麻烦
forEach: 无法中途跳出forEach循环,break命令或return命令都不能奏效
for...in 循环可以遍历数组的键名 缺点:
* 数组的键名是数字,但是for...in循环是以字符串作为键名“0”、“1”、“2”等等。
* for...in循环不仅遍历数字键名,还会遍历手动添加的其他键,甚至包括原型链上的键。
* 某些情况下,for...in循环会以任意顺序遍历键名。

总之: for...in循环主要是为遍历对象而设计的,不适用于遍历数组。
for...of:优点:
* 有着同for...in一样的简洁语法,但是没有for...in那些缺点。
* 不同于forEach方法,它可以与break、continue和return配合使用。
* 提供了遍历所有数据结构的统一操作接口。

for (var n of fibonacci) {
  if (n > 1000)
    break;
  console.log(n);
}

你可能感兴趣的:(步遥——Iterator对象和for...of循环)