JS-经典场景面试题:前端大文件上传

58 阅读2分钟

前言

在前端开发中,处理图片裁剪、Excel 导出、或大文件上传时,绕不开 BlobFile。它们是 JavaScript 处理二进制数据的基石。本文将带你梳理这些 API 的关系,并提供一个完整的“大文件切片上传”实现思路。

一、 核心概念:Blob 与 File

1. Blob (Binary Large Object)

Blob 表示不可变的、原始的二进制数据。

  • 核心方法slice(start, end, contentType)
  • 场景:常用于大文件分片、将 Canvas 转换为图片、处理流媒体数据。

2. File

File 继承自 Blob,它在 Blob 的基础上增加了与系统文件相关的信息。

  • 常见属性

    • name:文件名。
    • size:字节大小。
    • type:MIME 类型(如 image/png)。
    • lastModified:最后修改时间的时间戳(推荐使用,代替已废弃的 lastModifiedDate)。

💡 贴士:向后端传输文件时,直接将 File 对象 appendFormData 即可,浏览器会自动处理编码,无需手动转换。

const formData = new FormData();
formData.append("image", file); // 将 File 对象附加到 FormData
fetch("/upload", { method: "POST", body: formData });


二、 异步读取:FileReader 家族

1. FileReader (主线程异步)

用于将文件内容读入内存。

方法读取结果 (result)适用场景
readAsDataURLBase64 编码的字符串图片预览
readAsText纯文本内容读取文本文件、配置文件
readAsArrayBufferArrayBuffer 对象二进制操作(加密、MD5 计算)
readAsBinaryString原始二进制字符串后台转换(较少用)

2. FileReaderSync (Worker 同步)

  • 场景:仅能在 Web Worker 中使用。
  • 特点:会阻塞线程直到读取完成。在后台线程中同步读取大文件,逻辑更直观,且不会卡死 UI 界面。

三、 实战:图片/文本预览示例

<!doctype html>
<html>
  <body>
    <div>
      <div>
        <input type="file" id="files-list" multiple />
        <div id="output" style="width: 300px; height: 300px;overflow: auto;"></div>
        <div id="progress"></div>
      </div>
    </div>

    <script>
      let filesList = document.getElementById('files-list')
      filesList.addEventListener('change', (event) => {
        let info = '',
          output = document.getElementById('output'),
          progress = document.getElementById('progress'),
          files = event.target.files,
          type = 'default',
          reader = new FileReader()
        if (/image/.test(files[0].type)) {
          reader.readAsDataURL(files[0])
          type = 'image'
        } else {
          reader.readAsText(files[0])
          type = 'text'
        }
        reader.onerror = function () {
          output.innerHTML = 'Could not read file, error code is ' + reader.error.code
        }

        reader.onprogress = function (event) {
          if (event.lengthComputable) {
            progress.innerHTML = `${event.loaded}/${event.total}`
          }
        }
        reader.onload = function () {
          let html = ''
          switch (type) {
            case 'image':
              html = `<img src="${reader.result}">`
              break
            case 'text':
              html = reader.result
              break
          }
          output.innerHTML = html
        }
      })
    </script>
  </body>

  <style></style>
</html>


四、 核心面试题:如何实现大文件上传?

当文件达到几百 MB 甚至几 GB 时,直接上传极易超时或因网络波动失败。

1. 实现步骤

  1. 切片 (Chunking) :利用 file.slice() 将大文件切割成等份的 Blob 块。

  2. 生成指纹 (Hashing) :利用 Web Worker 计算文件的 MD5 值(唯一标识)。这样即便文件名改了,服务器也能识别出是同一个文件(实现秒传)。

  3. 并发上传:调用接口循环上传切片,通常会限制并发数(如一次只传 3-6 个)。

  4. 合并请求

    • 前端主动触发:所有切片传完后,发一个 merge 请求。
    • 后端自动感应:后端计数,发现切片到齐后自动合并。

2. 切片逻辑代码

function createFileChunk(file, size = 1024 * 1024 * 5) { // 默认 5MB 一片
    const fileChunkList = [];
    let cur = 0;
    while (cur < file.size) {
        fileChunkList.push({
            file: file.slice(cur, cur + size) // 核心:利用 Blob 的 slice
        });
        cur += size;
    }
    return fileChunkList;
}

3. 进度条的细节

上传和下载的进度监听对象不同,面试常考:

  • 下载进度xhr.onprogress
  • 上传进度xhr.upload.onprogress
xhr.upload.onprogress = (event) => {
    if (event.lengthComputable) {
        const percent = (event.loaded / event.total) * 100;
        console.log(`当前切片上传进度:${percent}%`);
    }
};