直接使用 innerHTML.replace()(如 replace('a text', 'a text'))看似简洁,但存在严重缺陷:它会在所有上下文(包括 HTML 标签属性、注释、script 内容等)中无差别替换,极易破坏 DOM 结构。例如,若页面中存在 或 ,该方法会错误地插入标签,导致解析失败或安全漏洞。
更可靠的做法是:先将 HTML 解析为真实 DOM 节点,再遍历文本节点(Text Nodes),在纯文本内容中进行语义化查找与包裹。以下是推荐实现方案:
function wrapTextInRange(htmlString, startText, endText, options = {}) {
const { id = 'phrase_1', tag = 'span', className = '' } = options;
// 步骤1:创建临时容器解析 HTML(不执行脚本,规避 XSS)
const temp = document.createElement('div');
temp.innerHTML = htmlString;
// 步骤2:递归查找并处理所有文本节点
function walk(node) {
if (node.nodeType === Node.TEXT_NODE && node.textContent.trim()) {
const text = node.textContent;
const startIndex = text.indexOf(startText);
const endIndex = text.indexOf(endText);
// 确保 s
tartText 出现在 endText 之前,且两者在同一文本节点内
if (startIndex !== -1 && endIndex !== -1 && startIndex <= endIndex) {
const wrapper = document.createElement(tag);
wrapper.id = id;
if (className) wrapper.className = className;
// 拆分文本节点:[前][中间][后]
const before = text.slice(0, startIndex);
const middle = text.slice(startIndex, endIndex + endText.length);
const after = text.slice(endIndex + endText.length);
// 替换当前文本节点为:文本(前) + 包裹元素 + 文本(后)
const fragment = document.createDocumentFragment();
if (before) fragment.appendChild(document.createTextNode(before));
wrapper.appendChild(document.createTextNode(middle));
if (after) fragment.appendChild(document.createTextNode(after));
node.parentNode.replaceChild(fragment, node);
return true;
}
}
// 深度优先遍历子节点
for (let child of node.childNodes) {
if (walk(child)) return true;
}
return false;
}
walk(temp);
return temp.innerHTML;
}
// 使用示例
const html = `This is a text that needs to be manipulated`;
const result = wrapTextInRange(html, 'a text', 'needs', {
id: 'phrase_1',
tag: 'span'
});
console.log(result);
// 输出:
// This is a text that needs to be manipulated