从输入URL到页面展示
前人栽树,后人乘凉,以下为学习笔记。
从输入URL到页面展示,这中间发生了什么?
这是一道经典面试题,涉及到了网络、操作系统、Web 等一系列的知识。
Before
判断用户输入是URL
虽然我们讨论的是从输入 URL 到看到页面,但实际上大多数现代浏览器支持的不仅仅是 URL 的输入。当浏览器判断我们输入的内容不符合 URL 规则,则会使用默认浏览器搜索引擎,结合输入的关键词组成新的带搜索内容的 URL。如果用户输入的是url地址,地址栏会根据规则,把这段内容加上协议,合成为完整的 URL。
如果存在卸载事件
当我们在地址栏中输入URL并按下回车后,浏览器里发生第一件事是触发卸载事件,即 beforeunload 事件。这也是当前页面最后的机会,你可以最后弹个弹窗尝试阻止用户关闭页面。此时用户可以选择取消导航,那么浏览器就会停止处理后续操作。
URL请求过程
浏览器进程会通过进程间通信(IPC)把 URL 请求发送至网络线程,网络线程接收到 URL 请求后,会在这里发起真正的 URL 请求流程。
网络线程会查找本地缓存是否缓存了该资源。如果有缓存资源,那么直接返回资源给浏览器进程;如果在缓存中没有查找到资源,那么直接进入网络请求流程。
1. DNS解析
进入网络请求的第一步,是进行DNS解析,获取URL对应的服务器 IP 地址。
2. TCP连接
获取到 IP 地址后,浏览器向该 IP 的服务器发起TCP连接,双方进行TCP三次握手。
3. HTTP请求
在三次握手成功建立 TCP 连接后,浏览器向服务器发送HTTP请求。
具体做法是,浏览器根据 HTTP 协议,构建请求行、请求头等信息,并把和该域名相关的 Cookie 等数据附加到请求头中,然后向服务器发送构建的请求信息。
4. 服务器处理请求
服务器接收到请求信息后,会根据请求信息生成响应数据(包括响应行、响应头和响应体等信息),并发给网络线程。
浏览器收到HTTP响应
1. 解析响应头
当网络线程接收到服务器响应信息之后,就开始解析响应头内容了。
-
处理响应行 (检查状态码)
响应的状态行(如 HTTP/1.1 200 OK)是服务器告诉浏览器需要做出的行为:
如果状态码是 301 或者 302,那么网络线程会读取响应头中的 Location 字段,并对该字段中指定的地址重新发起请求,一切重新开始,这个行为被称为重定向。 -
处理 Content-Type
当响应头中的 Content-Type 为application/octet-stream ,则将该请求提交给下载管理器,该导航流程结束,不再进行。
当响应头中的 Content-Type 为 text/html,就是告诉浏览器,则通知浏览器进程准备渲染进程准备进行渲染。
2. 浏览器渲染
(1). 基本过程
- 浏览器会将HTML解析成一个DOM树。
DOM 树的构建过程是一个深度遍历过程:当前节点的所有子节点都构建好后才会去构建当前节点的下一个兄弟节点。DOM树的生成过程中可能会被CSS和JS的加载执行阻塞。
- 将CSS解析成 CSS Rule Tree 。
每个CSS文件都被分析成一个StyleSheet对象,每个对象都包含CSS规则。CSS规则对象包含对应于CSS语法的选择器和声明对象以及其他对象。
- 根据DOM树和CSSOM来构造 Rendering Tree。
渲染树构建完成后,每个节点都是可见节点并且都含有其内容和对应规则的样式。这也是渲染树与DOM树的最大区别所在。渲染树是用于显示,那些不可见的元素当然就不会在这棵树中出现了,譬如。除此之外,display等于none的也不会被显示在这棵树里头,但是visibility等于hidden的元素是会显示在这棵树里头的。
- 遍历渲染树开始layout,计算每个节点的位置大小信息。
有了Render Tree,浏览器已经能知道网页中有哪些节点、各个节点的CSS定义以及他们的从属关系.
- 遍历render树,将render树每个节点绘制到屏幕。
在绘制阶段,遍历渲染树,调用渲染器的paint()方法在屏幕上显示其内容。渲染树的绘制工作是由浏览器的UI后端组件完成的。
- 渲染阻塞
当浏览器遇到一个 script 标记时,DOM 构建将暂停,直至脚本完成执行,然后继续构建DOM。每次去执行JavaScript脚本都会严重地阻塞DOM树的构建,如果JavaScript脚本还操作了CSSOM,而正好这个CSSOM还没有下载和构建,浏览器甚至会延迟脚本执行和构建DOM,直至完成其CSSOM的下载和构建。
(2). 从渲染进程的角度来看
- 分配渲染进程
当 Chrome 的浏览器网络线程拿到一个 HTML 资源后,将会给该资源分配一个渲染进程。这个渲染进程往往是浏览器新建立的一个进程,除非该 URL 是从同一网站(same-site)中打开,那么他们将共享同一个渲染进程。
- 渲染进程解析数据
网络线程接收到 HTML 数据,浏览器通知渲染进程,渲染进程与网络线程建立连接,解析 HTML 数据并加载需要的资源,接收数据完毕后,通知浏览器进程更新界面。
- 页面渲染
(3). 举例WebKit渲染引擎的渲染过程
举例WebKit渲染的过程:
浏览器是一个边解析边渲染的过程。首先浏览器解析HTML文件构建DOM树,然后解析CSS文件构建渲染树,等到渲染树构建完成后,浏览器开始布局渲染树并将其绘制到屏幕上。
JS的解析是由浏览器中的JS解析引擎完成的。
JS是单线程运行,但是又存在某些任务比较耗时,如IO读写等,所以需要一种机制可以先执行排在后面的任务,这就是:同步任务(synchronous)和异步任务(asynchronous)。JS的执行机制就可以看做是一个主线程加上一个任务队列(task queue)。同步任务就是放在主线程上执行的任务,异步任务是放在任务队列中的任务。所有的同步任务在主线程上执行,形成一个执行栈;异步任务有了运行结果就会在任务队列中放置一个事件;脚本运行时先依次运行执行栈,然后会从任务队列里提取事件,运行任务队列中的任务,这个过程是不断重复的,所以又叫做事件循环(Event loop)。
浏览器在解析过程中,如果遇到请求外部资源时,如图像,iconfont,JS等。浏览器将重复前面的过程(DNS/TCP/HTTP Request/Response)下载该资源。
如果需要下载的外部资源太多,浏览器会创建多个TCP连接,并行地去下载。但是同一时间对同一域名下的请求数量也不能太多,要不然服务器访问量太大,受不了。所以浏览器要限制一下, 例如Chrome在Http1.1下只能并行地下载6个资源。
当服务器给浏览器发送JS,CSS这些文件时,会告诉浏览器这些文件什么时候过期(使用Cache-Control或者Expire),浏览器可以把文件缓存到本地,当第二次请求同样的文件时,如果不过期,直接从本地取就可以了。如果过期了,浏览器就可以询问服务器端,文件有没有修改过?(依据是上一次服务器发送的Last-Modified和ETag),如果没有修改过(304 Not Modified),还可以使用缓存。否则的话服务器就会被最新的文件发回到浏览器。
请求过程是异步的,并不会影响HTML文档进行加载,但是当文档加载过程中遇到JS文件,HTML文档会挂起渲染过程,要等到文档中JS文件加载完毕并且解析执行完毕,才会继续HTML的渲染过程。原因是因为JS有可能修改DOM结构,这就意味着JS执行完成前,后续所有资源的下载是没有必要的,这就是JS阻塞后续资源下载的根本原因。CSS文件的加载不影响JS文件的加载,但是却影响JS文件的执行。JS代码执行前浏览器必须保证CSS文件已经下载并加载完毕。