javascript - 使用 reduce() 同时读取两个变量
问题描述
我想用 来降低以下函数的复杂性reduce()
,因为这两个变量的功能几乎相似,selectedEnrolled
而selectedNotEnrolled
.
我尝试使用map()
,但在这里我没有返回任何东西,所以最终产生了副作用,我意识到这是一种糟糕的编码习惯。
countSelectNotEnrolled(selected: any) {
this.selectedNotEnrolled = selected.reduce((count, id) => {
return this.hasSteps(id)
? count + (this.userShelf.courseIds.indexOf(id) === -1 ? 1 : 0)
: count;
}, 0);
this.selectedEnrolled = selected.reduce((count, id) => {
return this.hasSteps(id)
? count + (this.userShelf.courseIds.indexOf(id) === -1 ? 0 : 1)
: count;
}, 0);
}
我很感激帮助。
解决方案
唯一的区别似乎是您作为增量返回的内容,具体取决于当前 id 是否是this.userShelf.courseIds
数组的一部分。
countSelectNotEnrolled(selected: any) {
this.selectedNotEnrolled = selected.reduce((count, id) => {
return this.hasSteps(id)
? count + (this.userShelf.courseIds.indexOf(id) === -1 ? 1 : 0)
// ^^^^^
: count;
}, 0);
this.selectedEnrolled = selected.reduce((count, id) => {
return this.hasSteps(id)
? count + (this.userShelf.courseIds.indexOf(id) === -1 ? 0 : 1)
// ^^^^^
: count;
}, 0);
}
一种更普遍适用于重构模式的方法是在创建函数时将该差异提取为动态部分。
您可以创建一个返回另一个函数并接受此增量值作为参数的函数。然后,您可以使用该函数创建两个函数,一个对计数selectedNotEnrolled
求和,另一个对 求和selectedEnrolled
。
注意: usingArray.prototype.includes
比Array.prototype.indexOf
检查数组中的存在性要干净一些。
function createEnrolmentSum( // the wrapper function accepts the dynamic setup logic
incrementIfSelected,
incrementIfUnselected = incrementIfSelected === 1 ? 0 : 1
) {
return function (selected) { // <--- return a function that does the common logic
return selected.reduce((count, id) => {
return this.hasSteps(id)
? count +
(this.userShelf.courseIds.includes(id) // <-- using includes is a bit cleaner
? incrementIfSelected
: incrementIfUnselected)
: count;
}, 0);
};
}
// ...
// create instance methods so we maintain proper `this` behavior
getEnrolledSum = createEnrolmentSum(1);
getNotEnrolledSum = createEnrolmentSum(0);
countSelectNotEnrolled(selected: any) {
this.selectedNotEnrolled = this.getNotEnrolledSum();
this.selectedEnrolled = this.getEnrolledSum();
}
以上只是对如何重构任何类似代码的通用演示。可以说,这个特定的 api 不是很可读:
// this api isn't very readable because it's not clear
// what `1` or `0` mean as arguments
getEnrolledSum = createEnrolmentSum(1);
getNotEnrolledSum = createEnrolmentSum(0);
您可以使用配置对象提高这种可读性:
getEnrolledSum = createEnrolmentSum({
incrementIfSelected: 1,
incrementIfUnselected: 0
});
getNotEnrolledSum = createEnrolmentSum({
incrementIfSelected: 0,
incrementIfUnselected: 1
});
但这并没有带来太大的改善,因为代码虽然DRY,但绝对是复杂的。
我建议,对于您的特定情况,显而易见的起始解决方案是在一个循环中计算两个总和。这不需要太多复杂性并且会更快,因为您只需遍历selected
数组一次而不是两次。
countSelectNotEnrolled(selected) {
let selectedNotEnrolled = 0,
selectedEnrolled = 0;
for (const id of selected) {
if (this.hasSteps(id)) {
if (this.userShelf.courseIds.includes(id)) {
selectedEnrolled += 1;
} else {
selectedNotEnrolled += 1;
}
}
}
this.selectedNotEnrolled = selectedNotEnrolled;
this.selectedEnrolled = selectedEnrolled;
}
如果您确实想使用数组缩减,您可以使用一个对象通过迭代循环携带两个变量:
countSelectNotEnrolled(selected) {
const { selectedNotEnrolled, selectedEnrolled } = selected.reduce(
(result, id) => {
if (this.hasSteps(id)) {
if (this.userShelf.courseIds.includes(id)) {
result.selectedEnrolled += 1;
} else {
result.selectedNotEnrolled += 1;
}
}
return result;
},
{ selectedNotEnrolled: 0, selectedEnrolled: 0 } // <-- reduction result contains both variables
);
this.selectedNotEnrolled = selectedNotEnrolled;
this.selectedEnrolled = selectedEnrolled;
}
去除一些嵌套,我们可以先过滤掉所有没有step的id:
countSelectNotEnrolled(selected) {
const { selectedNotEnrolled, selectedEnrolled } = selected
.filter(this.hasSteps) // <-- apply the filter first
.reduce(
(result, id) => {
if (this.userShelf.courseIds.includes(id)) {
result.selectedEnrolled += 1;
} else {
result.selectedNotEnrolled += 1;
}
return result;
},
{ selectedNotEnrolled: 0, selectedEnrolled: 0 }
);
this.selectedNotEnrolled = selectedNotEnrolled;
this.selectedEnrolled = selectedEnrolled;
}
如果您已经在实例上初始化了必要的变量并且更新实例数据没有重大的性能损失,您也可以直接为其赋值:
countSelectNotEnrolled(selected) {
// setup
this.selectedEnrolled = 0;
this.selectedNotEnrolled = 0;
// sum
selected
.filter(this.hasSteps)
.forEach(id => {
if (this.userShelf.courseIds.includes(id)) {
this.selectedEnrolled += 1;
} else {
this.selectedNotEnrolled += 1;
}
});
}
或者,您可以利用过滤来拆分属于已注册和未注册的 id,然后提取长度:
countSelectNotEnrolled(selected) {
const selectedWithSteps = selected.filter(this.hasSteps);
this.selectedNotEnrolled = selectedWithSteps.filter(
id => !this.userShelf.courseIds.includes(id)
).length;
this.selectedEnrolled = selectedWithSteps.filter(id =>
this.userShelf.courseIds.includes(id)
).length;
}
其实不用过滤两次,就可以互相表示已注册和未注册:
countSelectNotEnrolled(selected) {
const selectedWithSteps = selected.filter(this.hasSteps);
this.selectedNotEnrolled = selectedWithSteps.filter(
id => !this.userShelf.courseIds.includes(id)
).length;
this.selectedEnrolled = selectedWithSteps.length - this.selectedNotEnrolled;
}
总的来说,我给出的最好的重构建议是减少对特定模式的担忧,而更多地关注代码的可读性。我们正在使用 JavaScript 等高级语言编写,以便人类可以阅读它,机器不在乎,它们可以很好地阅读二进制文件。我们应该编写花费最少的人类认知努力来理解的代码
推荐阅读
- python - 陷入嵌套的 While 循环
- html - 弹性容器的非直接子子项似乎垂直未对齐。为什么?
- selenium - 当用户关闭 Selenium 中的选项卡时关闭 Chromewebdriver
- javascript - 根据用户输入 id 从 JSON 中按名称获取项目
- python - How to log results as and when available without losing it during a crash?
- angular8 - ngrx store state undefined
- python-3.x - subclass tuple with typed arguments
- python - 熊猫内部连接未正确连接
- android - 使用 Android 8.1.0 连接到 BLE(基于 RN4080 Microchip)设备时出现问题
- php - php拆分字符串,3个字符长度,每个应该有3个字符