js和css对于DOM的阻塞

263 阅读4分钟

前言

当文档解析过程中遇到<script src=""></script>时,文档就无法再解析,它必须去执行脚本,这时就会影响到页面的加载,使得用户体验感不好。

我们可以通过将js脚本放在末尾来解决问题,该方法就是强制在html加载完成之后再去加载js脚本。

但是,如果网速较慢或者html文档过长,也会有明显的延迟。因此<script>标签有俩个属性来解决这个问题

一、javascript阻塞dom

async

<script async src="Beatrix.js"></script>

Beatrix.js的加载和执行 和 后续文档元素的加载和渲染过程 并行进行(指的不是多线程的并行) 一旦js文件加载完成,就会开始执行,因此它会阻塞文档的解析。

因为不会阻塞js文件的加载过程,因此多次脚本的执行顺序得不到保证。

defer

<script defer src="Beatrix.js"></script>

有defer属性,后续文档元素的加载 和 Beatrix.js的加载是并行进行的。 当整个文档解析完成之后才会开始执行js脚本文件,在DOMContentLoaded事件触发之前完成 当多个js脚本时,按照加载的顺序执行

图画解析

灰线表示为HTML文档解析(GUI渲染线程)

蓝线表示为JS脚本加载

红线表示为JS脚本执行(JS解析线程)

如图所示:

1.defer和async两者的js脚本文件在加载过程是相同的,异步的(相对于html文档解析)

2.defer和async两者的区别在于js脚本加载完成之后何时执行

3.async的js脚本是乱序执行,因为它的加载和执行是连接在一起的,不管你在声明时的顺序是怎么样的,只要加载完成就执行

  1. GUI渲染线程和JS解析线程互斥,不能同时进行

关于延迟脚本执行问题

<script src="test1.js" async></script> => console.log('test1')
<script src="./test2.js" defer></script> => console.log('test2')
<script>console.log('normal');</script>

以上执行顺序为normal=》test2=》test1

HTML5规范要求脚本执行应该按照脚本出现的先后顺序执行,但实际情况下,延迟脚本不一定按照先后顺序执行!!

具体情况下要去仔细分析,不能一概而论

二、css阻塞Dom

  1. css加载会阻塞后续js的执行 当js文件位于css文件之后,它会去等待css的下载完成才会去执行js脚本,位于css文件之前的js脚本不会受到影响

  2. css加载解析会阻塞dom的渲染 这个很简单,因为css需要去解析成为cssom,这时候它才可以和已经生成的dom树共同构建布局树(layout tree)

  3. css加载解析不会阻塞dom解析 因为dom解析过程是发生在css解析之前的, 注意:这个结论成立的前提条件是,没有js脚本文件的存在.

  4. css加载解析会阻塞dom解析 因为js脚本文件有修改cssom的能力,因此如果js脚本文件在css文件之后的话,js的执行是会在cssom生成之后,也就是css加载解析之后完成的。而js的执行是会阻塞dom解析的,从而css的加载是会间接影响dom的解析

三、浏览器首次渲染

在浏览器中从发起url请求开始,到首次显示页面的内容,主要经历三个阶段:

  1. 请求发出去,提交相关数据,此时的页面还是原来的内容

  2. 提交数据成功之后,渲染进程会创建一个空白页面----解析白屏,需要等待dom构建,cssom构建、layout tree 生成。

  3. 绘制相关图层,展示页面内容

在以上过程,根据前面的内容,我们知道,css文件、js文件的下载解析或多或少是会阻塞第二个阶段的相关流程,因此如果时间过长,白屏时间就会过长,这样用户迟迟看不到页面内容,是十分影响使用体验的。

因此,如果想要浏览器首次渲染,缩短白屏时长,在前端方面就需要去处理css文件、js文件的下载和执行问题

以下是主要解决办法:

  1. 通过内联js、css来移除两种类型的文件的下载(前提是文件小)
  2. 遇到文件比较大的情况,那么就尽量减少文件的大小,压缩文件的体积,比如使用webpack等工具
  3. 通过async和defer属性将一些不需要在dom解析阶段使用的js文件进行标记处理
  4. 对于大的css文件,可以通过媒体查询属性,将其拆分成为不同用途的css文件,这样只在特定的场景加载特定的css,从而减小下载css文件的内容