以前我总是这样过滤搜索内容

data.filter(item => item.indexOf(keywords) >= 0)

这样确实可以满足关键字搜索需求,但鉴于前端是直接呈现画面给用户的人,我们总是需要站在用户的角度去考虑问题。后面项目做多了,其实发现这样在用户体验上并不够友好,我们来看下下面这个例子:

chrome浏览器内编辑样式
chrome

vs code内编辑样式
vscode

在chrome浏览器内无法进行非连续的字符匹配,造成了一些体验感的缺失,而在vs code内却可以自由间断地输入关键字匹配。毫无疑问在vs code中我们的编辑体验会更加流畅,因为在很多场景下,我们记不得一段连续的关键字,而是在断断续续的关键字,此时我们使用这种间断的关键字进行搜索可以很好地提高用户体验,而且也不影响连续的关键字搜索。

什么场景下可以用非连续关键字搜索

这类需求一般为搜索本地数据时,且需求场景没有明确指定需要连续的关键字搜索时,我们都可以使用非连续关键字搜索来实现,具体列举了以下几个场景:

  1. 树形菜单项搜索:类似管理后台页面的菜单、多节点搜索
  2. checkbox选项搜索:选项较多的可搜索checkbox内
  3. 路径列表搜索:如vs code的文件搜索
  4. 本地缓存搜索:微信的缓存聊天记录关键字搜索

接下来我们探讨一下如何实现

得益于万能的npmjs.com,经历百般搜索,我在上面找到了一个不错的js函数库实现了非连续关键字搜索,我们先看一个简单的Demo,再剖析一下它的实现原理。

js库在此 -> string-discontinuous-match
demo
此Demo中在1000条长度为200的随机字符串中非连续关键字搜索,完成单次搜索只需要1-2ms,性能可观。

接下来贴一下实现代码,通过Vue实现

<div id="app">
  <div class="search-wrap">
    <label>🔍</label>
    <input class="search-box" v-model="val" @input="inputHandler" />
  </div>
  <div class="info">
    <span>Data totals: {{ numbers }}</span>
    <span v-if="performance">Performance: {{ performance }}</span>
  </div>
  <div class="result">
    <span class="item" v-for="item in strings" :key="item" v-html="item"></span>
  </div>
</div>
// 初始状态
data() {
  return {
    val: '',
    strings: strings,  // strings内包含1000条测试随机字符串
    numbers: strings.length,
    performance: '',
  };
}

以下为input事件实现

<input @input="inputHandler" />

import { discontinuousMatch, replaceMatchedString } from 'string-discontinuous-match';
function inputHandler() {
  // this.val为搜索关键字
  if (this.val) {
    let s = performance.now();
      let ret = discontinuousMatch(strings, this.val);
      let e = performance.now();
      this.strings = ret.map(item => {
        // 辅助函数replaceMatchedString
        return replaceMatchedString(item, chars => `<span class="keyword">${chars}</span>`);
      });
      this.performance = (e - s) + 'ms';
    }
    else {
      this.data = strings;
      this.strings = strings;
      this.performance = '';
    }
    this.numbers = this.strings.length;
  }

搜索结果格式为:

[
  {
    value: 'xxx',   // 被搜索的字符串
    index: 0,       // 该项在数组中的索引
    
    // 匹配的关键字索引位置,如[0, 2]表示value中下标为0-2(包含2)位置的字符,14表示下标为14的单个字符。
    position: [[0, 2], [10, 12], 14, 17],
    lastIndex: 17   // 最后一个匹配的索引
  },
  // ...
]

所以你可以通过position信息去高亮对应位置的字符,但实际上你并不需要自己处理,string-discontinuous-match为我们提供了一个辅助函数来处理它,例如在inputHandler函数中,你可以看到将匹配结果循环传入了replaceMatchedString函数中,它的作用是帮我们提取匹配到的字符串,它根据position数组依次触发回调,在 position: [[0, 2], [10, 12], 14, 17]中会分别提取下标0到2的字符串,并触发一次回调,在回调中你可以返回转化后的字符串例如用<span>标签包裹它,接下来再提取10到12的字符串并触发回调,以此类推共计触发4次回调,最后把转化后的字符串返回给我们,这样我们就可以很方便地实现匹配关键字的高亮状态了。

通过这样一顿操作,就已经实现了非连续关键字搜索的功能了。

性能怎么样???

大家应该都会有这样的疑问,数据量小还好,但对于大数据量的匹配,会不会很慢呢?毕竟是非连续的匹配呢!!!
这个问题,此js库给出了这样的回答:
image.png

在10000个5000位随机字符串中搜索50位随机关键字符串,忽略大小写,花了101ms,性能好是不错的。

结尾

对于一个前端来说,我们还是应该把用户体验放在重要的位置,而这个搜索就是其中的优化点之一,只需要简单一步操作,我们就可以解决此问题,不知道正在看文章的你觉得是否值得一试呢?如果对这个js库感兴趣的可以自行研究源码,其实它也很简单。
今天就到此啦,如有什么问题可以给作者去提issue哦。对我的教程感到满意,请不要吝啬你手中的免费赞赞哦!!!


爱编程的小金
39 声望7 粉丝

爱研究各种技术的小金