js 二进制相关

js提供了ArrayBufferTypedArray(只是一个统称,没有一个叫 TypedArray 的对象)、DataViewBlobFile等对象来处理二进制。

ArrayBuffer

ArrayBufferjs 中基础的二进制对象,可以分配一段存放数据的连续内存区域。注意 ArrayBuffer 虽然名字里面有 Array ,但是和 Array 没有任何关系。该构造函数只是创建一个通用的固定长度的原始二进制数据缓冲区。

// 开辟了一个8字节的内存
const ab = new ArrayBuffer(8);

javascript 二进制编解码 js解析二进制数据_整型

开发者不能直接操作 ArrayBuffer 的内容,而是要通过TypedArrayDataView 对象来操作,它们会将缓冲区中的数据表示为特定的格式,并通过这些格式来读写缓冲区的内容。ArrayBuffer 并没有暴露太多方法和属性,构造函数本身有一个静态方法,isView 用来判断所给参数是否是一个 ArrayBuffer 的视图,其实就是判断是否是一个 TypedArray 或者 DataView 的实例。

const ab = new ArrayBuffer(8);
console.log(ArrayBuffer.isView(ab)); // false
console.log(ArrayBuffer.isView(new Int8Array([1]))); // true
console.log(ArrayBuffer.isView(new DataView(ab))); // true

ArrayBuffer.prototype 上暴露了一个属性 byteLength 和一个方法 slicebyteLength就是二进制缓冲区的字节数。slice方法用来复制这个缓冲区的内容到一个新的 ArrayBuffer 中,接受两个参数,一个是开始的字节索引,另一个是结束的字节索引(不包括结束的这个字节)。

xhr中设置xhr.responseType = 'arraybuffer'可以获取到二进制格式的响应。

TypedArray

一个 TypedArray 对象描述了ArrayBuffer类数组视图,TypedArray不是全局属性,也不能作为构造函数使用。TypedArray是对类型化数组的一个统称,实际上标准中定义了 TypedArray 的构造函数,但是并没有暴露出来,不过可以通 Object.getPrototypeOf(Int8Array) 来访问。

console.log(TypedArray);

javascript 二进制编解码 js解析二进制数据_javascript 二进制编解码_02

console.log(Object.getPrototypeOf(Int8Array));

javascript 二进制编解码 js解析二进制数据_javascript 二进制编解码_03


TypedArray类型

类型

值范围

字节大小

描述

Int8Array

-128 到 127

1

8 位有符号整型(补码)

Uint8Array

0 到 255

1

8 位无符号整型

Uint8ClampedArray

0 到 255

1

8 位无符号整型(一定在 0 到 255 之间)

Int16Array

-32768 到 32767

2

16 位有符号整型(补码)

Uint16Array

0 到 65535

2

16 位无符号整型

Int32Array

-2147483648 到 2147483647

4

32 位有符号整型(补码)

Uint32Array

0 到 4294967295

4

32 位无符号整型

Float32Array

-3.4E38 到 3.4E38 并且 1.2E-38 是最小的正数

4

32 位 IEEE 浮点数(7 位有效数字,例如 1.234567)

Float64Array

-1.8E308 到 1.8E308 并且 5E-324 是最小的正数

8

64 位 IEEE 浮点数(16 位有效数字,例如 1.23456789012345)

BigInt64Array

-2^63 到 2^63 - 1

8

64 位有符号整型(补码)

BigUint64Array

0 到 2^64 - 1

8

这 11 种 TypedArray 就是用来创建操作底层二进制缓冲区的视图的,为了处理不同数据类型的数据而被区分成了不同 size 不同含义的类型。TypedeArray的类型化数组必须使用 new 来创建,直接调用会报错!

通用传参

TypedArray指 11 种中的一个具体构造函数

new TypedArray();
new TypedArray(length);
new TypedArray(typedArray);
new TypedArray(object);

new TypedArray(buffer);
new TypedArray(buffer, byteOffset);
new TypedArray(buffer, byteOffset, length);

以下传参demo均使用Int8Array为例子:

无参数,相当于传入0作为length

创建一个 0 字节的缓冲区

const int8WithNoParam = new Int8Array();
console.log(int8WithNoParam.byteLength); // 0
传入一个数字(length

创建一个length字节的缓冲区

const int8WithLength = new Int8Array(2);
// 因为TypedArray是类数组,可以使用对象的方法引用数组中的元素,或使用标准数组索引语法 ( 即,使用括号注释)
for (const byte of int8WithLength) {
  int8WithLength[byte] = byte;
}
传入一个typedArray实例

创建一个数组长度跟传入的typedArray相同的新TypedArray实例。

const int16WithBuffer = new Int16Array([1000, 15000]);
const int8WithTypedArray = new Int8Array(int16WithBuffer);
console.log(int8WithTypedArray); // Int8Array(2)[-24, -104]
console.log(int16WithBuffer.byteLength); // 4  两项,每一项为2字节
console.log(int8WithTypedArray.byteLength); // 2 两项,每一项为1字节

虽然Int16Array每一项都是 2 个字节,但是作为参数传入Int8Array会把int16Array中的每一个值转换成Int8Array所对应的类型后再赋值。

传入一个对象

当传入一个对象时,该对象必须为类数组对象或者可迭代对象。

const int8WithObject = new Int8Array({ length: 2 });
console.log(int8WithObject); // Int8Array(2)[0, 0]
传入ArrayBuffer

传入一个 arraybuffer,这样就直接生成了这个指定 arraybuffer 视图,还有两个可选的参数 byteOffsetlength,我们可以用这两个参数选择 ArrayBuffer 的指定区域建立视图。这样我们就可以在一段 ArrayBuffer 上使用不同类型的 TypedArray,因为很多时候我们的 ArrayBuffer 中不都是相同类型的数据。

const ab = new ArrayBuffer(8);
const int8WithArrayBuffer = new Int8Array(ab, 1, 3);
console.log(int8WithArrayBuffer); // Int8Array(3) [0, 0, 0]

TypedArray 方法

关于挂载在 TypedArray 上的属性和方法就不一一介绍了,很多都可以和数组进行类比,详细内容查阅 MDN, 主要介绍两个普通数组中没有的方法:

set

用于从数组或者 TypedArray 中读取值,并保存在 TypedArray

语法:

typedarray.set(array[, offset]);
typedarray.set(typedarray[, offset])

第一个参数就是复制源,第二个参数表示复制的目标的起始位置(默认为 0)。如果复制源(参数)的长度加上 offset 已经超出了复制目标的长度,则会抛出错误。

const buffer = new ArrayBuffer(10);
const int8Array = new Int8Array(buffer);
// 从int8Array的第四项开始覆盖
int8Array.set([1, 2, 3], 3);
console.log(int8Array); // Int8Array(10) [0, 0, 0, 1, 2, 3, 0, 0, 0, 0]
subarray

返回一个基于相同 ArrayBuffer、元素类型也相同的 TypedArray的一部分。因为是基于同一个ArrayBuffer,所以通过任意视图修改的ArrayBuffer都会改变原来的TypedArray

语法:

typedarray.subarray([begin[, end]])
  • begin: 可选,元素开始的索引,不传默认返回全部元素
  • end: 可选,元素结束的索引,不传就默认选择到数组末尾。
const int8Array = new Int8Array([1, 2, 3, 4, 5]);
const sub = int8Array.subarray(1, 3);

sub[1] = 6;
console.log(int8Array); // Int8Array(5) [1, 2, 6, 4, 5]
console.log(sub); // Int8Array(2) [2, 6]

注意: TypedArray.prototype.subarrayTypedArray.prototype.slice 是有区别的,虽然他们都是通过 beginend 索引(左闭右开)生成新视图,但是 slice 生成的视图会同时生成一个新的 ArrayBuffer,而 subarray 不会。

const int8Array = new Int8Array([1, 2, 3, 4, 5]);
const slice = int8Array.slice(1, 3);
slice[1] = 7;
console.log(int8Array); // Int8Array(5) [1, 2, 3, 4, 5]
console.log(slice); // Int8Array(2) [2, 7]

DataView

DataView 视图是一个可以从二进制 ArrayBuffer 对象中读写多种数值类型的底层接口,使用它时,不用考虑不同平台的字节序问题。

语法:

new DataView(buffer)
new DataView(buffer, [,byteOffset,  [, byteLength])

传参说明:

  • buffer: 数据源(ArrayBufferSharedArrayBuffer 对象)
  • byteOffset: DataView 对象的第一个字节在 buffer中的偏移。如果不指定则默认从第一个字节开始
  • byteLength: DataView 对象的字节长度。如果不指定则默认与 buffer 的长度相同

如果由偏移(byteOffset)和字节长度(byteLength)计算得到的结束位置超出了 buffer 的长度, 会报 RangeError

比如

const ab = new ArrayBuffer(8);
// 从第四个字节开始,到16个,由于传入的buffer只有8个字节,因此会报RangeError
const dv = new DataView(ab, 4, 16);

方法

DataViewTypedArray不同,它内部可以直接通过不同方法来设置不同类型二进制数据

setInt8

DataView 起始位置以 byte 为计数的指定偏移量 (byteOffset) 处储存一个 8-bit 数(一个字节)

语法

setInt8(byteOffset, value)
  • byteOffset: 偏移量,当偏移量超出视图能储存的值,就会抛出错误
  • value: 设置的整数,如果设置的值超过Int8Array值范围时,会取余数来找到对应范围内的数字
const ab = new ArrayBuffer(2);
const dv = new DataView(ab);
dv.setInt8(0, 128); // 虽然超过 -128 ~ 127的范围,但是取余为1,对应范围内的第一个 -128,通过因此ab中的Int8Array的值为 [-128, 0]
dv.setInt8(2, 0); // 异常,ab只有两个字节,无法操作第三个字节
getInt8

DataView 起始位置以 byte 为计数的指定偏移量 (byteOffset) 处获取一个 8-bit 数(一个字节)

const ab = new ArrayBuffer(2);
const dv = new DataView(ab);
dv.setInt8(0, 128);
console.log(dv.getInt8(0)); // 128
console.log(db.getInt8(1)); // 0

其他方法类似就不介绍了,详情可查看MDN 文档

Uint8Array的取值遵循Unicode编码,可以通过String.fromCharCode转成字符串。
对于中文或者中文字符,由于Unicode编码不支持,需要进行utf-8解析。

const uint8 = new Uint8Array([228, 184, 173, 230, 150, 135]);const decoder = new TextDecoder("utf-8"); decoder.decode(uint8);

Blob

blob(Binary Large Object缩写),二进制大对象(用于存储二进制数据)。

js 中,blob对象是一个类似文件的不可变原始数据对象(不能通过像ArrayBuffer一样利用视图读写);它们可以作为文本或二进制数据读取,或转换为 ReadableStream 以便其方法可用于处理数据。Blob 也可以用来表示没有采用 JavaScript 原生格式的数据,File 接口就是继承自 Blob 并进行了扩展来支持用户系统中的文件。

blob 对象

可以用 Blob 构造函数创建一个新的 Blob 对象。

语法:

new Blob( array, options );
  • array: 可以理解为构建 Blob 的数据源。是一个由ArrayBufferTypedArrayBlobDOMString 等对象构成的 Array ,或者其他类似对象的混合体,它将会被放进 BlobDOMString 会被编码为 UTF-8
  • options: 可选参数
  • type: 默认值是 "",这个值是 blob 数据的 MIME typetext/htmlimage/pngtext/plain等)。
  • endings:默认值为"transparent",用于指定包含行结束符\n的字符串如何被写入。它是以下两个值中的一个:"native",代表行结束符会被更改为适合宿主操作系统文件系统的换行符,或者 "transparent",代表会保持 blob 中保存的结束符不变
var aFileParts = ['<a id="a"><b id="b">hey!</b></a>']; // 一个包含 DOMString 的数组
var oMyBlob = new Blob(aFileParts, { type: "text/html" });

方法

  • slice([start[, end[, contentType]]]):返回一个新的 Blob 对象,包含了源 Blob 对象中指定范围内的数据。
  • stream():返回一个能读取 blob 内容的 ReadableStream
  • text():返回一个 Promise 对象且包含 blob 所有内容的 UTF-8 格式的字符串。
  • arrayBuffer():返回一个 Promise 对象且包含 blob 所有内容的二进制格式的 ArrayBuffer

Blob URL

可以通过window.URL.createObjectURL,接收一个BlobFile)对象,将其转化为Blob URL。通过这个方法可以让浏览器在内存中保留对该文件的引用。

在每次调用 createObjectURL() 方法时,都会创建一个新的 URL 对象,即使你已经用相同的对象作为参数创建过。当不再需要这些 URL 对象时,每个对象必须通过调用 URL.revokeObjectURL() 方法来释放(可以在 sourceopen 被处理之后的任何时候调用 revokeObjectURL()。这是因为 createObjectURL() 仅仅意味着将一个媒体元素的 src 属性关联到一个 MediaSource 对象上去。调用revokeObjectURL() 使这个潜在的对象回到原来的地方,允许平台在合适的时机进行垃圾收集。虽然浏览器在 document 卸载的时候,也会自动释放它们)

const inp = document.createElement("input");
const img = document.createElement("img");
inp.multiple = true;
img.width = 300;
img.height = 300;
inp.type = "file";
inp.addEventListener("change", (e) => {
  img.src = window.URL.createObjectURL(e.target.files[0]);
  img.onload = () => {
    window.URL.revokeObjectURL(img.src);
  };
  document.body.append(img);
});
document.body.append(inp);

从 Blob 中读取数据

通过 FileReader可以读取Blob`或者文件对象并转化为其他格式的数据。

const reader = new FileReader();
reader.addEventListener("loadend", () => {
  // reader.result 包含被转化为类型化数组的 blob 中的内容
});
reader.readAsDataURL(blob);

FileReader方法

  • readAsText:该方法有两个参数,其中第二个参数是文本的编码方式,默认值为 UTF-8。这个方法非常容易理解,将文件以文本方式读取,读取的结果即是这个文本文件中的内容。
  • readAsBinaryString:该方法将文件读取为二进制字符串。
  • readAsDataURL:该方法将文件读取为一段以 data: 开头的字符串,这段字符串的实质就是 Data URLData URL是一种将小文件直接嵌入文档的方案。这里的小文件通常是指图像与 html 等格式的文件。

另一种读取 Blob 中内容的方式是使用 Response。下述代码将 Blob 中的内容读取为文本:

const text = await new Response(blob).text();

或者,也可以使用 Blob.prototype.text()

const text = await blob.text();

File

File接口提供有关文件的信息,并允许网页中的 JavaScript 访问其内容。
File 对象是特殊类型的 Blob,且可以用在任意的 Blob 类型的 context 中。比如说, FileReader, URL.createObjectURL(), createImageBitmap()XMLHttpRequest.send() 都能处理 BlobFile

通常情况下,File对象通过input元素上选择文件或着拖拽生成得DataTransfer对象中获取。

语法:

new File(bits, name[, options])
  • bits: 必传, ArrayBufferArrayBufferViewBlobArray[string]或者任何这些对象的组合。这是 UTF-8 编码的文件内容。
  • name: 必传,文件名称,或者文件路径.
  • options 选项对象,包含文件的可选属性:
  • type: 文件内容的 MIME 类型。默认值为 `` 。
  • lastModified: 文件最后修改时间的 Unix 时间戳(毫秒)。默认值为 Date.now()
<input type="file" id="input">

当上传成功后,可以通过 document.getElementById('input').files[0] 获取到上传的文件,即一个 File 对象,它是 Blob 的子类,可以通过 FileReader 或者 Response 获取文件内容。