collect.js 中 whereNotBetween() 方法完全指南:按范围反向筛选集合元素
collect.js 中 whereNotBetween() 方法完全指南:按范围反向筛选集合元素
whereNotBetween() 是 collect.js 提供的一组「范围过滤」方法中的排除式实现:它从集合中保留所有不在指定区间内的元素,常用于剔除价格、日期、评分等数值字段落在某个区间内的记录。本文将以 docs/api/whereNotBetween.md 为骨架,结合 src/methods/whereNotBetween.js 源码与 test/methods/whereNotBetween_test.js 测试用例,完整讲解其用法、边界语义、实现原理,以及与 whereBetween()、whereIn()、whereNotIn() 等姊妹方法的选型差异。读完本文,你将能在实际项目中准确使用范围排除过滤,并理解其比较规则与嵌套键访问机制。
方法概述与函数签名
whereNotBetween 过滤集合中不处于给定范围(含两个端点)内的元素。它接收两个参数:
| 参数 | 类型 | 说明 |
|---|---|---|
key |
string | 用于比较的字段名,支持用点号 a.b.c 访问嵌套属性 |
values |
array | 长度为 2 的数组 [min, max],定义排除区间 |
其语义与 Laravel 集合中的同名方法一致:凡是 min <= value <= max 的元素都会被移除,其余元素被保留。
基础用法:完整示例
以下是官方文档中的标准示例,演示了如何筛选出价格不在 100 到 200 区间内的商品:
const collect = require('collect.js');
const collection = collect([
{ product: 'Desk', price: 200 },
{ product: 'Chair', price: 80 },
{ product: 'Bookcase', price: 150 },
{ product: 'Pencil', price: 30 },
{ product: 'Door', price: 100 },
]);
const filtered = collection.whereNotBetween('price', [100, 200]);
filtered.all();
// [
// { product: 'Chair', price: 80 },
// { product: 'Pencil', price: 30 },
// ]
可以看到,Desk(200)、Bookcase(150)、Door(100)的价格都落在 [100, 200] 区间内而被过滤掉;Chair(80)和 Pencil(30)位于区间之外,得以保留。注意这里的区间包含两个端点:价格恰好等于 100 或 200 的元素同样会被排除。
源码实现解析
whereNotBetween 的实现非常精简,完整源码位于 src/methods/whereNotBetween.js:
'use strict';
const nestedValue = require('../helpers/nestedValue');
module.exports = function whereNotBetween(key, values) {
return this.filter(item => (
nestedValue(item, key) < values[0] || nestedValue(item, key) > values[values.length - 1]
));
};
从源码结构可以提炼出三个关键设计:
- 基于
filter()实现:方法直接调用集合自身的filter方法,对每一项执行谓词判断,返回值是一个新的 Collection 实例。因此原集合不会被修改(这一点在下面的测试用例中也有专门验证)。 - 开区间判定逻辑:保留条件为
value < values[0] || value > values[values.length - 1],即小于区间起点、或大于区间终点。这等价于「不在闭区间[min, max]内」,与whereBetween()的闭区间语义正好互补。 - 只取区间两端:源码取
values[0]作为下界、values[values.length - 1]作为上界,而不是遍历整个数组。这意味着即使传入超过两个元素的数组,也只会使用首尾两个值作为区间边界。
与之对照的 whereBetween(见 src/methods/whereBetween.js)则用两次 where 组合实现闭区间筛选:
module.exports = function whereBetween(key, values) {
return this.where(key, '>=', values[0]).where(key, '<=', values[values.length - 1]);
};
两个方法的取舍非常清晰:whereBetween 返回 value >= min && value <= max 的元素,whereNotBetween 返回 value < min || value > max 的元素。它们互为补集,组合使用即可精确控制保留哪些区间的数据。
边界语义与比较细节
whereNotBetween 直接使用 JavaScript 的 < 与 > 运算符进行数值比较,由此产生几个容易踩坑的语义细节,均有测试用例佐证(见 test/methods/whereNotBetween_test.js):
1. 区间包含端点(闭区间排除)
测试用例 'should filter out values within given scope' 验证了端点行为:
const collection = collect([{ v: 1 }, { v: 2 }, { v: 3 }, { v: '3' }, { v: 4 }]);
// 区间 [2, 4]:v 等于 2、3、4 的项全部被排除
expect(collection.whereNotBetween('v', [2, 4]).all()).to.eql([
{ v: 1 },
]);
值为 2、3、4 的元素都落在 [2, 4] 内(包括端点)而被移除,仅剩 { v: 1 }。
2. 单点区间
当 values[0] === values[values.length - 1] 时,区间退化为单个值,whereNotBetween 表现为「不等于该值」的过滤:
// 区间 [3, 3]:只排除 v === 3 的元素
expect(collection.whereNotBetween('v', [3, 3]).all()).to.eql([
{ v: 1 }, { v: 2 }, { v: 4 },
]);
注意 { v: '3' }(字符串 '3')也被排除了——原因见下一条。
3. 弱类型比较与字符串数字
由于源码使用 < / > 运算符,比较遵循 JavaScript 的隐式类型转换规则:数字与字符串数字比较时会被转换为数值。因此 v: '3'(字符串)与数字 3 比较时数值相等,同样落入区间被排除。如果需要严格的类型区分,应改用 where() 配合 !== 等严格比较运算符(相关支持见 src/methods/where.js)。
4. 不修改原集合
测试用例 'should not modify the existing collection' 专门验证了方法的不可变性:
const collection = collect([{ v: 1 }, { v: 2 }, { v: 3 }, { v: '3' }, { v: 4 }]);
expect(collection.whereNotBetween('v', [2, 4]).all()).to.eql([{ v: 1 }]);
// 原集合保持不变
expect(collection.all()).to.eql([{ v: 1 }, { v: 2 }, { v: 3 }, { v: '3' }, { v: 4 }]);
这是因为 filter 返回的是全新集合,whereNotBetween 本身不做任何原地修改,适合在链式调用中安全使用。
嵌套键访问:点号路径支持
whereNotBetween 内部通过 src/helpers/nestedValue.js 获取每一项的属性值。该工具函数支持用点号分隔的嵌套路径:
module.exports = function nestedValue(mainObject, key) {
try {
return key.split('.').reduce((obj, property) => obj[property], mainObject);
} catch (err) {
// 如果中途取不到值,说明不是对象,mainObject 本身就是值
return mainObject;
}
};
因此 key 参数不仅可以传 'price' 这样的顶层字段,还可以传 'order.total'、'meta.score' 等嵌套路径,例如:
const collection = collect([
{ name: 'A', meta: { score: 88 } },
{ name: 'B', meta: { score: 60 } },
{ name: 'C', meta: { score: 95 } },
]);
// 保留 meta.score 不在 [70, 90] 内的记录
collection.whereNotBetween('meta.score', [70, 90]).all();
// [
// { name: 'B', meta: { score: 60 } },
// { name: 'C', meta: { score: 95 } },
// ]
当路径解析抛错(例如某项不是对象)时,nestedValue 会回退返回该项本身,这一容错行为保证了过滤过程不会因个别异常项而崩溃。
与相关过滤方法的选型对比
collect.js 提供了完整的「区间 / 枚举」过滤家族,全部注册于 src/index.js,可按需求灵活选用:
| 方法 | 保留条件 | 典型场景 |
|---|---|---|
whereBetween(key, [min, max]) |
min <= value <= max |
只保留区间内的记录 |
whereNotBetween(key, [min, max]) |
value < min || value > max |
剔除区间内的记录 |
whereIn(key, [v1, v2, ...]) |
value 属于给定列表 |
只保留枚举集合中的记录 |
whereNotIn(key, [v1, v2, ...]) |
value 不属于给定列表 |
剔除枚举集合中的记录 |
where(key, operator, value) |
满足指定运算符比较 | 任意比较规则的通用入口 |
其中 whereIn / whereNotIn 是精确值匹配(whereNotIn 的实现见 src/methods/whereNotIn.js,使用 indexOf 判断),而 whereNotBetween 是范围匹配,两者互补:
- 已知明确的黑名单值 → 用
whereNotIn; - 已知需要剔除的数值区间 → 用
whereNotBetween; - 需要更复杂或非闭区间的比较 → 用
where配合<、<=、>、>=、!==等运算符。
由于 whereNotBetween 只基于 < / > 两个运算符,它天然不支持开区间语义(如「只排除 > 100 的情况」);这类需求请改用 where(key, '<', 100) 或 where(key, '>=', 100) 的组合,见 src/methods/where.js 中完整的运算符支持列表。
快速上手
collect.js 是一个零依赖、专门用于操作数组与对象的包装库。在项目中使用 whereNotBetween 只需两步:
- 安装:
npm install collect.js - 引入:
const collect = require('collect.js');(对应 package.json 与 src/index.js 的导出方式)
之后即可按照本文示例,将任何数组包装为 Collection 并调用 whereNotBetween,再配合 all()(见 src/methods/all.js)取回原生数组,或与其他方法链式组合使用。
小结
whereNotBetween(key, [min, max]) 是 collect.js 中实现「区间排除」过滤的首选方法:底层一条 filter + 两次比较即可完成工作,支持闭区间端点、点号嵌套键、字符串数字隐式转换,并且不会修改原集合。理解它与 whereBetween、whereIn、whereNotIn 的语义差异,能帮助你在数据清洗、报表统计、列表筛选中快速写出简洁且正确的过滤逻辑。