lodash已死?radash最全使用介绍(附源码详细说明)—— Array方法篇(1)
- 相信很多前端同学甚至非前端都或多或少使用过lodash库,我们都知道lodash是一个非常丰富的前端工具库,比如最常用的防抖和节流,使用lodash都能很快实现,在github上更是有着58.7k的star数。但最近出现的Radash库,号称lodash plus版本,比之更新、更小、更全面、源码更易于理解。
- 阅读本文你能了解些什么?
- radash是什么;
- 它相较于lodash有哪些优势;
- radash 数组相关方法介绍及源码解析。
认识Radash
一句话介绍:radash是一个强大的零依赖的前端工具库。如果你会使用lodash,那么你使用radash将没有任何门槛。
使用Radash有哪些优势?
- 零依赖,radash不依赖任何第三方库,仅在自己的源码里面去实现功能,所以非常的轻量。使用它你只需要加载radash本身;
- Typescript编写,使用起来更安全,不用担心变量类型问题;
- 全面支持es6+的新特性。它去除了lodash身上一些过时的方法(这些方法能够使用es6+新特性快速简单实现);
- 方法更全面。包含数组相关、对象相关、排序相关、字符串相关、优化相关等等等等...,几乎能满足你能想到的前端工具方法。
- 源码更易于理解。我们甚至可以说radash的某些方法的实现时直接而暴力的(这点你会在我后续的方法源码介绍中有所感受)。
Radash相关方法如何使用
-
下载radash
npm install radash --save // 或 yarn下载 yarn add radash
-
引入你需要的方法
import { alphabetical } from 'radash'
-
按照要求传入相关参数就可以使用了。
Radash的数组相关操作方法详解
注意:以下我们示例将直接使用,不再进行引入操作,实际使用时记得先引入再使用
alphabetical:把对象数组按照选定key的value的字母顺序排列
-
用法说明
- 参数:目标对象数组、用于排序的属性的回调函数、第三个参数可选(不传是升序排序,传入
desc
字符则表示降序排序); - 返回值:排序后的数组。
- 参数:目标对象数组、用于排序的属性的回调函数、第三个参数可选(不传是升序排序,传入
-
基础使用代码示例
const ig = [ { name: 'ning', power: 100 }, { name: 'rookie', power: 98 }, { name: 'jkl', power: 95 }, { name: 'theshy', power: 100 } ] // 这里输出的依然是对象数组,这里简单表示 alphabetical(ig, g => g.name) // => [jkl, ning, rookie, theshy] alphabetical(ig, g => g.name, 'desc') // => [theshy, rookie, ning, jkl]
-
源码解析
// 定义一个泛型函数 `alphabetical`,接受一个泛型数组 `array`, // 一个用于从数组项中获取排序依据字符串的函数 `getter`, // 和一个可选的方向参数 `dir`,默认值为 'asc'(升序)。 export const alphabetical = <T>( array: readonly T[], getter: (item: T) => string, dir: 'asc' | 'desc' = 'asc' ) => { // 如果输入数组不存在或为空,直接返回一个空数组 if (!array) return [] // 定义一个升序比较函数,使用 `localeCompare` 方法比较通过 `getter` 获取的字符串。 const asc = (a: T, b: T) => `${getter(a)}`.localeCompare(getter(b)) // 定义一个降序比较函数,它将通过 `getter` 获取的字符串逆序比较。 const dsc = (a: T, b: T) => `${getter(b)}`.localeCompare(getter(a)) // 使用 `slice` 方法克隆数组,避免修改原数组,然后根据 `dir` 参数选择排序函数进行排序。 return array.slice().sort(dir === 'desc' ? dsc : asc) }
- 方法工作流程说明:
-
这个函数的作用是对任何类型的数组进行排序,排序依据是数组每项通过
getter
函数得到的字符串。调用localeCompare
是为了正确地比较可能包含特殊字符的字符串。这个函数还允许用户指定排序方向,升序或降序; -
localeCompare
是一个字符串方法,用于比较两个字符串,并返回一个表示这两个字符串在排序中相对位置的数字。该方法基于本地语言环境的排序规则进行比较,这意味着它可以正确地比较具有特定语言字符和变音符号的字符串。
当localeCompare
被调用时,它将返回三种可能的值:- 如果字符串在排序中应该出现在比较字符串之前,则返回一个负数;
- 如果两个字符串相等(在排序中的位置相同),则返回 0;
- 如果字符串在排序中应该出现在比较字符串之后,则返回一个正数;
例如,利用
localeCompare
方法可以正确地对包含德语、法语或西班牙语等特殊字符的字符串进行排序,而不仅仅是基于ASCII码值的简单比较。
-
- 方法工作流程说明:
boil:返回对象数组中满足条件的对象
-
用法说明
- 参数:目标对象数组、条件函数;
- 返回值:满足条件的对象。
-
基础代码示例
const rng = [ { name: 'Uzi', power: 100 }, { name: 'Xiaohu', power: 98 }, { name: 'Ming', power: 72 } ] boil(gods, (a, b) => (a.power > b.power ? a : b)) // => { name: 'Uzi', power: 100 } boil(gods, (a, b) => (a.power < b.power ? a : b)) // => { name: 'Ming', power: 72 }
-
源码解析
// 定义一个泛型函数 `boil`,它接受一个具有只读属性的泛型数组 `array`, // 以及一个用于比较数组中两个元素并返回其中一个的比较函数 `compareFunc`。 export const boil = <T>( array: readonly T[], compareFunc: (a: T, b: T) => T ) => { // 如果传入的数组不存在或长度为0,则函数返回 null。 if (!array || (array.length ?? 0) === 0) return null // 使用数组的 `reduce` 方法应用 `compareFunc`,将数组归约为单一的值。 return array.reduce(compareFunc) }
-
方法工作流程说明:
在这个函数中,
reduce
方法接收compareFunc
作为参数。reduce
方法会遍历数组的所有元素,并且在每一步中应用compareFunc
,将数组中的元素逐渐归约到一个单一的结果。compareFunc
函数负责决定如何从两个元素中选择一个。
-
cluster:把一个数组尽量均匀的分成多个数组
- 用法说明
- 参数:目标数组、分组个数n;
- 返回值:分组后的二维数组。
- 基础代码示例
const gods = ['Ra', 'Zeus', 'Loki', 'Vishnu', 'Icarus', 'Osiris', 'Thor', 'Apollo', 'Artemis', 'Athena'] cluster(gods, 3) // => [ // [ 'Ra', 'Zeus', 'Loki' ], // [ 'Vishnu', 'Icarus', 'Osiris' ], // ['Thor', 'Apollo', 'Artemis'], // ['Athena'] // ]
- 源码解析
// 定义一个泛型函数 `cluster`,它接收一个具有只读属性的泛型数组 `list`, // 以及一个可选的数字参数 `size`,默认值为2,表示子数组的大小。 export const cluster = <T>(list: readonly T[], size: number = 2): T[][] => { // 计算出需要多少个子数组群组来容纳原数组,确保即使不能完全平分也会创建一个额外的群组来容纳剩余的元素。 const clusterCount = Math.ceil(list.length / size) // 创建一个新数组,长度为 `clusterCount`,初始填充为 `null`。 return new Array(clusterCount).fill(null).map((_c: null, i: number) => { // 对于新数组中的每个元素,使用 `slice` 方法从原数组 `list` 中提取出相应的子数组。 // 子数组的开始索引是 `i * size`,结束索引是 `i * size + size`。 return list.slice(i * size, i * size + size) }) }
- 方法工作流程说明:
- 首先,使用
Math.ceil
函数计算出给定数组大小和子数组大小的情况下,需要多少个子数组群组。因为Math.ceil
向上取整,这确保了即使最后一个群组不满也会被创建; - 接着,创建一个新的数组,这个数组的长度是我们刚才计算出的群组数量
clusterCount
。使用fill(null)
方法将其填充为null
,这样我们就可以在其上使用map
方法; - 然后,对这个新数组使用
map
方法,对于其中的每个元素(最初都是null
),我们计算原数组list
中对应的子数组应该从哪里开始(i * size
),到哪里结束(i * size + size
),并使用slice
方法提取这个子数组; - 最终,我们得到一个新的数组,它由原数组
list
切分成多个子数组组成,每个子数组的最大长度由size
参数决定。
- 首先,使用
- 方法工作流程说明:
counting:统计对象数组中每个唯一标识符的出现次数
- 用法说明
- 参数:目标对象数组、条件函数(内部是传入目标对象,返回对象身上的某一项——根据这项来做统计);
- 返回值:统计对象。
- 基础代码示例
const skt = [ { name: 'Ra', culture: 'egypt' }, { name: 'Zeus', culture: 'greek' }, { name: 'Loki', culture: 'greek' } ] counting(gods, g => g.culture) // => { egypt: 1, greek: 2 }
- 源码解析
// 定义一个泛型函数 `counting`,它接收一个具有只读属性的泛型数组 `list`, // 和一个函数 `identity`,该函数用于从数组每个元素中提取一个唯一标识符(可以是字符串、数字或符号)。 export const counting = <T, TId extends string | number | symbol>( list: readonly T[], identity: (item: T) => TId ): Record<TId, number> => { // 如果传入的数组不存在,则返回一个空对象。 if (!list) return {} as Record<TId, number> // 使用数组的 `reduce` 方法来累计每个唯一标识符的出现次数。 return list.reduce((acc, item) => { // 使用 `identity` 函数从当前元素 `item` 中获取唯一标识符。 const id = identity(item) // 如果 `acc`(累加器)中已经有这个标识符的记录,则增加它的计数,否则初始化为1。 acc[id] = (acc[id] ?? 0) + 1 // 返回更新后的累加器对象。 return acc }, {} as Record<TId, number>) // 初始化累加器为一个空对象。 }
- 方法工作流程说明:
- 接收一个数组
list
和一个identity
函数,后者用于指定如何从数组项中提取唯一标识符; - 如果传入的
list
为空,返回一个空的记录对象; - 使用
reduce
方法遍历数组。reduce
的累加器acc
是一个对象,其键是通过identity
函数从数组项中提取的唯一标识符,值是标识符出现的次数; - 在每次迭代中,从当前项
item
中提取唯一标识符id
。如果acc
中已经存在id
键,就将其值加1;如果不存在,就将其值设置为1; - 最终,返回这个累加器对象,它是一个记录对象,其键是唯一标识符,值是对应的出现次数。
- 接收一个数组
- 方法工作流程说明:
diff:返回数组1中出现但是没在数组2中出现的项
- 用法说明
- 参数:目标数组1、目标数组2;
- 返回值:包含符合项的数组。
- 基础代码示例
import { diff } from 'radash' const oldWorldGods = ['rng', 'uzi'] const newWorldGods = ['vishnu', 'uzi'] diff(oldWorldGods, newWorldGods) // => ['rng']
- 源码解析
// 定义一个泛型函数 `diff`,它接收两个具有只读属性的泛型数组 `root` 和 `other`, // 以及一个可选的函数 `identity`,用于从数组元素中提取一个唯一标识符(默认为将元素直接作为标识符)。 export const diff = <T>( root: readonly T[], other: readonly T[], identity: (item: T) => string | number | symbol = (t: T) => t as unknown as string | number | symbol ): T[] => { // 如果两个数组都为空或未定义,则返回一个空数组。 if (!root?.length && !other?.length) return [] // 如果 `root` 数组未定义或为空,则返回 `other` 数组的副本。 if (root?.length === undefined) return [...other] // 如果 `other` 数组未定义或为空,则返回 `root` 数组的副本。 if (!other?.length) return [...root] // 使用 `other` 数组的元素创建一个记录对象 `bKeys`,键是通过 `identity` 函数提取的唯一标识符,值为 `true`。 const bKeys = other.reduce((acc, item) => { acc[identity(item)] = true return acc }, {} as Record<string | number | symbol, boolean>) // 过滤 `root` 数组,只返回不在 `bKeys` 记录对象中的元素。 return root.filter(a => !bKeys[identity(a)]) }
-
方法工作流程说明:
- 检查
root
和other
数组是否都为空或未定义,如果是,则返回空数组; - 如果
root
数组为空或未定义,而other
数组不是,返回other
数组的副本; - 如果
other
数组为空或未定义,而root
数组不是,返回root
数组的副本; - 如果两个数组都不为空,使用
other
数组的元素创建一个记录对象bKeys
。identity
函数用于为每个元素提取唯一标识符,这些标识符作为bKeys
对象的键,其对应的值被设置为true
; - 使用
filter
方法遍历root
数组,返回那些其通过identity
函数提取的唯一标识符不在bKeys
对象中的元素。这些元素构成了root
和other
数组的差异集。
- 检查
-
下期我们将介绍以下方法
提示:如果是简单使用的话可以直接按照介绍选择合适的方法进行使用,我们后续会详细介绍。
- first:获取数组第一项,不存在返回默认值;
- flat:数组扁平化 —— 把多维数组转为一维数组;
- fork:按条件将数组拆分成两个数组,满足条件的一个,不满足条件的一个;
- group:根据条件函数指定的唯一标识符出现次数对数组进行排序;
- intersects:判断两个数组是否有公共项,返回一个布尔值。
写在后面
后续作者会整理一份方法目录上传,方便没法访问外网的同学对照查看使用。
大家有任何问题或者见解,欢迎评论区留言交流!!!
点击访问:Radash官网