专栏首页grain先森前端-模糊搜索

前端-模糊搜索

我们先看一下效果图:

这是搜索关键字 cfg时,会自动匹配到 config方法

同样,我们再看另一个例子

通过关键字 bi会匹配到好几个结果

这个和一些编辑器的搜索功能很像,比如 sublime text,不需要知道关键字的完整拼写,只需要知道其中的几个字母即可。

那么这个功能在前端我们如何去实现呢?

不考虑性能的话,我们可以用正则简单实现如下:

把关键字拆分,加入 (.?),如 cfg 最终为 (.?)(c)(.?)(f)(.?)(g)(.*?), 然后拿这个正则去测试要搜索的列表,把符合要求的选项给拿出来即可

考虑到要高亮结果,我们还要生成对应的替换表达式,最后的函数如下

var escapeRegExp = /[-#$^*()+[]{}|\,.?s]/g;
var KeyReg = (key) => {
    var src = ['(.*?)('];
    var ks = key.split('');
    if (ks.length) {
        while (ks.length) {
            src.push(ks.shift().replace(escapeRegExp, '\$&'), ')(.*?)(');
        }
        src.pop();
    }
    src.push(')(.*?)');
    src = src.join('');
    var reg = new RegExp(src, 'i');
    var replacer = [];
    var start = key.length;
    var begin = 1;
    while (start > 0) {
        start--;
        replacer.push('$', begin, '($', begin + 1, ')');
        begin += 2;
    }
    replacer.push('$', begin);

    info = {
        regexp: reg,
        replacement: replacer.join('')
    };
    return info;
};

调用 KeyReg把关键字传入,拿返回值的 regexp去检测搜索的列表,把符合的保存下来即可。

到目前为止我们只实现了搜索功能,按更优的体验来讲,在搜索结果中,要优先把相连匹配的放在首位,如 bi关键字,要把 bind结果放到 beginUpdate前面。第二个截图是有优化的地方的。

要完成这个功能,我们使用 KeyReg返回值中的 replacement,用它进行检测,把结果中长度最长的放前面即可,这块代码以后有时间再补充

2018.5.31 今天重构了下,增加了结果排序,完整的代码及使用示例如下

let Searcher = (() => {
    let escapeRegExp = /[-#$^*()+[]{}|\,.?s]/g;
    let escapeReg = reg => reg.replace(escapeRegExp, '\$&');
    //groupLeft 与 groupRight是对结果进一步处理所使用的分割符,可以修改
    let groupLeft = '(',
        groupRight = ')';
    let groupReg = new RegExp(escapeReg(groupRight + groupLeft), 'g');
    let groupExtractReg = new RegExp('(' + escapeReg(groupLeft) + '[\s\S]+?' + escapeReg(groupRight) + ')', 'g');
    //从str中找到最大的匹配长度
    let findMax = (str, keyword) => {
        let max = 0;
        keyword = groupLeft + keyword + groupRight;
        str.replace(groupExtractReg, m => {
            //keyword完整的出现在str中,则优秀级最高,排前面
            if (keyword == m) {
                max = Number.MAX_SAFE_INTEGER;
            } else if (m.length > max) {//找最大长度
                max = m.length;
            }
        });
        return max;
    };
    let keyReg = key => {
        let src = ['(.*?)('];
        let ks = key.split('');
        if (ks.length) {
            while (ks.length) {
                src.push(escapeReg(ks.shift()), ')(.*?)(');
            }
            src.pop();
        }
        src.push(')(.*?)');
        src = src.join('');
        let reg = new RegExp(src, 'i');
        let replacer = [];
        let start = key.length;
        let begin = 1;
        while (start > 0) {
            start--;
            replacer.push('$', begin, groupLeft + '$', begin + 1, groupRight);
            begin += 2;
        }
        replacer.push('$', begin);

        info = {
            regexp: reg,
            replacement: replacer.join('')
        };
        return info;
    };

    return {
        search(list, keyword) {
            //生成搜索正则
            let kr = keyReg(userInput);
            let result = [];
            for (let e of list) {
                //如果匹配
                if (kr.regexp.test(e)) {
                    //把结果放入result数组中
                    result.push(e.replace(kr.regexp, kr.replacement)
                        .replace(groupReg, ''));
                }
            }
            //对搜索结果进行排序
            //1. 匹配关键字大小写一致的优先级最高,比如搜索up, 结果中的[user-page,beginUpdate,update,endUpdate],update要排在最前面,因为大小写匹配
            //2. 匹配关键字长的排在前面
            result = result.sort((a, b) => findMax(b, keyword) - findMax(a, keyword));
            return result;
        }
    };
})();

//假设list是待搜索的列表
let list = ['config', 'user-page', 'bind', 'render', 'beginUpdate', 'update', 'endUpdate'];
//假设userInput是用户输入的关键字
let userInput = 'up';

//获取搜索的结果
console.log(Searcher.search(list, userInput));
// ["(up)date", "begin(Up)date", "end(Up)date", "(u)ser-(p)age"]

对搜索结果中的内容做进一步处理渲染出来即可,比如把 ( 替换成 <spanstyle="color:red"> 把 ) 替换成 </span>显示到页面上就完成了高亮显示

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 前端- CSS 的空格处理

    上面是一行 HTML 代码,文字的前部、内部和后部各有两个空格。为了便于识别,这里使用半圆形符号◡表示空格。

    grain先森
  • 彻底明白闭包!

    那么 JavaScript 解析器是如何执行这段代码的呢?今天我们再一步一步解释一下上面代码的执行过程。

    grain先森
  • 面试官想要的 JS 基本类型

    作者曾经也是这样回答的,并且一直觉得没有什么问题。那么面试官问你 JS 数据类型时,他想知道什么呢?接着往下看???

    grain先森
  • LeetCode 5 迅速判断回文串的曼切斯特算法

    虽然LeetCode里给这道题的难度是Medium,但实际上并不简单,我们通过自己思考很难想到最佳解法。

    TechFlow-承志
  • 图像处理: 无损地旋转图像

    JNingWei
  • Python 脚本 GUI 界面生成工具

    对于一些 Python 脚本,我们想把它打包给其他人使用,不是黑色的命令行框,而是打包成一个有界面的程序包给别人,让普通用户也能用上是多么酷的事啊。

    用户6825444
  • var、let、const声明变量的区别

    let和var声明变量的区别: 1.let所声明的变量只在let命令所在的代码块内有效。(块级作用域)

    IT人一直在路上
  • C++常见问题(三)——宏定义、const、static、sizeof的相关实例

    第一部分 宏定义   宏定义的语法格式:#define <标识符><字符串>,其中的标识符就是所谓的符号常量,也称为”宏名”。预处理工作也叫宏展开。宏定义不占用...

    深度学习思考者
  • 业余时间学数据分析,如何快速上手

    广泛被应用的数据分析:谷歌的数据分析可以预测一个地区即将爆发的流感,从而进行针对性的预防;淘宝可以根据你浏览和消费的数据进行分析,为你精准推荐商品;口碑极好的网...

    华章科技
  • 关于数据分析:你想知道的都在这里!

    本篇学习整理笔记来源于:简书@功彬eleven、《谁说菜鸟不会数据分析》、公众号:杜王丹、公众号:数据分析。 在原作者的基础上进行整理分类,将本篇分为:数据分析...

    张俊红

扫码关注云+社区

领取腾讯云代金券