replace 函数处理含$'字符踩坑记录

111 阅读1分钟

问题场景

在一个需要替换 HTML 内容的场景中,我们使用 replace 函数来替换一个 div 元素,但是得到了意外的结果:原本的 HTML 闭合标签被重复了多次。

原始代码

大概如下

const newRootHTML = '<div id="root">11</div>\n \n\t<script> \n\n window.PAGE_DATA={"username":"€%.&\u002F$'.€\£¥~#"}'; 
const rootHTML = '<div id="root"></div>'; 
const content = '<!DOCTYPE html>\n<html lang="en">\n\n<body>\n <div id="root"></div>\n</body>\n\n</html>\n'; 
const result = content.replace(rootHTML, newRootHTML);

问题现象

执行上述代码后,我们发现输出结果中包含了多个 </body></html>闭合标签。

实际输出

<!DOCTYPE html>
<html lang="en">

<body>
 <div id="root">11</div>
 
	<script> 

 window.PAGE_DATA={"username":"€%.&/
</body>

</html>
.€\£¥~#"}
</body>

</html>

排查过程

  1. 首先验证简单替换场景:

    const test1 = "<html><body><div>test</div></body></html>"; 
    const simple = test1.replace("<div>test</div>", "<div>abc/def</div>"); // 输出正常,没有重复的闭合标签
    
  2. 测试带有 Unicode 转义的场景:

    const test2 = "<html><body><div>test</div></body></html>"; 
    const withUnicode = test2.replace("<div>test</div>", "<div>abc\u002Fdef</div>"); // 输出正常,说明 Unicode 转义不是问题根源
    
  3. 测试包含特殊字符的场景:

    const test3 = "<html><body><div>test</div></body></html>"; 
    const withSpecial = test3.replace("<div>test</div>", '<div>{"user":"€%.&\u002F$'.€\£¥~#"}</div>'); // 出现了重复的闭合标签!
    

原因分析

问题的根源在于 JavaScript 的 replace函数支持特殊的替换模式:

  • $&: 插入匹配的子串
  • $`: 插入当前匹配的子串之前的内容
  • $': 插入当前匹配的子串之后的内容
  • $n: 插入第 n 个捕获组的内容
  • $$: 插入字面量的 $

在我们的场景中,字符串中的 $'replace 函数解释为"插入匹配内容之后的所有内容",这就导致了 HTML 闭合标签被重复插入。

验证测试

const str = "hello world goodbye"; 
console.log(str.replace("world", "test$'")); // 输出: "hello test goodbye goodbye" // ^^^^^^^^ 这部分被重复了

解决方案

  1. 使用正则表达式的替换函数:
const result = content.replace(rootHTML, (match) => newRootHTML);

知识点总结

  1. JavaScript replace 函数支持特殊的替换模式
  2. 在替换字符串中,$ 后跟特定字符会触发特殊替换行为
  3. 处理包含特殊字符的字符串替换时需要特别注意

实际应用建议

  1. 处理复杂字符串替换时,优先考虑使用替换函数而不是直接的字符串替换
  2. 如果必须使用字符串替换,记得转义特殊字符
  3. 在处理包含用户输入或复杂数据的场景时,要特别注意 replace 的特殊替换模式
  4. 建议在关键场景下添加测试用例,验证替换结果

参考资料