JavaScript 数据处理 - 列表篇

Posted 边城狂人

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JavaScript 数据处理 - 列表篇相关的知识,希望对你有一定的参考价值。

程序中的常用数据集合无非两类,列表 (List) 和映射 (Map)。在 javascript 的语言基础中就提供了这两种集合结构的支持 —— 用数组 (Array) 表示列表,用直接对象 (Plain Object) 表示映射(属性键值对映射)。

今天我们只说数组。 从 ​​Array​​​ 类中提供的实例方法可以看出来,数组涵盖了一般的列表操作,增删改查俱全,更提供了 ​​shift()/unshift()​​​ 和 ​​push()/pop()​​ 这样的方法,使数组具有队列和栈的基本功能。 除了日常的 CRUD 之外,最重要的就是对列表进行完全或部分遍历,拿到预期的结果,这些遍历操作包括

  1. 逐一遍历:​​for​​​、​​forEach()​​​、​​map()​​ 等;
  2. 筛选/过滤:​​filter()​​​、​​find()​​​、​​findIndex()​​​、​​indexOf()​​ 等;
  3. 遍历计算(归约):​​reduce()​​​、​​some()​​​、​​every()​​​、​​includes()​​ 等。

Array 对象提供来用于遍历的实例方法,大多数都是接收一个处理函数在遍历过程中对每个元素进行处理。而且处理函数通常会具有三个参数:​​(el, index, array)​​​,分别表示当前处理的元素、当前元素的索引以及当前处理的数组(即原数组)。当然,这里说的是大多数,也有一些例外,比如 ​​includes()​​​ 就不是这样,而 ​​reduce​​​ 的处理函数会多一个表示中间结果的参数。具体情况不用多说,​​查阅 MDN​​ 即可。

一、简单遍历

大家都知道 ​​for​​​ 语法在 JavaScript 中除了基本的 ​​for ( ; ; )​​​ 之外,还包含了两种 for each 遍历。一种是 ​​for ... in​​​ 用来遍历键/索引;另一种是 ​​for ... of​​​ 用来遍历值/元素。两种 for each 结构都不能同时拿到键/索引和值/元素,而 ​​forEach()​​​ 方法可以拿到,这是 ​​forEach()​​​ 的便利所在。不过在 for each 结构中要终止循环,可以使用 ​​break​​​,而在 ​​forEach()​​​ 中要想终止循环只能通过 ​​throw​​​。使用 ​​throw​​​ 来终止循环需要在外面进行 ​​try ... catch​​ 处理,不够灵活。举例:

try 
list.forEach(n =>
console.log(n);
if (n >= 3) throw undefined;
);
catch
console.log("The loop is broken");

如果没有 ​​try ... catch​​​,里面的 ​​throw​​ 会直接中断程序运行。

当然,其实也有更简单的方法。注意到 ​​some()​​​ 和 ​​every()​​​ 这两个方法都是对数组进行遍历,直到遇到符合条件/不符合条件的元素。简单地说它们是根据处理函数的返回值来判断是否中断遍历。对于 ​​some()​​​ 来说,是要找到一个符合条件的元素,处理函数如果返回 ​​true​​​,就中断遍历;而 ​​every()​​​ 正好相反,它是要判断每个元素都符合条件,所以只要遇到返回 ​​false​​ 就会中断遍历。

根据我们对一般 for 循环和 while 循环的理解,都是条件为真是进行循环,所以看起来 ​​every()​​​ 更符合习惯。上面的示例用 ​​every()​​ 改写:

list.every(n => 
console.log(n);
return n < 3;
);

使用 ​​some()​​​ 和 ​​every()​​​ 特别需要注意一点:它不需要精确返回 boolean 类型的值,只需要判断真值 (truthy) 和 假值(falsy) 即可。 JavaScript 函数在没有显式返回值的情况下等同于 ​​return undefined​​​,也就是返回假值,效果和 ​​return false​​ 等同。

关于 JavaScript 的假值,可以查阅 ​​MDN - Falsy​​。除了假值,都是真值。

二、遍历映射

有时候我们需要对一个数组进行遍历,根据其每个元素提供的信息,产生另一个数值和对象,而结果仍然放在一个数组中。前端开发中这种操作最常见的场景就是将从后端拿到的​模型数据​列表,处理成前端呈现需要的​视图数据​列表。常规操作是这样:

// 源数据
const source = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

// 创建目标数组容器
const target = [];
// 循环处理每一个源数据元素,并将结果添加到目标数组中
for (const n of source)
target.push( id: n, label: `label$n` );


// 消费目标数组
console.log(target);

​map()​​​ 就是用来封装这样的遍历的,它可以用来处理一对一的元素数据映射。上例改用 ​​map()​​ 只需要一句话代替循环:

const source = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const target = source.map(n => ( id: n, label: `label$n` ));
console.log(target);

除了减少语句之外,使用 ​​map()​​ 还把原来的若干语句,变成了一个表达式,可以灵活地用于上下逻辑衔接。

三、处理多层结构 - 展开 (flat 和 flatMap)

展开,即 ​​flat()​​ 操作可以把多维度的数组减少 1 个或多个维度。举例来说

const source = [1, 2, [3, 4], [5, [6, 7], 8, 9], 10];
console.log(source.flat());
// [ 1, 2, 3, 4, 5, [ 6, 7 ], 8, 9, 10 ]

这个例子是个包含了三个维度(虽然不整齐)的数组,使用 ​​flat()​​​ 减少了一个维度,其结果变成了两个维度。​​flat()​​​ 可以通过参数指定展开的维度层数,这里只需要指定一个大于等于 ​​2​​ 的值,它就能把所有元素全部展平到一个一维数组中:

const source = [1, 2, [3, 4], [5, [6, 7], 8, 9], 10];
console.log(source.flat(10));
// [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]

有了这个东西,我们在处理一些子项的时候就会比较方便。比如一个常见问题:

有一个二层的菜单数据,我想拿到所有菜单项列表,应该怎么办?数据如下

const data = [

label: "文件",
items: [
label: "打开", id: 11 ,
label: "保存", id: 12 ,
label: "关闭", id: 13
]
,

label: "帮助",
items: [
label: "查看帮助", id: 91 ,
label: "关于", id: 92
]

];
>

怎么办?毫无悬念应该是使用一个双层循环来处理。不过利用 ​​map()​​​ 和 ​​flat()​​ 可以简化代码:

const allItems = data.map(( items ) => items).flat();
// ^^^^ ^^^^^

第一步 ​​map()​​​ 把 ​​ label, items ​​​ 类型的元素映射成为 ​​[...items]​​ 这种形式的数组,映射结果是一个二维数组(示意):

[
[...文件菜单项],
[...帮助菜单项]
]

再用 ​​flat()​​​ 展平,就得到了 ​​[...文件菜单项, ...帮助菜单项]​​,也就是预期的结果。

通常我们直接拿到二维数组来处理的情况极少,一般都需要先 ​​map()​​​ 再 ​​flat()​​​,所以 JavaScript 为这两个常用组合逻辑提供了 ​​flatMap()​​​ 方法。要理解 ​​flatMap()​​​ 的作用,就理解为先 ​​map(...)​​​ 再 ​​flat()​​ 即可。上面的示例可改为

const allItems = data.flatMap(( items ) => items);
// ^^^^^^^^

这里解决了一个两层结构的数据,如果是多层结构呢?多层结构不就是普通的树形结构,使用递归对所有子项进行 ​​flatMap()​​ 处理即可。代码先不提供,请读者动动脑。

四、过滤

如果我们有一组数据,需要把其中符合某种条件的筛选出来使用,就会用到过滤,​​filter()​​​。​​filter()​​​ 接收一个用于判断的处理函数,并对每个元素使用该处理函数进行判断。如果该函数对某个元素的判断结果是真值,该元素会被保留;否则不会收录到结果中。​​filter()​​ 的结果是原数组的子集。

​filter()​​ 的用法很好理解,比如下面这个示例筛选出能被 3 整除的数:

const a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const r = a.filter(n => n % 3 === 0);
// [ 3, 6, 9 ]

有两点需要强调: 第一,如果所有元素都​不符合​​条件,会得到一个空数组。既不是 ​​null​​​ 也不是 ​​undefined​​​,而是 ​​[]​​;

第二,如果所有元素都​符合​条件,会得到一个包含所有元素的​​​数组。它与原数组进行 ​​===​​​ 或 ​​==​​​ 比较均会得到 ​​false​​。

过滤虽然简单,但是要注意灵活运用。比如说需要统计某组数据中符合条件的个数,一般会想到遍历计数。但我们也可以先按指定条件过滤,再取结果数组的 ​​length​​。

五、查找

查找和过滤的区别在于:查找是找到一个符合条件的元素即可,而过滤是找到全部。从实现效果上来说,​​arr.filter(fn)[0]​​​ 就可以达到查找效果。只不过 ​如何从 firestore 检查有多少项目具有真值或假值并在列表中仅显示真值? - 颤动

[python学习篇][书籍学习][python standrad library][内置类型]对象测试真值,布尔值操作, 比较操作

给定 SML 中的变量列表,生成所有真值赋值的列表?

获取布尔列表中第一个真值的索引

Dart如何在列表中找到真值的索引

JavaScript 真值和假值