lodash已死?radash最全使用介绍(附源码详细说明)—— Array方法篇(1)

  • 相信很多前端同学甚至非前端都或多或少使用过lodash库,我们都知道lodash是一个非常丰富的前端工具库,比如最常用的防抖和节流,使用lodash都能很快实现,在github上更是有着58.7k的star数。但最近出现的Radash库,号称lodash plus版本,比之更新、更小、更全面、源码更易于理解。
  • 阅读本文你能了解些什么?
    1. radash是什么;
    2. 它相较于lodash有哪些优势;
    3. radash 数组相关方法介绍源码解析

认识Radash

一句话介绍:radash是一个强大的零依赖的前端工具库。如果你会使用lodash,那么你使用radash将没有任何门槛。

使用Radash有哪些优势?

  • 零依赖,radash不依赖任何第三方库,仅在自己的源码里面去实现功能,所以非常的轻量。使用它你只需要加载radash本身;
  • Typescript编写,使用起来更安全,不用担心变量类型问题;
  • 全面支持es6+的新特性。它去除了lodash身上一些过时的方法(这些方法能够使用es6+新特性快速简单实现);
  • 方法更全面。包含数组相关、对象相关、排序相关、字符串相关、优化相关等等等等...,几乎能满足你能想到的前端工具方法。
  • 源码更易于理解。我们甚至可以说radash的某些方法的实现时直接而暴力的(这点你会在我后续的方法源码介绍中有所感受)。

Radash相关方法如何使用

  1. 下载radash

    npm install radash --save
    // 或 yarn下载
    yarn add radash
    
  2. 引入你需要的方法

    import { alphabetical } from 'radash'
    
  3. 按照要求传入相关参数就可以使用了。

Radash的数组相关操作方法详解

注意:以下我们示例将直接使用,不再进行引入操作,实际使用时记得先引入再使用

alphabetical:把对象数组按照选定key的value的字母顺序排列

  1. 用法说明

    • 参数:目标对象数组、用于排序的属性的回调函数、第三个参数可选(不传是升序排序,传入desc字符则表示降序排序);
    • 返回值:排序后的数组。
  2. 基础使用代码示例

    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]  
    
  3. 源码解析

    // 定义一个泛型函数 `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:返回对象数组中满足条件的对象

  1. 用法说明

    • 参数:目标对象数组、条件函数;
    • 返回值:满足条件的对象。
  2. 基础代码示例

    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 }
    
  3. 源码解析

    // 定义一个泛型函数 `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:把一个数组尽量均匀的分成多个数组

  1. 用法说明
    • 参数:目标数组、分组个数n;
    • 返回值:分组后的二维数组。
  2. 基础代码示例
    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']
    // ]
    
  3. 源码解析
    // 定义一个泛型函数 `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)
      })
    }
    
    • 方法工作流程说明
      1. 首先,使用 Math.ceil 函数计算出给定数组大小和子数组大小的情况下,需要多少个子数组群组。因为 Math.ceil 向上取整,这确保了即使最后一个群组不满也会被创建;
      2. 接着,创建一个新的数组,这个数组的长度是我们刚才计算出的群组数量 clusterCount。使用 fill(null) 方法将其填充为 null,这样我们就可以在其上使用 map 方法;
      3. 然后,对这个新数组使用 map 方法,对于其中的每个元素(最初都是 null),我们计算原数组 list 中对应的子数组应该从哪里开始(i * size),到哪里结束(i * size + size),并使用 slice 方法提取这个子数组;
      4. 最终,我们得到一个新的数组,它由原数组 list 切分成多个子数组组成,每个子数组的最大长度由 size 参数决定。

counting:统计对象数组中每个唯一标识符的出现次数

  1. 用法说明
    • 参数:目标对象数组、条件函数(内部是传入目标对象,返回对象身上的某一项——根据这项来做统计);
    • 返回值:统计对象。
  2. 基础代码示例
    const skt = [
      {
        name: 'Ra',
        culture: 'egypt'
      },
      {
        name: 'Zeus',
        culture: 'greek'
      },
      {
        name: 'Loki',
        culture: 'greek'
      }
    ]
    
    counting(gods, g => g.culture) // => { egypt: 1, greek: 2 }
    
  3. 源码解析
    // 定义一个泛型函数 `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>) // 初始化累加器为一个空对象。
    }
    
    • 方法工作流程说明
      1. 接收一个数组 list 和一个 identity 函数,后者用于指定如何从数组项中提取唯一标识符;
      2. 如果传入的 list 为空,返回一个空的记录对象;
      3. 使用 reduce 方法遍历数组。reduce 的累加器 acc 是一个对象,其键是通过 identity 函数从数组项中提取的唯一标识符,值是标识符出现的次数;
      4. 在每次迭代中,从当前项 item 中提取唯一标识符 id。如果 acc 中已经存在 id 键,就将其值加1;如果不存在,就将其值设置为1;
      5. 最终,返回这个累加器对象,它是一个记录对象,其键是唯一标识符,值是对应的出现次数。

diff:返回数组1中出现但是没在数组2中出现的项

  1. 用法说明
    • 参数:目标数组1、目标数组2;
    • 返回值:包含符合项的数组。
  2. 基础代码示例
    import { diff } from 'radash'
    
    const oldWorldGods = ['rng', 'uzi']
    const newWorldGods = ['vishnu', 'uzi']
    
    diff(oldWorldGods, newWorldGods) // => ['rng']
    
  3. 源码解析
    // 定义一个泛型函数 `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)])
    }
    
    • 方法工作流程说明:

      1. 检查 rootother 数组是否都为空或未定义,如果是,则返回空数组;
      2. 如果 root 数组为空或未定义,而 other 数组不是,返回 other 数组的副本;
      3. 如果 other 数组为空或未定义,而 root 数组不是,返回 root 数组的副本;
      4. 如果两个数组都不为空,使用 other 数组的元素创建一个记录对象 bKeysidentity 函数用于为每个元素提取唯一标识符,这些标识符作为 bKeys 对象的键,其对应的值被设置为 true
      5. 使用 filter 方法遍历 root 数组,返回那些其通过 identity 函数提取的唯一标识符不在 bKeys 对象中的元素。这些元素构成了 rootother 数组的差异集。

下期我们将介绍以下方法

提示:如果是简单使用的话可以直接按照介绍选择合适的方法进行使用,我们后续会详细介绍。

  1. first:获取数组第一项,不存在返回默认值;
  2. flat:数组扁平化 —— 把多维数组转为一维数组;
  3. fork:按条件将数组拆分成两个数组,满足条件的一个,不满足条件的一个;
  4. group:根据条件函数指定的唯一标识符出现次数对数组进行排序;
  5. intersects:判断两个数组是否有公共项,返回一个布尔值。

写在后面

后续作者会整理一份方法目录上传,方便没法访问外网的同学对照查看使用。

大家有任何问题或者见解,欢迎评论区留言交流!!!

点击访问:Radash官网

参考文章:Lodash is dead. Long live Radash.

posted @ 2024-03-30 09:37  山里看瓜  阅读(359)  评论(2编辑  收藏  举报