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 对象