一:浏览器解析 URL 过程:
1 用户输入 URL 地址。
2 对 URL 地址进行 DNS 域名解获得IP地址。
3 建立 TCP 连接(三次握手)。
4 浏览器向 web 服务器发送一个 HTTP 请求报文。
5 服务器返回 HTTP 响应报文给客户端。
6 关闭 TCP 连接(四次挥手)。
7 浏览器解析文档资源并渲染页面。
二:浏览器解析流程
1 浏览器可以解析的资源,HTML,SVG,XHTML等,解析完会生成DOM Tree
2 CSS资源会解析成CSS Rule Tree。
3 JS通过DOM API和CSSOM API来操作DOM树和CSS树。
4 解析完成后综合DOM树和CSS树会生成Rendering Tree,计算每个元素(Frame)的位置,这个过程就是layout或者叫reflow过程。
5 调用操作系统Native GUI的API绘制。
注意:上述这个过程是理论上是逐步完成的,但是实际实现中为了更好的用户体验,渲染引擎为了尽可能早的将内容呈现到屏幕上,会在构建DOM树的同时去解析CSS构建CSS树,并且还会去生成Rendering Tree。解析完一部分内容就显示一部分内容,同时,可能还在通过网络下载其余内容,这样就可以更快的显示出页面,其中解析后面的内容涉及到布局和样式的改变引起的reflow过程和repaint。
三、浏览器渲染流程
https://coolshell.cn/articles/9666.html
渲染的主要过程分为——Render Tree(渲染树)生成——Layout(布局)——Paint(绘制)。
1,Render Tree的生成
DOM树和CSS树结合生成Render Tree(渲染树)——这是由可视化元素按照其显示顺序组成的树形结构,是文档可视化的表示,它的作用是让浏览器能够按照正确的顺序渲染页面元素。Firefox中称之为“框架”,Webkit中的术语则是呈现器或者呈现对象。
渲染树是和DOM元素相对应的,但是并非全部一一对应,例如:1,非可视化元素是不会出现在渲染树中,如“head”元素,2,如果元素的display属性值为“none”,也不会出现在渲染树中(但是visibility属性值为“hidden”的元素会出现在渲染树中)。
2,布局
渲染树中并不包含位置和大小的信息,计算这些值的过程就是布局或者重排。
布局的过程是一个递归的过程,从根元素开始,递归遍历部分或者所有的渲染树结构,并为每一个需要显示元素计算几何信息。一般根元素位置坐标(0,0),大小为浏览器窗口的可见区域。
这里涉及到两个重要的概念reflow和repaint:
repaint(重绘):元素的某一部分属性发生改变,如字体颜色,背景颜色等改变,尺寸并未改变,这时发生的改变过程就是repaint。
reflow(回流): 因为浏览器渲染是一个由上而下的过程,当发现某部分的变化影响了布局时,就需要倒回去重新渲染,这个过程就称之为reflow。reflow几乎是没法避免的,现在一些常用的效果,比如树状目录的折叠、展开(实质上是元素的显示与隐藏)等,都将引起浏览器的 reflow。鼠标滑过、点击……只要这些行为引起了页面上某些元素的占位面积、定位方式、边距等属性的变化,都会引起它内部、周围甚至整个页面的重新渲染。基本上能引起reflow的主要有几个原因:
1,网页初始化。
2,JS操作DOM树的时候,增加删除元素等。
3,某些元素的尺寸改变。
4,CSS属性的改变,
但是浏览器很聪明,为了避免细小的改变就进行repaint或者reflow,浏览器采用一种"dirty"系统,会将这些改变操作积攒一批,然后做一次reflow,这又叫异步reflow或增量异步reflow。但是有些特殊情况不会这么做,比如:resize窗口,改变了页面默认的字体,等,对于这些操作,浏览器会马上进行reflow。
但是有的时候,我们自己编写的脚本会阻止浏览器的这种操作,比如我们请求下面的值的时候:offsetTop, offsetLeft, offsetWidth, offsetHeight,scrollTop/Left/Width/Height,clientTop/Left/Width/Height,IE中的 getComputedStyle(), 或 currentStyle等,如果我们的程序运行的时候需要这些值,那么浏览器需要给我们返回最新的值,而这样就会将当前积攒的操作执行,从而引起频繁的reflow或者repaint。
通常reflow比repaint会耗费更多的时间,从而也就会影响性能,所以编写代码的时候要尽可能避免过多的reflow或者repaint。减少reflow/repaint的方法:
1,修改样式不要逐条修改,建议定义CSS样式的class,然后直接修改元素的className。
2,不要将DOM节点的属性值放在循环中当成循环的变量。
3,为动画的 HTML 元素使用 fixed 或 absoult 的 position,那么修改他们的 CSS 是不会 reflow 的。
4,把DOM离线后修改。如设置DOM的display:none,然后进行你需要的多次修改,然后再显示出来,或者clone一个节点到内存中,然后随意修改,修改完成后再与在线的交换。
5,千万不要使用table布局,一个微小的改变就可能引起整个table的重新布局。
3,绘制
在绘制阶段,系统会遍历渲染树,并且调用呈现器将的“paint”方法,将内容显示在屏幕上。同样,类似于布局过程,也分为全局和增量两种。更多绘制详情参考文末资料。
三次握手:解析URL时建立TCP连接阶段,TCP是以后总归面向连接的,可靠的,基于字节流的传输控制协议。经历了三次握手,客户端与服务端就建立了连接。
第一次握手:起初两端都处于 CLOSED 关闭状态,Client 将标志位 SYN(TCP/IP建立连接时使用的握手信号) 置为 1,随机产生一个值 seq = x,并将该数据包发送给 Server,Client 进入 SYN-SENT 状态,等待 Server 确认。
第二次握手:Server 收到数据包后由标志位 SYN = 1 得知 Client 请求建立连接,Server 将标志位 SYN 和 ACK 都置为 1,ack = x + 1,随机产生一个值 seq = y,并将该数据包发送给Client以确认连接请求,Server 进入 SYN-RCVD 状态,此时操作系统为该 TCP 连接分配 TCP 缓存和变量。
第三次握手:Client 收到确认后,检查 seq 是否为 x + 1,ACK 是否为 1,如果正确则将标志位 ACK 置为 1,ack = y + 1,并且此时操作系统为该 TCP 连接分配 TCP 缓存和变量,并将该数据包发送给 Server,Server 检查 ack 是否为 y + 1,ACK 是否为 1,如果正确则连接建立成功,Client 和 Server 进入 established 状态,完成三次握手,随后 Client 和 Server 就可以开始传输数据。
四次挥手:解析URL时关闭TCP连接的操作
第一次挥手:Client 的应用进程先向其 TCP 发出连接释放报文段(FIN<结束TCP回话的结束标志> = 1,序号 seq = u),并停止再发送数据,主动关闭 TCP 连接,进入 FIN-WAIT-1(终止等待1)状态,等待 Server 的确认。
第二次挥手:Server 收到连接释放报文段后即发出确认报文段,(ACK = 1,确认号 ack = u + 1,序号 seq = v),Server 进入 CLOSE-WAIT(关闭等待)状态,此时的 TCP 处于半关闭状态,Client 到 Server 的连接释放。
注:Client 收到 Server 的确认后,进入 FIN-WAIT-2(终止等待2)状态,等待 Server 发出的连接释放报文段。
第三次挥手:Server 已经没有要向 Client 发出的数据了,Server 发出连接释放报文段(FIN = 1,ACK = 1,序号 seq = w,确认号 ack = u + 1),Server 进入 LAST-ACK(最后确认)状态,等待 Client 的确认。
第四次挥手:Client 收到 Server 的连接释放报文段后,对此发出确认报文段(ACK = 1,seq = u + 1,ack = w + 1),Client 进入 TIME-WAIT(时间等待)状态。此时 TCP 未释放掉,需要经过时间等待计时器设置的时间 2MSL 后,Client 才进入 CLOSED 状态。
六、性能优化
1,提升HTML加载速度
- 页面精简,删除不必要的注释,空格,将内嵌的JS和CSS移至外部文件,使用压缩工具等。
- 减少文件数量,减少页面上引入的文件数量可以减少请求的次数,可以合并的JS和CSS文件尽量合并。
- 减少域名查询,DNS查询和解析域名需要消耗时间,减少对外部JavaScript、CSS、图片等资源的引用,不同域名的使用越少越好。
- 使用缓存,重用数据。
- 优化页面元素的加载顺序。
- 使用现在CSS和合法的标签。
- 指定图片的大小,如果浏览可以立即确定图片大小就不需要重新进行布局操作。
- 根据浏览器类型选择合适的策略。
2,编写合理的CSS
首先说明CSS选择符的匹配顺序,从右到左!从右到左!从右到左!(重要的事情说三遍),所以,类似于“#nav li” 我们以为很简单的规则,应该马上就可以匹配成功,但是,需要从右往左匹配,所以,先会去查找所有的li,然后再去确定它的父元素是不是#nav。因此,编写合理的CSS也可以提高我们的页面行能:
- DOM的深度尽量浅,不要嵌套过深。
- 减少inline javascript css的数量。
- 使用合法的CSS属性。
- 不要为ID选择器指定类名或者标签名。
- 避免后代选择器,尽量使用子选择器。
- 避免使用通配符。
3,关于javascript标签
对于javascript标签首先得了解其加载和执行的特点:1,载入后立即执行,2,执行时会阻塞页面后续的内容,针对这些特点,我们使用javascript标签时应该注意:
- 将所有的javascript标签放在页面底部,也就是body标签闭合之前,这样可以保证脚本执行前已完成DOM渲染。
- 尽可能合并脚本,页面中引入的脚本越少,加载响应速度也就越快。
- 减少inline javascript的使用。
- 所有的javascript标签会按照其引入顺序依次执行,只有前面的内容解析完成才会解析下一个,所以注意多个javascript标签的引入顺序。
- 使用defer属性,该属性可以使脚本在文档完全呈现以后再执行。
- 使用async属性,可以使当前脚本不必等待其他脚本的执行,也不必阻塞文档的呈现。
资源外链的下载
遇到外链时的处理
遇到外链时,会单独开启一个下载线程去下载资源(http1.1中是每一个资源的下载都要开启一个http请求,对应一个tcp/ip链接)
遇到CSS样式资源
CSS资源的处理有几个特点:
CSS下载时异步,不会阻塞浏览器构建DOM树
会阻塞渲染,也就是
在构建render时,会等到css下载解析完毕后才进行
(这点与浏览器优化有关,防止css规则不断改变,避免了重复的构建)
media query声明的CSS是不会阻塞渲染的
遇到JS脚本资源
JS脚本资源的处理有几个特点:
阻塞浏览器的解析
,也就是说发现一个外链脚本时,需等待脚本下载完成并执行后才会继续解析HTML
浏览器的优化
,一般现代浏览器有优化,在脚本阻塞时,也会继续下载其它资源(当然有并发上限),但是
虽然脚本可以并行下载,解析过程仍然是阻塞的,也就是说必须这个脚本执行完毕后才会接下来的解析
,并行下载只是一种优化而已
defer与async,普通的脚本是会阻塞浏览器解析的,但是可以加上defer或async属性,这样脚本就变成异步了,可以等到解析完毕后再执行 详见异步加载JS
遇到img图片类资源
遇到图片等资源时,直接就是异步下载,不会阻塞解析,下载完毕后直接用图片替换原有src的地方
为什么要先引入CSS文件,再引入js文件?
(1)js的下载是阻塞下载,不可以和其他代码并行下载和解析。但是CSS的加载不会阻塞DOM树的解析(会阻塞其渲染,也会阻塞后面js的执行)
(2)页面加载时,是按照从上到下,从左到右的顺序加载的,如果将js放在前面,会立即执行,阻塞后面的资源下载和执行。如果外部脚本加载时间过长,就会造成网页长时间失去响应,浏览器会呈现假死状态
(3)部分js的执行依赖于前面的CSS样式
(4)js一般是处理功能,所以不需要提前加载。先跟用户观感,再给用户上手体验
loaded和domcontentloaded
简单的对比:
DOMContentLoaded: 事件触发时,仅当DOM加载完成,不包括样式表,图片(譬如如果有async加载的脚本就不一定完成)
load: 事件触发时,页面上所有的DOM,样式表,脚本,图片都已经加载完成了
为了避免用户的白屏时间,应尽可能提高CSS加载速度,方法?
使用CDN(Content Delivery Network,内容分发网络),CDN会根据你的网络状况,替你挑选最近的一个具有缓存内容的节点为你提供资源,减少加载时间
将CSS压缩
合理使用缓存(设置cache-control,expires以及E-tag)
减少HTTP请求数,多个CSS合并,或者干脆直接写成内联样式(但是内联样式的缺点是不能缓存)
JS引擎解析过程
JS的解释阶段
JS是解释型语言,所以它无需提前编译,而是由解释器实时运行
核心的JIT编译器
将源码编译成机器码运行
JS的预处理阶段
分号补全
变量提升
JS的执行阶段
执行上下文,执行堆栈概念(如全局上下文,当前活动上下文)
VO(变量对象)和AO(活动对象)
作用域链
this机制等