网页渲染性能优化 —— 渲染原理(1),微信web开发

标题

标题2

首先需要在 Chrome 控制台的 Network 面板设置网络节流,让网络速度变慢,以便更好进行调试。

下图说明 JavaScript 的确需要在 CSS 加载并解析完毕之后才会执行。

为什么需要阻塞 JavaScript 的运行呢?


因为 JavaScript 可以操作 DOM 和 CSSOM,如果 link 标记不阻塞 JavaScript 运行,这时 JavaScript 操作 CSSOM,就会发生冲突。更详细的说明可以在 使用 JavaScript 添加交互 这篇文章中查阅。

解析

CSS 解析的步骤与 HTML 的解析是非常类似的。

词法分析


CSS 会被拆分成如下一些标记:

CSS 的色值使用十六进制优于函数形式的表示?


函数形式是需要再次计算的,在进行词法分析时会将它变成一个函数标记,由此看来使用十六进制的确有所优化。

语法分析


每个 CSS 文件或嵌入样式都会对应一个 CSSStyleSheet 对象(authorStyleSheet),这个对象由一系列的 Rule(规则) 组成;每一条 Rule 都会包含 Selectors(选择器) 和若干 Declearation(声明),Declearation 又由 Property(属性)和 Value(值)组成。另外,浏览器默认样式表(defaultStyleSheet)和用户样式表(UserStyleSheet)也会有对应的 CSSStyleSheet 对象,因为它们都是单独的 CSS 文件。至于内联样式,在构建 DOM Tree 的时候会直接解析成 Declearation 集合。

内联样式和 authorStyleSheet 的区别


所有的 authorStyleSheet 都挂载在 document 节点上,我们可以在浏览器中通过 document.styleSheets 获取到这个集合。内联样式可以直接通过节点的 style 属性查看。

通过一个例子,来了解下内联样式和 authorStyleSheet 的区别:

Document
test

可以看到一共有三个 CSSStyleSheet 对象,每个 CSSStyleSheet 对象的 rules 里面会有一个 CSSStyleDeclaration,而内联样式获取到的直接就是 CSSStyleDeclaration。

需要属性合并吗?


在解析 Declearation 时遇到属性合并,会把单条声明转变成对应的多条声明,比如:

.box {

margin: 20px;

}

margin: 20px 就会被转变成四条声明;这说明 CSS 虽然提倡属性合并,但是最终还是会进行拆分的;所以属性合并的作用应该在于减少 CSS 的代码量。

计算

为什么需要计算?


因为一个节点可能会有多个 Selector 命中它,这就需要把所有匹配的 Rule 组合起来,再设置最后的样式。

准备工作


为了便于计算,在生成 CSSStyleSheet 对象后,会把 CSSStyleSheet 对象最右边 Selector 类型相同的 Rules 存放到对应的 Hash Map 中,比如说所有最右边 Selector 类型是 id 的 Rules 就会存放到 ID Rule Map 中;使用最右边 Selector 的原因是为了更快的匹配当前元素的所有 Rule,然后每条 Rule 再检查自己的下一个 Selector 是否匹配当前元素。

idRules

classRules

tagRules

选择器命中


一个节点想要获取到所有匹配的 Rule,需要依次判断 Hash Map 中的 Selector 类型(id、class、tagName 等)是否匹配当前节点,如果匹配就会筛选当前 Selector 类型的所有 Rule,找到符合的 Rule 就会放入结果集合中;需要注意的是通配符总会在最后进行筛选。

从右向左匹配规则


上文说过 Hash Map 存放的是最右边 Selector 类型的 Rule,所以在查找符合的 Rule 最开始,检验的是当前 Rule 最右边的 Selector;如果这一步通过,下面就要判断当前的 Selector 是不是最左边的 Selector;如果是,匹配成功,放入结果集合;否则,说明左边还有 Selector,递归检查左边的 Selector 是否匹配,如果不匹配,继续检查下一个 Rule。

为什么需要从右向左匹配呢?


先思考一下正向匹配是什么流程,我们用 div p .yellow 来举例,先查找所有 div 节点,再向下查找后代是否是 p 节点,如果是,再向下查找是否存在包含 class=“yellow” 的节点,如果存在则匹配;但是不存在呢?就浪费一次查询,如果一个页面有上千个 div 节点,而只有一个节点符合 Rule,就会造成大量无效查询,并且如果大多数无效查询都在最后发现,那损失的性能就实在太大了。

这时再思考从右向左匹配的好处,如果一个节点想要找到匹配的 Rule,会先查询最右边 Selector 是当前节点的 Rule,再向左依次检验 Selector;在这种匹配规则下,开始就能避免大多无效的查询,当然性能就更好,速度更快了。

设置样式


设置样式的顺序是先继承父节点,然后使用用户代理的样式,最后使用开发者(authorStyleSheet)的样式。

authorStyleSheet 优先级


放入结果集合的同时会计算这条 Rule 的优先级;来看看 blink 内核对优先级权重的定义:

switch (m_match) {

case Id:

return 0x010000;

case PseudoClass:

return 0x000100;

case Class:

case PseudoElement:

case AttributeExact:

case AttributeSet:

case AttributeList:

case AttributeHyphen:

case AttributeContain:

case AttributeBegin:

case AttributeEnd:

return 0x000100;

case Tag:

return 0x000001;

case Unknown:

return 0;

}

return 0;

因为解析 Rule 的顺序是从右向左进行的,所以计算优先级也会按照这个顺序取得对应 Selector 的权重后相加。来看几个例子:

/*

  • 65793 = 65536 + 1 + 256

*/

#container p .text {

font-size: 16px;

}

/*

  • 2 = 1 + 1

*/

div p {

font-size: 14px;

}

当前节点所有匹配的 Rule 都放入结果集合之后,先根据优先级从小到大排序,如果有优先级相同的 Rule,则比较它们的位置。

内联样式优先级


authorStyleSheet 的 Rule 处理完毕,才会设置内联样式;内联样式在构建 DOM Tree 的时候就已经处理完成并存放到节点的 style 属性上了。

内联样式会放到已经排序的结果集合最后,所以如果不设置 !important,内联样式的优先级是最大的。

!important 优先级


在设置 !important 的声明前,会先设置不包含 !important 的所有声明,之后再添加到结果集合的尾部;因为这个集合是按照优先级从小到大排序好的,所以 !important 的优先级就变成最大的了。

书写 CSS 的规则


结果集合最后会生成 ComputedStyle 对象,可以通过 window.getComputedStyle 方法来查看所有声明。

可以发现图中的声明是没有顺序的,说明书写规则的最大作用是为了良好的阅读体验,利于团队协作。

调整 Style


这一步会调整相关的声明;例如声明了 position: absolute;,当前节点的 display 就会设置成 block。

参考资料


  1. 从Chrome源码看浏览器如何计算CSS

  2. 探究 CSS 解析原理

  3. Webkit内核探究【2】——Webkit CSS实现

  4. Webkit CSS引擎分析

  5. css加载会造成阻塞吗?

  6. 原来 CSS 与 JS 是这样阻塞 DOM 解析和渲染的

  7. 外链 CSS 延迟 DOM 解析和 DOMContentLoaded

  8. CSS/JS 阻塞 DOM 解析和渲染

  9. 构建对象模型 —— CSS 对象模型 (CSSOM)

  10. 阻塞渲染的 CSS

Render Object Tree


在 DOM Tree 和 CSSOM Tree 构建完毕之后,才会开始生成 Render Object Tree(Document 节点是特例)。

创建 Render Object


在创建 Document 节点的时候,会同时创建一个 Render Object 作为树根。Render Object 是一个描述节点位置、大小等样式的可视化对象。

每个非 display: none | contents 的节点都会创建一个 Render Object,流程大致如下:生成 ComputedStyle(在 CSSOM Tree 计算这一节中有讲),之后比较新旧 ComputedStyle(开始时旧的 ComputedStyle 默认是空);不同则创建一个新的 Render Object,并与当前处理的节点关联,再建立父子兄弟关系,从而形成一棵完整的 Render Object Tree。

布局(重排)


Render Object 在添加到树之后,还需要重新计算位置和大小;ComputedStyle 里面已经包含了这些信息,为什么还需要重新计算呢?因为像 margin: 0 auto; 这样的声明是不能直接使用的,需要转化成实际的大小,才能通过绘图引擎绘制节点;这也是 DOM Tree 和 CSSOM Tree 需要组合成 Render Object Tree 的原因之一。

布局是从 Root Render Object 开始递归的,每一个 Render Object 都有对自身进行布局的方法。为什么需要递归(也就是先计算子节点再回头计算父节点)计算位置和大小呢?因为有些布局信息需要子节点先计算,之后才能通过子节点的布局信息计算出父节点的位置和大小;例如父节点的高度需要子节点撑起。如果子节点的宽度是父节点高度的 50%,要怎么办呢?这就需要在计算子节点之前,先计算自身的布局信息,再传递给子节点,子节点根据这些信息计算好之后就会告诉父节点是否需要重新计算。

数值类型


所有相对的测量值(rem、em、百分比…)都必须转换成屏幕上的绝对像素。如果是 em 或 rem,则需要根据父节点或根节点计算出像素。如果是百分比,则需要乘以父节点宽或高的最大值。如果是 auto,需要用 (父节点的宽或高 - 当前节点的宽或高) / 2 计算出两侧的值。

盒模型


众所周知,文档的每个元素都被表示为一个矩形的盒子(盒模型),通过它可以清晰的描述 Render Object 的布局结构;在 blink 的源码注释中,已经生动的描述了盒模型,与原先耳熟能详的不同,滚动条也包含在了盒模型中,但是滚动条的大小并不是所有的浏览器都能修改的。

// ***** THE BOX MODEL *****

// The CSS box model is based on a series of nested boxes:

// http://www.w3.org/TR/CSS21/box.html

// top

// |----------------------------------------------------|

// | |

// | margin-top |

// | |

// | |-----------------------------------------| |

// | | | |

// | | border-top | |

// | | | |

// | | |--------------------------|----| | |

// | | | | | | |

// | | | padding-top |####| | |

// | | | |####| | |

// | | | |----------------| |####| | |

// | | | | | | | | |

// left | ML | BL | PL | content box | PR | SW | BR | MR |

// | | | | | | | | |

// | | | |----------------| | | | |

// | | | | | | |

// | | | padding-bottom | | | |

// | | |--------------------------|----| | |

// | | | ####| | | |

// | | | scrollbar height ####| SC | | |

// | | | ####| | | |

// | | |-------------------------------| | |

// | | | |

// | | border-bottom | |

// | | | |

// | |-----------------------------------------| |

// | |

// | margin-bottom |

// | |

// |----------------------------------------------------|

//

// BL = border-left

// BR = border-right

// ML = margin-left

// MR = margin-right

// PL = padding-left

// PR = padding-right

// SC = scroll corner (contains UI for resizing (see the ‘resize’ property)

// SW = scrollbar width

box-sizing


box-sizing: content-box | border-box,content-box 遵循标准的 W3C 盒子模型,border-box 遵守 IE 盒子模型。

它们的区别在于 content-box 只包含 content area,而 border-box 则一直包含到 border。通过一个例子说明:

// width

// content-box: 40

// border-box: 40 + (2 * 2) + (1 * 2)

div {

width: 40px;

height: 40px;

padding: 2px;

border: 1px solid #ccc;

}

参考资料


  1. 从Chrome源码看浏览器如何layout布局

  2. Chromium网页Render Object Tree创建过程分析

  3. 浏览器的工作原理:新式网络浏览器幕后揭秘 —— 呈现树和 DOM 树的关系

  4. 谈谈我对盒模型的理解

  5. 渲染树构建、布局及绘制

Render Layer Tree


Render Layer 是在 Render Object 创建的同时生成的,具有相同坐标空间的 Render Object 属于同一个 Render Layer。这棵树主要用来实现层叠上下文,以保证用正确的顺序合成页面。

创建 Render Layer


满足层叠上下文条件的 Render Object 一定会为其创建新的 Render Layer,不过一些特殊的 Render Object 也会创建一个新的 Render Layer。

创建 Render Layer 的原因如下:

  • NormalLayer

  • position 属性为 relative、fixed、sticky、absolute

  • 透明的(opacity 小于 1)、滤镜(filter)、遮罩(mask)、混合模式(mix-blend-mode 不为 normal)

  • 剪切路径(clip-path)

  • 2D 或 3D 转换(transform 不为 none)

  • 隐藏背面(backface-visibility: hidden)

  • 倒影(box-reflect)

  • column-count(不为 auto)或者column-widthZ(不为 auto)

  • 对不透明度(opacity)、变换(transform)、滤镜(filter)应用动画

  • OverflowClipLayer

  • 剪切溢出内容(overflow: hidden)

另外以下 DOM 元素对应的 Render Object 也会创建单独的 Render Layer:

  • Document

  • HTML

  • Canvas

  • Video

如果是 NoLayer 类型,那它并不会创建 Render Layer,而是与其第一个拥有 Render Layer 的父节点共用一个。

参考资料


  1. 无线性能优化:Composite —— 从 LayoutObjects 到 PaintLayers

  2. Chromium网页Render Layer Tree创建过程分析

  3. WEBKIT 渲染不可不知的这四棵树

Graphics Layer Tree


软件渲染


软件渲染是浏览器最早采用的渲染方式。在这种方式中,渲染是从后向前(递归)绘制 Render Layer 的;在绘制一个 Render Layer 的过程中,它的 Render Objects 不断向一个共享的 Graphics Context 发送绘制请求来将自己绘制到一张共享的位图中。

硬件渲染


有些特殊的 Render Layer 会绘制到自己的后端存储(当前 Render Layer 会有自己的位图),而不是整个网页共享的位图中,这些 Layer 被称为 Composited Layer(Graphics Layer)。最后,当所有的 Composited Layer 都绘制完成之后,会将它们合成到一张最终的位图中,这一过程被称为 Compositing;这意味着如果网页某个 Render Layer 成为 Composited Layer,那整个网页只能通过合成来渲染。除此之外,Compositing 还包括 transform、scale、opacity 等操作,所以这就是硬件加速性能好的原因,上面的动画操作不需要重绘,只需要重新合成就好。

上文提到软件渲染只会有一个 Graphics Context,并且所有的 Render Layer 都会使用同一个 Graphics Context 绘制。而硬件渲染需要多张位图合成才能得到一张完整的图像,这就需要引入 Graphics Layer Tree。

Graphics Layer Tree 是根据 Render Layer Tree 创建的,但并不是每一个 Render Layer 都会有对应的 Composited Layer;这是因为创建大量的 Composited Layer 会消耗非常多的系统内存,所以 Render Layer 想要成为 Composited Layer,必须要给出创建的理由,这些理由实际上就是在描述 Render Layer 具备的特征。如果一个 Render Layer 不是 Compositing Layer,那就和它的祖先共用一个。

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024c (备注前端)
img

后话

对于面试,说几句个人观点。

面试,说到底是一种考试。正如我们一直批判应试教育脱离教育的本质,为了面试学习技术也脱离了技术的初心。但考试对于人才选拔的有效性是毋庸置疑的,几千年来一直如此。除非你有实力向公司证明你足够优秀,否则,还是得乖乖准备面试。这也并不妨碍你在通过面试之后按自己的方式学习。
其实在面试准备阶段,个人的收获是很大的,我也认为这是一种不错的学习方式。首先,面试问题大部分基础而且深入,这些是平时工作的基础。就好像我们之前一直不明白学习语文的意义,但它的意义就在每天的谈话间。

易碰到天花板技术停滞不前!**

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-YBUgxY8x-1712043143371)]
[外链图片转存中…(img-pr0rNG32-1712043143371)]
[外链图片转存中…(img-zGbQJU7v-1712043143372)]
[外链图片转存中…(img-u02cn7FR-1712043143372)]
[外链图片转存中…(img-gvXBDoXM-1712043143372)]
[外链图片转存中…(img-ZeXPD4xU-1712043143372)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024c (备注前端)
[外链图片转存中…(img-gwthuPNd-1712043143373)]

后话

对于面试,说几句个人观点。

面试,说到底是一种考试。正如我们一直批判应试教育脱离教育的本质,为了面试学习技术也脱离了技术的初心。但考试对于人才选拔的有效性是毋庸置疑的,几千年来一直如此。除非你有实力向公司证明你足够优秀,否则,还是得乖乖准备面试。这也并不妨碍你在通过面试之后按自己的方式学习。
其实在面试准备阶段,个人的收获是很大的,我也认为这是一种不错的学习方式。首先,面试问题大部分基础而且深入,这些是平时工作的基础。就好像我们之前一直不明白学习语文的意义,但它的意义就在每天的谈话间。

所谓面试造火箭,工作拧螺丝。面试往往有更高的要求,也迫使我们更专心更深入地去学习一些知识,也何尝不是一种好事。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值