🔍 🔥 MDN搜索➕高亮效果学习心得

767 阅读4分钟

MDN右上角的搜索效果:展示搜索结果高亮

实现该效果主要分两个步骤:搜索 ➕ 高亮

🔍 flexSearch 搜索

flexSearch的工作原理:根据初始化(new)选项将待查询的数据集注册(add)生成如下图的数组存储的是注册时的id集合,根据搜索时(search)选项从数据集合中筛选符合条件的id集合。 image.png

引入flexSearch

👇以下代码以vue项目为例

npm install flexsearch@0.7.0

const { Index, Document, Worker } = require("flexsearch");

options 其他选项

new Index({ 
    tokenize: 'full', 
    resolution: 5,
    encode: (str: string) => str.trim().toLowerCase().split(/[\p{Z}\p{S}\p{P}\p{C}]+/u)
})

tokenize

  • 可选值 full reverse forward strict(default)
  • strictadd 阶段可以注册 encode 编码后的整个单词,在 search 阶段也会讲搜索内容进行编码
  • 其他值可以匹配encode编码后的字符子集,reverse 表示从后往前 forward表示从前往后。且有 full 包含 reverse 包含 forward ;例如 html
    • full 会被分成 "html", "htm", "ht", "h", "tml", "tm", "t", "ml", "m", "l"
    • reverse 会被分成 "l", "ml", "tml", "h", "ht", "htm", "html"
    • forward 会被分成 "h", "ht", "htm", "html"
    • strict 会被分成 website

resolution

resolution 存储索引的容器大小。定义 flexSearch.map 的长度,默认为9。

context

context is just supported by tokenizer "strict"

  • 需要配合 tokenize: 'strict' 使用
  • context.depth: 2(左图) 和 3(右图) 可以定义 flexSearch.map 的深度(树形结构) 如下图👇
  • "html 开发技术 test 学习 开发"
  • bidirectional 决定方向
  • context.bidirectional: false 会被匹配成 {html: {"开发技术": [0]}}
  • context.bidirectional: true 会被匹配成 {"开发技术": {html: [0]}}

context.depth.png bidirectional.png

charset

charset可以选择FlexSearch的内部编码策略。格式为 latin: *** 可选值有default(默认), simple, balance, advanced, extra; flexSearch 根据编码策略定义选项 encode,rtl,tokenize ,但是初始化时的 encode,rtl,tokenize 优先级高于 charset

例如 charset: 'latin:advanced'

例如 advanced 通过encode方法会将 web -> fep

encode

编码类型。选择一个内置函数或传递一个自定义编码函数。

  • 可以根据 charset 的配置选用内置函数。
  • 也可以自己定义 encode: (str: string) => str.trim().toLowerCase().split(''), 返回值为数组;会对add时的title以及search时的query进行编码。

optimize

它为索引使用一个内存优化的堆栈流。默认为true。

  • optimize: true map: {0: {}, 1: {}};
  • optimize: false map: [];

minlength

定义最少存放的字符长度,默认为1

boost

在将内容索引到索引时使用的自定义增强函数。仅支持在 strict 模式下使用。

  • function (words[], term, index) => Float 三个参数:所有单词的数组当前项当前项的索引。返回值**<1表示相关性降低,>1表示相关性增加**,可以用在降低某些搜索项的关联性。

lang

自定义语言或者内置语言,lang: {matcher, stemmer, filter}

rtl

支持从右到左的编码。

Index

初始化 new

const indexIndex = new Index({ 
    tokenize: 'full'
});

const dataList = [{title, url}];

添加注册索引 add

dataList.forEach(({ title }, index) => {
    indexIndex.add(index, title);
});

搜索 search

const indexResults = indexIndex.search({
    query: q,
    limit,
    suggest: true
});
const result = indexResults.map(
    (index: number) => (dataList || [])[index]
);
  • query 表示搜索的内容,搜索过程中会根据 option.encode 进行编码;
  • limit 限制最多搜索数量,默认100
  • suggest: true 可以实现 搜索 web abc 可以返回包含web的数据; search 得到的结果 indexResultsdataList索引index集合。

完整片段

const dataList = [
    {"title":"html 开发技术 web 学习技术 开发","url":"html/zh-CN/docs/Web"},
    {"title":"html CSS JavaScript","url":"/zh-CN/docs/Web/JavaScript"},
    {"title":"JavaScript 参考","url":"/zh-CN/docs/Web/JavaScript/Reference"},
    {"title":"Web API 接口参考","url":"/zh-CN/docs/Web/API"},
]
if(!indexIndex){
    indexIndex = new FlexSearch.Index({ 
        tokenize: 'full', 
    });
    dataList!.forEach(({ title }, i) => {
        indexIndex.add(i, title);
    });
}
let indexResults = indexIndex.search({
    query: q,
    limit: 50,
});
return indexResults.map(
    (index: number) => (dataList || [])[index]
);

Document

image.png

options

1. index/field: Array

创建多个Index模式的对象,可用于多个字段的搜索。例如 field: ['title', 'url']

2. tag: string

tag 给标签起一个名称,可以在注册或查找时给数据进行分类。add/search 中表示分类的key为该字符串。 例如:tag: 'tagName';则 add/search时候需要配置属性 tagName: 'content'

3. worker: boolean

是否开启 Worker 模式

add

添加注册时需要根据初始化时的index/field字段来注册,且可以设置tag

documentIndex.add({id: i, title, url, tag: tag})

search

1. tag

add 的时候配置了多个tag,可以选择那些tag中进行搜索。tag: [ 'content', 'link' ] 2. bool 结合 tag 进行逻辑运算。and 表示从各tag中取交集,且按照tag的先后顺序取交集。

完整代码

const dataList = [
    {"title":"html 开发技术 web 学习技术 开发","url":"html/zh-CN/docs/Web", tag: ['content', 'link']},
    {"title":"html CSS JavaScript","url":"/zh-CN/docs/Web/JavaScript", tag: ['link']},
    {"title":"JavaScript 参考","url":"/zh-CN/docs/Web/JavaScript/Reference", tag: 'link'},
    {"title":"Web API 接口参考","url":"/zh-CN/docs/Web/API", tag: 'link'},
]
if(!documentIndex){
    documentIndex = new FlexSearch.Document({ 
        tokenize: 'full',
        field: [ 'title', 'url' ],
        tag: 'tag',

    });
    dataList!.forEach(({title, url, tag}, i) => {
        documentIndex.add({id: i, title, url, tag: tag})
    });
}

const words = q.trim().toLowerCase().split(/[ ,]+/);
let indexResults: searchItem[] = [];
const documentResults = documentIndex.search(q, {
    limit,
    bool: 'and',
    tag: [ 'content', 'link' ]
})

Worker

Worker 模式是通过 async 异步方式实现搜索功能。

触发方式

1. Index 模式

new Worker(option)

2. Document 模式

option 中设置worker: true

工作原理

  1. 创建worker
// worker.js
import Worker from './index.worker.js'

this.worker = new worker()
  1. 通信
// worker.js
// 主线程 发送消息
this.worker.postMessage({
    "task": "init",
    "factory": factory,
    "options": options
});


// 主线程 接收消息
this.worker["on"]("message", function(msg){
    _self.resolver[msg["id"]](msg["msg"]) ;
});
// index.worker.js
// worker线程接受消息onmessage后发送消息postMessage
self.onmessage = (data) => {
    postMessage({ "id": id, "msg": message });
}

webworker 入门

⏩ 传送门