Daily-Interview-Question icon indicating copy to clipboard operation
Daily-Interview-Question copied to clipboard

第 90 题:实现模糊搜索结果的关键词高亮显示

Open yygmind opened this issue 5 years ago • 26 comments

yygmind avatar Jun 16 '19 15:06 yygmind

考虑节流、缓存。其实还可以上列表diff+定时清理缓存

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>auto complete</title>
  <style>
    bdi {
      color: rgb(0, 136, 255);
    }

    li {
      list-style: none;
    }
  </style>
</head>
<body>
  <input class="inp" type="text">
  <section>
    <ul class="container"></ul>
  </section>
</body>
<script>

  function debounce(fn, timeout = 300) {
    let t;
    return (...args) => {
      if (t) {
        clearTimeout(t);
      }
      t = setTimeout(() => {
        fn.apply(fn, args);
      }, timeout);
    }
  }

  function memorize(fn) {
    const cache = new Map();
    return (name) => {
      if (!name) {
        container.innerHTML = '';
        return;
      }
      if (cache.get(name)) {
        container.innerHTML = cache.get(name);
        return;
      }
      const res = fn.call(fn, name).join('');
      cache.set(name, res);
      container.innerHTML = res;
    }
  }

  function handleInput(value) {
    const reg = new RegExp(`\(${value}\)`);
    const search = data.reduce((res, cur) => {
      if (reg.test(cur)) {
        const match = RegExp.$1;
        res.push(`<li>${cur.replace(match, '<bdi>$&</bdi>')}</li>`);
      }
      return res;
    }, []);
    return search;
  }
  
  const data = ["上海野生动物园", "上饶野生动物园", "北京巷子", "上海中心", "上海黄埔江", "迪士尼上海", "陆家嘴上海中心"]
  const container = document.querySelector('.container');
  const memorizeInput = memorize(handleInput);
  document.querySelector('.inp').addEventListener('input', debounce(e => {
    memorizeInput(e.target.value);
  }))
</script>
</html>

lhyt avatar Jun 16 '19 17:06 lhyt

我的大概思路是,用正则替换掉关键词。

let panter = new RegExp(关键词, 'g')
该行字符串.replace(panter, '<b style="color: #2D7BFF">' + 关键词 + '</b>')

ps:如果是vue项目,直接与v-html结合使用更爽哦~

5SSS avatar Jun 17 '19 03:06 5SSS

实现不难,写一个pipe做统一的转换会让代码看起来更加简洁

<ul>
  <li *ngFor="let data of mockData">
    <span innerHTML="{{data.name | highLight: target}}"></span>
  </li>
</ul>

image

foolmadao avatar Jun 17 '19 03:06 foolmadao

<div class="input">
    <input type="text" oninput="search(event)">
    <ul class="options"></ul>
</div>

<script>
    const list = ['上海', '上海市', '上海海昌海洋公园', '上海市徐汇区', '上海自来水来自海上'];

    function setList(value) {
      const ul = document.querySelector('.options');
      ul.innerHTML = '';
      if (!value) {
        ul.innerHTML = '';
        return;
      }
      list.forEach((item, index) => {
        if (item.indexOf(value) !== -1) {
          const li = document.createElement('li');
          const innerHtml = item.replace(value, `<span style="color:red">${value}</span>`);
          console.log(innerHtml)
          li.innerHTML = innerHtml;
          li.setAttribute('key', index);
          ul.appendChild(li);
        }
      })
    }

    function search(e) {
      const value = e.target.value;
      setList(value)
    }
</script>

ouyinheng avatar Jul 12 '19 02:07 ouyinheng

直接把 输入的字符 变蓝, 服务端返回的数据 把输入字符替换掉,然后追加后面那部分 显示。 没有结果的时候做一下判断。

sailei1 avatar Jul 18 '19 08:07 sailei1

我的大概思路是,用正则替换掉关键词。

let panter = new RegExp(关键词, 'g')
该行字符串.replace(panter, '<b style="color: #2D7BFF">' + 关键词 + '</b>')

ps:如果是vue项目,直接与v-html结合使用更爽哦~

new RegExp 要注意排除有正则含义的字符,比如用户输入 ^ $ 等。

qinshenxue avatar Jul 23 '19 01:07 qinshenxue

用关键词去切分搜索结果为3段 export const keyWordHeightLight = (item, value) => { const mIdx = item.indexOf(value) const first = mIdx >= 0 ? mIdx : item.length const second = value.length

let _head = item.substr(0, first) let _heightLight = item.substr(first, second) let _foot = item.substr(second + mIdx, item.length)

return _head + <b>${_heightLight}</b> + _foot }

hviwen avatar Jul 24 '19 02:07 hviwen

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <div id='app'>   
    </div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script>
    var myitem = {
        template:`<div class='item'>
            <p v-html="info.name"></p>
        </div>`,
        props:{
            info:{
                type:Object,
                default:()=>{return {}},
            }
        }

    }
    var vm = new Vue({
        el:'#app',
        template:`
        <div>
            <input @input='inputSearchText'>
            <myitem v-for='info in results' :key='info.name' :info='info'></item>
        </div>
        `,
        data(){
            return {
                infos:[
                    {name:'地铁1',},
                    {name:'地铁6',},
                    {name:'地铁7',},
                    {name:'地铁10',},
                    {name:'地铁11',},
                    {name:'公交112',},
                    {name:'公交597',},
                    {name:'公交593',}, 
                ],
                results:[],
            }
        },
        created() {
            this.results = JSON.parse(JSON.stringify(this.infos));
        },
        methods: {
            inputSearchText : (function(timeout){
                var timer;
                return function(e){
                    if(timer){
                        clearTimeout(timer);
                    }
                    timer = setTimeout(() => {
                        this.search(e.target.value);
                        //this.search_text = e.target.value
                    }, timeout);
                }
            })(1000),
            search(text){
                var reg = RegExp(`(${text})`);
                var results = JSON.parse(JSON.stringify(this.infos));
                var matches = results.filter(info=>info.name.match(reg));
                matches.forEach(info=>{
                    info.name = info.name.replace(reg,`<span class='highlight'>$1</span>`
                )});
                this.results = matches;
                console.log(this.results);
            }
        },
        components:{
            myitem,
        },
    })
</script>
<style>
.highlight{
    color:red;
}
</style>
</html>

zzNire avatar Aug 14 '19 07:08 zzNire

export function addMark(q, val) {
    if (/^[\w\s\+\-]+$/.test(q)) {
        let reg = q;
        if (/(\s-|-\s)+[^\s-]/.test(q)) {
            reg = q.split(/(\s-|-\s)/)[0];
        } else if (/[\s+]+[^\s+]/.test(q)) {
            reg = q.replace(/([\s+]+)/, '|');
        }
        reg = new RegExp(`(${reg})`,'igm');
        return val.replace(reg,`<b>$1</b>`)
    } else {
        return val.replace(q,`<b>${q}</b>`)
    }
}

个人网站在我看到此题前实现了一个,为纯英文时,需要注意不区分大小写.返回值通过vue v-html渲染 搜索支持简单布尔运算

  • key1 key2: 搜索包含key1或key2的条目
  • key1 + key2: 搜索即包含key1又包含key2的条目
  • key1 - key2: 搜索只包含key1不包含key2的条目

image

pagemarks avatar Aug 15 '19 10:08 pagemarks

考虑节流、缓存。其实还可以上列表diff+定时清理缓存

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>auto complete</title>
  <style>
    bdi {
      color: rgb(0, 136, 255);
    }

    li {
      list-style: none;
    }
  </style>
</head>
<body>
  <input class="inp" type="text">
  <section>
    <ul class="container"></ul>
  </section>
</body>
<script>

  function debounce(fn, timeout = 300) {
    let t;
    return (...args) => {
      if (t) {
        clearTimeout(t);
      }
      t = setTimeout(() => {
        fn.apply(fn, args);
      }, timeout);
    }
  }

  function memorize(fn) {
    const cache = new Map();
    return (name) => {
      if (!name) {
        container.innerHTML = '';
        return;
      }
      if (cache.get(name)) {
        container.innerHTML = cache.get(name);
        return;
      }
      const res = fn.call(fn, name).join('');
      cache.set(name, res);
      container.innerHTML = res;
    }
  }

  function handleInput(value) {
    const reg = new RegExp(`\(${value}\)`);
    const search = data.reduce((res, cur) => {
      if (reg.test(cur)) {
        const match = RegExp.$1;
        res.push(`<li>${cur.replace(match, '<bdi>$&</bdi>')}</li>`);
      }
      return res;
    }, []);
    return search;
  }
  
  const data = ["上海野生动物园", "上饶野生动物园", "北京巷子", "上海中心", "上海黄埔江", "迪士尼上海", "陆家嘴上海中心"]
  const container = document.querySelector('.container');
  const memorizeInput = memorize(handleInput);
  document.querySelector('.inp').addEventListener('input', debounce(e => {
    memorizeInput(e.target.value);
  }))
</script>
</html>

应用用节流还是防抖?

SoftwareEngineerPalace avatar Aug 27 '19 14:08 SoftwareEngineerPalace

debounce

debounce不是防抖么?

callmezhenzhen avatar Sep 03 '19 01:09 callmezhenzhen

mark一下,这个问题真的有意思

fireairforce avatar Nov 19 '19 07:11 fireairforce

处理中文输入,再加个防抖。如果用数据驱动视图,可以“原地复用”,才是最优解。

<body>
    <input id="input" type="text" />
    <div id="box"></div>
    <script>
      let list = [
        { id: 1, name: "部门A", parentId: 0 },
        { id: 2, name: "部门B", parentId: 0 },
        { id: 3, name: "部门C", parentId: 1 },
        { id: 4, name: "部门D", parentId: 1 },
        { id: 5, name: "部门E", parentId: 2 },
        { id: 6, name: "部门F", parentId: 3 },
        { id: 7, name: "部门G", parentId: 2 },
        { id: 8, name: "部门H", parentId: 4 }
      ];
      input.addEventListener("input", handleSearch);
      input.addEventListener("compositionstart", handleStart);
      input.addEventListener("compositionend", handleEnd);

      function regLikeSearch(str) {
        let htmls = "";
        list.forEach(item => {
          const match = item.name.replace(
            new RegExp(str, "ig"),
            (all, match) => {
              return `<span style="color: red">${all}</span>`;
            }
          );
          htmls += `<p>${match}</p>`;
        });
        box.innerHTML = htmls;
      }

      let isZhcn = false;
      function handleSearch(e) {
        if (!isZhcn) {
          const val = e.target.value;
          console.log("handleSearch", val);
          regLikeSearch(val);
        }
      }
      function handleStart(e) {
        isZhcn = true;
      }
      function handleEnd(e) {
        isZhcn = false;
        handleSearch(e);
      }
    </script>
  </body>

Jmingzi avatar Nov 27 '19 02:11 Jmingzi

我的大概思路是,用正则替换掉关键词。

let panter = new RegExp(关键词, 'g')
该行字符串.replace(panter, '<b style="color: #2D7BFF">' + 关键词 + '</b>')

ps:如果是vue项目,直接与v-html结合使用更爽哦~ 缺点就是: v-html有XSS 攻击问题

lightYourFire avatar Jan 18 '20 08:01 lightYourFire

我的大概思路是,用正则替换掉关键词。

let panter = new RegExp(关键词, 'g')
该行字符串.replace(panter, '<b style="color: #2D7BFF">' + 关键词 + '</b>')

ps:如果是vue项目,直接与v-html结合使用更爽哦~ 缺点就是: v-html有XSS 攻击问题

v-html的xss攻击这样这样解决?

<template>
  <div id="app" v-html="fixVHTMLXSS(text)"></div>
</template>
<script>
export default {
  data () {
    return {
      text: '<span style="color:red">1111111</span>'
    }
  },
  methods: {
    fixVHTMLXSS (str) {
      return (str || '').replace(/</g, '&lt;').replace(/>/g, '&gt;')
    }
  }
}
</script>

chenweihuan avatar Feb 14 '20 04:02 chenweihuan

考虑节流、缓存。其实还可以上列表diff+定时清理缓存

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>auto complete</title>
  <style>
    bdi {
      color: rgb(0, 136, 255);
    }

    li {
      list-style: none;
    }
  </style>
</head>
<body>
  <input class="inp" type="text">
  <section>
    <ul class="container"></ul>
  </section>
</body>
<script>

  function debounce(fn, timeout = 300) {
    let t;
    return (...args) => {
      if (t) {
        clearTimeout(t);
      }
      t = setTimeout(() => {
        fn.apply(fn, args);
      }, timeout);
    }
  }

  function memorize(fn) {
    const cache = new Map();
    return (name) => {
      if (!name) {
        container.innerHTML = '';
        return;
      }
      if (cache.get(name)) {
        container.innerHTML = cache.get(name);
        return;
      }
      const res = fn.call(fn, name).join('');
      cache.set(name, res);
      container.innerHTML = res;
    }
  }

  function handleInput(value) {
    const reg = new RegExp(`\(${value}\)`);
    const search = data.reduce((res, cur) => {
      if (reg.test(cur)) {
        const match = RegExp.$1;
        res.push(`<li>${cur.replace(match, '<bdi>$&</bdi>')}</li>`);
      }
      return res;
    }, []);
    return search;
  }
  
  const data = ["上海野生动物园", "上饶野生动物园", "北京巷子", "上海中心", "上海黄埔江", "迪士尼上海", "陆家嘴上海中心"]
  const container = document.querySelector('.container');
  const memorizeInput = memorize(handleInput);
  document.querySelector('.inp').addEventListener('input', debounce(e => {
    memorizeInput(e.target.value);
  }))
</script>
</html>

厉害,但是有点就是我看到实现的debounce是防抖,确实是使用防抖不能使用节流,只是最上面的文案是要修改一手。赞

hduhdc avatar Apr 03 '20 01:04 hduhdc

let list = [ { id: 1, address: '上海野生动物园' }, { id: 2, address: '上饶野生动物园' }, { id: 3, address: '北京巷子' }, { id: 4, address: '上海中心' }, { id: 5, address: '上海黄埔江' }, { id: 6, address: '迪士尼上海' }, { id: 7, address: '陆家嘴上海中心' }, ] const input1 = document.querySelector('#input1'); const ul = document.querySelector('ul'); input1.addEventListener('keyup', debounceFunc(function() { const result = getResult(this.target.value); renderUl(result); }, 500))

function debounceFunc (fn, delay) {
    let lastTime = 0;
    let timer = null;
    return function (event) {
        let now = +(new Date());
        clearTimeout(timer)
        timer = setTimeout(function () {
            fn.call(event);
        }, delay);
        if (now - lastTime < delay) {
            lastTime = now;
        }
    }
}

function getResult(str) {
    return str.trim() ? list.filter(item => item.address.includes(str))
                .map(item => {
                    return {
                        ...item,
                        address: item.address.replace(str, `<span style="color:#31b0d5">${str}</span>`)
                    }
                }) : [];
}

function renderUl(lis) {
    ul.innerHTML = lis.map(item => `<li>${item.address}</li>`).join('')

}

xiaochen-01 avatar Apr 22 '20 13:04 xiaochen-01

function a(str, childStr){ const arr = str.split(childStr) return arr.join(<span class="highlight">${childStr}</span>) }

myzhoulang avatar Jun 24 '20 11:06 myzhoulang

function queryStr(str, query) {
  let result
  const index = str.indexOf(query)
  if (index !== -1) {
    result = `${str.substr(0, index)}<span style="color: red;">${query}</span>${str.substr(index + query.length)}`
  } else {
    result = str
  }
  return result
}
queryStr('hello world', 'r')

523451928 avatar Jul 14 '20 06:07 523451928

这不是防抖吗? @lhyt

UzumakiHan avatar Apr 16 '21 07:04 UzumakiHan

结合输入中文:compisitionsart compositionend

<body>
  <input type="text" class="box">
  <ul class="txt"></ul>
</body>
<script>
  let ipt = document.querySelector('.box')
  let txt = document.querySelector('.txt')
  console.log('txt', txt)

  const list = ['上海', '上海市', '上海海昌海洋公园', '上海市徐汇区', '上海自来水来自海上'];
  ipt.addEventListener('input', function (e) {
    if (e.target.composing) {
      return
    }

    txt.innerHTML = '';
    list.forEach(item => {
      if (item.indexOf(ipt.value) != -1) {
        let li = document.createElement('li')
        const innerHtml = item.replace(ipt.value, `<span style="color:red">${ipt.value}</span>`)
        console.log('innerHtml', innerHtml)
        li.innerHTML = innerHtml;
        txt.appendChild(li)
      }
    })
  })
  ipt.addEventListener('compositionstart', function (e) {
    e.target.composing = true
  })
  ipt.addEventListener('compositionend', function (e) {
    e.target.composing = false;
    let event = document.createEvent('HTMLEvents')
    event.initEvent('input')
    e.target.dispatchEvent(event);
  })
</script>

LXhby avatar May 18 '21 03:05 LXhby

我的大概思路是,用正则替换掉关键词。

let panter = new RegExp(关键词, 'g')
该行字符串.replace(panter, '<b style="color: #2D7BFF">' + 关键词 + '</b>')

ps:如果是vue项目,直接与v-html结合使用更爽哦~

let panter = new RegExp(关键词, 'ig') replace(panter, matchValue => <span style="xxx">${matchValue}</span>); 正则忽略大小写,然后replace第二个参数用函数更佳哈

tustzdm avatar Jun 05 '21 08:06 tustzdm

在这里我有个问题,如果是富文本编辑器返回的内容,如何高亮选中,然后后端怎么在查找的时候,帅选掉那些标签呢

zhujianxiong avatar Jun 20 '21 07:06 zhujianxiong

'变蓝变红变绿变蓝变'.match(/(变蓝)|((?!变蓝).)+/g)

q673115816 avatar Aug 09 '21 02:08 q673115816

React 版本

import { useEffect, useRef, useState } from "react";

const allData = ["asdew", "aedf", "123", "asdf"];

export default function App() {
  const [inputVal, setInputVal] = useState("");
  const data = useData(inputVal);

  return (
    <div className="App">
      <input
        value={inputVal}
        onChange={(e) => {
          const text = e.target.value;
          setInputVal(text);
        }}
      />
      <ul>
        {data.map((dataItem) => {
          return (
            <li>
              {dataItem.split("").map((item) => {
                return (
                  <span style={{ color: inputVal.includes(item) ? "red" : "" }}>
                    {item}
                  </span>
                );
              })}
            </li>
          );
        })}
      </ul>
    </div>
  );
}

const useData = (value) => {
  const val = useDebounce(value, 500);
  const [data, setData] = useState([]);
  useEffect(() => {
    if (!val) {
      setData([]);
      return;
    }
    setData(allData.filter((item) => item.includes(val)));
  }, [val]);
  return data;
};

const useDebounce = (value, interval = 1000) => {
  const [data, setData] = useState(value);
  const timer = useRef(null);

  useEffect(() => {
    clearTimeout(timer.current);
    timer.current = setTimeout(() => {
      setData(value);
    }, interval);
    return () => {
      clearTimeout(timer.current);
    };
  }, [value, interval]);

  return data;
};

goldEli avatar Jan 27 '22 08:01 goldEli

 const datas = ["上海野生动物园", "上饶野生动物园", "北京巷子", "上海中心", "上海黄埔江", "迪士尼上海", "陆家嘴上海中心"]
  const inputSearchText = (val) => {
    let reg = new RegExp(val)
    let arr = []
    for (let c of datas) {
      if (reg.test(c)) {
        let s = c.replace(val, `<span style="color:#31b0d5">${val}</span>`)
        arr.push(s)
      }
    }
    console.log('结果', arr)
  }
  inputSearchText('野生')

XW666 avatar May 30 '23 08:05 XW666