这篇文章专治面试中:"在浏览器输入URL回车以后发生了什么?"、"浏览器输入URL发送的一系列操做!"等面试问题。 css
这个问题已是老生常谈了,更是常常被做为面试的压轴题出现,网上也有不少文章,但最近闲的无聊,而后就本身作了一篇笔记,感受比以前理解更透彻了。html
这篇笔记是我这两天看了数十篇文章总结出来的,因此相对全面一点,但因为我是作前端的,因此会比较重点分析浏览器渲染页面那一部分,至于其余部分我会罗列出关键词,感兴趣的能够自行查阅,前端
本文的步骤是创建在,请求的是一个简单的 HTTP 请求,没有 HTTPS、HTTP二、最简单的 DNS、没有代理、而且服务器没有任何问题的基础上,尽管这是不切实际的。html5
首先判断你输入的是一个合法的 URL 仍是一个待搜索的关键词,而且根据你输入的内容进行自动完成、字符编码等操做。git
因为安全隐患,会使用 HSTS 强制客户端使用 HTTPS 访问页面。详见:你所不知道的 HSTS。github
浏览器还会进行一些额外的操做,好比安全检查、访问限制(以前国产浏览器限制 996.icu)。面试
先检查浏览器中是否有存在缓存,没有则调用系统库函数进行查询。浏览器
操做系统也有本身的 DNS缓存,但在这以前,会向检查域名是否存在本地的 Hosts 文件里,没有则向 DNS 服务器发送查询请求。缓存
路由器也有本身的缓存。安全
ISP DNS(因特网服务提供商) 就是在客户端电脑上设置的首选 DNS 服务器,它们在大多数状况下都会有缓存。
在前面全部步骤没有缓存的状况下,本地 DNS 服务器会将请求转发到互联网上的根域,下面这个图很好的诠释了整个流程:
须要注意的点
TCP/IP 分为四层,在发送数据时,每层都要对数据进行封装:
在前面的步骤咱们已经获得服务器的 IP 地址,浏览器会开始构造一个 HTTP 报文,其中包括:
其中须要注意的点:
传输层会发起一条到达服务器的 TCP 链接,为了方便传输,会对数据进行分割(以报文段为单位),并标记编号,方便服务器接受时可以准确地还原报文信息。
在创建链接前,会先进行 TCP 三次握手。关于 TCP/IP 三次握手,网上已经有不少段子和图片生动地描述了。
将数据段打包,并加入源及目标的IP地址,而且负责寻找传输路线。
判断目标地址是否与当前地址处于同一网络中,是的话直接根据 Mac 地址发送,不然使用路由表查找下一跳地址,以及使用 ARP 协议查询它的 Mac 地址。
注意:在 OSI 参考模型中 ARP 协议位于链路层,但在 TCP/IP 中,它位于网络层。
以太网协议
根据以太网协议将数据分为以“帧”为单位的数据包,每一帧分为两个部分:
Mac 地址
以太网规定了连入网络的全部设备都必须具有“网卡”接口,数据包都是从一块网卡传递到另外一块网卡,网卡的地址就是 Mac 地址。每个 Mac 地址都是独一无二的,具有了一对一的能力。
广播
发送数据的方法很原始,直接把数据经过 ARP 协议,向本网络的全部机器发送,接收方根据标头信息与自身 Mac 地址比较,一致就接受,不然丢弃。
注意: 接收方回应是单播。
相关知识点: ARP 攻击
服务器接受请求 接受过程就是把以上步骤逆转过来,参见上图。
大体流程
HTTPD
最多见的 HTTPD 有 Linux 上经常使用的 Apache 和 Nginx,以及 Windows 上的 IIS。
它会监听获得的请求,而后开启一个子进程去处理这个请求。
处理请求
接受 TCP 报文后,会对链接进行处理,对HTTP协议进行解析(请求方法、域名、路径等),而且进行一些验证:
重定向
假如服务器配置了 HTTP 重定向,就会返回一个 301永久重定向响应,浏览器就会根据响应,从新发送 HTTP 请求(从新执行上面的过程)。
关于更多:详见这篇文章
URL 重写
而后会查看 URL 重写规则,若是请求的文件是真实存在的,好比图片、html、css、js文件等,则会直接把这个文件返回。
不然服务器会按照规则把请求重写到 一个 REST 风格的 URL 上。
而后根据动态语言的脚本,来决定调用什么类型的动态文件解释器来处理这个请求。
以 PHP 语言的 MVC 框架举例,它首先会初始化一些环境的参数,根据 URL 由上到下地去匹配路由,而后让路由所定义的方法去处理请求。
浏览器接收到来自服务器的响应资源后,会对资源进行分析。
首先查看 Response header,根据不一样状态码作不一样的事(好比上面提到的重定向)。
若是响应资源进行了压缩(好比 gzip),还须要进行解压。
而后,对响应资源作缓存。
接下来,根据响应资源里的 MIME 类型去解析响应内容(好比 HTML、Image各有不一样的解析方式)。
浏览器内核
不一样的浏览器内核,渲染过程也不彻底相同,但大体流程都差很少。
基本流程
首先要知道浏览器解析是从上往下一行一行地解析的。
解析的过程能够分为四个步骤:
1.解码(encoding)
传输回来的其实都是一些二进制字节数据,浏览器须要根据文件指定编码(例如UTF-8)转换成字符串,也就是HTML 代码。
2.预解析(pre-parsing)
预解析作的事情是提早加载资源,减小处理时间,它会识别一些会请求资源的属性,好比img标签的src属性,并将这个请求加到请求队列中。
3.符号化(Tokenization)
符号化是词法分析的过程,将输入解析成符号,HTML 符号包括,开始标签、结束标签、属性名和属性值。
它经过一个状态机去识别符号的状态,好比遇到<,>状态都会产生变化。
4.构建树(tree construction)
注意:符号化和构建树是并行操做的,也就是说只要解析到一个开始标签,就会建立一个 DOM 节点。
在上一步符号化中,解析器得到这些标记,而后以合适的方法建立DOM对象并把这些符号插入到DOM对象中。
<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>
复制代码
你历来没有在浏览器看过相似”语法无效”的错误,这是由于浏览器去纠正错误的语法,而后继续工做。
事件
当整个解析的过程完成之后,浏览器会经过DOMContentLoaded事件来通知DOM解析完成。
一旦浏览器下载了 CSS,CSS 解析器就会处理它遇到的任何 CSS,根据语法规范解析出全部的 CSS 并进行标记化,而后咱们获得一个规则表。
CSS 匹配规则
在匹配一个节点对应的 CSS 规则时,是按照从右到左的顺序的,例如:div p { font-size :14px }会先寻找全部的p标签而后判断它的父元素是否为div。
因此咱们写 CSS 时,尽可能用 id 和 class,千万不要过分层叠。
其实这就是一个 DOM 树和 CSS 规则树合并的过程。
注意:渲染树会忽略那些不须要渲染的节点,好比设置了display:none的节点。
计算
经过计算让任何尺寸值都减小到三个可能之一:auto、百分比、px,好比把rem转化为px。
级联
浏览器须要一种方法来肯定哪些样式才真正须要应用到对应元素,因此它使用一个叫作specificity的公式,这个公式会经过:
而后得出一个权重值,取最高的那个。
渲染阻塞
当遇到一个script标签时,DOM 构建会被暂停,直至脚本完成执行,而后继续构建 DOM 树。
但若是 JS 依赖 CSS 样式,而它尚未被下载和构建时,浏览器就会延迟脚本执行,直至 CSS Rules 被构建。
全部咱们知道:
为了不这种状况,应该如下原则:
另外,若是要改变阻塞模式,可使用 defer 与 async,详见:这篇文章
肯定渲染树种全部节点的几何属性,好比:位置、大小等等,最后输入一个盒子模型,它能精准地捕获到每一个元素在屏幕内的准确位置与大小。
而后遍历渲染树,调用渲染器的 paint() 方法在屏幕上显示其内容。
把以上绘制的全部图片合并,最终输出一张图片。
回流(reflow)
当浏览器发现某个部分发现变化影响了布局时,须要倒回去从新渲染,会从html标签开始递归往下,从新计算位置和大小。
reflow基本是没法避免的,由于当你滑动一下鼠标、resize 窗口,页面就会产生变化。
重绘(repaint)
改变了某个元素的背景色、文字颜色等等不会影响周围元素的位置变化时,就会发生重绘。
每次重绘后,浏览器还须要合并渲染层并输出到屏幕上。
回流的成本要比重绘高不少,因此咱们应该尽可能避免产生回流。
好比:
display:none 会触发回流,而 visibility:hidden 只会触发重绘。
大体流程
能够分为三个阶段:
JS 脚本加载完毕后,会首先进入语法分析阶段,它首先会分析代码块的语法是否正确,不正确则抛出“语法错误”,中止执行。
几个步骤:
JS 有三种运行环境:
每进入一个不一样的运行环境都会建立一个对应的执行上下文,根据不一样的上下文环境,造成一个函数调用栈,栈底永远是全局执行上下文,栈顶则永远是当前执行上下文。
建立执行上下文
建立执行上下文的过程当中,主要作了如下三件事:
JS 线程
虽然 JS 是单线程的,但实际上参与工做的线程一共有四个:
其中三个只是协助,只有 JS 引擎线程是真正执行的
注:浏览器对同一域名的并发链接数是有限的,一般为 6 个。
宏任务
分为:
微任务
微任务是ES6和Node环境下的,主要 API 有:Promise,process.nextTick。
微任务的执行在宏任务的同步任务以后,在异步任务以前。
代码例子
console.log('1'); // 宏任务 同步
setTimeout(function() {
console.log('2'); // 宏任务 异步
})
new Promise(function(resolve) {
console.log('3'); // 宏任务 同步
resolve();
}).then(function() {
console.log('4') // 微任务
})
console.log('5') // 宏任务 同步
以上代码输出顺序为:1,3,5,4,2
复制代码
参考文档
本文做者:4Ark
本文连接: 4ark.me/post/b6c7c0…
版权: 本站文章均采用 CC BY-NC-SA 3.0 CN 许可协议,请勿用于商业,转载注明出处!