共计 5330 个字符,预计需要花费 14 分钟才能阅读完成。
在 Web 开发中,当咱们解决文件时(创立,上传,下载),常常会遇到二进制数据。另一个典型的利用场景是图像处理。
与其余语言相比,JavaScript 中的二进制数据是以非标准形式实现的。
1. 创立二进制数据
根本的二进制对象是 ArrayBuffer —— 对固定长度的间断内存空间的援用。
let buffer = new ArrayBuffer(16); // 创立一个长度为 16 的 buffer
alert(buffer.byteLength); // 16
它会调配一个 16 字节的间断内存空间,并用 0 进行预填充
留神:
ArrayBuffer
不是某种货色的数组
让咱们先廓清一个可能的误区。ArrayBuffer
与Array
没有任何共同之处:
- 它的长度是固定的,咱们无奈减少或缩小它的长度。
- 它正好占用了内存中的那么多空间。
- 要拜访单个字节,须要另一个“视图”对象,而不是
buffer[index]
。
ArrayBuffer
是一个内存区域。它外面存储了什么?无从判断。只是一个原始的字节序列。
2. 操作二进制数据
如要操作 ArrayBuffer
,咱们须要应用“视图”对象。
2.1 视图对象
视图对象自身并不存储任何货色。它是一副“眼镜”,透过它来解释存储在 ArrayBuffer
中的字节。
例如:
Uint8Array
—— 将ArrayBuffer
中的每个字节视为 0 到 255 之间的单个数字(每个字节是 8 位,因而只能包容那么多)。这称为“8 位无符号整数”。Uint16Array
—— 将每 2 个字节视为一个 0 到 65535 之间的整数。这称为“16 位无符号整数”。Uint32Array
—— 将每 4 个字节视为一个 0 到 4294967295 之间的整数。这称为“32 位无符号整数”。Float64Array
—— 将每 8 个字节视为一个5.0x10-324
到1.8x10308
之间的浮点数。
因而,一个 16 字节 ArrayBuffer 中的二进制数据能够解释为 16 个“小数字”,或 8 个更大的数字(每个数字 2 个字节),或 4 个更大的数字(每个数字 4 个字节),或 2 个高精度的浮点数(每个数字 8 个字节)。
2.2 应用视图操作二进制数据
ArrayBuffer
是外围对象,是所有的根底,是原始的二进制数据。
然而,如果咱们要写入值或遍历它,基本上简直所有操作 —— 咱们必须应用视图(view),例如:
let buffer = new ArrayBuffer(16); // 创立一个长度为 16 的 buffer
let view = new Uint32Array(buffer); // 将 buffer 视为一个 32 位整数的序列
alert(Uint32Array.BYTES_PER_ELEMENT); // 每个整数 4 个字节
alert(view.length); // 4,它存储了 4 个整数
alert(view.byteLength); // 16,字节中的大小
// 让咱们写入一个值
view[0] = 123456;
// 遍历值
for(let num of view) {alert(num); // 123456,而后 0,0,0(一共 4 个值)}
2.3 TypedArray
所有这些视图(Uint8Array
,Uint32Array
等)的通用术语是 TypedArray。它们共享同一办法和属性集。
请留神,没有名为 TypedArray
的结构器,它只是示意 ArrayBuffer
上的视图之一的通用总称术语。Int8Array
,Uint8Array
及其他,很快就会有残缺列表。
当你看到 new TypedArray
之类的内容时,它示意 new Int8Array
、new Uint8Array
及其他中之一。
类型化数组的行为相似于惯例数组:具备索引,并且是可迭代的。
一个类型化数组的结构器(无论是 Int8Array
或 Float64Array
,都无关紧要),其行为各不相同,并且取决于参数类型。
参数有 5 种变体:
new TypedArray(buffer, [byteOffset], [length]);
new TypedArray(object);
new TypedArray(typedArray);
new TypedArray(length);
new TypedArray();`
-
如果给定的是
ArrayBuffer
参数,则会在其上创立视图。咱们曾经用过该语法了。可选,咱们能够给定起始地位
byteOffset
(默认为 0)以及length
(默认至 buffer 的开端),这样视图将仅涵盖buffer
的一部分。 -
如果给定的是
Array
,或任何类数组对象,则会创立一个雷同长度的类型化数组,并复制其内容。咱们能够应用它来预填充数组的数据:
let arr = new Uint8Array([0, 1, 2, 3]); alert(arr.length); // 4,创立了雷同长度的二进制数组 alert(arr[1] ); // 1,用给定值填充了 4 个字节(无符号 8 位整数)`
-
如果给定的是另一个
TypedArray
,也是如此:创立一个雷同长度的类型化数组,并复制其内容。如果需要的话,数据在此过程中会被转换为新的类型。let arr16 = new Uint16Array([1, 1000]); let arr8 = new Uint8Array(arr16); alert(arr8[0] ); // 1 alert(arr8[1] ); // 232,试图复制 1000,但无奈将 1000 放进 8 位字节中(详述见下文)。
-
对于数字参数
length
—— 创立类型化数组以蕴含这么多元素。它的字节长度将是length
乘以单个TypedArray.BYTES_PER_ELEMENT
中的字节数:`let arr = new Uint16Array(4); // 为 4 个整数创立类型化数组
alert(Uint16Array.BYTES_PER_ELEMENT); // 每个整数 2 个字节
alert(arr.byteLength); // 8(字节中的大小)` - 不带参数的状况下,创立长度为零的类型化数组。
咱们能够间接创立一个 TypedArray
,而无需提及 ArrayBuffer
。然而,视图离不开底层的 ArrayBuffer
,因而,除第一种状况(已提供 ArrayBuffer
)外,其余所有状况都会主动创立 ArrayBuffer
。
如要拜访底层的 ArrayBuffer
,那么在 TypedArray
中有如下的属性:
arr.buffer
—— 援用ArrayBuffer
。arr.byteLength
——ArrayBuffer
的长度。
因而,咱们总是能够从一个视图转到另一个视图:
let arr8 = new Uint8Array([0, 1, 2, 3]);
// 同一数据的另一个视图
let arr16 = new Uint16Array(arr8.buffer);
上面是类型化数组的列表:
Uint8Array
,Uint16Array
,Uint32Array
—— 用于 8、16 和 32 位的整数。Uint8ClampedArray
—— 用于 8 位整数,在赋值时便“固定“其值(见下文)。Int8Array
,Int16Array
,Int32Array
—— 用于有符号整数(能够为正数)。Float32Array
,Float64Array
—— 用于 32 位和 64 位的有符号浮点数。
没有
int8
或相似的单值类型请留神,只管有相似
Int8Array
这样的名称,但 JavaScript 中并没有像int
,或int8
这样的单值类型。
这是合乎逻辑的,因为Int8Array
不是这些单值的数组,而是ArrayBuffer
上的视图。
2.4 越界行为
如果咱们尝试将越界值写入类型化数组会呈现什么状况?不会报错。然而多余的位被切除。
例如,咱们尝试将 256 放入 Uint8Array
。256 的二进制格局是 100000000
(9 位),但 Uint8Array
每个值只有 8 位,因而可用范畴为 0 到 255。
对于更大的数字,仅存储最左边的(低位无效)8 位,其余部分被切除:
因而后果是 0。
257 的二进制格局是 100000001
(9 位),最左边的 8 位会被存储,因而数组中会有 1
:
换句话说,该数字对 28 取模的后果被保留了下来。示例如下
let uint8array = new Uint8Array(16);
let num = 256;
alert(num.toString(2)); // 100000000(二进制示意)uint8array[0] = 256;
uint8array[1] = 257;
alert(uint8array[0]); // 0
alert(uint8array[1]); // 1
Uint8ClampedArray
在这方面比拟非凡,它的体现不太一样。对于大于 255 的任何数字,它将保留为 255,对于任何正数,它将保留为 0。此行为对于图像处理很有用。
3. TypedArray 办法
TypedArray
具备惯例的 Array
办法,但有个显著的例外。
咱们能够遍历(iterate),map
,slice
,find
和 reduce
等。
但有几件事咱们做不了:
- 没有
splice
—— 咱们无奈“删除”一个值,因为类型化数组是缓冲区(buffer)上的视图,并且缓冲区(buffer)是固定的、间断的内存区域。咱们所能做的就是调配一个零值。 - 无
concat
办法。
还有两种其余办法:
arr.set(fromArr, [offset])
从offset
(默认为 0)开始,将fromArr
中的所有元素复制到arr
。arr.subarray([begin, end])
创立一个从begin
到end
(不包含)雷同类型的新视图。这相似于slice
办法(同样也反对),但不复制任何内容 —— 只是创立一个新视图,以对给定片段的数据进行操作。
有了这些办法,咱们能够复制、混合类型化数组,从现有数组创立新数组等。
4. DataView
DataView 是在 ArrayBuffer
上的一种非凡的超灵便“未类型化”视图。它容许以任何格局拜访任何偏移量(offset)的数据。
- 对于类型化的数组,结构器决定了其格局。整个数组应该是对立的。第 i 个数字是
arr[i]
。 - 通过
DataView
,咱们能够应用.getUint8(i)
或.getUint16(i)
之类的办法拜访数据。咱们在调用办法时抉择格局,而不是在结构的时候。
语法:
new DataView(buffer, [byteOffset], [byteLength])
buffer
—— 底层的ArrayBuffer
。与类型化数组不同,DataView
不会自行创立缓冲区(buffer)。咱们须要当时筹备好。byteOffset
—— 视图的起始字节地位(默认为 0)。byteLength
—— 视图的字节长度(默认至buffer
的开端)。
例如,这里咱们从同一个 buffer 中提取不同格局的数字:
// 4 个字节的二进制数组,每个都是最大值 255
let buffer = new Uint8Array([255, 255, 255, 255]).buffer;
let dataView = new DataView(buffer);
// 在偏移量为 0 处获取 8 位数字
alert(dataView.getUint8(0) ); // 255
// 当初在偏移量为 0 处获取 16 位数字,它由 2 个字节组成,一起解析为 65535
alert(dataView.getUint16(0) ); // 65535(最大的 16 位无符号整数)// 在偏移量为 0 处获取 32 位数字
alert(dataView.getUint32(0) ); // 4294967295(最大的 32 位无符号整数)dataView.setUint32(0, 0); // 将 4 个字节的数字设为 0,行将所有字节都设为 0`
当咱们将混合格局的数据存储在同一缓冲区(buffer)中时,DataView
十分有用。例如,当咱们存储一个成对序列(16 位整数,32 位浮点数)时,用 DataView
能够轻松拜访它们。
2. 总结
ArrayBuffer
是外围对象,是对固定长度的间断内存区域的援用。
简直任何对 ArrayBuffer
的操作,都须要一个视图。
-
它能够是
TypedArray
:Uint8Array
,Uint16Array
,Uint32Array
—— 用于 8 位、16 位和 32 位无符号整数。Int8Array
,Int16Array
,Int32Array
—— 用于有符号整数(能够为正数)。Float32Array
,Float64Array
—— 用于 32 位和 64 位的有符号浮点数。
- 或
DataView
—— 应用办法来指定格局的视图,例如,getUint8(offset)
。
在大多数状况下,咱们间接对类型化数组进行创立和操作,而将 ArrayBuffer
作为“共同之处(common denominator)”暗藏起来。咱们能够通过 .buffer
来拜访它,并在须要时创立另一个视图。
还有另外两个术语,用于对二进制数据进行操作的办法的形容:
ArrayBufferView
是所有这些视图的总称。BufferSource
是ArrayBuffer
或ArrayBufferView
的总称。
咱们将在下一章中学习这些术语。BufferSource
是最罕用的术语之一,因为它的意思是“任何类型的二进制数据”—— ArrayBuffer
或其上的视图。
这是一份备忘单: