如何高效加载资源和手写一个实用的资源加载器

1,187 阅读3分钟

如何高效的加载网络资源

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

页面加载流程

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

页面渲染流程

image

页面加载时间

浏览器开发者工具

image

Navigation Timing API

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

image

资源加载时间

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

image

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

  • 获取全部的加载性能数据
    • 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>

输出如下:

image

资源加载优先级

大概从高到低如下:

  • 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连接

图片的加载

image

<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>

image

资源加载器

  • 通过程序加载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"]

资源加载器组成

image

image

示例代码

工具方法-资源下载

image

工具方法-版本比较

image

工具方法-对象克隆

image

工具方法-生成资源地址

image

工具方法-验证key

image

消息通知

image

本地缓存管理

image

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