Binary Data In Frontend
Introduction
在日常的前端开发中,涉及到的工作内容大多和 UI 有关,比如说页面样式调整之类的基础工作。但是随着前端的发展,很多业务逻辑都放到了前端,例如文件生成和下载,图片处理等功能,这时候就涉及到了前端的二进制相关的内容,这篇文章从二进制相关内容和 API 出发,探究前端二进制相关的用途。
ArrayBuffer
ArrayBuffer 是一段基础的,固定长度的二进制数据,类似于其他语言的byte array
。不能直接修改相关的内容,但是可以通过TypedArray
和DataView
进行读写。
1 | const buffer = new ArrayBuffer(8); |
如上所示,可以通过 new ArrayBuffer 创建新的 buffer, slice 截取 buffer 内容,slice 的操作类似于Array.prototype.slice
TypedArray
TypedArray 提供了多种类型用来处理和操作二进制数据,如下所示[1]:
Uint8Array vs Uint8ClampedArray
Uint8Array 在处理小数位的时候采用的是向下取整,Uint8ClampedArray 则是采用四舍五入的形式取整,举个例子:
1 | Uint8Array([0.9]); // 0 |
Uint8ClampedArray 当赋值在区间[0,255]
之外,则只有取值为 0 或者 255 的两种情况,所以更多的运用于防止溢出的情况,例如增加图片的亮度[2]
overflow
TypedArray 的溢出处理方式简单来说就是抛弃溢出的位,然后按照视图类型进行解释。如下所示:
1 | const uint8 = new Uint8Array(1); |
256 转换为 2 进制为100000000
,但是 unsigned int 只能最多保存 8 位,最开始的1
被舍弃,结果为00000000
,转换为十进制的数则为 0,所以最终结果为 0
负数转化为二进制则是将对应的正数做否运算,然后加1
, 这里-1 对应的正整数为 1,转换为 unsigned int 为11111110
,加1
之后则为11111111
,转换为十进制的数则为 255,所以最终结果为 255
TypedArray 的溢出可以总结为如下:
- 当前类型的最高值加 1 会被转换为当前类型的最低值
- 当前类型的最低值减一则会被转换为当前类型的最高值
DataView
DataView 提供底层接口用来读写不同类型的组成的 ArrayBuffer,并且可以不关心不同环境下的字节序endianness。具体的使用可以参考MDN
Little Endian VS Big Endian
小端字节序(Little Endian)和大端字节序(Big Endian)主要是在存储大数的时候出现的不同存储规则,Little Endian 在存储数据的时候是按照从小到大的顺序存储的,常常用在本地数据存储交互的情况下。Big Endian 则按照从大到小的顺序存储,通常被称作network byte order
,更符合人类阅读理解习惯,更多的用于网络传输时的字节存储交互。
Blob(Binary Large Object)
Blob 表示二进制类型的大对象,在前端中多用于文件,音频,视频等内容。特点是 Blob 对象只读,不能进行相应的修改,可以通过Blob.prototype.slice()
来获取相应的分割之后的结果。Blob 在前端中的应用主要在以下方面。
File Download
结合URL
和Fetch
以及Blob
可以实现本地生成文件和远程下载文件
1 |
|
如上所示,演示了本地生成的 blob 和远程 fetch 的 blob 最终生成下载文件的整个过程,需要注意以下几点:
URL.createObjectURL()
方法可以允许使用 Blob 对象作为 URL 源,以实现下载二进制文件- 远程通过 fetch 方法获取到的 Response 对象,需要调用
Response.prototype.blob()
方法来将响应转化为 blob - 下载完成之后,调用
URL.revokeObjectURL()
方法来释放内存资源和性能优化
Image Preview
URL.createObjectURL()
创建的 URL 也可以用于img
元素的src
属性,以实现图片等文件的本地预览
1 |
|
需要注意的是 input 的 type 为 file 时,对应的 files 属性是一个FileList对象
Upload Sliced Files
分片上传在前端处理大文件上传的时候有以下的优点:
- 断点续传,大文件分割上传失败之后只需要上传对应失败的分片内容
- 某些服务器会有上传内容和上传时间的限制,分片上传可以避免这些限制
使用Blob.prototype.slice()
可以实现文件的分片上传,如下示例:
1 | const file = new File(['a'.repeat(1000000)], 'test.txt'); |
NOTICE
- 关于分片之后不同分片的到达后端的顺序,我们只需要传的时候把对应的分片序号一并带过去就可以,这样在所有的分片都传输完成之后,后端就可以将分片拼接成一个整体的文件
- 可以事先判断文件的 md5 值,对于每个文件,md5 值是唯一的。这样当重复的文件上传时,服务器只需要根据 md5 值先判断文件是否存在,如果存在则不需要再次传输,实现类似于”秒传”功能
Blob VS ArrayBuffer
- 除非你需要使用 ArrayBuffer 提供的写入/编辑的能力,否则 Blob 格式可能是最好的。
- Blob 对象是不可变的,而 ArrayBuffer 是可以通过 TypedArrays 或 DataView 来操作。
- ArrayBuffer 是存在内存中的,可以直接操作,而 Blob 可以位于磁盘、高速缓存内存和其他不可用的位置。
- 虽然 Blob 可以直接作为参数传递给其他函数,比如 window.URL.createObjectURL()。但是,你可能仍需要 FileReader 之类的 File API 才能与 Blob 一起使用。Blob 与 ArrayBuffer 对象之间是可以相互转化的:
- 使用 FileReader 的 readAsArrayBuffer()方法,可以把 Blob 对象转换为 ArrayBuffer 对象;
- 使用 Blob 构造函数,如 new Blob([new Uint8Array(data]);,可以把 ArrayBuffer 对象转换为 Blob 对象