JavaScript.jpg

一个问题

为什么推荐把 <script> 都放在 <body> 底部?

  • 防止加载资源而导致的长时间的白屏。
  • js 只能获取在 <script> 以上的 dom
  • ...

正文

document.readyState

该属性描述了文档的加载状态,发生变化时,会在 document 对象上触发 readystatechange 事件。有3种状态:

  • loading 正在加载
  • interactive 文档已被解析,loading 状态结束,但是诸如图像,样式表和框架之类的子资源仍在加载,会在 documentwindow 对象上触发 DOMContentLoaded 事件。
  • complete 所有资源完成加载,会在 window 对象上触发 load 事件。

浏览器页面渲染过程

  1. 浏览器与服务器建立 TCP 连接发送 HTTP 请求,获取 HTML 文档并开始从上到下解析,构建 DOM
  2. 在构建 DOM 过程中:

    • 如果遇到外联的 css 文件,下载文件并执行构建 CSSOM,此过程不影响 DOM 构建,但在完成之前会阻止页面渲染。
    • 如果遇到外联的 js 文件,则暂停构建 DOM

      • 若在这之前的 css 文件已加载完毕且 CSSOM 构建完成,则合并已经构建好的 DOMCSSOM 并渲染到页面上
      • 之后等 js 文件下载并执行后,然后继续构建后边的 DOM
  3. 完成文档解析后,将 DOMCSSOM 进行关联和映射,生成 Render Tree 渲染页面。
  4. 当所有同步的 js 代码执行完毕后,会在 documentwindow 对象上触发 DOMContentLoaded 事件,此时对应 document.readyState === 'interactive'
  5. 当所有资源完成加载后,会在 window 对象上触发 load 事件,此时对应 document.readyState === 'complete'

在这个过程中,js 文件的下载和执行会阻塞文档的解析,这也就是为什么推荐把 <script> 都放在 <body> 底部,下文中还将介绍更优雅的实现方式。

小结

  • DOMContentLoaded 事件在 html 文档加载完毕,并且 html 所引用的内联 js、以及外链 js 的同步代码都执行完毕后触发。
  • load 事件在html 文档中所有资源完成加载后,才会触发。
  • 注意: videoaudioflash 不会影响 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

浅谈script标签的defer和async

document.readyState [MDN


再次重逢的时间
20 声望2 粉丝