前言
最近在工作组内code review时,看到一段代码:
// str的内容是一段富文本htmlstring
const tmp = str.replace(/<style>.*?<\/style>/g, '');
const dom = new DOMParser().parseFromString(tmp, 'text/html');
const text = dom.body.innerText;
// ...
这段代码的功能是,提取出一段富文本字符串中的用户可见的纯文本字符串。
当时大家不约而同地指出其中的两处知识点:DOMParser与innerText。按照大家的开发思路会写出以下代码:
const div = document.createElement('div');
div.innerHTML = str;
const text = div.innerText;
// ...
你们看,代码是不是简洁了很多?
针对同事的代码,同开发组的其他同事提出疑问:
- 为什么使用
DOMParser而不使用innerHTML,是有什么特殊用意吗? - 为什么要使用
replace将style替换掉。innerText不会获取到style或script标签内的文本,textContent才会获取到,所以可以不用replace。
不知你们有没有同样的疑惑,可以在评论区回复,反馈给笔者和其他读者你的想法。
那接下来就跟着笔者一步步来解惑,欢迎查漏补缺。
DOMParser
这个对象笔者平时没使用过,在
《vue的设计与实现》一书中见过。
DOMParser可以将储存在字符串中的xml或html源代码解析为一个DOM document。乍看与innerHTML在使用上没有明显的差别,不过在安全防御开发中DOMParser可能更安全。
const name = "<script>alert('I am John in an annoying alert!')</script>";
document.createElement('div').innerHTML = name; // harmless in this case
尽管这看上去网页会有弹框,但结果并不会导致什么。HTML 5 中指定不执行由 innerHTML 插入的script标签,好像我们可以放心地使用该方式。
然而,有很多不依赖script标签去执行 JavaScript 的方式。所以当你使用innerHTML 去设置你无法控制的字符串时,这仍然是一个安全问题。例如:
const name = "<img src='x' onerror='alert(1)'>";
document.createElement('div').innerHTML = name; // shows the alert
但是,如果我们采用DOMParser对象则可以避免这种情况的alert。
const name = "<img src='x' onerror='alert(1)'>";
const dom = new DOMParser().parseFromString(name, 'text/html'); // harmless in this case
因此,当我们想要采用DOM api去处理一段无法保证安全的富文本时,借助DOMParser将会是一个更好的做法。
innerText
它表示一个节点及其后代所渲染的文本内容。简而言之,它获取到的节点文本内容是用户可见的,像style,script,display:none等不可见的则不会获取到。
由于
innerText受 CSS 样式的影响,它会触发回流去确保是最新的计算样式。回流在计算上可能会非常昂贵,因此应尽可能避免,可以采用textContent替换。
那我们回到最初的疑问,为什么需要replace处理style,script标签。
通过查阅资料(虽然前期也看过多次😖),发现一直以来我忽略了的知识点。innerText表示一个节点及其后代所渲染的文本内容,如果元素本身没有被渲染(例如,从文档中分离出来或从视图中隐藏起来),返回值与textContent一致。
前言中的两段代码都是脚本生成的文档片段,并没有挂载到DOM树中,因此像这样的富文本:
<div>hello</div>
<style> div { color: red }</style>
<script>console.log(1)</script>
通过innerText或textContent拿到的文本都是hello div { color: red }console.log(1),所以同事在开发时发现了单独使用innerText无法满足需求,所以又采用了replace替换。然而,这样仍无法避免获取到设置了display:none隐藏的节点的文本。
在笔者采用上方的富文本例子进行测试时,返回的结果让当时信誓旦旦说采用innerText可行并不需要replace的同事们产生了认知颠覆的震惊。我很惊讶于大家陷入了一样的思维困境,都自动忽略了那个重要的知识😂。
戳一戳,快速体验👇
最后
很多时候大家都陷入了思维的局限性,相互之间的沟通交流能打破这面思维的墙,甚至能碰撞出精彩的观点。
就像文章中的innerText,笔者平时也时不时地查阅资料,在MDN innerText上多次看到,同时也多次自动忽略了那段文本,经由这次的code review才再次认真地认识它。
虽然是一个很小的知识点,老生常谈的知识点,但对于你们是否也是重新认识了它们呢?😬