浏览器中输入URL后发生了什么?
浏览器中输入URL
后发生了什么?
会经过下面的主要流程:
DNS域名解析 => 建立TCP连接 => 发起HTTP请求 => 接收响应结果 => 浏览器解析HTML渲染
1.DNS域名解析
DNS原理
DNS(Domain Name Server)
用来返回某个域名对应主机的ip的服务器。
根DNS (.)
只负责提供各类顶级DNS服务器ip地址. 是域名解析的入口.
顶级DNS (TLD, Top Level Domain)
负责提供二级域名的DNS服务器IP地址. 每一个顶级域名都有对应的DNS的服务器,它们通常由专门的机构公司来维护. 比如.com
由Verisign Global Registry Services
公司维护,.edu
由Educause
公司维护. 它们各自提供自家域名下的子域名(二级域名)的名称服务. 通常我们所说的"购买域名"就是向这些公司的数据库注册一条记录.
权威DNS
负责提供三级域名对应的主机IP地址。由域名购买者提供,大多数域名注册公司同时提供了权威DNS托管服务。
解析过程
获取域名对应的服务器ip的过程:
1、浏览器缓存
当用户通过浏览器访问某域名时,浏览器首先会在自己的缓存中查找是否有该域名对应的IP地址(若曾经访问过该域名且没有清空缓存便存在);
2、系统Hosts
当浏览器缓存中无域名对应IP则会自动检查用户计算机系统Hosts文件是否有该域名对应IP;
3、本地域名服务器
当在用户客服端查找不到域名对应IP地址,则将进入本地DNS缓存中进行查询。通常这个本地域名DNS会配置成运营商(ISP)指定的DNS,但也不是必须的。
4、迭代查询
当以上均未完成,则会由本地DNS开始进行迭代查询:
- 向根域名服务器查询,得到顶级域名服务器的IP地址
- 向顶级域名服务器查询,得到权威域名服务器的IP地址
- 向权威域名服务器查询,最终得到域名的ip
都不行则进入根服务器进行查询。全球仅有13台根域名服务器,1个主根域名服务器,其余12为辅根域名服务器。根域名收到请求后会查看区域文件记录,若无则将其管辖范围内顶级域名(如.com)服务器IP告诉本地DNS服务器;
5、保存结果到缓存
本地域名服务器把返回的结果保存到缓存,以备下一次使用,同时将该结果反馈给客户端,客户端通过这个IP地址与web服务器建立链接。
2.建立TCP连接
TCP三次握手与四次挥手
为什么tcp是三次握手不是两次
“已失效的连接请求报文段”的产生在这样一种情况下:client发出的第一个连接请求报文段并没有丢失,而是在某个网络结点长时间的滞留了,以致延误到连接释放以后的某个时间才到达server。本来这是一个早已失效的报文段。但server收到此失效的连接请求报文段后,就误认为是client再次发出的一个新的连接请求。于是就向client发出确认报文段,同意建立连接。假设不采用“三次握手”,那么只要server发出确认,新的连接就建立了。由于现在client并没有发出建立连接的请求,因此不会理睬server的确认,也不会向server发送数据。但server却以为新的运输连接已经建立,并一直等待client发来数据。这样,server的很多资源就白白浪费掉了。采用“三次握手”的办法可以防止上述现象发生。例如刚才那种情况,client不会向server的确认发出确认。server由于收不到确认,就知道client并没有要求建立连接。”
TCP连接关闭时进行四次握手过程:
客户端:“你好,我这边没有数据要传了,我要关闭咯。”
服务端:“收到~我看一下我这边有没数据要传的。”
服务端:“我这边也没有数据要传啦,我们可以关闭连接咯~”
客户端:“ok~”
3.发起HTTP请求
这个过程会涉及到HTTP缓存的问题。
http缓存
浏览器缓存可以分为两种模式,强缓存和协商缓存。
强缓存(无HTTP请求,无需协商)
直接读取本地缓存,无需跟服务端发送请求确认,http返回状态码是200(from memory cache
或者from disk cache
,不同浏览器返回的信息不一致的)。
- 对应的Http header有:
-
- Cache-Control
- Expires
协商缓存(有HTTP请求,需协商)
浏览器虽然发现了本地有该资源的缓存,但是不确定是否是最新的,于是想服务器询问,若服务器认为浏览器的缓存版本还可用,那么便会返回304(Not Modified) http状态码。
-
对应的Http header有:
-
- Last-Modified(缺点只能精确到1s)
-
- ETag
Http Header | 描述 |
---|---|
Cache-Control | 指定缓存机制,优先级最高 |
Pragma | http1.0字段,已废弃,为了兼容一般使用no-cache |
Expires | http1.0字段,指定缓存的过期时间 |
Last-Modified | http1.0字段,资源最后一次的修改时间 |
ETag | 唯一标识请求资源的字符串,会覆盖Last-Modified |
4.接收响应结果
接收服务器对请求处理后返回的数据,服务器具体怎么处理的,这个在前端就不具体展开了。
5.浏览器解析HTML渲染
- 从HTML解析出
DOM Tree
(DOM树) - 从CSS解析出
CSSOM Tree
(CSS规则树) - JavaScript代码由JavaScript引擎处理
- DOM树建立后根据CSS样式进行构建内部绘图模型,生成
RenderObject
树(渲染树) - 根据网页层次结构构建
RenderLayer
树,同时构建虚拟绘图上下文(重排) - 依赖2D和3D图形库渲染成图像结果呈现在浏览器中(重绘)
css的下载过程不会阻塞解析,但JS会等待其下载并执行完成后才会继续解析。JS下载时,会并行下载其他的资源。
webkit内核浏览器渲染过程为例:
重绘与重排(回流)
一旦渲染树构建完成,就要开始绘制(paint)页面元素了。当DOM的变化包括
- 添加或删除可见的DOM元素
- 元素位置改变
- 元素本身的尺寸发生改变
- 内容改变
- 页面渲染器初始化
- 浏览器窗口大小发生改变
导致浏览器不得不重新计算元素的几何属性,并重新构建渲染树,这个过程称为“重排”。完成重排后,要将重新构建的渲染树渲染到屏幕上,这个过程就是“重绘”。
简单的说,重排负责元素的几何属性更新,重绘负责元素的样式更新。而且,重排必然带来重绘,但是重绘未必带来重排。比如,改变某个元素的背景,这个就不涉及元素的几何属性,所以只发生重绘。
减少重绘和重排的措施包括:
- 一次性改变样式:
var el = document.querySelector('.el');
el.style.borderLeft = '1px';
el.style.borderRight = '2px';
el.style.padding = '5px';
var el = document.querySelector('.el');
el.style.cssText = 'border-left: 1px; border-right: 2px; padding: 5px';
//或者
// css
.active {
padding: 5px;
border-left: 1px;
border-right: 2px;
}
// javascript
var el = document.querySelector('.el');
el.className = 'active';
- 隐藏元素,进行修改后,然后再显示该元素
let ul = document.querySelector('#mylist');
ul.style.display = 'none';
appendNode(ul, data);
ul.style.display = 'block';
- 使用文档片段创建一个子树,然后再拷贝到文档中
let fragment = document.createDocumentFragment();
appendNode(fragment, data);
ul.appendChild(fragment);
- 将原始元素拷贝到一个独立的节点中,操作这个节点,然后覆盖原始元素
let old = document.querySelector('#mylist');
let clone = old.cloneNode(true);
appendNode(clone, data);
old.parentNode.replaceChild(clone, old);
- 缓存布局信息
current = div.offsetLeft;
div.style.left = 1 + ++current + 'px';
div.style.top = 1 + ++current + 'px';
总结
用户输入https://www.baidu.com/
后,浏览器会判断缓存中是否由该域名的DNS信息,如果有则使用缓存中的DNS,否则查询用户本地Hosts文件,如果没有,则去ISP的DNS服务器查询IP,一般到这里就能查到了;
拿到IP后就会发起请求,此时会建立TCP连接,一个完整的TCP连接包含3次握手和4次挥手,然后发起HTTP网络请求,此时会判断是否由缓存,缓存分为强缓存和协商缓存,强缓存就是本地缓存,保存在内存或者用户磁盘中,强缓存是不会发送HTTP请求的,协商缓存则会发送HTTP请求来判断缓存是否过期,如果缓存可用,服务器会返回304状态码,接收到响应的数据后,浏览器会解析并渲染HTML,会将HTML中的DOM生成DOM树,将CSS生成CSS规则树,然后合并起来交给渲染引擎处理最后呈现到页面中,用户就可以看到界面了。
如果用户修改元素的几何属性,会触发浏览器的重排,如果用户修改元素的样式,例如颜色相关的属性则会造成重绘,重排一定会造成重绘,但重绘不一定造成重排,如果要考虑性能,那么写代码时就应该避免触发浏览器的频繁重排,例如:
改变样式的时候,最好一次性改变,可以把多个样式写成一个类名。
如果需要大量修改元素的几何位置,可以先将元素进行隐藏,修改完后显示元素,这个过程只会触发浏览器2次重排,远比一行行修改后触发几十上百次重排要划算。
其实都是为了应付面试,这种知识根本不可能深入理解的,除非你造一个mini版的浏览器,要自己实现一遍里面的各种parser和render,你不实践一遍,你怎么可能理解里面的细节呢?就像现在很多教Vue、React源码的,都开始手写一个mini版本了,就是因为你不写,光脑子里面背下那些概念,没有深入理解,屁用没有,顶多吹吹牛逼,显得很厉害,实际遇到棘手问题还是解决不了,况且,编程还是重在实践的学科。
记录一下,有时间研究怎么手写一个浏览器,哈哈,其实还是很有趣的。