<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          富文本關(guān)鍵字搜索高亮,解決方法及優(yōu)化(收藏?。?/h1>

          共 4150字,需瀏覽 9分鐘

           ·

          2022-07-13 06:44

          大廠技術(shù)  高級(jí)前端  Node進(jìn)階

          點(diǎn)擊上方 程序員成長(zhǎng)指北,關(guān)注公眾號(hào)

          回復(fù)1,加入高級(jí)Node交流群

          作者:猿猴望月
          原文:https://juejin.cn/post/7070688497929043998


          去年在冬季面試的時(shí)候,被某廠面試官問了這個(gè)問題:

          如果我們的數(shù)據(jù)是富文本,現(xiàn)在要加一個(gè)搜索功能,怎么樣才能完美的實(shí)現(xiàn)高亮呢?

          當(dāng)時(shí)回答的很粗糙,只答了提取出文字進(jìn)行搜索,怎么回填原本的樣式并沒有說清楚。

          今天坐在我旁邊的小哥開始做MarkDown搜索匹配了,又喚起了我塵封已久的記憶,于是今天就讓我們來(lái)一起震懾一下面試官吧!

          首先能想到的思路是和leetcode的上車問題相關(guān)。

          有一群乘客,當(dāng)中一個(gè)人在1號(hào)站臺(tái)下車,兩個(gè)人在2號(hào)上車上車,最后求N號(hào)站臺(tái)有多少乘客。

          這個(gè)問題也可以那么去考慮:在富文本串之中,在遇到開標(biāo)簽就理解為當(dāng)前的文字“上車”,而遇到閉標(biāo)簽就理解為當(dāng)前的文字”下車“。

          (開標(biāo)簽:<span>、閉標(biāo)簽:</span>

          第一版

          首先我們開發(fā)一個(gè)頁(yè)面,其中僅包含搜索框和幾段富文本的數(shù)據(jù)

          舉個(gè)??,數(shù)據(jù)第一項(xiàng)為:

          今天真是個(gè)<span style="text-decoration:underline">好天氣</span>

          這個(gè)時(shí)候我們想搜索“好天氣”,整體的思路是分為3個(gè)部分:

          • 處理富文本,生成標(biāo)簽在字符串位置的映射關(guān)系
          • 在處理好的文本中搜索key,對(duì)命中的詞在映射關(guān)系中增加命中樣式
          • 根據(jù)映射關(guān)系生成新的富文本

          在其中,我們需要記錄一些關(guān)鍵狀態(tài)值:

          • currentTag:當(dāng)前正處理標(biāo)簽
          • isOpen:當(dāng)前標(biāo)簽是否是開標(biāo)簽
          • needAdd: 文本需要增加的開標(biāo)簽
          • nowText:目前正在處理的文本
          • textToTagMap:文本與標(biāo)簽的映射,這里提供一下類型
          type TextToStyleMap = Array<
           {
            key: string,
            // 從這個(gè)字符增加的標(biāo)簽index
            up: number[],
            // 從這個(gè)字符結(jié)束的標(biāo)簽index
            down: number[]
           }
          >

          讓我們簡(jiǎn)單的畫個(gè)流程圖看一下怎么去做~

          codesandbox.io/s/new-smoke…[1]

          首版完成!(ps:現(xiàn)在我們搜索用的是正則,在大文本的情況下可能會(huì)有性能問題

          優(yōu)化1

          看起來(lái)好像很完美了?如果我們?cè)谶@個(gè)文本框中搜索“個(gè)好”呢?

          咦,下劃線丟了?

          因?yàn)槲覀兯阉鱾€(gè)好后的富文本結(jié)果是

          今天真是<em>個(gè)<span style="text-decoration:underline">好</em>天氣</span>

          這里生產(chǎn)出了一個(gè)錯(cuò)誤的標(biāo)簽不對(duì)應(yīng)的富文本

          我們?cè)趺磻?yīng)對(duì)這個(gè)問題呢?

          最簡(jiǎn)單的方式應(yīng)該是把強(qiáng)調(diào)的樣式加在每一個(gè)文字上

          今天真是<em>個(gè)<span style="text-decoration:underline">好</em>天氣</span>

          ??

          今天真是<em>個(gè)</em><span style="text-decoration:underline"><em>好</em>天氣</span>

          codesandbox.io/s/upbeat-fe…[2]

          簡(jiǎn)單的改了一下代碼之后,我們就修改了這個(gè)bug

          優(yōu)化2

          這么做了以后,我們會(huì)添加許多多余的強(qiáng)調(diào)樣式標(biāo)簽。比如還是在上面這個(gè)例子里,我們?nèi)绻阉鳌禾鞖狻坏脑?,結(jié)果會(huì)是這樣:

          今天真是個(gè)<span style="text-decoration:underline">好<em>天</em><em>氣</em></span>

          實(shí)際上,天氣兩個(gè)字本身就可以用同一個(gè)em標(biāo)簽來(lái)包裹,這樣可以減少頁(yè)面中的dom節(jié)點(diǎn)樹,從而提升性能。

          那么,具體該怎么做呢?這里整體的思路就是原本是在同一個(gè)文本段里的文本,我們只用一個(gè)em標(biāo)簽包裹,只在出現(xiàn)標(biāo)簽的地方添加額外的命中樣式標(biāo)簽。

          這里需要注意的是,添加命中樣式標(biāo)簽的時(shí)候,需要添加在最內(nèi)層,也就是命中樣式的開標(biāo)簽要放在所有其他開標(biāo)簽之后,而閉標(biāo)簽則要放在所有其他閉標(biāo)簽之前,這樣可以保證命中樣式的優(yōu)先級(jí)是最高的,不會(huì)被其他標(biāo)簽的樣式覆蓋。

          核心代碼邏輯如下:

          const match = [...strs.matchAll(reg)].forEach(({ index }) => {
           for (let i = 0; i < word.length; i++) {
            const letterIndex = i + index;
            if (
             i === 0 || // 匹配區(qū)域區(qū)間開始需要有命中樣式的開標(biāo)簽
             textToTagMap[letterIndex].up.length > 0 || // 當(dāng)有新的開標(biāo)簽時(shí),需要在內(nèi)部有命中樣式的開標(biāo)簽
             textToTagMap[letterIndex - 1].down.length > 0 // 當(dāng)上一個(gè)標(biāo)簽有閉標(biāo)簽時(shí),下一個(gè)標(biāo)簽需要有命中樣式的開標(biāo)簽
            ) {
             textToTagMap[letterIndex].up.push(emStyleStart);
            }
            if (
             i === word.length - 1 || // 匹配區(qū)域結(jié)束需要有命中樣式的閉標(biāo)簽
             textToTagMap[letterIndex].down.length > 0 || // 當(dāng)有新的閉標(biāo)簽時(shí),需要在內(nèi)部有命中樣式的閉標(biāo)簽
             textToTagMap[letterIndex + 1].up.length > 0 // 當(dāng)下一個(gè)標(biāo)簽有開標(biāo)簽時(shí),上一個(gè)標(biāo)簽需要有命中樣式的閉標(biāo)簽
            ) {
             textToTagMap[letterIndex].down.unshift(emStyleEnd);
            }
           }
          });

          最后的成果??

          大功告成!

          codesandbox.io/s/restless-…[3]

          結(jié)論

          看似完成了?其實(shí)還有一些功能沒有做,比如局部匹配、多詞搜索、emoji匹配等功能,這些就留給大家自己去實(shí)現(xiàn)啦

          并且,這里的搜索匹配沒有考慮轉(zhuǎn)義字符和不合法標(biāo)簽等問題,實(shí)際實(shí)現(xiàn)起來(lái)也需要多加判斷

          剛剛也提到在大文本的情況下使用正則性能會(huì)有問題,那是不是可以考慮把textToTagMap換一種數(shù)據(jù)格式呢?像是字典樹之類

          ps:做超大文本量的匹配時(shí)也可以選擇分片去做,先處理可視區(qū)的文字,保證搜索不卡頓

          pss:做富文本相關(guān)的內(nèi)容一定要注意防范XSS攻擊哦!

          參考資料

          [1]

          https://codesandbox.io/s/new-smoke-w5hcjv?file=/index.html: https://link.juejin.cn?target=https%3A%2F%2Fcodesandbox.io%2Fs%2Fnew-smoke-w5hcjv%3Ffile%3D%2Findex.html

          [2]

          https://codesandbox.io/s/upbeat-feynman-qch060?file=/index.html: https://link.juejin.cn?target=https%3A%2F%2Fcodesandbox.io%2Fs%2Fupbeat-feynman-qch060%3Ffile%3D%2Findex.html

          [3]

          https://codesandbox.io/s/restless-pond-9b14j7?file=/index.html: https://link.juejin.cn?target=https%3A%2F%2Fcodesandbox.io%2Fs%2Frestless-pond-9b14j7%3Ffile%3D%2Findex.html

          Node 社群



          我組建了一個(gè)氛圍特別好的 Node.js 社群,里面有很多 Node.js小伙伴,如果你對(duì)Node.js學(xué)習(xí)感興趣的話(后續(xù)有計(jì)劃也可以),我們可以一起進(jìn)行Node.js相關(guān)的交流、學(xué)習(xí)、共建。下方加 考拉 好友回復(fù)「Node」即可。



          如果你覺得這篇內(nèi)容對(duì)你有幫助,我想請(qǐng)你幫我2個(gè)小忙:

          1. 點(diǎn)個(gè)「在看」,讓更多人也能看到這篇文章
          2. 訂閱官方博客 www.inode.club 讓我們一起成長(zhǎng)

          點(diǎn)贊和在看就是最大的支持

          瀏覽 92
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)

          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  91天天干| 久久久婷婷 | 亚洲狼人香蕉 | 淫色人妻首页 | 国产伦精品一区二区三区四区视频 |