Loading [MathJax]/jax/input/TeX/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >【总结】关于 JS 与 CSS 是否阻塞 DOM 的渲染和解析

【总结】关于 JS 与 CSS 是否阻塞 DOM 的渲染和解析

作者头像
pingan8787
发布于 2021-09-09 07:30:09
发布于 2021-09-09 07:30:09
1.5K00
代码可运行
举报
文章被收录于专栏:前端自习课前端自习课
运行总次数:0
代码可运行

最近系统梳理HTML5所有涉及到的标签时,梳理至<link><script>标签时,碰巧想到一个困扰很久的问题,即一般把<script>放在<body>尾部,<link>标签放在<head>内部,而页面通过CDN引入第三方框架或库时,基本都是将其<script>标签放在<link>标签前面。

可能此方式已经成为了约定俗成,但是究竟其好处在哪里,或者说其它的方式为什么不可取,想必你也和我有同样的疑问,那就接着来往下看吧。

准备工作

首先需要做的准备工作是,搭建一个服务器,目的是为了返回css样式和js脚本,并且让服务器根据传递的参数,固定延时返回数据。

其目录结构如下,其中index.jsstyle.css就是用于返回的数据,app.js为服务器启动文件,index.html是用来测试案例的文件,剩余文件或文件夹可以忽略。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
├── static
│   ├── index.js
│   ├── style.css
├── app.js
├── index.html
├── package.json
├── node_modules/
复制代码

涉及的相关代码也贴一下吧,方便复制调试。有必要说明一下,本地运行node app.js启动后,浏览器输入http://127.0.0.1:3000/就能访问到index.html,而访问style.css可以输入http://127.0.0.1:3000/static/style.css?sleep=3000,其中sleep参数则可自由控制css文件延时返回,例如想要文件5s后返回就设置sleep=5000

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// app.js
const express = require('express')
const fs = require('fs')
const app = new express()
const port = 3000

const sleepFun = time => {
    return new Promise(res => {
        setTimeout(() => {
            res()
        }, time)
    })
}

const filter = (req, res, next) => {
    const { sleep } = req.query || 0

    if (sleep) {
        sleepFun(sleep).then(() => next())
    } else {
        next()
    }
}

app.use(filter)

app.use('/static/', express.static('./static/'))

app.get('/', function (req, res, next) {
    fs.readFile('./index.html', 'UTF-8', (err, data) => {
        if (err) return
        res.send(data)
    })
})

app.listen(port, () => {
    console.log(`app is running at http://127.0.0.1:${port}/`)
})

// static/index.js
var p = document.querySelector('p');
console.log(p);

// static/index.css
p { color: lightblue; }
复制代码

接着就是index.html的准备工作,其中HTML部分的架子就长下面那样,然后你只需要记住DOMContentLoaded事件将在页面DOM解析完成后触发。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<!DOCTYPE html>
<html lang="zh-CN">

<head>
    <script>
        document.addEventListener('DOMContentLoaded', () => {
            var p = document.querySelector('p')
            console.log(p)
        })
    </script>
</head>

<body>
    <p>hello world</p>
</body>

</html>
复制代码

CSS 不会阻塞 DOM 解析,但是会阻塞 DOM 渲染

首先在index.html插入如下<link>标签,然后在浏览器输入http://127.0.0.1:3000/访问此页面。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<head>
    <script>
        document.addEventListener('DOMContentLoaded', () => {
            var p = document.querySelector('p')
            console.log(p)
        })
    </script>
    <link rel="stylesheet" href="./static/style.css?sleep=3000">
</head>

<body>
    <p>hello world</p>
</body>
复制代码

页面初始显示为空白,控制台打印出了p元素,同时浏览器标签页上加载loading3s后页面显示出浅蓝色的hello world

在这里插入图片描述

以上情况也就说明,CSS不会阻塞DOM的解析,如果说CSS阻塞DOM解析的话,那么p标签不会被解析,进而DOM不会被解析完成,CSS请求过程中也不可能会触发DOMContentLoaded事件。而且在css请求过程中,控制台立即打印出了p元素,由此也验证了此结论的正确性。

另一个情况就是,虽然DOM很早就被解析完成,但是p标签却迟迟没有渲染,原因在于CSS样式还未请求完成,在样式获取后hello world才被渲染出来,所以说CSS会阻塞页面渲染。

简单阐述一下浏览器的解析渲染过程,解析DOM生成DOM Tree,解析CSS生成CSSOM Tree,两者结合生成render tree渲染树,最后浏览器根据渲染树渲染至页面。由此可以看出DOM Tree的解析和CSSOM Tree的解析是互不影响的,两者是并行的。因此CSS不会阻塞页面DOM的解析,但是由于render tree的生成是依赖DOM TreeCSSOM Tree的,因此CSS必然会阻塞DOM的渲染。

更为严谨一点的说,CSS会阻塞render tree的生成,进而会阻塞DOM的渲染。

JS 会阻塞 DOM 解析

为了避免加载CSS造成的干扰,如下仅关注JS的执行情况,其中for循环的循环体中逻辑暂不考虑,仅仅是让JS执行更多时间。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<head>
    <script>
        document.addEventListener('DOMContentLoaded', () => {
            var p = document.querySelector('p')
            console.log(p)
        })
    </script>
</head>

<body>
    <script>
        const p = document.querySelector('p')
        console.log(p)
    
        for (var i = 0, arr = []; i < 100000000; i++) {
            arr.push(i)
        }
    </script>
    <p>hello world</p>
</body>
复制代码

浏览器访问页面,初始时为空白且控制台打印null,浏览器loading短暂延时后,控制台打印出p标签同时页面渲染出hello world

在这里插入图片描述

以上情况很容易说明JS会阻塞DOM解析了,JS执行初控制台打印null,因为此时p标签还未被解析,for循环执行时,可以明显感觉到执行耗时,执行完成p标签被解析,此时触发DOMContentLoaded事件,控制台打印出p标签,同时页面渲染出hello world

比较合理的解释就是,首先浏览器无法知晓JS的具体内容,倘若先解析DOM,万一JS内部全部删除掉DOM,那么浏览器就白忙活了,所以就干脆暂停解析DOM,等到JS执行完成再继续解析。

CSS 会阻塞 JS 的执行

如下在页内JS脚本前插入<link>标签,并且延时3s获取CSS样式。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<head>
    <script>
        document.addEventListener('DOMContentLoaded', () => {
            var p = document.querySelector('p')
            console.log(p)
        })
    </script>
    <link rel="stylesheet" href="./static/style.css?sleep=3000">
    <script src="./static/index.js"></script>
</head>

<body>
    <p>hello world</p>
</body>
复制代码

初始页面空白,浏览器loading加载3s后,控制台打印出null,紧接着打印出p标签,同时页面渲染出浅蓝色p标签。

在这里插入图片描述

此情况好像是CSS不仅阻塞了DOM的解析,而且也阻塞了DOM渲染。

但是首先要思考下是什么阻塞了DOM的解析,刚刚已经证明了CSS不会阻塞DOM的解析,所以只可能是JS阻塞了DOM解析。但是JS只有两行代码,不会阻塞长达3s左右的时间。所以只有一个可能就是CSS会阻塞JS的执行。

因此输出结果也能大致分析出来了,首先解析到第一个<script>标签,document绑定上DOMContentLoaded事件,紧接着解析到link标签,浏览器请求CSS样式,由于CSS不会阻塞DOM解析,因此浏览器继续向下解析,发现第二个<script>标签,浏览器请求JS脚本,此时JS获取完成,但是由于CSS还在获取,所以不能立即执行。

而第二个<script>不能立即执行,导致它后面的p标签也没办法解析,原因则是JS会阻塞DOM解析。只有等待到CSS样式获取成功后,此时JS立即执行,控制台输出null,然后浏览器继续解析到p标签,解析完成,DOMContentLoaded事件触发,控制台输出p标签,最后浅蓝色hello world渲染至页面。

其实这样做也是有道理的,设想JS脚本中的内容是获取DOM元素的CSS样式属性,如果JS想要获取到DOM最新的正确的样式,势必需要所有的CSS加载完成,否则获取的样式可能是错误或者不是最新的。因此要等到JS脚本前面的CSS加载完成,JS才能再执行,并且不管JS脚本中是否获取DOM元素的样式,浏览器都要这样做。

回溯文章开头的那个疑问,所以一般将<script>放在<link>标签前面是有道理的。

JS 会触发页面渲染

如下CSS采用页内方式,其中颜色名及其rgb值分别为浅绿色lightbluergb(144, 238, 144))、粉色pinkrgb(255, 192, 203))。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// index.html
<head>
    <style>
        p {
            color: lightgreen;
        }
    </style>
</head>

<body>
    <p>hello</p>
    <script src="./static/index.js?sleep=2000"></script>
    <p>beautiful</p>
    <style>
        p {
            color: pink;
        }
    </style>
    <script src="./static/index.js?sleep=4000"></script>
    <p>world</p>
    <style>
        p {
            color: lightblue;
        }
    </style>
</body>

// static/index.js
var p = document.querySelector('p');
var style = window.getComputedStyle(p, null);
console.log(style.color);
复制代码

页面初始渲染出浅绿色hello,紧接着2s后渲染出粉色hello beautiful且控制台打印rgb(144, 238, 144),然后又2s后渲染出浅蓝色hello beautiful world且控制台打印rgb(255, 192, 203)

在这里插入图片描述

上述结果大致分析为浏览器首先解析第一个<style>标签和hello文本的p标签,此时继续向下解析发现了第一个<script>标签,紧接着触发一次渲染,由于此过程非常快所以页面初始就能看到浅绿色hello

然后浏览器发出JS请求,2sJS获取完成立即运行控制台输出rgb(144, 238, 144)JS运行完成后浏览器继续向下解析到beautiful文本的p标签和第二个<style>标签,再继续向下解析发现了第二个<script>标签,触发一次渲染,这个过程也是非常快,所以可以看到控制台输出结果和渲染粉色hello beautiful几乎是同时的。

解析到第二个<script>标签时,浏览器不会发出请求(稍作解释),2s后获取到JS脚本并执行,控制台输出rgb(255, 192, 203),紧接着浏览器继续向下解析到world文本的p标签和第三个<style>标签,此时DOM解析完成,再进行正常的渲染,这个过程也是非常快,所以也能看到控制台输出结果和渲染浅蓝色hello beautiful world几乎是同时的。

现在来解答刚才那个问题,浏览器解析DOM时,虽然会一行一行向下解析,但是它会预先加载具有引用标记的外部资源(例如带有src标记的<script>标签),而在解析到此标签时,则无需再去加载,直接运行,以此提高运行效率。所以就会有上述两个输出结果间隔2s的情况,而不是4s,因为浏览器预先就一起加载了两个<script>脚本,第一个<script>脚本加载完成时,第二个<script>脚本还剩大概2s加载完成。

而这个结论才是解释为何CSS会阻塞JS的执行的真正原因,浏览器无法预先知道脚本的具体内容,因此在碰到<script>标签时,只好先渲染一次页面,确保<script>脚本内能获取到DOM的最新的样式。倘若在决定渲染页面时,还有尚未加载完成的CSS样式,只能等待其加载完成再去渲染页面。

Body 内的 CSS

来看一个较为特殊的情况。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<head>
    <script>
        document.addEventListener('DOMContentLoaded', () => {
            var p = document.querySelector('p')
            console.log(p)
        })
    </script>
</head>

<body>
    <p>hello</p>
    <link rel="stylesheet" href="./static/style.css?sleep=3000">
    <p>world</p>
</body>
复制代码

按照上述的所有结论,预先分析一下运行结果,首先浏览器解析<script>脚本,document上绑定了DOMContentLoaded事件,紧接着浏览器继续向下解析,发现了文本为hellop标签和<link>标签,浏览器发起CSS请求,由于CSS不会阻塞DOM解析,浏览器继续向下解析至文本为worldp标签,此时页面解析完成,DOMContentLoaded事件触发控制台输出p标签,3s后页面渲染出浅蓝色hello world

因此按照分析,初始时页面空白,浏览器loading加载3s后,控制台打印出p标签,同时页面渲染出浅蓝色hello world

但是实际结果并不是这样,而是页面初始就渲染出hello,3s后页面渲染出浅蓝色hello world并且打印p标签。

在这里插入图片描述

如下是我个人的分析和理解,首先是浏览器解析并运行<script>标签,然后在解析文本为hellop标签,当解析到<link>标签时,触发一次渲染,然后浏览器发起CSS请求,但是此时浏览器不会继续向下解析,而是将<link>标签当做是DOM的一部分,换句话说浏览器将其认为是特殊的DOM元素,这个DOM元素的特殊性就在于需要进行加载,因此浏览器不会继续向下解析,所以也就没有DOMContentLoaded的输出结果。

3s<link>这个特殊的DOM元素解析完成,浏览器继续向下解析world文本的p标签,此时触发DOMContentLoaded事件,再进行正常的渲染,页面渲染出浅蓝色hello world,由于此过程非常快,所以控制台输出和渲染浅蓝色hello world几乎是同时的。

上述仅仅是我个人的分析和猜测,可以不必理会,仅作为讨论,所以也不敢妄下结论,误人子弟,此小节仅走马观花即可。

综上所述

综合上述所有情况,可以得出如下结论。

  • CSS不会阻塞DOM解析,但是会阻塞DOM渲染,严谨一点则是CSS会阻塞render tree的生成,进而会阻塞DOM的渲染
  • JS会阻塞DOM解析
  • CSS会阻塞JS的执行
  • 浏览器遇到<script>标签且没有deferasync属性时会触发页面渲染
  • Body内部的外链CSS较为特殊,请慎用

关于本文

来源:Don_GW

https://juejin.cn/post/6973949865130885157

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-08-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 前端自习课 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
浏览器加载解析渲染机制的全面解析
(注1:如果有问题欢迎留言探讨,一起学习!转载请注明出处,喜欢可以点个赞哦!) (注2:更多内容请查看我的目录。)
love丁酥酥
2018/08/27
1.2K0
浏览器加载解析渲染机制的全面解析
一次useEffect引发浏览器执行机制的思考
我相信大家首先想到的思路就是在useEffect中通过getBoundingClientRect()获得对应传入元素(id)的位置,然后通过定位增加一个类似的弹窗效果。
19组清风
2021/11/15
9940
一次useEffect引发浏览器执行机制的思考
穷追猛打,阿里二面问了我30分钟从URL输入到渲染...
同样的问题,可以拿来招聘P5也可以是P7,只是深度不同。所以我重新整理了一遍整个流程,本文较长,建议先收藏。
用户9899350
2022/07/29
6270
穷追猛打,阿里二面问了我30分钟从URL输入到渲染...
<script> 脚本以及 <link> 标签对 DOM 解析渲染的影响
当HTML解析器解析HTML,如果遇到script标签,普通的script标签会暂停对DOM解析渲染,因为该脚本可能会修改DOM。
不叫猫先生
2023/11/04
7100
<script> 脚本以及 <link> 标签对 DOM 解析渲染的影响
前端魔法堂:解秘FOUC
前言  对于问题多多的IE678,FOUC(flash of unstyled content)——浏览器样式闪烁是一个不可忽视的话题,但对于ever green的浏览器就不用理会了吗?下面尝试较全面地解密FOUC。 到底什么是FOUC?  页面加载解析时,页面以样式A渲染;当页面加载解析完成后,页面突然以样式B渲染,导致出现页面样式闪烁。  样式A,浏览器默认样式 或 浏览器默认样式 层叠 部分已加载的页面样式;  样式B,浏览器默认样式 叠加 全部页面样式。 为什么会出现FOUC  我们了解当输入网
^_^肥仔John
2018/01/18
1.5K0
css加载会造成阻塞吗
可能大家都知道,js执行会阻塞DOM树的解析和渲染,那么css加载会阻塞DOM树的解析和渲染吗?接下来,我就来对css加载对DOM树的解析和渲染的影响做一个测试。
嘿嘿嘿
2018/12/11
4.5K0
css加载会造成阻塞吗
CSS到底会不会阻塞页面渲染
可能大家都知道,js执行会阻塞DOM树的解析和渲染,那么css加载会阻塞DOM树的解析和渲染吗?接下来,我们就一起来分析一下。
前端逗逗飞
2021/04/30
5K0
CSS到底会不会阻塞页面渲染
浏览器渲染页面与DOM相关常见的面试题以及问题
  1.DOM Tree:浏览器将HTML解析成树形的数据结构,构建一颗DOM树,同时进行第三步。
用户10106350
2022/10/28
1.3K0
浏览器渲染页面与DOM相关常见的面试题以及问题
domReady的理解
domReady是名为DOMContentLoaded事件的别称,当初始的HTML文档被完全加载和解析完成之后,DOMContentLoaded事件被触发,而无需等待样式表、图像和子框架的完全加载。
WindRunnerMax
2020/09/24
1.1K0
浏览器渲染原理及流程
大多数设备的刷新频率是60Hz,也就说是浏览器对每一帧画面的渲染工作要在16ms内完成,超出这个时间,页面的渲染就会出现卡顿现象,影响用户体验。前端的用户体验给了前端直观的印象,因此对B/S架构的开发人员来说,熟悉浏览器的内部执行原理显得尤为重要。
前端迷
2019/08/29
4.7K0
浏览器渲染原理及流程
浏览器原理0. 前言1. 解析过程2. 渲染树2.1 CSS样式计算2.2 构建渲染树3. 布局(重要)4. 重绘与重排(重要)5. paint(绘制)6. composite(重要)7. 浏览器加载
身为前端,打交道最多的就是浏览器和node了,也是我们必须熟悉的。接下来我们讲一下浏览器工作原理和工作过程。从url到页面的过程,......,我们直接来到收到服务器返回内容部分开始。
lhyt
2018/10/31
5.4K0
JS完美收官之——js加载时间线
浏览器在开始运行一个页面的时候,首先它会初始化js功能,当js发挥它的功能时候,记录了一系列浏览器按照顺序做的事情,也就是一个执行顺序,谁在谁之前发生,谁在谁之后发生。
程序员法医
2022/08/11
1.4K0
JS完美收官之——js加载时间线
前端资源浏览器渲染原理
当有了DOM Tree和 CSSOM Tree后,就可以两个结合来构建Render Tree了
冷环渊
2023/02/26
6040
前端资源浏览器渲染原理
Web页面全链路性能优化指南
性能优化不单指优化一个页面的打开速度,在开发环境将一个项目的启动时间缩短使开发体验更好也属于性能优化,大文件上传时为其添加分片上传、断点续传也属于性能优化。在项目开发以及用户使用的过程中,能够让任何一个链路快一点,都可以被叫做性能优化。
唐志远
2022/10/27
1.9K0
Web页面全链路性能优化指南
HTML解析之DOMContentLoaded和onload
在很久很久以前,我在封装自己的JQuery库时就使用过DOMContentLoaded,觉得这个知识点看看别的文章就行了,不过现在我想把它记下来。
全栈程序员站长
2022/11/16
1.7K0
HTML解析之DOMContentLoaded和onload
翻译 | 关键CSS和Webpack: 减少阻塞渲染的CSS的自动化解决方案
摘要总结:本文介绍了一种用于Web应用程序的基于关键CSS和Webpack的代码拆分和优化方法。该方法通过将关键CSS与Webpack生成的非关键CSS进行拆分,在提高页面加载速度的同时,保留对关键CSS的访问权限,适用于对性能要求较高的Web应用程序,同时具有较好的可维护性。
iKcamp
2018/01/04
2.1K0
翻译 | 关键CSS和Webpack: 减少阻塞渲染的CSS的自动化解决方案
【Webpack】867- Webpack 优化阻塞的 CSS
随着浏览器的日新月异,网页的性能和速度越来越好,并且对于用户体验来说也越来越重要。
pingan8787
2021/02/26
1.3K0
【Webpack】867- Webpack 优化阻塞的 CSS
画了20张图,详解浏览器渲染引擎工作原理
通常,我们编写的HTML、CSS、JavaScript等文件,经过浏览器运行之后就会显示出页面,那他们是如何转化为页面的?这背后的原理是什么?这个过程就是浏览器的渲染进程来操作实现的。浏览器的渲染进程的主要任务就是「将静态资源转化为可视化界面:」
HoMeTown
2022/10/26
2.8K0
画了20张图,详解浏览器渲染引擎工作原理
探究网页资源究竟是如何阻塞浏览器加载的
一个页面允许加载的外部资源有很多,常见的有脚本、样式、字体、图片和视频等,对于这些外部资源究竟是如何影响整个页面的加载和渲染的呢?今天我们来一探究竟。
用户4456933
2021/06/01
2.2K0
探究网页资源究竟是如何阻塞浏览器加载的
js执行会阻塞DOM树的解析和渲染,那么css加载会阻塞DOM树的解析和渲染吗
为了完成本次测试,先来科普一下,如何利用chrome来设置下载速度(会用的可直接跳过) 1.打开chrome控制台(按下F12),可以看到下图,重点在我画红圈的地方
yuezhongbao
2019/02/26
2.4K0
js执行会阻塞DOM树的解析和渲染,那么css加载会阻塞DOM树的解析和渲染吗
推荐阅读
相关推荐
浏览器加载解析渲染机制的全面解析
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验