前端做模糊搜索

我们先看一下效果图:

640?wx_fmt=png

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

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

640?wx_fmt=png

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

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

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

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

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

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

 
  
  1. var escapeRegExp = /[\-#$\^*()+\[\]{}|\\,.?\s]/g;

  2. var KeyReg = (key) => {

  3.    var src = ['(.*?)('];

  4.    var ks = key.split('');

  5.    if (ks.length) {

  6.        while (ks.length) {

  7.            src.push(ks.shift().replace(escapeRegExp, '\\$&'), ')(.*?)(');

  8.        }

  9.        src.pop();

  10.    }

  11.    src.push(')(.*?)');

  12.    src = src.join('');

  13.    var reg = new RegExp(src, 'i');

  14.    var replacer = [];

  15.    var start = key.length;

  16.    var begin = 1;

  17.    while (start > 0) {

  18.        start--;

  19.        replacer.push('$', begin, '($', begin + 1, ')');

  20.        begin += 2;

  21.    }

  22.    replacer.push('$', begin);

  23.    info = {

  24.        regexp: reg,

  25.        replacement: replacer.join('')

  26.    };

  27.    return info;

  28. };

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

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

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

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

 
  
  1. let Searcher = (() => {

  2.    let escapeRegExp = /[\-#$\^*()+\[\]{}|\\,.?\s]/g;

  3.    let escapeReg = reg => reg.replace(escapeRegExp, '\\$&');

  4.    //groupLeft 与 groupRight是对结果进一步处理所使用的分割符,可以修改

  5.    let groupLeft = '(',

  6.        groupRight = ')';

  7.    let groupReg = new RegExp(escapeReg(groupRight + groupLeft), 'g');

  8.    let groupExtractReg = new RegExp('(' + escapeReg(groupLeft) + '[\\s\\S]+?' + escapeReg(groupRight) + ')', 'g');

  9.    //从str中找到最大的匹配长度

  10.    let findMax = (str, keyword) => {

  11.        let max = 0;

  12.        keyword = groupLeft + keyword + groupRight;

  13.        str.replace(groupExtractReg, m => {

  14.            //keyword完整的出现在str中,则优秀级最高,排前面

  15.            if (keyword == m) {

  16.                max = Number.MAX_SAFE_INTEGER;

  17.            } else if (m.length > max) {//找最大长度

  18.                max = m.length;

  19.            }

  20.        });

  21.        return max;

  22.    };

  23.    let keyReg = key => {

  24.        let src = ['(.*?)('];

  25.        let ks = key.split('');

  26.        if (ks.length) {

  27.            while (ks.length) {

  28.                src.push(escapeReg(ks.shift()), ')(.*?)(');

  29.            }

  30.            src.pop();

  31.        }

  32.        src.push(')(.*?)');

  33.        src = src.join('');

  34.        let reg = new RegExp(src, 'i');

  35.        let replacer = [];

  36.        let start = key.length;

  37.        let begin = 1;

  38.        while (start > 0) {

  39.            start--;

  40.            replacer.push('$', begin, groupLeft + '$', begin + 1, groupRight);

  41.            begin += 2;

  42.        }

  43.        replacer.push('$', begin);

  44.        info = {

  45.            regexp: reg,

  46.            replacement: replacer.join('')

  47.        };

  48.        return info;

  49.    };

  50.    return {

  51.        search(list, keyword) {

  52.            //生成搜索正则

  53.            let kr = keyReg(userInput);

  54.            let result = [];

  55.            for (let e of list) {

  56.                //如果匹配

  57.                if (kr.regexp.test(e)) {

  58.                    //把结果放入result数组中

  59.                    result.push(e.replace(kr.regexp, kr.replacement)

  60.                        .replace(groupReg, ''));

  61.                }

  62.            }

  63.            //对搜索结果进行排序

  64.            //1\. 匹配关键字大小写一致的优先级最高,比如搜索up, 结果中的[user-page,beginUpdate,update,endUpdate],update要排在最前面,因为大小写匹配

  65.            //2\. 匹配关键字长的排在前面

  66.            result = result.sort((a, b) => findMax(b, keyword) - findMax(a, keyword));

  67.            return result;

  68.        }

  69.    };

  70. })();

  71. //假设list是待搜索的列表

  72. let list = ['config', 'user-page', 'bind', 'render', 'beginUpdate', 'update', 'endUpdate'];

  73. //假设userInput是用户输入的关键字

  74. let userInput = 'up';

  75. //获取搜索的结果

  76. console.log(Searcher.search(list, userInput));

  77. // ["(up)date", "begin(Up)date", "end(Up)date", "(u)ser-(p)age"]

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


作者:行列

https://segmentfault.com/a/1190000015486180


640?wx_fmt=png

猜你喜欢

转载自blog.csdn.net/vm199zkg3y7150u5/article/details/80973169