你是否注意到,很多前端工程师对 File 和 Blob API 的认知停留在"上传文件"和"下载文件"这个表面?但如果我告诉你,掌握这两个 API 的细节差异,能让你用纯前端完成:大文件断点续传、客户端图片处理、完全离线的数据导出、甚至 P2P 文件传输——听起来是不是有点夸张?

事实上,字节跳动、阿里巴巴这样的大厂在构建 Web 应用时,都深度依赖这两个看似简单的 API。而很多中小团队却把它当成"学过就行"的基础知识。

今天我们不聊"如何上传文件",而是深度剖析 File 和 Blob 的本质差异、内存管理的陷阱、以及在生产环境中真正的应用设计

第一部分:你真的理解 File 和 Blob 吗?

Blob 是什么?换个角度理解

如果用现实中的比喻,**Blob(Binary Large Object)就像一个"已经打好的包裹"**——它包含了原始的二进制数据,但对这份数据的来源、用途、名字都一无所知。

?? Blob = 原始二进制数据 + MIME 类型
                - 不知道文件名
                - 不知道修改时间
                - 不知道来自哪个文件
        

而 **File 则像是"贴了标签的包裹"**:

?? File = Blob + 元数据
                - 包含 name(文件名)
                - 包含 lastModified(修改时间戳)
                - 通常来自用户交互(选文件、拖拽等)
        

关键认知File 继承自 Blob,所以每个 File 都是 Blob,但不是每个 Blob 都是 File

// File 是 Blob 的子类
        const file = new File(['hello'], 'test.txt', { lastModified: Date.now() });
        console.log(file instanceof Blob);  // ?? true
        console.log(file instanceof File);  // ?? true
        // 但反过来不行
        const blob = new Blob(['hello'], { type: 'text/plain' });
        console.log(blob instanceof File);  // ?? false
        

内存视角:它们到底住在哪里?

这是很多人遗漏的关键点——当你从 <input type="file"> 或拖拽获得 File 对象时,浏览器不是真的把文件内容加载到内存里

浏览器的处理流程:
        ┌─────────────────────────┐
        │  用户选择文件           │
        └────────────┬────────────┘
                     │
                     ▼
        ┌─────────────────────────┐
        │  浏览器创建 File 对象    │  ??─ 关键!只是元数据 + 引用
        │  ├─ name                │
        │  ├─ size                │
        │  ├─ type                │
        │  └─ lastModified        │
        └────────────┬────────────┘
                     │
                     ▼
        ┌─────────────────────────┐
        │  真实文件数据仍在磁盘上  │  ??─ 尚未加载到内存
        │  (或被浏览器缓冲)     │
        └─────────────────────────┘
        

只有当你显式调用读取方法(如 FileReader 或 stream())时,才会真正读取数据。这个设计的核心目的是安全:防止恶意 JavaScript 任意访问用户的文件系统。

第二部分:FileReader — 读取的艺术

基础用法(你可能只知道这些)

当用户选择文件时,最常见的操作:

<input type="file" id="fileInput" />
        
const input = document.getElementById("fileInput");
        input.addEventListener("change", () => {
          const file = input.files[0];
          
          console.log(`?? 文件名: ${file.name}`);
          console.log(`?? 大小: ${(file.size / 1024).toFixed(2)} KB`);
          console.log(`?? 类型: ${file.type}`);
        });
        

读取方式的本质区别

FileReader 提供多种读取方式,但底层逻辑完全不同:

读取方式

返回值

使用场景

性能表现

readAsText()

String

纯文本、JSON、CSV

需要字符编码转换

readAsDataURL()

Data URL

图片预览、Base64 传输

???? 会膨胀 33%

readAsArrayBuffer()

ArrayBuffer

二进制处理、加密、图像处理

高效,直接操作内存

readAsArrayBuffer() + TextDecoder

String

纯文本(推荐方案)

更快,避免 FileReader 开销

陷阱 1:Data URL 的隐性成本

很多人用 readAsDataURL() 做图片预览,以为这样"轻量级":

// ?? 常见的"快速方案"
        reader.onload = () => {
          const img = document.createElement("img");
          img.src = reader.result;  // 这是一个超长的 Data URL
          document.body.appendChild(img);
        };
        reader.readAsDataURL(file);
        

问题在于:Data URL 会增加约 33% 的数据体积(因为 Base64 编码)。一个 3MB 的图片经过 readAsDataURL(),字符串会膨胀到 4MB。

更好的做法

// ?? 推荐方案:使用 Object URL
        const url = URL.createObjectURL(file);
        const img = document.createElement("img");
        img.src = url;
        document.body.appendChild(img);
        // 重要!不用时释放,否则内存泄漏
        img.onload = () => URL.revokeObjectURL(url);
        

性能对比(以 3MB 图片为例):

readAsDataURL(): 产生 4MB 字符串,绑定到 DOM,内存持续占用

Object URL: 浏览器内部优化,内存占用最小,不需要字符串化

陷阱 2:FileReader 的异步陷阱

const reader = new FileReader();
        reader.onload = () => {
          console.log("?? 读取完成");
        };
        reader.readAsText(file);
        console.log("?? 读取中...");  // 这行会先执行!
        

FileReader 是完全异步的,没有 Promise 支持(在较新的浏览器中可以使用 File.text() 等现代 API)。如果你需要处理多个文件,很容易陷入回调地狱。

现代方案

// ?? 使用 File API 的现代方法(已被大多数浏览器支持)
        asyncfunction readFileAsText(file) {
        returnawait file.text();
        }
        asyncfunction readFileAsBuffer(file) {
        returnawait file.arrayBuffer();
        }
        // 使用示例
        const file = input.files[0];
        const content = await readFileAsText(file);
        console.log(content);
        

第三部分:Blob 的真正超能力

场景 1:客户端生成文件并下载

这是字节跳动、阿里云那样的平台常见的需求——用户点击"导出数据",前端直接生成 CSV、JSON、甚至 PDF,不需要任何后端参与

// 导出 CSV 的完整示例
        function exportToCSV(data) {
        // 1???? 构造 CSV 字符串
        const csvContent = data
            .map(row =>Object.values(row).join(','))
            .join('\n');
        // 2???? 创建 Blob
        const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
        // 3???? 生成临时 URL
        const url = URL.createObjectURL(blob);
        // 4???? 触发下载
        const link = document.createElement('a');
          link.setAttribute('href', url);
          link.setAttribute('download', `data_${Date.now()}.csv`);
          link.style.visibility = 'hidden';
        document.body.appendChild(link);
          link.click();
        // 5???? 清理资源
        document.body.removeChild(link);
          URL.revokeObjectURL(url);
        }
        // 使用
        const mockData = [
          { name: '小明', age: 28, city: '北京' },
          { name: '小红', age: 26, city: '上海' },
          { name: '小刚', age: 30, city: '深圳' }
        ];
        exportToCSV(mockData);
        

为什么这很强大

?? 不依赖服务器,减少网络往返(在字节的数据分析平台中广泛使用)

?? 对隐私敏感的数据,处理不离开浏览器

?? 用户选择"导出"到"下载完成"的延迟最小化

场景 2:构造 File 对象,与后端 API 兼容

很多时候,你的后端 API 期望接收 FormData 里的 File 对象。但你的数据来自:

网络请求返回的 Blob

动态生成的内容

Canvas 绘制的图片

解决方案:从 Blob 构造 File

// 场景:从网络获取图片,要上传到另一个服务
        asyncfunction transferImage(imageUrl) {
        // 1???? 获取图片作为 Blob
        const response = await fetch(imageUrl);
        const imageBlob = await response.blob();
        // 2???? 从 Blob 创建 File
        const imageFile = new File(
            [imageBlob],
            'transferred_image.jpg',
            { type: imageBlob.type, lastModified: Date.now() }
          );
        // 3???? 通过 FormData 上传(与标准上传无区别)
        const formData = new FormData();
          formData.append('file', imageFile);
        const uploadRes = await fetch('/api/upload', {
            method: 'POST',
            body: formData
          });
        returnawait uploadRes.json();
        }
        

关键点:服务器无法区分这个 File 是用户选择的,还是前端动态创建的。这就是 File API 的灵活性——它打破了"文件必须来自用户"的认知。

场景 3:二进制数据的网络传输

当你从网络获取二进制文件(如 PDF、音频、视频)时,如果直接让浏览器处理,可能会触发下载或渲染。但有时你需要检查、处理或转发这个数据

// 获取 PDF,而不让浏览器默认下载
        asyncfunction fetchAndPreviewPDF(pdfUrl) {
        const response = await fetch(pdfUrl);
        const pdfBlob = await response.blob();
        // 1???? 创建临时 URL(不是 Data URL)
        const url = URL.createObjectURL(pdfBlob);
        // 2???? 在 iframe 或特殊查看器中预览
        const iframe = document.createElement('iframe');
          iframe.src = url;
        document.body.appendChild(iframe);
        // 3???? 用户关闭后释放
        // URL.revokeObjectURL(url);
        }
        // 或者,将其转发到另一个服务
        asyncfunction forwardBlobToAnotherService(sourceUrl) {
        const response = await fetch(sourceUrl);
        const blob = await response.blob();
        const formData = new FormData();
          formData.append('file', blob);
        return fetch('/api/process', {
            method: 'POST',
            body: formData
          });
        }
        

为什么比 Data URL 好

Object URL 不膨胀数据(不需要 Base64 编码)

浏览器内部优化,内存占用小

支持流式处理大文件

第四部分:大文件断点续传的底层逻辑

现在来到 Blob 和 File API 最实用的场景——如何高效地上传几百 MB 或几 GB 的文件。

核心思路:分片 + 并行 + 重传

大文件上传流程(完整版)
        ┌──────────────────────────────┐
        │    选择 1GB 文件             │
        └───────────────┬──────────────┘
                        │
                        ▼
            ┌───────────────────────┐
            │ 分割成 1MB 的 Chunks  │  ??─ 使用 Blob.slice()
            │ Chunk 1 / Chunk 2 ... │
            └───────────────┬───────┘
                            │
                ┌───────────┼───────────┐
                │           │           │
                ▼           ▼           ▼
           上传Chunk1   上传Chunk2   上传Chunk3  ??─ 并行上传(3个同时)
                │           │           │
                └───────────┼───────────┘
                            │
                            ▼
                  ┌─────────────────┐
                  │ 服务器校验MD5   │
                  │ 或验证分片完整性 │
                  └────────┬────────┘
                           │
                           ▼
                  ┌─────────────────┐
                  │ 服务器合并分片   │
                  │ 生成完整文件    │
                  └─────────────────┘
        

实现细节

class ResumableUploader {
        constructor(file, options = {}) {
            this.file = file;
            this.chunkSize = options.chunkSize || 1024 * 1024; // 默认 1MB
            this.concurrency = options.concurrency || 3;       // 并行数
            this.uploadedChunks = newSet();
            this.uploadUrl = options.uploadUrl;
          }
        // 分割文件
          *chunkGenerator() {
            let start = 0;
            while (start < this.file.size) {
              const end = Math.min(start + this.chunkSize, this.file.size);
              yield {
                index: Math.floor(start / this.chunkSize),
                blob: this.file.slice(start, end),
                start,
                end
              };
              start = end;
            }
          }
        // 上传单个分片
        async uploadChunk(chunk) {
            const formData = new FormData();
            formData.append('chunkIndex', chunk.index);
            formData.append('chunkBlob', chunk.blob);
            formData.append('fileId', this.file.lastModified); // 简单的文件标识
            try {
              const response = await fetch(this.uploadUrl, {
                method: 'POST',
                body: formData
              });
              if (response.ok) {
                this.uploadedChunks.add(chunk.index);
                returntrue;
              }
            } catch (error) {
              console.error(`分片 ${chunk.index} 上传失败:`, error);
              returnfalse;
            }
          }
        // 并行上传所有分片
        async uploadAll(onProgress) {
            const chunks = Array.from(this.chunkGenerator());
            let completed = 0;
            for (let i = 0; i < chunks.length; i += this.concurrency) {
              const batch = chunks.slice(i, i + this.concurrency);
              
              awaitPromise.all(
                batch.map(chunk =>this.uploadChunk(chunk))
              );
              completed += batch.length;
              onProgress?.(completed / chunks.length);
            }
            returnthis.uploadedChunks.size === chunks.length;
          }
        }
        // 使用示例
        const input = document.getElementById('fileInput');
        input.addEventListener('change', async (e) => {
        const file = e.target.files[0];
        const uploader = new ResumableUploader(file, {
            uploadUrl: '/api/upload-chunk',
            chunkSize: 1024 * 1024,    // 1MB
            concurrency: 3             // 同时上传 3 个分片
          });
          uploader.uploadAll((progress) => {
            console.log(`?? 上传进度: ${(progress * 100).toFixed(2)}%`);
          });
        });
        

关键点

?? file.slice(start, end) 返回一个新的 Blob,不复制底层数据,只是引用

?? 即使文件是 5GB,分片操作也非常快(O(1))

?? 在网络中断时,只需重传失败的分片,不需要重新上传整个文件

实际应用:字节跳动的云存储、阿里云的 OSS 上传工具,都基于这个原理。

第五部分:流式处理 — 突破内存限制

对于超大文件(如 1GB+ 视频),即使分片上传,单次读取仍可能撑爆内存。这时需要流式处理:

// 流式读取大文件,避免一次性加载
        asyncfunction streamLargeFile(file) {
        const stream = file.stream();
        const reader = stream.getReader();
        try {
            while (true) {
              const { value, done } = await reader.read();
              
              if (done) {
                console.log('?? 流式处理完成');
                break;
              }
              // value 是 Uint8Array,大小可控(通常 64KB)
              console.log(`?? 处理了 ${value.byteLength} 字节`);
              // 在这里处理每个数据块
              // 例如:上传、计算哈希、压缩等
            }
          } finally {
            reader.releaseLock();
          }
        }
        

与分片上传的区别

分片是"我主动分割文件"(应用层)

流是"浏览器帮我分割数据读取"(系统层)

场景:

分片上传:需要用户控制块大小、并行度、错误重试

流式处理:处理无法一次性加载的超大文件

第六部分:安全性 — 浏览器的防线

很多人不知道,File 和 Blob API 的设计本身就内置了多层安全机制:

1. 沙箱隔离

// ?? 你无法做到的事情
        const files = await navigator.filesystem.getFile('/etc/passwd');  // 不存在此 API
        // ?? 你只能读取用户选择的文件
        input.addEventListener('change', (e) => {
          const file = e.target.files[0];  // 用户授权
        });
        

JavaScript 无法任意访问用户的文件系统。即使有恶意代码,也只能操作用户明确选择的文件。

2. 同源策略 + Object URL

// Object URL 有作用域限制
        const objectUrl = URL.createObjectURL(blob);
        // ?? 同一页面内可用
        const img = document.createElement('img');
        img.src = objectUrl;
        // ?? 跨域窗口无法访问
        window.open(objectUrl);  // 另一个窗口打开这个 URL,会被拒绝
        

Object URL 自动遵守同源策略,且生命周期受限于创建它的文档

3. CORS 限制

// 如果尝试读取跨域的文件...
        fetch('https://another-domain.com/file.bin')
          .then(res => res.blob())
          .catch(err => {
            // ?? 没有 CORS 头会失败
            console.log('跨域失败');
          });
        

即使是 Blob,跨域限制仍然适用。

第七部分:常见陷阱与优化

陷阱 1:忘记释放 Object URL

// ?? 内存泄漏代码
        for (let i = 0; i < 1000; i++) {
        const url = URL.createObjectURL(new Blob(['data']));
        // 没有 revokeObjectURL,内存不断增长!
        }
        // ?? 正确做法
        for (let i = 0; i < 1000; i++) {
        const url = URL.createObjectURL(new Blob(['data']));
        // 使用...
          URL.revokeObjectURL(url);  // 及时释放
        }
        

陷阱 2:混淆 FileList 和数组

// ?? FileList 不是数组,不能直接使用数组方法
        const files = document.getElementById('input').files;
        files.map(file => upload(file));  // ?? FileList 没有 map 方法
        // ?? 转换为数组
        const filesArray = Array.from(files);
        filesArray.map(file => upload(file));  // ?? 正确
        

陷阱 3:Blob 的 MIME 类型问题

// ?? 常见错误:依赖浏览器猜测
        const blob = new Blob(['some data']);  // 默认 type 是 'application/octet-stream'
        // ?? 显式指定 type
        const textBlob = new Blob(['hello'], { type: 'text/plain' });
        const jsonBlob = new Blob([JSON.stringify(data)], { type: 'application/json' });
        const csvBlob = new Blob([csvContent], { type: 'text/csv;charset=utf-8' });
        

实战案例:完整的上传系统

最后,让我们整合所有知识,实现一个生产级别的文件上传系统(参考字节跳动、阿里云的实现思路):

class ProductionFileUploader {
        constructor(options = {}) {
            this.chunkSize = options.chunkSize || 1024 * 1024; // 1MB
            this.maxConcurrency = options.maxConcurrency || 4;
            this.maxRetries = options.maxRetries || 3;
            this.uploadUrl = options.uploadUrl;
          }
        // 计算文件哈希(用于秒传和校验)
        async calculateFileHash(file) {
            const chunks = [];
            const chunkSize = 1024 * 1024; // 1MB
            
            let start = 0;
            while (start < file.size) {
              chunks.push(file.slice(start, start + chunkSize));
              start += chunkSize;
            }
            // 只取第一、中间、最后的分片计算哈希(快速模式)
            const samplesToHash = [
              chunks[0],
              chunks[Math.floor(chunks.length / 2)],
              chunks[chunks.length - 1],
              new Blob([file.name, file.size, file.lastModified])
            ];
            const hashInput = awaitPromise.all(
              samplesToHash.map(chunk => chunk.arrayBuffer())
            );
            // 简化版:用原生 crypto API
            const concatenated = newUint8Array(
              hashInput.reduce((acc, buf) => acc + buf.byteLength, 0)
            );
            let offset = 0;
            for (const buf of hashInput) {
              concatenated.set(newUint8Array(buf), offset);
              offset += buf.byteLength;
            }
            const hashBuffer = await crypto.subtle.digest('SHA-256', concatenated);
            returnArray.from(newUint8Array(hashBuffer))
              .map(b => b.toString(16).padStart(2, '0'))
              .join('');
          }
        // 上传前检查服务器是否已有该文件(秒传)
        async checkExist(fileHash) {
            const response = await fetch(`${this.uploadUrl}/check`, {
              method: 'POST',
              headers: { 'Content-Type': 'application/json' },
              body: JSON.stringify({ fileHash })
            });
            const data = await response.json();
            return data.exists;  // 返回 true 则秒传成功
          }
        // 分片上传(带重试机制)
        async uploadChunkWithRetry(chunk, retryCount = 0) {
            try {
              const formData = new FormData();
              formData.append('chunkIndex', chunk.index);
              formData.append('chunkBlob', chunk.blob);
              formData.append('fileId', chunk.fileId);
              formData.append('totalChunks', chunk.totalChunks);
              const response = await fetch(this.uploadUrl, {
                method: 'POST',
                body: formData
              });
              if (!response.ok) {
                thrownewError(`HTTP ${response.status}`);
              }
              return { success: true, index: chunk.index };
            } catch (error) {
              if (retryCount < this.maxRetries) {
                // 指数退避重试
                awaitnewPromise(resolve =>
                  setTimeout(resolve, Math.pow(2, retryCount) * 1000)
                );
                returnthis.uploadChunkWithRetry(chunk, retryCount + 1);
              }
              return { success: false, index: chunk.index, error };
            }
          }
        // 完整上传流程
        async upload(file, onProgress) {
            // 步骤 1:计算哈希
            console.log('?? 计算文件哈希...');
            const fileHash = awaitthis.calculateFileHash(file);
            // 步骤 2:检查秒传
            console.log('?? 检查秒传...');
            if (awaitthis.checkExist(fileHash)) {
              console.log('?? 服务器已有该文件,秒传成功!');
              onProgress?.(1);
              return { success: true, type: 'instant' };
            }
            // 步骤 3:分片上传
            console.log('?? 开始分片上传...');
            const chunks = this.generateChunks(file, fileHash);
            const uploadQueue = [...chunks];
            let completed = 0;
            let uploading = 0;
            const failed = [];
            returnnewPromise((resolve) => {
              const processNext = async () => {
                if (uploadQueue.length === 0 && uploading === 0) {
                  if (failed.length === 0) {
                    resolve({ success: true, type: 'chunked', hash: fileHash });
                  } else {
                    resolve({ success: false, failed });
                  }
                  return;
                }
                while (uploading < this.maxConcurrency && uploadQueue.length > 0) {
                  uploading++;
                  const chunk = uploadQueue.shift();
                  this.uploadChunkWithRetry(chunk).then(result => {
                    uploading--;
                    if (result.success) {
                      completed++;
                    } else {
                      failed.push(result.index);
                    }
                    onProgress?.(completed / chunks.length);
                    processNext();
                  });
                }
              };
              processNext();
            });
          }
        // 生成分片
          generateChunks(file, fileId) {
            const chunks = [];
            const totalChunks = Math.ceil(file.size / this.chunkSize);
            for (let i = 0; i < totalChunks; i++) {
              const start = i * this.chunkSize;
              const end = Math.min(start + this.chunkSize, file.size);
              chunks.push({
                index: i,
                blob: file.slice(start, end),
                fileId,
                totalChunks
              });
            }
            return chunks;
          }
        }
        // 使用示例
        const uploader = new ProductionFileUploader({
        uploadUrl: '/api/upload',
        chunkSize: 1024 * 1024,  // 1MB
        maxConcurrency: 4,
        maxRetries: 3
        });
        document.getElementById('fileInput').addEventListener('change', async (e) => {
        const file = e.target.files[0];
        const result = await uploader.upload(file, (progress) => {
            console.log(`?? 上传进度: ${(progress * 100).toFixed(2)}%`);
          });
        if (result.success) {
            console.log('?? 上传成功', result);
          } else {
            console.error('?? 上传失败', result);
          }
        });
        

深度对比:何时用 File API,何时用其他方案

┌─────────────────────────────────────────────────────────┐
        │  场景分析:选择合适的文件处理方案                       │
        ├──────────────┬──────────────────────────────────────────┤
        │ 场景         │ 推荐方案                                 │
        ├──────────────┼──────────────────────────────────────────┤
        │ 小文件上传   │ FormData + File                         │
        │ (<5MB)       │ 直接POST,简单高效                     │
        ├──────────────┼──────────────────────────────────────────┤
        │ 大文件上传   │ 分片 + 并行 + 断点续传                 │
        │ (>100MB)     │ 使用 Blob.slice() + 并发控制            │
        ├──────────────┼──────────────────────────────────────────┤
        │ 超大文件     │ 流式处理 + 分片                         │
        │ (>1GB)       │ file.stream() + chunk 上传              │
        ├──────────────┼──────────────────────────────────────────┤
        │ 客户端生成   │ Blob + Object URL                      │
        │ 数据导出     │ CSV/JSON/PDF 生成后下载                 │
        ├──────────────┼──────────────────────────────────────────┤
        │ 图片预览     │ Object URL(绝不用 Data URL)           │
        │ (任意大小) │ 性能差 33% 会崩溃                      │
        ├──────────────┼──────────────────────────────────────────┤
        │ 二进制处理   │ ArrayBuffer + TypedArray              │
        │ 加密/压缩    │ Crypto API 或第三方库结合              │
        ├──────────────┼──────────────────────────────────────────┤
        │ 离线存储     │ IndexedDB + Blob                       │
        │ 数据同步     │ 配合 Service Worker                    │
        └──────────────┴──────────────────────────────────────────┘
        

总结:为什么要深度理解 File 和 Blob

性能优化:Object URL vs Data URL,性能差异 30%+

内存管理:Blob.slice() 不复制数据,处理 GB 级文件不会卡顿

架构设计:客户端处理,减少服务器压力(字节跳动的经验)

安全隐患:Object URL 泄漏会导致跨域访问,需要及时释放

用户体验:断点续传 + 秒传,让大文件上传"感觉很快"

很多前端工程师觉得这些 API"太基础"而忽略,但真正的竞争力在于细节。掌握 File 和 Blob,你就掌握了:

?? 如何优化百 MB 级别的网络传输

?? 如何在内存受限的设备上处理大数据 

?? 如何构建生产级别的云存储前端 

?? 如何给用户提供闪电般的上传体验

常见问题解答

Q:为什么不用 Fetch 的 upload 模式?

A: Fetch 目前没有原生的 upload 进度回调,只能用 XMLHttpRequest。对于分片上传,你需要自己控制并发和重试逻辑,这恰好是我们上面实现的。

Q:Object URL 和 Data URL 有什么本质区别?

A:

Data URL:整个内容编码为字符串,占用内存大、字符串膨胀 33%、不适合大文件

Object URL:浏览器内部引用,占用内存小、性能高、适合任何大小的 Blob

Q:File 和 Blob 的内存什么时候释放?

A:

Blob:当没有引用时,垃圾回收自动清理

Object URL:必须显式调用 URL.revokeObjectURL() 释放,否则内存泄漏

Q:如何在离线状态下保存大文件?

A: 使用 IndexedDB 配合 Blob 存储:

const db = await openDB('myapp');
        const tx = db.transaction('files', 'readwrite');
        await tx.objectStore('files').add({ name: 'data', blob: largeBlob });
        

Q:Blob 可以跨域上传吗?

A: 可以,Blob 本身不受 CORS 限制,但取决于服务器 API 的 CORS 策略。

原文链接:https://mp.weixin.qq.com/s/tTmc1pEdWiGhT43KBfcUzA