JavaScript 数据处理 - 列表篇
Posted 边城狂人
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JavaScript 数据处理 - 列表篇相关的知识,希望对你有一定的参考价值。
程序中的常用数据集合无非两类,列表 (List) 和映射 (Map)。在 javascript 的语言基础中就提供了这两种集合结构的支持 —— 用数组 (Array) 表示列表,用直接对象 (Plain Object) 表示映射(属性键值对映射)。
今天我们只说数组。
从 Array
类中提供的实例方法可以看出来,数组涵盖了一般的列表操作,增删改查俱全,更提供了 shift()/unshift()
和 push()/pop()
这样的方法,使数组具有队列和栈的基本功能。
除了日常的 CRUD 之外,最重要的就是对列表进行完全或部分遍历,拿到预期的结果,这些遍历操作包括
- 逐一遍历:
for
、forEach()
、map()
等; - 筛选/过滤:
filter()
、find()
、findIndex()
、indexOf()
等; - 遍历计算(归约):
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][内置类型]对象测试真值,布尔值操作, 比较操作