DOMContentLoaded
当初始的 HTML 文档被完全加载和解析完成之后,DOMContentLoaded 事件被触发,而无需等待样式表、图像和子框架的完成加载。另一个不同的事件 load 应该仅用于检测一个完全加载的页面。 在使用 DOMContentLoaded 更加合适的情况下使用 load 是一个令人难以置信的流行的错误,所以要谨慎。注意:DOMContentLoaded 事件必须等待script之前的样式表加载解析完成才会触发。
// 客户端 html 代码
<head>
<meta charset="UTF-8">
<title>Title</title>
<script>
document.addEventListener('DOMContentLoaded',function(){
console.log('3 seconds passed');
});
</script>
<link rel="stylesheet" href="http://localhost:3000/sleep">
</head>
<body>
<div><a >1234567</a></div>
</body>
// express 代码
var express = require('express');
var app = express();
app.get('/sleep', function(req, res, next) {
res.type('text/css')
setTimeout(()=>{
res.send("a {\n" +
" color: red;\n" +
"}")
},3000)
});
当script在上,link在下时:
控制台立即显示 3 seconds passed。 页面是空白的。 过三秒后,页面直接显示红字。
当link在上,script在下时:
页面是空白的。过三秒后,
控制台显示 3 seconds passed。
页面直接显示红字。
说明:css link会阻塞 DOM 渲染
- css link会阻塞 DOM 渲染,当css加载完成后才会渲染。
- DOMContentLoaded 事件只等待script之前的样式表
- 因为:CSS 会阻塞 JS 执行,JS 会阻塞 DOM解析,下面会代码说明
因为 DOM 的渲染需要 DOM 树和 CSSOM 树共同来生成渲染树,所以在 CSS 加载完成之前, DOM 是不会进行渲染的。还是用上面那个例子,我们会发现 CSS 没有加载完成时,页面上是不显示的,而当 CSS 加载完成的瞬间,标签就被渲染到页面上了。
当把css link放在body内,而不是head内又不同
像截图这种代码,link也在script标签后面。结果:页面先显示出div内容,过三秒后,字体颜色改变+控制台打印。
CSS 不会阻塞 DOM 解析
// html内的代码
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="http://localhost:3000/sleep">
<script defer src="js/test.js"></script>
</head>
<body>
<div><a >1234567</a></div>
</body>
// js/test.js
const div = document.querySelector('div');
console.log(div);
此时我们的 css 还没加载完成,但是我们可以看到 console 选项卡里面 div 标签已经被打印出来了,说明,此时 DOM 已经解析完成触发 DOMContentLoaded 事件。
CSS 会阻塞 JS 执行
<script>
var starttime = new Date().getTime();
console.log("page start" + starttime);
</script>
<link rel="stylesheet" href="http://localhost:3000/sleep">
<script src="js/test.js"></script>
// js/test.js
var endtime = new Date().getTime();
console.log("delay:" + (endtime - starttime));
sleep 3秒后才返回
JS 会阻塞 DOM解析
当解析起遇到 script 标签时,文档会立即停止解析直到脚本执行完毕,如果脚本是外部的,那么解析过程会停止,直到从网络同步抓取资源完成后再继续。因为我们的脚本会操作 DOM,所以在脚本跑完之前浏览器不知道脚本会把 DOM 改成什么样,所以就等脚本执行完再进行解析。
<script >
let ui = document.getElementById('ui')
ui.innerText = 'blue'
</script>
<div id="ui">test3</div>
从代码和图中看到,当把dom放在js下面,取不到元素,报错。
script 标签对 dom 渲染的影响
GUI 渲染线程与 JS 引擎线程是互斥的,当 JS 引擎执行时 GUI 线程会被挂起(相当于被冻结了),GUI 更新会被保存在一个队列中等到 JS 引擎空闲时立即被执行。
- 当遇到script标签为同步的形式,会立即执行js,执行完再渲染。
<div id="ui">test3</div>
<script >
var now = new Date().getTime();
let ui = document.getElementById('ui')
while (new Date().getTime() - now < 3000) {
continue;
}
ui.innerText = 'blue'
</script>
- 当遇到script标签为引用的形式,会立即把前面已经解析的部分渲染了。
<div id="ui">test3</div>
<script src="js/test.js"></script>
// js/test.js 内容与上面script内的内容一样
另一套测试代码
// 引入远程文件
<script src="http://localhost:3000/test.js"></script>
// express 服务端代码,2秒后再返回
app.get('/test.js', function(req, res, next) {
res.type('application/javascript')
setTimeout(()=>{
res.send(`alert()`)
},2000)
});
总结
浏览器执行 css dom js 在同步模式下,都是从上往下执行。 渲染等到 JS 引擎空闲时立即被执行。
CSS 会阻塞 JS 执行
JS 会阻塞 DOM解析
CSS 不会阻塞 DOM 解析
Css 会阻塞 DOM 渲染
引用script 会渲染部分已经解析过的dom,
包含内的形式会先执行js,渲染挂起。
主要原因还是script内代码有没有加载完,如果没有加载完就会先渲染一次,加载完就会先执行js,看js有没有改变dom,避免浪费渲染一次,很智能。
比起js的懒加载, css懒加载和初始加载最小体积会更加提高性能。
defer async
async 模式下,JS 不会阻塞浏览器做任何其它的事情。它的加载是异步的,当它加载结束,JS 脚本会立即执行。
defer 模式下,JS 的加载是异步的,执行是被推迟的。等整个文档解析完成、DOMContentLoaded 事件即将被触发时,被标记了 defer 的 JS 文件才会开始依次执行。
从应用的角度来说,一般当我们的脚本与 DOM 元素和其它脚本之间的依赖关系不强时,我们会选用 async;当脚本依赖于 DOM 元素和其它脚本的执行结果时,我们会选用 defer。
一句话,defer是“解析完dom再执行”,async是“下载完就执行”。另外,如果有多个defer脚本,会按照它们在页面出现的顺序加载,而多个async脚本是不能保证加载顺序的。
preload prefetch
preload
<link>
元素的 rel 属性的属性值preload能够让你在你的HTML页面中 <head>
元素内部书写一些声明式的资源获取请求,可以指明哪些资源是在页面加载完成后即刻需要的。对于这种即刻需要的资源,你可能希望在页面加载的生命周期的早期阶段就开始获取,在浏览器的主渲染机制介入前就进行预加载。这一机制使得资源可以更早的得到加载并可用,且更不易阻塞页面的初步渲染,进而提升性能。
- 只是预加载,并不运行。
- 用下面两种方法即可加载完运行
// 预加载Css
<link rel="preload" as="style" href="async_style.css" onload="this.rel='stylesheet'">
// 预加载js
<link rel="preload" as="script" href="async_script.js"
onload="var script = document.createElement('script');
script.src = this.href;
document.body.appendChild(script);">
prefetch
浏览器会查找关系类型(rel)为 next 或 prefetch 的 HTML 或 HTTP Link: header。例子:
<link rel="prefetch" href="/images/big.jpeg">
<link rel="prefetch alternate stylesheet" title="Designed for Mozilla" href="mozspecific.css">
<link rel="next" href="2.html">
已经被许多浏览器支持了相当长的时间,但它是意图预获取一些资源, 以备下一个导航/页面使用(比如,当你去到下一个页面时)。 这很好,但对当前的页面并没有什么助益。 此外,浏览器会给使用prefetch的资源一个相对较低的优先级——与使用preload的资源相比。毕竟,当前的页面比下一个页面相对更加重要。
preload > load > prefetch 优先级测试
<link rel="stylesheet" href="http://localhost:3000/sleep2.load" >
<link rel="preload" as="style" href="http://localhost:3000/sleep2.preload" >
<link rel="preload" as="style" href="http://localhost:3000/sleep2.preload1" >
<link rel="prefetch" as="style" href="http://localhost:3000/sleep2.prefetch" >
<link rel="preload" as="style" href="http://localhost:3000/sleep2.preload2" >
<link rel="preload" as="style" href="http://localhost:3000/sleep2.preload3" >
<link rel="preload" as="style" href="http://localhost:3000/sleep2.preload4" >
<link rel="preload" as="style" href="http://localhost:3000/sleep2.preload5" >
<link rel="preload" as="style" href="http://localhost:3000/sleep2.preload6" >
<link rel="preload" as="style" href="http://localhost:3000/sleep2.preload7" >
<link rel="stylesheet" href="http://localhost:3000/sleep.load" >
从下图看出,preload比正常load优先级大,会优先占用tcp链接,即使sleep2.load
写在最上面,也要等6条tcp占用释放一条。prefetch 会在空闲时再使用。
- preload > load > prefetch
- preload 不会阻塞 DOMContentLoaded
- preload 会让请求在dom解析之前发出去,在真实请求的时候 会复用preload的请求缓存