溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

怎么在前端使用JS進(jìn)行分類

發(fā)布時(shí)間:2023-04-04 16:45:09 來源:億速云 閱讀:225 作者:iii 欄目:開發(fā)技術(shù)

今天小編給大家分享一下怎么在前端使用JS進(jìn)行分類的相關(guān)知識(shí)點(diǎn),內(nèi)容詳細(xì),邏輯清晰,相信大部分人都還太了解這方面的知識(shí),所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來了解一下吧。

    提出問題

    不扯遠(yuǎn)了,先來看問題。根據(jù)下面的樣例數(shù)據(jù),要求得到

    • 先按業(yè)務(wù),再按部門分組的數(shù)據(jù);

    • 不按部門,直接按業(yè)務(wù)分別統(tǒng)計(jì)每年的數(shù)據(jù)

    [
      {
        name: "部門1",
        businesses: [
          {
            name: "產(chǎn)品銷售",
            years: [
              { name: "2021", value: 132 }, { name: "2022", value: 183 }, { name: "2023", value: 207 }
            ]
          },
          {
            name: "原料采購",
            years: [
              { name: "2021", value: 143 }, { name: "2022", value: 121 }, { name: "2023", value: 120 }
            ]
          }
        ]
      },
      {
        name: "部門2",
        businesses: [
          {
            name: "產(chǎn)品銷售",
            years: [
              { name: "2021", value: 230 }, { name: "2022", value: 112 }, { name: "2023", value: 288 }
            ]
          },
          {
            name: "原料采購",
            years: [
              { name: "2021", value: 168 }, { name: "2022", value: 203 }, { name: "2023", value: 115 }
            ]
          }
        ]
      },
      {
        name: "部門3",
        businesses: [
          {
            name: "產(chǎn)品銷售",
            years: [
              { name: "2021", value: 279 }, { name: "2022", value: 163 }, { name: "2023", value: 271 }
            ]
          },
          {
            name: "原料采購",
            years: [
              { name: "2021", value: 129 }, { name: "2022", value: 121 }, { name: "2023", value: 226 }
            ]
          }
        ]
      }
    ];

    這個(gè)數(shù)據(jù),如果用金山文檔的輕維表(飛書多維表類似)來查看,會(huì)更直觀

    原數(shù)據(jù)(按部門再按業(yè)務(wù))的輕維表呈現(xiàn)

    怎么在前端使用JS進(jìn)行分類

    按業(yè)務(wù)再按部門分組的輕維表呈現(xiàn)

    怎么在前端使用JS進(jìn)行分類

    按業(yè)務(wù)按年統(tǒng)計(jì)的輕維表呈現(xiàn)

    怎么在前端使用JS進(jìn)行分類

    展平多級(jí)數(shù)據(jù)

    原數(shù)據(jù)按部門再按業(yè)務(wù)進(jìn)行了兩級(jí)分類,所以它不是簡(jiǎn)單的二維表(行/列)數(shù)據(jù),而是在二維表的基礎(chǔ)上增加了兩個(gè)維度(部門/業(yè)務(wù))。從要求來看,我們需要的是從另外的維度(業(yè)務(wù)/部門,業(yè)務(wù)/年度)來進(jìn)行處理。所以需要先把這些數(shù)據(jù)降維展開成可以重新劃分維度的程度,也就是二維表。

    JS 中二維表的表示方法挺多,行對(duì)象集合是最常見的一種,這里我們也就采用這種表示方法。

    還有一種常見的方式是列集合+行集合,其中行集合可以是對(duì)象表示(字段名對(duì)應(yīng))也可以是數(shù)組表示(索引號(hào)對(duì)應(yīng))。不過這種表示一會(huì)是用在 UI 中。單純數(shù)據(jù)處理用行對(duì)象集合就夠了,不需要單獨(dú)的列信息。

    觀察原數(shù)據(jù)的每一級(jí),發(fā)現(xiàn)名稱都命名為 name,但是子集命名各不相同,層級(jí)有限。由于對(duì)每一層需要去處理名稱到列(對(duì)象屬性名)的轉(zhuǎn)換,也需要對(duì)不同名稱的子集進(jìn)行進(jìn)一步處理,各層級(jí)之間缺乏顯而易見的共性,不太適合遞歸的方式來處理。所以我們定做一個(gè)展開函數(shù)。

    下面是對(duì)原數(shù)據(jù)量身定做的展開函數(shù),展開后會(huì)得到一個(gè)包含部門 (dept)、業(yè)務(wù) (business)、年份 (year)、數(shù)值 (value) 四個(gè)屬性的對(duì)象集合。

    function flatBusinesses(list) {
        return list.flatMap(({ name: dept, businesses }) => {
            return businesses.flatMap(({ name: business, years }) => {
                return years.map(({ name: year, value }) => ({
                    dept,
                    business,
                    year,
                    value
                }));
            });
        });
    }


    晉級(jí):如果想用遞歸該怎么處理?

    并不是多級(jí)展開就一定會(huì)用到遞歸。比如規(guī)則的數(shù)組結(jié)構(gòu),比如規(guī)則的樹結(jié)構(gòu),是可以使用遞歸遍歷展開的。但是像這個(gè)案例的數(shù)據(jù),每一層的子級(jí)屬性名稱都不同,層級(jí)有限,需要逐級(jí)處理。

    如果實(shí)在想用遞歸的話,也可以通過一個(gè)參數(shù)來定義每一級(jí)的處理規(guī)則。以這個(gè)例子來說,每一級(jí)要處理兩件事:① 找到子級(jí)節(jié)點(diǎn)屬性名;② 將 name 處理成適當(dāng)?shù)拿Q用在展開的數(shù)據(jù)中。

    function flatMultiLevelList(list, rules) {
        return flatList(list, 0);
    
        function flatList(list, level) {
            const rule = rules[level];
            if (!rule) { return [{}]; }
            // 取得 field(子級(jí)屬性名)和 convert(屬性處理器)
            // 如果沒有 convert 則指定一個(gè)默認(rèn)的 it => it,即不做轉(zhuǎn)換
            const { field, convert = it => it } = rule;
            if (field) {
                // 如果存在子級(jí),則繼續(xù) flatMap,展平。
                // ? { fff, ...others } 可以將 fff 屬性從原對(duì)象中剝離出來
                // ? { [feild]: nodes } 解構(gòu)可以將 field 的值所指向的屬性取出來賦予一個(gè)叫 nodes 的變量
                return list.flatMap(({ [field]: nodes, ...props }) => {
                    return flatList(nodes, level + 1).map(it => ({ ...convert(props), ...it }));
                });
            } else {
                // 如果不存在子級(jí),只需要對(duì)當(dāng)前節(jié)點(diǎn)進(jìn)行轉(zhuǎn)換,直接返回即可
                return list.map(it => convert(it));
            }
        }
    }

    展開后會(huì)拿到這樣的數(shù)據(jù)(假設(shè)賦值變量 table

    [
        { "dept": "部門1", "business": "產(chǎn)品銷售", "year": 2021, "value": 132 },
        { "dept": "部門1", "business": "產(chǎn)品銷售", "year": 2022, "value": 183 },
        { "dept": "部門1", "business": "產(chǎn)品銷售", "year": 2023, "value": 207 },
        { "dept": "部門1", "business": "原料采購", "year": 2021, "value": 143 },
        { "dept": "部門1", "business": "原料采購", "year": 2022, "value": 121 },
        { "dept": "部門1", "business": "原料采購", "year": 2023, "value": 120 },
        { "dept": "部門2", "business": "產(chǎn)品銷售", "year": 2021, "value": 230 },
        { "dept": "部門2", "business": "產(chǎn)品銷售", "year": 2022, "value": 112 },
        ...
    ]

    拿到二維表之后,某些需要的數(shù)據(jù)或視圖就可以通過電子表格來獲得。比如問題一中需要的統(tǒng)計(jì)數(shù)據(jù),使用電子表格的透視圖功能就能實(shí)現(xiàn),而金山文檔的輕維表,或者飛書的多維表可以實(shí)現(xiàn)得更容易。不過我們現(xiàn)在需要用代碼來實(shí)現(xiàn)。

    分類及分類匯總

    第一個(gè)問題的需求是分類和分類匯總。說到分類,那首先想到的肯定是 group 操作。很可惜原生 JS 不支持 group,如果想用現(xiàn)成的,可以考慮 Lodash,要自己寫一個(gè)倒也不難。group 操作前面提到的展開操作的逆操作。

    function groupBy(list, key) {
        // 這里簡(jiǎn)單地兼容一下傳入 key 值和 keyGetter 的情況
        const getKey = typeof key === "function" ? key : it => it[key];
        return list.reduce(
            (groups, it) => {
                (groups[getKey(it)] ??= []).push(it);
                return groups;
            },
            {}  // 空對(duì)象作為初始 groups
        );
    }

    按業(yè)務(wù)再按部門分組

    有了 groupBy,可以先按業(yè)務(wù)進(jìn)行分組

    // 前面假設(shè)展平的數(shù)據(jù)存放在變量 table 中
    const groups = groupBy(table, "dept");

    現(xiàn)在我們拿到的 byDept 是一個(gè) JS 對(duì)象(注意不是數(shù)組哦),其鍵是部門名稱,值是一個(gè)數(shù)組,包含該部門下的所有數(shù)據(jù)。接下來進(jìn)行第二層分組,是需要對(duì) byDept 的每一個(gè)“值”進(jìn)行分組處理。

    for (const key in groups) {
        const list = groups[key];
        groups[key] = groupBy(list, "business");
    }

    處理之后的 groups 長得像這樣

    {
        "產(chǎn)品銷售": {
            "部門1": [
                { dept: "部門1", business: "產(chǎn)品銷售", year: "2021", value: 132 },
                ...
            ],
            "部門2": [
                { dept: "部門2", business: "產(chǎn)品銷售", year: "2021", value: 230 },
                ...
            ],
            "部門3": ...
        },
        "原料采購": ...
    }

    結(jié)果是拿到了,但是和符合原始的數(shù)據(jù)規(guī)范(原始層級(jí)每層是用 name 屬性作為字段名,子級(jí)命名各不相同)所以還需要做一次轉(zhuǎn)換。比如第一層的轉(zhuǎn)換是這樣:

    const converted = Object.entries(groups)
        .map(([name, depts]) => ({ name, depts }));

    它會(huì)把第一層(對(duì)象)處理成數(shù)組,每個(gè)元素包含 name 和 depts 兩個(gè)屬性,name 屬性是名稱,depts 則是按部門分組的結(jié)果(目前還是對(duì)象)。那么第二、三層轉(zhuǎn)換也類似。把前面的分組和后面的轉(zhuǎn)換合并起來,是這樣

    const result1 = Object.entries(groupBy(table, "business"))
        .map(([name, list]) => ({
            name,
            depts: Object.entries(groupBy(list, "dept"))
                .map(([name, list]) => ({
                    name,
                    years: list.map(({ year: name, value }) => ({ name, value }))
                }))
        }));

    得到最終結(jié)果

    [
      {
        name: "產(chǎn)品銷售",
        depts: [
          {
            name: "部門1",
            years: [{ name: "2021", value: 132 }, { name: "2022", value: 183 }, { name: "2023", value: 207 }]
          },
          {
            name: "部門2",
            years: [{ name: "2021", value: 230 }, { name: "2022", value: 112 }, { name: "2023", value: 288 }]
          },
          {
            name: "部門3",
            years: [{ name: "2021", value: 279 }, { name: "2022", value: 163 }, { name: "2023", value: 271 }]
          }
        ]
      },
      ...
    ]

    按業(yè)務(wù)分組再按年統(tǒng)計(jì)

    對(duì)于第一個(gè)問題的第二個(gè)需求,要按年統(tǒng)計(jì)業(yè)務(wù)(忽略部門),處理方法與上面的方法類型。第二層分組改為按年份,而不是按部門;同時(shí)第二層的數(shù)組轉(zhuǎn)換時(shí)不再轉(zhuǎn)換第三層的數(shù)據(jù),而是對(duì)第三層數(shù)據(jù)進(jìn)行匯總。

    const result2 = Object.entries(groupBy(table, "business"))
        .map(([name, list]) => ({
            name,
            years: Object.entries(groupBy(list, "year"))
    //      ^^^^^                               ^^^^^^ 按年分組
                .map(([name, list]) => ({
                    name,
                    value: list.reduce((sum, { value }) => sum + value, 0)
    //              ^^^^^ 直接取值,使用 reduce 匯總
                }))
        }));

    結(jié)果(用前面做的輕維表統(tǒng)計(jì)來核對(duì)一下,完全正確)

    [
      {
        name: "產(chǎn)品銷售",
        years: [{ name: "2021", value: 641 }, { name: "2022", value: 458 }, { name: "2023", value: 766 }]
      },
      {
        name: "原料采購",
        years: [{ name: "2021", value: 440 }, { name: "2022", value: 445 }, { name: "2023", value: 461 }]
      }
    ]

    如果用 Lodash 會(huì)怎么寫

    用 Lodash 來處理代碼結(jié)構(gòu)看起來更清晰一些,但代碼量不見得少。

    展開的部分用 Lodash 和使用原生方法沒什么區(qū)別,都是使用 flatMap。Lodash 提供的 flatMapDeep 可以用來展開純粹的多級(jí)數(shù)組,但在這里不適用,因?yàn)槊恳患?jí)都不是單純的展開,而是要進(jìn)行單獨(dú)的映射處理。Lodash 的 flatMapDeep 更像是原生的 map().flat(Number.MAX_SAFE_INTEGER)

    const result1 = _(table)
        // groupBy 的結(jié)果是一個(gè)對(duì)象,屬性名是組名,屬性值是組內(nèi)數(shù)據(jù)列表。
        .groupBy("business")
        // 第一種處理值集的方法,先把值處理了 (mapValues),再來處理鍵值對(duì) (map)
        .mapValues(depts => _(depts)
            .groupBy("dept")
            // 第二種處理值集的方法,處理鍵值對(duì)的時(shí)候,同時(shí)處理值集合
            .map((values, name) => ({
                name,
                years: values.map(({ year: name, value }) => ({ name, value }))
            }))
            .value()
        )
        .map((depts, name) => ({ name, depts }))
        .value();
    const result2 = _(table).groupBy("business")
        .map((list, name) => ({
            name,
            years: _(list).groupBy("year")
                .map((list, name) => ({
                    name,
                    value: _.sumBy(list, "value")
                }))
                .value()
        }))
        .value();

    小結(jié)

    如果需要對(duì)某個(gè)數(shù)據(jù)進(jìn)行分類或者分類匯總,首先得拿到這個(gè)數(shù)據(jù)的二維表,也就是完全展開的數(shù)據(jù)列表。多數(shù)情況下從后端拿到的數(shù)據(jù)都是二維表,畢竟關(guān)系型數(shù)據(jù)庫邏輯結(jié)構(gòu)是表存儲(chǔ)。接下來所謂的“分類”其實(shí)就是分組操作,而“匯總”就是把分類后的子列表拿來進(jìn)行聚合計(jì)算(計(jì)數(shù)、合計(jì)、平均、最大/小等都是聚合計(jì)算),得到最終的結(jié)果。

    以上就是“怎么在前端使用JS進(jìn)行分類”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家閱讀完這篇文章都有很大的收獲,小編每天都會(huì)為大家更新不同的知識(shí),如果還想學(xué)習(xí)更多的知識(shí),請(qǐng)關(guān)注億速云行業(yè)資訊頻道。

    向AI問一下細(xì)節(jié)

    免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

    js
    AI