如何高效加载资源和手撸一个资源加载器

如何高效的加载网络资源

  1. 页面加载的流程
  2. 解析渲染的过程
  3. 页面加载的时间
  4. 资源加载的时间
  5. 资源加载的优先级
  6. css 和 script 的阻塞情况
  7. 预加载系列
  8. 图片加载

页面加载流程

  • 页面卸载 => DNS解析 => TCP链接 => HTTP请求 => 服务器响应 => 浏览器解析

页面渲染流程

页面加载时间

浏览器开发者工具

Navigation Timing API

  • 提供了可用于衡量一个网站性能的数据
  • JS的对象模型:PerformanceTiming
  • 页面加载所需的总时长:loadEventEnd - navigationStart
  • 请求返回时长:responseEnd - requestStart
  • DNS解析时间:domainLookupEnd - domainLookupStart

资源加载时间

  • 获取和分析应用资源加载的详细网络计时数据,比如XMLHttpRequest、 \<SVG>、图片、或者脚本
  • JS对象模型为PerformanceResourceTiming

使用代码统计页面和资源的记载性能

  • 获取全部的加载性能数据
    • performance.getEntries()
<link rel="stylesheet" href="./index.css" />
 <body>
 <img src="./logo.png" />
 <script src="./index.js"></script>
 <div>
 资源加载时间:
 <div id="result"></div>
 </div>
 <script>
 // https://developer.mozilla.org/zh-CN/docs/Web/API/PerformanceEntry
 function getPerformanceEntries() {
 const p = performance.getEntries();
 for (let i = 0; i < p.length; i++) {
 console.log(p[i]);
 printPerformanceEntry(p[i]);
 }
 }
 function printPerformanceEntry(perfEntry) {
 const properties = ["name", "entryType", "startTime", "duration"];
 if (perfEntry.entryType === "navigation") {
 result.innerHTML += `
 <div>页面资源:${perfEntry.name}</div>
 <div>加载时间:${perfEntry.responseEnd - perfEntry.startTime}</div><hr>
 `;
 } else if (perfEntry.entryType == "resource") {
 result.innerHTML += `
 <div>其他资源:${perfEntry.name}</div>
 <div>加载时间:${perfEntry.duration}</div>
 <hr>
 `;
 }
 }
 getPerformanceEntries();
 </script>

输出如下:

资源加载优先级

大概从高到低如下:

  • htmlcssfont、同步的XMLHttpRequest这三种类型的资源优先级最高
  • 在可视区的图片,script标签,异步XMLHttpRequestfetch
  • 图片,音视频
  • prefetch预读取的资源

注意事项

  • csshead和在body里的优先级不一样
  • 可视区的图片优先级高于js,但是js会优先加载
  • 图片,视频虽然优先级较高,但是是属于可推迟加载资源

自定义优先级

  • linkimageiframescript标签均有一个属性importance,试验性的功能
<img src="./assets/dragon.png?p=low" importance="low" width="30px">

阻塞渲染

CSS不阻塞DOM的解析,阻塞页面渲染

  • CSS没有回来之前,我们的页面没有渲染出任何东西
  • 但是请求其实和HTML文件是同一时间发出来了,说明其解析了DOM后来的内容

JS的执行阻塞DOM解析

<!-- js执行会阻塞DOM解析, index.js执行3秒,下面的内容就会延时3秒出现 -->
<script src="./index1.js"></script>
<div>内容</div>

Pre系列

  • preload:表示用户十分有可能需要在当前浏览中加载目标资源,所以浏览器必须预先获取和缓存对应资源
  • prefetch:是为了提示浏览器,用户未来的浏览有可能需要加载目标资源,所以浏览器有可能通过事先获取和缓存对应资源,优化用户体验。主要用于预取将在下一次导航/页面加载中使用的资源
  • prerender:内容被预先取出,然后在后台被浏览器渲染,就好像内容已经被渲染到一个不可见的单独的标签页
  • preconnect:预先建立连接(TCP)
<!-- 预先加载链接文档的资源 -->
<link rel="prerender" href="xxx.html" />
<!-- 资源预加载,优先级低 -->
<link rel="prefetch" href="xxx.css" />
<!-- DNS预解析 -->
<link rel="dns-prefetch" href="xxx.js" />
<!-- 预加载,优先级高 -->
<link rel="preload" href="xxx.js" as="script" />
<!-- 预先建立TCP连接 -->
<link rel="preconnect" href="xxx.com" />
  • dns-prefetch:是尝试在请求资源之前解析域名,仅对跨域域上的DNS查找有效,避免指向自己的站点
  • dns-prefetchpreconnect(预连接)一起搭配使用效果更好,一个解析DNS一个预先建立TCP连接

图片的加载

<style>
 #imgContainers {
 border: 1px solid #333;
 height: 400px;
 width: 500px;
 overflow: auto;
 }
 #imgContainers img {
 border: 1px solid #666;
 width: 400px;
 height: 400px;
 display: block;
 }
 </style>
 </head>
 <body>
 <div id="imgContainers">
 <img data-src="./images/dragon.png?t=1" />
 <img data-src="./images/dragon.png?t=2" />
 <img data-src="./images/dragon.png?t=3" />
 <img data-src="./images/dragon.png?t=4" />
 <img data-src="./images/dragon.png?t=5" />
 <img data-src="./images/dragon.png?t=6" />
 <img data-src="./images/dragon.png?t=7" />
 <img data-src="./images/dragon.png?t=8" />
 </div>
 <script>
 window.onload = function () {
 const imagesCol = imgContainers.querySelectorAll("img[data-src]");
 const options = {
 threshold: 0,
 rootMargin: "0px",
 root: null,
 };
 const ioCallBack = function (entries, obs) {
 entries.forEach((entry) => {
 if (entry.isIntersecting) {
 // 可见
 entry.target.src = entry.target.dataset.src;
 obs.unobserve(entry.target); // 停止观察
 }
 });
 };
 const observer = new IntersectionObserver(ioCallBack, options);
 imagesCol.forEach(function (item) {
 console.log("observer", item.dataset.src);
 observer.observe(item);
 });
 };
 </script>
 </body>

资源加载器

  • 通过程序加载JS、CSS、视频等资源以便重复使用

类似下面这种

// 加载
this.load.image("yun")
// 使用
this.add.image(400, 400, "yun")
  • 资源加载库:PreloadJS

资源加载的基本原理

  • 发送请求获取资源
  • key标记资源
  • URL.createObjectURL生成url以便复用

资源加载缺陷

  • 没有显式的版本问题
  • 没有缓存
  • 资源之间没有依赖关系

改进资源加载器

  • 支持版本:用属性字段标记版本
  • 支持缓存:indexedDB
  • 支持依赖关系:一个字段标记前置依赖,比如react-dialog依赖[react, react-dom]

资源属性设计

  • key:资源的唯一标记
  • url:资源的地址
  • ver:资源的版本标记
  • pre:资源加载的前置项,比如react-dialog的依赖项["react", "react-dom"]

资源加载器组成

示例代码

工具方法-资源下载

工具方法-版本比较

工具方法-对象克隆

工具方法-生成资源地址

工具方法-验证key

消息通知

本地缓存管理

完整代码请查看git地址:xixixiaoyu/resource-load: 资源加载器 (github.com)

作者:云牧

%s 个评论

要回复文章请先登录注册