我报名参与金石方案1期挑战——瓜分10万奖池,这是我的第2篇文章,点击检查活动详情

愉快的周末,我在试验室里清点礼品,当数到小黄鸭的时分,学长走了过来

学长:你这是不是一种迭代鸭子的行为。

我:emmm或许算是吧。

学长:那你给我讲一讲在JS中的迭代吧

这次我没有直接逃走,而是走到了学姐死后,哭诉学长对自己的镇压(苦楚流涕),于是乎学长掏出了红宝书阴沉沉地笑道:你学不学!

话不多说,本文将由浅入深地从根底与原理再到实战与手撕展开,探求JS迭代的原理,解析官方文档的案例

迭代的根底

举一个经典且刻在印象中的迭代比方:对数组的元素按序拜访,当遍历次数到达数组长度时中止迭代

let arr = [1,2,3]
for(let i = 0; i < arr.length; i++) {
  console.log(arr[i])
}

上面我标注了按序拜访中止迭代,思考一下,得出迭代必须满意如下两点

  1. 有某种固有的次序,能够按某种次序开始或许中止迭代。例如:在数组中次序对应着下标,遍历次数到达数组长度则中止
  2. 了解怎么拜访迭代方针的元素。例如:在数组中能够经过下标拜访对应的元素

迭代的原理

好,上面咱们现已掌握迭代的条件,那么将其带入Javascript中,剖析一下其他可迭代的数据结构的迭代内部原理是怎么样的。

可迭代的数据结构

咱们运用for of来对JS中常见的数据结构进行迭代,成果如下

let str = "猪痞恶霸" // 猪 痞 恶 霸
let arr = [1] // 1
let obj = {
  name:"猪痞恶霸" 
} // obj is not iterable
let m = new Map() // ['猪痞恶霸', 1]
m.set(str,1)
let s = new Set([1]) // 1
for (item of str) {
  console.log(item)
}

这儿看到只有obj抛出了错误:obj is not iterable,翻译为obj不是可迭代目标,那么便是能够理解为,mapsetarr等都是可迭代目标,这儿或许有人想到那obj目标类型能够运用for in进行迭代,为什么不能够叫做可迭代目标呢?其是因为内部的完成机制与其他数据成果的迭代不同,这点咱们放在实战部分聊。

上面这段话我标注了可迭代目标这一概念,而可迭代目标是什么呢?指的是mapsetstr这些数据成果吗?其实不然,官方文档针对可迭代目标给出了明确的要求:

  1. 完成正式的interable接口
  2. 能够经过interator迭代器消费

这是啥啊,看不明白,那么咱们下面来深化解析这两点要求。

interable接口

interable接口又称可迭代协议是一个比较笼统的东西,我将它理解为数据结构迭代操作的进口,不同的数据结构经过该接口能够完成相同的操作,而在javascript中正是利用Symbol.iterator来作为这个接口,假如不了解Symbol的掘友能够看一下这篇文章:学长突然问我用过Symbol吗,我呜咽住了(准备挨骂)比较详尽地阐明了Symbol的一些运用。

假如你了解Symbol那么肯定知道内置Symbol是什么,Symbol.iterator正是一种内置Symbol,它对应的外部操作即是for of,当外部运用for of办法时,内部就会启用以Symbol.iterator为键的办法,下面咱们经过代码来剖析内部的构造

let arr = [1,2,3]
console.log(arr[Symbol.iterator]) //  values() { [native code] }

values() { [native code] }正是Symbol.iterator对应的函数,而只有相关类型内置了Symbol.iterator才会打印出来

let obj = {}
console.log(obj[Symbol.iterator]) // undefined

如上比方obj[Symbol.iterator]undefined,这也是为什么说obj无法进行for of进行迭代,因为其没有内置Symbol.iteratorinterable接口

承继interable接口

interable接口是能够被承继的,也便是说当父类完成了interable接口,那么子类也会完成

class Son extends String {}
let son = new Son()
console.log(son[Symbol.iterator]) // [Symbol.iterator]() { [native code] }

咱们经过上面的代码能够看到Son承继String类,所以内部也完成了interable接口

非常好,咱们经过上面的两点现已了解了可迭代目标的第一个要求:完成正式的interable接口,那么interator迭代器又是什么呢?

interator迭代器

interator迭代器本质上是目标,由interable接口对应的函数履行生成

let arr = [1,2,3]
console.log(arr[Symbol.iterator]()) // Array Iterator{}      

上面调用了arr[Symbol.iterator]()办法获得了一个Array Iterator {}目标,也便是迭代器,其实这种办法是手动获取的,真实迭代时的内部操作。

interator迭代器是怎么做到迭代数据结构的呢?他的核心便是运用next(),下面咱们手动迭代一个字符

let str = "猪痞恶霸"
let strInter = str[Symbol.iterator]()
strInter.next() // {value: '猪', done: false}
strInter.next() // {value: '痞', done: false}

手动迭代的过程是这样的

  1. 经过str[Symbol.iterator]()回来拿到迭代器
  2. 调用迭代器目标的next()办法
  3. 回来一个IteratorResult 目标比方{value: '猪', done: false}
  4. 该目标内包括了两种特点对应着不同的作用
  5. value代表当时迭代的内容,done符号是否能够进行下一次next()
  6. donetrue则不会进行下一次迭代,阐明迭代中止

迭代内部的机制咱们现已学习结束,下面开始操练时刻,来看看常用的迭代的实战场景

迭代的实战

咱们知道很多迭代的操作,比方最经典的for,还有forEachfor offor in等等,那么同样是迭代,咱们该怎么选择正确的迭代方式呢?下面来逐个剖析

forEach场景

forEach办法常见的能够遍历目标有SetMaparrayNodeList,它会迭代每个数据结构知道中止,因为无法在内部运用return或许是break所以无法中止迭代,所以说假如咱们的迭代无需中止,就能够运用forEach

let arr = [
   {name:"猪痞恶霸",age:20},
   {name:"Ned",age:21}
]
arr.forEach((item) => {
  item.age+=1
})

比方运用forEach来对数组元素进行批量处理

for of场景

上面提到了forEach的迭代是无法中止的,所以当咱们想中止迭代,那么咱们就能够运用for of,比方在当元素满意某个条件的时分中止迭代

let arr = [1,3,10,7,10]
for(item of arr) {
  console.log(item)
  if(item%2 === 0) {
    break
   }
} 
// 1,3,10

如上,当元素是偶数的时分那么就中止迭代,相对于forEach来看for of有个缺点便是无法获取当时元素对应的索引,而forEach能够,所以咱们需求选择不同的迭代操作来适应当时的需求。

for in场景

因为Object没有内置迭代器,所以for of无法对其进行迭代,咱们能够运用for in办法来迭代目标,其回来的是特点的键名

let obj = {
  name:"ned",
  like:"man"
}
for(item in obj) {
  console.log(item)
} // name like

当然假如你想要针对目标进行一些特性地迭代,比方迭代Symbol特点,那么能够参阅一下这篇文章:JS遍历目标的七种办法

Very Good!!!经过上面的三种迭代场景,咱们学到了能够依据不同类型的迭代目标,参数需求,以及功能相关来判别运用哪种迭代操作,那么下面我给咱们带来点花的:手撕迭代

手撕迭代

来历:JS高程4,下面我会依据这个手撕来给咱们剖析,协助咱们进一步透彻迭代

class Counter {
 constructor(limit) {
  this.limit = limit;
  }
  [Symbol.iterator]() {
  let count = 1,
   limit = this.limit;
  return {
   next() {
    if (count <= limit) {
     return { done: false, value: count++ };
     } else {
     return { done: true, value: undefined };
     }
    },
   };
  }
}
let counter = new Counter(3);
for (let i of counter) { console.log(i); } 

这是一个完成迭代的类Counter,内部有构造函数与迭代器接口Symbol.iterator

  1. 创建实例的时分传入limit给予实例长度,其实这儿能够理解为咱们在初始化数组的时分给与数组长度

  2. 进行for of操作的时分对应内部Symbol.iterator办法并调用,回来一个迭代器目标,该目标包括一个next办法

    // 内部机制
    counter[Symbol.iterator]()
    // {
    //  {next: }
    // }
    
  3. 调用next办法,回来{ done: false, value: count++ }格局数据,每次调用为countlimit做判别,假如超过范围,那么将回来的目标的done特点符号为false阐明迭代结束

最终

经过学长的鞭笞与学姐的鼓励,我又一次地领会了迭代器地微妙,收拾并输出了该篇文章,假如对你有协助就点小爱心吧!