最近系统梳理HTML5
所有涉及到的标签时,梳理至<link>
和<script>
标签时,碰巧想到一个困扰很久的问题,即一般把<script>
放在<body>
尾部,<link>
标签放在<head>
内部,而页面通过CDN
引入第三方框架或库时,基本都是将其<script>
标签放在<link>
标签前面。
可能此方式已经成为了约定俗成,但是究竟其好处在哪里,或者说其它的方式为什么不可取,想必你也和我有同样的疑问,那就接着来往下看吧。
首先需要做的准备工作是,搭建一个服务器,目的是为了返回css
样式和js
脚本,并且让服务器根据传递的参数,固定延时返回数据。
其目录结构如下,其中index.js
和style.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 = 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
解析完成后触发。
<!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>
复制代码
首先在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
元素,同时浏览器标签页上加载loading
,3s
后页面显示出浅蓝色的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 Tree
和CSSOM Tree
的,因此CSS
必然会阻塞DOM
的渲染。
更为严谨一点的说,CSS
会阻塞render tree
的生成,进而会阻塞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
执行完成再继续解析。
如下在页内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>
标签前面是有道理的。
如下CSS
采用页内方式,其中颜色名及其rgb
值分别为浅绿色lightblue
(rgb(144, 238, 144)
)、粉色pink
(rgb(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
请求,2s
后JS
获取完成立即运行控制台输出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
样式,只能等待其加载完成再去渲染页面。
来看一个较为特殊的情况。
<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
事件,紧接着浏览器继续向下解析,发现了文本为hello
的p
标签和<link>
标签,浏览器发起CSS
请求,由于CSS
不会阻塞DOM
解析,浏览器继续向下解析至文本为world
的p
标签,此时页面解析完成,DOMContentLoaded
事件触发控制台输出p
标签,3s
后页面渲染出浅蓝色hello world
。
因此按照分析,初始时页面空白,浏览器loading
加载3s
后,控制台打印出p
标签,同时页面渲染出浅蓝色hello world
。
但是实际结果并不是这样,而是页面初始就渲染出hello
,3s
后页面渲染出浅蓝色hello world
并且打印p
标签。
在这里插入图片描述
如下是我个人的分析和理解,首先是浏览器解析并运行<script>
标签,然后在解析文本为hello
的p
标签,当解析到<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>
标签且没有defer
或async
属性时会触发页面渲染Body
内部的外链CSS
较为特殊,请慎用本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/c0LpjBVlrwXg3eEQy1iYlw
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。