一、什么是迭代器
说起迭代,小伙伴们肯定很熟悉!!我们可能经常听到这样一句话“xxx是可迭代的”,在疑惑的同时,我们心里肯定也在想,逼格这么高的词汇,我要是可以用到,那岂不是佬中佬?emmm…其实也没有那么高逼格,让我们一步步揭开它的面纱~
要知道,你好像在学一个很新的东西,所以看官方文档肯定是最靠谱不过的了。对于迭代器,mdn上是这样解释的:
“在 JavaScript 中,迭代器是一个对象,它定义一个序列,并在终止时可能返回一个返回值。更具体地说,迭代器是通过使用 next() 方法实现 Iterator protocol 的任何一个对象,该方法返回具有两个属性的对象: value,这是序列中的 next 值;和 done ,如果已经迭代到序列中的最后一个值,则它为 true 。如果 value 和 done 一起存在,则它是迭代器的返回值。”
我们可以看到:
1.迭代器是一个对象
2.迭代器里面必须要有一个next方法,通过next方法实现迭代
3.next方法必须返回一个对象
即迭代器是一个对象,这个对象一定要有next方法去实现迭代(要符合Iterator协议,不然不能够被叫做迭代器),同时next也要返回一个对象(要符合Iterator协议),这样是不是更加清楚一点了?还有更清楚的,就是我个人理解(TAT)
迭代器是一个对象(其实就是对象。。。。逼格一下子就下去了),使用户在容器(数组,链表等)对象上遍历的对象,迭代的时候不需要关心容器对象的内部实现细节(管你内部的结构是咋样,该迭还得迭)
即:
帮我们对某个数据结构进行遍历的对象
在js里面迭代器必须符合迭代器协议
next也有自己的要求:
有一个无参数或者一个参数的函数,返回一个对象:
{
done:布尔值,迭代对象是否迭代完,迭代之后为true
value:具体值(要有值~)/undefined
}
实践是检验真理的唯一标准,所以,手写一个迭代器,如何?
const nums = [1, 2, 3, 4, 5] //实现针对数组的自定义迭代器,虽然数组自带迭代器(一个对象而已)
function createIterator(arr) { //返回一个迭代器,要按自定义的规则迭代,不管你是什么数组,
let i = 0;
return {
// 因为还在使用i值,所以这个函数不会被回收
next: function () { //适合数组的迭代器
if (i < arr.length) {
return { //next要返回一个对象
done: false,
value: arr[i++]
}
} else {
return {
done: true,
}
}
}
}
}
const iterator = createIterator(nums) //返回一个迭代器(就是一个对象啦)
console.log(iterator) //真的是一个对象,官方诚不欺我
console.log(iterator.next())//调用迭代的next方法对其进行迭代
/* {
返回值是这个,一直调用的话,会一直遍历,直到遍历完之后,返回{done:true}
"done": false,
"value": 1
}*/
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
//这样就实现了一个针对数组的自定义迭代器,其实就是:
//迭代器(一个实现了符合Iterator协议的next方法的对象)
大家可能会疑惑,为什么明明调用完成了,i还可以继续用,因为next方法始终要用到createIterator的i值,所以这个函数的空间不会被释放,即i值一直存在
二、什么是可迭代对象
了解了什么是迭代器(一个实现了符合Iterator协议的next方法的对象)之后,我们又回到了上面的那个问题,“xxx是不可迭代的”,那,到底啥才是迭代对象呢,啥又是不可迭代的对象呢?
官方解释是:“要成为可迭代对象,该对象必须实现 @@iterator 方法,这意味着对象(或者它原型链上的某个对象)必须有一个键为 @@iterator 的属性”
我们又可以看到:对象里面或者它的原型上必须要有 @@iterator 方法
啊这这这,@@iterator方法好抽象啊,到底是啥啊
别急,官方还有解释:
“可通过常量 Symbol.iterator 访问该属性”,访问?那Symbol.iterator生成一个变量咯,那方法名就是介个样子:[Symbol.iterator](都说可以访问了,那么使用这个语法访问该属性)
那知道了,实现了@@iterator方法的对象(或者在原型上有)就可以被叫作可迭代对象,那原生里面到底有哪些是可迭代对象呢?我们平时经常用到的大概有这些:Array,Map,Set,String,TypedArray,arguments 等。是的,没有对象,那可迭代对象上面真的实现了@@iterator方法吗?那数组和对象来举例,打印出来对比一下~
const arr = []
console.log(arr)
数组的原型链上有使用Symbol.iterator来访问@@iterator方法,根据mdn说法,这是一个可迭代对象!
再打印对象出来看看!
const obj = {}
console.log(obj,obj[Symbol.iteraor])
啊,对象或者对象的原型上没有实现@@iterator方法,根据标准对象是不可迭代的,那我们可能会想,每一次都要打印一次,那不是麻烦死了,其实我们可以使用for…of…来检测:
先检测对象
const obj = {
name:"aaa",
age:"bbb"
}
for(let item of obj){
console.log(item)
}
报错:obj不是可迭代的
再看看数组:
const name =["aaa","bbb"]
for(let item of name){
console.log(item)
}
好神奇!
其实用for of 来检测一个对象是不是可迭代的并不是,空穴来风,mdn上也有提到:
“for…of 语句遍历可迭代对象定义要迭代的数据。”
所以以后如果相检测一个东西是不是可迭代的,最简单的方法使用for of
三、让一个对象成为可迭代的
上面已经提到,对象是不可迭代的,但是我们有办法让它变成迭代的,怎么来呢~肯定是符合官方的定义:在对象或者其原型上有一个[Symbol.iterator]方法!仅仅有这一个方法就够了嘛?肯定不是:
“如果一个可迭代对象的 @@iterator 方法不能返回迭代器对象,那么可以认为它是一个不符合标准的(Non-well-formed)可迭代对象。”
-----mdn官方文档
所以这个方法必须返回一个迭代器对象,让我们回想一下迭代器对象~~
迭代器对象:一个实现了符合Iterator协议的next方法的对象
哦,现在我懂了,开始写代码叭
class Person {
// 这个类的实例都可以变成可迭代对象
constructor(name, age) {
this.name = name
this.age = age
}
[Symbol.iterator]() {
// 将这个放在Person的显示原型上,这样所有的类实例都可以访问
let index = 0
const keys = Object.keys(this) //实例方法调用
const Iterator = {
next: function () {
if (index < keys.length) {
return {
value: keys[index++],
done: false
}
} else {
return {
done: true
}
}
},
return: () => {
return {
done: true //当迭代器中端的时候会调用这个方法进行处理或者提示
}
}
}
return Iterator
}
}
const p1 = new Person("wx", 21)
for (let item of p1) {
console.log(item)
}
打印出来看看~
可以看到不报错了~因为上面是对对象的key进行迭代,如果需要对其value进行迭代的话,可以改
四、总结
经过上述,可能大家对于迭代器已经有一个初步的了解了:
1.其实迭代器就是一个普普通通的对象,但是它符合了迭代协议,所以才被叫做迭代器,进一步说,一个普普通通的对象都可以成为迭代器,只要它符合迭代协议。
2.并不是所有的对象都是可迭代的,只有实现了[Symbpl.iterator]方法,并且这个方法也要返回一个迭代器,才可以进行迭代的
3.可以使用for of 来检测一个对象是不是可迭代对象
4.本文还有很多没有说到的地方,比如迭代器里面除了必须的next方法有详细说到以外,其他的return方法,throw方法等都没有说到,感兴趣的小伙伴们可以去mdn上官网查阅~
5.迭代器也有异步的,本文只说明了同步的
6.只有学好了迭代器才可以明白生成器的作用和实际内容,所以下一节内容是:生成器!