十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Fuse.js 模糊搜索完全指南:从 Bitap 匹配到分词、扩展操作符与逻辑查询

Fuse.js 模糊搜索完全指南:从 Bitap 匹配到分词、扩展操作符与逻辑查询 前端搜索引擎【免费下载链接】FuseLightweight fuzzy-search, in JavaScript项目地址https://gitcode.com/gh_mirrors/fu/Fuse点击查看免费下载Fuse.js 是一个零依赖的轻量级 JavaScript 模糊搜索库核心解决输入错误或拼写不完整时仍能命中目标的搜索需求。本文以本仓库文档站首页docs/index.md为主线完整展开其快速上手、模糊搜索Bitap 算法、分词搜索、扩展搜索操作符与逻辑查询五大能力并结合仓库源码说明各配置项的默认值与底层原理。读完后你将能在浏览器、Node.js 与 Deno 中搭建一套支持错别字容错、多词查询、精确过滤的结构化搜索方案。一分钟快速上手Fuse.js 的使用极其简洁构造Fuse实例时传入文档数组与索引键keys随后调用search()即可得到按相关度排序的结果。import Fuse from fuse.js const books [ { title: Old Mans War, author: John Scalzi }, { title: The Lock Artist, author: Steve Hamilton }, { title: JavaScript Patterns, author: Stoyan Stefanov } ] const fuse new Fuse(books, { keys: [title, author], includeScore: true }) fuse.search(jon) // [{ item: { title: Old Mans War, author: John Scalzi }, refIndex: 0, score: 0.25 }] fuse.search(patterns) // [{ item: { title: JavaScript Patterns, ... }, refIndex: 2, score: 0.0 }]注意两个细节查询jon能命中John Scalzi错字容错且得分0.25而patterns的完美命中得分是0.0。Fuse.js 的分数范围是 010 表示完美匹配1 表示完全不匹配refIndex指向该文档在原数组中的下标。首页列出的核心特性可归纳为四层能力后续章节逐一展开模糊搜索基于 Bitap 算法的容错匹配分词搜索把多词查询拆成词元逐个模糊匹配并用 IDF 加权排序扩展搜索支持精确、前缀、后缀、排除、包含等 unix 风格操作符逻辑搜索用$and/$or表达式构造结构化查询。此外还支持加权键boost 指定字段、嵌套搜索点号/数组记法或自定义getFn、零依赖跨端运行以及完整版约 8.6 kB gzip与基础版约 6.8 kB gzip两种构建产物。上述体积数据与特性清单均出自 docs/index.md 与 docs/getting-started.md当前仓库版本为 7.4.2见 package.json。安装、导入与构建选择包管理器安装npm install fuse.js也支持 pnpm、yarn 与 bunpnpm add fuse.js yarn add fuse.js bun add fuse.js模块导入同时支持 ESM 与 CommonJS 两种风格// ESM import Fuse from fuse.js // CommonJS const Fuse require(fuse.js)仓库 package.json 的exports字段定义了多入口映射fuse.js默认指向 ESM 的dist/fuse.mjs与 CJS 的dist/fuse.cjsrequire与import可自动解析到对应格式。零依赖意味着安装后没有传递依赖负担包体即为全部代码且sideEffects: false声明了纯模块语义便于摇树优化。两种构建完整版与基础版构建包含能力gzip 体积完整版Full模糊 扩展 逻辑 分词搜索约 8.6 kB基础版Basic仅模糊搜索约 6.8 kB导入路径与构建文件对应关系如下// 完整版默认 import Fuse from fuse.js // 基础版 import Fuse from fuse.js/basic // 压缩变体 import Fuse from fuse.js/min import Fuse from fuse.js/min-basicpackage.json 中./basic、./min、./min-basic三个子路径出口与之一一对应构建文件统一落在dist/目录UMDCommonJSES Module完整版fuse.jsfuse.cjsfuse.mjs基础版fuse.basic.jsfuse.basic.cjsfuse.basic.mjs完整版压缩fuse.min.js—fuse.min.mjs基础版压缩fuse.basic.min.js—fuse.basic.min.mjs基础版不包含扩展搜索与分词搜索。如果你在基础版上仍需这两项能力可以在运行时通过插件机制Fuse.use()注册详见 src/core/register.ts 对应的注册入口import Fuse from fuse.js/basic import { ExtendedSearch } from fuse.js Fuse.use(ExtendedSearch)CDN 与 Deno浏览器可直接通过 CDN 以script标签引入完整版或以script typemodule方式引入 ESM 产物Deno 环境可配合类型声明文件使用dist/fuse.min.mjs构建产物具体 URL 请以安装时的版本为准完整示例见 docs/getting-started.md 的 CDN 与 Deno 小节。构造选项与默认值从源码看配置全貌Fuse构造函数的签名是new Fuse(docs, options?, index?)见 src/core/index.tsoptions 缺省时逐层合并默认配置。所有默认值集中定义在 src/core/config.ts分为四组基础选项BasicOptions选项默认值说明isCaseSensitivefalse是否大小写敏感ignoreDiacriticsfalse是否忽略变音符如é可匹配eincludeScorefalse是否在结果中附带scorekeys[]要搜索的字段键shouldSorttrue是否按相关度排序结果sortFn内置默认按分数升序、同分按原始下标排序匹配选项MatchOptions选项默认值说明includeMatchesfalse是否返回匹配位置信息findAllMatchesfalse完美匹配后是否继续扫描全文高亮需要minMatchCharLength1低于该长度的匹配片段不返回模糊选项FuzzyOptions选项默认值说明location0模式在文本中的期望出现位置threshold0.6模糊度阈值0 要求完全匹配1 匹配一切distance100距location多远开始惩罚到排除高级选项AdvancedOptions选项默认值说明useExtendedSearchfalse启用扩展搜索操作符useTokenSearchfalse启用分词搜索tokenMatchany分词匹配模式anyOR或allANDgetFn内置自定义字段取值函数ignoreLocationfalse是否关闭位置计分ignoreFieldNormfalse是否忽略字段长度归一化fieldNormWeight1字段长度归一化的强度系数构造时src/core/index.ts 的构造函数会检查两个特性开关当useExtendedSearch: true或useTokenSearch: true而当前构建未启用对应能力时直接抛出错误错误消息定义见 src/core/errorMessages.ts——这就是基础版使用分词搜索会报错的源码级原因。模糊搜索Bitap 算法与三参数控制模糊搜索是 Fuse.js 的根基。它使用改进的 Bitap 算法做近似字符串匹配容忍错字、字符换位与缺字本质上在每个文本位置上计算模式与文本的编辑距离并用位运算加速每个搜索词的模式长度上限为 32 字符这也是分词搜索存在的原因之一。算法输出 01 的模糊分0 完美匹配、1 完全不匹配。const fuse new Fuse([apple, banana, orange], { includeScore: true }) fuse.search(aple) // [{ item: apple, refIndex: 0, score: 0.25 }]aple缺少一个p依然命中apple。编辑距离的可视化推演可参考仓库文章 docs/articles/how-fuzzy-search-works.md。threshold、location、distance 三者如何协同threshold默认0.6模糊分阈值。0.0要求完美匹配1.0匹配任意内容location默认0模式在文本中的预期位置远离该位置的匹配会被惩罚distance默认100距location多远开始惩罚到排除。有效搜索窗口的计算公式为threshold × distance 距 location 的最大偏移量用默认值0.6 × 100 60模式必须出现在距位置 0 的 60 个字符以内才可能命中。原文档给出的例子很直观——在句子Fuse.js is a powerful, lightweight fuzzy-search library, with zero dependencies中搜索zero它出现在第 62 个字符处恰好超出窗口因此不会命中。由此得出一个实战要点如果字段是长文本默认配置只会扫描开头约 60 个字符。此时应增大distance或直接设置ignoreLocation: true关闭位置计分让模式在文本任意位置都可命中。其余匹配相关选项isCaseSensitive默认false开启后比较区分大小写ignoreDiacritics默认false开启后忽略重音如é可匹配e实现见 src/helpers/diacritics.tsfindAllMatches默认false即使已找到完美匹配也继续扫描到文本末尾用于高亮所有匹配位置minMatchCharLength默认1只返回长度超过该值的匹配设为2可忽略单字符匹配。最终评分模糊分 × 键权重 × 字段长度归一化最终相关度分数由三部分组合计算逻辑见 src/core/computeScore.ts模糊分上文 Bitap 算法的原始输出键权重每个键可配weight默认1权重更高对排序影响更大内部会做归一化相关实现见 src/tools/fieldNorm.ts 与 src/tools/KeyStore.ts字段长度归一化短字段的命中比长字段更显著例如标题中的命中权重高于长描述中的相同命中。两个调节开关ignoreFieldNorm: true让字段长度不再影响分数fieldNormWeight调节归一化强度0等价于忽略、0.5减弱、2.0放大。开启includeScore: true即可在结果中看到最终分数。键权重相关的类型定义weight、getFn见 src/types.ts 的FuseOptionKeyObject。分词搜索多词查询的正确打开方式默认模糊搜索把整个查询当作一个模式适合javscript→JavaScript这种单词纠错。但面对javascript design patterns这类多词查询单个 Bitap 搜索会撞上 32 字符上限也无法逐词独立匹配。何时使用分词搜索搜索框用户输入react state management这类自然多词查询文档检索标题、描述、正文多词同时相关自动补全按命中的词元数量排序罕见词加权更高。开启方式const fuse new Fuse(docs, { useTokenSearch: true, keys: [title, author, description] }) fuse.search(javascrpt paterns) // → [{ item: { title: JavaScript Patterns, ... }, score: 0.12 }]两个词都有拼写错误仍能命中。原有的includeScore、includeMatches、键权重、threshold、limit、shouldSort等选项全部照常生效。内部四步流水线分词Tokenization默认使用 unicode 感知的正则/[\p{L}\p{M}\p{N}_]/gu把查询拆成词元开箱即支持 CJK、西里尔、希腊、阿拉伯、希伯来、天城文等文字可用tokenize选项覆盖见下文逐词模糊匹配每个词元对每个字段独立执行 Bitap 匹配且强制ignoreLocation: true词元可出现在字段任意位置——多词查询不再受 32 字符模式上限约束IDF 加权构造时即构建倒排索引实现见 src/search/token/InvertedIndex.ts每个词元的 IDF 权重采用 BM25 风格公式idf log(1 (fieldCount - docFreq 0.5) / (docFreq 0.5))罕见词出现在更少文档中权重更高命中一个特征鲜明的词比命中一个随处可见的词贡献更大分数合并各词元得分按 IDF 权重做加法合并再归一化到 01 区间0 为完美匹配。关键行为部分命中仍返回3 个词命中 2 个的文档依然在结果里但排在 3 词全中的文档之后词序无关patterns javascript与javascript patterns结果完全一致逐词容错每个词独立模糊匹配任一词的错字都被容忍长查询可用6 个词的查询会执行 6 次独立的 Bitap 搜索每次都在 32 字符上限之内。匹配模式 tokenMatchany与all默认tokenMatch: anyOR 语义命中任意一个词即返回该记录适合排序搜索场景——把最佳匹配排最前部分匹配仍浮出水面。需要过滤语义时改用tokenMatch: allAND 语义只有每个查询词都在该记录中命中才返回即加词即收窄列表。const list [red shirt, red hat, blue shirt] new Fuse(list, { useTokenSearch: true }) .search(red shirt) .map((r) r.item) // any默认[red shirt, red hat, blue shirt] ← 命中任一词即可 new Fuse(list, { useTokenSearch: true, tokenMatch: all }) .search(red shirt) .map((r) r.item) // all[red shirt] ← 两词都须命中注意all是按整条记录跨字段评估的——每个词只需出现在记录的任意字段或数组元素中即可而不是要求同一字段同时包含所有词const products [ { title: Red, description: cotton shirt }, // red 与 shirt 分处不同字段 { title: Red dress, description: silk } ] new Fuse(products, { useTokenSearch: true, tokenMatch: all, keys: [title, description] }) .search(red shirt) .map((r) r.item) // → [{ title: Red, description: cotton shirt }] 第二条记录里没有任何 shirt两点补充all只改变哪些记录被返回不改变幸存记录的排序IDF 计分不变tokenMatch仅对分词搜索生效与逻辑搜索的$and/$or操作符是两套独立机制——后者组合的是按字段划分的子句而非一个查询中的多个词并且逐词模糊匹配依然生效拼错的词只要足够接近就计入 AND 条件。自定义分词器 tokenize默认 tokenizer 把任意 unicode 字母、附加符号、数字视为词的一部分对绝大多数自然语言文本都够用但两类场景需要覆盖含内部标点的词元如node.js、c、U.S.A、文件路径、hashtag——传一个把这些标点包含进词元的自定义正则需要分词的中文/泰文默认会把每个连续脚本段当一个词元可以传入基于Intl.Segmenter的函数做真正的按词切分。正则形式必须带g全局标志否则每段文本只取第一个词元开发构建中缺失g会输出一次性console.warnconst fuse new Fuse(docs, { useTokenSearch: true, keys: [text], // 把点、加号、短横线保留在词元内部 tokenize: /[\w.-]/g }) fuse.search(node.js) // 命中包含字面量 node.js 的文档函数形式适用于 CJK 等非空格分词语言用Intl.Segmenter做 locale-aware 切词并通过isWordLike过滤标点与空白段const segmenter new Intl.Segmenter(zh, { granularity: word }) const fuse new Fuse(docs, { useTokenSearch: true, keys: [text], tokenize: (text) Array.from(segmenter.segment(text), (s) s.isWordLike ? s.segment : null) .filter(Boolean) })函数形式接收的文本是经过大小写折叠与去变音符之后的字段/查询文本依isCaseSensitive/ignoreDiacritics而定必须返回string[]且保证确定性——不确定的分词器会静默破坏文档频率统计。另外函数分词器无法通过postMessage传输到 Web Worker因此FuseWorker 不支持函数形式分词器相关说明见 docs/web-workers.md。动态集合更新与性能分词搜索的倒排索引在构造时构建并在集合变化时同步维护const fuse new Fuse(docs, { useTokenSearch: true, keys: [title] }) // 新增文档会同步更新倒排索引 fuse.add({ title: New Book }) // 删除文档同样更新索引 fuse.remove((doc) doc.title Old Book)新增/删除的索引维护逻辑在 src/core/index.ts 中体现add追加文档并调用倒排索引的addToInvertedIndexremove调用removeAndShiftInvertedIndex处理下标偏移。仓库还提供了可复跑的基准脚本bench/token-search.mjs。原文档在 2 个键title body的随机文档上测得指标100 篇1,000 篇5,000 篇索引创建开销2.5x5.2x5.5x单词查询开销1.8x1.8x1.7x多词查询开销1.3x1.3x1.2x索引创建是一次性成本5,000 篇约 46ms查询开销 1.21.8x 主要来自每个查询词各自执行一次 Bitap 搜索而倒排索引本身的查找是 O(1)。分词搜索仅包含在完整版构建中基础版使用useTokenSearch: true会直接抛错与构造函数中的特性检查一致。扩展搜索unix 风格操作符扩展搜索让查询字符串携带精确、前缀、后缀、排除、包含等操作符开启方式为useExtendedSearch: trueconst fuse new Fuse(list, { useExtendedSearch: true, keys: [title, author] })操作符一览Token匹配类型含义jscript模糊匹配模糊匹配jscriptscheme精确匹配恰好是schemepython包含匹配包含python!ruby反向精确匹配不包含ruby^java前缀精确匹配以java开头!^earlang反向前缀匹配不以earlang开头.js$后缀精确匹配以.js结尾!.go$反向后缀匹配不以.go结尾操作符的解析实现见 src/search/extended/parseQuery.ts各操作符的匹配器在 src/search/extended/matchers.ts测试覆盖见 test/extended-search.test.js。组合规则空格 AND、竖线 OR空格表示AND所有词元都必须匹配竖线|表示OR任一组匹配即可。// 同时包含 Man 与 Old或者以 Artist 结尾 fuse.search(Man Old | Artist$)解析为两个 OR 组①ManANDOld包含 Man 且包含 Old②Artist$以 Artist 结尾。带空格的短语双引号引用fuse.search(scheme language) // 精确匹配 scheme language fuse.search(^hello world) // 包含匹配 hello world完整示例const books [ { title: Old Mans War, author: John Scalzi }, { title: The Lock Artist, author: Steve Hamilton }, { title: Artist for Life, author: Michelangelo } ] const fuse new Fuse(books, { useExtendedSearch: true, keys: [title] }) // 以 Old 开头 AND 模糊匹配 war fuse.search(^Old war) // 不包含 Artist AND 以 Old 开头 fuse.search(!Artist ^Old) // 以 Artist 结尾 OR 包含 War fuse.search(Artist$ | War)扩展搜索操作符还可以内嵌到逻辑查询中使用详见下节。与分词搜索相同扩展搜索只包含在完整版构建中基础版可通过Fuse.use(ExtendedSearch)运行时注册启用。逻辑搜索$and / $or 结构化查询当搜索条件需要结构化组合时search()的参数可以从字符串升级为表达式对象解析实现在 src/core/queryParser.ts。$and全部子句须匹配const result fuse.search({ $and: [{ author: abc }, { title: xyz }] })采用短路求值——第一个表达式为假则跳过其余。$or任一子句匹配const result fuse.search({ $or: [{ author: abc }, { author: def }] })同样短路求值——第一个表达式为真即跳过其余。任意深度嵌套const result fuse.search({ $and: [ { title: old war }, { $or: [ { title: ^lock }, { title: !arts } ] } ] })隐式 AND对象内逗号分隔的表达式列表默认执行隐式 AND当同一字段或同一操作符在多个表达式中出现时用显式$and更清晰。含字面点的键$path 与 $val如果数据中的键本身就含点例如first.name是单个键而非嵌套路径逻辑查询需要使用$path与$val显式声明路径与取值const books [ { title: Old Mans War, author: { first.name: John, last.name: Scalzi } } ] const fuse new Fuse(books, { keys: [title, [author, first.name], [author, last.name]] }) const result fuse.search({ $and: [ { $path: [author, first.name], $val: jon }, { $path: [author, last.name], $val: scazi } ] })与扩展搜索联用开启useExtendedSearch后逻辑表达式中的字符串值会被当作扩展搜索模式解析const fuse new Fuse(books, { useExtendedSearch: true, keys: [title, color] }) const result fuse.search({ $and: [ { title: old war }, // 模糊匹配 old war { color: blue }, // 精确包含 blue { $or: [ { title: ^lock }, // 以 lock 开头 { title: !arts } // 不包含 arts ] } ] })逻辑查询的完整测试见 test/logical-search.test.js。嵌套字段、数组与自定义取值首页特性清单中的嵌套搜索由keys的三种写法支撑类型定义见 src/types.ts 的FuseOptionKey字符串键title直接取值点号路径author.name按嵌套路径取值数组路径[author, name]等价于点号路径键对象{ name: title, weight: 2 }附加权重或自定义getFn。默认取值函数实现于 src/helpers/get.ts它递归遍历路径遇到数组会对每个元素分别取值并记录下标返回{ v, i }结构以支持数组元素级别的匹配位置定位最终把字符串、数字、布尔、bigint 统一字符串化。这也是为什么includeMatches能精确到数组中的哪个元素命中了匹配。当数据结构特殊如字段名动态、需先做清洗、或要拼接多个字段再搜索时通过getFn提供自定义取值逻辑const fuse new Fuse(docs, { keys: [ { name: title, weight: 2, getFn: (book) book.meta?.title ?? }, description ] })加权键的实际影响在排序阶段体现权重越高的键命中后对最终分数的贡献越大配合fieldNormWeight即可精细调控标题优先于描述这类排序策略仓库中另有 test/key-weight-normalization.test.js 验证权重归一化行为。进阶资源导航本文覆盖了首页 docs/index.md 全部主题各主题的完整专项文档与配套资源如下分步安装与构建细节docs/getting-started.md模糊搜索参数与评分详解docs/fuzzy-search.md分词搜索完整指南docs/token-search.md扩展搜索操作符手册docs/extended-search.md逻辑查询语法docs/logical-search.md编辑距离的交互式可视化讲解docs/articles/how-fuzzy-search-works.mdWeb Worker 多线程搜索方案docs/web-workers.md性能基准方法论docs/performance.md可运行脚本见 bench/search.mjs 与 bench/index-creation.mjs配套测试模糊搜索 test/fuzzy-search.test.js、扩展搜索 test/extended-search.test.js、逻辑搜索 test/logical-search.test.js、分词搜索 test/token-search.test.js实际动手时建议从三组默认值出发微调搜索范围受threshold、distance、location约束长文本记得调大distance或开启ignoreLocation多词场景优先useTokenSearch并按排序 or 过滤选择tokenMatch结构化筛选场景组合useExtendedSearch与$and/$or。这套配置组合可以覆盖从搜索框自动补全到复杂文档检索的绝大多数前端搜索需求。赞分享前端搜索引擎【免费下载链接】FuseLightweight fuzzy-search, in JavaScript项目地址https://gitcode.com/gh_mirrors/fu/Fuse点击查看免费下载相关推荐10分钟搞懂Fuse.js模糊搜索Bitap算法实战指南10分钟搞懂Fuse.js模糊搜索Bitap算法实战指南 Fuse.js是一款轻量级的JavaScript模糊搜索库能够帮助开发者轻松实现高效的文本搜索功能前端搜索引擎Sunshine 游戏串流新手教程5 步装完串出第一帧Sunshine 游戏串流新手教程5 步装完串出第一帧 书房里的游戏主机配置拉满人却在客厅手里只有一台带不动 3A 的轻薄本。想玩又懒得折腾显示硬件Su前端搜索引擎KJFrameForAndroid进阶教程自定义组件与扩展框架功能KJFrameForAndroid进阶教程自定义组件与扩展框架功能 KJFrameForAndroid是一个功能强大的Android开发框架它封装了Andr移动开发开发工具上一篇Kubescape性能测试验证安全工具的可靠性与效率下一篇如何用patch-package修复传递依赖解决深层node_modules问题的终极指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表