浏览器环境中二进制操作集合

279 阅读9分钟

前言

浏览中提供了许多使用 Javascript 操作二进制数据的 api 方法,初学 JS 时难免会傻傻分不清彼此。本文将一一介绍这些常见方法的基本定义和用法,希望对你有所帮助~

基础 Api 介绍

Blob

官方文档developer.mozilla.org/en-US/docs/…

Blob 是 JavaScript 中用于处理不可变的二进制数据对象,尤其在处理文件和数据流时非常有用。Blob (Binary Large Object) 可以用来创建、读取和操作二进制数据,比如图像、音频、视频文件等。

Blob 对象的创建主要使用 new Blob() 构造函数:

 const blob = new Blob([data], { type: 'mime/type' });
  • data 是包含数据的数组,可以是字符串、数组缓冲区(ArrayBuffer)、TypedArray 等。
  • type 是 MIME 类型,用于描述数据的格式,比如 image/jpeg, text/plain 等。

示例:

 const text = "Hello, world!";
 const blob = new Blob([text], { type: 'text/plain' });

ArrayBuffer

官方文档developer.mozilla.org/en-US/docs/…

ArrayBuffer 是 JavaScript 中用于处理二进制数据的对象,尤其适用于处理大规模的原始二进制数据,例如在 Web 应用中传输和解析图像、音频、视频文件,或进行网络通信时的数据处理。ArrayBuffer 不能直接操作二进制数据,但可以通过视图(如 TypedArrayDataView)进行访问。

与 Blob 的区别

上文中提到可以用 ArrayBuffer 来创建 Blob 对象,那么它们既然都表示二进制数据,那么区别是什么呢?

一句话概括:ArrayBuffer 是可变的二进制缓冲区,而 Blob 是不可变的,如果要修改 Blob 的数据就只能新建一个 Blob 对象

用途上的区别:

ArrayBuffer:主要用于内存中的二进制数据操作。它允许创建可变大小的缓冲区,以字节为单位分配内存,并通过 TypedArrayDataView 视图来读取和写入其中的数据。ArrayBuffer 更接近底层,可以用来操作和解析复杂的二进制数据。

Blob:是不可变的二进制数据的高层封装,主要用于处理文件和数据流。Blob 的内容不可修改,并且它适合传输和保存数据(如下载或上传文件)。Blob 可以直接用于显示图像、音频、视频等数据,或者上传文件。

数据操作方式的区别:
  • ArrayBuffer:需要通过 TypedArray(如 Uint8ArrayInt32Array)或 DataView 来读写数据。TypedArray 可以直接访问数组元素,而 DataView 提供更灵活的字节级访问能力。
  • Blob:不支持直接访问或操作数据,只能通过 FileReader 将其读取为文本、Data URL、ArrayBuffer 等格式。

Blob 与 ArrayBuffer 之间的转换:

ArrayBufferBlob:可以通过 Blob 构造函数将 ArrayBuffer 转换为 Blob

 const buffer = new ArrayBuffer(8);
 const blob = new Blob([buffer], { type: 'application/octet-stream' });

备注: application/octet-stream 是一种 MIME 类型,用于表示任意的二进制数据。通常在以下场景中使用:

  1. 不确定文件类型时的默认类型:当服务器或客户端无法确定文件的具体类型时,会将其标记为 application/octet-stream。它充当一种“通用”二进制格式。
  2. 文件下载:浏览器在接收到 application/octet-stream 类型的文件时,通常会提示用户下载,而不是直接显示。因为它表示的内容是原始的二进制数据,可能不是图像、文本或其他常见格式,无法直接展示。
  3. 数据传输的二进制流:如果 Web 应用程序需要以二进制形式传输文件或数据块,可以使用 application/octet-stream 来定义内容类型。特别是在处理自定义文件格式或不常见的文件时。

示例:

在 HTTP 响应中,设置 application/octet-stream 来提示下载:

 Content-Type: application/octet-stream
 Content-Disposition: attachment; filename="example.bin"

这种方式会通知浏览器这是一个需要下载的二进制文件,不会尝试直接打开它。

BlobArrayBuffer:可以使用 FileReaderBlob 内容读取为 ArrayBuffer

 const reader = new FileReader();
 reader.onload = () => {
   const arrayBuffer = reader.result;
 };
 reader.readAsArrayBuffer(blob);

TypedArray

官方文档developer.mozilla.org/en-US/docs/…

TypedArray 适合低级别的字节操作,例如解码网络数据包、处理自定义文件格式等。利用 DataView 可以精确地在 ArrayBuffer 上按不同字节顺序或位数读写数据,为字节级操作提供更大的灵活性。

常见的 TypedArray 类型

  • Int8Array:8位有符号整数
  • Uint8Array:8位无符号整数
  • Int16Array:16位有符号整数
  • Uint16Array:16位无符号整数
  • Int32Array:32位有符号整数
  • Uint32Array:32位无符号整数
  • Float32Array:32位浮点数
  • Float64Array:64位浮点数

JavaScript 中的普通数组不支持直接存储和操作二进制数据,TypedArray 提供了对不同类型(如整数、浮点数等)的精确控制,可以根据需求选择对应的类型,例如 Uint8ArrayInt16ArrayFloat32Array。这种控制尤其适合需要进行底层数据处理的场景,比如音频、图像数据处理和计算密集型任务。

许多 Web API(如 WebGLWeb Audio APIWebSockets)都要求使用 TypedArray 来传递数据。TypedArray 提供了与底层 API 之间的桥梁,使数据可以高效、准确地传输。

  • WebGL:在图形编程中,TypedArray 用于传递顶点数据、颜色、纹理坐标等,通常用 Float32Array 来传递浮点数坐标或用 Uint16Array 传递索引。

     const vertices = new Float32Array([
       0.0,  1.0,  0.0,
      -1.0, -1.0,  0.0,
       1.0, -1.0,  0.0,
     ]);
    
  • Web Audio API:用于音频处理,通过 Float32Array 来传递音频缓冲数据。

DataView

官方文档developer.mozilla.org/en-US/docs/…

DataView 是 JavaScript 中一个用来操作和读取二进制数据的对象,特别是在处理 ArrayBuffer 类型的数据时非常有用。它允许你在不需要知道底层数据结构的情况下读取、写入不同类型的数据(如 int8, float32 等),这使得它非常适合用于处理二进制流或与网络协议打交道时。

 let buffer = new ArrayBuffer(8); // 创建一个 8 字节的缓冲区
 let view = new DataView(buffer);
 ​
 // 写入数据
 view.setInt8(0, 42); // 在偏移量 0 处写入一个 8 位带符号整数
 view.setInt32(1, 12345); // 在偏移量 1 处写入一个 32 位带符号整数
 ​
 // 读取数据
 console.log(view.getInt8(0));  // 输出 42
 console.log(view.getInt32(1)); // 输出 12345

File

官方文档developer.mozilla.org/en-US/docs/…

BlobFile 类似,但 File 对象专门用于表示用户在 <input type="file"> 中选择的文件。File 继承自 Blob,因此它有相同的属性和方法。

可以使用 new File() 创建一个 File 对象,传入 Blob 数据和文件名:

 const file = new File([blob], "example.txt", { type: "text/plain" });

FileReader

官方文档developer.mozilla.org/zh-CN/docs/…

读取 Blob 或者 File 对象的内容通常使用 FileReader,可以将 Blob 转换为文本、Data URL 或 ArrayBuffer 格式。

 const reader = new FileReader();
 reader.readAsText(blob);  // 读取为文本
 reader.onload = () => {
   console.log(reader.result); // 输出 Blob 的文本内容
 };

FileReader 支持以下几种读取方法:

  1. readAsText(blob): 读取 Blob 并返回文本。
 const reader = new FileReader();
 reader.onload = function(event) {
   console.log(event.target.result); // 文件的文本内容
 };
 reader.onerror = function(event) {
   console.error("文件读取失败", event);
 };
 
 reader.readAsText(file);
  1. readAsDataURL(blob): 读取 Blob 并返回 Base64 编码的数据 URL。
 const reader = new FileReader();
 reader.onload = function(event) {
   const dataURL = event.target.result;
   document.querySelector('img').src = dataURL; // 预览图片
 };
 ​
 reader.readAsDataURL(file);
  1. readAsArrayBuffer(blob): 读取 Blob 并返回 ArrayBuffer。
 const reader = new FileReader();
 reader.onload = function(event) {
   const arrayBuffer = event.target.result;
   console.log(arrayBuffer);
 };
 ​
 reader.readAsArrayBuffer(file);

Base64

官方文档developer.mozilla.org/en-US/docs/…

Base64 是一种用于将二进制数据编码为 ASCII 字符串的编码方式。它特别适用于需要通过文本传输的二进制数据,例如在 URL、电子邮件和 JSON 中传递图像、文档等数据。Base64 编码的核心思想是将每三个字节的二进制数据转换成四个可打印的 ASCII 字符。其编码过程如下:

  1. 将数据分割为字节组:将二进制数据分为每 3 个字节(24 位)为一组。
  2. 转换成字符:将 24 位分为 4 组,每组 6 位,每组使用一个 Base64 表中的字符表示。
  3. 填充:如果数据长度不是 3 的倍数,就用 0 进行填充,并在编码结果的末尾添加 = 符号来补足长度(最多可有两个 =)。

基础用法:

  1. Base64 和字符串的相互转换

注意btoaatob 仅适用于 ASCII 字符。如果包含非 ASCII 字符(如中文字符),需要对字符串进行编码(如 UTF-8 编码)后再进行 Base64 转换。

 // 将字符串编码为 Base64
 const encodeToBase64 = (input: string): string => {
   return btoa(input);
 };
 ​
 // 将 Base64 解码为字符串
 const decodeFromBase64 = (base64: string): string => {
   return atob(base64);
 };
 ​
 // 示例
 const original = "Hello, TypeScript!";
 const base64Encoded = encodeToBase64(original);
 console.log(base64Encoded); // "SGVsbG8sIFR5cGVTY3JpcHQh"
 console.log(decodeFromBase64(base64Encoded)); // "Hello, TypeScript!"
  1. Base64 和 Blob 的相互转换

Blob(如文件数据)转换为 Base64,或将 Base64 解码为 Blob,通常使用 FileReader API 来处理。

 // 将 Blob 转换为 Base64
 const blobToBase64 = (blob: Blob): Promise<string> => {
   return new Promise((resolve, reject) => {
     const reader = new FileReader();
     reader.onloadend = () => {
       const base64 = reader.result?.toString().split(",")[1]; // 去掉 data 前缀
       base64 ? resolve(base64) : reject("Failed to convert blob to base64");
     };
     reader.onerror = reject;
     reader.readAsDataURL(blob);
   });
 };
 ​
 // 将 Base64 转换为 Blob
 const base64ToBlob = (base64: string, mimeType: string): Blob => {
   const byteString = atob(base64);
   const byteNumbers = new Array(byteString.length).map((_, i) =>
     byteString.charCodeAt(i)
   );
   const byteArray = new Uint8Array(byteNumbers);
   return new Blob([byteArray], { type: mimeType });
 };
 ​
 // 示例
 const exampleBase64 = "SGVsbG8sIFR5cGVTY3JpcHQh";
 const exampleBlob = base64ToBlob(exampleBase64, "text/plain");
 blobToBase64(exampleBlob).then(console.log); // "SGVsbG8sIFR5cGVTY3JpcHQh"
  1. Base64 和 ArrayBuffer 的相互转换

ArrayBuffer 转换为 Base64 主要通过将二进制数据处理为字符串,再进行编码。同理,Base64 可以解码为 ArrayBuffer

 // 将 ArrayBuffer 转换为 Base64
 const arrayBufferToBase64 = (buffer: ArrayBuffer): string => {
   const bytes = new Uint8Array(buffer);
   let binary = "";
   for (let i = 0; i < bytes.length; i++) {
     binary += String.fromCharCode(bytes[i]);
   }
   return btoa(binary);
 };
 ​
 // 将 Base64 转换为 ArrayBuffer
 const base64ToArrayBuffer = (base64: string): ArrayBuffer => {
   const binaryString = atob(base64);
   const len = binaryString.length;
   const bytes = new Uint8Array(len);
   for (let i = 0; i < len; i++) {
     bytes[i] = binaryString.charCodeAt(i);
   }
   return bytes.buffer;
 };
 ​
 // 示例
 const buffer = new TextEncoder().encode("Hello, ArrayBuffer!").buffer;
 const base64EncodedBuffer = arrayBufferToBase64(buffer);
 console.log(base64EncodedBuffer); // Base64 string of ArrayBuffer
 console.log(new TextDecoder().decode(base64ToArrayBuffer(base64EncodedBuffer))); // "Hello, ArrayBuffer!"

实践

1. 创建一个文件的下载链接

 const downloadFile = (content: string | Blob, fileName: string, mimeType: string): void => {
   // 将内容包装为 Blob
   const blob = content instanceof Blob ? content : new Blob([content], { type: mimeType });
   
   // 创建 URL
   const url = URL.createObjectURL(blob);
 ​
   // 创建下载链接
   const link = document.createElement("a");
   link.href = url;
   link.download = fileName;
 ​
   // 触发点击事件自动下载
   link.click();
 ​
   // 清理 URL 对象
   URL.revokeObjectURL(url);
 };
 ​
 // 示例 - 下载文本文件
 downloadFile("Hello, TypeScript!", "example.txt", "text/plain");

2. 加解密操作

在前端的加密和解密应用中,ArrayBuffer 常用于存储和操作密钥、密文等原始二进制数据。许多现代浏览器支持 Crypto API,它允许开发者对 ArrayBuffer 类型的数据进行加密、解密、签名和验证。

 const data = new TextEncoder().encode('secret data');
 crypto.subtle.encrypt(
   { name: 'AES-GCM', iv: new Uint8Array(12) },
   encryptionKey,
   data
 ).then((encryptedData: ArrayBuffer) => {
   // 加密后的数据是 ArrayBuffer
 });

3. 音频处理和实时音频分析

在 Web Audio API 中,可以使用 ArrayBuffer 来处理和分析音频数据。例如,下载音频文件为 ArrayBuffer 后可以传递到 AudioContext 中进一步处理。这在音频应用和游戏开发中非常有用。

 const audioContext = new AudioContext();
 fetch('https://example.com/audio.mp3')
   .then(response => response.arrayBuffer())
   .then(arrayBuffer => audioContext.decodeAudioData(arrayBuffer))
   .then(audioBuffer => {
     // 可以在 AudioContext 中播放和分析音频数据
   });