前言
在学习前端的过程中,大家都会对浏览器这个神秘的盒子感到好奇
从输入一串url
到页面解析渲染完成,浏览器都干了些啥?
直观展示
为了更好的理解这个过程,我们使用一个工具来帮助我们Chrome
自带的开发工具中的performance
(老版本和其他浏览器为timeline
)
点击录制,然后在地址栏输入我们的url
这里以百度为例,输入www.baidu.com
,回车
然后点击录制结束,就能看到这个东西
网络请求
我们最先看到的是这个
字面意思就可以理解,浏览器在向服务器发送请求,并接收响应头和响应体
不过实际上在这之前,浏览器还做了一些事
得先知道朝哪个服务器发送请求吧
- 根据
url
,先去本地DNS
缓存列表里寻找对应的服务器的ip地址和端口号
,若没有找到,继续寻找系统缓存和路由缓存,若找到则跳转第三步 - 还没找到的话则请求本地
DNS
服务器,没有就将域名发送给其他服务器,递归寻找,从根域名服务器开始不断向下递归,直到返回对应的IP地址和端口号
,并将其缓存 - 根据
ip地址和端口号
,与目标服务器建立TCP
连接(三次握手)
这三步并没有被我们看见,然后接下来的事就被我们观察到了
浏览器向服务器发送http
请求,并接收返回的响应头和响应体
继续往下看
黄色部分都是浏览器的一些默认行为,其中包括隐藏本来的标签页内容等等
以及下面的Recalculate Style
(重新计算样式),Layout
(重排)是为清空本来的页面,为新页面做准备
这些不用管它,不过在这中间我们又一次看到了Receive Data
那是因为服务器在发送数据的时候,可能会进行拆包,分几次发送
解析html
浏览器比较勤快,它并不会等html
完全接收完才开始解析,而是接受一部分就开始解析一部分
HTML Parser
的任务是将HTML
标记,解析成DOM Tree
,这是一个深度遍历的过程,只有Dom
下的子节点都被遍历完成,才遍历下一个同级Dom
节点
同时,在解析的过程中,如果遇到了图片,link
标签,script
标签,都会向服务器发送请求
例如我们上图,遇到了百度的logo
图片,就请求下载
js解析
遇到js
就立即解析下载执行执行
不管是内联的还是外部的,都会阻塞后续dom
的解析和渲染
所以一般将<script>
标签放在<body>
后面
如果是外部的,还可以在<script>
标签上加上defer
或async
属性defer
可以让js的下载不影响html的后续解析,且在html解析完了再执行js文件,且按照原来的下载顺序async
也是让js的下载不影响html的后续解析,但一旦下载完了就立即执行,因此也无法保证按照下载顺序执行
css解析
遇到内联的css
样式就开始解析
外部的css
文件,则在接收到了之后进行解析
两者都会不会阻塞Dom
的解析,但会阻塞Dom
的渲染
这也是为什么要把css
的<link>
标签放进<head>
中
当css文件放在<head>中时,虽然css解析也会阻塞后续dom的渲染,但是在解析css的同时也在解析dom,所以等到css解析完毕就会逐步的渲染页面了
把css
语句解析成为CSSOM
渲染布局与绘制
在有了Dom Tree
和CSSOM
之后
浏览器就会构建Render Tree
(渲染树)
其实把DOM Tree和CSSOM
进行附加
所以Render Tree
实际上就是一个计算好了样式,同时不包含display:none
之类,不占据空间的元素的的渲染树。
然后浏览器就根据这个Render Tree
进行第一次的布局(Layout
)以及绘制(Paint
)
重排和重绘
完成了第一次绘制之后,浏览器会继续收到服务器发来的数据
图片,css
文件,js
文件
其中可能会有导致页面布局更改或样式更改的内容,都会添加到我们的Render Tree上去
引起重排(Layout
)或重绘(Paint
)
其中重排一定会连带着引起重绘,反之则不然
重排(Reflow)
引起重排的操作
页面首次渲染
浏览器窗口大小发生改变
元素尺寸或位置发生改变
元素内容变化(文字数量或图片大小等等)
元素字体大小变化
添加或者删除可见的DOM元素
激活CSS伪类(例如::hover)
设置style属性
那是遇到一次就重排一次吗?也不是
浏览器会有一个渲染队列来进行优化
积累了一定量或一定时间的更改内容,才会进行重排
不过当访问了特定属性的时候
比如js
文件里遇到一句console.log(body.clientWidth)
会强制刷新渲染队列
以下属性的访问会立即刷新渲染队列
widthheight | margin | padding | display | border |
position | overflow | clientWidth | clientHeight | clientTop |
clientLeft | offsetWidth | offsetHeight | offsetTop | offsetLeft |
scrollWidth | scrollHeight | scrollTop | scrollLeft | scrollIntoView() |
scrollTo() | getComputedStyle() | getBoundingClientRect() | scrollIntoViewIfNeeded() |
所以访问这些属性要谨慎,最好分离读写,以免过多的重排影响性能
重绘
引起重绘的一般都是修改元素的属性
color | border-style | visibility | background |
text-decoration | background-image | background-position | background-repeat |
outline-color | outline | outline-style | border-radius |
outline-width | box-shadow | background-size |
性能优化
从性能优化的角度,我们要尽量减少浏览器的重排和重绘,尤其是重排
- 尽量不要在布局信息改变时做查询(会导致渲染队列强制刷新)
- 减少
DOM
操作,同一个DOM
的多个属性改变可以写在一起,在一个局部方法中需要多次访问同一个Dom
,则先暂存它的引用。批量添加DOM
,可以先让元素脱离文档流,操作完后再带入文档流,这样只会触发一次重排(应用fragment
元素) - 将需要多次重排的元素,
position
属性设为absolute
或fixed
,例如有动画效果的元素 - 采用更优的
API
替代消费高的API
,转换优化消费高的集合,如用querySelectorAll()
替代getElementByXX()
。 - 开启动画的
GPU
加速 - 少用
HTML
集合(类数组)来遍历,因为集合遍历比真数组遍历耗费更高。 - 用事件委托来减少事件处理器的数量。
- 避免设置大量的
style
属性,因为通过每一次设置都会触发一次reflow
,所以最好是使用class
属性 - 动画实现的速度太精细平滑,会导致
reflow
过于频繁 - 不要使用
table
布局,因为table
中某个元素旦触发了reflow
,那么整个表格都会reflow
。可以设置table-layout:auto
;或者是table-layout:fixed
这样可以只reflow
一行, - 减少通过
JavaScript
代码修改元素样式,尽量使用修改class
名方式操作样式或动画; - 隐藏在屏幕外,或在页面滚动时,尽量停止动画;
最后
大家可以自己去尝试一下performance
的使用,可以更直观的理解浏览器的工作流程
参考文章
https://segmentfault.com/a/11...
https://segmentfault.com/a/11...
https://sylvanassun.github.io...
https://www.imooc.com/article...
https://segmentfault.com/a/11...
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。