一个问题
为什么推荐把 <script>
都放在 <body>
底部?
- 防止加载资源而导致的长时间的白屏。
-
js
只能获取在<script>
以上的dom
。 - ...
正文
document.readyState
该属性描述了文档的加载状态,发生变化时,会在 document
对象上触发 readystatechange
事件。有3种状态:
-
loading
正在加载 -
interactive
文档已被解析,loading
状态结束,但是诸如图像,样式表和框架之类的子资源仍在加载,会在document
和window
对象上触发DOMContentLoaded
事件。 -
complete
所有资源完成加载,会在window
对象上触发load
事件。
浏览器页面渲染过程
- 浏览器与服务器建立
TCP
连接发送HTTP
请求,获取HTML
文档并开始从上到下解析,构建DOM
。 -
在构建
DOM
过程中:- 如果遇到外联的
css
文件,下载文件并执行构建CSSOM
,此过程不影响DOM
构建,但在完成之前会阻止页面渲染。 -
如果遇到外联的
js
文件,则暂停构建DOM
,- 若在这之前的
css
文件已加载完毕且CSSOM
构建完成,则合并已经构建好的DOM
与CSSOM
并渲染到页面上 - 之后等
js
文件下载并执行后,然后继续构建后边的DOM
。
- 若在这之前的
- 如果遇到外联的
- 完成文档解析后,将
DOM
和CSSOM
进行关联和映射,生成Render Tree
渲染页面。 - 当所有同步的
js
代码执行完毕后,会在document
和window
对象上触发DOMContentLoaded
事件,此时对应document.readyState === 'interactive'
。 - 当所有资源完成加载后,会在
window
对象上触发load
事件,此时对应document.readyState === 'complete'
。
在这个过程中,js
文件的下载和执行会阻塞文档的解析,这也就是为什么推荐把 <script>
都放在 <body>
底部,下文中还将介绍更优雅的实现方式。
小结
-
DOMContentLoaded
事件在html
文档加载完毕,并且html
所引用的内联js
、以及外链js
的同步代码都执行完毕后触发。 -
load
事件在html
文档中所有资源完成加载后,才会触发。 - 注意:
video
、audio
、flash
不会影响load
事件触发。
关于文件加载
浏览器对同一域名下的资源并发下载线程数是有限的,比如 chrome
为6个。
因此建议对于文件请求并发量比较大的页面,把静态资源尽可能放在不同的域名下,一方面尽可能多的加大并发请求数量,另一方面避免静态资源请求与数据请求在同一主域名下导致静态资源请求携带 cookie
造成不必要的带宽浪费。
script 的 defer 与 async 属性
上文中提到 js
文件的下载和执行会阻塞文档的解析,推荐把 <script>
都放在 <body>
底部,此处给出更优雅的解决方式。
-
defer
用于开启新的线程下载js
文件,并使之在文档解析完成后执行,此过程不会阻塞文档解析。- 相当于把
<script>
都放在<body>
底部。 - 对无
src
属性的<script>
无效。 - 理论上,如果有多个声明了
defer
的<script>
,则会按顺序下载和执行(有序)。 - 理论上,声明了
defer
的<script>
会在DOMContentLoaded
之前执行。
- 相当于把
-
async
用于异步下载脚本文件,下载完毕立即解释执行代码,此过程不会阻塞文档解析。- 对无
src
属性的<script>
无效。 - 如果有多个声明了
async
的<script>
,先下载完哪个执行哪个(无序)。
- 对无
参考文章
再谈 load 与 DOMContentLoaded
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。