简单谈谈浏览器渲染页面过程的几个问题

浏览器渲染页面,简单的说,主要是以下几步

  1. 解析html
  2. 构建DOM树(第1,2一般是同时执行)
  3. 加载外部样式表
  4. 解析外部样式表为CSSOM树
  5. 将DOM树和CSSOM树关联在一起形成渲染树(render树),有哪些节点,各个节点的CSS定义以及他们的从属关系。
  6. 布局,根据渲染树计算页面元素的大小,位置等信息
  7. 渲染,将布局计算好的元素渲染到页面上

几个问题

  1. 解析html的时候,是从上到下解析的,如果遇到外部样式表(link),会新开一个线程去下载,并不会影响DOM树的构建,也就是说,解析html,构建DOM树,以及加载css可以同时进行,因此将外部样式表放到head中,越早加载越好。另外是解析完外部样式表,才会将DOM和CSSOM关联再一起形成渲染树,所以css的link如果放到页面最下面,就算html已经解析完成,还是要等外部样式表加载完成,才会形成渲染树,很大几率会浪费时间。
  2. 解析html的时候,是从上到下解析的,如果遇到外部脚本(script),会停掉解析html的工作,先加载script内容,等加载完,并且script的内容执行完毕之后,才会继续解析html的工作,既然解析html的工作停止了,那页面渲染肯定不会继续执行,客户端用户看到的就是一片空白,所以如果将外部脚本放到html上部,会阻塞页面的渲染,因为阻塞了解析html的工作。另外,如果script中需要操作DOM,此时DOM树还没有构建完成,因此找不到DOM元素,就会报错。
  3. script的标签上,可以添加async和defer属性。
    ————
    defer:用于开启新的线程下载脚本文件,并使脚本在文档解析完成后执行。
    1)defer只适用于外联脚本,如果script标签没有指定src属性,只是内联脚本,不要使用defer
    2)如果有多个声明了defer的脚本,则会按顺序下载和执行
    如果有两个script标签:scriptA(文件大),scriptB(文件小)都有defer属性,A在B之前,浏览器并发的请求一般为6个,因此可能A和B同时加载的情况,但是defer是按顺序加载和执行的,所以就算B下载完了,也会等待A加载执行完之后,再执行。
    3)defer脚本会在DOMContentLoaded和load事件之前执行
    ————
    async:HTML5新增属性,用于异步下载脚本文件,下载完毕立即解释执行代码。
    1)只适用于外联脚本,这一点和defer一致
    2)如果有多个声明了async的脚本,其下载和执行也是异步的,不能确保彼此的先后顺序
    不会限制执行顺序,谁先加载完,谁先执行,如果多个script之间有依赖关系,就不适用async。
    3)async会在load事件之前执行,但并不能确保与DOMContentLoaded的执行先后顺序
  4. 在第五步,将DOM树和CSSOM关联在一起,形成渲染树的过程,是一个非常复杂并且
    开销很大的过程(具体过程我也没研究过),因此DOM结构尽量简单,css中尽量使用class和id,不要各种嵌套标签写太深。
  5. 重排(回流,Reflow),第6步中,布局计算好了页面元素的大小,位置等信息之后,通过js等手段,修改元素的几何属性或者DOM节点,就要重新计算布局。由于浏览器渲染界面是基于流式布局模型的,也就是某一个DOM节点信息更改了,就需要对DOM结构进行重新计算,重新布局界面,再次引发回流,只是这个结构更改程度会决定周边DOM更改范围,即全局范围和局部范围,全局范围就是从根节点html开始对整个渲染树进行重新布局,例如当我们改变了窗口尺寸或方向或者是修改了根元素的尺寸或者字体大小等;而局部布局可以是对渲染树的某部分或某一个渲染对象进行重新布局。
    ————
    会引起重排的操作有:
    1)页面首次渲染。
    2)浏览器窗口大小发生改变。
    3)元素尺寸或位置发生改变。
    4)元素内容变化(文字数量或图片大小等等)。
    5)元素字体大小变化。
    6)添加或者删除可见的DOM元素。
    7)激活CSS伪类(例如::hover)。
    8)设置style属性
    9)现代浏览器大多都是通过队列机制来批量更新布局,浏览器会把修改操作放在队列中,至少一个浏览器刷新(即16.6ms)才会清空队列,但当你获取布局信息的时候,队列中可能有会影响这些属性或方法返回值的操作,即使没有,浏览器也会强制清空队列,触发回流与重绘来确保返回正确的值。
    offsetTop、offsetLeft、offsetWidth、offsetHeight
    scrollTop、scrollLeft、scrollWidth、scrollHeight
    clientTop、clientLeft、clientWidth、clientHeight
    width、height
    getComputedStyle()
    getBoundingClientRect()
  6. 由于浏览器使用流式布局,对Render Tree的计算通常只需要遍历一次就可以完成,但table及其内部元素除外,他们可能需要多次计算,通常要花3倍于同等元素的时间,这也是为什么要避免使用table布局的原因之一。
  7. 重绘(Repainting)在第7步,将元素渲染到页面上,如果元素的样式(color,background等)改变是,不会触发第6步重新计算各个元素的位置大小等,只需要重新渲染页面就可以,因此开销要比重排小的多。
  8. 为什么通常在发送数据埋点请求的时候使用的是 1x1 像素的透明 gif 图片?
    1)避免跨域(img 天然支持跨域)
    2)利用空白gif或1x1 px的img是互联网广告或网站监测方面常用的手段,简单、安全、相比PNG/JPG体积小,1px 透明图,对网页内容的影响几乎没有影响,这种请求用在很多地方,比如浏览、点击、热点、心跳、ID颁发等等,
    3)图片请求不占用 Ajax 请求限额
    4)不会阻塞页面加载(在第一步的时候,会和link标签一样,新开一个线程去加载图片),不会影响用户的体验,只要new Image对象就好了,一般情况下也不需要append到DOM中,通过它的onerror和onload事件来检测发送状态。

参考资料:
https://www.jianshu.com/p/3be88ec81e21
http://www.imooc.com/article/45936
https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/24
https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/87

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值