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

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

准备工作

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

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

├── 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

// app.js
const express = require('express')
const fs = require('fs')
const app = new express()
const port = 3000const sleepFun = time => {return new Promise(res => {setTimeout(() => {res()}, time)})
}const filter = (req, res, next) => {const { sleep } = req.query || 0if (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) returnres.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解析完成后触发。

<!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/访问此页面。

<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执行更多时间。

<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样式。

<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))。

// 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

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

<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

1. JavaScript 重温系列(22篇全)

2. ECMAScript 重温系列(10篇全)

3. JavaScript设计模式 重温系列(9篇全)

4. 正则 / 框架 / 算法等 重温系列(16篇全)

5. Webpack4 入门(上)|| Webpack4 入门(下)

6. MobX 入门(上) ||  MobX 入门(下)

7. 120+篇原创系列汇总

回复“加群”与大佬们一起交流学习~

点击“阅读原文”查看 120+ 篇原创文章

【Web技术】1005- 关于 JS 与 CSS 是否阻塞 DOM 的渲染和解析相关推荐

  1. js、css的阻塞问题

    js.css的阻塞问题 这篇文章主要是探索js.css的加载顺序及其影响问题. 下面的代码可以让浏览器阻塞: <!DOCTYPE html> <html lang="en& ...

  2. JS 和 CSS 是如何影响页面渲染的?

    为什么script脚本要放在body标签的最后面? 这是面试的时候经常遇到的问题,但是很少听到有人能完整的回答出来.其实这个问题并不简单,它涉及到浏览器渲染方面的知识,搞懂了这一块对就能对性能优化有一 ...

  3. 【Web技术】1374- 纯 JS 实现灵活的前端主题切换功能

    demo在线体验地址:https://hongqingcao.github.io/v-theme-colors/ 源码地址:https://github.com/HongqingCao/v-theme ...

  4. java js webservice_java Web技术探路:js Ajax调用WebService

    StuList = new List(); for (int i = 0; i < 10; i++) { Student s = new Student(); s.Sid = i + 1; s. ...

  5. Web技术基础学②——初步学习CSS

    初步学习CSS 目录 初步学习CSS 前言 ✍CSS如何使用? ✍CSS相关样式 ✎颜色.尺寸及对齐 ✎盒子模型 ✎边框与边距 ✎定位 ✎定位 ✎浮动 ✎不透明度 ✎组合选择器 ✎伪类和伪元素 ✍CS ...

  6. hbuilder新建web apk项目_【CUCS】Ionic利用你喜欢的(html css js) web技术创建跨平台的移动app...

    点击上方"CUCS" 可以订阅哦! Ionic 是一个强大的 HTML5 应用程序开发框架,号称 Advanced HTML5 Hybrid Mobile AppFramework ...

  7. java Web程序使用wro4j合并、压缩js、css等静态资源

    在Web项目中,js.css合并压缩,不仅有利于减少Http请求数量.减少宽带资源占用,还能有效的管理各种js.css的引入,使整个项目更加有序.而对于访问用户来说,其更大的好处是增加了页面的打开速度 ...

  8. js合并压缩 java_Java Web程序使用wro4j合并、压缩js、css等静态资源

    在Web项目中,js.css合并压缩,不仅有利于减少Http请求数量.减少宽带资源占用,还能有效的管理各种js.css的引入,使整个项目更加有序.而对于访问用户来说,其更大的好处是增加了页面的打开速度 ...

  9. java css js 合并_java Web程序使用wro4j合并、压缩js、css等静态资源

    在Web项目中,js.css合并压缩,不仅有利于减少Http请求数量.减少宽带资源占用,还能有效的管理各种js.css的引入,使整个项目更加有序.而对于访问用户来说,其更大的好处是增加了页面的打开速度 ...

最新文章

  1. 如何去判断一个面试者的深度学习水平?
  2. 管理敏捷迭代任务和任务协作
  3. mysql数据库 中文乱码_在CMD中操作mysql数据库出现中文乱码解决方案
  4. 我的KT库之-----扩展方法
  5. 浏览器复制不进行url编码_谷歌浏览器测试版支持在PC上复制号码并操作关联安卓设备进行拨打...
  6. 深入浅出解释深拷贝、浅拷贝、对象拷贝、引用拷贝原理和应用
  7. 如何在SAP S/4HANA Fiori UI上创建新的扩展字段
  8. Table accessed during One order advanced search
  9. 联想投资服务器5g芯片,从5G投票到要没必要做芯片,联想到了最危险的时候
  10. eslint 规则中文注释
  11. vue axios 简单封装以及思考
  12. JavaScript笔记集
  13. IPLATUI--下拉列表
  14. 当自己购买的基金已经超过自己的心里的收益后会选择继续还是退出?
  15. P1012 拼数(水题)
  16. Chrome OS 下载及安装教程
  17. 传智播客asp.net基础视频免费分享
  18. 计算机网络安全及故障谢辞,计算机网络安全初探.pdf
  19. 软件设计:“度”、“裁剪”与“变通”
  20. 邮政平邮批量查询未签收物流的方法

热门文章

  1. Windows下base64编解码命令
  2. 计算机第一性原理局限性,第一性原理分子动力学
  3. 微信小程序开发 image mode属性显示图片对应的格式详解
  4. 外贸新手需做哪些准备?如何开发客户?
  5. 【python机器学习:朴素贝叶斯分类算法】
  6. Python小白的数学建模课-09 微分方程模型
  7. 十问公务员(迄今为止我看到的最全面客观的评价)
  8. H3C 之 IP 存储方案学习
  9. kkFileView代码分析(四)——office文件的转换(1)office插件管理
  10. windows2003服务之网络负载平衡(NLB)