0

    前端面试大全:浏览器渲染原理

    2023.04.24 | admin | 179次围观

    当然,在解析 HTML 文件的时候,浏览器还会遇到 CSS 和 JS 文件,这时候浏览器也会去下载并解析这些文件,接下来就让我们先来学习浏览器如何解析 CSS 文件。

    将 CSS 文件转换为 CSSOM 树

    其实转换 CSS 到 CSSOM 树的过程和上一小节的过程是极其类似的

    在这一过程中,浏览器会确定下每一个节点的样式到底是什么,并且这一过程其实是很消耗资源的。因为样式你可以自行设置给某个节点,也可以通过继承获得。在这一过程中,浏览器得递归 CSSOM 树,然后确定具体的元素到底是什么样式。

    如果你有点不理解为什么会消耗资源的话,我这里举个例子

    
    
    

    对于第一种设置样式的方式来说,浏览器只需要找到页面中所有的 span 标签然后设置颜色,但是对于第二种设置样式的方式来说,浏览器首先需要找到所有的 span 标签,然后找到 span 标签上的 a 标签,最后再去找到 div 标签,然后给符合这种条件的 span 标签设置颜色,这样的递归过程就很复杂。所以我们应该尽可能的避免写过于具体的 CSS 选择器,然后对于 HTML 来说也尽量少的添加无意义标签,保证层级扁平。

    生成渲染树

    当我们生成 DOM 树和 CSSOM 树以后,就需要将这两棵树组合为渲染树。

    在这一过程中,不是简单的将两者合并就行了。渲染树只会包括需要显示的节点和这些节点的样式信息,如果某个节点是 display: none 的,那么就不会在渲染树中显示。

    当浏览器生成渲染树以后,就会根据渲染树来进行布局(也可以叫做回流),然后调用 GPU 绘制,合成图层,显示在屏幕上。对于这一部分的内容因为过于底层,还涉及到了硬件相关的知识,这里就不再继续展开内容了。

    那么通过以上内容,我们已经详细了解到了浏览器从接收文件到将内容渲染在屏幕上的这一过程。接下来,我们将会来学习上半部分遗留下来的一些知识点。

    为什么操作 DOM 慢

    想必大家都听过操作 DOM 性能很差,但是这其中的原因是什么呢?

    因为 DOM 是属于渲染引擎中的东西,而 JS 又是 JS 引擎中的东西。当我们通过 JS 操作 DOM 的时候,其实这个操作涉及到了两个线程之间的通信,那么势必会带来一些性能上的损耗。操作 DOM 次数一多,也就等同于一直在进行线程之间的通信,并且操作 DOM 可能还会带来重绘回流的情况,所以也就导致了性能上的问题。

    经典面试题:插入几万个 DOM,如何实现页面不卡顿?

    对于这道题目来说,首先我们肯定不能一次性把几万个 DOM 全部插入,这样肯定会造成卡顿,所以解决问题的重点应该是如何分批次部分渲染 DOM。大部分人应该可以想到通过 requestAnimationFrame的方式去循环的插入 DOM,其实还有种方式去解决这个问题:虚拟滚动(virtualized scroller)。

    这种技术的原理就是只渲染可视区域内的内容,非可见区域的那就完全不渲染了,当用户在滚动的时候就实时去替换渲染的内容。

    从上图中我们可以发现,即使列表很长,但是渲染的 DOM 元素永远只有那么几个,当我们滚动页面的时候就会实时去更新 DOM,这个技术就能顺利解决这道经典面试题。如果你想了解更多的内容可以了解下这个 react-virtualized。

    什么情况阻塞渲染

    首先渲染的前提是生成渲染树,所以 HTML 和 CSS 肯定会阻塞渲染。如果你想渲染的越快,你越应该降低一开始需要渲染的文件大小,并且扁平层级,优化选择器。

    然后当浏览器在解析到 script 标签时,会暂停构建 DOM浏览器工作原理是怎样的,完成后才会从暂停的地方重新开始。也就是说,如果你想首屏渲染的越快,就越不应该在首屏就加载 JS 文件,这也是都建议将 script 标签放在 body 标签底部的原因。

    当然在当下,并不是说 script 标签必须放在底部,因为你可以给 script 标签添加 defer 或者 async 属性。

    当 script 标签加上 defer 属性以后,表示该 JS 文件会并行下载,但是会放到 HTML 解析完成后顺序执行,所以对于这种情况你可以把 script 标签放在任意位置。

    对于没有任何依赖的 JS 文件可以加上 async 属性,表示 JS 文件下载和解析不会阻塞渲染。

    重绘(Repaint)和回流(Reflow)

    重绘和回流会在我们设置节点样式时频繁出现,同时也会很大程度上影响性能。

    回流必定会发生重绘,重绘不一定会引发回流。回流所需的成本比重绘高的多,改变父节点里的子节点很可能会导致父节点的一系列回流。

    以下几个动作可能会导致性能问题:

    并且很多人不知道的是,重绘和回流其实也和 Eventloop 有关。

    当 Eventloop 执行完 Microtasks 后,会判断 document 是否需要更新,因为浏览器是 60Hz 的刷新率,每 16.6ms 才会更新一次。然后判断是否有 resize 或者 scroll 事件浏览器工作原理是怎样的,有的话会去触发事件,所以 resize 和 scroll 事件也是至少 16ms 才会触发一次,并且自带节流功能。判断是否触发了 media query更新动画并且发送事件判断是否有全屏操作事件执行 requestAnimationFrame 回调执行 IntersectionObserver 回调,该方法用于判断元素是否可见,可以用于懒加载上,但是兼容性不好更新界面以上就是一帧中可能会做的事情。如果在一帧中有空闲时间,就会去执行 requestIdleCallback回调。

    以上内容来自于 HTML 文档。

    既然我们已经知道了重绘和回流会影响性能,那么接下来我们将会来学习如何减少重绘和回流的次数。

    减少重绘和回流

    for(let i = 0; i < 1000; i++) {
     // 获取 offsetTop 会导致回流,因为需要去获取正确的值
     console.log(document.querySelector('.test').style.offsetTop)
    }
    

    思考题

    思考题:在不考虑缓存和优化网络协议的前提下,考虑可以通过哪些方式来最快的渲染页面,也就是常说的关键渲染路径,这部分也是性能优化中的一块内容。

    首先你可能会疑问,那怎么测量到底有没有加快渲染速度呢

    当发生 DOMContentLoaded 事件后,就会生成渲染树,生成渲染树就可以进行渲染了,这一过程更大程度上和硬件有关系了。

    提示如何加速:

    从文件大小考虑从 script 标签使用上来考虑从 CSS、HTML 的代码书写上来考虑从需要下载的内容是否需要在首屏使用上来考虑

    以上提示大家都可以从文中找到,同时也欢迎大家踊跃在评论区写出你的答案。

    小结

    以上就是我们这一部分的内容了。在这一篇文章中,我们了解了浏览器如何将文件渲染为页面,同时也掌握了一些优化的小技巧。这部分的内容理解起来不大容易,希望大伙能自己再去查阅资料详细的了解。

    版权声明

    本文仅代表作者观点。
    本文系作者授权发表,未经许可,不得转载。

    标签: dom
    发表评论