浏览器主要进程(多进程的),主要分为:
- 浏览器主进程:只有一个,主要控制页面的创建,销毁,网络资源管理,下载等。
- 第三方插件进程:每一种类型的插件对应一个进程,仅当使用插件时才创建。
- CPU 进程:最多一个,用于3D绘制等。
- 浏览器渲染进程(浏览器内核):每个Tab 页对应一个进程,互不影响
第一步:输入网址并解析
这里我们只考虑输入的是一个 URL 结构字符串。 URL 的组成:协议,主机,端口,路径,查询,参数,锚点。
输入以后,浏览器会解析出协议,主机端口,路径等信息,并构造一个 HTTP 请求。
- 浏览器发出请求前,根据请求头的 expires 和 cache-control 判断是否命中(包括是否过期)强缓存策略,如果命中,直接从缓存获取资源并不会发送请求。
- 如果没有命中。浏览器会发送请求,根据请求头的 If-Modified0-Since 和 If-None-Match 判断是否命中,如果命中,如果命中,直接从缓存获取资源并不会发送请求。
- 如果前面两步都没有命中,则直接从服务器端获取资源。
浏览器缓存
强缓存: 就是向浏览器缓存查找该请求结果,并根据该结果的缓存规则来决定是否使用该缓存结果的过程。
Expires: 版本:HTTP/1.0,来源:存在于服务端返回的响应头中,语法:Expires: Web, 22 Nov 2019 08:41:00 GMT,缺点:服务器的时间和浏览器的时间可能并不一致导致失效。
Cache-Control: 版本:HTTP/1.1,来源:响应头和请求头,语法:Cache-Control:max-age=3600,缺点:时间最终还是会失效
请求头
第二步:TCP/IP 连接三次握手
客户端和服务端在进行 http 请求和返回的工作中,需要创建一个 TCP connection(由客户端发起),http 不存在链接这个概念,它只有请求和响应,请求和响应都是数据包,它们之间的传输通道就是 TCP connection。
- 第一次:主机 A 发送位码位 SYN = 1,随机产生 Seq =1234567 的 数据包到服务器,主机 B 由 SYN = 1知道,A 机要求建立联机;(由浏览器发起,告诉服务器我要发送请求了)
- 主机 B 收到请求后要确认联机信息,向 A 发送 ack = (主机 A 的 Seq+1)ACK = 1234567+1,SYN = 1, 随机产生 Seq = 7654321 的包;(由服务器发起,告诉浏览器我准备接收了,赶紧发吧)
- 主机 A 收到后检查 ack 是否正确,即第一次发送的 Seq+1以及位码 SYN 是否位1,若正确,主机 A 会再发送 ack = (主机 B Seq+1), ack = 7654321,主机 B 收到确认 Seq 值与 ACK = 7654321, 则连接成功;(由浏览器发起,告诉服务器,我马上要发了,你准备接收吧)
位码即 tcp 标志位,有6中表示:SYN (synchronous 建立联机),ACK (acknowledgement 确认),PSH (push 传送),FIN (finish 结束), RET (reset 重置),URG (urgrent 紧急)
三次握手的原因:其实这是由 TCP 的自身特点可靠传输决定的。客户端和服务端要进行可靠传输,那么就需要确认双方的 “接收”和“发送”能力。第一次确定了客户端的“发送能力”,第二次确定了服务端的“发送能力”和“接收能力”,第三次确定了客户端的“接收能力”。这样不容易出现丢包的现象。
第三部分:HTTP 请求
第四部分:服务器处理请求并返回 HTTP 报文
Http 请求一般可以分为两类;静态资源和动态资源
- 请求访问静态资源,这个就直接根据 URL 地址去服务器里找就行了
- 请求动态资源,就需要 web Server 把不同请求,委托给服务器上处理相应请求的程序进行处理(例如 CGI 脚本, js脚本,servelets, ASP 脚本,服务器短 JavaScript,或者一些其他的服务器端等技术等),然后返回后台程序处理产出的结果作为响应,发送到客户端。 服务器在处理请求的时候主要有3种方式:
-
- 是用一个线程来处理所有的请求,并且同时只能处理一个请求,单这样的话性能是非常的低。
-
- 是每一个请求都给他分配一个线程,但是当连接和请求比较多的时候就会导致服务器的CPU不堪负重。
-
- 采用复用 I/O 的方式来处理例如通过epoll 方式监视所有连接,当链接状态发生变化的时候才去分配空间进行处理。
第五部分:浏览器渲染页面
5.1 渲染流程
- 解析 HTML 生成 DOM 树 - 渲染引擎首先解析 HTML文档,生成 DOM 树,
- 构建 Render 树 - 接下来不管是内联式,外联式还是嵌入式引入的 CSS 样式会被解析成 CSSOM 树,根据 DOM 和 CSSOM 树生成另一棵用于渲染的树-渲染树(Render-tree),
- 布局 Render 树 - 然后对渲染树的每个节点进行布局处理,确定其在屏幕上的显示位置,
- 绘制 Render 树 - 最后遍历渲染树并用UI后端层将每一个节点绘制出来。 以上步骤是一个渐进的过程,为了提高用户体验,渲染引擎试图尽可能快的把结果显示给最终用户。它不会等到所有 HTML 都被解析完成才创建并布局渲染树。它会在从网络层获取文档内容的同时把已经接收到的布局内容先展示出来。
重绘(repaint)
当页面中元素样式的改变并不影响它在文档流中的位置(例如:color,background,visibility等),浏览器会将新的样式赋予给元素并重新绘制它,这个过程称为重绘。
回流(feflow)--- 重排
当 Render Tree 中部分或全部元素的尺寸,结构或某些属性发生变化时。浏览器重新渲染部分或全部文档的过程称为回流。reflow 会从 这个 root frame 开始递归往下,依次计算所有的节点几何尺寸和位置。reflow 几乎是无法避免的,现在界面上流行的一些效果,比如树状目录的折叠和展开等,都将引起浏览器的 reflow。鼠标滑过,点击...只要这些行为引起了页面上某些元素的占位面积,定位方式,边距等属性的变化,都会引起它内部,周围甚至整个页面的重新渲染,通常我们都无法预估浏览器到底会 reflow 那一部分的代码,它们都彼此相互影响着。
- 页面首次渲染
- 浏览器窗口大小发生变化
- 元素尺寸或者位置发生改变
- 元素内容发生变化(文字数量或者图片大小等等)
- 元素字体大小变化
- 添加或删除课件的 DOM 元素
- 激活 CSS 伪类,例如 hover
- 查询某些属性或者调用某些方法
引起回流的属性和方法:
clientWidth, clientHeight, clientTop, clientLeft
offsetWidth, offsetHeight, offsetTop, offsetLeft
scrollWidth, scrollHeight, scrollTop, scrollLeft, scrollIntoView, scrollIntoViewIfNeeded
getComputedStyle, getBoundingClientRect, scrollTo
如何减少:
- 避免使用 table 布局
- 尽可能在 DOM 树的最末端改变 class
- 避免设置多层内联样式
- 讲动画效果应用到 position 属性为 absolute 或 fixed 的元素上
- 避免使用 css 表达式(如:cale)
- 避免频繁操作样式,最好一次性重写 style 属性,或者将样式列表定义为 class 并一次性更改 class 属性
- 避免频繁操作 DOM,创建一个 documentFragment,在它上面进行所有 DOM 操作,最后在把它添加待文档中。
- 可可以先为元素设置display:none,操作结束后再把它显示出来,因为 dispaly 为 none 的元素上进行的 DOM 操作不会引发回流和重绘
- 避免频繁读取会引发回流和重绘的属性,如果确实需求多次使用,就用一个变量缓存起来
- 对具有复杂动画的元素使用绝对定位,使它脱离文档流,否则会引起父元素频繁回流
每次回流,重绘后浏览器还需要合并渲染层并输出到屏幕上。所有的这些都会是浏览器卡顿的原因, 回流的成本比重绘的成本高德多的多,一个节点的回流很有可能导致子节点,甚至父节点以及同级节点的回流。在一些高性能的电脑上也许还没什么,但是如果发生在手机上,那么这个过程是延满加载和耗电的。
有些情况下比如修改了元素的样式,浏览器并不会立即回流或者重绘一次,而是会把这样的操作积攒一批,然后做一次回流,这又叫异步回流或增量异步回流。
回流必将引起重绘,而重绘不一定引起回流
第六部分:断开链接:TCP 四次分手
刚开始双方都处于 established 状态,假如是客户端先发起关闭请求
- 第一次挥手:客户端发送一个 FIN 报文,报文中会指定一个序列号。此时客户端处于 FIN_WAIT-1状态
- 第二次挥手:服务端接受到 FIN 之后,会发送 ACK 报文,并把客户端的序列号值+1作为 ACK 报文的序列号值,表明已经收到客户端的报文了,此时服务端处于 CLOSE_WAIT 状态。
- 第三次挥手:如果服务端也想断开链接了,和客户端的第一次挥手一样,发送 FIN 报文,并且指定一个序列号,此时服务端处于 LAST_ACK 的状态。
- 第四次挥手:过一段时间,确保服务端收到了自己的 ACK 报文之后才会进入 CLOSE 状态,服务端收到 ACK 报文之后,就处于关闭链接状态了( CLOSED )。
**为什么需要四次?**
当服务端收到客户端的 SYN 链接请求报文后,可以直接发送 SYN+ACK 报文,其中 ACK 报文是用来应答的,SYN 报文是用来同步的。但是关闭连接时,当服务器端收到 FIN 报文时,很可能并不会立即关闭 SOCKET,所以只能先回复一个 ACK 报文,告诉客户端,“你发的 FIN 报文我收到了”。只有等待我服务器端所有的报文都发送完了,我才能发送 FIN 报文,因此不能一起发送。
**为什么客户端发送 ACK 之后不直接关闭,而是要等待一阵子才关闭?**
客户端收到服务器端的连接释放报文后,对此发出确认报文段( ACK=1,Seq=u+1,ack=w+1),客户端进入 TIME_WAIT( 时间等待 )状态,此时 TCP 未释放掉,需要经过时间等待计时器设置的时间 2MSL 后,客户端才进入 CLOSE 状态。如果不等待,客户端直接跑路,当服务器端还有很多数据包要给客户端发送,且还在路上的时候,且客户端的端口此时刚好被新的应用占用,那么就接受到了无用数据包,造成数据包混乱
**为什么 TIME_WAIT状态需要经过 2MSL(最大报文生存时间)才能返回到 CLOSED 状态?**
理论上,四个报文都发送完毕,就可以直接进入 CLOSE 状态了,但是可能网络是不可靠的,有可能最后一个 ACK 丢失。所以 TIME_WAIT 状态就是用来重发可能丢失的 ACK 报文,1个 MSL 确保四次挥手中主动关闭方最后的 ACK 报文最终能到达到对端;1个 MSL 确保对端没有收到 ACK 重传的 FIN 报文可以到达。