项目中需要在网页中实现关键字高亮,原以为用 innerHTML.replace 即可,但实际遇到了许多问题。本文总结几种方法及最终的最佳方案。
方式一:简单正则替换
基本思路:直接用正则将关键字包裹成标签
const regex = new RegExp(keyword, 'g'); element.innerHTML = element.innerHTML.replace(regex, '' + keyword + '');
问题:
污染原始 DOM,影响后续逻辑
容易破坏结构,如属性中含有关键字或特殊符号
方式二:正则优化尝试
尝试规避标签和属性内容:
var safeKeyword = keyword.replace(/[-/\^$*+?.()|[]{}]/g, '\$&');
var finder = new RegExp('>[^<]*?' + safeKeyword + '[^<]*?<', 'g');
element.innerHTML = element.innerHTML.replace(finder, function(m) {
return m.replace(new RegExp(safeKeyword, 'g'), '' + keyword + '');
});缺陷: 依旧无法处理标签属性中含有 < 或 > 的情况。
方式三:使用占位符处理标签
let html = element.innerHTML; html = html.replace(//g, '[divStart]'); html = html.replace(/
');
html = html.replace(/[divEnd]/g, '
');问题: 若 placeholder 与关键字冲突,替换会出错,仍不安全。
最终方案:基于 DOM 节点递归处理
通过递归遍历 DOM,查找文本节点并替换关键字,无需操作 innerHTML,避免破坏结构。
function highlight(node, keyword) {
const reg = new RegExp(keyword.replace(/[-/\^$*+?.()|[]{}]/g, '\$&'));
if (node.nodeType === 3) {
const match = node.data.match(reg);
if (match) {
const highlightEl = document.createElement("b");
highlightEl.textContent = match[0];
highlightEl.dataset.highlight = "y";
const wordNode = node.splitText(match.index);
wordNode.splitText(match[0].length);
wordNode.parentNode.replaceChild(highlightEl, wordNode);
}
} else if (node.nodeType === 1 && node.dataset.highlight !== "y") {
for (let i = 0; i < node.childNodes.length; i++) {
highlight(node.childNodes[i], keyword);
}
}
}优点:
安全:不破坏原始 DOM
灵活:可处理嵌套元素
可控:通过
dataset标记已高亮内容
总结
基于 DOM 的高亮方式是目前最稳定、安全的方案。建议大家在实际项目中直接采用递归遍历 + 文本节点替换的方式。