本文将带大家从认识可迭代对象 与 类数组对象 ,并手搓一个IRange
类,他既是可迭代对象又是类数组对象😎;
IRange
类:该类需要传入两个参数:from
to
(from和to是整数),然后该类的实例对象即是可迭代对象又是类数组对象,迭代的每一项是[from,to]
区间的所有整数;
可迭代对象
可迭代对象 是遵从可迭代协议 的对象,并且JavaScript专门为可迭代对象提供了遍历的方法:for..of
(这也是验证一个对象是否是可迭代对象的依据--能否使用for..of
遍历);
可迭代协议
那什么是可迭代协议呢?
可迭代协议允许JavaScript对象定义或定制它们的迭代行为!
在JavaScript中,有以下类型的对象都实现了可迭代协议并有其迭代行为:
String
(也是类数组对象)Array
(也是类数组对象)TypedArray
(也是类数组对象)Map
Set
可迭代协议要求一个可迭代对象必须实现@@iterator
方法,也就是必须要有一个@@iterator
属性,而这个键可以通过Symbol.iterator
访问 --->也就是说以上的JavaScript内置的可迭代对象都拥有一个Symbol.iterator
属性;
[Symbol.iterator]
属性的值是一个无参函数,且返回一个符合迭代器协议的对象
现在我们知道了这一点后,已经可以实现支持可迭代的IRange
类了:
分析:
- 该类有两个属性:
from
to
; - 拥有一个
[Symbol.iterator]
属性,其功能是使得该类实例对象可以使用for..of
遍历[from,to]
内的所有整数;
虽然你现在还不知道迭代器协议 是什么,但是我们知道Array
的实例身上有属性[Symbol.iterator]
,且其返回值为一个符合迭代器协议的对象,那么我们可以利用他来实现:
js
function IRange(from, to) {
this.from = from;
this.to = to;
this[Symbol.iterator] = function () {
const array = new Array(to - from + 1).fill(0).map((_, index) => from + index);
//注意我们构造了一个from到to的Array,而他的[Symbol.iterator]属性的值是一个函数,该函数的返回值才是我们这个[Symbol.iterator]属性所需要的,因此一定要记得返回的是array[Symbol.iterator]的返回值
return array[Symbol.iterator]();
}
}
然后你可以使用for..of
测试一下:
js
const range = new IRange(2,4);
for(const cur of range){
console.log(cur);
}// 2 3 4
当然到这里肯定还不够,我们需要去了解迭代器协议
迭代器协议
迭代器协议规定了迭代器对象,一个对象需要包含以下属性,才可以被称为一个迭代器对象:
- next()方法 (必须)
- 返回值必须是一个对象,以下属性均为可选
done:boolean
表示迭代器是否可以生成下一个值value:any
表示被迭代的值
- 返回值必须是一个对象,以下属性均为可选
因此我们可以得到下图所示的知识结构:
一个可迭代对象应该与下图类似:
依靠前面的知识,我们就可以完善IRange
类了:
js
function IRange(from, to) {
this.from = from;
this.to = to;
this[Symbol.iterator] = function () {
let current = this.from;
const last = this.to;
return {
next() {
if (current <= last) {
return { done: false, value: current++ };
} else {
return { done: true };
}
}
}
};
let index = 0;
for (const item of this) {
this[index] = item;
index++;
}
this.length = index;
}
类数组对象
类数组对象十分简单,一个类数组对象需要满足以下两个条件:
- 被遍历项有索引
- 有
length
属性
那么我们构造一个类数组对象是非常简单的:
js
const arrayLike = {
0:'hello',
1:'world',
length:2
}
对于这样的对象,我们只能使用普通的for
循环来遍历:
js
for(let i = 0; i<arrayLike.length;i++){
console.log(arrayLike[i]);
}
类数组对象十分的普通,对他的要求就是这么简单!
那么再完善我们的IRange
类,此时我们只需要再对象上添加对应的索引和值以及length
即可,我们直接利用刚刚实现的可迭代协议:使用for..of
😎
最终的完整代码如下:
js
function IRange(from, to) {
this.from = from;
this.to = to;
this[Symbol.iterator] = function () {
let current = this.from;
const last = this.to;
return {
next() {
if (current <= last) {
return { done: false, value: current++ };
} else {
return { done: true };
}
}
}
};
let index = 0;
for (const item of this) {
this[index] = item;
index++;
}
this.length = to - from + 1;
}
扩展
text
为什么一开始讲解可迭代对象时,要先借助其他内置可迭代对象的[Symbol.iterator]?
一方面是因为这样能循序渐进的讲述,另一方面是因为遇见了一个面试题:如何让下面的代码成立:const [a,b] = {a:3,b:4}
我们知道这是一个解构语法,但是右边是对象,左边是数组,显然是不成立的,但是如果你知道解构这个语法糖背后做了什么,你就可以解决了;
支持解构的对象,必须是一个可迭代对象
那么问题就在于这个普通对象{a:3,b:4}
不是可迭代对象,而我们只要将其转换为可迭代对象就可以了,简单的方法就是利用数组的[Symbol.iterator]
属性;
js
Object.prototype[Symbol.iterator] = function () {
// const arr = [3, 4]; //这种方式不是动态的
const arr = Object.values(this) //使用Object.values(obj)返回obj 自有 可枚举 字符串类型的键 对应的值所组成的数组
return arr[Symbol.iterator]();
}
text
Array.from()方法
Array.from 可以接受一个可迭代或类数组的值,并从中获取一个"真正的"数组。然后我们就可以对其调用数组方法了。
这个方法使得:
- 一个类数组对象或可迭代对象能够拥有数组的方法:
push pop map forEach...
- 还能使一个类数组对象变为可迭代对象,或者使可迭代对象变为类数组对象,因为我们知道Array类型对象两者都是;
js
Array.from(obj[, mapFn, thisArg])
我们可以借助Array.from来修改IRange
类:
js
function IRange(from, to) {
return Array.from({ length: to - from + 1 }, (_, index) => from + index);
}
这好像没啥意义了,IRange成了一个创建数组的方法了😅;
不过有个小知识点:就是当使用new
关键词调用构造函数生成对象时,如果返回的是原始类型,则返回值无效,最终返回的是构造的对象,如果返回值是对象那么这个对象有效;
总结
- 可迭代对象:遵循可迭代协议,对象必须拥有
[Symbol.iterator]
属性,值是一个无参函数,并且返回一个迭代器;- 迭代器:遵循迭代器协议,必须拥有next()方法,next方法返回一个对象{done:boolean,value:any};
- 类数组对象:需要遍历的值都有类似数组一样的索引,并且具有
length
属性;