LokiJS Collection Transforms 完全指南:把查询链序列化为可命名、可持久化的“存储过程“
数据库后端【免费下载链接】LokiJSjavascript embeddable / in-memory database项目地址https://gitcode.com/gh_mirrors/lo/LokiJS点击查看免费下载LokiJS 的 Collection Transforms 允许把一整套 Resultset 链式查询find、where、排序、分页等抽象为纯对象的步骤step数组定义并可选地命名保存到 Collection 中、随数据库一起序列化持久化从而获得类似存储过程的能力。本文以 tutorials/Collection Transforms.md 为骨架结合 src/lokijs.js 的源码实现与 spec/generic/transforms.spec.js 测试用例完整讲解 transform 的结构、命名、参数化[%lktxp]、执行方式以及在 DynamicView 中的分支提取用法。读完后你将能写出可保存、可复用、可参数化的复杂查询管线并理解其底层执行机制。一、核心概念把查询链变成数据定义LokiJS 常规的查询链是这样写的var results coll.chain() .find({Age: {$gt: 20}}) .where(function(obj) { return obj.Country.indexOf(FR) 0; }) .simplesort(Name) .offset(100) .limit(25) .data();Transform 的基本思想是将上述 Resultset 链式处理过程转换成一个对象数组定义这个数据定义随后可以命名并随 Collection 一起保存在数据库内。它本质上是查询逻辑的数据化、可序列化版本。从源码角度看LokiJS 把 transform 的执行入口放在了Resultset.prototype.transformsrc/lokijs.js它会对步骤数组做switch (step.type)分发逐个映射到对应的 Resultset 链式方法上。这意味着 transform 与普通链式查询共享同一套底层实现行为完全等价。二、适用场景官方文档给出的典型用途包括编写操作 Loki 数据库的工具以数据形式描述查询便于工具解析、生成和转换创建存储过程式的命名查询把频繁使用的复杂查询保存下来按名调用为数据抽取extraction目的转换数据配合 map、mapReduce、eqJoin 等步骤做格式化或提取可扩展自定义元数据通过附加属性描述 transform 的用途、参数说明等信息。Transform 不是必选功能也不打算取代方法链式调用它的价值在于抽象和整理重复性查询使代码更整洁、查询更动态。三、Transform 的结构有序步骤数组一个 transform 是一个有序的 step 对象数组每个 step 对应一次链式操作。支持的 step 类型如下全部可在Resultset.prototype.transform的 switch 分支中找到对应实现step 类型对应链式方法说明findrs.find(value)基于 mongo 风格查询对象过滤可利用索引wherers.where(value)传入 JavaScript 过滤函数simplesortrs.simplesort(property, desc)按属性名排序compoundsortrs.compoundsort(value)多属性组合排序sortrs.sort(value)传入自定义比较函数排序limitrs.limit(value)限制结果数offsetrs.offset(value)跳过前 N 条updaters.update(value)对结果集中的文档执行更新函数removers.remove()从集合中删除结果集中的文档maprs.map(value, dataOptions)映射到新的匿名集合mapReducers.mapReduce(mapFunction, reduceFunction)map reduce 数据转换eqJoinrs.eqJoin(joinData, leftJoinKey, rightJoinKey, mapFun, dataOptions)左连接两套数据其中limit、offset、map、eqJoin、mapReduce等步骤会产生新的 Resultset或直接返回数据数组源码中会重新赋值rs引用rs rs.limit(...)以保证后续步骤在正确的对象上继续执行。步骤的参数可以硬编码也可以使用 LokiJS 专门为 transform 提供的参数替换机制。四、最简单的一步 transformvar tx [ { type: find, value: { owner: odin } } ];这是一个只包含find步骤的 transform硬编码了查询条件owner odin。它可以直接传入chain()执行。五、命名保存 transformaddTransform 与 transforms 存储上面的tx可以命名保存到 Collection 中userCollection.addTransform(OwnerFilter, tx);addTransform的实现src/lokijs.js会先检查同名 transform 是否已存在若存在则抛出Error(a transform by that name already exists)否则存入this.transforms[name]。transforms是一个以名字为键的普通对象在 Collection 构造时初始化为{}见 src/lokijs.js。与之配套的 Collection API 还有getTransform(name)按名取回 transform 定义src/lokijs.jssetTransform(name, transform)覆盖更新一个已存在的命名 transformsrc/lokijs.jsremoveTransform(name)删除命名 transformsrc/lokijs.js。关键点transforms 随 Collection 一起序列化。在 Collection 的序列化/复制逻辑中可以看到copyColl.transforms coll.transforms的拷贝行为src/lokijs.js因此命名 transform 会随数据库的 save/load 周期保留下来这正是存储过程式命名查询能力的根基。六、执行 transform 的两种方式命名保存后transform 可以通过名字执行userCollection.chain(OwnerFilter).data();也可以直接传入原始 transform 数组执行无需事先保存userCollection.chain(tx).data();从源码看Collection.prototype.chain(transform, parameters)src/lokijs.js会先实例化一个新的Resultset然后委托给rs.transform(transform, parameters)。Resultset.prototype.transform内部若收到字符串参数会先从this.collection.transforms中按名查找若既不是名字也不是数组typeof transform ! object || !Array.isArray(transform)则抛出Error(Invalid transform)src/lokijs.js。七、参数化机制[%lktxp] 前缀Transform 参数化的规则是transform 中任何以字符串[%lktxp]开头的对象属性右值right-hand value都会被当作参数占位符在执行时从传入的params对象中取值替换。例如var tx [ { type: find, value: { owner: [%lktxp]OwnerName } } ];执行时需要传入包含对应参数的 params 对象var params { OwnerName: odin }; userCollection.chain(tx, params).data();或按名字执行userCollection.chain(OwnerFilter, params).data();替换值的类型可以是任意数据类型字符串、数字、对象、函数……。参数扫描是递归的源码中的Utils.resolveTransformObjectsrc/lokijs.js会遍历 step 对象的整个对象层级凡发现typeof subObj[prop] string subObj[prop].indexOf([%lktxp]) 0即取出参数名substring(8)并在params.hasOwnProperty(pname)成立时完成替换同时递归深度被限制在10 层if (depth 10) return subObj;避免过度深挖。顶层的Utils.resolveTransformParamssrc/lokijs.js会先对每个 step 执行clone(transform[idx], shallow-recurse-objects)克隆再在克隆副本上做扫描替换因此原始 transform 定义不会被修改——这一点在测试中也有明确断言见 spec/generic/transforms.spec.js。八、where 过滤器 参数化解决函数无法序列化的问题where 过滤函数不能直接保存进数据库函数无法可靠地 JSON 序列化/反序列化。但如果确实需要这类过滤可以借助 transform 加参数化机制把函数放在执行时传入的 params 里从而让保存的 transform与执行期函数干净地分离var tx [ { type: where, value: [%lktxp]NameFilter } ]; items.addTransform(ByFilteredName, tx); // 以下调用可以在保存/加载周期之后立即发生 // 这里使用匿名函数也可以使用具名函数引用 var params { NameFilter: function(obj) { return (obj.name.indexOf(nir) ! -1); } }; var results items.chain(ByFilteredName, params).data();由于where步骤的value是一个[%lktxp]参数占位符整个 transform 定义保持纯数据、可序列化而真正的过滤函数只在执行时注入。九、多步 transform 与终止链的步骤Transform 可以包含多个按顺序执行的步骤。幕后chain()会实例化一个 Resultset把每个步骤当作独立的链式操作依次调用最后返回结果。需要特别注意某些内置步骤如mapReduce会通过返回数据数组来终止 transform/链此时chain()的返回值就是实际数据而不是需要再调用.data()解析的 Resultset。源码注释也明确提示make any of these last in transform stepssrc/lokijs.js。同理update、remove会直接修改/删除当前过滤结果集中的文档应谨慎使用源码注释同样有警告见 src/lokijs.js。一个更复杂的、把三种参数化步骤组合在一起的多步 transformvar tx [ { type: find, value: { owner: { $eq: [%lktxp]customOwner } } }, { type: where, value: [%lktxp]customFilter }, { type: limit, value: [%lktxp]customLimit } ]; function myFilter(obj) { return (obj.name.indexOf(nir) ! -1); } var params { customOwner: odin, customFilter: myFilter, customLimit: 100 } users.chain(tx, params);正如上例所示LokiJS 会扫描对象层级最多 10 层对所有形如参数占位符的右值做替换并用 params 中对应键的值可以是任意类型替换掉那个字符串。注意这里的find步骤内嵌了$eq操作符与参数化的组合这说明参数替换发生在查询对象内部任意层级不只限于最外层。十、多参数步骤simplesort / mapReduce / eqJoin / remove某些步骤除了type和value外还需要具名的专用属性。下面的例子单独展示了这些步骤它们不一定适合放在同一个 transform 中更多是各自独立的形态var step1 { type: simplesort, property: name, desc: true }; var step2 { type: mapReduce, mapFunction: myMap, reduceFunction: myReduce }; var step3 { type: eqJoin, joinData: jd, leftJoinKey: ljk, rightJoinKey: rjk, mapFun: myMapFun }; var step4 { type: remove }对应源码分支如下src/lokijs.jssimplesortrs.simplesort(step.property, step.desc || step.options)—— 第二个参数既可以是布尔desc也可以是包含desc的 options 对象mapReducers.mapReduce(step.mapFunction, step.reduceFunction)eqJoinrs.eqJoin(step.joinData, step.leftJoinKey, step.rightJoinKey, step.mapFun, step.dataOptions)removers.remove()无需额外参数。十一、在 DynamicView 中使用 transformbranchResultset 分页提取Transform 还可以作为DynamicView 的提取extraction方法。典型场景是先建一个 DynamicView 保存广义过滤结果集再由用户自定义的 transform 从这个视图里快速提取、分页。该功能由DynamicView.prototype.branchResultset(transform, parameters)提供src/lokijs.js它既能接受原始 transform 数组也能接受保存在 Collection 层的命名 transform。官方示例var db new loki(test); var coll db.addCollection(mydocs); var dv coll.addDynamicView(myview); var tx [ { type: offset, value: [%lktxp]pageStart }, { type: limit, value: [%lktxp]pageSize } ]; coll.addTransform(viewPaging, tx); // add some records var results dv.branchResultset(viewPaging, { pageStart: 10, pageSize: 10 }).data();一个值得强调的区别分支branching——进而你的 transform 结果——只反映分支那一刻视图的状态。这些 transform 是提取extracts并不会被 DynamicView 内部用于维护视图本身。DynamicView 始终新鲜而分支出来的 Resultset 是即时求值的快照。测试 spec/generic/transforms.spec.js 中就有「动态视图命名 transform」用例验证了dv.branchResultset(tx)与按名调用dv.branchResultset(desc4limit2)的等价行为。十二、为自定义方案附加元数据metaTransform 的一个典型用途是用户驱动的解决方案由 UI 层来构造、管理和执行这些 transform。这种场景下你可能希望给 transform 附加自定义元数据进一步描述 transform、步骤或参数的语义未知 type 的步骤会被忽略任何带type但 LokiJS 不认识的步骤都会被switch的default分支跳过src/lokijs.js。因此你可以约定 transform 的第一个步骤总是type: meta用它携带作者、描述、必需参数说明等信息执行时自然被跳过、零副作用。步骤可以带超出必需的额外属性每个 step 除了定义好的必填属性外还可以嵌入步骤描述、最后修改日期等元数据这些额外字段在执行过程中会被原样忽略不影响逻辑。这一机制让 transform 既可作为可执行的查询定义又可充当带注释的查询元数据载体。十三、测试佐证源码级验证仓库的 spec/generic/transforms.spec.js 为本文大部分结论提供了可运行验证basic find transform单步findtransform 经items.chain(tx).data()返回正确结果数L14-L30basic multi-step transformfindwhere多步串联验证步骤按顺序执行L32-L54parameterized find[%lktxp]OwnerName占位符在chain(tx, params)中被替换L56-L76parameterized find with $and/$or参数化在$or嵌套数组内部同样生效L78 起参数替换不修改原始 transform执行后断言tx2[0].value.age.$gt仍为[%lktxp]minimumAgeL202-L204named find transform / dynamic view named transform验证命名 transform 的存取与在 DynamicView 分支上的使用。你还可以参考 examples/quickstart-transforms.js 中的完整可运行示例以及查询操作符$eq、$gte等的完整讲解 tutorials/Query Examples.mdtransform 的find步骤复用这套查询语法。总结LokiJS Collection Transforms 以很小的额外开销为数据自动化转换建立了一套流程它把 Resultset 链式查询抽象为有序的 step 对象数组支持find、where、simplesort、compoundsort、sort、limit、offset、update、remove、map、mapReduce、eqJoin十二类步骤通过addTransform可命名保存并随数据库持久化形成存储过程式命名查询[%lktxp]参数化机制支持任意数据类型的注入含函数从而绕过 where 函数无法序列化的限制在 DynamicView 上通过branchResultset还能对始终新鲜的视图做即时的分页、提取。它不是链式查询的替代品而是对重复性查询的抽象与组织——用于整洁、动态、可持久化的数据操作场景。赞分享数据库后端【免费下载链接】LokiJSjavascript embeddable / in-memory database项目地址https://gitcode.com/gh_mirrors/lo/LokiJS点击查看免费下载相关推荐Akka Persistence 插件机制完全指南可插拔的 Journal、快照存储与持久化查询后端Akka Persistence 插件机制完全指南可插拔的 Journal、快照存储与持久化查询后端 Akka Persistence 是 Akka 提供的持后端并发编程异步编程图片占位符之外的惊喜BlurHash 的 3 大隐藏用途——敏感图遮挡、平均色提取与着色阴影图片占位符之外的惊喜BlurHash 的 3 大隐藏用途——敏感图遮挡、平均色提取与着色阴影 BlurHash https://link.gitcode.co图像处理如何让微信聊天记录成为你的数字记忆银行WeChatMsg完全使用指南如何让微信聊天记录成为你的数字记忆银行WeChatMsg完全使用指南 你是否曾有过这样的经历更换手机时那些珍贵的聊天记录瞬间消失想要回顾某段重要对话却上一篇ImmortalWrt主题定制教程打造个性化管理界面下一篇Rector条件语句简化提升代码执行效率创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

Rust 智能指针之 `Rc<T>`:引用计数与多所有权共享实战(TRPL 第 15 章)

Rust 智能指针之 `Rc<T>`:引用计数与多所有权共享实战(TRPL 第 15 章)

教程文档 【免费下载链接】book The Rust Programming Language 项目地址&#xff1a; https://gitcode.com/gh_mirrors/bo/book 点击查看 免费下载 导读 Rc<T>&#xff08;Reference Counting&#xff0c;引用计数&#xff09;是 Rust 标准库中用于实现多所有权的核心…

2026/10/11 3:59:00 阅读更多 →
Vulkan稀疏资源实战:内存分配、稀疏绑定与虚拟纹理流式加载

Vulkan稀疏资源实战:内存分配、稀疏绑定与虚拟纹理流式加载

如果你在项目里做过 8K、16K 分辨率贴图的流式加载&#xff0c;那你多半遇到过一个很现实的局面&#xff1a;整张纹理一次性往显存里塞&#xff0c;要么分配失败&#xff0c;要么可用显存直接被吃掉一大块。Vulkan 内存分配把显存管理权完全交到应用层&#xff0c;堆、类型、属…

2026/10/9 13:31:29 阅读更多 →
10 分钟给 Windows 11 去臃肿:Win11Debloat 新手上手指南

10 分钟给 Windows 11 去臃肿:Win11Debloat 新手上手指南

10 分钟给 Windows 11 去臃肿&#xff1a;Win11Debloat 新手上手指南 【免费下载链接】Win11Debloat A simple, lightweight PowerShell script that allows you to remove pre-installed apps, disable telemetry, as well as perform various other changes to declutter and…

2026/10/9 13:31:38 阅读更多 →

最新新闻

Java面试翻车现场:HashMap、线程池、JVM深度拆解

Java面试翻车现场:HashMap、线程池、JVM深度拆解

“严肃面试官 vs 搞笑水货程序员谢飞机&#xff08;本名王大瓜&#xff09;——互联网大厂 Java 面试实录与技术拆解”&#xff0c;光看这个标题你可能觉得是个段子&#xff0c;但我在现场的感觉是&#xff1a;这简直就是一场喜剧外壳下的技术解剖课。谢飞机&#xff0c;简历上…

2026/10/11 3:58:54 阅读更多 →
RT-Thread—STM32—环境搭建

RT-Thread—STM32—环境搭建

RT-Thread——STM32——环境搭建 概述 本教程主要根据官方推荐的教程进行环境搭建&#xff0c;但是在打包方面按照自己的习惯进行了打包。 RT-Thread官网有特别详细的教程&#xff0c;这儿就不详细说明RT-Thread官网 软件准备 MDK528a (Keil5)CubeMx_v5-2-0STM32CubeMx的支持…

2026/10/11 3:58:54 阅读更多 →
智能工厂建设方案全解析:从ISA-95架构到MES/SCADA系统选型

智能工厂建设方案全解析:从ISA-95架构到MES/SCADA系统选型

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 3:58:54 阅读更多 →
JavaScript核心考点索引:从原型链到事件循环的面试体系

JavaScript核心考点索引:从原型链到事件循环的面试体系

做前端面试辅导这几年&#xff0c;我收到最多的问题不是“这道题答案是什么”&#xff0c;而是“面对这么多考点&#xff0c;到底哪些才值得深学”。JavaScript知识体系太庞杂了&#xff0c;从语言基础到浏览器原理&#xff0c;从手写代码到性能优化&#xff0c;随便拉一个列表…

2026/10/11 3:58:53 阅读更多 →
第1章,[Win32 章节]:编程环境与 MSDN

第1章,[Win32 章节]:编程环境与 MSDN

专栏导航 上一篇&#xff1a;第1章&#xff0c;[Win32 章节]&#xff1a;编程语言与框架选择 回到目录 下一篇&#xff1a;第1章 &#xff1a;第一个 Win32 程序&#xff0c;头文件 本专栏课件 关于本专栏课件的获取方法&#xff0c;请参考下述课节。 参考课节&#xff1a…

2026/10/11 3:58:53 阅读更多 →
开源吐槽大会:开发者从项目吐槽中学到的避坑与成长之道

开源吐槽大会:开发者从项目吐槽中学到的避坑与成长之道

1. 这个标题是怎么“火”起来的&#xff1a;开源吐槽大会的由来与定位如果你混迹开发者社区有一阵子&#xff0c;大概率见过这类帖子&#xff1a;“某某开源项目到底能不能用”“维护者又跑路了”“README吹得天花乱坠&#xff0c;一跑就崩”。这些帖子往往评论区最热闹&#x…

2026/10/11 3:57:53 阅读更多 →

日新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介&#xff1a;基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码&#xff0c;面向计算机相关专业课程设计与期末大作业学生&#xff0c;以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程&#xff0c;…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程&#xff1a;键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化&#xff0c;十个新手有八个栽在"往输入框里填东西"这件事上&#xff1a;要么填不进去&#xff0c;要么填了一半&#xff0c;要么直接把原来内容追加在后面。这背后的根因&…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程&#xff1a;阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀&#xff1a;什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面&#xff0c;跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/11 0:00:27 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介&#xff1a;基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码&#xff0c;面向计算机相关专业课程设计与期末大作业学生&#xff0c;以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程&#xff0c;…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程&#xff1a;键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化&#xff0c;十个新手有八个栽在"往输入框里填东西"这件事上&#xff1a;要么填不进去&#xff0c;要么填了一半&#xff0c;要么直接把原来内容追加在后面。这背后的根因&…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程&#xff1a;阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀&#xff1a;什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面&#xff0c;跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/11 0:00:27 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 5:23:50 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 21:32:20 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 10:38:42 阅读更多 →