👩‍💻:innerText,很高兴重新认识你

2,089 阅读4分钟

前言

最近在工作组内code review时,看到一段代码:

// str的内容是一段富文本htmlstring
const tmp = str.replace(/<style>.*?<\/style>/g, '');
const dom = new DOMParser().parseFromString(tmp, 'text/html');
const text = dom.body.innerText;
// ...

这段代码的功能是,提取出一段富文本字符串中的用户可见的纯文本字符串。

当时大家不约而同地指出其中的两处知识点:DOMParserinnerText。按照大家的开发思路会写出以下代码:

const div = document.createElement('div');
div.innerHTML = str;
const text = div.innerText;
// ...

你们看,代码是不是简洁了很多?

针对同事的代码,同开发组的其他同事提出疑问:

  1. 为什么使用DOMParser而不使用innerHTML,是有什么特殊用意吗?
  2. 为什么要使用replacestyle替换掉。innerText不会获取到stylescript标签内的文本,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>

通过innerTexttextContent拿到的文本都是hello div { color: red }console.log(1),所以同事在开发时发现了单独使用innerText无法满足需求,所以又采用了replace替换。然而,这样仍无法避免获取到设置了display:none隐藏的节点的文本。

在笔者采用上方的富文本例子进行测试时,返回的结果让当时信誓旦旦说采用innerText可行并不需要replace的同事们产生了认知颠覆的震惊。我很惊讶于大家陷入了一样的思维困境,都自动忽略了那个重要的知识😂。

戳一戳,快速体验👇

最后

很多时候大家都陷入了思维的局限性,相互之间的沟通交流能打破这面思维的墙,甚至能碰撞出精彩的观点。

就像文章中的innerText,笔者平时也时不时地查阅资料,在MDN innerText上多次看到,同时也多次自动忽略了那段文本,经由这次的code review才再次认真地认识它。

虽然是一个很小的知识点,老生常谈的知识点,但对于你们是否也是重新认识了它们呢?😬