首先来看一张图片
一、浏览器的渲染过程
从解析 HTML 和 CSS 到最终页面呈现。以下是浏览器的渲染过程的主要步骤:
- 浏览器开始解析 HTML,转换收到的数据为 DOM 树。
-
- 浏览器每次发现外部资源就初始化请求,无论是样式、脚本或者嵌入的图片引用。
-
- 有时请求会阻塞,这意味着解析剩下的 HTML 会被终止直到重要的资源被处理。
-
- 浏览器接着解析 HTML,发请求和构造 DOM 直到文件结尾
(HTML 解析->DOM)
。
- 浏览器接着解析 HTML,发请求和构造 DOM 直到文件结尾
-
- 这时开始构造 CSS 对象模型, 树表示了页面的样式信息,包括每个元素的样式规则和计算出的样式值
(CSS 解析->CSSOM 树)
。
- 这时开始构造 CSS 对象模型, 树表示了页面的样式信息,包括每个元素的样式规则和计算出的样式值
-
- 等到 DOM 和 CSSOM 完成之后,浏览器构造渲染树
(Render Tree)
。
- 等到 DOM 和 CSSOM 完成之后,浏览器构造渲染树
-
计算(布局Layout)
所有可见内容的样式。
-
- 一旦渲染树完成布局开始,
定义
所有渲染树元素的位置和大小(Paint)
。
- 一旦渲染树完成布局开始,
-
- 完成之后,页面被渲染完成,或者说是绘制到屏幕上。
这些步骤是逐渐进行的,且是异步执行的。在渲染过程中,浏览器会通过优化手段提高性能,例如将一些操作合并为一次执行、使用异步加载脚本等。渲染过程中的每个步骤都对页面性能有影响,因此优化渲染过程是提高网页性能的关键。
补充
预加载扫描器
浏览器构建 DOM 树时,这个过程占用了主线程。当这种情况发生时,预加载扫描仪将解析可用的内容
并请求高优先级资源
,如 CSS、JavaScript 和 web 字体。多亏了预加载扫描器,我们不必等到解析器找到对外部资源的引用来请求它。它将在后台检索资源,以便在主 HTML 解析器到达请求的资源时,它们可能已经在运行,或者已经被下载。 预加载扫描仪提供的优化减少了阻塞。
二 、html解析过程中遇到js代码会怎么样
当 HTML 解析过程中遇到包含 JavaScript 代码的 <script>
标签时,默认是同步执行,就是说浏览器只能干完一件事再干另一件,不能同时做html解析和处理js这两件事,这个过程可能会对渲染和页面加载产生影响,具体表现如下:
多种表现形式:
同步执行
-
HTML 解析暂停 并且 阻塞渲染:
- 遇到
<script>
标签时,会暂停 HTML 解析过程,以执行 JavaScript 代码。 - 默认情况下,浏览器会阻塞渲染,直到 JavaScript 代码执行完成。这会导致页面渲染被延迟,用户可能会感觉到页面加载速度较慢。
- 遇到
-
JavaScript 代码执行:
- 遇到
<script>
标签时,会先加载(把资源下载下来),在解析成可执行机器码,然后执行;具体解释看这(todo); - 执行的过程包括变量声明、函数定义、事件绑定等。
- 遇到
异步执行(async)
- 异步执行:
- 如果
<script>
标签设置了async
属性,表示 JavaScript 代码可以异步加载、解析、执行,不会阻塞 HTML 解析和渲染过程。一旦 JavaScript 文件下载完成,会立即执行,无论DOM是否构建完成; - 多个带有
async
属性的脚本,在 HTML 中的并行执行, 顺序不确定;
- 影响 DOM 和 CSSOM 构建:
- JavaScript 代码可能会修改 DOM 结构和样式,因此它的执行可能会影响到 DOM 树和 CSSOM 树的构建过程。
延迟执行(defer)
- 延迟执行(defer):
- 如果
<script>
标签设置了defer
属性,表示 JavaScript 代码将延迟到文档解析完成后执行,但会在DOMContentLoaded
事件之前执行。DOMContentLoaded
会在DOM 树构建完成之后触发; - 多个带有
defer
属性的脚本会按照它们在 HTML 中的顺序依次执行。
async 和 defer 同时存在
- async优先级高:
-
async优先级高,按照异步执行顺序;
总体来说,默认情况下,JS代码
阻塞 HTML 解析
阻塞渲染
影响页面加载性能
;使用async
或defer
属性的脚本不会阻塞 HTML 解析和渲染过程,有助于提高页面的加载性能。选择使用哪个属性取决于脚本的执行时机需求: -
使用
async
当不关心脚本的执行顺序,且脚本独立于其他脚本。 -
使用
defer
当希望保持脚本按照它们在 HTML 中的顺序执行,但又不想阻塞 HTML 解析。
三、 script 标签使用 async 或者 defer,js脚本就会下载和执行,如果代码中有操作dom的代码,执行会报错, 如何避免
- 如果 异步执行
async
脚本尝试访问尚未解析的 DOM 元素,可能会导致 DOM 元素不存在的情况,从而引发错误。 - 如果 延迟执行
defer
脚本尝试访问尚未解析的 DOM 元素,可能会导致问题,但通常情况下,由于defer
会等待 HTML 解析完成,大部分 DOM 操作是安全的。
会导致的其他问题:
- 如果脚本尝试在异步加载期间绑定事件到尚未存在的元素上,可能会导致事件无法正确绑定;
为了避免这些问题,可以采取以下策略:
1. 推迟 DOM 操作:
- 在异步加载的脚本中,确保 DOM 操作发生在 DOM 元素完全构建后。可以使用
DOMContentLoaded
事件来确保在 DOM 就绪后再执行相应的操作。
js
document.addEventListener('DOMContentLoaded', function() {
// 在此处进行 DOM 操作
});
2.动态加载脚本:
- 可以通过 JavaScript 动态创建
<script>
标签,并设置async
或defer
属性,以更好地控制脚本的加载和执行时机。
js
var script = document.createElement('script');
script.src = 'example.js';
script.async = true; // 或者 script.defer = true;
document.head.appendChild(script);
3.脚本位置:
- 推荐将脚本放在页面底部(在
</body>
标签之前), 以确保在 HTML 解析完成前加载。
四、html解析过程中遇到css代码会怎么样
- 包括:
处理内联样式(inline styles)
、外部样式表(link 外部样式)
、以及通过@import 导入的样式表
的流程如下
-
内联样式(Inline Styles):
- 内联样式是直接嵌入在 HTML 元素中的样式,通过
style
属性定义。 - 浏览器在
解析 HTML 的过程中
,会识别每个包含内联样式的元素,并将这些样式应用到相应的元素上。
html<div style="color: red;">This is a red text.</div>
- 内联样式是直接嵌入在 HTML 元素中的样式,通过
-
外部样式表(External Stylesheet):
- 外部样式表是通过
<link>
标签引入的独立的 CSS 文件。 - 遇到
<link>
标签时会异步下载
外部样式表,但不会阻塞 HTML 解析。一旦外部样式表
下载完成,浏览器会应用样式表中的规则。
html<link rel="stylesheet" type="text/css" href="styles.css">
- 外部样式表是通过
-
@import 导入样式表:
过程
@import
是 CSS 提供的一种方式,可以在样式表中导入其他样式表。- 浏览器在
解析样式表
时,会处理@import
语句。 - 遇到
<style>
标签中的@import
声明时,它会识别并记录下这些导入语句。 异步加载
外部样式表,开始加载,等待
样式表加载完成, 但不会阻塞 HTML 解析和渲染。并行加载
样式表,浏览器会尽可能并行加载多个外部样式表。- 样式表加载完成后
应用样式
,一旦样式表加载完成,浏览器会应用其中的样式规则,影响文档的渲染
css/* styles.css */ @import url('imported-styles.css'); body { background-color: #f0f0f0; }
css/* imported-styles.css */ h1 { color: blue; }
需要注意的是,虽然
@import
可以用于动态加载外部样式表,但它也可能导致一些性能问题,因为它引入了额外的网络请求
。推荐的做法是在页面的<head>
部分使用<link>
标签来直接引入样式表,以利用浏览器的并行下载机制,提高页面加载性能。可能导致的问题
1.影响 CSSOM 阶段**
- 在 CSSOM 的构建阶段,浏览器需要等待通过
@import
导入的样式表加载完成后,才能获取到其中的样式规则。这可能会在构建 CSSOM 的过程中引入一些延迟。
2.样式规则的应用:
- 在 CSSOM 构建完成后,浏览器会将样式规则应用于 DOM 树,影响文档的渲染。样式表加载的异步性可能会导致一部分文档在构建 CSSOM 之前已经渲染,但在加载样式表后需要重新渲染。
五、了解CSSDOM
在 JavaScript 中,要访问和操作 CSSOM(CSS Object Model)对象,你可以使用一些 DOM 接口和方法。 例如:
- 通过
document.styleSheets
: 返回一个样式表
列表,你可以通过索引访问其中的样式表。每个样式表都有一个cssRules
属性,它包含样式规则
列表。
css
// 获取第一个样式表
var styleSheet = document.styleSheets[0];
// 获取样式规则列表
var rules = styleSheet.cssRules || styleSheet.rules;
// 遍历样式规则
for (var i = 0; i < rules.length; i++) {
console.log(rules[i].cssText);
}
- 使用
document.querySelector
获取元素的样式:
css
// 获取第一个 p 元素的内联样式
var paragraphStyle = document.querySelector('p').style;
// 修改内联样式
paragraphStyle.color = 'blue';
- 通过
getComputedStyle
获取计算后的样式: 可以获取计算后的样式,即应用在元素上的最终样式
css
// 获取第一个 p 元素的计算后样式
var computedStyle = getComputedStyle(document.querySelector('p'));
// 获取计算后的颜色值 var color = computedStyle.color;
六、 浏览器渲染优化,提升加载速度
- 针对JS:
- 也就是前面的JS阻塞 html解析和渲染的解决方案:
- 脚本位置: script放在最后,之前;
- 异步加载: async;
- 延迟执行: defer;
- 动态加载脚本;
- 针对CSS:
- 使用外部样式
link
的方式;
- 针对DOM构建:
- HTML文件的代码层级尽量不要太深
- 使用语义化的标签,来避免不标准语义化的特殊处理
- 减少CSS代码的层级
- 减少回流与重绘
总的来说就是,·优化关键渲染路径
CRP(构建dom -》 构建cssDOM -》 构建渲染树 -》 布局 -》 绘制)。提升页面加载速度需要通过被加载资源的优先级
、控制它们加载的顺序
和减小这些资源的体积
。 性能提升包含
- 通过
异步、延迟加载
或者消除
非关键资源来减少关键资源的请求数量, - 优化必须的请求
数量
和每个请求的文件体积
, - 通过
区分关键资源的优先级
来优化被加载关键资源的顺序
,来缩短关键路径长度。