JavaScript--一文吃透 可迭代对象 与 类数组对象

本文将带大家从认识可迭代对象 与 类数组对象 ,并手搓一个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;
}

类数组对象

类数组对象十分简单,一个类数组对象需要满足以下两个条件:

  1. 被遍历项有索引
  2. 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]();
}

Object.values()


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属性;
相关推荐
再吃一根胡萝卜30 分钟前
08 · 前端:Vue3 + SSE 流式与执行链路可视化
面试
再吃一根胡萝卜33 分钟前
03 · 后端:FastAPI 与分层架构
面试
mldong2 小时前
你的 Vue3 项目也能有钉钉同款审批流设计器:npm 装包,10 分钟画出第一条审批流
前端·vue.js
2分钟速写快排3 小时前
什么是 RAG?如何用 RAG 实现一个用户记忆?
前端·后端·ai编程
passerby60613 小时前
如何自己造一个时间处理库
前端·javascript·github
走到天涯海角4 小时前
react里面的长列表渲染优化
前端·react.js·前端框架
小羊没烦恼!4 小时前
Hello Web API系列教程——Web API与国际化
java·服务器·前端·javascript·php
北岛贰5 小时前
迷茫焦虑期,我做了一个带支付带官网的 AI 聊天虚拟恋人 App
前端·人工智能·后端
Interview Aid1125 小时前
Walmart Global Tech SDE 三轮面经|基础、并发、压力面
面试·职场和发展
mayaairi6 小时前
Vue2 组件通讯(三):全局事件总线、PubSub、插槽与组件实例属性
前端·javascript·vue.js