二进制数据与文件处理
在处理音视频、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)、Int16Array、Float32Array 等。
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.src 或 a.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);
});
}