二进制数据及文件
二进制数组
ArrayBuffer
对于二进制数据(如文件的创建、下载、上传),使用二进制性能会更高。
JavaScript 的二进制数据采用非标准方式实现
基本二进制对象:ArrayBuffer(对固定长度的连续内存空间引用)
// 分配一个 16 字节的连续内存空间,并用 0 进行预填充
let buffer = new ArrayBuffer(16); // 创建一个长度为 16 的 buffer
console.info(buffer.byteLength); // 16
注意:
ArrayBuffer只是一个连续空间的引用,储存着原始字节序列,此外什么也不是。- 与
Array要进行区别看待:长度固定,无法动态增加或减少长度。 - 访问内部数据不能使用下标访问元素,需要使用视图对象。
视图对象 TypedArray
想要查看 ArrayBuffer 内部数据,需要借助视图对象,统称为 TypedArray。其本身不存储任何数据,仅用来解释 ArrayBuffer。
Uint8Array:将ArrayBuffer中的每个字节视为 0 到 255 之间的单个数字(8 位无符号整数)。Uint16Array:将每 2 个字节视为一个 0 到 65535 之间的整数(16 位无符号整数)。Uint32Array:将每 4 个字节视为一个 0 到 4294967295 之间的整数(32 位无符号整数)。Float64Array:将每 8 个字节视为一个约 ±2.2×10⁻³⁰⁸ 到 ±1.8×10³⁰⁸ 之间的浮点数(双精度 IEEE 754)。
因此,一个 16 字节长度的 ArrayBuffer 在不同视图对象中表示不同的数据:
| Uint8Array | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
| Uint16Array | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | ||||||||
| Uint32Array | 0 | 1 | 2 | 3 | ||||||||||||
| Float64Array | 0 | 1 |
完整视图对象一览表:
| 类型名称 | 位宽(bit) | 字节数(byte) | 取值范围(十进制) | 是否有符号 | 特性说明 |
|---|---|---|---|---|---|
| Uint8Array | 8 | 1 | 0 到 255 | 否 | 标准无符号 8 位整数 |
| Uint8ClampedArray | 8 | 1 | 0 到 255 | 否 | 赋值时自动“钳制”:小于 0 → 0,大于 255 → 255,非整数四舍五入 |
| Int8Array | 8 | 1 | -128 到 127 | 是 | 有符号 8 位整数 |
| Uint16Array | 16 | 2 | 0 到 65,535 | 否 | 无符号 16 位整数 |
| Int16Array | 16 | 2 | -32,768 到 32,767 | 是 | 有符号 16 位整数 |
| Uint32Array | 32 | 4 | 0 到 4,294,967,295 | 否 | 无符号 32 位整数 |
| Int32Array | 32 | 4 | -2,147,483,648 到 2,147,483,647 | 是 | 有符号 32 位整数 |
| Float32Array | 32 | 4 | 约 ±1.2×10⁻³⁸ 到 ±3.4×10³⁸(含小数) | 是 | 单精度 IEEE 754 浮点数 |
| Float64Array | 64 | 8 | 约 ±2.2×10⁻³⁰⁸ 到 ±1.8×10³⁰⁸(含小数) | 是 | 双精度 IEEE 754 浮点数 |
注:
- 所有
TypedArray都基于ArrayBuffer,提供对底层二进制数据的高效访问。Uint8ClampedArray常用于图像处理(如 Canvas 的ImageData),确保颜色值始终在 [0, 255] 范围内。
对 ArrayBuffer 的所有操作都需要借助于视图对象,只有基于视图对象才知道每次应该操作多少个字节空间。
let buffer = new ArrayBuffer(16); // 创建一个长度为 16 的 buffer
let view = new Uint32Array(buffer); // 将 buffer 视为一个 32 位整数的序列
console.info(Uint32Array.BYTES_PER_ELEMENT); // 每个整数 4 个字节
console.info(view.length); // 4,它存储了 4 个整数
console.info(view.byteLength); // 16,字节中的大小
// 让我们写入一个值
view[0] = 123456;
// 遍历值
for(let num of view) {
console.info(num); // 123456,然后 0,0,0(一共 4 个值)
}
所有的视图对象都有以下五种参数变体,除了第一种传入了 buffer,其余都会自动创建 buffer,因为 buffer 是基础。
如要访问底层的 ArrayBuffer,那么在 TypedArray 中有如下的属性:
typedArr.buffer—— 引用ArrayBuffer。因此可以将一种
TypedArray转变为另一种TypedArray。typedArr.byteLength——ArrayBuffer的长度。
// 会在 buffer 上创建视图,可以指定起始字节偏移量 byteOffset,以及长度 length
new TypedArray(buffer, [byteOffset], [length]);
// 给的是 Array 或者类数组对象,会创建一个相同长度的类型化数组,并复制其内容
new TypedArray(object);
// 给的是其他 TypedArray,创建一个相同长度的类型化数组,并复制其内容,
// 对于原始数据超出当前 TypedArray 类型所能表示的最大数据时会造成越界被截断
// 如:Uint8 表示范围 0-255(八位,全为 1:11111111),
// 转换前数据为 256(二进制为:100000000),则会被截断成后八位(00000000)即为 0
new TypedArray(typedArray);
// 数字参数 length —— 创建类型化数组以包含这么多元素。
// 它的字节长度将是 length 乘以单个 TypedArray.BYTES_PER_ELEMENT 中的字节数
new TypedArray(length);
// 创建长度为零的类型化数组
new TypedArray();
TypedArray 方法
-
set(arrayOrTypedArray[, offset])
从offset(默认为0)位置开始,将另一个数组或类型化数组的所有元素复制到当前 TypedArray 中。const arr = new Uint8Array(5); arr.set([10, 20, 30], 1); // arr 变为 [0, 10, 20, 30, 0] -
subarray([begin[, end]])
返回一个相同类型的新 TypedArray 视图,覆盖从begin到end(不包括end)的原始缓冲区区域;不复制数据,仅创建新视图。const arr = new Int16Array([100, 200, 300, 400]); const sub = arr.subarray(1, 3); // Int16Array [200, 300] sub[0] = 999; console.log(arr[1]); // 999(原数组也被修改) -
slice([begin[, end]])
返回一个新分配的、独立副本的 TypedArray,包含从begin到end的元素(类似普通数组的slice);会复制数据。const arr = new Uint8Array([1, 2, 3, 4]); const copy = arr.slice(1, 3); // Uint8Array [2, 3] copy[0] = 99; console.log(arr[1]); // 2(原数组未变)
明确不支持的方法(与普通 Array 不同):
-
splice()
不可用。TypedArray 基于固定大小的ArrayBuffer,无法动态增删元素。若需“删除”,只能手动用0或其他值覆盖。 -
concat()
不可用。不能直接拼接两个 TypedArray。可通过set()或新建更大的 TypedArray 手动实现合并。
支持的常见 Array 方法(部分列举):
TypedArray 支持大多数不改变长度的数组方法,例如:
map(),filter(),reduce(),find(),forEach()indexOf(),includes(),every(),some()reverse(),fill(),sort()keys(),values(),entries()(用于迭代)
注意:
filter()和map()等返回新 TypedArray(同类型),而非普通数组。
DataView 超灵活视图
允许以任何格式访问任何偏移量(offset)的数据。
创建语法,参数与常规 TypedArray 一致:
new DataView(buffer, [byteOffset], [byteLength])
类型化的数组,构造器已经决定了格式,数组是统一的,第 i 个元素就是 typedArr[i],而 DataView 允许在调用时选择格式,而非构造时。
// 4 个字节的二进制数组,每个都是最大值 255
let buffer = new Uint8Array([255, 255, 255, 255]).buffer;
let dataView = new DataView(buffer);
// 在偏移量为 0 处获取 8 位数字
console.info( dataView.getUint8(0) ); // 255
// 现在在偏移量为 0 处获取 16 位数字,它由 2 个字节组成,一起解析为 65535
console.info( dataView.getUint16(0) ); // 65535(最大的 16 位无符号整数)
// 在偏移量为 0 处获取 32 位数字
console.info( dataView.getUint32(0) ); // 4294967295(最大的 32 位无符号整数)
dataView.setUint32(0, 0); // 将 4 个字节的数字设为 0,即将所有字节都设为 0
二进制字符文本
TextDecoder
将给定缓冲区的二进制数据转为文本字符串。
创建语法:
let decoder = new TextDecoder([label], [options]);
label:编码格式,默认utf-8,支持big5、windows-1251等许多其他编码格式。options:可选对象配置fatal:布尔类型true:为无效(不可解码)字符时抛出异常false:(默认)用字符\uFFFD替换无效字符
ignoreBOM:布尔类型true:忽略 BOM(可选的字节顺序 Unicode 标记),很少需要使用
解码语法:
let str = decoder.decode([input], [options]);
input:要被解码的BufferSourceoptions:可选对象配置stream:布尔类型true:将传入的数据块(chunk)作为参数重复调用 decoder。这种情况下,多字节的字符可能偶尔会在块与块之间被分割。这个选项告诉TextDecoder记住“未完成”的字符,并在下一个数据块来的时候进行解码。
let uint8Array = new Uint8Array([228, 189, 160, 229, 165, 189]);
console.info( new TextDecoder().decode(uint8Array) ); // 你好
TextEncoder
将字符串转换为字节。
创建语法:
let encoder = new TextEncoder();
注意:只支持 UTF-8 编码。
两个方法:
-
encode(str)
将字符串str编码为新的Uint8Array(返回新数组,不修改原数据)。const encoder = new TextEncoder(); const uint8 = encoder.encode("Hello"); // Uint8Array [72, 101, 108, 108, 111] -
encodeInto(str, destination)
将字符串str编码到预分配的destination(必须是Uint8Array)中,不返回新数组,而是返回对象{ read: number, written: number }(表示读取的字符数和写入的字节数)。const encoder = new TextEncoder(); const destination = new Uint8Array(10); // 预分配缓冲区 const result = encoder.encodeInto("Hello", destination); console.log(result); // { read: 5, written: 5 } console.log(destination); // Uint8Array [72, 101, 108, 108, 111, 0, 0, 0, 0, 0]
本质区别
| 方法 | 返回值 | 是否创建新数组 | 适用场景 |
|---|---|---|---|
encode(str) | 新 Uint8Array | ✅ 是 | 简单转换,无需复用缓冲区 |
encodeInto(str, destination) | 对象 { read, written } | ❌ 否 | 高性能场景:复用现有缓冲区,避免内存分配(如流处理、网络传输) |
为什么
encodeInto不返回Uint8Array?
因为它的设计目标是直接操作预分配的缓冲区(destination),而非创建新对象。这能显著减少内存分配开销(尤其在循环或大数据处理中),是 Web API 为性能优化的关键设计。
Blob
由一个可选的字符串 type(通常是 MIME 类型)和 blobParts 组成 —— 一系列其他 Blob 对象、字符串和 BufferSource。
Blob = image/png(type) + blob1、blob2、blob3、blob4...str、buffer(blobParts)
Blob 对象不允许修改,但可以自行对 blob 部分进行拼接。
创建语法:
new Blob(blobParts, options);
使用示例:
// 从类型化数组(typed array)和字符串创建 Blob
let hello = new Uint8Array([72, 101, 108, 108, 111]); // 二进制格式的 "hello"
let blob = new Blob([hello, ' ', 'world'], {type: 'text/plain'});
blobParts是Blob/BufferSource/String类型的值的数组。options可选对象:type—— Blob 类型,通常是 MIME 类型,例如image/png,endings—— 是否转换换行符,使 Blob 对应于当前操作系统的换行符(\r\n或\n)。默认为"transparent"(啥也不做),不过也可以是"native"(转换)。
可以用 slice 方法来提取 Blob 片段:
blob.slice([byteStart], [byteEnd], [contentType]);
允许传负数
byteStart—— 起始字节,默认为 0。byteEnd—— 最后一个字节(不包括,默认为最后)。contentType—— 新 blob 的 type,默认与源 blob 相同。
Blob 作 URL
let link = document.createElement('a');
link.download = 'hello.txt';
let blob = new Blob(['Hello, world!'], {type: 'text/plain'});
// URL.createObjectURL 取一个 Blob,并为其创建一个唯一的 URL,形式为 blob:<origin>/<uuid>
// 类似于 blob:https://javascript.info/1e67e00e-860d-40a5-89ae-6ab0cbee6273
link.href = URL.createObjectURL(blob);
link.click();
URL.revokeObjectURL(link.href);
对于创建的 Blob 链接,如果不再需要,应当手动移除引用,否则将会一直存在于内存中,无法释放。
URL.revokeObjectURL(url):从内部映射中移除引用,因此允许 Blob 被删除(如果没有其他引用的话),并释放内存。
Blob 转为 base64
除了使用 URL 创建链接外,也可将其转为 base64 编码的字符串作为链接:
<img src="data:image/png;base64,R0lGODlhDAAMAKIFAF5LAP/zxAAAANyuAP/gaP///wAAAAAAACH5BAEAAAUALAAAAAAMAAwAAAMlWLPcGjDKFYi9lxKBOaGcF35DhWHamZUW0K4mAbiwWtuf0uxFAgA7">
实际效果:
通过 base64 进行下载:
let link = document.createElement('a');
link.download = 'hello.txt';
let blob = new Blob(['Hello, world!'], {type: 'text/plain'});
let reader = new FileReader();
reader.readAsDataURL(blob); // 将 Blob 转换为 base64 并调用 onload
reader.onload = function() {
link.href = reader.result; // data url
link.click();
};
对比一览表:
| 特性 | URL.createObjectURL(blob) | Blob 转换为 Data URL (FileReader / blob.text() 等) |
|---|---|---|
| 使用方式 | const url = URL.createObjectURL(blob); | const reader = new FileReader(); reader.readAsDataURL(blob); |
| 返回值类型 | 短字符串(如 blob:https://example.com/abc-123) | 长字符串(如 data:image/png;base64,iVBORw0KG...) |
| 内存占用 | 低(仅创建引用,不复制数据) | 高(将整个 Blob 编码为 Base64 字符串,体积增大约 33%) |
| 性能 | 快速(O(1) 操作) | 较慢(需完整读取并编码 Blob,O(n) 时间和内存) |
| 是否需要手动释放 | ✅ 是,应调用 URL.revokeObjectURL(url) 避免内存泄漏 | ❌ 否,Data URL 是普通字符串,随作用域自动回收 |
| 适用场景 | 大文件预览、视频/音频播放、临时资源引用 | 小文件嵌入(如图标、小图)、需要内联数据的场景 |
| 浏览器兼容性 | 广泛支持(IE10+) | 广泛支持(IE10+,但需配合 FileReader) |
| 可缓存性 | 不可被 Service Worker 或 HTTP 缓存 | 可直接嵌入 HTML/CSS,但无法被外部缓存 |
| 安全性 | 同源策略保护,仅当前 origin 可访问 | 无特殊限制,但大字符串可能影响页面性能 |
- 优先使用
URL.createObjectURL:简单、高效,适合绝大多数场景。 - 记得调用
revokeObjectURL:在确定不再需要该 URL 时(如元素卸载后)释放资源。 - 仅对小 Blob 使用 Data URL:例如小于 100KB 的图像,用于 CSS background 或
<img src>内联。
// 推荐用法示例
const url = URL.createObjectURL(blob);
img.src = url;
img.onload = () => URL.revokeObjectURL(url); // 加载完成后释放
Image 转 Blob
图像操作是通过 <canvas> 元素来实现的:
- 使用
canvas.drawImage在 canvas 上绘制图像(或图像的一部分)。 - 调用 canvas 方法
.toBlob(callback, format, quality)创建一个 Blob,并在创建完成后使用其运行 callback。
// 获取任何图像
let img = document.querySelector('img');
// 生成同尺寸的 <canvas>
let canvas = document.createElement('canvas');
canvas.width = img.clientWidth;
canvas.height = img.clientHeight;
let context = canvas.getContext('2d');
// 向其中复制图像(此方法允许剪裁图像)
context.drawImage(img, 0, 0);
// 我们 context.rotate(),并在 canvas 上做很多其他事情
// toBlob 是异步操作,结束后会调用 callback
canvas.toBlob(function(blob) {
// blob 创建完成,下载它
let link = document.createElement('a');
link.download = 'example.png';
link.href = URL.createObjectURL(blob);
link.click();
// 删除内部 blob 引用,这样浏览器可以从内存中将其清除
URL.revokeObjectURL(link.href);
}, 'image/png');
或者使用 async/await:
let blob = await new Promise(resolve => canvasElem.toBlob(resolve, 'image/png'));
Blob 转换为 ArrayBuffer
可以从 blob.arrayBuffer() 中获取最低级别的 ArrayBuffer:
// 从 blob 获取 arrayBuffer
const bufferPromise = await blob.arrayBuffer();
// 或
blob.arrayBuffer().then(buffer => /* 处理 ArrayBuffer */);
Blob 转换为 Stream
读取和写入超过 2 GB 的 blob 时,将其转换为 arrayBuffer 的使用来说会更加占用内存。这种情况下,可以直接将 blob 转换为 stream 进行处理。
stream 是一种特殊的对象,可以从它那里逐部分地读取(或写入)
Blob 接口里的 stream() 方法返回一个 ReadableStream,在被读取时可以返回 Blob 中包含的数据。
// 从 blob 获取可读流(readableStream)
const readableStream = blob.stream();
const stream = readableStream.getReader();
while (true) {
// 对于每次迭代:value 是下一个 blob 数据片段
let { done, value } = await stream.read();
if (done) {
// 读取完毕,stream 里已经没有数据了
console.log('all blob processed.');
break;
}
// 对刚从 blob 中读取的数据片段做一些处理
console.log(value);
}
File 和 FileReader
File
File 对象继承自 Blob,并扩展了与文件系统相关的属性和功能,主要用于表示用户设备上的文件。
获取方式
-
通过构造函数创建(较少使用):
new File(fileParts, fileName, [options])fileParts:Blob/BufferSource/String类型值的数组。fileName:文件名字符串。options:可选对象,支持:lastModified:最后一次修改的时间戳(整数,毫秒)。
-
从浏览器接口获取(最常见):
<input type="file">- 拖放(Drag and Drop)
- 剪贴板 API 等
此时,File对象会从操作系统获取真实的文件元信息。
属性(继承自 Blob 并扩展)
-
name
文件名(只读)。console.log(file.name); // "report.pdf" -
lastModified
文件最后修改时间的时间戳(毫秒,自 Unix Epoch 起),只读。console.log(new Date(file.lastModified)); // Mon Mar 10 2025 14:30:00 GMT+0800
⚠️ 注意:
<input type="file">的files是一个FileList(类数组),即使只选一个文件也需通过input.files[0]访问。
FileReader
FileReader 是一个用于异步读取 Blob 或 File 内容的对象。由于读取可能涉及磁盘 I/O,因此采用事件驱动模型。
构造函数
new FileReader()
创建一个新的 FileReader 实例,无参数。const reader = new FileReader();
主要方法
-
readAsArrayBuffer(blob)
将Blob读取为二进制ArrayBuffer,适用于处理原始二进制数据(如图像、音频解析)。reader.readAsArrayBuffer(file); -
readAsText(blob, [encoding])
将Blob读取为文本字符串;可指定编码(默认'utf-8')。reader.readAsText(file, 'utf-8'); -
readAsDataURL(blob)
将Blob读取并编码为 Base64 Data URL(如data:image/png;base64,...),常用于<img>标签预览。reader.readAsDataURL(imageFile); -
abort()
中止当前读取操作;触发abort和loadend事件。reader.abort(); // 取消读取
事件(按执行顺序)
| 事件 | 触发时机 |
|---|---|
loadstart | 开始读取 |
progress | 读取过程中(可用于显示进度) |
load | 成功完成读取 |
error | 读取出错 |
abort | 调用了 abort() |
loadend | 读取结束(无论成功、失败或中止) |
读取结果
-
reader.result
读取成功后的结果,类型取决于所用的readAs*方法:readAsArrayBuffer→ArrayBufferreadAsText→stringreadAsDataURL→string(Data URL)
-
reader.error
读取失败时的DOMException对象。
基本使用示例
<input type="file" onchange="handleFile(this)">
<script>
function handleFile(input) {
const file = input.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = function(e) {
console.log('内容:', reader.result);
// e.target.result 等价于 reader.result
};
reader.onerror = function() {
console.error('读取失败:', reader.error);
};
// 根据需求选择读取方式
if (file.type.startsWith('text/')) {
reader.readAsText(file);
} else if (file.type.startsWith('image/')) {
reader.readAsDataURL(file);
}
}
</script>
补充说明
- 不仅限于 File:
FileReader可读取任意Blob对象。 - Web Workers 中的同步版本:
在 Web Worker 中可使用FileReaderSync,其方法(如readAsText())同步返回结果,不会触发事件:// 仅在 Worker 中可用 const reader = new FileReaderSync(); const text = reader.readAsText(blob);
✅ 提示:对于图片预览等场景,优先考虑
URL.createObjectURL(file)(性能更好),仅在需要字符串形式(如上传前校验文本内容)时使用FileReader。