由一篇文章引申出的关于原生 JavaScript 在 createTextNode 和 innerText 操作的性能差异的探究
#
前言昨天在浏览 RSS 推送的文章时,看到奇舞周刊推送了一篇 《我对 Svelte 的看法》 的文章,看到其中一段代码示例,使用原生 JavaScript
实现一个不使用 Virtual DOM
的 Counter:
发现示例中给相关的 DOM 插入 Text 使用的是 document.createTextNode
,由于我在写代码的过程中基本没有使用过这种方式来创建 TextNode 并作为子节点插入到相关的 DOM 中,于是对这个方法有了一丝好奇。
通常情况下,我都会选择使用 innerText
的方式将 Text 插入到相关的 DOM 中,这就产生了一个问题:
- 在写代码的时候,到底是使用
document.createTextNode
创建 TextNode 并插入到相关的 DOM 中,还是直接使用innerText
将 Text 插入更加高效?
#
思路为了获得这个问题的答案,第一个想到的办法是使用 Jest
分别跑两段测试代码,然后查看两者之间的时间差。
但是仔细一想,Jest
是跑在 Node
环境下的,不是浏览器环境,也就没有 DOM 对象,只能通过 JSDOM
来模拟,这样的话就是运行纯 JavaScript
了,少了 JavaScript
通过 Web IDL
修改 DOM 对象的过程,也就没办法测出这两种方式的差异。
关于这个知识点,可以看一下这份知乎回答:前端为什么操作 DOM 是最耗性能的呢? - justjavac 的回答 - 知乎。
不过嘛,既然是测试,跑一跑也无妨,验证一下是不是真的如我想的那样无法测出非 DOM 操作和 DOM 操作之间的差异。
JSDOM
#
document.title
& document.xxx
#
先上两段代码,来验证一下在 Node
环境下是不是真的无法测出非 DOM 操作和 DOM 操作之间的差异。
运行之后发现,事实并非我们想象的那样:
问题来了,为什么即使是在 Node
环境下也有如此大的差异?
Debug 了一轮之后发现,对 document.title
做修改的时候,会触发 setter
:
而对 document.xxx
做修改的时候仅仅只是一个赋值操作:
这就出现区别了,document
对象是使用 Object.defineProperty
包装过部分属性的,其中 title
就重写了它的 getter/setter
,因此在修改 document.title
的时候耗时明显增加。
不过这是在测试修改 document.title
和 document.xxx
的差异,跑题了!
现在回到测试 document.createTextNode
和 innerText
这两者的差异上。
createTextNode
& innerText
#
结果出人意料:
非常意外!于是我又 Debug 了一轮,发现 JSDOM
提供的 getElementById
的方法返回的 HTMLElement
并没有 innerText
这个属性。
与上面修改 document.xxx
的情况一样,修改 innerText
变成了一个简单的赋值操作。
#
Chrome#
Version 1既然在 Node
环境下没办法完成这个测试,于是我将试验场更换到了 Chrome,让我们看一下在 Chrome 中运行会有什么差异。
于是我创建了两个页面来进行测试:
结果出人意料,差距实在太大了:
这样看来,createTextNode
的性能显然比修改 innerText
好太多了。
不应该会有这么大的差距啊!是什么问题呢?
回到 JavaScript
代码,对比之后发现,修改 innerText
为了实现和 createTextNode
一样的行为,有一个 +=
操作。
难道是这里耗时严重?
思考之后,决定重新设计这个实验,去掉 +=
的操作,同时要保证最终的实现效果一致。
#
Version 2实验改进之后,应该算严格控制了两个实验行为一致了,去掉了 +=
操作,现在结果就非常接近了:
为了确保这个结果是准确的,我们将循环次数放大一个数量级,再测试一次。
循环次数放大一个数量级之后,结果是这样的:
依然非常接近,基本没有差别。
是不是就能确定这两种不同的写法性能上没区别呢?
事实上改进后的实验,可能还存在问题,问题出在哪呢?
#
Version 3其实问题出在了循环体中的代码顺序上:
为什么说问题出在了循环体中的代码顺序上呢?
因为在 child
被插入到 root
之前,它还是一个 JavaScript
对象,对 JavaScript
对象的属性做修改的速度是非常快的。
所以我们又修改了一下,把 child
插入 root
的代码提前,先插入到 root
中,再修改 Text。
再来看结果:
到这里我们应该能够确定,这两种方式插入 Text 在性能上没有区别了。
#
总结因为一次技术文章的阅读引出这么一些内容,然后测试了一下,也不确定测试方法对不对,如果有跟了解的大佬可以指点一下~