DOMContentLoaded 和 load
咱们先理解两个事件,有助于前面的剖析。
load 事件:load 应该仅用于检测一个齐全加载的页面 当一个资源及其依赖资源已实现加载时,将触发 load 事件。也就是说,页面的 html、css、js、图片等资源都曾经加载完之后才会触发 load 事件。
DOMContentLoaded 事件:当初始的 HTML 文档被齐全加载和解析实现之后,DOMContentLoaded 事件被触发,而无需期待样式表、图像和子框架的实现加载。也就是说,DOM 树曾经构建结束就会触发 DOMContentLoaded 事件。
js 阻塞了什么
因为 js 在执行的过程中可能会操作 DOM,产生回流和重绘,所以 GUI 渲染线程与 JS 引擎线程是互斥的。
在解析 HTML 过程中,如果遇到 script 标签,渲染线程会暂停渲染过程,将控制权交给 JS 引擎。内联的 js 代码会间接执行,如果是 js 内部文件,则要下载该 js 文件,下载实现之后再执行。等 JS 引擎运行结束,浏览器又会把控制权还给渲染线程,持续 DOM 的解析。
因而,js 会阻塞 DOM 树的构建。
那么,是否会阻塞页面的显示呢?咱们用上面的代码来测试一下。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div>hello world</div>
<script>
debugger
</script>
<div>hello world2</div>
</body>
</html>
能够看到,这个页面的 DOMContentLoaded 产生在 2.23s,可见 js 阻塞了 DOM 树的构建。然而,页面上却简直在一瞬间显示了hello world
,阐明 js 不会阻塞位于它之前的 dom 元素的渲染。
古代浏览器为了更好的用户体验,渲染引擎将尝试尽快在屏幕上显示的内容。它不会等到所有 DOM 解析实现后才布局渲染树。而是当 js 阻塞产生时,会将曾经构建好的 DOM 元素渲染到屏幕上,缩小白屏的工夫。
这也是为什么咱们会将 script 标签放到 body 标签的底部,因为这样就不会影响后面的页面的渲染。
css 阻塞了什么
当咱们解析 HTML 时遇到 link 标签或者 style 标签时,就会计算款式,构建 CSSOM。
css 不会阻塞 dom 树的构建,然而会阻塞页面的显示。咱们仍然用一个例子来测试:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" type="text/css" href="https://h5.sinaimg.cn/m/weibo-pro/css/chunk-vendors.d6cac585.css">
</head>
<body>
<div class="woo-spinner-filled">hello world</div>
<div>hello world2</div>
</body>
</html>
应用一个内部 css 文件,关上 Slow 3G
模仿比较慢的网速,能够看到,DOMContentLoaded 事件触发只用了 30ms,页面此时仍然是空白,而简直是 loaded 事件 2.92s 产生时,页面才呈现内容。
起因是,浏览器在构建 CSSOM 的过程中,不会渲染任何已解决的内容。即使 DOM 曾经解析结束了,只有 CSSOM 不没构建好,页面也不会显示内容。
只有当咱们遇到 link 标签或者 style 标签时,才会构建 CSSOM,所以如果 link 标签之前有 dom 元素,当加载 css 产生阻塞时,浏览器会将后面曾经构建好的 DOM 元素渲染到屏幕上,以缩小白屏的工夫。比方上面这样:
<body>
<div class="woo-spinner-filled">hello world</div>
<link rel="stylesheet" type="text/css" href="https://h5.sinaimg.cn/m/weibo-pro/css/chunk-vendors.d6cac585.css">
<div>hello world2</div>
</body>
这样做会导致一个问题,就是页面闪动,在 css 被加载之前,浏览器依照默认款式渲染 <div class="woo-spinner-filled">hello world</div>
,当 css 加载实现,会为该 div 计算新的款式,从新渲染,呈现闪动的成果。
为了防止页面闪动,通常 link 标签都放在 head 中。
css 会不会阻塞前面 js 执行?答案是会!
JS 的作用在于批改,它帮忙咱们批改网页的方方面面:内容、款式以及它如何响应用户交互。这“方方面面”的批改,实质上都是对 DOM 和 CSSDOM 进行批改。当在 JS 中拜访了 CSSDOM 中某个元素的款式,那么这时候就须要期待这个款式被下载实现能力持续往下执行 JS 脚本。
运行上面这个例子,就会发现等 css 加载实现后,才会在控制台打印“this is a test”。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" type="text/css" href="https://h5.sinaimg.cn/m/weibo-pro/css/chunk-vendors.d6cac585.css">
</head>
<body>
<div class="woo-spinner-filled">hello world</div>
<div>hello world2</div>
<script>
console.log('this is a test')
</script>
</body>
</html>
优化
- 应用内联 JavaScript 和 CSS,这样获取到 HTML 文件之后就能够间接开始渲染流程了。
- 并不是所有的场合都适宜内联,那么还能够尽量减少文件大小,比方通过 webpack 等构建工具删除无用代码、压缩 css、JavaScript 文件的体积;并且启用 CDN 放慢文件的下载速度。
- 对于大的 CSS 文件,能够通过媒体查问属性,将其拆分为多个不同用处的 CSS 文件,这样只有在特定的场景下才会加载特定的 CSS 文件。
-
如果 JavaScript 文件中没有操作 DOM 相干代码,就能够将该 JavaScript 脚本设置为异步加载,通过 async 或 defer 来标记代码。
<script src="index.js"></script> // 浏览器必须期待 index.js 加载和执行结束能力去做其它事件。<script async src="index.js"></script> //index.js 的加载是异步的,加载时不会阻塞浏览器做任何其它的事件。// 当它加载完结,JS 脚本会立刻执行。<script defer src="index.js"></script> //JS 的加载是异步的,执行是被推延的。// 应用了 defer 标记的脚本文件,会等整个文档解析实现,在 DOMContentLoaded 事件触发之前执行