JavaScript内核与高级编程之:`JavaScript` 的 `Blob` 和 `ArrayBuffer`:其在二进制数据处理中的底层实现。

各位观众老爷,晚上好!今天咱们聊点硬核的,关于 JavaScript 里处理二进制数据的两个重量级人物:BlobArrayBuffer。 这俩哥们,平时可能不太起眼,但你要是玩音视频处理、文件上传下载、甚至是一些高级的 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

    这里,Int32ArrayArrayBuffer 视为一个包含两个 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 的内存。 Uint8ArrayArrayBuffer 视为 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 参数,指定字节序。 如果 littleEndiantrue,则使用小端字节序;否则,使用大端字节序。

  • 字节序的重要性:

    字节序是指多字节数据在内存中的存储顺序。 小端字节序是指低位字节存储在低地址,高位字节存储在高地址。 大端字节序是指高位字节存储在低地址,低位字节存储在高地址。 不同的计算机体系结构可能使用不同的字节序。 因此,在处理跨平台的数据时,必须注意字节序的问题。

    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 构造函数接受一个数组,数组中的每个元素可以是字符串、ArrayBufferTypedArray 或另一个 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。

第五幕:BlobArrayBuffer 的爱恨情仇

BlobArrayBuffer 经常一起使用。 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

    可以使用 FileReaderreadAsArrayBuffer() 方法从 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 转换为字符串 使用 TextDecoderArrayBuffer 解码为 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 });

结尾:二进制的世界,无限可能

BlobArrayBuffer 是 JavaScript 处理二进制数据的基石。 掌握它们,你就能在 Web 开发中处理各种复杂的任务,例如音视频处理、文件上传下载、WebSockets 等。 希望今天的分享对大家有所帮助! 下次有机会再和大家聊聊 WebAssembly,那可是二进制数据的究极形态! 感谢各位的观看!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注