javascript es6:解构剩余参数的用例

javascript es6: use case for destructuring rest parameter

我刚刚在 MDN 中看到一个关于解构其余参数的代码片段,如下所示:

function f(...[a, b, c]) {
  return a + b + c;
}

f(1)          // NaN (b and c are undefined)
f(1, 2, 3)    // 6
f(1, 2, 3, 4) // 6 (the fourth parameter is not destructured)

代码片段在此页面中:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/rest_parameters

虽然剩余参数的常见用例对我来说非常清楚 (function foo(...params){/*code*/}),但我无法想象一个真实世界的用例来使用剩余参数,就像该代码片段中呈现的方式一样。相反,我认为在那种情况下,我应该只使用一个通用函数定义:

function f(a, b, c) {
  return a + b + c;
}

f(1)          // NaN (b and c are undefined)
f(1, 2, 3)    // 6
f(1, 2, 3, 4) // 6 (the fourth parameter is not defined)

你的function f(a, b, c) { … }确实是正确的写法。它与 rest+destructuring 语法之间的唯一区别是 rest 参数不会增加参数的数量,即 f.length == 0.

将数组解构模式作为剩余参数的目标确实没有好的用例。仅仅因为语法允许它并不意味着它在某个地方有用。 MDN 示例可能应该更清楚。

这个例子说明了 rest 和 destructuring 语法足够灵活,甚至可以这样组合。

据了解,目前 TypeScript nor Babel 稳定版本均不支持此语法,主要是因为它没有实际用途。

其实...运算符有两种方式。根据您的用例,它被称为 restspread 。它们都是非常强大的运算符,尤其是对于函数式方法。您可以始终使用传播运算符作为,

var a = [1,2,3],
    b = [4,5,6];
a.push(...b);

这会使 a 一下子变为 [1,2,3,4,5,6]。此时此刻,可以说 .concat() 也可以做到这一点。是的,concat 具有内置的传播功能,但 a.concat(b) 不会影响 a。我刚刚创建 returns 一个新数组。事实上,在适当的函数式语言中,为了纯粹起见,将 a 视为不可变对象是很好的。然而 JS 是一种奇怪的语言。它被认为是函数式的,但同时深深地包含了引用类型。长话短说,如果你想在改变它的同时保持对 a 的引用完整,那么你不能使用 a.concat(b)a.push(...b)。在这里我不得不提到 .push() 并不是完美的设计,因为它 returns 一个愚蠢的长度 属性 完全没用 。它应该返回 a。所以我最终大部分时间都使用像 (a.push(...b),a) 这样的逗号运算符。

好的,除了简单的用例之外,您还可以 ... 进一步扩展以获得更复杂但看起来更酷的实现。比如你可以做一个 Haskellesque 模式匹配来分割数组的头和尾并相应地递归。

这是展开和剩余运算符协同工作以展平任意嵌套数组的有用案例。

var flat = (x,...xs) => x ? [...Array.isArray(x) ? flat(...x) : [x], ...flat(...xs)] : [];

var na = [[1,2],[3,[4,5]],[6,7,[[[8],9]]],10];
    fa = flat(na);
console.log(fa);

假设我们有一个函数 returns 像这样的对象:

function getCustomer(id) {
    return fetch(`http://myapi.com/customer/${id}`);
}

假设我有这样的回复:

{
    "customer": {
        "id": 1234,
        "name": "John Doe",
        "latestBadges": [
            "Platinum Customer",
            "100 Buys",
            "Reviewer"
        ]
    }
}

在更传统的方法中,我可以编写一个函数来显示最新的 3 个徽章,如下所示:

function showLatestBadges(a, b, c) {
    console.log(a, b, c);
}

要使用该功能,我需要:

getCustomer(1234).then((customer) => {
    showLatestBadges(
        customer.latestBadges[0],
        customer.latestBadges[1],
        customer.latestBadges[2]
    );
});

有了这个新的传播运算符,我可以改为这样做:

getCustomer(1234).then((customer) => {
    showLatestBadges(...customer.latestBadges);
});

因此,在函数定义中使用扩展运算符可能看起来有点无用。但是,事实上,它在非常特殊的情况下很有用:

假设我们有一个遗留系统,假设在数百个地方调用 showLatestBadges 函数而不使用扩展运算符,就像过去一样。我们还假设我们正在使用一个 linting 工具来防止未使用的变量,并且我们还假设我们是 运行 一个关心 linting 结果的构建过程,如果 linting 说有什么地方不对,构建失败。 我们还假设对于一些奇怪的业务规则,我们现在只需要显示第一个和第三个徽章。 现在,假设这个函数调用是在遗留系统的数百个地方进行的,而我们没有太多时间来交付这个新业务规则的实现,我们就没有时间为所有这数百个调用重构代码. 因此,我们现在将函数更改为:

function showLatestBadges(a, b, c) {
    console.log(a, c);
}

但现在我们遇到了一个问题:由于未使用 b 变量,构建失败,我们必须在 YESTERDAY 交付此更改!!!我们没有时间重构对该函数的所有数百次调用,我们不能只在所有位置进行简单的查找和替换,因为我们的代码如此混乱,到处都是 eval 和不可预测的行为可能发生。

因此,一种解决方案是:使用扩展运算符更改函数签名,以便构建成功,并在板上创建一个任务来进行重构。 所以,我们可以这样修改函数:

function showLatestBadges(...[a,,c]) {
    console.log(a, c);
}

好的,我知道这是一个非常特殊的情况,而且这不太可能发生,但是,谁知道呢? ¯\_(ツ)_/¯

这是我必须使用它的用例之一

const tail = function([, ...xs]) { return xs; } tail([1,2]); // [2]

const head = ([a]) => a
head([1,2,3,4]) // 1