HTML 页面中的资源加载策略

HTML 页面及页面内包含的资源一般基于 HTTP 协议进行资源的请求和获取。

浏览器根据照资源对页面的影响计算加载的优先级,并按照优先级下载资源。

我们可以使用异步脚本、preloadprefetch等方法更细粒度的对优先级进行微调。

页面在资源加载之前会进行安全检查,阻止不满足条件的资源加载并报错,详见内容安全策略 CSP

浏览器缓存机制

按照浏览器的缓存位置分类,优先级从高到低:

  1. Memory Cache:内存缓存,主要缓存为页面已加载的图片、样式、脚步等资源,特点是:
    1. 生命周期短,会随页面关闭失效
    2. 通过prefetch预加载的资源就是在内存中缓存
  2. Service Worker离线缓存
    1. Service Worker 是运行在浏览器背后的独立线程,可以充当 Web 程序和后端服务的代理,实现请求缓存等功能。
  3. Disk Cache:硬盘缓存,相对于内存缓存速度稍慢,但是时效性更久,HTTP 缓存是使用的硬盘缓存
  4. Push Cache:HTTP2 推送缓存,以上三种缓存都未命中才会使用

资源加载优先级

浏览器默认加载优先级:

  • 第一级:会影响页面布局的资源,包括:html、css、front
  • 第二级:preload 资源、script、xhr
  • 第三级:图片、音频、视频
  • 第四级:prefetch 资源

在默认优先级基础上浏览器会根据一些规整对优先级进行调整,例如:

  • 将同步 XHR 请求优先级调为最高
  • 现代浏览器为了提升用户体验会将首屏图片优先级调高
  • 脚本规则
    • 异步脚本的优先级会降低
    • 在页面第一张图片之前的标记为early**,提升优先级
    • 在页面第一张图片之后的标记为late**

Chrome资源优先级

异步脚本

HTML 页面通过script标签可以加载外部 JS 脚本,总共有三种方式:

  1. 普通模式:<script src="path.js" />
  2. 延迟模式:<script src="path.js" defer />
  3. 异步模式:<script src="path.js" async />

异步模式优先级高于延迟模块,如果在script标签上同时设置asyncdefer属性,生效的是async属性。

JS脚本加载时间轴

如上图不同的模式会对页面的解析和渲染产生不同的影响。

  • 普通模式:在 DOM 解析过程中遇到script标签会停止解析,等待脚本加载运行完成后再继续进行解析。
  • defer 延迟模式:
    • 加载行为:脚本文件会在后台异步下载,不回阻塞 HTML 文档的解析
    • 执行时机:当 HTML 文档解析完成后,按照脚本在文档的出现顺序依次执行
    • 适用场景:依赖完整 DOM 结构的脚步,或者脚步之间有依赖关系的场景
  • async 异步模式:
    • 加载行为:脚本文件在后台异步下载,通用不阻塞 HTML 文档解析
    • 执行时机:一旦脚本下载完成,立即执行,可能在文档解析过程中执行
    • 执行顺序:脚本执行顺序取决于下载完成的先后顺序
    • 适用场景:适用于独立的脚本,如网站统计或广告脚本,这些脚本没有依赖关系

PreFetching&Preloading

prefetchpreload为我们提供了更细粒度的控制浏览器加载资源的方法,在<head>中使用<link>标签声明,告诉浏览器提前进行加载,后执行,加载和执行解耦。

预提取(preload)

作用:用来告诉浏览器当前页面的必须资源,浏览器会高优加载这些资源 特性:preload加载的样式和脚本在加载完成后不会执行,加载过程也不会阻塞页面渲染

  • 场景一:为了避免 JS 脚本阻塞页面渲染,我们通常会将脚本放<body>底部加载,使用preload可以实现在不阻塞页面渲染的前提下尽早的开始资源加载。
  • 场景二:在使用自定义字体时常常会发生字体样式闪动(FOUT,Flash of Unstyled Text),原因是字体时通过 CSS 引入的,在 css 解析后才会加载,可以使用 preload 告诉浏览器提前进行字体加载。
<head>
  <meta charset="utf-8">
  <title>JS and CSS preload example</title>

  <link rel="preload" href="style.css" as="style">
  <link rel="preload" href="main.js" as="script">
  <link rel="preload" href="fonts/xxx.woff2" as="font" type="font/woff2" crossorigin>

  <link rel="stylesheet" href="style.css">
</head>

<body>
  <h1>bouncing balls</h1>
  <canvas></canvas>

  <script src="main.js" defer></script>
</body>

注意

  1. type属性可以优化preload,浏览器之后对执行的类型进行预加载
  2. 当预加载资源使用 CORS(例如 font、xhr、fetch) 加载时,必须加上crossorigin属性,否则会进行多次加载,即使这个资源不是跨域请求也需要。
    1. 原因是不添加crossorigin属性,浏览器会以匿名的方式请求,preload 相对于正常请求,请求头中没有origin字段

预加载(prefetch)

告诉浏览器将来可能会用到的资源,告诉浏览器在主要内容加载完成之后加载,利用浏览器空闲时间下载,以提升之后的用户体验

<!-- 资源预加载 -->
<link rel="prefetch" href="next-page.html">
<link rel="prefetch" href="next-styles.css" as="style">
<link rel="prefetch" href="next-script.js" as="script">
<!-- DNS预解析: -->
<link rel="dns-prefetch" href="//xxx.com">
<!-- http预连接:将建立对该域名的TCP链接 -->
<link rel="preconnect" href="https://xxx.com">
<!-- 页面预渲染:将会预先加载链接文档的所有资源 -->
<link rel="prerender" href="//xxx.com">

页面加载事件

页面加载主要节点和事件:

  • 页面加载完成
  • 资源加载完成
  • 页面关闭

页面加载

页面加载完成DOMContentLoaded

HTML 文档加载和解析完成之后触发,DOM 已经就绪(DOMTree Ready),但像 <img> 和样式表之类的外部资源可能尚未加载完成。

浏览器在 HTML 页面下载完成后会自上而下进行解析并开启下载进程,对页面引用的资源进行优先级排序并下载 JS 脚本和 CSS 样式会阻塞页面的解析和渲染,影响DOMContentLoaded事件触发,详见关键渲染路径&渲染优化

document.addEventListener("DOMContentLoaded", ready);

资源加载完成onload

外部资源已加载完成。浏览器不仅加载完成了 HTML,还加载完成了所有外部资源:图片,样式等。

// 方式一
window.addEventListener('load', (event) => {
// 方式二
window.onload = function() {};

页面关闭

用户准备离开beforeunload,如果访问者触发了离开页面的导航(navigation)或试图关闭窗口。可以检查用户是否保存了更改,并询问他是否真的要离开

window.onbeforeunload = function() {
  return "There are unsaved changes. Leave now?";
};

用户马上离开unload,当用户关闭页面时触发,但是我们仍然可以启动一些操作,例如发送统计数据。

window.addEventListener("unload", function() {});

获取页面状态

使用document.readyState属性可以获取当前加载状态的信息,有以下属性:

loading: 文档正在被加载。 interactive: 文档被全部读取。与DOMContentLoaded几乎同时发生,但是在DOMContentLoaded之前发生。 complete: 文档被全部读取,并且所有资源(例如图片等)都已加载完成,与window.onload几乎同时发生,但是在window.onload之前发生。

// 监听状态变化
document.addEventListener('readystatechange', callback);

// DOM加载完成后立刻执行
function doSomething() { /*...*/ }
if (document.readyState == 'loading') {
  // 仍在加载,等待事件
  document.addEventListener('DOMContentLoaded', doSomething);
} else {
  // DOM 已就绪!
  doSomething();
}

参考

浏览器里的资源请求

results matching ""

    No results matching ""