js 二进制相关
js 二进制相关
js
提供了ArrayBuffer
、TypedArray
(只是一个统称,没有一个叫 TypedArray
的对象)、DataView
、Blob
、File
等对象来处理二进制。
ArrayBuffer
ArrayBuffer
是 js
中基础的二进制对象,可以分配一段存放数据的连续内存区域。注意 ArrayBuffer
虽然名字里面有 Array
,但是和 Array
没有任何关系。该构造函数只是创建一个通用的固定长度的原始二进制数据缓冲区。
// 开辟了一个8字节的内存
const ab = new ArrayBuffer(8);
开发者不能直接操作 ArrayBuffer
的内容,而是要通过TypedArray
或 DataView
对象来操作,它们会将缓冲区中的数据表示为特定的格式,并通过这些格式来读写缓冲区的内容。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
和一个方法 slice
,byteLength
就是二进制缓冲区的字节数。slice
方法用来复制这个缓冲区的内容到一个新的 ArrayBuffer
中,接受两个参数,一个是开始的字节索引,另一个是结束的字节索引(不包括结束的这个字节)。
在xhr
中设置xhr.responseType = 'arraybuffer'
可以获取到二进制格式的响应。
TypedArray
一个 TypedArray
对象描述了ArrayBuffer
的类数组视图,TypedArray
不是全局属性,也不能作为构造函数使用。TypedArray
是对类型化数组的一个统称,实际上标准中定义了 TypedArray
的构造函数,但是并没有暴露出来,不过可以通 Object.getPrototypeOf(Int8Array)
来访问。
console.log(TypedArray);
console.log(Object.getPrototypeOf(Int8Array));
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
视图,还有两个可选的参数 byteOffset
和 length
,我们可以用这两个参数选择 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.subarray
和 TypedArray.prototype.slice
是有区别的,虽然他们都是通过 begin
和 end
索引(左闭右开)生成新视图,但是 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
: 数据源(ArrayBuffer
或SharedArrayBuffer
对象)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);
方法
DataView
跟TypedArray
不同,它内部可以直接通过不同方法来设置不同类型二进制数据。
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
的数据源。是一个由ArrayBuffer
、TypedArray
、Blob
、DOMString
等对象构成的Array
,或者其他类似对象的混合体,它将会被放进Blob
。DOMString
会被编码为UTF-8
。options
: 可选参数-
type
: 默认值是""
,这个值是blob
数据的MIME type
(text/html
、image/png
、text/plain
等)。
-
endings
:默认值为"transparent"
,用于指定包含行结束符\n
的字符串如何被写入。它是以下两个值中的一个:"native"
,代表行结束符会被更改为适合宿主操作系统文件系统的换行符,或者"transparent"
,代表会保持blob
中保存的结束符不变
// TODO 修改 demo,添加不同类型 demo
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
,接收一个Blob
(File
)对象,将其转化为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 URL
,Data 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()
都能处理 Blob
和 File
。
通常情况下,File
对象通过input
元素上选择文件或着拖拽生成得DataTransfer
对象中获取。
语法:
new File(bits, name[, options])
bits
: 必传,ArrayBuffer
,ArrayBufferView
,Blob
、Array[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
获取文件内容。