10 個(gè)值得掌握的 reduce 技巧

作為一個(gè)前端開發(fā)者,一定有接觸過 reduce 函數(shù),它是一個(gè)強(qiáng)大而實(shí)用的數(shù)組方法,熟練掌握 reduce 的使用可以在開發(fā)中提高開發(fā)效率和代碼質(zhì)量。本文介紹的 reduce 的 10 個(gè)技巧值得擁有,可以讓你少寫很多代碼!

reduce 方法在數(shù)組的每個(gè)元素上執(zhí)行提供的回調(diào)函數(shù)迭代器。它傳入前一個(gè)元素計(jì)算的返回值,結(jié)果是單個(gè)值,它是在數(shù)組的所有元素上運(yùn)行迭代器的結(jié)果。

迭代器函數(shù)逐個(gè)遍歷數(shù)組的元素,在每一步中,迭代器函數(shù)將當(dāng)前數(shù)組值添加到上一步的結(jié)果中,直到?jīng)]有更多元素要添加。

語法

參數(shù)包含回調(diào)函數(shù)和可選的初始值,如下:

array.reduce(callback(accumulator, currentValue[, index[, array]])[, initialValue])

  • callback(必須):執(zhí)行數(shù)組中每個(gè)值(如果沒有提供 initialValue 則第一個(gè)值除外)的 reducer 函數(shù),包含四個(gè)參數(shù)

    • accumulator(必須):累計(jì)器累計(jì)回調(diào)的返回值; 它是上一次調(diào)用回調(diào)時(shí)返回的累積值,初始值可以通過initialValue定義,默認(rèn)為數(shù)組的第一個(gè)元素值,累加器將保留上一個(gè)操作的值,就像靜態(tài)變量一樣

    • currentValue(必須):數(shù)組中正在處理的元素

    • index(可選):數(shù)組中正在處理的當(dāng)前元素的索引。 如果提供了 initialValue,則起始索引號(hào)為 0,否則從索引 1 起始。

      注意:如果沒有提供 initialValue,reduce 會(huì)從索引 1 的地方開始執(zhí)行 callback 方法,跳過第一個(gè)索引。如果提供 initialValue,從索引 0 開始。

    • array(可選):調(diào)用 reduce() 的數(shù)組

  • initialValue(可選):作為第一次調(diào)用 callback 函數(shù)時(shí)的第一個(gè)參數(shù)的值。 如果沒有提供初始值,則將使用數(shù)組中的第一個(gè)元素。 在沒有初始值的空數(shù)組上調(diào)用 reduce 將報(bào)錯(cuò)

1. 計(jì)算數(shù)組的最大值和最小值

有很多種方式可以獲取數(shù)組的最大值或最小值?

使用 Math.max 和 Math.min

使用 Math 的 API 是最簡(jiǎn)單的方式。

const arrayNumbers = [-1, 10, 6, 5, -3];
const max = Math.max(...arrayNumbers); // 10
const min = Math.min(...arrayNumbers); // -3
console.log(`max=${max}`); // max=10
console.log(`min=${min}`); // min=-3

使用 reduce

一行代碼,就可以實(shí)現(xiàn)與 Math 的 API 相同的效果。

const arrayNumbers = [-1, 10, 6, 5, -3];
const getMax = (array) => array.reduce((max, num) => (max > num ? max : num));
const getMin = (array) => array.reduce((max, num) => (max < num ? max : num));

const max = getMax(arrayNumbers); // 10
const min = getMin(arrayNumbers); // -3
console.log(`max=${max}`); // max=10
console.log(`min=${min}`); // min=-3

或者寫成一個(gè)函數(shù):

const arrayNumbers = [-1, 10, 6, 5, -3];

const getMaxOrMin = (array, type = "min") =>
    type === "max"
        ? array.reduce((max, num) => (max > num ? max : num))
        : array.reduce((max, num) => (max < num ? max : num));

const max = getMaxOrMin(arrayNumbers, "max"); // 10
const min = getMaxOrMin(arrayNumbers, "min"); // -3
console.log(`max=${max}`); // max=10
console.log(`min=${min}`); // min=-3

2. 數(shù)組求和和累加器

使用 reduce ,可以輕松實(shí)現(xiàn)多個(gè)數(shù)相加或累加的功能。

// 數(shù)組求和
const sum = (...nums) => {
    return nums.reduce((sum, num) => sum + num);
};

// 累加器
const accumulator = (...nums) => {
    return nums.reduce((acc, num) => acc * num);
};
const arrayNumbers = [1, 3, 5];

console.log(accumulator(1, 2, 3)); // 6
console.log(accumulator(...arrayNumbers)); // 15

console.log(sum(1, 2, 3, 4, 5)); // 15
console.log(sum(...arrayNumbers)); // 9

3. 格式化搜索參數(shù)

獲取 URL 種的搜索參數(shù)是經(jīng)常要處理的功能。

// url http://www.46649.cn/index.shtml?name=devpoint&id=100
// 格式化 search parameters
{
    name: "devpoint",
    id: "100",
}

常規(guī)方式

這是大多數(shù)人使用它的方式。

const parseQuery = (search = window.location.search) => {
    const query = {};
    search
        .slice(1)
        .split("&")
        .forEach((it) => {
            const [key, value] = it.split("=");
            query[key] = decodeURIComponent(value);
        });
    return query;
};
console.log(parseQuery("?name=devpoint&id=100")); // { name: 'devpoint', id: '100' }

使用 reduce

const parseQuery = (search = window.location.search) =>
    search
        .replace(/(^\?)|(&$)/g, "")
        .split("&")
        .reduce((query, it) => {
            const [key, value] = it.split("=");
            query[key] = decodeURIComponent(value);
            return query;
        }, {});

console.log(parseQuery("?name=devpoint&id=100")); // { name: 'devpoint', id: '100' }

4. 反序列化搜索參數(shù)

當(dāng)要跳轉(zhuǎn)到某個(gè)鏈接并為其添加一些搜索參數(shù)時(shí),手動(dòng)拼接的方式不是很方便。如果要串聯(lián)的參數(shù)很多,那將是一場(chǎng)災(zāi)難。

const searchObj = {
    name: "devpoint",
    id: 100,
    // ...
};
const strLink = `http://www.46649.cn/index.shtml?name=${searchObj.name}&age=${searchObj.id}`;
console.log(strLink); // http://www.46649.cn/index.shtml?name=devpoint&age=100

reduce 可以輕松解決這個(gè)問題。

const searchObj = {
    name: "devpoint",
    id: 100,
    // ...
};
const stringifySearch = (search = {}) =>
    Object.entries(search)
        .reduce(
            (t, v) => `${t}${v[0]}=${encodeURIComponent(v[1])}&`,
            Object.keys(search).length ? "?" : ""
        )
        .replace(/&$/, "");

const strLink = `http://www.46649.cn/index.shtml${stringifySearch(
    searchObj
)}`;
console.log(strLink); // http://www.46649.cn/index.shtml?name=devpoint&age=100

5. 展平多層嵌套數(shù)組

如何展平多層嵌套數(shù)組嗎?

const array = [1, [2, [3, [4, [5]]]]];
const flatArray = array.flat(Infinity);

console.log(flatArray); // [ 1, 2, 3, 4, 5 ]

如果運(yùn)行環(huán)境支持方法 flat ,則可以直接用,如果不支持,使用 reduce 也可以實(shí)現(xiàn)和flat一樣的功能。

const array = [1, [2, [3, [4, [5]]]]];

const flat = (arrayNumbers) =>
    arrayNumbers.reduce(
        (acc, it) => acc.concat(Array.isArray(it) ? flat(it) : it),
        []
    );
const flatArray = flat(array);

console.log(flatArray); // [ 1, 2, 3, 4, 5 ]

6. 計(jì)算數(shù)組成員的數(shù)量

如何計(jì)算數(shù)組中每個(gè)成員的個(gè)數(shù)?即計(jì)算重復(fù)元素的個(gè)數(shù)。

const count = (array) =>
    array.reduce(
        (acc, it) => (acc.set(it, (acc.get(it) || 0) + 1), acc),
        new Map()
    );
const array = [1, 2, 1, 2, -1, 0, "0", 10, "10"];
console.log(count(array));

這里使用了數(shù)據(jù)類型 Map ,關(guān)于 JavaScript 的這個(gè)數(shù)據(jù)類型,有興趣可以閱讀下文:

上面代碼的輸出結(jié)果如下:

Map(7) {
  1 => 2,
  2 => 2,
  -1 => 1,
  0 => 1,
  '0' => 1,
  10 => 1,
  '10' => 1
}

7.獲取一個(gè)對(duì)象的多個(gè)屬性

這是一個(gè)項(xiàng)目開發(fā)中比較常遇見的場(chǎng)景。通過 API 獲取后端數(shù)據(jù),前端很多時(shí)候只需要取其中部分的數(shù)據(jù)。

// 一個(gè)有很多屬性的對(duì)象
const obj = {
    a: 1,
    b: 2,
    c: 3,
    d: 4,
    e: 5,
    // ...
};
// 只是想得到它上面的一些屬性來創(chuàng)建一個(gè)新的對(duì)象
const newObj = {
    a: obj.a,
    b: obj.b,
    c: obj.c,
    d: obj.d,
    // ...
};

這個(gè)時(shí)候可以使用 reduce 來解決。

/**
 *
 * @param {*} obj 原始對(duì)象
 * @param {*} keys 需要獲取的屬性值列表,數(shù)組形式
 * @returns
 */
const getObjectKeys = (obj = {}, keys = []) =>
    Object.keys(obj).reduce(
        (acc, key) => (keys.includes(key) && (acc[key] = obj[key]), acc),
        {}
    );

const obj = {
    a: 1,
    b: 2,
    c: 3,
    d: 4,
    e: 5,
    // ...
};
const newObj = getObjectKeys(obj, ["a", "b", "c", "d"]);
console.log(newObj); // { a: 1, b: 2, c: 3, d: 4 }

8.反轉(zhuǎn)字符串

反轉(zhuǎn)字符串是面試中最常問到的 JavaScript 問題之一。

const reverseString = (string) => {
    return string.split("").reduceRight((acc, s) => acc + s);
};
const string = "devpoint";
console.log(reverseString(string)); // tniopved

9.數(shù)組去重

reduce 也很容易實(shí)現(xiàn)數(shù)組去重。

const array = [1, 2, 1, 2, -1, 10, 11];
const uniqueArray1 = [...new Set(array)];
const uniqueArray2 = array.reduce(
    (acc, it) => (acc.includes(it) ? acc : [...acc, it]),
    []
);

console.log(uniqueArray1); // [ 1, 2, -1, 10, 11 ]
console.log(uniqueArray2); // [ 1, 2, -1, 10, 11 ]

10. 模擬方法 flat

雖然現(xiàn)在的JavaScript有原生方法已經(jīng)實(shí)現(xiàn)了對(duì)深度嵌套數(shù)組進(jìn)行扁平化的功能,但是如何才能完整的實(shí)現(xiàn)扁平化的功能呢?下面就是使用 reduce 來實(shí)現(xiàn)其功能:

// 默認(rèn)展開一層
Array.prototype.flat2 = function (n = 1) {
    const len = this.length;
    let count = 0;
    let current = this;
    if (!len || n === 0) {
        return current;
    }
    // 確認(rèn)當(dāng)前是否有數(shù)組項(xiàng)
    const hasArray = () => current.some((it) => Array.isArray(it));
    // 每次循環(huán)后展開一層
    while (count++ < n && hasArray()) {
        current = current.reduce((result, it) => result.concat(it), []);
    }
    return current;
};
const array = [1, [2, [3, [4, [5]]]]];
// 展開一層
console.log(array.flat()); // [ 1, 2, [ 3, [ 4, [ 5 ] ] ] ]
console.log(array.flat2()); // [ 1, 2, [ 3, [ 4, [ 5 ] ] ] ]
// 展開所有
console.log(array.flat(Infinity)); // [ 1, 2, 3, 4, 5 ]
console.log(array.flat2(Infinity)); // [ 1, 2, 3, 4, 5 ]

 

reducejavascript