Skip to content

页面和资源加载

页面生命周期

页面的生命周期重要的就这四个:

  • DOMContentLoaded
    浏览器完全加载 HTML,构建了 DOM 树,但是 img,stylesheet 等外部样式不确定

    INFO

    浏览器自动填充表单功能会在这里触发

  • load
    浏览器完成了所有资源的加载
  • beforeUnload
    用户正在离开页面,此时可以询问是否保存了重要内容
  • unload
    用户几乎已经离开,此时可以发送统计数据等内容

    INFO

    浏览器的 navigator.sendBeacon 方法可以在后台发送数据,保证页面离开流畅

脚本加载

对于普通 <script> 资源标签来讲,如果遇到它就会停止构建 DOM 然后执行脚本内容, 所以上述的事件 DOMContentLoaded 是发生在所有普通 script 标签加载之后的

然而万事万物都有例外:

  • 带有 async 属性的脚本不会阻塞 DOMContentLoaded
  • 通过 document.createElement('script') 创建的脚本不会阻塞 DOMContentLoaded

样式表不会直接阻塞 DOMContentLoaded 但是会阻塞它后面的 script, 这样会间接地阻塞 DOMContentLoaded,比如:

js
<link rel="stylesheet" href="style.css"></link>
<script>
    // 可能要获取上面样式的属性,所以就等待
</script>

加载进度、状态

document.readyState 表示文档加载状态,它的值有三种:

  • loading,正在加载
  • interactive —— DOMContentLoaded 文档读取完成
  • complete —— load 所有资源加载完成

TIP

上述列表后两条是几乎同时发生的,但是 document.readyState 的变化在先, window 对应的变化(生命周期)在后

特殊属性 async、defer

如果 <script> 标签添加了上述属性,则会产生不同的 “效果”

相同点:
脚本的加载不会阻塞页面渲染(并行加载)
仅对外部资源有效(带 src 属性的)

不同点:

  • defer 按照 <script> 标签顺序加载,即使 bundle2 率先加载完成也会等等 bundle1
  • DOM 构建完成后,脚本才会执行
  • defer 加载会在事件 DOMContentLoaded 之前完成
js
<script src="bundle1.min.js" defer></script>
<script src="bundle2.min.js" defer></script>

TIP

适合跟 DOM 无关的脚本


而 async 则表现的格外 “独立”,所有带 async 的 <script> 标签的执行顺序表现为:

  • 先加载完成的先执行
  • 脱离生命周期的 “限制”,与 DOMContentLoaded 无关

TIP

适合广告、统计等脚本

提上这么一嘴

下面的脚本只会输出 hello 而没有 world, 知识点不言而喻了吧。

js
<script src="index.js">
    console.log('world')
</script>
js
console.log('hello')

动态脚本

通过 document.createElement('script') 创建的脚本默认会异步执行,行为跟 “async” 一样

但是可以显式地设置 script.async = false 使得它的行为变成 “defer”

模块(module)

这样的脚本 <script src="xxx" type="module"></script> 称之为模块,这里不讨论模块,只讨论 async 和 defer 属性

  1. 模块 默认 总是延迟加载的(不阻塞页面渲染),表现为 defer 的特性

  2. async 可以适用于内联脚本

    js
    <script type="module" async>
      import {config} from './nanote.js'
    </script>