目录
一、从输入 url 到得到 html 的过程
- 输入资源地址(地址栏输入、页面跳转、页面加载)发起请求
- 浏览器缓存机制,优先查找本地有无缓存可用
- 搜索自身的 DNS 缓存
- 搜索操作系统自身的DNS 缓存
- 读取本地的 HOST 文件
- 发起一个 DNS 的系统调用
- 浏览器向 DNS 服务器发起
域名
解析 请求- 宽带运营服务商服务器查看本身缓存
- 运营商服务器发起一个迭代DNS解析的请求
- 运营商服务器把结果返回操作系统内核同时缓存起来
- 操作系统内核把结果返回浏览器
- 浏览器拿到
域名
对应的IP
地址
- 建立与服务器的 TCP/IP 连接,
三次握手
过程 - 向服务器发送
http
/https
请求,创建端口 - 服务器在端口监听客户端请求
- 接收请求,根据路径参数,经过后端处理后
- 返回状态和内容
- 浏览器得到返回内容
- 拿到HTML页面代码,开始解析页面
- 碰到的 js、css、图片等静态资源,发起请求
- 从发起请求到返回结果,同样经过上面的步骤
- 根据拿到的资源对页面进行渲染,最终把一个完整的页面呈现给用户
二、浏览器渲染页面的过程
- 解析html
- 构建dom树
- dom树结合css文件,构建呈现树
- 布局
- 绘制
先整体描述一下我的理解:
- 解析html和构建dom树是同步进行的,这个过程就是逐行解析代码,包括html标签和js动态生成的标签,最终生成dom树。
- 构建呈现树,就是把css文件和style标签的中的内容,结合dom树的模型,构建一个呈现树,写到内存,等待进一步生成界面。呈现树一定依赖dom树,呈现节点一定会有对应的dom节点,但是dom节点不一定会有对应的呈现节点,比如,被隐藏的一个div。
- 布局,这一步就是结合呈现树,把dom节点的大小、位置计算出来。虽然呈现节点已经附着在都没节点上,会有对元素大小、位置的定义,但是浏览器还需要根据实际窗口大小进行计算,比如对auto的处理。
- 绘制,把css中有关颜色的设置,背景、字体颜色等呈现出来。
三、浏览器渲染页面的详细过程分解
1、解析与构建DOM树
前两步我们放在一起讨论,浏览器的实际工作也是将他们放在一起进行的。
对于HTML浏览器有专门的html解析器来解析HTML,并在解析的过程中构建DOM树。在这里我们讨论两种DOM元素的解析,即样式(link、style)与脚本文件(script)。由于浏览器采用自上而下的方式解析,在遇到这两种元素时都会阻塞浏览器的解析,直到外部资源加载并解析或执行完毕后才会继续向下解析html。对于样式与脚本的先后顺序同样也会影响到浏览器的解析过程,究其原因主要在于:script脚本执行过程中可能会修改html界面(如document.write函数);DOM节点的CSS样式会影响js的执行结果。
在测试中得到以下四条结论:
- 外部样式会阻塞后续脚本执行,直到外部样式加载并解析完毕。
- 外部样式不会阻塞后续外部脚本的加载,但会阻塞外部脚本的执行。
- 如果后续外部脚本含有async属性(IE下为defer),则外部样式不会阻塞该脚本的加载与执行
- 对于动态创建的link标签不会阻塞其后动态创建的script的加载与执行,不管script标签是否具有async属性,但对于其他非动态创建的script,以上三条结论仍适用
1)外部样式会阻塞后续脚本执行,直到外部样式加载并解析完毕。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JS Bin</title>
<script>var start = +new Date;</script>
<link href="http://udacity-crp.herokuapp.com/style.css?rtt=2" rel="stylesheet">
</head>
<body>
<span id="result"></span>
<script>
var end = +new Date;
document.getElementById('result').innerHTML = (end-start);
</script>
</body>
</html>
2)外部样式不会阻塞后续外部脚本的加载,但会阻塞外部脚本的执行。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JS Bin</title>
<script>var start = +new Date;</script>
<link href="http://udacity-crp.herokuapp.com/style.css?rtt=2" rel="stylesheet">
</head>
<body>
test
<script src="http://udacity-crp.herokuapp.com/time.js?rtt=1&a"></script>
<div id="result"></div>
<script>var end = +new Date;document.getElementById("result").innerHTML = end-start;</script>
</body>
</html>
var loadTime = document.createElement('div');
loadTime.innerText = document.currentScript.src + ' executed @ ' + window.performance.now();
loadTime.style.color = 'blue';
document.body.appendChild(loadTime);
从瀑布图中我们可以看到,外部脚本与外部样式是并行加载,但直到外部样式加载完毕,外部脚本才开始执行
3)如果后续外部脚本含有async属性(IE下为defer),则外部样式不会阻塞该脚本的加载与执行
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JS Bin</title>
<script>var start = +new Date;</script>
<link href="http://udacity-crp.herokuapp.com/style.css?rtt=2" rel="stylesheet">
</head>
<body>
test
<script src="http://udacity-crp.herokuapp.com/time.js?rtt=1&a" async></script>
<div id="result"></div>
<script>var end = +new Date;document.getElementById("result").innerHTML = end-start;</script>
</body>
</html>
从瀑布图中可以看到外部脚本的加载与执行并不受link的阻塞
4)对于动态创建的link标签不会阻塞其后动态创建的script的加载与执行,不管script标签是否具有async属性,但对于其他非动态创建的script,以上三条结论仍适用
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JS Bin</title>
<script>var start = +new Date;</script>
</head>
<body>
test
<script>
var link = document.createElement('link');
link.href = "http://udacity-crp.herokuapp.com/style.css?rtt=2";
link.rel = "stylesheet";
document.head.appendChild(link);
var script = document.createElement('script');
script.src = "http://udacity-crp.herokuapp.com/time.js?rtt=1&a";
document.head.appendChild(script);
</script>
<div id="result"></div>
<script>var end = +new Date;document.getElementById("result").innerHTML = end-start;</script>
</body>
</html>
这是最终页面结构
通过瀑布图与页面结果可以看到动态创建的外部脚本并未受link的阻塞。
2、构建呈现树
HTML解析完毕后,开始构建呈现树RenderTree,这一步的主要工作在于将css样式应用到DOM节点上,WebKit内核将这一过程称为附着,其他浏览器有不同的概念。对前端工程师而言这个过程会涉及到CSS层叠问题。
首先将根据样式重要性排序,由低到高依次为:
- 浏览器声明
- 用户普通声明
- 作者普通声明
- 作者重要声明
- 用户重要声明
对于同一重要级别,则是根据CSS选择符的特指度来判定优先级;一条样式声明的特指度由以下四个部分决定:S-I-C-E
- 声明来自内联的style属性则 S+1;
- 声明中含有id属性则 I+1;
- 声明中含有类、伪类、属性选择器则 C+1;
- 声明中含有元素、伪元素选择器则 E+1;
特指度的比较类似于两个字符串之间比较大小。
呈现树的每一个节点即为与其相对应的DOM节点的CSS框,框的类型与DOM节点的display属性有关,block元素生成block框,inline元素生成inline框。每一个呈现树节点都有与之相对应的DOM节点,但DOM节点不一定有与之相对应的呈现树节点,比如display属性为none的DOM节点,而且呈现树节点在呈现树中的位置与他们在DOM树中的位置不一定相同,比如float与绝对定位元素。
下图为呈现树与其相对应的DOM树节点
3、布局
呈现树构造完成后浏览器便进行布局处理,及计算每个呈现树节点的大小和位置信息。有道友可能要问,前面已将样式附着到DOM节点上,不是已经有了样式信息为何还要计算大小。这里可以这样理解,以上包含大小的样式信息只是存在内存里,并没有实际使用,浏览器要根据窗口的实际大小来处理呈现树节点的实际显示大小和位置,比如对于margin为auto的处理。
布局是一个递归过程,从跟呈现节点开始,递归遍历子节点,计算集合几何信息。具体过程还是比较复杂偶也不甚了解,道友们还是查阅其他资料吧。
4、绘制
布局完成后,便是将呈现树绘制出来显示在屏幕上。对于每一个呈现树节点来说,主要绘制顺序如下:
- 背景颜色
- 背景图片
- 边框
- 子呈现树节点
- 轮廓
参考文献:
【1】https://blog.csdn.net/chenjuan1993/article/details/81712599