共计 3300 个字符,预计需要花费 9 分钟才能阅读完成。
Inside look at modern web browser 是介绍浏览器实现原理的系列文章,共 4 篇,本次精读介绍第三篇。
概述
本篇宏观的介绍 renderer process 做了哪些事件。
浏览器 tab 内 html、css、javascript 内容基本上都由 renderer process 的主线程解决,除了一些 js 代码会放在 web worker 或 service worker 内,所以浏览器主线程外围工作就是解析 web 三剑客并生成可交互的用户界面。
解析阶段
首先 renderer process 主线程会解析 HTML 文本为 DOM(Document Object Model),只译为中文就是文档对象模型,所以首先要把文本结构化能力持续解决。不仅是浏览器,代码的解析也得首先经验 Parse 阶段。
对于 HTML 的 link、img、script 标签须要加载近程资源的,浏览器会调用 network thread 优先并行处理,但遇到 script 标签就必须停下来优先执行,因为 js 代码可能会扭转任何 dom 对象,这可能导致浏览器要从新解析。所以如果你的代码没有批改 dom 的副作用,能够增加 async、defer 标签,或 JS 模块的形式使浏览器不用期待 js 的执行。
款式计算
只有 DOM 是不够的,style 标签申明的款式须要作用在 DOM 上,所以基于 DOM,浏览器要生成 CSSOM,这个 CSSOM 次要是基于 css 选择器(selector)确定作用节点的。
布局
有了 DOM、CSSOM 依然不足以绘制网页,因为咱们仅晓得构造和款式,但不晓得元素的地位,这就须要生成 LayoutTree 以形容布局的构造。
LayoutTree 和 DOM 构造很像了,但比方 display: none
的元素不会呈现在 LayoutTree 上,所以 LayoutTree 仅思考渲染构造,而 DOM 是一个综合形容构造,它不适宜间接用来渲染。
原文特地提到,LayoutTree 有个很大的技术难点,即排版,Chrome 专门有一整个团队在攻克这个技术难题。为什么排版这么难?能够从这几个例子中领会冰山一角:盒模型间碰撞、字体撑开内容导致换行,引发更大区域的从新排版、一个盒模型撑开挤压另一个盒模型,但另一个盒模型大小变动后内容排版也随之变动,导致盒模型再次变动,这个变动又导致了内部其它盒模型的布局变动。
布局最难的中央在于,须要对所有奇奇怪怪的布局定式做一个尽量正当的解决,而很多时候布局定式间规定是互相抵触的。而且这还不思考布局引擎的批改在数亿网页上引发未知 BUG 的危险。
绘图
有了 DOM、CSSOM、LayoutTree 就够了吗?还不行,还短少最初一环 PaintRecord,这个指绘图记录,它会记录元素的层级关系,以决定元素绘制的程序。因为 LayoutTree 仅决定了物理构造,但不决定元素的高低空间结构。
有了 DOM、CSSOM、LayoutTree、PaintRecord 之后,终于能够绘图了。然而当 HTML 变动时,重绘的代价是微小的,因为下面任何一步的计算结果都依赖后面一步,HTML 扭转时,须要对 DOM、CSSOM、LayoutTree、PaintRecord 进行从新计算。
大部分时候浏览器都能够在 16ms 内实现,使 FPS 放弃在 60 左右,但当页面构造过于简单,这些计算自身超过了 16ms,或其中遇到 js 代码的阻塞,都会导致用户感觉到卡顿。当然对于 js 卡顿问题能够通过 requestAnimationFrame
把逻辑运算扩散在各帧闲暇时进行,也能够独立到 web worker 里。
合成
绘图的步骤称为 rasterizing(光栅化)。在 Chrome 最早公布时,采纳了一种较为简单的光栅化计划,即仅渲染可是区域内的像素点,当滚动后,再补充渲染以后滚动地位的像素点。这样做会导致渲染永远滞后于滚动。
当初个别采纳较为成熟的合成技术(compositing),行将渲染内容分层绘制与渲染,这能够大大晋升性能,并可通过 CSS 属性 will-change
手动申明为一个新层(不要滥用)。
浏览器会依据 LayoutTree 剖析后失去 LayerTree(层树),并依据它逐层渲染。
合成层会将绘图内容切分为多个栅格并交由 GPU 渲染,因而性能会十分好。
精读
从渲染分层看性能优化
本篇提到了浏览器渲染的 5 个重要环节:解析、款式、布局、绘图、合成,是前端开发者日常工作中对浏览器体感最深的局部,也是优化最长产生在的局部。
其实从性能优化角度来看,解析环节能够被代替为 JS 环节,因为古代 JS 框架往往没有什么 HTML 模版内容要解析,简直全是 JS 操作 DOM,所以能够看作 5 个新环节:JS、款式、布局、绘图、合成。
值得注意的是,简直每层的计算都依赖下层的后果,但并不是每层都肯定会反复计算,咱们须要尤其留神以下几种状况:
- 批改元素几何属性(地位、宽低等)会触发所有层的从新计算,因为这是一个十分重量级的批改。
- 批改某个元素绘图属性(比方色彩和背景色),并不影响地位,则会跳过布局层。
- 批改比方 transform 属性会跳过布局与绘图层,这看上去很不堪设想。
对于第三点,因为 transform 的内容会晋升到合成层并交由 GPU 渲染,因而并不会与浏览器主线程的布局、绘图放在一起解决,所以视觉上这个元素确实产生了位移,但它和批改 left
、top
的位移在实现上却有实质的不同。
所以站在浏览器开发者的角度,能够轻松了解为什么这种优化不是奇技淫巧了,因为自身浏览器的实现就把布局、绘图与合成层的行为分来到了,不同的代码底层计划不同,性能必定会不同。你能够通过 csstriggers 查看不同 css 属性会引发哪些层的重计算。
当然作为开发者还是能够吐槽,为什么浏览器不能“主动把 left
top
与 transform
的实现细节屏蔽,并主动进行正当的分层”,然而如果浏览器厂商做不到这一点,开发者还是被动去理解实现原理吧。
隐式合成层、层爆炸、层主动合并
除了 transform
、will-change
属性外,还有很多种状况元素会晋升到合成层,比方 video
、canvas
、iframe
,或 fixed
元素,但这些都有明确的规定,所以属于显示合成。
而隐式合成是指元素没有被特地标记,但也被晋升到合成层的状况,这种状况常见产生在 z-index
元素产生重叠时,下方的元素显示申明晋升到合成层,则浏览器为了保障 z-index
笼罩关系,就要隐式把上方的元素晋升到合成层。
层爆炸是指隐式合成的起因,当 css 呈现一些简单行为时(比方轨迹动画),浏览器无奈实时捕获哪些元素位于以后元素上方,所以只好把所有元素都晋升到合成层,当合成层数量过多,主线程与 GPU 的通信可能会成为瓶颈,反而影响性能。
浏览器也会反对层主动合并,比方隐式晋升到合成层时,多个元素会主动合并在一个合成层里。但这种形式也并不总是靠谱,主动解决毕竟猜不到开发者的用意,所以最好的优化形式是开发者被动干涉。
咱们只有留神将所有显示晋升到合成层的元素放在 z-index
的上方,这样浏览器就有了判断根据,不必再担惊受怕会不会这个元素忽然挪动到某个元素的地位,导致压住了那个元素,于是又不得不把这个元素给隐式晋升到合成层以保障它们之间程序的正确性,因为这个元素原本就位于其它元素的最上方。
总结
读完这篇文章,心愿你能依据浏览器在渲染过程的实现原理,总结出更多代码级别的性能优化教训。
最初想要吐槽的是,浏览器标准因为是逐渐迭代的,因而看似都在形容地位的 css 属性其实背地实现原理是不同的,尽管这个规定体现在 W3C 标准上,但如果仅从属性名是很难看进去端倪的,因而想要做极致性能优化就必须理解浏览器实现原理。
探讨地址是:精读《深刻理解古代浏览器三》· Issue #379 · dt-fe/weekly
如果你想参加探讨,请 点击这里,每周都有新的主题,周末或周一公布。前端精读 – 帮你筛选靠谱的内容。
关注 前端精读微信公众号
<img width=200 src=”https://img.alicdn.com/tfs/TB165W0MCzqK1RjSZFLXXcn2XXa-258-258.jpg”>
版权申明:自在转载 - 非商用 - 非衍生 - 放弃署名(创意共享 3.0 许可证)