我发现文章里的语法高亮插件自动把一些转义字符给解码了,导致显示的不正确。可以移步知乎查看:
https://zhuanlan.zhihu.com/p/689946202
需求:反转义/解码字符
API 返回的某个字段里是用于渲染到页面上的 HTML 代码,其中可能混杂有转义字符、正常字符、HTML 标签。如下:
// 解码后应为:1,2'<br />3<4 5>
const str = '1,2'<br />3<4 5>'
// 3、4、5 后面的其实都是转义字符,但是高亮插件给自动解码了,所以你看到的是正常的字符
现在有个需求,将其保存到一个 txt 纯文本里,为了可读性,需要将转义字符变成其“真正”的字符,可以称为“反转义”或“解码”。(这里不需要处理 HTML 标签如 <br />
等,原样保留即可)。
下面有 3 个方法,第一个是正确的,后面两个是不符合预期的:
// 解码后应为:1,2'<br />3<4 5>
const str = '1,2'<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
不就不会转义了吗?
确实如此,所以它可以用来解码。但它也有一些问题:
- 由于获取的是 text 而非 html,所以换行标签
<br />
会变成\n
,这是个副作用。如果你希望原样返回 HTML 标签就不能用这个方法。 - 实际上,在上面的代码中换行标签完全丢失了,连
\n
都没有。
为什么换行标签会丢失?因为上面的代码没有把 div 添加到页面上,而是只在内存中,实际上文本没有被渲染,也就没有换行效果,所以也就没有 \n
。
如果将 div 添加到页面上 document.body.append(div)
然后再获取就会有 \n
了,但这会影响性能。
div.textContent
在上面的代码中,textContent
和 innerText
的表现相同(因为它们都没有返回 \n
,所以输出是完全相同的)。
学到了