JavaScript 的 Reduce 是如何赋值的?

How is JavaScript's Reduce assigning its value?

问题: 我不明白 reduce 是如何 assigning/reducing 数组中的客户名称。我需要有人来准确解释这里发生的事情。

详细说明

Fun Fun Function 的函数式编程系列 (https://www.youtube.com/watch?v=1DMolJ2FrNY) 的第 4 集中,Mattias Petter Johansson 介绍了一个加载简单数据集的示例客户及其订单,并通过 reduce 函数将该数据转换为分层对象。

这是示例制表符分隔的数据集——我在本地将其命名为 data.txt

mark johannson  waffle iron 80  2
mark johannson  blender 200 1
mark johannson  knife   10  4
Nikita Smith    waffle iron 80  1
Nikita Smith    knife   10  2
Nikita Smith    pot 20  3

这是加载和处理数据集的 JavaScript 文件——我在本地将其命名为 reduce.js

var fs = require("fs");

var output = fs
    .readFileSync("data.txt", "utf-8")
    .trim()
    // NOTE: On Windows, I needed to use \r\n. You may only need \r or \n.
    .split("\r\n")
    .map(line => line.split("\t"))
    .reduce((customers, line) => {
        customers[line[0]] = customers[line[0]] || [];
        customers[line[0]].push({
            name: line[1],
            price: line[2],
            quantity: line[3]
        });
        return customers;
    }, {});

console.log("output", JSON.stringify(output, null, 2));

最后,这是我用来通过 NodeJS.

执行此 js 文件的简单命令
node reduce.js

(或者,如果您想在一个地方看到所有的代码和数据,我在这里包含了一个 jsFiddle:https://jsfiddle.net/anthonygatlin/27vpoa13/

代码执行的输出类似于

{
  "mark johannson": [
    {
      "name": "waffle iron",
      "price": "80",
      "quantity": "2"
    },
    {
      "name": "blender",
      "price": "200",
      "quantity": "1"
    },
    {
      "name": "knife",
      "price": "10",
      "quantity": "4"
    }
  ],
  "Nikita Smith": [
    {
      "name": "waffle iron",
      "price": "80",
      "quantity": "1"
    },
    {
      "name": "knife",
      "price": "10",
      "quantity": "2"
    },
    {
      "name": "pot",
      "price": "20",
      "quantity": "3"
    }
  ]
}

让我们快速浏览一下代码中发生的事情。

utf-8 转换之前...

<Buffer 6d 61 72 6b 20 6a 6f 68 61 6e 6e 73 6f 6e 09 77 61 66 66 6c 65 20 69 72 6f 6e 09 38 30 09 32 0d 0a 6d 61 72 6b 20 6a 6f 68 61 6e 6e 73 6f 6e
09 62 6c ... >

utf-8 转换后...

mark johannson  waffle iron     80      2
mark johannson  blender 200     1
mark johannson  knife   10      4
Nikita Smith    waffle iron     80      1
Nikita Smith    knife   10      2
Nikita Smith    pot     20      3

.split("\r\n")

之后
[ 'mark johannson\twaffle iron\t80\t2',
  'mark johannson\tblender\t200\t1',
  'mark johannson\tknife\t10\t4',
  'Nikita Smith\twaffle iron\t80\t1',
  'Nikita Smith\tknife\t10\t2',
  'Nikita Smith\tpot\t20\t3' ]

.map(line => line.split("\t"))

之后
[ [ 'mark johannson', 'waffle iron', '80', '2' ],
  [ 'mark johannson', 'blender', '200', '1' ],
  [ 'mark johannson', 'knife', '10', '4' ],
  [ 'Nikita Smith', 'waffle iron', '80', '1' ],
  [ 'Nikita Smith', 'knife', '10', '2' ],
  [ 'Nikita Smith', 'pot', '20', '3' ] ]

重要的是要指出数组的每一行中恰好有四个元素。这些对应于 customer name [索引 0]、item name [索引 1]、price [索引 2] 和 quantity [索引 3]。

Reduce 在这里接受三个参数(虽然我们没有提供另外两个可选参数。)提供的参数是:1)由我们的变量 customers 表示的 reduce 累加器的当前值,当前行在由我们的变量 line 表示的数组,以及 reduce 将构建的起始值——由空对象文字 {} 提供。

我们的行变量的值为

  1. line[0] = 客户名称
  2. line[1] = 产品名称
  3. line[2] = 价格
  4. line[3] = 数量

在 reduce 函数中,发生了三件事。

  1. customers[line[0]] = customers[line[0]] || [];,我们(以某种方式神奇地)提取出每个客户。如果存在客户,则保留当前客户;如果没有客户,则将客户设置为空数组。

    问题:即使我们将行设置为 customers[line[0]] = [],仍然会返回 customer name这怎么可能? 如果我们将 customer 设置为空数组,reduce 如何返回 customer 的值?在 customers[line[0]] = [] 中,我们没有将任何内容推入数组。

    如果我们将 customers[line[0]] 设置为一个空数组并且从不将 line[0] 的值推送给 customer,我完全不明白 customer 如何在此处设置并返回。

  2. 项目的 namepricequantity 的数据被推送到附加到客户的数组中。

        customers[line[0]].push({
            name: line[1],
            price: line[2],
            quantity: line[3]
    
  3. 返回 customer 对象并提供 reduce 的下一次迭代。当数组中没有更多行可用于处理时,返回 customer 数组作为最终结果。

行[0]是客户的姓名。这意味着:

customers[line[0]] = customers[line[0]] || []

等于:

const customerName = line[0];
customers[customerName] = customers[customerName] || [];

它的作用是确保在尝试将对象推入客户列表之前,列表确实存在。让我们赋值,看看会发生什么:

customers['mark johnson'] = customers['mark johnson'] || [];

所以客户对象被分配一个空列表给 属性 'mark johnson' 当什么都没有时,或者当前存在的列表。

所以它等同于:

const customerName = line[0];
if (!customers[customerName]) {
    customers[customerName] = [];
}

之后的代码基本上是破坏 'line' 列表,并将其映射到对象的属性,该对象被推送到上面的列表。所以它等同于:

.reduce((customers, line) => {
    const customerName = line[0];
    if (!customers[customerName]) {
        customers[customerName] = [];
    }
    const price = line[2];
    const quantity = line[3];
    const currentLine = {
        name: customerName,
        price: price,
        quantity: quantity
    };

    customers[customerName].push(currentLine);
    return customers;
}, {});

但显然以前的代码更好:) 如果使用Destructuring会更清楚一点:

.reduce((customers, [name, price, quantity]) => {
    customers[name] = customers[name] || [];
    customers[name].push({name, price, quantity});
    return customers;
}, {});

希望对您有所帮助。