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"
}
]
}
让我们快速浏览一下代码中发生的事情。
.readFileSync("data.txt", "utf-8")
使用 utf-8
编码读取文件。 (否则文字看不懂。)
在 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
.trim()
函数删除文件末尾的任何换行符。
.split("\r\n")
和.map(line => line.split("\t"))
函数将传入的文件文本转换为数组数组。
.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...
函数将数组转换为JavaScript对象。
.reduce((customers, line) => {
customers[line[0]] = customers[line[0]] || [];
customers[line[0]].push({
name: line[1],
price: line[2],
quantity: line[3]
});
return customers;
}, {});
Reduce 在这里接受三个参数(虽然我们没有提供另外两个可选参数。)提供的参数是:1)由我们的变量 customers
表示的 reduce 累加器的当前值,当前行在由我们的变量 line
表示的数组,以及 reduce 将构建的起始值——由空对象文字 {}
提供。
我们的行变量的值为
line[0]
= 客户名称
line[1]
= 产品名称
line[2]
= 价格
line[3]
= 数量
在 reduce 函数中,发生了三件事。
从 customers[line[0]] = customers[line[0]] || [];
,我们(以某种方式神奇地)提取出每个客户。如果存在客户,则保留当前客户;如果没有客户,则将客户设置为空数组。
问题:即使我们将行设置为 customers[line[0]] = []
,仍然会返回 customer name
。 这怎么可能? 如果我们将 customer
设置为空数组,reduce
如何返回 customer
的值?在 customers[line[0]] = []
中,我们没有将任何内容推入数组。
如果我们将 customers[line[0]] 设置为一个空数组并且从不将 line[0] 的值推送给 customer,我完全不明白 customer 如何在此处设置并返回。
项目的 name
、price
和 quantity
的数据被推送到附加到客户的数组中。
customers[line[0]].push({
name: line[1],
price: line[2],
quantity: line[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;
}, {});
希望对您有所帮助。
问题: 我不明白 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
.
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"
}
]
}
让我们快速浏览一下代码中发生的事情。
.readFileSync("data.txt", "utf-8")
使用utf-8
编码读取文件。 (否则文字看不懂。)
在 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
.trim()
函数删除文件末尾的任何换行符。.split("\r\n")
和.map(line => line.split("\t"))
函数将传入的文件文本转换为数组数组。
.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...
函数将数组转换为JavaScript对象。.reduce((customers, line) => { customers[line[0]] = customers[line[0]] || []; customers[line[0]].push({ name: line[1], price: line[2], quantity: line[3] }); return customers; }, {});
Reduce 在这里接受三个参数(虽然我们没有提供另外两个可选参数。)提供的参数是:1)由我们的变量 customers
表示的 reduce 累加器的当前值,当前行在由我们的变量 line
表示的数组,以及 reduce 将构建的起始值——由空对象文字 {}
提供。
我们的行变量的值为
line[0]
= 客户名称line[1]
= 产品名称line[2]
= 价格line[3]
= 数量
在 reduce 函数中,发生了三件事。
从
customers[line[0]] = customers[line[0]] || [];
,我们(以某种方式神奇地)提取出每个客户。如果存在客户,则保留当前客户;如果没有客户,则将客户设置为空数组。问题:即使我们将行设置为
customers[line[0]] = []
,仍然会返回customer name
。 这怎么可能? 如果我们将customer
设置为空数组,reduce
如何返回customer
的值?在customers[line[0]] = []
中,我们没有将任何内容推入数组。如果我们将 customers[line[0]] 设置为一个空数组并且从不将 line[0] 的值推送给 customer,我完全不明白 customer 如何在此处设置并返回。
项目的
name
、price
和quantity
的数据被推送到附加到客户的数组中。customers[line[0]].push({ name: line[1], price: line[2], quantity: line[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;
}, {});
希望对您有所帮助。