从用户输入浏览器输入url到页面最后呈现 有哪些过程?考的是基本网络原理,和浏览器加载 css,js 过程。答案大致如下:
如果文档中有资源 重复6 7 8 动作 直至资源全部加载完毕
以上答案基本简述了一个网页基本的响应过程背后的原理。
但这只是浏览器获取数据的部分,至于浏览器拿到数据之后,怎么渲染页面的呢。

这是一个渐进的过程。为达到更好的用户体验,呈现引擎会力求尽快将内容显示在屏幕上。它不必等到整个 HTML 文档解析完毕之后,就会开始构建呈现树和设置布局。在不断接收和处理来自网络的其余内容的同时,呈现引擎会将部分内容解析并显示出来。
HTML Parser的任务是将HTML标记解析成DOM Tree。(文档对象模型)
这个解析可以参考React解析DOM的过程,但是这里面有很多别的规则和操作,比如容错机制,识别</br>和<br>等等。
例如:
<html>
<head>
<title>Web page parsing</title>
</head>
<body>
<div>
<h1>Web page parsing</h1>
<p>This is an example Web page.</p>
</div>
</body>
</html>

将文本的 HTML 文档,提炼出关键信息---嵌套层级的树形结构。便于计算拓展,这就是 HTML Parser 的作用。
CSS Parser 将 CSS 解析成 Style Rules,Style Rules 也叫 CSSOM(CSS Object Model,样式对象模型)。
StyleRules 也是一个树形结构,根据CSS文件整理出来的类似DOM Tree的树形结构:

于HTML Parser相似,CSS Parser作用就是将很多个CSS文件中的样式合并解析出具有树形结构Style Rules。(Style Rules则包含选择器和声明对象,以及其他与 CSS 语法对应的对象)
浏览器是解析 DOM 生成 DOM Tree,结合 CSS 生成的 CSS Tree,最终组成render tree,再渲染页面。由此可见,在此过程中 CSS 完全无法影响 DOM Tree,因而无需阻塞DOM解析。
即当遇到 <link>标签,尽管外部CSS下载需要3s,但这个过程中,浏览器不会傻等着CSS下载完,而是会解析DOM的。
然而,DOM Tree 和 CSS Tree 会组合成 render tree,那 CSS 会不会页面阻塞渲染呢?
原因:如果CSS 不会阻塞页面渲染,那么 CSS 文件下载之前,浏览器就会渲染出一种样式的页面, CSS 文件下载完成之后样式改变了又渲染出另一种样式的页面。导致页面首先会呈现出一个原始的模样,待CSS下载完之后又突然变了一个模样。用户体验可谓极差,而且渲染是有成本的。
因此,基于性能与用户体验的考虑,浏览器会尽量减少渲染的次数,CSS 顺理成章地阻塞页面渲染,直至 CSSOM 构建完毕。
由于 CSS 不会阻塞 HTML 文档的解析,因此在下载 CSS 文件的时候,意味着<link>和<script>等其他的外部文件都会提前并行下载(加载)。即使 JS 会阻塞 html 文档的解析,但浏览器会"偷看"DOM,预先下载相关资源。总之,html 文档中的所有外部资源总是提前加载的(并行下载)(浏览器不会傻等到解析到那里时才下载)。
<header>
<link rel="stylesheet" href="/css/sleep3000-common.css">
<script src="/js/logDiv.js"></script>
</header>
我们知道 CSSOM 的构建不会阻塞 DOM 的解析,并且外部资源都会提前加载(下载)。需要注意的是,Js 的执行会等到前面 CSSOM 的构建完毕才开始。
原因:如果脚本的内容是获取元素的样式,宽高等 CSS 控制的属性,浏览器是需要计算的,也就是依赖于CSS。所以浏览器需要前面所有的样式下载完后,再执行JS。
【不使用@import】
比如一个 CSS 文件index.css包含了以下内容:@import url("reset.css")。那么浏览器就必须先把index.css下载、解析和执行后,才下载、解析和执行第二个文件reset.css。
【为什么要避免层级或过度限制的CSS】
浏览器对 CSS 选择器是从右到左解析。.nav h3 a{font-size: 14px;}首先找到所有的a,沿着a的父元素查找h3,然后再沿着h3,查找.nav。中途找到了符合匹配规则的节点就加入结果集。如果找到根元素html都没有匹配,则不再遍历这条路径,从下一个a开始重复这个查找匹配(只要页面上有多个最右节点为a)。
简洁的选择器不仅可以减少css文件大小,提高页面的加载性能,浏览器解析时也会更加高效,也会提高开发人员的开发效率,降低了维护成本。过度的嵌套会导致代码变得臃肿、沉余、复杂,导致 CSS 文件体积变大,造成性能浪费,影响渲染的速度,而且过于依赖 HTML 文档结构。这样的 CSS 样式,维护起来,极度麻烦,如果以后要修改样式,可能要使用!important覆盖。
【CSS中的可继承属性与不可继承属性】:
渲染树(Render-Tree)的关键渲染路径中,要求同时具有 DOM 和 CSSOM,之后才会构建渲染树。即,HTML 和 CSS 都是阻塞渲染的资源。HTML 显然是必需的,因为包括我们希望显示的文本在内的内容,都在 DOM 中存放,那么可以从 CSS 上想办法。
最容易想到的当然是精简 CSS 并尽快提供它。除此之外,还可以用媒体类型(media type)和媒体查询(media query)来解除对渲染的阻塞。
<link href="index.css" rel="stylesheet">
<link href="print.css" rel="stylesheet" media="print">
<link href="other.css" rel="stylesheet" media="(min-width: 30em) and (orientation: landscape)">
第一个资源会加载并阻塞。
第二个资源设置了媒体类型,会加载但不会阻塞,print 声明只在打印网页时使用。
第三个资源提供了媒体查询,会在符合条件时阻塞渲染。
网络的模型是同步的,浏览器解析文档,当遇到<script>标签的时候,会立即解析(执行)脚本,停止解析文档(停止DOM Tree的构建,因为 JS 可能会改动 DOM 和 CSS ,万一脚本内全删了后面的DOM,浏览器就白干活了。所以继续解析会造成浪费)。
如果脚本是外部的,会等待脚本下载完毕并执行完毕,再继续解析 HTML 文档。现在可以在 script 标签上增加属性 defer或者async,来使脚本的处理相对 DOM Tree 的构建是异步的。注意,如果脚本是内联在 html 文档内,无法更改脚本的执行顺序,只能是立即解析脚本,并停止 DOM Tree 的构建,直到脚本执行完毕。像这样的脚本:
<script>
console.log("3");
</script>
<script src="script.js"></script>
<script async src="script.js"></script>
<script defer src="script.js"></script>

蓝色线代表网络读取,红色线代表执行时间,这俩都是针对脚本的;绿色线代表 HTML 解析。
此图告诉我们以下几个要点:
原因:浏览器不知道脚本的内容,因而碰到脚本时,只好先渲染页面,确保脚本能获取到最新的DOM元素信息。
例子:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
div {
width: 100px;
height: 100px;
background: lightgreen;
}
</style>
</head>
<body>
<div></div>
<script src="/js/sleep3000-logDiv.js"></script>
<style>
div {
background: lightgrey;
}
</style>
<script src="/js/sleep5000-logDiv.js"></script>
<link rel="stylesheet" href="/css/common.css">
</body>
// js 文件
function sleep(time) {
return new Promise(function(res) {
setTimeout(() => {
res()
}, time);
})
}
sleep(time);
// common.css
div {
background: pink;
}
答案:先浅绿色,再浅灰色,最后粉红。(浏览器渲染速度较大,只能微微看到两面的颜色一闪而过)
即浏览器遇到 <script> 且没有 defer 或 async 属性的 标签时,会触发页面渲染,因而如果前面CSS资源尚未加载完毕时,浏览器会等待它加载完毕在执行脚本。
当执行脚本时,其它线程会解析剩下的文档,找出里面的外部资源(script/style/img)来提前加载(可以并行加载)。这种解析只是去查找需要加载的外部资源,不会修改content tree。
所以我们可以看到多个外部资源并行下载。
使用 document.createElement 创建的 script 默认是异步的,示例如下。
console.log(document.createElement("script").async); // true
所以,通过动态添加 script 标签引入 JavaScript 文件默认是不会阻塞页面的。如果想同步执行,需要将 async 属性人为设置为 false。
脚本解析会将脚本中改变 DOM 和 CSS 的地方分别解析出来,追加到 DOM Tree 和 Style Rules 上。
这是由可视化元素按照其显示顺序而组成的树,也是文档的可视化表示。它的作用是保证按照正确的顺序来绘制内容。
渲染树的每个节点(renderer)代表一个矩形区域——对应 DOM 元素的 CSS Box。
在渲染树创建后进入 Layout 阶段,给渲染树的每个节点设置在屏幕上的位置信息
renderer 在创建完成并添加到 render tree 时,并不包含 位置和大小 信息。计算这些值的过程称为布局或重排(Layout/Reflow)。
Paint 阶段,通过 UI backend 绘制 render tree 到屏幕。
一般来说,可以把普通文档流看成一个图层。特定的属性可以生成一个新的图层。不同的图层渲染互不影响,所以对于某些频繁需要渲染的建议单独生成一个新图层,提高性能。但也不能生成过多的图层,会引起反作用。
通过以下几个常用属性可以生成新图层
3D 变换:translate3d、translateZ
will-change
video、iframe 标签
通过动画实现的 opacity 动画转换
position: fixed
重绘是当节点需要更改外观而不会影响布局的,比如改变 color 就叫称为重绘
回流是布局或者几何属性需要改变就称为回流。
回流必定会发生重绘,重绘不一定会引发回流。回流所需的成本比重绘高的多,改变深层次的节点很可能导致父节点的一系列回流。
减少重绘和回流:

