前端作模糊搜索

咱們先看一下效果圖:
image前端

這是搜索關鍵字cfg時,會自動匹配到config方法數組

一樣,咱們再看另外一個例子編輯器

image
經過關鍵字bi會匹配到好幾個結果函數

這個和一些編輯器的搜索功能很像,好比sublime text,不須要知道關鍵字的完整拼寫,只須要知道其中的幾個字母便可。性能

那麼這個功能在前端咱們如何去實現呢?測試

不考慮性能的話,咱們能夠用正則簡單實現以下:優化

把關鍵字拆分,加入(. ?),如cfg最終爲 (.?)(c)(. ?)(f)(.?)(g)(.*?),而後拿這個正則去測試要搜索的列表,把符合要求的選項給拿出來便可

考慮到要高亮結果,咱們還要生成對應的替換表達式,最後的函數以下spa

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去檢測搜索的列表,把符合的保存下來便可。3d

到目前爲止咱們只實現了搜索功能,按更優的體驗來說,在搜索結果中,要優先把相連匹配的放在首位,如bi關鍵字,要把bind結果放到beginUpdate前面。第二個截圖是有優化的地方的。code

要完成這個功能,咱們使用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(keyword);
            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"]

對搜索結果中的內容作進一步處理渲染出來便可,好比把 ( 替換成 <span style="color:red">) 替換成</span>顯示到頁面上就完成了高亮顯示

相關文章
相關標籤/搜索