DOM元素过多实测:112个首页68.8%越过1400这条线
本文目录
- DOM的大小,该按哪把尺子量?
- 三个数各不相同
- 本文的口径
- 112个首页的静态DOM,到底有多大?
- 字节多,就等于元素多吗?
- 元素都是些什么标签
- 放进真浏览器跑一遍,这个数变成多少?
- 从11个元素长到1878个,真是脚本干的吗?
- 解析器停在了第1260字节
- 另外两种长法
- 那脚本到底净造了多少元素?
- 先确认基线本身干净
- 净增中位只有7%
- 20秒里,元素是什么时候长出来的?
- 还有一层数不进去的树
- 两轮跑下来,这些数字稳不稳?
- DOM大了,代价具体落在哪儿?
- 对用户
- 对搜索引擎
- 自己站上怎么量,改什么
- 有三件事不建议做
- 常见问题解答
- Lighthouse说的800和1400,是元素还是节点?
- 我的页面第1秒只有几十个元素,是客户端渲染的锅吗?
- 那到底怎么判断脚本有没有把DOM撑大?
- 删掉一些div能省多少?
- 内联svg图标要不要换成雪碧图?
- Googlebot渲染的时候会等到第20秒吗?
- 深度和宽度,哪个更值得先治?
- 影子DOM里的节点算不算进去?
- 这批数字能代表我的站吗?
- 权威参考资料
摘要:112份海外品牌首页的静态HTML按Lighthouse的口径数了一遍,body里的节点中位2053个,68.8%越过1400这条报错线,86.6%越过800那条警告线,最大的一份22374个。真正容易看错的是另一头:把其中20个站放进真浏览器跑两轮,第1秒到第20秒元素最多涨了168倍,看着像整页由脚本现搭。可拿浏览器自己收到的那份原始文档一比,脚本净增的元素中位只有7%,19个站里只有3个翻倍,反倒有3个站脚本删掉的比加上的多。那个168倍量的根本不是脚本产量,是解析器在第1260字节被一个第三方脚本卡住了。
上一篇量的是字节:一份HTML怎么到,浏览器要读到第几个字节才第一次有活干。这一篇往后挪一步,看那些字节落地之后长成了什么。
页面体积和DOM规模常常被混在一起说,其实是两件事。一份150万字符的文档可能只造出238个元素,另一份60万字符的能造出8000个。前者的字节都堆在数据块里,后者的字节都花在标签上。真要判断一个页面重不重,得把这两把尺子分开拿。
DOM的大小,该按哪把尺子量?
先把口径钉死,因为这一层的误差比想象中大。
三个数各不相同
Lighthouse那条避免过大DOM的审计给的是两条线:body元素下超过800个节点开始警告,超过1400个判为过大。web.dev那篇讲DOM规模与交互性的文章把术语说得更细——审计数的是节点,节点包含元素、文本和注释;而平时写代码时习惯数的是元素。
这个差别不小。112份文档量下来,body里的元素中位1561.5个,加上非空文本节点和注释之后中位2053个,只数元素会把这个数低估24%。注释这一项两头极端,中位19条,最多的一份1545条;文本节点中位342.5个,最多3244个。
把这个倍数排一遍,两头差得很开:article.com的body节点是它body元素的2.38倍,因为那份文档里有1545条注释;on.com 2.16倍、ikea.com 1.79倍。另一头有二十来个站几乎是1比1,文本节点和注释都极少——那通常意味着页面上的可读文字本来就不多。
还有一个容易忽略的口径:审计只看body子树,head不算。而这批站的head元素中位113个,占全文元素的7.1%;最多的harrys.com有976个,burrow.com 568个,aloyoga.com 512个——这些数字全部落在审计的统计范围之外,可它们照样要被解析、要占内存。用document.querySelectorAll('*').length随手一数,数的是整份文档,天然比审计口径大一截。MDN对querySelectorAll的说明里写明它匹配的是整个文档下的元素,要对齐审计口径得从body起数。
本文的口径
下文凡是提“节点”,都是body子树里的元素加非空文本加注释,跟审计口径对齐;凡是提“元素”,都是元素标签本身。静态那一层从112份首页HTML原样解析,剔掉了6份没有标题标签或不足20000字符的拦截页;运行时那一层用真浏览器,读的是整份文档的元素数,跟静态层的元素数可比,跟审计的1400那条线不直接可比。
112个首页的静态DOM,到底有多大?
| 指标 | 中位 | 四分位 | 最大 | 最小 |
|---|---|---|---|---|
| body节点(对齐审计口径) | 2053 | 1135 / 3002 | 22374 | 14 |
| body元素 | 1561.5 | 861 / 2428 | 19126 | 12 |
| head元素 | 113 | 87 / 150 | 976 | 15 |
| 最大嵌套深度 | 20 | 16 / 24 | 38 | 3 |
| 最宽父节点的子元素数 | 34 | 20 / 57 | 663 | 8 |
按两条线数一遍:
| 判定 | 站数 | 占112份 |
|---|---|---|
| body节点超过800(开始警告) | 97 | 86.6% |
| body节点超过1400(判为过大) | 77 | 68.8% |
| body元素超过1400 | 63 | 56.2% |
| 最大深度超过32 | 4 | 3.6% |
| 有父节点带超过60个子元素 | 25 | 22.3% |
七成的品牌首页在这条审计上是不及格的。这个比例高到已经说明不了什么问题——它更像是在说这条线对电商首页这种形态偏严,而不是说这七成站都做错了。真正值得看的是尾部:misen.com的body里有22374个节点,是那条线的16倍;parachutehome.com 14436个;casetify.com 12560个。深度那一栏倒是普遍不深,只有4个站超过32层:bigcommerce.com 38层、misen.com 37层、untuckit.com 35层、on.com 33层。最宽的那个父节点在jysk.com,一口气挂了663个子元素。
字节多,就等于元素多吗?
不等于,而且差得很远。
112份文档的密度中位是每KB约2.65个body元素。两头的样本完全撕开:
| 站点 | 文档字符数 | body元素 | 每KB元素数 | 字节主要花在哪 |
|---|---|---|---|---|
| kotn.com | 1522397 | 160 | 0.11 | 一个巨大的序列化数据块 |
| bolia.com | 118644 | 23 | 0.20 | 元素属性里的内联表达式 |
| monos.com | 4889351 | 4631 | 0.97 | 数据块与标记各占一半 |
| casetify.com | 1463431 | 10897 | 7.62 | 商品卡片标记 |
| dji.com | 161633 | 1493 | 9.46 | 规格表与卡片标记 |
密度最低的六个站是kotn.com 0.11、burrow.com 0.17、bolia.com 0.20、sostrenegrene.com 0.32,再往上是byredo.com和prose.com各0.39;最高的六个是dji.com 9.46、casetify.com 7.62、sulwhasoo.com 6.65、brompton.com 6.36、weber.com 6.33、joolz.com 6.18。最高和最低差了88倍,而这两批站的文档字节数常常是同一个量级。
kotn.com那份最典型:150万字符的HTML里只有160个body元素,字节几乎全在一个前端框架用来接管页面的数据副本里。这种形态在内联字节那一轮量过另一面,也是页面体积撞上抓取上限里最常见的元凶。反过来casetify.com一份140多万字符的文档造出一万多个元素,那是实打实的商品卡片。
元素都是些什么标签
div占全部元素的比例中位23.7%,四分之一的元素是没有语义的容器。这个比例本身不算离谱,语义化能改善的空间在语义化标签那一篇里讲过。更有意思的是图标:svg与path加起来占全部元素中位6.4%,misen.com一个站就有1931个svg和1107个path,光图标就吃掉了它15.7%的元素预算。把内联图标换成雪碧图或者按需渲染,对这类站是单笔最划算的削减。
放进真浏览器跑一遍,这个数变成多少?
静态层只能说明服务器发了什么。用户和渲染型抓取面对的是脚本跑完之后的那棵树。
挑了20个站,覆盖从几十个元素到近两万个元素的整个跨度,用无头浏览器按1440×900打开,在第1秒、第3秒、第8秒、第20秒各拍一次快照,整套跑两轮。
| 站点 | 第1秒 | 第3秒 | 第8秒 | 第20秒 | 原始文档元素 | 第20秒的请求数 |
|---|---|---|---|---|---|---|
| magicspoon.com | 11 | 1541 | 1729 | 1878 | 1456 | 548 |
| casetify.com | 90 | 10960 | 11144 | 11177 | 10985 | 138 |
| bolia.com | 60 | 912 | 914 | 915 | 54 | 35 |
| kotn.com | 39 | 351 | 578 | 577 | 238 | 91 |
| parachutehome.com | 13774 | 14345 | 56571 | 56604 | 13714 | 654 |
| misen.com | 17724 | 19249 | 19333 | 20123 | 19328 | 588 |
| anker.com | 2113 | 2113 | 2113 | 2113 | 2501 | 26 |
| nativecos.com | 4512 | 4563 | 6210 | 6409 | 8081 | 346 |
| boohoo.com | 59 | 6417 | 8029 | 8033 | 6492 | 250 |
| quince.com | 113 | 4081 | 4100 | 4103 | 4048 | 221 |
| jackery.com | 8253 | 14065 | 14159 | 14229 | 8264 | 406 |
| monos.com | 4736 | 6767 | 6769 | 6769 | 4773 | 258 |
| everlane.com | 4125 | 4129 | 4375 | 4375 | 4088 | 355 |
| dreametech.com | 4846 | 5647 | 12 | 12 | 4985 | 366 |
第20秒的元素数中位3589,最大56604,最小12。深度中位22.5。请求数中位250,最多的一次到了1000。第一列到第四列的跨度触目惊心:magicspoon.com从11个长到1878个,是168倍。
但这个168倍,量的不是脚本的产量。
从11个元素长到1878个,真是脚本干的吗?
把magicspoon.com那一秒的快照拆开看,当时页面上的11个元素是:html、head、title、一个link、六个meta,加一个script。这不是一棵被脚本搭起来一半的树,这是一份刚开始解析的文档。
解析器停在了第1260字节
浏览器自己记的时间是:这份文档在第374毫秒就收完了。441787个字符全在手上,可到第1000毫秒,解析器只走到第11个元素。
原因在文档的第1260字节:那里有一个不带异步标记的外部脚本,指向一家第三方实验平台的域名。HTML标准里关于解析的这一章规定得很死,碰到这类脚本,解析必须停下来,等它下载完、执行完再继续。文档早就躺在内存里,可后面那四十几万字符一个都还没变成元素。
换句话说,第1秒那个数字量的是网络与解析的进度,不是脚本的产量。这跟上一篇量字节流开工点是同一件事的两端:那边看的是浏览器什么时候能开始下第二样东西,这边看的是它什么时候能把字节变成节点。
另外两种长法
casetify.com第1秒只有90个元素,其中49个link、35个meta——一整个head,body还是空的。它的原因跟magicspoon.com不同:这份文档到第2059毫秒才收完,第1秒的时候后半截还在路上。关键渲染路径那一轮讲的阻塞机制和这里说的是同一套,只是那篇讲怎么改,这篇量的是发生频率。
dreametech.com是第三种,也是唯一一个真出事的:它在第3秒还有5647个元素,到第8秒整页只剩12个——一个html、一个head、一个title、三个meta、四个div、一条水平分隔线,正文139个字符。两轮跑下来一模一样,说明不是偶发。这个形态是典型的服务端错误页或者风控拦截页把原页面顶掉了。凡是量到“DOM缩水”的,先确认自己还在原来那个页面上。
那脚本到底净造了多少元素?
要回答这个,得换一条基线:不能拿第1秒当起点,得拿浏览器自己收到的那份原始文档当起点。
先确认基线本身干净
为此专门跑了第三轮,用同一个浏览器把这20个站的主文档响应体原样存下来,再用跟静态层完全一样的解析器数一遍。这一步是为了排掉两种污染:语料是两天前抓的,中间可能改版;以及有的站给命令行客户端和给浏览器的不是同一份文档。
结果是19个能取到的站里,18个的元素数与两天前那份语料一个不差,只有boohoo.com差了1.2%。两种担心都没发生,基线可以用。eufy.com这一轮连续两次导航超时没取到,从这一节的统计里剔除。
净增中位只有7%
| 站点 | 第20秒元素 | 原始文档元素 | 比值 |
|---|---|---|---|
| bolia.com | 915 | 54 | 16.94 |
| parachutehome.com | 56604 | 13714 | 4.13 |
| kotn.com | 577 | 238 | 2.42 |
| jackery.com | 14229 | 8264 | 1.72 |
| magicspoon.com | 1878 | 1456 | 1.29 |
| casetify.com | 11177 | 10985 | 1.02 |
| anker.com | 2113 | 2501 | 0.84 |
| buckmason.com | 663 | 815 | 0.81 |
| nativecos.com | 6409 | 8081 | 0.79 |
19个站的比值中位数是1.07,也就是脚本净增了7%的元素;超过2倍的只有3个,而有3个站的脚本删掉的比加上的多。casetify.com那个看起来吓人的125倍,从这条基线看只有1.02——它的一万多个元素全是服务端发过来的,脚本一个没添。
删元素这件事值得单说。nativecos.com从8081降到6409,逐个标签比对能看清是怎么回事:div少了546个、span少了1000个,同时多出297个输入框、288个标签和200个下拉选项。脚本不是在删,是把一整块静态标记换成了真正的表单控件。buckmason.com是另一种,div、a、img全线减少,像是把服务端渲染的重复变体收掉了。
所以“前端框架会把DOM撑大”这个说法,在这批站上只有三分之一不到成立。关掉脚本再抓一遍那一轮量的是内容会不会丢,这一轮量的是结构会不会胀,两个方向的结论倒是一致:真正出事的是少数站,不是人人有份。
20秒里,元素是什么时候长出来的?
逐段看快照之间的增量,节奏分得很清楚。
把每个站第3秒和第8秒的读数各跟第20秒比一遍,差在5%以内就算定型:第3秒定型的两轮分别是11个和10个站,到第8秒都是18个。换句话说八到九成的站在开页后八秒内就不再长了,剩下两个站的动静发生在更靠后。
多数站在第3秒就基本定型。20个站里有11个第3秒到第20秒的增量不到5%,misen.com从19249长到20123,everlane.com从4129到4375,casper.com从3053到3075。这些站的脚本干的是补齐工作,不是搭建工作。
真正的大动作发生在第3秒之后的只有一个:parachutehome.com第3秒还是14345个元素,第8秒变成56571个,五秒里多出四万多。拆开看,多出来的是7622个色卡组件、10055个链接和8526个列表项——这是把整个商品目录连同每件商品的所有颜色变体一次性铺进了DOM。它第20秒的请求数是654,第二轮跑到了1000。这种页面对用户是流畅的无限滚动,对内存和样式重算是一笔实打实的账。
另一头,anker.com四次快照全是2113,一个元素都没动,正文却有39561个字符,是20个站里正文最多的。它整页只发了26个请求。这是这批样本里唯一一个纯服务端渲染的形态。
还有一层数不进去的树
顺手记了每个站有多少个影子根。20个站里14个至少有一个,jackery.com有96个,casper.com 11个,magicspoon.com 10个。querySelectorAll穿不进影子根,所以上面所有的元素数其实都少算了这部分——组件用得越多,少算得越厉害。这一层对机器读取的影响比对性能更值得留意,因为里面的内容在常规查询里是隐形的。要真数清楚,得逐个元素判断有没有影子根再往里递归。
两轮跑下来,这些数字稳不稳?
上一篇的结论是字节稳、时间不稳。DOM这一层更接近字节那一侧。
20个站两轮的第20秒元素数,相对差中位0.13%,超过5%的只有2个。深度那一栏20个站两轮完全相同,一个都没变。
两个例外都能解释。anker.com第一轮2113、第二轮2509,差18.7%——第一轮它的文档到第8336毫秒才收完,比第二轮慢得多,这跟上一篇量到它下载窗口两轮从0.593秒跳到1.362秒是同一件事的两个观察面。boohoo.com第一轮第1秒只有59个元素、第二轮6417个,第一轮那次也是文档还在路上。
这里有一条可以直接搬走的判据:DOM规模本身是稳定量,可以只跑一遍;但只要读数取自某个固定时刻,它就掺进了当次的网络运气,必须跑两遍。第1秒那一列是最脏的,第20秒那一列基本可信。
DOM大了,代价具体落在哪儿?
审计文档里列了三条:初次渲染时样式计算与布局更重,交互时浏览器要反复重算节点的位置和样式,以及用通用选择器查询会在内存里攒下大量引用。
对用户
最直接的是交互延迟。web.dev关于交互到下一次绘制的说明把这条指标拆得很细,节点多、选择器复杂的时候,一次点击引发的重算会明显变长。这条指标怎么排查,交互到下一次绘制那一篇给过完整流程;布局抖动那一侧则在累积布局偏移那一篇里。样式那一层的负担也别忽略,CSS覆盖率那一轮量到主样式表里八成以上的类名在当前页面一次都没用上,这些规则要和五万多个节点逐一比对;优先级标记那一轮量的是同一层的另一种复杂度。
顺带一个容易被忽略的连带项:元素多的页面往往请求也多。本轮第20秒的请求数中位250,parachutehome.com那一次到了1000个,misen.com 588个,而元素最少的anker.com只有26个。请求这一侧的账在第三方域名那一轮算过,两笔账通常出自同一批组件。
对搜索引擎
渲染型抓取跑的是同一套内核。Google关于JavaScript与搜索的基础文档说明了渲染是排队进行的,页面越重排得越靠后。这条链路各步骤怎么走,抓取与渲染分几步那篇拆过;要不要为此上服务端渲染,取消警告之后该怎么选那篇讨论过。
还有一层是给机器读的结构。节点多不等于信息多——本轮anker.com用2113个元素承载了39561个正文字符,另一头有站用五万多个元素承载六千字符。智能体读的是无障碍树那一篇讲过机器实际消费的是哪一层,首页空壳那一轮量的是这一层的极端情况,隐藏内容算不算那一轮量的则是节点在不在树上与算不算数之间的差别。
自己站上怎么量,改什么
三步。
第一步,量两个数而不是一个。在控制台跑document.body.querySelectorAll('*').length拿元素数,再跑一遍完整的节点统计——把文本节点和注释算进去,才是审计口径。两个数差得越多,说明文档里的文本与注释越密。想对齐官方判定,直接跑一次Lighthouse,它会同时报出总节点数、最大深度和最宽父节点。
第二步,把静态和运行时分开看。先用命令行取一次首页HTML存成文件,数里面的元素;再在浏览器里等页面完全静下来数一次。两个数的比值才是脚本的净产量。比值接近1,问题在服务端模板;比值大于2,问题在客户端渲染策略;比值小于1,去查脚本在删什么,那通常意味着服务端发了一份没人用的标记。
第三步,找最宽的那个父节点。本轮最宽的一个带了663个子元素,多半是一整个商品列表或者一个下拉里的全部选项。Lighthouse那条避免过大DOM的审计建议的办法是只在需要时创建节点、用完就销毁,长列表用虚拟滚动。这类改动的收益最集中,因为削掉的是同一个模式重复出来的成百上千个节点。
有三件事不建议做
一是别为了把数字压到1400以下去删语义标签,把section、article换成div什么都没省下,还丢了结构信号——H标签这一层的坑在视觉层级和标题标签对不上那一轮里有实例。二是别把首屏内容改成点击后才渲染,那是把渲染成本换成了内容不可见,代价通常更大。三是别只看数字不看构成,同样是五千个元素,一个是五千件商品的列表,一个是同一个组件嵌了三十层,处理方式完全不同。
保哥手上一个3C配件独立站碰上的是第二种:首页头部导航为了做多级悬停,同一份菜单在不同断点各渲染了一遍,三套并存,光导航就占掉两千多个元素。改法是把断点判断挪到服务端,只发一套,元素数直接掉了三分之一,页面上什么都没少。这种改法的前提是站点有服务端渲染能力,纯静态托管的站得换成脚本按需挂载,风险要高一些。
常见问题解答
Lighthouse说的800和1400,是元素还是节点?
是节点,而且只算body子树里的。节点包含元素、文本和注释,所以比元素数大。本轮112份文档里,body节点比body元素中位多24%,最多的一份多了138%。用querySelectorAll('*')数出来的既漏了文本和注释,又多算了head,两个偏差方向相反,很容易凑巧对上又其实不对。
我的页面第1秒只有几十个元素,是客户端渲染的锅吗?
先别下这个结论。本轮三个第1秒元素极少的站,原因各不相同:一个是文档还没收完,一个是解析器卡在第1260字节的同步脚本上,还有一个是整页被错误页顶掉了。判据是看浏览器自己记的响应结束时刻,以及第一个不带异步标记的外部脚本在文档的第几个字节。
那到底怎么判断脚本有没有把DOM撑大?
拿浏览器自己收到的那份原始文档当基线,不要拿第1秒的快照。方法是在开发者工具里查看主文档响应的原始内容,或者用同一个浏览器把响应体存下来再数。本轮19个站按这条基线算,比值中位只有1.07,超过2倍的只有3个。
删掉一些div能省多少?
取决于删的是不是重复出现的那一批。本轮div占全部元素中位23.7%,但这些div分布很散,逐个删收益有限。收益集中的是两类:同一个组件重复渲染多份的,以及内联图标——svg与path加起来占中位6.4%,有个站占到15.7%。
内联svg图标要不要换成雪碧图?
如果图标数量上百就值得。一个稍复杂的图标是一个svg加若干path,十几个节点起步。本轮misen.com有1931个svg和1107个path。换成引用外部符号表之后,每个图标在DOM里只剩一个引用节点,代价是多一次网络请求,而这个请求可以缓存一年。
Googlebot渲染的时候会等到第20秒吗?
官方没有公布过固定的等待时长,把结论建立在某个秒数上不稳妥。可以确定的是渲染是排队进行的,页面越重越靠后。更稳的做法是让关键内容不依赖渲染就在文档里,把渲染当锦上添花而不是必要条件。
深度和宽度,哪个更值得先治?
宽度。本轮只有3.6%的站深度超过32层,而22.3%的站存在带60个以上子元素的父节点,最宽的一个663个。宽的那一批通常是长列表,改成虚拟滚动或者分页收益立竿见影;深的那一批多半是组件框架的固有结构,硬压容易改坏。
影子DOM里的节点算不算进去?
审计工具通常算,常规的查询语句不算。本轮20个站里14个至少有一个影子根,最多的一个96个,所以文中报的元素数对这些站是偏低的。自己量的时候如果站上大量使用组件,得写一段递归穿透影子根再数,否则会低估。
这批数字能代表我的站吗?
只能当参照系。样本是国际品牌电商首页,商品卡片密集,元素数天然偏高;内容站、博客、企业官网的分布会低不少。可搬走的是方法:静态和运行时分开量、按body节点口径对齐审计、以及第1秒的读数不可信这三条,跟站点类型无关。
权威参考资料
本文标题:《DOM元素过多实测:112个首页68.8%越过1400这条线》
本文链接:https://zhangwenbao.com/dom-element-count-static-vs-runtime-audit.html
版权声明:本文原创,转载与引用请注明作者与原文链接。许可协议: CC BY 4.0