Daily-Interview-Question
Daily-Interview-Question copied to clipboard
第 90 题:实现模糊搜索结果的关键词高亮显示

考虑节流、缓存。其实还可以上列表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>
我的大概思路是,用正则替换掉关键词。
let panter = new RegExp(关键词, 'g')
该行字符串.replace(panter, '<b style="color: #2D7BFF">' + 关键词 + '</b>')
ps:如果是vue项目,直接与v-html结合使用更爽哦~
实现不难,写一个pipe做统一的转换会让代码看起来更加简洁
<ul>
<li *ngFor="let data of mockData">
<span innerHTML="{{data.name | highLight: target}}"></span>
</li>
</ul>
<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>
直接把 输入的字符 变蓝, 服务端返回的数据 把输入字符替换掉,然后追加后面那部分 显示。 没有结果的时候做一下判断。
我的大概思路是,用正则替换掉关键词。
let panter = new RegExp(关键词, 'g') 该行字符串.replace(panter, '<b style="color: #2D7BFF">' + 关键词 + '</b>')
ps:如果是vue项目,直接与v-html结合使用更爽哦~
new RegExp 要注意排除有正则含义的字符,比如用户输入 ^ $
等。
用关键词去切分搜索结果为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
}
<!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>
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的条目
考虑节流、缓存。其实还可以上列表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
debounce不是防抖么?
mark一下,这个问题真的有意思
处理中文输入,再加个防抖。如果用数据驱动视图,可以“原地复用”,才是最优解。
<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>
我的大概思路是,用正则替换掉关键词。
let panter = new RegExp(关键词, 'g') 该行字符串.replace(panter, '<b style="color: #2D7BFF">' + 关键词 + '</b>')
ps:如果是vue项目,直接与v-html结合使用更爽哦~ 缺点就是: v-html有XSS 攻击问题
我的大概思路是,用正则替换掉关键词。
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, '<').replace(/>/g, '>')
}
}
}
</script>
考虑节流、缓存。其实还可以上列表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是防抖,确实是使用防抖不能使用节流,只是最上面的文案是要修改一手。赞
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('')
}
function a(str, childStr){
const arr = str.split(childStr)
return arr.join(<span class="highlight">${childStr}</span>
)
}
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')
这不是防抖吗? @lhyt
结合输入中文: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>
我的大概思路是,用正则替换掉关键词。
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第二个参数用函数更佳哈
在这里我有个问题,如果是富文本编辑器返回的内容,如何高亮选中,然后后端怎么在查找的时候,帅选掉那些标签呢
'变蓝变红变绿变蓝变'.match(/(变蓝)|((?!变蓝).)+/g)
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;
};
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('野生')