各位观众老爷,晚上好!今天咱们聊点硬核的,关于 JavaScript 里处理二进制数据的两个重量级人物:Blob
和 ArrayBuffer
。 这俩哥们,平时可能不太起眼,但你要是玩音视频处理、文件上传下载、甚至是一些高级的 Web API,那绝对离不开它们。
开场白:数据,数据,还是数据!
在编程世界里,数据就是一切。但数据也有不同形态,最常见的是文本数据(字符串),但还有图片、音频、视频等二进制数据。浏览器最初是为文本而生,但现在早就不是那个只显示 HTML 的小家伙了。它得处理各种各样的二进制数据。所以,JavaScript 就需要有能力来操纵这些二进制数据。
第一幕:ArrayBuffer
– 内存里的原始二进制数据
ArrayBuffer
,你可以把它想象成一块连续的内存空间,里面存放着原始的二进制数据。注意,它仅仅是一块内存区域,不告诉你里面是什么类型的数据,也不提供任何读取或写入的接口。它就是纯粹的 0 和 1 的序列。
-
创建
ArrayBuffer
:// 创建一个 8 字节的 ArrayBuffer const buffer = new ArrayBuffer(8); console.log(buffer.byteLength); // 输出: 8
byteLength
属性告诉你这个ArrayBuffer
占用了多少字节的内存。 -
ArrayBuffer
的局限性:ArrayBuffer
本身是不能直接操作的。你不能直接读取或写入它的内容。它需要借助视图(View)才能发挥作用。
第二幕:TypedArray – 赋予 ArrayBuffer
类型
TypedArray 是一组特殊的数组类型,它们提供了访问 ArrayBuffer
中数据的接口,并且可以指定数据的类型。 常见的 TypedArray 包括:
-
Int8Array
: 8位有符号整数 -
Uint8Array
: 8位无符号整数 -
Int16Array
: 16位有符号整数 -
Uint16Array
: 16位无符号整数 -
Int32Array
: 32位有符号整数 -
Uint32Array
: 32位无符号整数 -
Float32Array
: 32位浮点数 -
Float64Array
: 64位浮点数 -
BigInt64Array
: 64位有符号大整数 -
BigUint64Array
: 64位无符号大整数 -
创建 TypedArray 视图:
// 创建一个 8 字节的 ArrayBuffer const buffer = new ArrayBuffer(8); // 创建一个 Int32Array 视图,指向该 ArrayBuffer const int32View = new Int32Array(buffer); console.log(int32View.length); // 输出: 2 (8 bytes / 4 bytes per int32 = 2) // 设置值 int32View[0] = 12345; int32View[1] = -67890; console.log(int32View[0]); // 输出: 12345 console.log(int32View[1]); // 输出: -67890
这里,
Int32Array
将ArrayBuffer
视为一个包含两个 32 位整数的数组。 注意int32View.length
是 2,因为 8 字节的ArrayBuffer
可以容纳两个 32 位整数。 -
共享内存:
TypedArray 视图并不复制数据,而是直接在
ArrayBuffer
上操作。这意味着,如果你修改了 TypedArray 视图中的数据,ArrayBuffer
中的数据也会被修改。反之亦然。const buffer = new ArrayBuffer(4); const uint8View = new Uint8Array(buffer); const int32View = new Int32Array(buffer); uint8View[0] = 0x12; uint8View[1] = 0x34; uint8View[2] = 0x56; uint8View[3] = 0x78; console.log(int32View[0]); // 输出: 2018915346 (0x78563412) 注意字节序
这个例子展示了 TypedArray 如何共享底层
ArrayBuffer
的内存。Uint8Array
将ArrayBuffer
视为 4 个 8 位无符号整数,而Int32Array
将其视为 1 个 32 位整数。 修改Uint8Array
会影响Int32Array
的值。 并且要注意字节序(Endianness)的问题, 不同平台字节存储的顺序不同。 -
不同视图:
可以为同一个
ArrayBuffer
创建多个不同类型的 TypedArray 视图。const buffer = new ArrayBuffer(8); const int16View = new Int16Array(buffer); const float32View = new Float32Array(buffer); int16View[0] = 1000; float32View[1] = 3.14; console.log(int16View[0]); // 输出: 1000 console.log(float32View[1]); // 输出: 3.140000104904175
这个例子展示了如何使用不同的 TypedArray 视图来访问和修改同一个
ArrayBuffer
中的数据。
第三幕:DataView
– 精确控制字节序
DataView
也是一种视图,它提供了更灵活的方式来读写 ArrayBuffer
中的数据。它允许你以任何偏移量和任何数据类型来访问 ArrayBuffer
中的数据,并且可以显式地指定字节序(大端或小端)。
-
创建
DataView
:const buffer = new ArrayBuffer(8); const dataView = new DataView(buffer); // 设置值 dataView.setInt32(0, 12345, true); // 从偏移量 0 开始,写入一个 32 位整数,使用小端字节序 dataView.setFloat64(4, 3.14, false); // 从偏移量 4 开始,写入一个 64 位浮点数,使用大端字节序 // 读取值 console.log(dataView.getInt32(0, true)); // 输出: 12345 console.log(dataView.getFloat64(4, false)); // 输出: 3.14
DataView
提供了getInt8()
,getUint8()
,getInt16()
,getUint16()
,getInt32()
,getUint32()
,getFloat32()
,getFloat64()
,getBigInt64()
,getBigUint64()
等方法来读取数据,以及对应的setInt8()
,setUint8()
,setInt16()
,setUint16()
,setInt32()
,setUint32()
,setFloat32()
,setFloat64()
,setBigInt64()
,setBigUint64()
等方法来写入数据。 每个方法都接受一个偏移量参数,指定从ArrayBuffer
的哪个位置开始读写数据,以及一个可选的littleEndian
参数,指定字节序。 如果littleEndian
为true
,则使用小端字节序;否则,使用大端字节序。 -
字节序的重要性:
字节序是指多字节数据在内存中的存储顺序。 小端字节序是指低位字节存储在低地址,高位字节存储在高地址。 大端字节序是指高位字节存储在低地址,低位字节存储在高地址。 不同的计算机体系结构可能使用不同的字节序。 因此,在处理跨平台的数据时,必须注意字节序的问题。
const buffer = new ArrayBuffer(4); const dataView = new DataView(buffer); dataView.setInt32(0, 0x12345678, false); // 大端字节序 console.log(new Uint8Array(buffer)); // 输出: Uint8Array(4) [ 18, 52, 86, 120 ] (0x12, 0x34, 0x56, 0x78) dataView.setInt32(0, 0x12345678, true); // 小端字节序 console.log(new Uint8Array(buffer)); // 输出: Uint8Array(4) [ 120, 86, 52, 18 ] (0x78, 0x56, 0x34, 0x12)
这个例子展示了字节序如何影响数据在内存中的存储顺序。
第四幕:Blob
– 文件数据的抽象
Blob
(Binary Large Object)表示一个不可变的、原始数据的类文件对象。 它可以包含文本数据,也可以包含二进制数据。 Blob
对象可以用来表示各种类型的文件数据,例如图片、音频、视频等。
-
创建
Blob
:// 从字符串创建 Blob const text = "Hello, Blob!"; const blob1 = new Blob([text], { type: 'text/plain' }); // 从 ArrayBuffer 创建 Blob const buffer = new ArrayBuffer(8); const uint8View = new Uint8Array(buffer); uint8View[0] = 0x41; // 'A' uint8View[1] = 0x42; // 'B' uint8View[2] = 0x43; // 'C' const blob2 = new Blob([buffer], { type: 'application/octet-stream' }); // 从多个部分创建 Blob const blob3 = new Blob([text, buffer], { type: 'mixed/content' }); console.log(blob1.size); // 输出: 13 console.log(blob1.type); // 输出: text/plain
Blob
构造函数接受一个数组,数组中的每个元素可以是字符串、ArrayBuffer
、TypedArray
或另一个Blob
对象。 第二个参数是一个可选的对象,用于指定Blob
的类型(MIME 类型)。 -
Blob
的属性:size
:Blob
的大小(字节数)。type
:Blob
的 MIME 类型。
-
读取
Blob
的内容:Blob
对象本身不能直接读取其内容。 你需要使用FileReader
对象来异步读取Blob
的内容。const blob = new Blob(["Hello, Blob!"], { type: 'text/plain' }); const reader = new FileReader(); reader.addEventListener('loadend', (e) => { const text = e.target.result; console.log(text); // 输出: Hello, Blob! }); reader.readAsText(blob); // 读取 Blob 的内容为文本
FileReader
提供了以下方法来读取Blob
的内容:readAsText(blob, encoding)
: 将Blob
的内容读取为文本字符串。encoding
参数是可选的,用于指定文本的编码方式。readAsArrayBuffer(blob)
: 将Blob
的内容读取为ArrayBuffer
。readAsDataURL(blob)
: 将Blob
的内容读取为 Data URL。 Data URL 是一种将数据编码为 URL 的方式,通常用于在 HTML 中嵌入图片或其他资源。readAsBinaryString(blob)
: 将Blob
的内容读取为二进制字符串(已废弃)。
-
Blob
的应用:- 文件上传: 可以将
Blob
对象作为FormData
的一部分上传到服务器。 - 文件下载: 可以将
Blob
对象创建为一个 URL,然后创建一个<a>
标签,并将href
属性设置为该 URL,从而实现文件下载。 - 图片处理: 可以使用
Blob
对象来处理图片数据,例如缩放、裁剪、旋转等。 - 音视频处理: 可以使用
Blob
对象来处理音视频数据,例如录制、播放、编码等。
- 文件上传: 可以将
-
URL.createObjectURL()
和URL.revokeObjectURL()
:URL.createObjectURL(blob)
方法可以创建一个指向Blob
对象的 URL。 这个 URL 可以用于在 HTML 中嵌入Blob
对象,例如在<img>
标签中显示图片。const blob = new Blob(["<p>Hello, Blob!</p>"], { type: 'text/html' }); const url = URL.createObjectURL(blob); const img = document.createElement('img'); img.src = url; document.body.appendChild(img); // 当不再需要该 URL 时,应该调用 URL.revokeObjectURL() 来释放资源 URL.revokeObjectURL(url);
URL.revokeObjectURL(url)
方法可以释放由URL.createObjectURL()
创建的 URL。 如果不释放 URL,可能会导致内存泄漏。 通常在Blob
对象不再使用时,应该立即释放 URL。
第五幕:Blob
与 ArrayBuffer
的爱恨情仇
Blob
和 ArrayBuffer
经常一起使用。 ArrayBuffer
负责存储原始的二进制数据,而 Blob
负责封装这些数据,并提供一些高级的功能,例如指定 MIME 类型、读取内容等。
-
Blob
包含ArrayBuffer
:Blob
可以直接包含ArrayBuffer
对象。const buffer = new ArrayBuffer(8); const uint8View = new Uint8Array(buffer); uint8View[0] = 0x41; // 'A' uint8View[1] = 0x42; // 'B' uint8View[2] = 0x43; // 'C' const blob = new Blob([buffer], { type: 'application/octet-stream' });
-
从
Blob
获取ArrayBuffer
:可以使用
FileReader
的readAsArrayBuffer()
方法从Blob
对象中获取ArrayBuffer
。const blob = new Blob(["Hello, Blob!"], { type: 'text/plain' }); const reader = new FileReader(); reader.addEventListener('loadend', (e) => { const buffer = e.target.result; console.log(buffer); // 输出: ArrayBuffer { byteLength: 13 } }); reader.readAsArrayBuffer(blob);
一些常用的技巧
技巧 | 说明 | 示例代码 |
---|---|---|
将字符串转换为 ArrayBuffer |
使用 TextEncoder 将字符串编码为 UTF-8 字节序列,然后将其存储在 ArrayBuffer 中。 |
javascript const encoder = new TextEncoder(); const buffer = encoder.encode("Hello, world!").buffer; console.log(buffer); // 输出: ArrayBuffer { byteLength: 13 } |
将 ArrayBuffer 转换为字符串 |
使用 TextDecoder 将 ArrayBuffer 解码为 UTF-8 字符串。 |
javascript const buffer = new Uint8Array([72, 101, 108, 108, 111, 44, 32, 119, 111, 114, 108, 100, 33]).buffer; const decoder = new TextDecoder(); const text = decoder.decode(buffer); console.log(text); // 输出: Hello, world! |
创建可下载的文件 | 创建一个 Blob 对象,然后使用 URL.createObjectURL() 创建一个 URL,然后创建一个 <a> 标签,并将 href 属性设置为该 URL,从而实现文件下载。 |
javascript const blob = new Blob(["Hello, world!"], { type: 'text/plain' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'hello.txt'; document.body.appendChild(a); a.click(); URL.revokeObjectURL(url); |
使用 fetch 上传二进制数据 |
将 ArrayBuffer 或者 Blob 作为 body 上传. |
javascript const buffer = new Uint8Array([1, 2, 3, 4, 5]).buffer; fetch('/upload', { method: 'POST', body: buffer }); |
结尾:二进制的世界,无限可能
Blob
和 ArrayBuffer
是 JavaScript 处理二进制数据的基石。 掌握它们,你就能在 Web 开发中处理各种复杂的任务,例如音视频处理、文件上传下载、WebSockets 等。 希望今天的分享对大家有所帮助! 下次有机会再和大家聊聊 WebAssembly,那可是二进制数据的究极形态! 感谢各位的观看!