JS数组方法,如何真正做到为我所用-数据处理(数组篇)

548 阅读12分钟

Array

数据处理-数组篇

前言:

关于Javascript数组,其原形对象上有很多方法,死记硬背是断然是行不通的,我相信数组方法从来不是冗长的罗列,每一个都有其鲜活的生命力‼️

但就我来说,我始终不能理清众多的数组方法,什么时候用,怎么用,无法随心所欲的让他们成为我日常工作的中的得力助手,要么就是代码写的不优雅,要么就是效率不高,其次是在阅读关于数据处理的代码时,也会有种半天看不出个所以然的感觉。我深知自己的痛点,围绕我的痛点,我决定通过本文的总结将数组问题一网打尽👊(说大话警告⚠️ hhhh)

表达有误、错别字、知识点错误 恳请指出🙏

本文目标

  1. 汇总数组,按照类别划分为9个速查目录
  2. 独创是否改变原数组记忆法,再也不因为这个方法改不改变原数组困惑
  3. 为数组方法尽可能都找到一个或多个使用他们的对应的业务场景,每个数组方法对应一个或多个应用场景,彻底理解他们的特点,让他们为日常工作助力
  4. 数组的浅拷贝和深拷贝
  5. 彻底理解迭代器
  6. 其他知识,如稀疏数组、数组length属性、效率问题等

随着不断整理文章,我才发现我对基础知识的掌握可谓是漏洞百出,非常薄弱,我很幸运做了这次总结,让我收获颇多

基本方法速查

第一类 数组创建/初始化/类型判断

image.png

Note📝

1⃣️ 稀疏数组,如果你执行了new Array(10)或Array(10),你将得到一个长度为10的稀疏数组,稀疏数组中的每个元素称空槽,用empty表示,我理解的稀疏数组就是内存给你开出来了,但是这块空间还没做任何初始化,每个元素对应的内存空间里面有什么都是未知的。

有些数组操作会把空槽当成undefined处理,有些则会跳过不处理。不同的数组方法对待空槽的处理不同。具体见友情link👉developer.mozilla.org/zh-CN/docs/…

2⃣ 初始化数组的常规写法 new Array(10).fill️(x) 将数组初始化成一个长度为10,每个元素都为x的数组。

3⃣️ Array.of 将任意元素的转为数组,或许可以用在入参转为数组的场景中 🤔

第二类 堆栈/队列操作

image.png

Note📝

1⃣️ 返回值规律

只要是数组元素被删除了(pop,shift) 就返回删除的元素

如果数组元素被追加了(push,unshift),就返回数组的长度

2⃣️ 传参特例

push() unshift()方法 不仅支持一次调用传递多个参数,且非必传(平时使用时好像经常只是push一个元素这样,忘记了还可以传递多个参数)

3⃣️ 返回值特例

空数组[]调用pop,shift方法 返回undefined

第三类 操作类

image.png

Note📝

1⃣️ cancat 方法入参可以是一维数组,也可以是值,如 concat(val1,val2,[e1,e2])都会被拼接的目标数组中

2⃣️ concat、slice方法不传参数时相当于对数组浅拷贝,‼️slice方法返回值是数组截取部分的浅拷贝

3⃣️ splice方法️功能最为强大,方法中标注的第2个参数deleteCount为删除项数、第3个参数...items为补充项,具体的功能根据后面两个参数变化而变化, 列举如下:

a. 删除 没有补充项

b. 插入 删除项数小于补充项

c. 替换 删除项数 = 补充项

4⃣️ with 方法实现优雅链式调用

【如一个业务上的修改场景,修改完成后还需要后续操作,那么找到修改元素的索引后,直接使用with方法修改,如 arr.with(index,value).map() arr.with(index,value).filter()

没有后续操作,因为with方法不改变原数组,并且返回的一个更改后的新数组,对于react的state更新机制,也可以用于修改后的状态更新】

第四类 查找

image.png

Note📝

1⃣️ 返回值规律

除includes方法外的其他查找方法,返回值遵循如下规律

rule1 找索引 没找到都返回 -1 找到的话返回索引

rule2 找元素 没找到都返回undefine 找到的话返回找到的那个元素

第五类 遍历方法

image.png

Note📝

🈳️数组调用every 返回true

🈳️数组调用some 返回false

第六类 归并reduce

image.png

Note📝

1⃣️归并操作执行逻辑

遍历每个元素,对每个元素调用归并函数,并将前一个元素处理的结果,作为下一个元素执行归并函数的入参,典型的例子就是累加了

第七类 迭代器相关方法

image.png

第八类 字符串转换方法

image.png

第九类 排序/扁平化方法

image.png

Note📝

1⃣️ sort 和 toSorted 在不传compareFn时,默认元素按照Unicode编码值升序排列,对象类型元素,则需要指定根据某一属性排序。

2⃣️ compareFn的定义

image.png

非常感谢antd的配色 🙇

ant-design.antgroup.com/docs/spec/c…

颜色深浅代表使用频率,使用频率仅凭个人经验和当前公司项目总结 仅供参考

是否改变原数组

分析原因

仔细想下就能明白,所谓改不改变原数组,说这个方法的内部实现中如果涉及到了对数组元素的操作,那么内部有没有把原数组先拷贝一份,后续的操作在拷贝的数组中开展,如果有则不改变原数组,无则改变原数组。

不像底层语言,或者自己造轮子,一个方法改变改不改变原数组,可以说完全由自己掌控,js中的数组方法都是给我们封装好的,不用自己实现,我们享受便利的同时,也增加了需要我们额外记忆的东西。

既然如此先说结论: 改变数组的方法其实不多,就这9个

image.png

数组原型上不完全统计共有39个数组方法中,改变原数组的方法有9个 占比约23%

简单分析🧐:

从数组操作的目的出发,发现我们非常常用的第四类和第五类操作本质均为遍历。遍历操作一般是不会更改原数组的。

fill 该方法的目的是将新建的空槽数组数组初始化为用户设定的初始值,设想使用这个方法的人希望自己拿到的一定不再是初始化之前的空槽数组,如果我们不改变原数组,保留原来的空槽数组留给用户也是没有意义的。

sort reverse splice 均有不改变原数组的版本

push pop unshift shift 第一类堆栈队列操作均改变原数组

麻将记忆法

自己总结麻将记忆法:

如果我的麻将牌是一个数组的话 那么

首尾增删 ( push pop unshift shift

切割排序 ( ****splice sort reverse

填充复制 ( copyWithin fill

这几个方法真的完全覆盖了我打麻将的过程 谁懂完美吻合

不知道大家能不能Get

image.png

对应的不改变方案

对于这九个改变原数组的方法MDN上都给出了对应的不改变的解决方案,请参考下表

image.png

思考

1⃣️ 改不改变原数组是个伪命题

其实我们应该回答的问题是 我什么时候想到需要区分一些方法该不改变原数组

a. 做业务的大部分时间我们都在做什么:拿后端数据、ui和数据隔离 -> 处理数据/收集用户信息 -> 提交给后端,当我们的一个数据信息 不只在一处使用,其他地方也依赖这个数据的时候,我们不能随意更改原数组

b. 前端框架的状态更新机制,如在使用react框架时,他的浅比较的状态更新机制

2⃣️ 除了这些数组方法之外,还有个方式可以改变原数组哦,那就是数组的length属性,别小看数组的length的属性,这个属性他竟然是可写的,它有让一个数组瞬间变成空数组的魔法 那就是 arr.length = 0,通过设置arr.length 还可以达到数组截断的目的。

数组方法场景对应

第二类 push pop shift unshift

也就是说有【首尾增删】这类需求的时候,就能想到要用第二类数组方法。

第三类和第四类方法

操作和查找方法经常用于修改场景

在修改场景下,我们经常先使用第五类的查找方法定位到用户修改的是那一行数组,然后使用第三类的修改方法对内容进行更新后提交给后端。达到修改的目的。

第五类 遍历/遍历+后处理

map方法可以说在日常工作中使用频率最高的了,map 使用场景也有很多 如:

  • 场景一

如果接口值返回了title 但是ui需要展示 Ant Design Title 1

map 常用语 后端接口返回数据和ui组件暴露的api或者展示内容之间存在差距,

需要一定的加工后才能满足业务要求。

  • 场景二 为对象数组更改元素对象的属性名/或从中挑选自己需要的属性组成新数组

比如我们使用一些UI组件的时候,组件会定义我们传递的参数的属性名,如Antd Select展示就需要options数组为[{label,value}] 但是如果后端返回返回的数据是[{id,name,...}]之类的,就需要用map处理

这里可以用map实现 相当于改了属性名

filter 可以说在日常工作中使用频率也非常高,它的作用主要用来过滤满足条件的元素,

如业务要求这个列表只展示男性信息,如果我们拿到的数据中有其他性别信息,就需要 filter一下

其次 对于 filter 一定要和删除这个关键词联系起来,过滤其实也是一种删除

every some 把符合条件的找出来

如上表格操作中,如果选中的这些选项age都是32岁,则有些后续操作出现

若不符合这个条件,则没有后续操作,此时就需要用every some来判断

因为我现在业务是B端saas系统,所以举例多是些表格或者列表操作,举例场景比较有限,如果大家对我们常用的数组方法有自己的理解,有自己脑中的画面感,欢迎补充!大家放在评论区我看会添加到文章中🌹

数组的浅拷贝和深拷贝

关于深拷贝和浅拷贝的理论概念我就不在此赘述了,因为有很多资料可以学习。相信大家也都已经理解。这里就举2个通俗的小例子聊一聊。

浅拷贝 就是一个块内存空间 起了多个名字 比如你有大名和小名,还有朋友之间的昵称

不管怎么样,这些名字都指向同一个你, 不管这块内存定义了多少个变量名,都指向同一块内存空间。

深拷贝则不同,深拷贝则需要重新开辟一块内存空间,再将之前的信息一模一样的抄过来一份,

就像连锁酒店的房间,不同的城市都有一份深拷贝。js数组本身没有实现深拷贝,如果我们想要对数组进行深拷贝的,需要自己DIY或者使用lodash _.deepclone()方法。

实现方法汇总:

image.png

题外话: 因为我之前短暂的使用过c语言,底层语言使用起来,对深拷贝和浅拷贝这样的操作不会像js这么抽象,比如在c语言中如果你想要深拷贝 则需要调用开辟新内存的方法malloc方法,如

int *p = malloc(sizeof(int)*arr.length)

此时p指针就指向了一块你新开辟的内存

而如果只是浅拷贝的话 如 int * x = p 这种操作就是浅拷贝 x只是获得了和p一样的内存地址而已。

这导致我对js数组浅拷贝曾有过巨大误解,因为受过往经验的影响,我曾认为js 数组浅拷贝后,仍然指向同一块内存空间,如使用展开运算符进行浅拷贝操作时 let a1 = [...a2] 我曾认为 a1 === a2 但事实不是如此,即使 a1 == a2 也不成立。

经过实践后 真相竟是:

如果js数组元素均非引用类型,其实数组的浅拷贝就相当于概念上的深拷贝,如上所示更改a1 不会影响a2,更改a2 也不会影响a1 ,a1 和 a2 已完全独立,而如果js数组元素包含引用类型,则数组元素的浅拷贝,才是真正意义上的浅拷贝,也就是说js数组的深拷贝和浅拷贝其实是针对数组元素而言的,而非数组本身,对于数组本身而言,无论数组元素是否为引用类型,数组的浅拷贝操作都已经让数组本身不同了,也就说在js中,你定义了一个新数组的时候默认调用了数组的构造函数实现中,已经帮你开辟了新内存,存放新数组对象。

这个其实问题困扰了我很久,因为我初期在使用React框架,在做数组类型状态更新时,我总认为浅拷贝是不足以让状态更新的,但其实数组的浅拷贝已经让两个数组对象不同了。

如果大家没有和我一样的困惑,不必深究。

何为迭代器

由于本篇篇幅过长 我把迭代器这一小节拆出去了,请参考 👉juejin.cn/post/737257…

最后:

本人是从c/c++开发转行前端后野蛮生长的前端工程师,目前还在一点一点夯实基础中....

希望和大家一起学习,共同进步❤️

如果有帮助🫡点个收藏不迷路 ❤️

参考资料:

ecmascript 草案 第23节 tc39.es/ecma262/#se…

MDN

《红宝书》

lodash中文网