lodash已死?radash最全使用介紹(附原始碼詳細說明)—— Array方法篇(1)

勤劳的墨菲特發表於2024-03-30
  • 相信很多前端同學甚至非前端都或多或少使用過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.

相關文章