likes
comments
collection
share

“当网页变身忍者:潜行于DOM,闪避重绘与重排的追捕”

作者站长头像
站长
· 阅读数 21

一、引擎的双重角色:渲染与JS执行

浏览器引擎主要由两大部分构成——渲染引擎JS引擎。它们分别负责不同的任务,但又紧密相连。渲染引擎处理HTML、CSS,将其转化为可视化的网页;而JS引擎则负责执行JavaScript代码,动态改变页面内容和行为。两者之间的交互效率直接影响网页性能。

二、过路费与性能瓶颈

“当网页变身忍者:潜行于DOM,闪避重绘与重排的追捕”

想象一下,当JS引擎频繁地修改DOM,每次修改都可能触发渲染引擎的重绘或重排,就像高速路上频繁的收费站,每次停车缴费都消耗时间。例如,连续多次修改DOM的字符串拼接操作,每改动一次,就可能引起一次重排或重绘。为了减少这种“过路费”,可以采用批量更新策略,即把所有DOM修改汇总,在一次操作中完成,比如使用innerHTML一次性更新大量内容,这样可以显著提升性能。

三、缓存与DOM访问

缓存DOM节点是另一种提高性能的方式。频繁地通过ID或类名查找DOM节点会增加不必要的开销。因此,建议在变量中存储经常访问的节点,减少重复查询。

四、重绘与重排:性能的双刃剑

“当网页变身忍者:潜行于DOM,闪避重绘与重排的追捕”

重绘是指当元素的样式变化但几何尺寸未变时发生的视觉更新,而重排则涉及元素尺寸或位置的变化,不仅影响自身,还可能影响周围元素,因此重排的成本远高于重绘。设计时应尽量减少导致重排的操作,如避免频繁改变元素尺寸、位置或可见性。

下面让我们来举例说明一下:

  1. 动态修改容器的尺寸和颜色

    Javascript
    1let container = document.getElementById('container');
    2container.style.width = '100px';
    3container.style.height = '200px';
    4container.style.backgroundColor ='purple';
    5container.classList.add('show');
    

    这段代码修改了container元素的宽度、高度和背景色,并添加了一个名为show的类。这里涉及到重排和重绘:

    • 重排(回流) :当container的尺寸发生变化时,浏览器需要重新计算该元素及其子元素的几何信息,这被称为重排或回流。
    • 重绘:更改backgroundColor和添加CSS类show,即使没有尺寸变化,也需要重新绘制元素以反映新的样式。
  2. 创建文档碎片并填充数据

    Javascript
    let fragment = document.createDocumentFragment();
    for(var count = 0; count < 10000; count++){
        let oSpan = document.createElement('span');
        oSpan.innerHTML = '测试';
        fragment.appendChild(oSpan);
    }
    container.appendChild(fragment);
    

    这里使用了文档碎片(DocumentFragment),它是一个轻量级的DOM节点,用于暂时存储多个DOM元素,直到它们被一次性添加到实际的DOM树中。这种方法可以显著减少页面重排和重绘的次数,因为所有的元素修改都在内存中进行,直到最后一步才将整个文档碎片添加到DOM树中。

五、页面绘制的链式反应

页面从HTML、CSS和JS资源加载开始,经由浏览器内核解析、计算、布局和绘制,最终呈现为图像。这个过程中的每个环节都有可能成为性能瓶颈。关键在于减少阻塞,优化解析、样式计算、布局和绘制的效率,确保快速而流畅的页面渲染。

毫无疑问咱们是要看例子的:

  1. 内联脚本与DOM元素访问

    Html
    <script>
        var container = document.getElementById('container');
        console.log('container',container);
    </script>
    

    这段脚本位于<head>标签内,在页面的DOM树还未完全构建完成之前就执行了。因此,当尝试通过document.getElementById('container')获取container元素时,该元素尚未被添加到DOM树中,所以container变量的值为null。这是因为脚本默认是同步执行的,而浏览器按照HTML文档的顺序解析和执行脚本。

  2. 使用defer属性的脚本

    Html
    <script src="demo.js" defer>
        var container = document.getElementById('container');
        console.log('container',container);
        console.log('container background color',getComputedStyle(container).backgroundColor);
    </script>
    

    使用defer属性的脚本会在整个页面解析完成后,但在DOMContentLoaded事件触发前执行。这意味着当demo.js中的脚本开始执行时,整个DOM树已经构建完成,因此可以通过getElementById正确地获取到container元素。此外,由于脚本在DOM解析完毕后执行,它还可以读取到页面上所有样式表对container元素的影响,包括在<head><body>内的样式规则。

  3. 动态修改样式

    Html
    <style>
        #container{
            background-color: blue;
        }
    </style>
    

    这段内联样式位于<body>标签内,会在<body>解析时应用到container元素上,将其背景色改为蓝色。由于defer脚本是在DOM解析完成后执行,它能够读取到这部分样式更改后的效果,因此console.log('container background color',getComputedStyle(container).backgroundColor)将输出蓝色(rgb(0, 0, 255))的十六进制或RGB表示形式。

六、JS引擎的霸道与重绘重排

JS引擎执行脚本时,可能触发重绘或重排,尤其是当脚本直接修改DOM结构或样式时。因此,编写高效的JS代码,减少对DOM的直接操作,可以降低对渲染性能的影响。

七、CSS选择器的优化策略

  • 避免使用通配符*选择器会导致整个页面的重排,应尽量避免。
  • 关注可继承属性:利用属性继承减少选择器使用,如colorfont-family
  • 慎用标签选择器:直接使用标签作为选择器可能会匹配过多元素,增加不必要的计算负担。
  • 合理使用组合选择器:避免过度复杂的组合,如.myList#title可能不如#title简洁高效。

八、结论

优化网页性能是一门艺术,需要开发者深入了解渲染引擎与JS引擎的工作机制,通过合理的设计和编码实践,减少不必要的重排与重绘,利用缓存技巧,以及精心优化CSS选择器,从而创建出既美观又响应迅速的网页体验。每一次小小的改进,都能在用户访问量巨大的网站上产生巨大的性能提升,让用户体验更加流畅,同时减轻服务器负担。

转载自:https://juejin.cn/post/7387306775488954395
评论
请登录