collect.js 中 whereNotBetween() 方法完全指南:按范围反向筛选集合元素

原创2026-10-14 09:12:431,811 阅读
文章标签:开发工具

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]
  ));
};

从源码结构可以提炼出三个关键设计:

  1. 基于 filter() 实现:方法直接调用集合自身的 filter 方法,对每一项执行谓词判断,返回值是一个新的 Collection 实例。因此原集合不会被修改(这一点在下面的测试用例中也有专门验证)。
  2. 开区间判定逻辑:保留条件为 value < values[0] || value > values[values.length - 1],即小于区间起点、或大于区间终点。这等价于「不在闭区间 [min, max] 内」,与 whereBetween() 的闭区间语义正好互补。
  3. 只取区间两端:源码取 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 只需两步:

  1. 安装:npm install collect.js
  2. 引入: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 的语义差异,能帮助你在数据清洗、报表统计、列表筛选中快速写出简洁且正确的过滤逻辑。

登录后查看全文
collect.js