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属性;
相关推荐
秦jh_4 分钟前
【Linux】多线程(概念,控制)
linux·运维·前端
蜗牛快跑21317 分钟前
面向对象编程 vs 函数式编程
前端·函数式编程·面向对象编程
Dread_lxy18 分钟前
vue 依赖注入(Provide、Inject )和混入(mixins)
前端·javascript·vue.js
涔溪1 小时前
Ecmascript(ES)标准
前端·elasticsearch·ecmascript
用户3157476081351 小时前
成为程序员的必经之路” Git “,你学会了吗?
面试·github·全栈
榴莲千丞1 小时前
第8章利用CSS制作导航菜单
前端·css
奔跑草-1 小时前
【前端】深入浅出 - TypeScript 的详细讲解
前端·javascript·react.js·typescript
羡与1 小时前
echarts-gl 3D柱状图配置
前端·javascript·echarts
guokanglun1 小时前
CSS样式实现3D效果
前端·css·3d
咔咔库奇2 小时前
ES6进阶知识一
前端·ecmascript·es6