【问题标题】:Array.reduce() in ES6, implicit returns and syntactic sugar. What is really happening?ES6 中的 Array.reduce(),隐式返回和语法糖。到底发生了什么?
【发布时间】:2019-11-05 22:28:10
【问题描述】:

我有兴趣了解此算法内部发生的情况。让我们考虑以下数据集:

const data = [
  {
    emp_code: "a001",
    company_code: "company_a",
    name: "abx",
    details: [],
    details_dtypes: []
  },
  {
    emp_code: "b002",
    company_code: "company_b",
    name: "xbz ",
    details: [],
    details_dtypes: []
  },
  {
    emp_code: "a002",
    company_code: "company_a",
    name: "xbz ",
    details: [],
    details_dtypes: []
  },
  {
    emp_code: "b003",
    company_code: "company_b",
    name: "xbz ",
    details: [],
    details_dtypes: []
  }
];

现在,如果我想将此数据压缩为 object {},其中每个键都是唯一的 company_code,其对应的值是该公司的 array [] 或 emp_codes,我可以执行以下操作:

let result = data.reduce((r, c) => {
  r[c.company_code] = [...(r[c.company_code] || []), c.emp_code];
  return r;
}, {});

在上面,我们显式返回了最终的对象,看起来很清楚。

现在,我最近发现你可以用一些语法糖来缩短它,如下所示:

let result = data.reduce(
  (r, c) =>
    (r[c.company_code] = [...(r[c.company_code] || []), c.emp_code]) && r,
  {}
);

结果是相同的,但我不确定这怎么可能。我对reduce() 的理解是,有一个内部循环遍历每个项目,并允许您应用某些逻辑来制定最终结果。 我看到他们正在使用&& 运算符。我的假设是括号 () 之间的逻辑像传统的 reduce() 方法一样执行,完成后,我们只需返回结果,因此 && r。

但这似乎有些不对劲。内循环还在发生吗?如果是他们如何能够在第一次迭代后不简单地执行条件而将其隔离,因此返回r。他们是否为循环的每次迭代返回r?

这是一个显示两种算法都有效的沙盒:https://codesandbox.io/s/hooks-with-reduce-nested-data-goee1。非常感谢您的贡献:)

【问题讨论】:

  • 两个代码是相同的,在第二个代码中,对于每个唯一的 company_code,您要添加所有 emp_code 并返回对象 r。
  • 当执行foo && bar之类的东西时,会返回最后一个被解释的值。
  • 有些人为此使用逗号:reduce (acc, x) => do_stuff, acc,以便在任何情况下都返回累加器。
  • @georg 和, 你需要在(acc, x) => (do_stuff, acc) 周围加上括号,否则, acc 将被解释为reduce 的第二个参数

标签: javascript arrays ecmascript-6


【解决方案1】:

对于&&,如果所涉及的所有表达式都是真值,它会计算出最终表达式的值——&& 之后的值。所以,如果你有这样的代码:

function foo() {
  <some expression>;
  return bar;
}

你也可以这样写

function foo() {
  return <some expression> && bar;
}

或者,隐式返回箭头函数:

const foo = () => <some expression> && bar;

相当于

const foo = () => {
  <some expression>;
  return bar;
};

所以是的,对于您的代码,&amp;&amp; r 在箭头函数的末尾,这意味着 r 在每次迭代中都会隐式返回。

说了这么多,这几乎是语法糖的对立面。如果您通过 minifier 运行代码,您经常会看到这种情况,其目标是尽可能减少 Javascript 中的文本量。但是缩小的代码通常很难阅读,正如您的问题所示,这也不例外。如果使用隐式return 需要逗号操作符或利用&amp;&amp; 来评估other而不是布尔值,则代码可能过于混乱而难以阅读,而显式return 可能更好选择。

您还可以考虑推送到累加器对象中的现有数组,从而避免创建许多仅在以后传播的不必要的中间数组:

let result = data.reduce((r, c) => {
  if (!r[c.company_code]) {
    r[c.company_code] = [];
  }
  r[c.company_code].push(c.emp_code);
  return r;
}, {});

这是我更喜欢的代码。它有点长,但是当每个语句/逻辑分支都在自己的行上时,它很多更容易阅读。

【讨论】:

  • 谢谢,这就是我认为在第二个代码块中发生的事情,我不知道为什么用户会选择在每次迭代后重复返回累加器。与仅在最后返回最终结果相比,它的性能似乎低于标准。我只是惊讶地看到为什么第二个块甚至是一件事,也许除了代码更少之外,我没有看到这样做的好处。
  • " 我不知道为什么用户会选择在每次迭代后重复返回累加器。" 因为这就是 reduce 的工作原理。第一次迭代的返回值是作为累加器传递给第二次迭代的值,依此类推。如果您在第一次迭代中没有返回值,那么您在第二次迭代中将 undefined 作为累加器。
  • 无论如何,整件事都是对reduce的滥用。它只是用于迭代数组的副作用。您可以使用filter 或map 或forEach 编写相同的代码。
  • @melpomene 不是真的,使用其他方法来迭代值将需要一个外部变量,reduce,所有内容都包含在内。
  • @Titus 请记住,const 只是防止重新分配,而不是突变。可以做const result = {}; data.forEach( &lt; mutate result &gt; )。更少的外部突变只是看起来更优雅,IMO
【解决方案2】:

这里的问题是简洁。当您过多地减少代码时,您会失去可读性。这就是问题所在。让我们扭转这个过程,让您的代码更传统。

let result = data.reduce(
  (r, c) => (r[c.company_code] = [...(r[c.company_code] || []), c.emp_code]) && r,
  {}
);

第 1、3 和 4 行相当简单。棘手的部分是第 2 行。

(r, c) => (r[c.company_code] = [...(r[c.company_code] || []), c.emp_code]) && r,

这相当于:

function processData(accumulator, currentItem) {
  if ( accumulator[currentItem.compony_code] === undefined ) {
    accumulator[currentItem.compony_code] = [];
  }

  accumulator[currentItem.compony_code].push(currentItem.emp_code);
  return accumulator;
}

现在开始您的假设,关于 (),它是一种创建表达式并允许您执行非表达式(在本例中为赋值)任务的 hack。

【讨论】:

    【解决方案3】:

    让我们反汇编这一行:

    (r[c.company_code] = [...(r[c.company_code] || []), c.emp_code]) && r
    

    JS always return assigned element 中的赋值和逻辑与运算符 (&amp;&amp;) 遵循规则:

    expr1 && expr2
    

    如果 expr1 可以转换为 true,则返回 expr2;否则,返回expr1。

    所以基本上是一样的

    if(r[c.company_code] = [...(r[c.company_code] || []), c.emp_code])
      return r
    

    并且由于 JS 中的数组 ([...(r[c.company_code] || []), c.emp_code]) 在解析为布尔值时会转换为 true(顺便说一下,与对象、函数和符号相同),它总是返回 r。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多