Skip to main content

二进制数据与文件处理

在处理音视频、Canvas/WebGL 绘图、WebSockets 传输或大文件分片上传时,传统的字符串和对象显得力不从心。前端必须掌握对二进制数据(Binary Data)的处理能力。 核心脉络:ArrayBuffer (底层内存) -> TypedArray / DataView (读写视图) -> Blob (不可变类文件对象) -> File (附带元数据的 Blob)。

1. ArrayBuffer 与视图 (TypedArray / DataView)

1.1 ArrayBuffer:纯粹的内存块

ArrayBuffer 是用来表示通用的、固定长度的原始二进制数据缓冲区。 注意:你不能直接操作 ArrayBuffer 的内容。它只是一块内存,要读写它,必须通过“视图”(TypedArray 或 DataView)。

// 分配一段 16 字节的连续内存,初始值全为 0
const buffer = new ArrayBuffer(16);
console.log(buffer.byteLength); // 16

🌟 ES2024 新特性:动态调整大小 (Resizable ArrayBuffer)

传统 ArrayBuffer 长度固定,扩容需要重新分配内存并拷贝。新标准支持创建可动态调整大小的 Buffer。

// 创建最大支持 1024 字节的可调 Buffer
const resizableBuffer = new ArrayBuffer(16, { maxByteLength: 1024 });

console.log(resizableBuffer.resizable); // true
resizableBuffer.resize(32); // 扩容到 32 字节
console.log(resizableBuffer.byteLength); // 32

🌟 性能优化利器:所有权转移 (transfer)

在 Web Worker 通信或数据处理时,深度拷贝大块 Buffer 极其耗时。transfer() 可以直接转移底层内存的所有权,实现“零拷贝”。转移后,原 Buffer 所在的变量会被“剥夺”(长度变为 0,无法再访问)。

const buffer1 = new ArrayBuffer(8);
// 转移所有权到 buffer2,buffer1 被清空
const buffer2 = buffer1.transfer();

console.log(buffer1.byteLength); // 0 (已剥离)
console.log(buffer2.byteLength); // 8

1.2 TypedArray:特定类型的读写视图

如果你知道内存里存的全是同一种类型的数据(比如全是 8 位无符号整数,或全是 32 位浮点数),就用 TypedArray。 常见类型:Uint8Array (0~255)、Int16ArrayFloat32Array 等。

const buffer = new ArrayBuffer(16);
// 把这 16 字节当做 32 位浮点数(4字节)来看待,所以一共能存 4 个数字
const float32View = new Float32Array(buffer);

float32View[0] = 3.14;
console.log(float32View.length); // 4 (元素个数)
console.log(float32View.byteLength); // 16 (占用字节数)

1.3 DataView:复合类型的读写视图

如果一段内存里混合了多种类型的数据(例如第 1 个字节是标识符 Uint8,后 4 个字节是数据 Float32),或者你需要手动控制字节序(Endianness),就必须用 DataView

const buffer = new ArrayBuffer(16);
const view = new DataView(buffer);

view.setUint8(0, 255); // 在第 0 个字节处写入一个 Uint8
view.setFloat32(1, 3.14, true); // 在第 1 个字节处写入 Float32,并指定为小端字节序(Little-Endian)

console.log(view.getFloat32(1, true)); // 3.14

2. Blob (Binary Large Object)

Blob 表示一个不可变、原始数据的类文件对象。 如果说 ArrayBuffer 偏向于“内存计算”,那么 Blob 则偏向于“网络传输和高层应用”。

2.1 创建 Blob

你可以用字符串、ArrayBuffer 或其他 Blob 拼接出一个新 Blob,并指定 MIME 类型。

const htmlString = "<div>Hello Blob</div>";
const blob = new Blob([htmlString], { type: "text/html" });

console.log(blob.size); // 21 (字节数)
console.log(blob.type); // 'text/html'

2.2 切片 (slice) - 大文件分片上传的核心

Blob.prototype.slice() 是大文件分片上传/断点续传的底层 API。它非常快,因为它只创建新的引用指针,并不真正拷贝底层数据。

const CHUNK_SIZE = 1024 * 1024; // 1MB
const chunk = bigBlob.slice(0, CHUNK_SIZE, "application/octet-stream");

2.3 Object URL (用于预览和下载)

URL.createObjectURL(blob) 可以把一个 Blob 映射为一个短链接(blob:http://...)。这个链接可以直接赋值给 img.srca.href,常用于前端本地预览或生成下载链接。

const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "hello.html";
a.click();

// 极其重要:使用完毕后必须手动释放内存!
URL.revokeObjectURL(url);

3. File (特殊的文件 Blob)

File 接口基于 Blob,并在其基础上增加了用户文件系统相关的元数据(文件名、最后修改时间)。 前端通常通过 <input type="file"> 标签或拖拽事件(Drag & Drop)获取 File 对象。

const fileInput = document.querySelector('input[type="file"]');
fileInput.addEventListener("change", (e) => {
const file = e.target.files[0]; // 这是一个 File 对象
console.log(file.name); // 文件名,如 'avatar.png'
console.log(file.lastModified); // 最后修改时间戳
console.log(file instanceof Blob); // true,File 是 Blob 的子类
});

4. 各种格式的相互转换 (面试常考)

在处理文件上传、Canvas 海报导出、图片裁剪等业务时,经常需要在这几种类型间反复横跳。

4.1 Blob / File 转 ArrayBuffer 或 文本

现代浏览器推荐使用 Blob 实例上的异步方法(基于 Promise)。

const blob = new Blob(["hello world"]);

// 1. 转为 ArrayBuffer
const buffer = await blob.arrayBuffer();

// 2. 转为纯文本字符串
const text = await blob.text();

4.2 ArrayBuffer 转 Blob

直接作为数组元素传入 Blob 构造函数即可。

const buffer = new ArrayBuffer(16);
const blob = new Blob([buffer], { type: "application/octet-stream" });

4.3 Canvas 导出为 Blob (生成海报)

const canvas = document.getElementById("myCanvas");
canvas.toBlob(
(blob) => {
// 得到图片的 Blob 对象,可用于上传或创建本地 URL
},
"image/jpeg",
0.9,
);

4.4 Blob / File 转 Base64 (DataURL)

当需要把图片转成 Base64 字符串以便随 JSON 接口一起发送时,可以使用 FileReader

function blobToBase64(blob) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result); // result 即为 Base64 字符串
reader.onerror = reject;
reader.readAsDataURL(blob);
});
}