前言
前面我们讲过了浏览器当中的任务队列与事件循环event-loop的概念,我觉得大家也需要好好回忆一下js:任务队列与事件循环这篇文章,毕竟这种算是干货,只能自己慢慢理解,然后加深印象。接下来我们也来讲解一下浏览器当中的其他干货。
浏览器从输入 url到页面渲染完成发生了什么
我们先来思考一下这个问题,当你在百度上看见一个很有意思的东西,然后你点进去,并观察到了这个页面给出的效果,在你点击到页面完全展现的过程当中,浏览器到底干了些什么呢?我们就先简略地来聊聊浏览器的前世今生!
前世今生
-1、DNS域名解析
所做的步骤是将网址转换为IP地址,这是一个递归查询的过程
-2、建立 TCP 连接
三次握手环节
-3、向服务器发送 HTTP 请求,返回结果
请求报文: HTTP协议的通信内容
-4、浏览器解析 HTML
-5、浏览器布局渲染
-6、断开TCP连接
四次挥手环节
可能很多小伙伴就疑惑了,内容介绍少也就算了,你中间的解析和渲染写都没写,啥都没学到
我们的重点在第四第五两个步骤
DOM树
浏览器会将接收到的数据转换成为字符片段,并且标记,也就是: 字节数据 ==》字符片段 ==》标记(token),标记完成之后紧接着转换成Node节点。
而后,不同的Node节点会根据之前的联系来构建dom树
CSSOM树
过程类似于DOM树的产生,但是这个过程更加消耗性能
因为css是可以自己定义的,也是可以继承得到的,这个过程浏览器需要递归得到CSSDOM树,这样才能确定每一个元素到底是什么样式。
渲染树
DOM树 + CSSOM树 = render树
渲染树不是简单的将两者合并,渲染树只包含需要显示的 节点和这些节点的样式信息:比如:display:none的节点不在渲染树中显示。
GPU绘制
浏览器拿着render树,开始GPU绘制
layout: 有了Render Tree,浏览器已经能知道网页中有哪些节点、各个节点的CSS定义以及他们的从属关系,从而去计算出每个节点在屏幕中的位置。
painting: 按照算出来的规则,通过显卡,把内容画到屏幕上。
------------回流 || 重排(计算页面布局)
当浏览器发现某个部分发生了点变化影响了布局,需要倒回去重新渲染,这个回退的过程叫 reflow。
回流和重绘
重排:当元素的几何属性发生变化时,导致页面布局变动
重绘:当元素的非几何属性发生变化时,
我们需要注意的是,重排一定重绘,重绘不一定重排
面试常考
导致重排的因素有哪些?
1.改变window的大小
2.改变元素的尺寸
3.添加、删除元素
4.页面初次渲染
为什么操作DOM结构慢?
1.因为DOM树归渲染引擎操作,js归js引擎操作,当js直接操作DOM时,涉及到了两个线程之间的通信,势必造成性能开销过大
2.可能还会带来回流的情况,所以也导致了性能上的问题
什么情况会阻塞渲染?
1.html和css会阻塞页面渲染 ----尽量不要写没有意义的dom结构(扁平化) 优化选择器
2.解析script标签会暂停构建DOM,如果希望页面更早的渲染,就不应该在页面的头部加载js
如何减少重排重绘?
-1.隐藏元素 display:none
-2.将元素脱离文档流,对其进行多次修改,再将其带回文档流中
-3.修改单一的DOM:
-使用类名
-CSSText
在如下代码当中,由于改变了app容器的属性,于是页面会产生四次重排和重绘,
当我们将display
设置为none
的时候,页面不显示该内容,那么这个Node节点将不在渲染树中显示,所以你可以对这个容器的任何属性进行修改,那么之后只需要将display
设置为block
,都是记作一次重绘
或者是将需要修改的属性放置cssText
中,那么也会是一次重绘
let app = document.getElementById('app')
app.addEventListener('click',() => {
//原本会进行四次渲染,但是浏览器有优化机制,实际上就一次
// app.style.display = 'none' 让元素消失,修改完所有需要修改的属性,再让它出现
app.style.width = '200px'
app.style.height = '100px'
app.style.padding = '20px'
app.style.margin = '10px'
//使用cssText会一次性加载完所以属性
// app.style.cssText = 'width:200px; heigth:100px;padding:20px;margin:10px'
//app.style.display = 'block' 需要设置回display属性
})
浏览器的优化机制
目前大多数浏览器都会通过队列化来批改 重排的过程,浏览器会将修改操作放到队列中,直到一段时间后,队列的存储达到阈值,才会一次性全部重排,清空队列。
但是,获取布局信息的操作,会强制队列刷新,如 offsetTop、offsetLeft、offsetWidth、offsetHeight、 scrollTop、scrollLeft、scrollWidth、scrollHeight 、clientTop、clientLeft、clientWidth、clientHeight、 getComputedStyle() 、getBoundingClientRect
碰见上述代码,直接强制刷新任务队列,如果队列当中存在事件,则进行一次重排
字节面试
利用上述代码,我们思考下列操作会进行几次重排?
let el = document.getElementById('app');
el.style.width = (el.offsetWidth+1) + 'px'; // 刷新但是不重排列
el.style.width = 1 + 'px'
很多人都会觉得是2次,甚至是3次,但是答案却是一次
上述代码当中,两行代码都是对容器的宽度进行修改,实际上,这两行代码会只会进行一次重绘,可以说下面的代码把上一行的代码的结果覆盖了,然后拿着数据1 + 'px'
作为宽度去进行页面渲染。可能又有人会问了,不是有offsetWidth操作吗,队列不是要刷新吗?这里需要注意一下,在刷新操作的时候,任务队列为空,也就是任务队列没有代码去执行,是不会发生重排重绘的,所以需要注意,队列当中需要存在事件。
总结
这里只是浏览器在拿到数据以后进行的操作,这些还是在面试当中会出现的,至于前面的那些骚操作。。。。
预知前事如何?待我学完计算机网络再说吧
我是小白,我们一起学习,若有错误,请指出,谢谢!