浏览器是怎样烘托页面的
当浏览器的网络线程收到 HTML 文档后,会产生一个烘托使命,并将其传递给烘托主线程的消息行列。在事件循环机制下,烘托主线程取出消息行列中的烘托使命,敞开烘托流程。
整个烘托流程分为多个阶段:HTML 解析、款式核算、布局、分层、制作、分块、光栅化、画。每个阶段都有清晰的输入输出,上一个阶段的输出便是下一个阶段的输入,整个流程相似流水线相同。
下面针对每个阶段做具体的研究。
一、解析 HTML
解析的进程中遇到 CSS 便解析 CSS,遇到 JS 履行 JS。为了进步解析功率,浏览器会在开始解析器安,发动一个预解析线程,首要下载 HTML 中和外部 CSS、外部 JS 文件。
假如主线程解析到 link 标签,此刻外部的 CSS 文件还没下载解析好,主线程不会等候,会持续解析后续的 HTML。这是因为下载和解析 CSS 的作业是在预解析线程中进行的。这便是 CSS 不会堵塞 HTML 解析的中心原因。
假如主线程解析到 script 方位,会停止解析 HTML,转而等候 JS 文件下载好,直到脚本加载和解析完结后,才能持续解析 HTML。这是因为 JS 代码履行进程或许会修正当时的 DOM,所以 DOM 树的生成有必要暂停。这便是 JS 会堵塞 HTML 解析的根本原因。
第一步完结后,会得到 DOM 树和 CSSOM 树,浏览器的默许款式、内部款式、外部款式、内联款式均会包括在 CSSOM 树中
QA:
-
为什么需求解析成 DOM、CSSOM?HTML 本身是字符串,无法操作,抽象成一层 JS 文档对象模型,并露出一些 API 给 开发者,便利去操作。
-
为什么 HTML、CSS 都有自己对应的 DOM、CSSOM,但 JS 没有自己对应的模型?因为 CSS、HTML 是需求常常改动的,后续的逻辑会常常操作,所以有自己对应的树形模型,JS 履行一次就结束了。
HTML 解析进程中遇到 CSS 怎样处理
为了进步解析功率,浏览器会发动一个预解析器首要下载和解析 CSS
包括哪些款式
HTML 解析会生成 DOM、CSSOM。StyleSheetList 是一切款式的集合,那包括哪些款式呢?
- 浏览器默许款式(比方 div 独自占一行)
- 内联款式
- 内部款式
- 外部款式
浏览器默许款式怎样表现。翻阅 Chromium 的源代码能够检查到浏览器为 html 设置的默许款式,针对 div 标签设置了
display: block
所以才独占一行,而不是因为是 div 所以就该占一行。
HTML 解析进程中遇到 JS 代码怎样办
浏览器烘托主线程遇到默许的 script 标签(不带 async、defer 润饰)则会暂停解析 HTML,等候 JS 下载并履行结束后方可持续解析 HTML。
预解析线程能够分担下载 JS 的使命。
QA:为什么 JS 不能设计成像 css 那样一边解析,一边履行呢? JS 或许会操作当时 DOM,DOM 不是悉数的 HTML 解析完才创立的,解析多少 HTML 创立多少 DOM,所以需求暂停 HTML 解析(相似出产者顾客模型,不加锁则或许存在问题。HTML 解析便是在出产 DOM,JS 脚本或许一边在读取 DOM,或许还在增、删 DOM 在消费 DOM)
为什么浏览器遇到 script 会暂停解析?
浏览器在解析 HTML 的时分,假如遇到一个没有任何特点的 script 标签,就会暂停 HTML 解析,先发送网络恳求获取该 JS 脚本的内容,然后让 JS 引擎履行该代码。 当 JS 履行结束后恢复 HTML 的解析,整个进程如下
处理方式:defer
和 async
都是异步加载的外部 JS 脚本,不会堵塞页面的解析,因而这2个计划都能够规避因为很多 JS 下载影响 HTML 解析的问题
async
async 表明异步,当浏览器遇到带有 async 的 script 时,浏览器会选用异步的方式去解析,不会堵塞浏览器解析 HTML,一旦网络恳求完结后,假如此刻 HTML 还没解析完的话,当即暂停 HTML 的解析作业,让 JS 引擎履行 JS 脚本,悉数 JS 履行结束后再去解析后边的 HTML。
假如遇到 async 润饰的 js 脚本,在异步下载好之后 HTML 现已解析结束,也相同,直接履行 JS
defer
defer 也表明异步延迟,当浏览器遇到带有 defer 的 script 时,会异步获取该脚本,不会堵塞浏览器解析 HTML,一旦网络恳求回来后,假如 HTML 解析还未完结,则会持续解析 HTML,直到 HTML 悉数解析完结后才履行 JS 代码。
假如存在多个 defer 标签润饰的 script 标签,浏览器会依照 script 次序履行,不会损坏脚本之间的依靠联系
二、款式核算
主线程会遍历得到的 DOM 树,顺次为树中的每个节点核算出终究的款式,称为 Computed Style。 在这一进程中,很多预设值会变成肯定值,比方 red 会变为 rgb(255, 0, 0),相对单位也会变成肯定单位,比方 em 变成 px。这一步完结后会得到一颗带有款式的 DOM 树。
什么叫核算后的款式(Computed Style):指的是元素在烘托时所选用的终究款式。 款式核算首要包括:CSS 特点值的核算进程(层叠、承继…)、视觉格式化模型(盒模型、包括块、BFC…)
这个核算涉及到:
- 确认声明值
- 层叠抵触
- 运用承继
- 运用默许值
CSS 特点核算进程
<div>
<p>p</p>
</div>
没有修正p 标签的款式,却能够看到他有一个色彩、字体等款式。 该元素上会有 css 的一切特点。在 Chrome 检查元素形式下,勾选 Computed 下的 Show all,就能够看到十分多的特点如下图:
也便是说:咱们开发的任何一个 HTML 元素,实践上在浏览器显现出来的时分都有一套完好的 css 款式。假如没有显现声明款式,大概率会选用默许值。
确认声明值
p {
color: red;
}
<div>
<h1>H1标题</h1>
<p>阶段</p>
</div>
css 代码清晰了 p 标签运用红色,那么浏览器展现就会依照此特点进行展现。
这种开发者写的代码,叫做作者款式表。一般浏览器还会存在用户署理款式表,能够认为是浏览器内置了一份款式表。
能够看到 p 标签在检查元素形式下,除了作者款式表中设置的 color 特点,其他的都选用了用户署理款式表的特点。比方:display…
层叠抵触
前面看了确认声明值,但或许存在一种状况,声明的款式规矩产生抵触。 此刻会进入处理层叠抵触的流程,分为3个进程:
- 比较源的重要性
- 比方优先级
- 比较次序
比较源的重要性
当不同的 css 款式来历具有相同的声明时,此刻就会依据款式表来历的重要性来确认该用哪一条款式规矩。那,有几种款式表来历:
- 浏览器会有一个基础的款式表来给任何网页设置默许的款式。该款式被称为:用户署理款式
- 网页的作者能够界说文档的款式,这是最常见的款式,被称为:页面作者款式
- 浏览器的用户,能够运用自界说款式表定制运用体会。被称为用户款式
重要性的次序为:页面作者款式 > 用户款式 > 用户署理款式 假定现在有页面作者款式表和用户署理款式表中存在特点抵触,那会以页面作者款式表优先。
p {
color: red;
display: inline-block;
}
<div>
<h1>H1标题</h1>
<p>阶段</p>
</div>
能够看到 p 标签的 display 特点,在页面作者款式表和用户署理款式表中同时存在时,依据源的重要性判断,终究页面作者款式表优先级更高,display 选用页面作者款式表中的特点值。
比较优先级
假如同一个源中,款式声明相同的状况下,怎样决议计划?此刻进入了款式声明的优先级比较。
.container p {
color: #00ff00;
}
p {
color: red;
display: inline-block;
}
<div class="container">
<h1>H1标题</h1>
<p>阶段</p>
</div>
能够看到,在上面的 css 代码中,都在页面作者款式表同一个源中,源相同,解下去依据挑选器的权重来比较重要性。 依据挑选器权重规矩(ID 挑选器 > 类挑选器 > 类型挑选器)来判断,独自一个标签挑选器的 color 特点会被打败。
关于挑选器的比较战略能够检查 MDN CSS Specificity
比较次序
当款式表同源,权重相同的状况下,进入第三阶段:比较声明的次序
.container p {
color: #00ff00;
}
.container p {
color: #0000ff;
}
<div class="container">
<h1>H1标题</h1>
<p>阶段</p>
</div>
都处于页面作者款式表中,挑选器的权重也相同,但依据所在方位的不同,下面的款式声明会覆盖上面的值,终究选用 #0000ff。
款式声明抵触的状况处理了
运用承继
层叠抵触这一步完结后,处理了相同元素被声明的多条款式规矩射中后,究竟该选用哪一条款式规矩的问题。 那假定一个元素没有声明的特点,该运用什么特点值?会存在承继这个战略。
.container {
color: #00ff00;
}
<div class="container">
<h1>H1标题</h1>
</div>
只对类名为 container color 进行了设置,针对 p 标签没有任何的设置,但因为 color 能够承继,所以 p 就从最近的 div 承继了色彩。
看另一个现象
.container {
color: blue;
}
.innerContainer {
color: red;
}
<div class="container">
<div class="innerContainer">
<h1>H1标题</h1>
</div>
</div>
这儿承继了 container、innerContainer 2个的特点值,说了承继会挑选更近的一个,innerContainer 胜出。
运用默许值
假如前面的进程都走了,但特点值还没确认下来,就只能选用默许值了。
<div>
<h1>H1标题</h1>
</div>
任何一个元素要在浏览器上烘托出来,有必要具有一切的 css 特点值,但很多特点咱们没有去设置,用户署理款式表中也没有设置,也无法从承继中拿到,因而终究都是运用默许值的。
三、布局 – layout
布局完结后会得到布局树。 布局阶段会顺次遍历 DOM 树的每一个节点,核算每个节点的几何信息,比方节点的宽高、相对包括块的方位。 元素的尺度和方位,会受它的包括块所影响。对于一些特点,例如 width, height, padding, margin,肯定定位元素的偏移值(比方 position 被设置为 absolute 或 fixed),当咱们对其赋予百分比值时,这些值的核算值,便是经过元素的包括块核算得来。
大部分时分,DOM 树和 Layout 树并非一一对应的,为什么?
比方某个节点的 display 设置为 none,这样的节点就没有几何信息,因而不会生成到 layout 树。有些运用了伪元素挑选器,尽管 DOM 树中并不存在这些伪元素节点,但它们需求显现在浏览器上,所以会生成到 layout 树上。还有匿名行盒、匿名块盒等等都会导致 DOM 树和 layout 树无法一一对应。
div::before {
content: '';
}
<div></div>
四、分层 Layer
浏览器拿到布局后的 layout tree 会考虑一些作业。大多数页面不是制作后静止的,常常会有一些动画、用户点击后一些交互处理等,需求常常刷新页面。但假如整个页面直接从头刷新,这个功能开销是很大的。能不能进步功率?能,于是现在浏览器支撑了分层。
主线程会运用一套复杂的战略对整个布局树进行分层。 分层的优点在于,将来在某一个层改动后,仅会对该层进行后续处理,然后进步功率。
滚动条、堆叠上下文有关的(z-index、transform、opacity )款式都会或多或少影响分层成果,也能够经过 will-change 特点更大程度的影响分层的成果。
<div>
100个<p>Lorem</p>
</div>
能够看到默许状况下浏览器对该代码分了2层。为什么滚动条需求独自设置一个 layer?因为100个 p 标签一定会超出一屏,所以会存在滚动条,且用户或许会高频滚动去检查内容,为了高效烘托,所以将滚动条独自设置了一个层。
假定咱们现现已过技术手法得知某些内容常常会改动,但直接页面全体重刷会很耗费资源,那怎样只对某一块区域设置一个 layer 呢?经过 will-change: transform
告知浏览器,这块区域的 transform 特点常常会变。
will-change
是一个用于通知浏览器某个元素行将产生改动的CSS特点。它能够被应用到任何元素上,用于提早告知浏览器该元素将要有哪些特点进行改动,然后优化烘托功能。
经过在元素上设置will-change
特点,开发者能够清晰指示浏览器对该元素进行优化处理。这样一来,浏览器能够提早分配资源和预备作业,以便在实践改动产生之前进行相应的组成操作。这样做有助于防止不必要的重绘和重排,进步页面的响应速度和动画的流通度。
will-change
特点能够接受多个特点值,表明将要改动的特点。例如,will-change: transform
表明元素行将进行变形操作,will-change: opacity
表明元素的透明度行将产生改动。
需求注意的是,will-change
特点应在实践改动产生前的一段时间内被设置,以便浏览器有足够的时间进行预备和优化。别的,will-change
特点并不会主动触发硬件加速,但它能够为浏览器供给一种优化烘托的提示。
此外,will-change
特点的运用应慎重,防止滥用。只有在清晰知道元素行将产生某种改动,并且这种改动对功能有影响时,才应运用will-change
特点。过度运用will-change
特点或许会导致浏览器进行不必要的优化,反而下降功能。
总的来说,will-change
特点是一个用于优化烘托功能的CSS特点,经过提早告知浏览器元素行将产生的改动,使浏览器能够提早进行预备和优化,然后进步页面的响应速度和动画的流通度
Demo:
div {
will-change: transform;
width: 200px;
background-color: red;
margin: 0 auto;
}
经过上面的 css 设置,告知浏览器:这个 div 的 transform 常常会变,浏览器会为该元素创立一个独立的图层,将这个图层标记为“行将改换”。这样,在进行布局和制作时,浏览器就能够更高效地处理这个元素,而无需从头核算整个烘托树。
五、制作 – Paint
第四步的产出便是分层。第五步制作阶段,会分别对每个层独自产生制作指令集,用于描绘这一层的内容该怎样画出来(相似 canvas 代码,告知核算机从哪个点经过中心几个点,最终到哪个点制作一条线,中心内容用什么色彩填充)
烘托主线程的作业到此为止,剩下进程交给其他线程完结。
六、分块 – Tiling
完结制作之后,主线程将每个图层的制作信息提交给组成线程,剩下的作业将由组成线程完结。
组成线程首要对每个图层分块(Tiling),将其划分为更多的小区域。组成线程相似一个使命调度者,它会从线程池中拿出多个线程来完结分块的作业。
QA:浏览器烘托进程中分块的效果是什么?
-
进步烘托功能:当浏览器需求烘托一个复杂的网页时,假如一次性将一切内容加载并烘托出来,或许会耗费很多的核算资源和时间,经过网页分成多个较小的块(tiling)浏览器能够并行地加载和烘托这些块,然后充分利用核算资源,进步烘托功能。
-
优化内存占用:假如浏览器一次性加载和烘托整个网页,或许会耗费很多的内存。经过将网页分成多个图块,浏览器能够更好的管理内存,防止不必要的内存耗费
-
完结懒加载:经过分块,浏览器能够完结懒加载,即只有当某个图块进入视口可见区域时,才开始加载和烘托该图块。这样能够削减不必要的加载和烘托,进步页面的加载速度和功能
-
便利进行并行处理和异步操作:经过将网页分成多个图块,浏览器能够更容易地进行并行处理和异步操作。例如,在移动端设备上,能够利用 GPU 进行图块的并行烘托,进步烘托功率。
分块 tiling 技术是浏览器烘托进程中进步功能、优化内存运用和完结懒加载的重要手法之一。
七、光栅化
分块完结后会进入光栅化阶段。光栅化是将每个块变成位图。 组成线程将分块后的块信息交给 GPU 进程,以极高的速度完结光栅化,并优先处理靠近视口的块。
八、画 – Draw
组成线程核算出每个位图在屏幕上的方位,交给 GPU 进行终究的出现。
组成线程拿到每个层(Layer)、每个块(Tile)的位图(bitmap)后,生成一个个指引(quad)信息。指引信息标识出每个位图应该画到屏幕上的哪个方位,此进程会考虑到旋转、缩放、变形等。
因为变形产生在组成线程,与烘托主线程无关,这也是 transform 功率高的实质原因。 组成线程会把 quad 提交给 GPU 进程,由 GPU 进程产生系统调用,提交给 GPU 硬件,完结终究的屏幕显现
完结进程如下
reflow
比方某个业务逻辑,导致咱们用 js 脚本修正了某个节点的宽度、色彩,这其实修正的是 cssom,假定业务逻辑导致操作了 DOM,DOM 节点增删了,cssom、DOM 改动了,则会触发一系列的的流程,比方从头核算款式 style、布局(layout)、分层 layer、制作 paint、分块 tiling、光栅化 raster、画 draw、系统调用 GPU 去真正显现。
当烘托树 render tree 中的一部分(或者悉数)因为元素的尺度、布局、色彩、躲藏等改动而需求从头构建,这就状况被称为回流。图上的 style 这部分。
回流产生时,浏览器会让烘托树中受到影响的部分失效,并从头构建这部分烘托树。完结回流 reflow 后,浏览器会从头制作受影响的部分到屏幕中,该进程称为重绘。
简单来说,reflow 便是核算元素在屏幕上确切的方位和巨细并从头制作。回流的价值远大于重绘。回流必定重绘,重绘不一定回流。
repaint
当烘托树 render tree 中的一些元素需求更新款式,但这些款式只是改动元素外观、风格,而不影响布局(方位、巨细),则叫重绘。 简单来说,重绘便是将烘托树节点转换为屏幕上的实践像素,不涉及从头布局阶段的方位与巨细核算
QA:为什么 transform 功率高?
假定在 css 中针对某个元素写了 transform: rotate(100deg)
,transform 既不会影响布局也不会影响制作指令,它影响的是烘托流程的最终一个阶段,图上的 draw 阶段。因为 draw 阶段产生在组成线程中,不在烘托主线程,所以 transform 的任何改动几乎不会影响主线程。同样,不管主线程怎样繁忙,都不影响组成线程中 transform 的改动。