当有了 DOM Tree 和 CSSOM Tree 后,就可以两个结合来 构建 Render Tree
- 注意一:link元素不会阻塞DOM Tree的构建过程,但是会阻塞Render Tree的构建过程
- 因为Render Tree在构建时,需要对应的CSSOM Tree。当我们DOMTree解析完成的时候,如果CSSOM Tree没解析完成就会阻塞。当然一般情况下浏览器会进行优化处理,不会傻傻的等待
- 注意二:Render Tree和DOMTree并不是一一对应的关系
- 比如对于display为none的元素,压根不会出现在render tree中
- 第四步是在**渲染树(Render Tree)**上运行 布局(Layout) 以计算每个节点的几何体。 渲染树会 表示 要显示哪些节点以及其他样式,但是 不表示 每个节点的尺寸、位置 等信息 布局的主要目的是为了确定呈现树中所有节点的宽度、高度和位置信息
- 第五步是将每个节点 绘制(Paint) 到屏幕上 在绘制阶段,浏览器将布局阶段计算的 每个frame转为屏幕上实际的像素点 包括 将元素的可见部分进行绘制,比如文本、颜色、边框、阴影、替换元素(比如img)
- 绘制的过程,可以将布局后的元素绘制到多个合成图层中【这是浏览器的一种优化手段】
- 默认情况下,标准流中的内容都是被绘制在同一个图层(Layer)中的
- 而一些特殊的属性,会创建一个新的合成层(Compositinglayer ),并且新的图层可以利用GPU来加速绘制
- 因为每个合成层都是单独渲染的
- 那么哪些属性可以形成新的合成层呢?常见的一些属性:
- 3D transforms
- video、canvas、iframe
- opacity动画转换时
- position: fixed
- will-change:一个实验性的属性,提前告诉浏览器元素可能发生哪些变化
- animation或 transition设置了opacity、transform
- 分层确实可以提高性能,但是它以内存管理为代价,因此不应作为web性能优化策略的一部分过度使用
- 回流reflow(也可以称之为重排) 第一次确定节点的大小和位置,称之为布局(layout) 之后对节点的大小、位置修改 重新计算 称之为回流
- 什么情况下引起回流呢? 比如DOM结构发生改变(添加新的节点或者移除节点) 比如改变了布局(修改了width、height、padding、font-size等值) 比如窗口resize(修改了窗口的尺寸等) 比如调用getComputedStyle方法获取尺寸、位置信息
- 重绘repaint【字面理解就是对页面再做绘制】 第一次渲染内容称之为绘制(paint) 之后重新渲染称之为重绘
- 什么情况下会引起重绘呢? 比如修改背景色、文字颜色、边框颜色、样式等
- 回流一定会引起重绘,所以回流是一件很消耗性能的事情。
- 所以在开发中要尽量避免发生回流 修改样式时尽量一次性修改【比如通过cssText修改,比如通过添加class修改】 尽量 避免频繁的操作DOM【我们可以在一个DocumentFragment或者父元素中将要操作的DOM操作完成,再一次性的操作】 尽量 避免通过getComputedStyle获取尺寸、位置等信息 对某些元素使用position的absolute或者fixed【并不是不会引起回流,而是开销相对较小,不会对其他元素造成影响】
- 我们现在已经知道了页面的渲染过程,但是JavaScript在哪里呢? 事实上,浏览器在解析HTML的过程中,遇到了 script元素是不能继续构建DOM树的 它会 停止继续构建,首先下载JavaScript代码,并且执行JavaScript的脚本 只有 等到JavaScript脚本执行结束后,才会继续解析HTML,构建DOM树
- 为什么要这样做呢? 这是 因为JavaScript的作用之一就是操作DOM,并且可以修改DOM 如果我们等到DOM树构建完成并且渲染再执行JavaScript会造成严重的回流和重绘,影响页面的性能 所以会在遇到script元素时,优先下载和执行JavaScript代码,再继续构建DOM树
- 但是这个也往往会带来新的问题,特别是现代页面开发中: 在目前的开发模式中(比如Vue、React),脚本往往比HTML页面更“重”,处理时间需要更长 所以会造成页面的解析阻塞,在脚本下载、执行完成之前,用户在界面上什么都看不到
- 为了解决这个问题,script元素给我们提供了两个属性(attribute) : defer和async
- defer属性告诉浏览器 不要等待脚本下载,而继续解析HTML,构建DOM Tree
- 脚本会 由浏览器来进行下载,但是不会阻塞DOM Tree的构建过程
- 如果脚本提前下载好了,它会 等待DOM Tree构建完成,在DOMContentLoaded事件之前先执行defer中的代码
- 所以DOMContentLoaded总是会等待defer中的代码先执行完成
<script src="./foo.js" defer></script>
<script>
window.addEventListener("DOMContentLoaded",()=>{
console.log("DOMContentLoaded");
})
</script>
- 多个带defer的脚步是可以保持正确的执行顺序的
- 从某种角度来说,defer可以提高页面的性能,并且推荐放到head元素中
- 注意:defer仅适用于外部脚本,对于script默认内容会被忽略
- async特性与defer有些类似,它也能够让脚本不阻塞页面
- async是让一个脚本完全独立的:
- 浏览器 不会因async 脚本而阻塞(与defer类似)
- async脚本不能保证顺序,它是独立下载、独立运行,不会等待其他脚本
- async不会能保证在DOMContentLoaded之前或者之后执行
- defer通常用于需要在文档解析后操作DOM的JavaScript代码,并且对多个script文件有顺序要求的
- async通常用于独立的脚本,对其他脚本,甚至DOM没有依赖的
在渲染完成后,浏览器可能会继续加载页面中的其他资源,如异步加载的内容或者通过JavaScript生成的动态内容。
而在此过程中,如果没有其他资源需要加载,浏览器将与服务器之间的TCP连接断开。
简单理解复制代码主动方:我已经关闭了向你那边的主动通道了,这是我最后一次给你发消息了,之后只能被动接收你的信息了
被动方:收到你通道关闭的信息
被动方:那我也告诉你,我这边向你的主动通道也关闭了
主动方:最后收到你关闭的信息,OK结束
断开连接,结束通讯
————————————————————————————————————————————————————————————————————————————
提出分手的可能是男生(客户端),也可能是女生(服务端)
主动方:分手吧,我不喜欢你了!
被动方:行,你等我忙完手上的工作我在收拾你!
被动方:我忙完了,分手就分手!
主动方:好,好聚好散,拜拜!
断开连接,结束通讯
详细分析
由于TCP连接是全双工的,因此,每个方向都必须要单独进行关闭,这一原则是当一方完成数据发送任务后,发送一个FIN来终止这一方向的连接,收到一个FIN只是意味着这一方向上没有数据流动了,即不会再收到数据了,但是在这个TCP连接上仍然能够发送数据,直到这一方向也发送了FIN。首先进行关闭的一方将执行主动关闭,而另一方则执行被动关闭。
任何一方都可以在数据传送结束后发出连接释放的通知,所有主动发起关闭请求可以是客户端,也可以是服务端
这里我们假设是由客户端先主动发起关闭请求
- 第一次挥手:TCP客户进程会发送TCP连接释放报文段,并进入终止等待1(FIN-WAIT-1)状态。 FIN:终止位,表示断开TCP连接 TCP规定终止位FIN等于1的报文段即使不携带数据,也要消耗掉一个序号
- 第二次挥手:TCP服务器进程收到TCP连接释放报文段后,会发送一个普通的TCP确认报文段并进入关闭等待(CLOSE-WAIT)状态。 序号seq字段的值设置为v,与之前收到的TCP连接释放报文段中的确认号匹配 TCP客户进程收到TCP确认报文段后就进入终止等待2(FIN-WAIT-2)状态,等待TCP服务器进程发出的TCP连接释放报文段 这时的TCP连接属于半关闭状态,也就是TCP客户进程已经没有数据要发送了。但如果TCP服务器进程还有数据要发送,TCP客户进程仍要接收,也就是说从TCP服务器进程到TCP客户进程这个方向的连接并未关闭,这个状态可能要持续一段时间。
- 第三次挥手:TCP服务器进程发送TCP连接释放报文段 假定序号seq字段的值为w,这是因为在半关闭状态下,TCP服务器进程可能又发送了一些数据。 确认号ack字段的值为u 1,这是对之前收到的TCP连接释放报文段的重复确认。
- 第四次挥手:TCP服务器进程收到确定报文段后就进入关闭状态,而TCP客户进程还要经过2MSL后才能进入关闭状态。
之后断开连接,结束通讯
总结- 浏览器先判断是否为合法的url格式,不合法则在搜索引擎中搜索
- 合法后,DNS解析会先判断缓存中是否有url的ip地址。
- 缓存的查询顺序是:浏览器缓存 -> 操作系统缓存(本地的hosts文件) -> 路由器缓存 -> 本地的DNS服务器缓存
- 在缓存中没有的情况,则向服务器发起请求查询ip地址。
- 查询IP地址的顺序是:根域名服务器 -> 顶级域名服务器 -> 权威域名服务器。直到查找到返回,并将其存储在缓存中下次使用
- TSP建立连接,也就是三次握手
- 第一次握手,携带建立连接请求SYN=1和随机序列seq=x
- 第二次握手,携带确定字段ACK=1、连接请求SYN=1、随机序列seq=y和ack为上一次握手的seq 1,就是x 1
- 第三次握手,携带确定字段ACK=1、ack=y 1、seq=x 1
- 如果是https,还有一个TLS四次握手
- 第一次握手,客户端向服务端发送 支持的协议版本 支持的加密方法 生成的随机数
- 第二次握手,服务端向客户端发送 证书 公钥 随机数
- 第三次握手前,客户端会先验证证书有没有过期、域名对不对、是否可信机构颁发的。
- 没有问题或者用户接受不受信的证书,浏览器会生成一个新的随机数
- 第三次握手,将之前的三个随机数通过一定的算法生成会话秘钥,之后的加密解密都是用这个秘钥
- 第四次握手,服务端收到回复,是用确定的加密方法进行解密,得到第三个随机数,使用同样的算法计算出会话秘钥
- 建立连接之后,浏览器发送http请求
- 请求报文由请求行、请求头、空行和请求体组成
- 服务器解析请求报文,返回响应报文
- 响应报文由响应行、响应头、空行和响应体组成,我们需要的html文件就在响应体中
- 浏览器拿到html文件并开始解析,构建dom tree。遇到css文件,下载并构建CSSOM tree。等到两者都构建完成之后,一起构建Render tree。然后进行布局和绘制
- 其中遇到了script标签,则停止构建dom tree,等下载完成之后才会继续构建dom tree
- 当资源传输完毕之后,TSP关闭连接,进行四次挥手的操作,其中四次挥手的操作客户端和服务器都可以发起
- 第一次挥手,携带断开连接的FIN=1、确定字段ACK=1、随机序列seq=u,ack=v
- 第二次挥手,携带确定字段ACK=1、随机序列seq=v,ack=u 1
- 第三次挥手,携带确定字段ACK=1、断开连接FIN=1、随机序列seq=w、ack=u 1
- 第四次挥手,携带确定字段ACK=1,随机序列seq=u 1,ack=w 1
- 等待2MSL后进入关闭状态
- 断开连接,结束通讯
作者:前端实习生鲸落
链接:https://juejin.cn/post/7279093851000242234