DOM解析和渲染与CSS、JS之间的关系

3,649 阅读6分钟

每个前端在第一次写一个完整功能的页面,都可能会是这个样子滴~

<html>
  <head>
    <link rel="stylesheet" href="test.css">
  </head>
  <body>
    <div id="app"></div>
    
    <script src="test.js"></script>
  </body>
</html>

我们都会被告知,css要放到head里面,js要放到body尾部前面。当然都能说出一二,但是我们还是有必要了解一下到底是为啥。

这里面有涉及到 DOM,CSS,JS 互相之间的一些关系,接下来会分别介绍

DOM

DOM这里有两个概念,解析渲染

DOM解析:就是把你所写的各种html标签,生成一个DOM TREE,可以认为就是生成了一个最原始的页面,一点样式都没有,毫无CSS修饰的那种;

DOM渲染:浏览器会把本身默认的样式+用户自己写得样式整合到一起,形成一个CSS TREE,而DOM渲染就是指DOM TREE 和 CSS TREE 结合到一起,生成一个Render TREE,呈现出一个带有样式的页面。

线程

浏览器会有不同的线程,比如说

  1. GUI 渲染线程

  2. JS 线程

  3. 定时器触发线程 (setTimeout)

  4. 浏览器事件线程 (onclick)

  5. http 异步线程

    ...

具体有关线程的内容,我会单独写一篇文章介绍,在这里我们只需要知道两点:

  1. DOM的渲染对应的就是GUI渲染进程;JS的执行对应的就是JS线程;所以,DOM的渲染与JS代码的运行,在同一瞬间只能有一个执行!
  2. 其他几个线程,用来辅助JS线程,不互斥,但是不是太影响JS线程和GUI线程两个大佬,不是主角

阻塞

阻塞XXX是指让XXX暂停了。比如JS的执行阻塞DOM解析,就是

DOM解析 --> JS执行(此时DOM解析暂停) --> JS执行完毕 --> DOM继续解析

DOM与CSS

先看它俩之间的关系,也就是分析CSS的加载对DOM的解析和渲染的影响。

很明显,DOM自己在那解析DOM TREE 和 css样式有啥关系啊,所以css不影响DOM解析。

也很明显,DOM渲染就是要生成样式呢,肯定和css有关系啊,所以css影响DOM渲染。

结论:

  1. css的加载不会阻塞DOM的解析
  2. css的加载阻塞DOM的渲染

DOM与JS

JS(加载和执行) 都会阻塞 DOM 的解析,因为JS中可能会对DOM进行操作,可能改变DOM的结构,所以JS的加载和执行是会阻塞DOM解析的。

JS(加载和执行) 都会阻塞 DOM 的渲染,同上面一样,因为JS中可能对样式进行操作。

注: html中每遇到< script >标签,页面就会重新渲染一次,因为要保证标签中的JS代码拿到的都是最新的样式。

结论:

  1. JS的加载和执行会阻塞DOM的解析
  2. JS的加载和执行会阻塞DOM的渲染

CSS与JS

在线程那里说了,CSS的加载会阻塞JS的执行,因为CSS的渲染GUI线程和JS运行线程互斥。 但是CSS不会阻塞JS的加载(浏览器可以预先扫描并下载)

注1:

CSS、JS之间的加载是否阻塞,这里不考虑,因为现代的浏览器都会预先偷看文档,并且下载。

注2:

这里的JS引入方式不包括async和defer

结论:

CSS的加载阻塞JS的运行,不阻塞JS的加载

三者

例1:

<header>
    <link href="test.css">
</header>

加载test.css是不会影响header的解析的,只影响渲染

例2:

<header>
    <link href="test.css">
    <script src="test.js"></script>
</header>

加载test.css阻塞了test.js的运行,test.js的运行阻塞了header的解析和渲染,所以,看似test.css既阻塞了header的渲染,又阻塞了header的解析。

总结

  1. css的加载不会阻塞DOM的解析
  2. css的加载阻塞DOM的渲染
  3. JS的加载和执行会阻塞DOM的解析
  4. JS的加载和执行会阻塞DOM的渲染
  5. CSS的加载阻塞JS的运行,不阻塞JS的加载
  6. CSS的加载与JS的加载之间是否有影响?不考虑,浏览器自身会偷看并预先下载
  7. 遇到script标签会触发渲染,以便获得最新的样式给JS代码

注意: 有些文章可能会提到,css的加载会阻塞DOM的解析,那是因为,如果不存在js的话,不阻塞;如果存在js的话,css的加载阻塞js的运行,js的运行阻塞了DOM的解析,所以css间接的影响了DOM(所以通常会把css放在头部,js放在body尾)

~~

补充

defer 和 asyc

因为上面介绍的都是同步的JS代码,所以当 script 标签中带有 defer 和 async ,要区分

首先,这两个都只能用于 script 外链文件的形式,不能把代码写到标签里

<script async>console.log("1")</script>
<script defer>console.log("2")</script>

上面两种形式是无效的,还是同步执行代码

先放个熟悉的图~

蓝色线代表网络读取,红色线代表执行时间,这俩都是针对脚本的;绿色线代表 HTML 解析。

defer

<script src="1.js" defer></script>
<script src="2.js" defer></script>
<script src="3.js" defer></script>

defer 属性表示延迟执行引入 JavaScript,即 JavaScript 加载时 HTML 并未停止解析,这两个过程是并行的。

整个 document 解析完毕且 defer-script 也加载完成之后(这两件事情的顺序无关),会执行所有由 defer-script 加载的 JavaScript 代码,再触发 DOMContentLoaded 事件。

defer 不会改变 script 中代码的执行顺序,示例代码会按照 1、2、3 文件 的顺序执行。

所以,defer 与相比普通 script,有两点区别:

  • 载入 JavaScript 文件时不阻塞 HTML 的解析
  • 执行阶段被放到 HTML 标签解析完成之后。

async

async 属性表示异步执行引入的 JavaScript,与 defer 的区别在于,如果已经加载好,就会开始执行,无论此刻是 HTML 解析阶段还是 DOMContentLoaded 触发(HTML解析完成事件)之后。需要注意的是,这种方式加载的 JavaScript 依然会阻塞 load 事件。换句话说,async-script 可能在 DOMContentLoaded 触发之前或之后执行,但一定在 load 触发之前执行。

从上一段也能推出,多个 async-script 的执行顺序是不确定的,谁先加载完谁执行。值得注意的是,向 document 动态添加 script 标签时,async 属性默认是 true。

document.createElement

使用 document.createElement 创建的 script 默认是异步的,示例如下。

console.log(document.createElement("script").async); // true

所以,通过动态添加 script 标签引入 JavaScript 文件默认是不会阻塞页面的。如果想同步执行,需要将 async 属性人为设置为 false。

如果使用 document.createElement 创建 link 标签会怎样呢?

const style = document.createElement("link");
style.rel = "stylesheet";
style.href = "index.css";
document.head.appendChild(style); // 阻塞?

其实这只能通过试验确定,已知的是,Chrome 中已经不会阻塞渲染,Firefox、IE 在以前是阻塞的,现在会怎样目前不太清楚。

转载请注明出处,谢谢~~