ArrayBuffer、TypedArray 与 DataView 详解
ArrayBuffer - 原始二进制数据缓冲区
[补充说明]:ArrayBuffer 表示通用的、固定长度的原始二进制数据缓冲区。
// 创建 ArrayBuffer [修正术语:Arraybuffer → ArrayBuffer]
const buffer = new ArrayBuffer(16); // 创建16字节的缓冲区
console.log(buffer.byteLength); // 16
// 不能直接操作 ArrayBuffer,需要通过视图操作
// console.log(buffer[0]); // 错误:不能直接访问TypedArray - 类型化数组视图
[补充说明]:TypedArray 提供了对 ArrayBuffer 的类型化视图访问。 在 JavaScript 中,TypedArray(类型化数组) 是一组用于处理二进制数据的数组-like对象,专为高效操作原始二进制数据而设计。它们与普通数组(Array)的主要区别在于:存储的数据类型固定(如整数、浮点数等),且长度不可变,因此性能更高,尤其适合处理大量二进制数据(如音频、图像、网络协议数据等)。
TypedArray 的特点
- 固定数据类型:每个元素的类型在创建时确定(如 8位整数、32位浮点数等),避免了普通数组的动态类型转换开销。
- 固定长度:创建时需指定长度,创建后无法通过
push/pop等方法改变(类似 C 语言的数组)。 - 存储原始二进制数据:直接映射到内存中的二进制数据缓冲区(
ArrayBuffer),读写效率远高于普通数组。 - 不继承
Array.prototype:不支持Array的部分方法(如concat、splice),但支持索引访问和length属性。
常见的 TypedArray 类型
JavaScript 提供了多种 TypedArray 构造函数,对应不同的数据类型和字节长度:
| 构造函数 | 元素类型 | 字节长度 | 取值范围(示例) |
|---|---|---|---|
Int8Array | 8位有符号整数 | 1 | -128 到 127 |
Uint8Array | 8位无符号整数 | 1 | 0 到 255 |
Uint8ClampedArray | 8位无符号整数(截断) | 1 | 超出范围时自动截断为 0 或 255 |
Int16Array | 16位有符号整数 | 2 | -32768 到 32767 |
Uint16Array | 16位无符号整数 | 2 | 0 到 65535 |
Int32Array | 32位有符号整数 | 4 | -2³¹ 到 2³¹-1 |
Uint32Array | 32位无符号整数 | 4 | 0 到 2³²-1 |
Float32Array | 32位浮点数(单精度) | 4 | 约 ±3.4×10³⁸ |
Float64Array | 64位浮点数(双精度) | 8 | 约 ±1.8×10³⁰⁸ |
TypedArray 的使用示例 TypedArray 必须基于 ArrayBuffer(原始二进制数据缓冲区)创建,直接创建会在内部自动创建,ArrayBuffer 是一块原始的内存区域,TypedArray 则是对这块内存的“视图”(按指定类型解析数据)。
1. 基本创建与使用
// 1. 创建一个 16 字节的 ArrayBuffer(内存缓冲区)
const buffer = new ArrayBuffer(16);
// 2. 创建 TypedArray 视图(以 32位整数解析 buffer)
const int32View = new Int32Array(buffer);
// 3. 长度由缓冲区大小和元素字节长度决定:16字节 / 4字节/元素 = 4个元素
console.log(int32View.length); // 4
// 4. 操作元素(索引访问)
int32View[0] = 100;
int32View[1] = 200;
console.log(int32View); // Int32Array(4) [100, 200, 0, 0]
// 查看对应的原始二进制数据缓冲区
// ArrayBuffer {
// [Uint8Contents]: <64 00 00 00 c8 00 00 00 00 00 00 00 00 00 00 00>,
// byteLength: 16
// }
// 5. 不同视图解析同一块缓冲区(二进制数据的灵活解读)
const uint8View = new Uint8Array(buffer);
console.log(uint8View); // Uint8Array(16) [100, 0, 0, 0, 200, 0, 0, 0, 0, ...]64 00 00 00 c8 00 00 00 00 00 00 00 00 00 00 00 低位—————————————————高位 对应16个字节,int32是用32个位,就是4个字节,也就是四个一组,转换为10进制: 对应100,200,0,0
2. 直接初始化
也可以直接通过长度或数组初始化 TypedArray(内部会对应的==自动创建== ==ArrayBuffer==):
// 用长度初始化(8个 16位整数)
const int16Arr = new Int16Array(8)
int16Arr[0] = 32767 // 最大16位有符号整数
console.log(int16Arr.buffer);
// ArrayBuffer {
// [Uint8Contents]: <ff 7f 00 00 00 00 00 00 00 00 00 00 00 00 00 00>,
// byteLength: 16
// }
// 用数组初始化
const float64Arr = new Float64Array([1.5, 2.5, 3.5])
console.log(float64Arr[1]) // 2.5
console.log(float64Arr.buffer)
// ArrayBuffer {
// [Uint8Contents]: <00 00 00 00 00 00 f8 3f 00 00 00 00 00 00 04 40 00 00 00 00 00 00 0c 40>,
// byteLength: 24
// }TypedArray 与普通数组的核心区别
| 特性 | TypedArray | 普通数组(Array) |
|---|---|---|
| 元素类型 | 固定(如 Int32、Float64) | 动态(可混合各种类型) |
| 长度 | 固定(创建后不可变) | 可变(可通过 push 等方法修改) |
| 存储方式 | 二进制缓冲区(高效) | 动态对象存储(有额外开销) |
| 继承关系 | 不继承 Array.prototype | 继承 Array.prototype |
| 适用场景 | 二进制数据处理(音视频等) | 通用数据存储与操作 |
// 不同类型的 TypedArray 视图
const int8Array = new Int8Array(buffer); // 8位有符号整数
const uint8Array = new Uint8Array(buffer); // 8位无符号整数
const int16Array = new Int16Array(buffer); // 16位有符号整数
const uint16Array = new Uint16Array(buffer); // 16位无符号整数
const int32Array = new Int32Array(buffer); // 32位有符号整数
const uint32Array = new Uint32Array(buffer); // 32位无符号整数
const float32Array = new Float32Array(buffer); // 32位浮点数
const float64Array = new Float64Array(buffer); // 64位浮点数
// 直接创建带数据的 TypedArray
const dataArray = new Uint8Array([1, 2, 3, 4, 5]);
console.log(dataArray.length); // 5
console.log(dataArray.byteLength); // 5DataView - 灵活的数据访问视图
[补充说明]:DataView 提供了更灵活的低级接口来读写 ArrayBuffer 中的数据。
// 创建 DataView
const buffer = new ArrayBuffer(4)
const view = new DataView(buffer)
// 设置和读取数据(可以指定字节偏移和端序)
view.setInt8(0, 127) // 在位置0写入8位有符号整数
console.log(view.buffer)
// ArrayBuffer { [Uint8Contents]: <7f 00 00 00>, byteLength: 4 }
// 小端序写入16位无符号整数
view.setUint16(2, 65534, true)
console.log(view.buffer);
// ArrayBuffer { [Uint8Contents]: <7f 00 fe ff>, byteLength: 4 }
console.log(view.getInt8(0))
// 127 因为就一位所以不分高低了
console.log(view.getUint16(2, true))
// 65534 (小端序读取)从低位(fe)到高位(ff)
console.log(view.getUint16(2))
// 65279 (大端序读取)反之内存布局与字节序
[补充说明]:理解内存布局对于二进制数据处理至关重要。
// 演示字节序(Endianness)的影响
const testBuffer = new ArrayBuffer(4);
const view1 = new DataView(testBuffer);
// 写入32位整数
view1.setInt32(0, 0x12345678, false); // 大端序
console.log(new Uint8Array(testBuffer)); // [0x12, 0x34, 0x56, 0x78]
view1.setInt32(0, 0x12345678, true); // 小端序
console.log(new Uint8Array(testBuffer)); // [0x78, 0x56, 0x34, 0x12]==实际应用示例==
// 示例:处理二进制文件格式(如图像文件头)
function parsePNGHeader(buffer) {
const view = new DataView(buffer);
// 检查PNG文件签名
const signature = view.getUint32(0, false);
if (signature !== 0x89504E47) {
throw new Error('不是有效的PNG文件');
}
// 读取图像尺寸
const width = view.getUint32(16, false);
const height = view.getUint32(20, false);
return { width, height };
}
// 示例:创建和操作二进制数据
function createBinaryData() {
const buffer = new ArrayBuffer(12);
const view = new DataView(buffer);
// 写入不同类型的数据
view.setFloat32(0, Math.PI, true); // 32位浮点数(小端序)
view.setUint16(4, 1000, false); // 16位无符号整数(大端序)
view.setInt8(6, -50); // 8位有符号整数
return buffer;
}性能优化与使用场景
| 数据类型 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| ArrayBuffer | 原始二进制数据存储 | 内存高效 | 不能直接操作 |
| TypedArray | 类型化数据处理 | 高性能,类型安全 | 固定数据类型 |
| DataView | 混合数据类型访问 | 灵活,支持端序控制 | 性能稍低 |
// 性能比较:TypedArray vs DataView
const testData = new ArrayBuffer(1000000);
// 使用 TypedArray(更快)
console.time('TypedArray');
const uint8View = new Uint8Array(testData);
for (let i = 0; i < uint8View.length; i++) {
uint8View[i] = i % 256;
}
console.timeEnd('TypedArray');
// 使用 DataView(更灵活但稍慢)
console.time('DataView');
const dataView = new DataView(testData);
for (let i = 0; i < testData.byteLength; i++) {
dataView.setUint8(i, i % 256);
}
console.timeEnd('DataView');与 Blob 的转换
// ArrayBuffer 转 Blob
const arrayBuffer = new ArrayBuffer(1024);
const blob = new Blob([arrayBuffer], {type: 'application/octet-stream'});
// Blob 转 ArrayBuffer
async function blobToArrayBuffer(blob) {
return await blob.arrayBuffer(); // 现代浏览器支持的方法
// 或使用 FileReader
// return new Promise((resolve) => {
// const reader = new FileReader();
// reader.onloadend = () => resolve(reader.result);
// reader.readAsArrayBuffer(blob);
// });
}Blob、File 与 Object URL 详解
Blob 对象
[补充说明]:Blob(Binary Large Object)表示不可变的原始数据类文件对象。
// 创建 Blob 对象
let debug = { name: 'Dano' }
let str = JSON.stringify(debug)
console.log(str)
// {"name":"Dano"} 15个字符
// 语法:new Blob(array, options)
let blob = new Blob([str], { type: 'application/json' })
// 获取 Blob 信息
console.log(blob) // Blob { size: 15, type: 'application/json' }
console.log(blob.size) // Blob 大小(字节)15
console.log(blob.type) // MIME 类型 application/json在JavaScript中,字符的存储方式取决于字符串的编码类型。JavaScript字符串使用UTF-16编码,这意味着每个字符通常使用==2个字节==(16位)存储。但是,对于Unicode中超出基本多文种平面(BMP)的字符(即码点大于0xFFFF的字符),它们需要使用两个16位码元(即==4个字节==)来表示,这称为代理对(surrogate pair)。
因此,一般情况下,一个字符占用2个字节,但对于辅助平面中的字符,则占用4个字节。
// 创建 Blob 对象
let debug = { name: 'Dano😊' }
let str = JSON.stringify(debug)
console.log(str)
let blob = new Blob([str], { type: 'application/json' })
// 获取 Blob 信息
console.log(blob)
console.log(blob.size)
console.log(blob.type)
// {"name":"Dano😊"}
// Blob { size: 19, type: 'application/json' }
// 19
// application/jsonFile 对象
[补充说明]:File 对象继承自 Blob,表示文件数据。
// 通过 input 获取 File 对象
const fileInput = document.getElementById('fileInput');
fileInput.addEventListener('change', (event) => {
const file = event.target.files[0];
console.log(file.name); // 文件名
console.log(file.size); // 文件大小
console.log(file.type); // 文件类型
console.log(file.lastModified); // 最后修改时间
});
// 创建 File 对象
const file = new File([JSON.stringify(debug)], 'user.json', {
type: 'application/json',
lastModified: Date.now()
});FileReader 读取文件内容
[修正代码]:FileReader是浏览器独有的API在Node中
let json = { name: 'Dano😊' }
const file = new File([JSON.stringify(json)], 'user.json', {
type: 'application/json',
lastModified: Date.now(),
})
console.log(file)
// File {
// size: 19,
// type: 'application/json',
// name: 'user.json',
// lastModified: 1757595534532
// }
let reader = new FileReader()
reader.readAsArrayBuffer(file)
reader.onload = function (e) {
const arrayBuffer = e.target.result
// 将 ArrayBuffer 转换为字符串
const decoder = new TextDecoder('utf-8')
const jsonString = decoder.decode(arrayBuffer)
const parsedJson = JSON.parse(jsonString)
// 显示结果
console.log(file, arrayBuffer, parsedJson)
}
reader.onerror = function (e) {
console.error('读取文件时出错: ', e.target.error)
}
Object URL
[补充说明]:创建指向Blob/File对象的临时URL
function download() {
let debug = {name: 'Domo'};
let blob = new Blob([JSON.stringify(debug)], {type: 'application/json'});
// 创建下载链接
let a = document.createElement('a');
a.download = 'user.json';
a.rel = 'noopener'; // 安全优化
a.href = URL.createObjectURL(blob);
// 模拟点击下载
a.click();
// 释放URL资源 [补充说明]
URL.revokeObjectURL(a.href);
}Base64 与 DataURL
[补充说明]:
// Data URL 格式:data:[<mediatype>][;base64],<data>
const dataURL = 'data:application/json;base64,eyJuYW1lIjoiRG9tbyJ9';
// Base64 编码解码
const base64Encoded = btoa('Hello World'); // 编码
const base64Decoded = atob('SGVsbG8gV29ybGQ='); // 解码
// Blob 转 Base64
function blobToBase64(blob) {
return new Promise((resolve) => {
const reader = new FileReader();
reader.onloadend = () => resolve(reader.result);
reader.readAsDataURL(blob);
});
}Base64 和 Data URL 不是同一个东西。 它们的关系非常密切,但扮演着完全不同的角色。 简单来说:
- Base64 是一种编码方式(把二进制数据变成文本)。
- Data URL 是一种 URL 方案(一种特殊格式的网址)。
- Data URL 经常使用 Base64 编码作为其数据部分。
Data URL
- 是什么:一种特殊格式的URL(统一资源定位符),它的协议头不是
http:或https:,而是data:。它允许将数据直接内嵌在文档中,而无需从外部服务器加载。 - 结构:一个完整的Data URL有固定的格式,它包含了元数据和数据本身。
data:[<mediatype>][;base64],<data> data::协议头,表明这是一个Data URL。[<mediatype>]:可选的MIME类型,告诉浏览器数据的格式是什么。例如image/png,text/css,application/pdf。如果省略,默认为text/plain。[;base64]:可选的标识符。如果存在,表示后面的<data>部分是经过 Base64编码的。如果不存在,则表示数据是URL编码的文本(通常仅适用于纯文本或SVG)。,:分隔符,前面是元信息,后面是数据本体。<data>:实际的数据内容。如果使用了;base64,这里就是Base64编码后的字符串。
例子: 一个PNG图片的Data URL可能长这样: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==
关系
- 依赖关系:Data URL 可以使用Base64编码,但不是必须的。对于二进制数据(如图片、PDF),必须使用Base64编码才能放入Data URL。对于纯文本(如CSS或SVG代码),可以不使用Base64,直接写入。
- 协作方式:Data URL 提供了一个完整的“包装”,告诉浏览器“这里面是什么类型的数据”以及“数据是如何编码的”。而Base64编码则负责完成实际的数据转换工作,将二进制数据变成Data URL能够安全使用的文本形式。
内存管理最佳实践
[补充说明]:
- 及时释放 Object URL:
const objectURL = URL.createObjectURL(blob);
// 使用完成后立即释放
URL.revokeObjectURL(objectURL);- 大文件处理:
// 使用切片处理大文件
const chunkSize = 1024 * 1024; // 1MB
let offset = 0;
while (offset < file.size) {
const chunk = file.slice(offset, offset + chunkSize);
// 处理分片...
offset += chunkSize;
}使用场景对比
| 技术 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| Object URL | 临时显示或下载文件 | 无需服务器,性能好 | 需要手动释放内存 |
| Data URL | 小文件内联 | 自包含,无需额外请求 | 体积增大33%,不适合大文件 |
| FileReader | 读取文件内容 | 多种读取方式 | 异步操作,需要回调 |
MIME
MIME 类型(也称为媒体类型或内容类型)是一种标准,用来表示文档、文件或字节流的性质和格式。 它的全称是 Multipurpose Internet Mail Extensions(多用途互联网邮件扩展)。顾名思义,它最初是为了在电子邮件中支持非 ASCII 字符和附件而设计的,但现在它被广泛应用在互联网的各个领域,尤其是 HTTP 协议中。
MIME 类型就是答案。 服务器在发送数据之前,会在 HTTP 响应的头部(Headers)中包含一个 Content-Type 字段,告诉浏览器(或其他客户端)正在发送的数据是什么类型。
Content-Type: text/html; charset=UTF-8 这行代码告诉浏览器:“接下来要发送的内容是 HTML 文本,使用的字符编码是 UTF-8”。浏览器收到这个信号后,就会启动 HTML 解析器来渲染页面。 如果没有 MIME 类型,浏览器只能靠猜测来处理数据,结果会非常不可靠和安全。
MIME 类型由类型(type) 和子类型(subtype) 两部分组成,中间由一条正斜杠 / 连接。 格式:type/subtype
1. 类型(Type)
代表大的类别,说明数据的一般类型。常见的顶级类型有:
text: 文本文件,人类可读的文字内容。- 例如:
text/plain,text/html,text/css
- 例如:
image: 图像文件或图形数据。- 例如:
image/jpeg,image/png,image/gif
- 例如:
audio: 音频或音乐数据。- 例如:
audio/mpeg,audio/wav
- 例如:
video: 视频或动态图像数据。- 例如:
video/mp4,video/webm
- 例如:
application: 二进制数据或不属于其他类别的数据。通常是需要由应用程序处理的文件。- 例如:
application/pdf,application/json,application/javascript
- 例如:
multipart: 由多个部分组成的数据,每个部分可以有自己独立的类型。常用于电子邮件或表单提交。- 例如:
multipart/form-data
- 例如:
font: 字体文件。- 例如:
font/woff2,font/ttf
- 例如:
2. 子类型(Subtype)
代表特定类型中的具体格式。它精确地指定了是哪种 text 或哪种 image。 例如,同是 text 类型,text/plain 是纯文本,而 text/html 是 HTML 文档。
常见 MIME 类型示例
| 文件格式 | MIME 类型 | 说明 |
|---|---|---|
.html | text/html | HTML 文档 |
.css | text/css | CSS 样式表 |
.js | application/javascript | JavaScript 文件 |
.json | application/json | JSON 数据 |
.png | image/png | PNG 图像 |
.jpg / .jpeg | image/jpeg | JPEG 图像 |
.pdf | application/pdf | Adobe PDF 文档 |
.zip | application/zip | ZIP 压缩档案 |
.mp3 | audio/mpeg | MP3 音频 |
.mp4 | video/mp4 | MP4 视频 |
重要参数:charset
对于一些文本类型的文件,经常需要指定一个重要的参数——字符编码(character set)。这用 charset 来表示,与主类型用分号 ; 分隔。
- 示例:
text/html; charset=UTF-8 - 作用: 告诉浏览器应该使用哪种字符编码来解读文本,防止出现乱码。对于现代 Web 开发,最常用的就是
UTF-8。
在实际中的应用
- HTTP 协议: 如上所述,在 HTTP 请求和响应的头部中,
Content-Type字段至关重要。 - 电子邮件: 用于标识邮件正文和附件的格式。
- 文件系统: 某些操作系统会用 MIME 类型来关联打开文件的默认应用程序。
<link>和<script>标签: 在 HTML 中,你可以指定引入资源的类型。 <link rel="stylesheet" type="text/css" href="theme.css"> <script type="application/javascript" src="app.js"></script> (注意:在现代 HTML5 中,type属性对于 CSS 和 JavaScript 通常可以省略,因为它们已是默认值。)