ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

collect.js 严格比较设计指南:全方法默认 `===` 的实现原理与 Laravel 差异对照

collect.js 严格比较设计指南:全方法默认 `===` 的实现原理与 Laravel 差异对照
  • 开发工具

【免费下载链接】collect.js

💎 Convenient and dependency free wrapper for working with arrays and objects

项目地址:https://gitcode.com/gh_mirrors/co/collect.js
点击查看免费下载

导读

本文围绕 collect.js 的**严格比较(Strictness and comparisons)**设计展开:collect.js 中所有涉及比较的方法默认一律使用 JavaScript 严格相等(===),因此不提供containsStrict、uniqueStrict、whereStrict等 Laravel 风格的“Strict 变体方法”。文章将结合仓库源码与测试用例,逐一剖析contains、unique、where、whereIn、duplicates、diff等方法的底层比较实现,并给出与 Laravel Collections 的差异对照与实战注意事项,帮助你写出行为可预期、不踩类型隐式转换陷阱的集合操作代码。

一、核心设计:所有比较默认使用严格相等

collect.js 官方文档(bundler/strictness_and_comparisons.md)开宗明义地声明:

All comparisons incollect.jsare done using strict equality. Using loose equality comparisons are generally frowned upon in JavaScript.

翻译过来即:collect.js 中的所有比较均使用严格相等;宽松相等(loose equality,即==)在 JavaScript 中普遍不推荐使用。这条约定是 collect.js 整个 API 的行为基石,直接影响contains、unique、where等一大批方法的判定结果。

从实现角度看,这一约定贯穿于 src/methods 目录下的各个方法文件,主要通过两种手段落实:

  1. ===/!==运算符:直接比较两个值是否“同类型且同值”;
  2. Array.prototype.indexOf:indexOf内部使用严格相等(SameValueZero 语义,与===等价),因此天然满足严格比较要求。

下面的小节会逐个方法给出源码证据与测试佐证。

二、严格比较的落地:核心方法源码剖析

2.1contains():查找即===

contains.js 是理解 collect.js 严格比较的最佳入口。该方法支持三种调用形态:

module.exports = function contains(key, value) { if (value !== undefined) { if (Array.isArray(this.items)) { return this.items .filter(items => items[key] !== undefined && items[key] === value) .length > 0; } return this.items[key] !== undefined && this.items[key] === value; } // ...省略函数回调、indexOf 分支 };

关键点在于:

  • key/value 形态:比较时使用items[key] === value,例如collect({ number: 8 }).contains('number', '8')返回false,因为8 === '8'不成立;
  • 单值形态(数组):使用this.items.indexOf(key) !== -1,同样不进行类型转换;
  • 单值形态(对象):把对象的值与键都展开后走indexOf查找,保证既可按值查、也可按键查。

对应测试 contains_test.js 明确断言了类型敏感性:

const contains2 = collection.contains('number', '8'); expect(contains2).to.eql(false); // 8 与 '8' 类型不同,严格比较不通过 const contains3 = collection.contains('number', 28); expect(contains3).to.eql(false); // 值不同,同样不通过

2.2unique():3与'3'是两个不同的值

unique.js 在无参数时通过indexOf去重,有参数(键名或回调)时维护一个usedKeys数组并用indexOf判断是否重复:

collection = this.items .filter((element, index, self) => self.indexOf(element) === index);

测试 unique_test.js 给出了最直观的严格比较证据:

const collection2 = collect([1, 1, 1, 2, 3, 3, '3']); expect(collection2.unique().all()).to.eql([1, 2, 3, '3']);

3与'3'被保留为两个独立元素——这正是严格相等去重的结果;若使用宽松相等,'3'会被当作与3重复而丢弃。

2.3duplicates():对象值先序列化再严格比对

duplicates.js 查找重复值时,对数组/对象类型的元素先JSON.stringify序列化,再用indexOf判断是否出现过:

const stringifiedValue = (value) => { if (Array.isArray(value) || typeof value === 'object') { return JSON.stringify(value); } return value; };

普通标量(数字、字符串等)直接以原始值参与indexOf严格比较;由于duplicates()本身就严格,Laravel 中的duplicatesStrict变体在这里没有存在意义。

2.4where():默认===,宽松运算符需显式声明

where.js 是少数允许使用宽松运算符的方法,但它默认就是严格模式:当只传两个参数(省略运算符)时,自动把运算符补成'===':

if (value === undefined) { comparisonValue = operator; comparisonOperator = '==='; }

只有显式传入==、!=、<>时才会进入宽松比较分支,例如nestedValue(item, key) === Number(comparisonValue) || nestedValue(item, key) === comparisonValue.toString()这种“尽力转换后再比对”的宽松逻辑。

测试 where_test.js 用商品数据验证了两种模式的差异——数据中既有price: 100也有price: '100':

// 宽松相等 '==':数字 100 与字符串 '100' 都命中 const loosly100 = collection.where('price', '==', 100); // => Chair(100) + Door('100') // 严格不等 '!==':排除 price === 100 的项 const not100 = collection.where('price', '!==', 100); // => Desk(200) + Bookcase(150) + Door('100')

可见where的行为基调是严格,宽松只是显式请求时的选项。

2.5whereIn()/whereNotIn():indexOf严格判定

whereIn.js 与 whereNotIn.js 的实现几乎对称:

// whereIn:值出现在候选列表中 const collection = this.items .filter(item => items.indexOf(nestedValue(item, key)) !== -1); // whereNotIn:值不在候选列表中 const collection = this.items .filter(item => items.indexOf(nestedValue(item, key)) === -1);

两者都依赖indexOf的严格语义,因此whereIn('price', [100, 200])不会匹配price: '100'的项。测试 whereIn_test.js 验证了这一点:返回结果只包含price严格等于100或200的两条记录,'100'字符串被排除。

2.6diff():集合差集同样严格

diff.js 使用valuesToDiff.indexOf(item) === -1过滤出“原集合中有、给定集合中没有”的值,indexOf再次保证了严格语义。

三、为什么没有*Strict系列方法?

这是本主题最容易让人困惑的一点:Laravel Collections 默认使用“宽松”比较,因此额外提供containsStrict、uniqueStrict、whereStrict、whereInStrict、whereNotInStrict等一组“严格”变体方法,供开发者按需切换。

collect.js 则反其道而行:既然所有方法默认就是严格的,那么“Strict 变体”就完全多余了。文档 bundler/not_implemented.md 用删除线明确列出了一批未实现的方法及替代方案:

Laravel 方法collect.js 替代写法
containsStrict直接使用contains()
duplicatesStrict直接使用duplicates()
uniqueStrict直接使用unique()
whereStrict直接使用where()
whereInStrict直接使用whereIn()
whereNotInStrict直接使用whereNotIn()

从 src/index.js 的方法注册表可以看到,Collection.prototype上挂载的是contains、unique、where、whereIn、whereNotIn、duplicates等普通方法,并不存在任何*Strict后缀的方法。也就是说,如果你是从 Laravel 迁移过来的开发者,不需要做任何额外动作:collect.js 的contains()就是 Laravel 的containsStrict(),collect.js 的where()就是 Laravel 的whereStrict(),两者行为对齐。

四、Laravel 对照:从“默认宽松”到“默认严格”

维度Laravel Collectionscollect.js
默认比较方式宽松(loose)严格(strict)
需要严格比较时调用containsStrict等方法无需额外方法,默认即严格
需要宽松比较时默认方法即宽松where()可显式传==/!=/<>运算符
API 体量方法 + Strict 变体方法本身,无冗余变体

这一设计取舍让 collect.js 在保持与 Laravel “几乎一致的 API”的同时,规避了 JavaScript 中==的隐式类型转换陷阱(如0 == ''、1 == true等),使结果更可预期。正如 README 所述,collect.js 提供与 Laravel Collections 几乎相同的 API,strictness_and_comparisons.md 正是两者差异的官方说明。

五、实战注意事项与迁移建议

结合以上源码分析,在实际开发中应特别注意以下几点:

  1. 数值与字符串不会互相匹配:collect([1, 2, 3]).contains('2')为false;如需匹配,请先对数据做显式类型转换,而不是依赖宽松相等;
  2. where()是唯一“可切换”比较模式的方法:想要宽松匹配时,必须显式写成where('price', '==', 100)或where('price', '!=', 100),省略运算符则自动使用===;
  3. 去重与差集都是严格的:unique()会把3和'3'视为不同值,diff()也不会把100与'100'当作相同,涉及混合类型数据时需自行清洗;
  4. 从 Laravel 迁移无需改写调用:原来调用containsStrict、uniqueStrict、whereStrict等方法的代码,直接改用contains()、unique()、where()即可,语义完全一致;
  5. 对象/数组值的特殊处理:duplicates()对对象和数组元素会先做JSON.stringify再比较,因此两个结构相同但引用不同的对象会被判定为重复,这是序列化比较的预期行为。

六、相关资源

  • 官方说明:bundler/strictness_and_comparisons.md、bundler/not_implemented.md
  • README 组装机制:bundler/bundle.js
  • 源码实现:src/methods/contains.js、src/methods/unique.js、src/methods/where.js、src/methods/whereIn.js、src/methods/whereNotIn.js、src/methods/duplicates.js、src/methods/diff.js
  • 测试佐证:test/methods/contains_test.js、test/methods/unique_test.js、test/methods/where_test.js、test/methods/whereIn_test.js
  • 开发工具

【免费下载链接】collect.js

💎 Convenient and dependency free wrapper for working with arrays and objects

项目地址:https://gitcode.com/gh_mirrors/co/collect.js
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进