如何遍历全部节点并将terms数组中存在的匹配词包裹在span标签内
文本关键词匹配高亮实现方案
需求说明
遍历所有nodes文本节点,检查节点内容是否包含terms数组中的任意词条,匹配到的词汇自动用带自定义属性的span标签包裹,可用于后续术语提示等功能。
原代码存在的问题
- 正则表达式的单词边界符
\b在JS字符串中没有转义,实际被解析为退格符,导致匹配完全失效 - 未处理词条重叠问题:如
knowledge base包含base词条,短词条先替换会破坏长词条的匹配,长词条先替换后短词条又会匹配span标签内的文本,导致标签嵌套错乱 - 遍历文本节点时重复修改父节点innerHTML,会导致TreeWalker生成的原始文本节点失效,出现重复处理、内容错乱的问题
修复后完整实现代码
JavaScript 代码
function getAllTextNodes(element) { let node; let nodes = []; // 仅获取不在script、style、已有glossary span内的文本节点,避免二次处理 let walk = document.createTreeWalker( element, NodeFilter.SHOW_TEXT, { acceptNode: function(node) { const parentTag = node.parentNode.tagName.toLowerCase(); if (['script', 'style', 'span'].includes(parentTag)) { return NodeFilter.FILTER_REJECT; } return NodeFilter.FILTER_ACCEPT; } }, false ); while (node = walk.nextNode()) nodes.push(node); return nodes; } const editor = document.getElementById('editor'); const terms = [ { "id": 1, "definition": "知识库是公开发布的文档集合,通常包含常见问题解答、操作指南和故障排查内容", "expression": "knowledge base", }, { "id": 2, "definition": "知识管理(KM)是组织创建、共享、使用和管理知识与信息的流程", "expression": "knowledge management", }, { "id": 3, "definition": "这是测试内容", "expression": "base", }, { "id": 4, "definition": "管理术语测试项", "expression": "management", }, { "id": 5, "definition": "测试", "expression": "Knowledge", } ] // 先按词条长度降序排序,优先匹配更长的词条,避免短词条破坏长词条匹配 const sortedTerms = terms.sort((a, b) => b.expression.length - a.expression.length); getAllTextNodes(editor).forEach(node => { if (!node.parentNode) return; let text = node.textContent; let fragment = document.createDocumentFragment(); let lastIndex = 0; // 构建匹配所有词条的正则,一次性匹配所有符合条件的内容 const matchReg = new RegExp( sortedTerms.map(term => `\\b${term.expression}\\b`).join('|'), 'ig' ); let match; while ((match = matchReg.exec(text)) !== null) { // 先插入匹配位置之前的普通文本 if (match.index > lastIndex) { fragment.appendChild(document.createTextNode(text.slice(lastIndex, match.index))); } // 找到匹配的词条信息 const matchedTerm = sortedTerms.find(term => term.expression.toLowerCase() === match[0].toLowerCase() ); // 创建包裹用的span const span = document.createElement('span'); span.className = 'hj-glossary-item'; span.dataset.definition = matchedTerm.definition; span.dataset.id = matchedTerm.id; span.textContent = match[0]; fragment.appendChild(span); lastIndex = matchReg.lastIndex; } // 插入剩余的普通文本 if (lastIndex < text.length) { fragment.appendChild(document.createTextNode(text.slice(lastIndex))); } // 替换原始文本节点 node.parentNode.replaceChild(fragment, node); });
HTML 代码
<div id="editor"> <p>Management is the reason why knowledge management is important. Knowledge base is also important to base.</p> <p>Management is the reason why knowledge management is important. Knowledge base is also important to base.</p> <p>Management is the reason why knowledge management is important. Knowledge base is also important to base.</p> </div>
优化点说明
- 新增文本节点过滤逻辑,跳过已经是span、script、style标签内的内容,避免二次替换出现标签嵌套
- 词条按长度排序后优先匹配长词,避免重叠词条匹配混乱
- 用文档碎片+正则单次遍历的方式替换直接修改innerHTML,性能更高,也不会破坏原有DOM结构的其他事件绑定
内容的提问来源于stack exchange,提问作者Denis Omerovic
相关产品推荐
相关产品推荐

