假设我有一个js对象数组,其中包含我的每月费用数据,如下所示
var data=[
{
"date": "2016-09-01",
"bills": "34"
},{
"date": "2016-09-02",
"bills": "34"
},{
"date": "2016-09-03",
"bills": null
},{
"date": "2016-09-04",
"bills": "34"
},{
"date": "2016-09-05",
"bills": null
},{
"date": "2016-09-06",
"bills": "34"
},{
"date": "2016-09-07",
"bills": "34"
},{
"date": "2016-09-08",
"bills": null
},{
"date": "2016-09-09",
"bills": "34"
},{
"date": "2016-09-10",
"bills": null
}
];
var totalAmount = function(response){
for(var i=0; i<response.length; i++ ){
if (response.bills!==null){
var totalSum = "";
totalSum += response.bills;
}
}
return totalSum;
};
totalAmount(data);
虚线表示其他日期的数据,并且您可以看到某些日期的帐单金额表示为整数,而某些日子的值为零。我可以使用零,但假设我的数组中有空值,并且我想查找一个月的总支出,该如何编写函数?选中此jsfiddle
最佳答案
您已经编写了遍历一件事并检查另一件事并添加另一件事的代码,这为您发现各种错误提供了很多机会!更加现代的编程风格不仅可以使您更紧凑地编写程序,还可以减少发生错误的机会,并帮助您(和其他人)通过阅读程序使自己确信程序是正确的。
我们将自上而下地进行处理。您要做的最基本的事情是创建总和。因此,我们将从最简单的程序开始:
sum(bills)
当然,现在我们必须实现
sum
,并弄清楚从哪里获取bills
!求和
让我们立即摆脱
sum
。我们只循环一些数组,然后将数字加起来:function sum(array) {
var total = 0;
for (var i = 0; i < array.length; i++) total += array[i];
return total;
}
现在,我们有了一个不错的,干净的,通用的,显然正确的函数来累加一个数字列表,即使在新项目中,我们也可以随时随地放心使用它。它对账单一无所知。用程序设计术语来说,它是“解耦的”。
我们还可以使用JavaScript中的新功能
for...of
循环,使其更加简洁。 for...of
直接为您提供数组的元素,而您不必担心索引。function sum(array) {
var total = 0;
for (var num of array) total += num;
return total;
}
请注意,我们不会通过检查null或将字符串转换为数字来弄乱此
sum
函数。我们假定,无论谁打电话给它,都已经负责提供干净的数字列表。这个概念称为“关注点分离”。我们将“获得和”的关注与“过滤/转换数字列表”的关注分开。还有其他更花哨的方式来编写此代码,例如使用有用的
reduce
函数,但我们暂时将其跳过。获取账单
接下来,当然,我们必须获取
bills
值以传递给sum
。这只是一个数字数组,例如[34, 29, 72]
。我们将使用功能data
从输入getBills
中提取票据,因此我们可以将顶级程序编写为sum(getBills(data))
现在,我们要做的就是编写
getBills
!最明显的方法是编写另一个for
循环:function getBills(data) {
var bills = [];
for (var i = 0; i < data.length; i++) bills.push(data[i].bills);
return bills;
}
还是再次提醒自己如何使用
for...of
:function getBills(data) {
var bills = [];
for (var obj of data) bills.push(obj.bills);
return bills;
}
没关系,但是JavaScript实际上提供了一种方便的通用方式来实现这一点-通过对旧数组的每个元素应用一些规则,从旧数组创建新数组。这称为
map
,我们通过在旧数组上调用它来使用它:newarray = oldarray.map(rule)
其中
rule
是一个函数,该函数接受旧数组中的每个元素,并返回一些值以放入新数组中。在这种情况下,旧数组中的元素是{"date": "2016-09-02", "bills": "34" }
形式的小对象,我们要应用的规则是从该对象中提取bills
属性的值。那真的很简单:function extractBills(object) { return object.bills; }
所以现在我们可以简单地写为
getBills
function getBills(data) {
return data.map(extractBills);
}
这里发生了什么?我们要将一个函数作为参数传递给另一个函数?你能做到吗?是的,在JavaScript函数中是“一流的值”,就像传递数字
1
一样容易传递。您可以像在此处一样将一个函数传递给另一个函数,然后该函数可以“调用”我们传递给它的函数。在这种情况下,我们将一个函数传递给map
,而map
则在每个元素上调用该函数。此类函数通常称为“回调”,因为map
是针对每个元素“回调”我们的函数。整理账单清单:消除空值
我们快到了!唯一剩下的问题是摆脱一些小对象中
bills
属性的空值。无需将检查内容混入我们的其他代码中,而是将其单独处理。让我们编写一个名为nonNull
的小函数,它使用一个旧数组并返回一个删除了所有null
值的新数组。同样,最明显的写法是循环:function nonNull(array) {
var result = [];
for (var i = 0; i < array.length; i++) {
var value = array[i];
if (value !== null) result.push(value);
}
return result;
}
或再次使用
for...of
:function nonNull(array) {
var result = [];
for (var value of array) if (value !== null) result.push(value);
return result;
}
很好,但是事实证明,还有另一个方便的函数已经在执行此过程,称为“过滤”,可以从仅包含某些元素的旧数组创建新数组。此功能称为
filter
,显然足够了。为了告诉它要包含在新数组中的元素,我们再次给它提供一个规则,但是这次规则说明是要包含一个元素还是要排除它。该规则也被写为一个函数,该函数传递一个元素,并返回true
或false
,表示包含或不包含。我们的规则很简单:值是否为null?所以我们可以很容易地写成这样
function notNull(val) { return val !== null; }
现在我们可以说出一些数组中的空值
newarray = oldarray.filter(notNull);
通过在控制台中键入
[1, null, 2].filter(notNull)
,我们可以轻松地以独立方式对其进行测试。我们也可以这样写
newarray = oldarray.filter(Boolean)
它将内置的
Boolean
函数应用于每个元素,仅当元素为“ falsy”(即undefined
,null
,0或空字符串)时,它才会返回false(从而排除该元素)。在这种情况下,也可以。通过在控制台中键入Boolean(null)
进行尝试。修正清单中的更多问题:转换为数字
但是,还有一个问题:
bills
属性的值是字符串。但是sum
旨在处理数字。同样,我们不想通过特殊的检查和转换来完善我们漂亮的sum
函数。因此,我们想做一个单独的映射,将数组中的所有值转换为数字。同样,我们可以使用map
:arrayOfNumbers = arrayOfStrings.map(Number)
这会在
Number
的每个元素上调用Number("34")
内置函数(在控制台中通过键入arrayOfStrings
尝试),并为我们提供一个数组,其中包含每个转换为数字的元素。现在,我们拥有完成小程序所需的所有内容,这仅仅是
sum(getBills(data).filter(notNull).map(Number))
您可以从“内部”阅读此内容。换句话说,我们首先从数据中获取账单;这将是一个值,例如
["34", ..., null]
。然后,我们对该数组进行过滤以删除null。然后,我们映射此数组以将其值从字符串转换为数字。最后,我们在整个过程中调用sum
。 Voilà,我们完成了。现在,我们几乎可以将其作为常规的英语句子来阅读,它说
getBills
中的data
,进行过滤以仅保留notNull
,将它们映射到Number
,然后获取整个内容的sum
。我发现采用一种“文学编程”风格很有用,使用这种风格的注释使编写起来容易:
sum( // Find the sum of
getBills(data) // the bills from the data
.filter(notNull) // with nulls removed
.map(Number) // and strings converted to numbers
)
完成程序
现在我们的完整程序
var data=[
{"date": "2016-09-01", "bills": "34"},
{"date": "2016-09-02", "bills": "34"},
{"date": "2016-09-08", "bills": null}
];
function sum(array) {
var total = 0;
for (var num of array) total += num;
return total;
}
function extractBills(object) { return object.bills; }
function getBills(data) { return data.map(extractBills); }
function notNull(val) { return val !== null; }
function addUpBills(data) { return sum(getBills(data).filter(notNull).map(Number)); }
console.log(addUpBills(data));
仅通过查看该程序,我们几乎可以知道它将会起作用,并按照我们想要的去做。该程序几乎不可能拥有原始代码中的那种错误,例如在循环内部而不是外部初始化一些变量。
但这一切必要吗?
在这种简单情况下,也许不是。您可以轻松地编写以下内容:
var total = 0;
for (var obj of data) total += +obj.bills;
利用一元
+
将字符串转换为数字,这也将null
视为0
。但是,随着您在编程中努力解决更复杂的问题,功能概念仍将为您提供良好的支持。摘要
这是所谓的“函数式编程”的一个简单示例,因为我们正在组织逻辑,例如提取逻辑,将其转换,转换或过滤其他内容,转换为诸如
extractBills
之类的函数,然后使用函数,例如map
和filter
,以逐步将这些函数应用于我们的数据以得到我们的输出。通过函数式编程,我们经常发现自己是“自上而下”的工作。换句话说,我们没有编写一堆完整的代码,而是编写了一个简单,顶层的解决方案。通常,我们可以通过提供一些虚拟数据立即进行测试。然后,我们继续“填补空白”,编写顶层逻辑所需的下层代码。我们还可以轻松地一对一地测试较低级别的代码;例如,我们可以通过在控制台中直接输入
sum
来测试sum([1, 2, 3])
,而不必担心账单或其他问题。另一个关键点是将不同的事物分开。在这种情况下,我们进行了循环,并检查了null,并将字符串转换为数字,并进行了加法运算。我们不会将所有这些事情混为一谈,而是将它们编写为独立的微型函数,然后通过
map
和filter
等函数进行“组合”,从而分别处理它们,以解决我们的问题。单独处理事物的好处还在于,我们创建的小片段可以轻松重用,测试或更改,而不会破坏其他事物。
关于javascript - 查找JS数组的总和,并排除所有NULL值,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39761469/