浏览器的“前世今生”——“今生”系列

142 阅读6分钟

前言

前面我们讲过了浏览器当中的任务队列事件循环event-loop的概念,我觉得大家也需要好好回忆一下js:任务队列与事件循环这篇文章,毕竟这种算是干货,只能自己慢慢理解,然后加深印象。接下来我们也来讲解一下浏览器当中的其他干货。

浏览器从输入 url到页面渲染完成发生了什么

我们先来思考一下这个问题,当你在百度上看见一个很有意思的东西,然后你点进去,并观察到了这个页面给出的效果,在你点击到页面完全展现的过程当中,浏览器到底干了些什么呢?我们就先简略地来聊聊浏览器的前世今生!

前世今生

-1、DNS域名解析

所做的步骤是将网址转换为IP地址,这是一个递归查询的过程

-2、建立 TCP 连接

三次握手环节

-3、向服务器发送 HTTP 请求,返回结果

请求报文: HTTP协议的通信内容

-4、浏览器解析 HTML

-5、浏览器布局渲染

-6、断开TCP连接

 四次挥手环节 
 

可能很多小伙伴就疑惑了,内容介绍少也就算了,你中间的解析和渲染写都没写,啥都没学到

src=http---inews.gtimg.com-newsapp_bt-0-12757718949-1000.jpg&refer=http---inews.gtimg.jpg

我们的重点在第四第五两个步骤

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操作吗,队列不是要刷新吗?这里需要注意一下,在刷新操作的时候,任务队列为空,也就是任务队列没有代码去执行,是不会发生重排重绘的,所以需要注意,队列当中需要存在事件。

总结

这里只是浏览器在拿到数据以后进行的操作,这些还是在面试当中会出现的,至于前面的那些骚操作。。。。

预知前事如何?待我学完计算机网络再说吧

我是小白,我们一起学习,若有错误,请指出,谢谢!