我报名参与金石方案1期挑战——瓜分10万奖池,这是我的第2篇文章,点击检查活动详情
愉快的周末,我在试验室里清点礼品,当数到小黄鸭的时分,学长走了过来
学长:你这是不是一种迭代鸭子的行为。
我:emmm或许算是吧。
学长:那你给我讲一讲在JS中的迭代吧
这次我没有直接逃走,而是走到了学姐死后,哭诉学长对自己的镇压(苦楚流涕),于是乎学长掏出了红宝书阴沉沉地笑道:你学不学!
话不多说,本文将由浅入深地从根底与原理再到实战与手撕展开,探求JS迭代的原理,解析官方文档的案例
迭代的根底
举一个经典且刻在印象中的迭代比方:对数组的元素按序拜访,当遍历次数到达数组长度时中止迭代
let arr = [1,2,3]
for(let i = 0; i < arr.length; i++) {
console.log(arr[i])
}
上面我标注了按序拜访与中止迭代,思考一下,得出迭代必须满意如下两点
- 有某种固有的次序,能够按某种次序开始或许中止迭代。例如:在数组中次序对应着下标,遍历次数到达数组长度则中止
- 了解怎么拜访迭代方针的元素。例如:在数组中能够经过下标拜访对应的元素
迭代的原理
好,上面咱们现已掌握迭代的条件,那么将其带入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不是可迭代目标,那么便是能够理解为,map,set,arr等都是可迭代目标,这儿或许有人想到那obj目标类型能够运用for in进行迭代,为什么不能够叫做可迭代目标呢?其是因为内部的完成机制与其他数据成果的迭代不同,这点咱们放在实战部分聊。
上面这段话我标注了可迭代目标这一概念,而可迭代目标是什么呢?指的是map,set,str这些数据成果吗?其实不然,官方文档针对可迭代目标给出了明确的要求:
- 完成正式的
interable接口 - 能够经过
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.iterator即interable接口
承继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}
手动迭代的过程是这样的
- 经过
str[Symbol.iterator]()回来拿到迭代器 - 调用迭代器目标的
next()办法 - 回来一个IteratorResult 目标比方
{value: '猪', done: false} - 该目标内包括了两种特点对应着不同的作用
-
value代表当时迭代的内容,done符号是否能够进行下一次next() - 若
done为true则不会进行下一次迭代,阐明迭代中止
迭代内部的机制咱们现已学习结束,下面开始操练时刻,来看看常用的迭代的实战场景
迭代的实战
咱们知道很多迭代的操作,比方最经典的for,还有forEach,for of,for in等等,那么同样是迭代,咱们该怎么选择正确的迭代方式呢?下面来逐个剖析
forEach场景
forEach办法常见的能够遍历目标有Set,Map,array,NodeList,它会迭代每个数据结构知道中止,因为无法在内部运用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
-
创建实例的时分传入
limit给予实例长度,其实这儿能够理解为咱们在初始化数组的时分给与数组长度 -
进行
for of操作的时分对应内部Symbol.iterator办法并调用,回来一个迭代器目标,该目标包括一个next办法// 内部机制 counter[Symbol.iterator]() // { // {next: } // } -
调用
next办法,回来{ done: false, value: count++ }格局数据,每次调用为count和limit做判别,假如超过范围,那么将回来的目标的done特点符号为false阐明迭代结束
最终
经过学长的鞭笞与学姐的鼓励,我又一次地领会了迭代器地微妙,收拾并输出了该篇文章,假如对你有协助就点小爱心吧!
