- 开发工具
【免费下载链接】collect.js
💎 Convenient and dependency free wrapper for working with arrays and objects
average()是 collect.js 集合库中用于计算集合内所有元素平均值的便捷方法,它与avg()完全等价(avg()是实际实现、average()是别名)。本文围绕 average() 官方文档 与配套的 avg() 文档,结合 average.js 源码、底层 sum.js 与 pluck.js 实现,以及 average_test.js、avg_test.js 测试用例,完整讲解无参求和、按键取值、回调函数三种调用形态,并深入其背后的求和与取值原理、边界行为与 TypeScript 类型签名,读完即可在真实项目中正确、稳健地使用该 API。
方法定位:average() 是 avg() 的别名
average()文档本身只有一句话定义:它是avg()方法的别名(Alias for theavg()method)。这一点在源码中有三重印证:
- 实现文件直接复用:src/methods/avg.js 全文仅一条语句,直接
require('./average')导出,说明avg与average指向同一个函数对象:'use strict'; const average = require('./average'); module.exports = average; - 注册入口同时挂载:src/index.js 中
Collection.prototype.average与Collection.prototype.avg分别加载两个方法文件,因此无论调用哪一个,行为完全一致。 - 测试用例明确验证别名关系:avg_test.js 断言
collect([1, 3, 3, 7]).avg()与collect([1, 3, 3, 7]).average()结果相同。
所以下面关于avg()的每一种用法与行为,对average()同样成立,文章中不再重复区分。
基本用法:无参数计算所有元素的平均值
当集合中的元素本身就是数值时,直接调用avg()即可,无需任何参数:
collect([1, 3, 3, 7]).avg(); // 3.5对应的average()写法:
collect([1, 3, 3, 7]).average(); // 3.5从 average.js 源码 看,无参分支的实现是:
module.exports = function average(key) { if (key === undefined) { return this.sum() / this.items.length; } // ... };即:先调用集合自身的sum()求出元素总和,再除以元素个数this.items.length,得到算术平均值。这里的sum()实现位于 src/methods/sum.js,它对每个元素执行parseFloat(items[i])并累加,因此数字字符串(如'3')也会被解析为数值参与运算。
按键(key)计算嵌套对象属性的平均值
当集合元素是对象或嵌套数组时,需要传入一个key,指明用哪个属性的值参与求平均:
const collection = collect([ { name: 'My story', pages: 176, }, { name: 'Fantastic Beasts and Where to Find Them', pages: 1096, }, ]); collection.avg('pages'); // 636对应average('pages')结果相同,即(176 + 1096) / 2 = 636。
该分支的源码实现为 average.js:
return new this.constructor(this.items).pluck(key).sum() / this.items.length;它通过new this.constructor(this.items)基于原集合元素重建一个新集合(不会修改原集合,这一点由 average_test.js 断言collection.all()仍等于原products得到验证),然后调用pluck(key)抽出对应属性的值,再求和、除以原集合长度。
得益于 pluck.js 内部使用 nestedValue.js 解析属性路径(后者支持用.分隔的多级路径,例如key.split('.').reduce(...)),avg()的key参数同样支持嵌套路径写法,例如:
collect([ { order: { amount: 100 } }, { order: { amount: 300 } }, ]).avg('order.amount'); // 200使用回调函数自定义取值逻辑
当平均值的计算逻辑不能用一个简单属性名表达时(例如需要对属性做变换、过滤后取值,或取值来自多个属性组合),可以传入一个回调函数,collect.js 会以每个元素为参数调用它:
const collection = collect([ { name: 'My story', pages: 176, }, { name: 'Fantastic Beasts and Where to Find Them', pages: 1096, }, ]); collection.avg(book => book.pages); // 636回调分支的源码为 average.js:
if (isFunction(key)) { return new this.constructor(this.items).sum(key) / this.items.length; }其中isFunction来自 src/helpers/is.js,本质是typeof item === 'function'。传入的sum(key)会对每个元素执行parseFloat(key(items[i]))后累加,所以回调的返回值既可以是数值,也可以是可被parseFloat解析的字符串。
回调形态是三种调用方式中扩展性最强的:你可以在回调里做item.pages * 2、拼接多个字段、甚至先过滤再映射,因为sum()只会消费回调的返回值。
源码级原理:average() 的三分支结构与底层依赖
把 average.js 完整展开,其逻辑清晰分为三条路径:
| 调用形态 | 条件 | 求值方式 |
|---|---|---|
| 无参 | key === undefined | this.sum() / this.items.length |
| 回调函数 | isFunction(key) | new this.constructor(this.items).sum(key) / this.items.length |
| 属性名 | 其他情况 | new this.constructor(this.items).pluck(key).sum() / this.items.length |
三个分支共享两个关键设计:
- 复用而非重写:
average不自己遍历元素,而是委托给集合自身的sum()与pluck()方法。这种组合式实现让平均值计算天然继承了sum()的精度处理和pluck()的嵌套取值能力,代码量极小且行为统一。 - 不变性(immutability):带参分支通过
new this.constructor(this.items)基于原元素重建集合再求值,原集合数据不会被改写;无参分支直接读取this.items也仅做读取。
关于浮点精度,值得留意sum()的收尾处理——sum.js 返回parseFloat(total.toPrecision(12)),即把累加结果先按 12 位有效数字四舍五入再转回数字。这一处理能削减大部分累加误差,但除法引入的小数位仍可能超出预期,例如测试用例中的collect([1, 3, 3, 7]).unique().avg()得到3.6666666666666665(而非精确的3.666...6),这是 IEEE 754 浮点数的正常表现,需要精确结果时应自行toFixed处理。
测试验证:三种形态均有对应用例
仓库测试 test/methods/average_test.js 覆盖了average()的全部三种调用形态,可直接作为行为规范参考:
it('should return the average value of collection values', () => { expect(collect([1, 3, 3, 7]).average()).to.eql(3.5); }); it('should accept a callback function', () => { const avg = collect(products).average(product => product.price); expect(avg).to.eql(550); }); it('should return the average value of collection values by key', () => { const collection = collect(products); const avg = collection.average('price'); expect(avg).to.eql(550); expect(collection.all()).to.eql(products); });测试数据products = [{ name: 'Chair', price: 600 }, { name: 'Desk', price: 900 }, { name: 'Lamp', price: 150 }]的均价为(600 + 900 + 150) / 3 = 550。
而 avg_test.js 除同样覆盖按键、回调外,还专门验证了别名等价性与链式组合(unique().avg()),证明avg/average可以与unique()等方法流畅串联。
边界情况与注意事项
结合源码可以确认以下几点行为,供实战时规避坑点:
- 空集合:无参分支中
sum()对空集合返回0(sum.js 循环不执行,total保持 0),再除以this.items.length(0),最终得到NaN。从 average.js 的实现结构可以推断,对空集合调用average()/avg()不会抛错,但会返回NaN,业务上应先判空。 - 混合类型元素:
sum()对所有元素统一执行parseFloat,非数字内容会被解析为NaN并污染累加结果,最终均价同样可能变成NaN,因此参与求平均的元素应保证为数值或数字字符串。 - 属性缺失:按键分支中,
pluck()对缺失的属性会返回null(见 pluck.js 的兜底逻辑),null经parseFloat后为NaN,同样会影响均价。 - 不修改原集合:带参分支使用新构造的集合完成求值,average_test.js 对此有显式断言。
TypeScript 类型签名
collect.js 自带类型声明 index.d.ts,average与avg的签名如下(index.d.ts):
/** * Alias for the avg() method. */ average<K>(key?: keyof Item | K): number; /** * The avg method returns the average of all items in the collection. */ avg<K>(key?: keyof Item | K): number;key可选,类型为keyof Item | K,既可以是集合元素的属性名,也可以是任意泛型(配合回调使用时由回调参数推导)。返回值统一为number。需要指出的是,该签名将回调形态也归类在可选key参数下,实际传函数时 TypeScript 会要求你自行断言或使用泛型,这是类型声明对三种调用形态的简化建模。
典型链式场景
average()/avg()通常与过滤、排序、去重等操作组合使用,例如基于 usage.md 的链式风格:
collect(products) .where('price', '>', 299) // 先过滤 .avg('price'); // 再求平均 collect([1, 3, 3, 7]) .unique() // 去重后元素为 [1, 3, 7] .avg(); // 3.6666666666666665作为 collect.js 核心 API 的一员,average()与avg()完全免费依赖(package 本身即为零依赖的数组/对象包装库),在需要对一组数据进行集中趋势统计时,是比手写reduce循环更简洁、可读性更高的选择。
- 开发工具
【免费下载链接】collect.js
💎 Convenient and dependency free wrapper for working with arrays and objects
相关推荐
Sails 中 Waterline 模型的 `.avg()` 聚合方法:跨记录计算数值属性的算术平均值
Sails 中 Waterline 模型的 .avg 聚合方法:跨记录计算数值属性的算术平均值 导读 .avg 是 Sails 内置 ORM——Waterlin
后端RxGo Average 算子详解:从 AverageInt 到并行平均值计算
RxGo Average 算子详解:从 AverageInt 到并行平均值计算 导读 本文聚焦 RxGo(Reactive Extensions for the
后端RxJS 4 的 average 操作符:聚合平均值计算的源码剖析与实战指南
RxJS 4 的 average 操作符:聚合平均值计算的源码剖析与实战指南 Rx.Observable.prototype.average 是 RxJS 4(
后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考