saber 酱的抱枕

Fly me to the moon

03/30
2024
学习 软件

JavaScript 编码(反转义)HTML 转义字符

我发现文章里的语法高亮插件自动把一些转义字符给解码了,导致显示的不正确。可以移步知乎查看:
https://zhuanlan.zhihu.com/p/689946202

需求:反转义/解码字符

API 返回的某个字段里是用于渲染到页面上的 HTML 代码,其中可能混杂有转义字符、正常字符、HTML 标签。如下:

// 解码后应为:1,2'<br />3<4 5>
const str = '1&#44;2&#39;<br />3<4 5>'
// 3、4、5 后面的其实都是转义字符,但是高亮插件给自动解码了,所以你看到的是正常的字符

现在有个需求,将其保存到一个 txt 纯文本里,为了可读性,需要将转义字符变成其“真正”的字符,可以称为“反转义”或“解码”。(这里不需要处理 HTML 标签如 <br /> 等,原样保留即可)。

下面有 3 个方法,第一个是正确的,后面两个是不符合预期的:

// 解码后应为:1,2'<br />3<4 5>
const str = '1&#44;2&#39;<br />3<4 5>'

function htmlDecode (str) {
    const textarea = document.createElement('textarea')
    textarea.innerHTML = str
    return textarea.value
}
console.log(htmlDecode(str))
// 正确
// 1,2'<br />3<4 5>

function divInnerHTML (str){
    const div = document.createElement('div')
    div.innerHTML = str
    return div.innerHTML
}
console.log(divInnerHTML(str))
// 错误
// 1,2'<br>3<4 5>

function divInnerText(str){
    const div = document.createElement('div')
    div.innerHTML = str
    return div.innerText
}
console.log(divInnerText(str))
// 错误
// 1,2'3<4 5>

textarea.value

正确的方法是使用 textarea 标签,先将原文本设置为 innerHTML,因为设置 innerHTML 时浏览器是会解码转义字符的。

然后通过 textarea.value 就可以获取到解码后的字符了,并且 HTML 标签是原样保留的。

div.innerHTML

那么 div.innerHTML 呢?首先,将字符串设置为 div.innerHTML 时它是会解码转义字符的,但它的问题有 2 个:

问题 1: 获取 div.innerHTML 时,它把部分字符进行了转义,所以 > < 又变回了转义字符(,'倒是保持不变)。为什么会这样呢?规范如此,MDN 上有写:
https://developer.mozilla.org/zh-CN/docs/Web/API/Element/innerHTML

如果一个 <div>, <span>, 或 <noembed> 节点有一个文本子节点,
该节点包含字符 (&), (<), 或 (>), innerHTML 将这些字符分别返回为 &, < 和 >。
使用 Node.textContent 可获取一个这些文本节点内容的正确副本。

我尚不确定原因,硬要说的话,可能是为了防止这些特殊文本挨着 HTML 标签时,并且保持原样的话,会导致某些情况下解析 HTML 标签出错,所以将某些特殊字符又转义了。这导致此方法完全不可用。

问题2 :如果传入的是 html 4 标准的标签(比如 <br /> 是有闭合标记的),设置为 div.innerHTML 时现代浏览器会将其变成 html 5 标准的标签,如 <br>。之后读取时也是 <br>

这通常没有影响,但在某些只能兼容 html 4 的软件或环境中就会出错。例如 EPUB 格式的电子书文件就只能使用 html 4 的标签,如果使用 <br> 就会导致阅读器解析文件时出错,小说无法打开阅读。

div.innerText

既然获取 div.innerHTML 时它把某些字符又转义回去了,那使用 div.innerText 不就不会转义了吗?

确实如此,所以它可以用来解码。但它也有一些问题:

  1. 由于获取的是 text 而非 html,所以换行标签 <br /> 会变成 \n,这是个副作用。如果你希望原样返回 HTML 标签就不能用这个方法。
  2. 实际上,在上面的代码中换行标签完全丢失了,连 \n 都没有。

为什么换行标签会丢失?因为上面的代码没有把 div 添加到页面上,而是只在内存中,实际上文本没有被渲染,也就没有换行效果,所以也就没有 \n

如果将 div 添加到页面上 document.body.append(div) 然后再获取就会有 \n 了,但这会影响性能。

div.textContent

在上面的代码中,textContentinnerText 的表现相同(因为它们都没有返回 \n,所以输出是完全相同的)。

JavaScript 编码(反转义)HTML 转义字符