跳到主要内容

如何实现大文件切片上传

· 阅读需 3 分钟
素明诚
Full stack development

为什么要大文件分割上传?

  1. 文件过大会因为网络环境不文件导致超时或者失败
  2. 分割成小块后可以提高上传的效率,更有效的利用带宽,提高上传速度
  3. 如果上传一半突然失败,可以向服务端获取一下进度,重新上传

前端分割文件

  1. 使用input获取文件,生成文件的MD5 ,在上传文件的时候,可以先生成文件的 MD5,把 hash 发给后端方便后端校验。

  2. 使用Blob.prototype.slice 或者 File.prototype.slice方法切割文件

async function splitFile(file, chunkSize = 1024 * 1024) {
const fileSize = file.size;
const chunks = Math.ceil(fileSize / chunkSize);
let currentChunk = 0;

while (currentChunk < chunks) {
const start = currentChunk * chunkSize;
const end = start + chunkSize >= fileSize
? fileSize
: start + chunkSize;

const chunk = file.slice(start, end);
const blob = new Blob([chunk], { type: file.type });

// 对每一块进行操作
await uploadChunk(blob, currentChunk, chunks);

currentChunk++;
}
}

async function uploadChunk(chunk, index, total) {
// 上传
}

  1. 向服务端发送上传文件的MD5值和分片数量,开始上传

  2. 初始化每一个分片上传任务,返回本次分片上传唯一标识

  3. 上传失败要返回:分片信息、文件名称、文件hash、分片大小、分片序号等,可以使用Promise.allSettled()

  4. 上传成功:服务端合并文件,校验文件hash,得到原始文件,返回成功信息

断点续传

断点续传其实就是让请求可中断,然后在接着上次中断的位置继续发送,此时要保存每个请求的实例对象,以便后期取消对应请求,并将取消的请求保存或者记录原始分块列表取消位置信息等,以便后期重新发起请求

  1. 如果使用原生 XHR 可使用 (new XMLHttpRequest()).abort() 取消请求
  2. 如果使用 axios 可使用 new CancelToken(function (cancel) {}) 取消请求
  3. 如果使用 fetch 可使用 (new AbortController()).abort() 取消请求

上传过程中刷新页面怎么办

你可以每次上传成功后将成功的分片信息保存在本地,刷新后再读取一下,再继续传

如何进行并行上传

  1. 使用Promise.race()来同时请求,用Promise.all()来确定都完成了
  2. 巨大的话会导致线程频繁切换,所以并行上传要考虑数量

大文件 MD5 时的优化

在使用js-md5生成 MD5 的时候,页面可能会假死,可以使用worker线程进行大文件md5加密的优化,防止页面卡死

如何实现秒传

秒传就是服务器有这个文件,所以你可以在上传前先请求一下服务器,看看hash是否一致,如果有直接返回上传成功。

JS Web Workers

· 阅读需 2 分钟
素明诚
Full stack development

什么是 Web Workers

Worker - Web API 接口参考 | MDN## 有哪些实际用处?

Worker 线程中全局对象为 self,代表子线程自身,这时 this指向self,其上有一些 api:

  • self.postMessage: worker 线程往主线程发消息,消息可以是任意类型数据,包括二进制数据
  • self.close: worker 线程关闭自己
  • self.onmessage: 指定主线程发 worker 线程消息时的回调,也可以self.addEventListener('message',cb)
  • self.onerror: 指定 worker 线程发生错误时的回调,也可以 self.addEventListener('error',cb)

优点

  1. 可以通过Web Workers把需要大量计算的工作交接给worker处理,不占用主线程。
  2. 通过postMessageonmessage进行信息的传递和接收。

主线程与 Worker 之间传递的数据是通过拷贝完成的,而传址来完成的。传递给 Worker 的对象需要经过序列化,接下来在另一端还需要反序列化。页面与 Worker 不会共享同一个实例,最终的结果就是在每次通信结束时生成了数据的一个副本。

也就是说,Worker 与其主页面之间只能单纯的传递数据,不能传递复杂的引用类型:如通过构造函数创建的对象等。并且,传递的数据也是经过拷贝生成的一个副本,在一端对数据进行修改不会影响另一端。

缺点

  1. worker 不支持跨域请求
  2. worker不能访问documentwindow,但是可以获取navigatorlocation(只读)XMLHttpRequestsetTimeout等浏览器 API。也可以进行AJAX请求。

多个 worker

  1. 一个worker可以委派多个worker
// 加载其他的worker
importScripts('...')

  1. 使用terminate()可以终止worker

JS数组方法练习-根据字符串最后一位大小排序

· 阅读需 1 分钟
素明诚
Full stack development
<script>
let Str = 'Nothing is to be got without pains but poverty';
let changeString = 'poverty9 Nothing1 without6 pains7 but8 is2 to3 be4 got5';
// 根据单词末尾的下标,还原句子

// 方法1 sort
let processString1 = changeString.split(' ').sort((a, b) => {
return a.slice(- 1) - b.slice(- 1);
}).join(' ');
console.log(processString1);
// 方法2 map
let processString2 = changeString.split(' ').map((e) => {
return {
index: e.slice(- 1),
value: e.slice(0, e.length - 1)
};
}).sort((a, b) => {
return a.index - b.index;
}).map((e) => {
return e.value;
}).join(' ');
console.log(processString2);
</script>

v-for 为什么不推荐用 index 做 key

· 阅读需 1 分钟
素明诚
Full stack development

在 Vue 文档列表渲染中,明确提到一句话

7587e7639680690776b021fc66f73da4

这里提到了除非可以依赖默认行为以获取性能上的提升,除了这种情况以外是一定要使用 key 的。

key 的作用

在 Vue 中更新视图的时候我们需要通过 diff 算法对新旧 DOM 进行比较差异,重新渲染。key 在这里起到的作用就是一个唯一标识,为了更高效的对比虚拟 DOM 中每个节点是否相同。

就地更新策略

Vue 采用“就地更新”的策略来更新 DOM,当数据项的顺序发生改变,Vue 不会随之移动 DOM 元素的顺序,而是就地更新每个元素。

为什么要采用这种方式?

因为这种方式是高效的,尤其是在用于列表渲染的时候,Vue 会尽可能复用相同元素。如果使用 index 有时候会导致渲染异常或者错位的现象。

所以,尽可能使用 key 并保障 key 是唯一的,如果没有唯一值可以使用 nanoId 等其他的库来生成 key

强缓存和协商缓存

· 阅读需 5 分钟
素明诚
Full stack development

一、浏览器缓存

为什么要缓存?

当你请求网页的时候拿到了数据资源,这个时候用户要等待一段时间才能看见网页渲染在你的面前。这时候产品就说了,你要提高用户体验啊,那就出现了各种缓存技术,例如 CDN 缓存、浏览器缓存、数据本地存储等等反正缓存策略就是为了优化用户体验的。

缓存的优点

节省服务器流量、减轻服务器压力、加快客户端访问速度、提升用户体验、提升网站性能

1b3ce8bc25aee106f756f9cc62a09f26### 字段名称

Cache-Control缓存控制(响应头)

  1. no-cache:指示浏览器忽略资源缓存副本,强制到服务器获取资源(浏览器依然缓存)
  2. no-store:强制缓存在任何情况下都不要保留任何副本
  3. max-age=3153600:指示缓存副本的有效时长,从请求时间开始到过期时间之间的秒数。
  4. public:表明响应可以被任何对象(包括 发送请求的客户端,代理服务器,等等)缓存。
  5. private:不共享缓存,只能被单个用户缓存(代理服务器无法缓存它)

Expires 有效期限

启用缓存和定义缓存时间。告诉浏览器缓存资源,缓存过期时间,如果还没过该时间点则不发请求。

HTTP1.1中,使用Cache-Control:max-age=秒,来代替。

7ee55197edba885d4126f7bb655d2bda

Last-Modified 最后修改时间

表示服务器本地记录中的文件最后修改时间,由服务器发送给客户端

If-Modified-Since 何时修改

表示浏览器缓存记录中该文件的最后服务器修改时间,由客户端发送给服务器

ETag 实体标签

Etag 是 Entity tag 的缩写,可以理解为“被请求变量的实体值”,Etag 是服务端的一个资源的标识。所谓的服务端资源可以是一个 Web 页面,也可以是 JSON 或 XML 等。服务器单独负责判断记号是什么及其含义,并在 HTTP 响应头中将其传送到客户端。

比如,浏览器第一次请求一个资源的时候,服务端给予返回,并且返回了 ETag: "50b1c1d4f775c61:df3" 这样的字样给浏览器,当浏览器再次请求这个资源的时候,浏览器会将 If-None-Match: W/"50b1c1d4f775c61:df3" 传输给服务端,服务端拿到该 Etag,对比资源是否发生变化,如果资源未发生改变,则返回 304 HTTP 状态码,不返回具体的资源。

If-None-Match

它表示客户端已经存储了服务器返回的资源的 ETag ,并且希望在该资源未被修改时获取一个缓存副本。如果该资源已被修改,服务器应该正常返回该资源的新版本,并且返回的响应应该不包含 304 Not Modified 状态代码。

二、缓存资源

浏览器在第一次请求后会缓存资源,再次请求的时候会经历以下步骤。

  1. 浏览器判断该资源 header 中的信息,根据 Expires 和 Chache-control 来判断是否是强缓存,如果是,直接从缓存获取数据
  2. 如果不是,浏览器会发送一次请求到服务器,这次请求会携带 IF-Modified-Since 或者 IF-None-Match,他们的值分别是第一次请求返回的 Last-Modified 或者是 Etag,根据携带的字段值服务器会进行判断是否返回资源。如果是命中,服务器返回 304 状态码,浏览器直接从本地获取资源。如果是否,服务器返回新的资源,并且更新 header 中相关的字段信息。

三、强缓存和协商缓存

强缓存

Expires 和 Chache-control 都是标识时间的字段,需要注意的是如果二者同时出现,Chache-control 的优先级是高于 Expires 。

但 Expires 是 HTTP1.0 的规范,时间格式是 GTM 字符串,如如果服务器时间和客户端时间差别较大时,会导致内存混乱。而服务器的时间和用户实际时间不正常是很正常的现象,所以在使用 Expires 的时候会出现一些问题,建议还是使用 Chache-control 来标识时间

Cache-Control 这个字段是 http 1.1 的规范,一般常用该字段的 max-age 值来进行判断,它是一个相对时间所以比较准确

协商缓存

协商缓存是由服务器来决定,这个资源到底是否更新。整个过程也是涉及到两个字段。在第一次请求的 header 中会出现 Last-Modified 或是 Etag,注意是第一次请求的时候就会有,因为要通过这个字段来判断资源是否可用。

如果是 Etag 对应的是 if-None-Match 字段,是一段唯一标识符,告诉服务器本地缓存文件的最后修改时间。

如果是 Last-Modified 对应的是 if-Modified-Since 字段,是一段时间格式的字符串,比如是哈希值,服务端判断哈希是否一致。HTTP 中并没有指定如何生成 ETag,这个由开发者自行决定。

以上两种,如果服务端判断一样,则返回 304 Not Modified,如果不一致则返回新的资源,并且更新本地缓存