18888628835/Blog

Iterator(迭代器)

18888628835 opened this issue · 0 comments

Iterator(迭代器)

什么是Iterator

可迭代(Iterable) 对象是数组的泛化。这个概念是说任何对象都可以被定制为可在 for..of 循环中使用的对象。

数组是可迭代的。但不仅仅是数组。很多其他内建对象也都是可迭代的。例如字符串也是可迭代的。

Iterator 就是针对那么多可迭代的数据结构中是一种统一的接口机制,只要部署了这个接口,就可以被依次遍历处理。

Iterator 的遍历过程是这样的:

  • 首先会在内部创建一个指针,指向指针开始位置
  • 当循环开始时,调用其内部的 next 方法
  • 返回当前成员信息=>{value:any,done:boolean}
  • 第二步-第三步循环执行,直到指针指到最后位置
  • 结束

手写 Iterator

下面我们通过一个例子快速了解核心概念。

需要知道的是,对象默认是没有 Iterator 接口的,所以它不能被 for..of 循环。这里我们手动给它设置 Iterator 接口。

let range={
  from:0,
  to:5
}

如果能让上面的range对象通过for..of循环打印出0、1、2、3、4、5。这就代表 iterator 接口部署成功了

1.首先我们要给它设置一个属性,这个属性只能通过Symbol.iterator访问,这个属性保存一个函数。

range[Symbol.iterator]=function(){}

这个 Symbol.iterator是部署的开始,当 for..of 开始循环时,会调用Symbol.iterator函数
2.Symbol.iterator函数会返回一个对象,内部有一个 next 方法,并返回{value:xxx,done:boolean}这样的对象。

// 由for...of 调用range[Symbol.iterator]()并返回 Iterator 对象
range[Symbol.iterator]=function(){
  // 返回一个iterator 对象
  return {
    first:0,
    last:5,
    next(){
    //这里的 this 指向iterator 对象,由 for..of 每次循环时自动调用该对象的 next 方法
      if(this.first<=this.last){
        return {value:this.first++,done:false}
      }else{
        return {done:true}
      }
    }
  }
}

3.打印信息

for(let i of range){
  alert(i)
}

会依次打出0-5的数,Iterator接口部署成功了。

小结

如果需要部署 Iterator 接口,我们需要手动添加 Symbol.Iterator属性。

这个属性保存着一个函数,当 for..of 开始时,就会调用该函数,并返回Iterator 对象,此时指针初始化。

Iterator对象内部保存着一个 next 函数,for..of 的每次循环都会调用到Iterator对象中的next 函数,所以这时候的 this 指向 Iterator 对象。

next函数会返回{value:xx,done:boolean}的数据结构。 当 done 为 true 时,循环停止。

for..of循环每次都会取返回结果的value属性

内置 Iterator

数组跟字符串内置了 Iterator 接口,所以我们可以直接使用 for..of来循环,比如下面的字符串

for(let str of 'strings'){
  console.log(str)
}
// s t r i n g s

下面我们直接获取它的Symbol.iterator属性,来看看到底发生了什么。

我们已经知道这个属性保存着一个函数,这个函数会返回 Iterator 对象,所以我们直接调用拿到这个对象。

let strings='strings'
let Iterator=strings[Symbol.iterator]()

这个对象内部会执行 next 方法,返回{value:xx,done:boolean}对象

Iterator.next()
//{value: "s", done: false}

现在我们得出一个结论:可迭代对象就是内部部署了 Iterator接口的对象,这个部署 Iterator 接口的标志是内部有 [Symbol.iterator]属性。

我们可以通过浏览器打印出来看一下,比如例子中的strings 的原型上就有[Symbol.iterator]属性,它是一个函数。

image.png

我们直接调用这个方法,然后看看内部到底是个啥

image.png
可以看到返回的StringIterator 的原型上有个 next 方法。

伪数组问题

可迭代对象的概念就是实现[Symbol.iterator]方法的对象

伪数组和数组一样,本质上是个对象,但是数组有数组原型(Array.prototype),而伪数组没有数组原型。

通过上面的截图我们知道[Symbol.iterator]会部署在原型上,比如数组原型、字符串原型里就有部署这个属性。

对象以及其原型是没有部署 Iterator 接口的。伪数组既然没有数组的原型,那就不一定有 Iterator 接口。

比如下面是一个伪数组

let arrayLike = { // 有索引和 length 属性 => 类数组对象
  0: "Hello",
  1: "World",
  length: 2
};

它的原型直接连接到 Object.prototype 上,所以它没有数组的共用方法。比如 pushpop 等等。

如果希望实现,那可以采用Array.from

Array.from(arrayLike)

这样这个伪数组就具备 Iterator 接口了。

有没有一种伪数组虽然没有数组的原型,但是却默认部署 Iterator 接口的呢?

有的,任何可以被扩展运算符变成数组的伪数组都有Iterator接口。

比如arguments虽然没有数组的原型,但是却具有 [Symbol.iterator] 接口

image.png

扩展运算符把伪数组变成数组?

也许你会认为扩展运算符能把所有伪数组变成数组,不过这只对了一半。

扩展运算符只能把已经具备 Iterator 接口的数据变成数组。不信我们看

image.png

结论就是只要你部署了Iterator 接口,才能用扩展运算符转化为数组。可不要弄反顺序噢。

总结

Iterator 是一个接口,具备 Iterator 接口的特点是内部有[Symbol.iterator]属性。这个属性保存着一个会返回 Iterator 对象的函数。

Iterator对象内部具备 next 方法,会返回键名为 done 和 value 的对象。

for..of 遍历器为部署了 Iterator 接口的数据结构而生,当使用 for..of 遍历时,会首先调用[Symbol.iterator] 方法,生成Iterator对象,每次循环都会调用这个对象内部的 next 方法。

有一些伪数组没有部署Iterator 接口,所以不能被扩展运算符转化为数组。这时候需要采用 Array.from方法。

Symbol.iterator 方法会被 for..of 自动调用,但我们也可以直接调用它。

内置的可迭代对象例如字符串和数组,都实现了 Symbol.iterator。