前端操作二进制数据的场景还是比较多的,像 webGL 中浏览器和 GPU 的通信、用户上传下载文件、canvas 导出图片、FileReader 读取本地文件、图片/音视频处理等等
注意:下文相关名词最好参考下 MDN,这里只做简短介绍
相关协议
除了 HttpURL,还有一些二进制数据相关的伪协议
object URL
(Blob URL
)。表示存储在浏览器内存中的 File 或 Blob 对象
// 创建 objectURL
URL.createObjectURL(Blob | File);
// 释放 objectURL
URL.revokeObjectURL(Blob | File);
Data URL
。携带 base64 数据
上述两种协议都可以作为资源 url 使用,比如在 img 标签中使用等等
相关对象
先看下几种二进制对象的关系:
原始二进制数据
我们知道,字节(byte)是计算机技术中关于二进制数据的一种基本单位,1 字节有 8 个二进制位,即 8 比特(bit)。前端可以通过 fetch 或者 ajax 请求从服务端获取二进制数据,具体表现形式其实就是字节流
fetch(httpURL | dataURL).then((res) => res.blob());
// res.arrayBuffer()
// res.formData() 表单数据
// res.json() JSON
// res.text() 文本
ArrayBuffer
**ArrayBuffer
**对象用于表示通用的原始二进制数据缓冲区,其实就是字节数组。通过这个对象,JavaScript 可以读写二进制数据,但是不能直接读写,可以借助 DataView
或 TypedArray
来读写 ArrayBuffer
// 创建一个 8 字节的 ArrayBuffer 数据
const arrayBuffer = new ArrayBuffer(8);
转换原始的二进制数据为 ArrayBuffer 对象,比如 fetch 内置了对应的方法进行转换:
fetch(url).then((res) => res.arrayBuffer());
Blob
Blob 对象(binary large object
)表示一个不可变、原始数据的类文件对象,它可以表示包含任意类型数据的不可变原始数据,例如图像、音频、视频、压缩文件等。Blob 提供了更便捷的文件操作方法,通过 Blob 对象可以实现文件上传、预览、读取、下载等操作,然后也可以借助它提供的 slice 方法实现数据切分上传
new Blob(array, option);
// array 可以是服务端返回的二进制文件流、blob、ArrayBuffer、DOMString等
// option.type 指定 MIME 类型
注意:浏览器通常使用 MIME 类型(而不是文件扩展名)来确定如何处理 URL,因此 Web 服务器在响应头中添加正确的 MIME 类型非常重要(详见 MDN-MIME)
ArrayBuffer 转 Blob:
const buffer = new ArrayBuffer(32);
const blob = new Blob([buffer]);
Blob 转 ArrayBuffer,借助 FileReader:
const blob2 = new Blob(["blob string"], {
type: "text/plain",
});
const reader = new FileReader();
reader.readAsArrayBuffer(blob2);
reader.onload = function (e) {
console.log(reader.result); // ArrayBuffer {}
const buf = new DataView(reader.result);
console.log(buf); // DataView {}
reader.readAsText(new Blob([buf]), "utf-8");
reader.onload = function () {
console.log(reader.result); // blob string
};
};
Blob
与 ArrayBuffer
的关系:
- 需要对二进制数据进行写入/编辑操作时使用
ArrayBuffer
,否则一般使用Blob
Blob
对象不可变,而 ArrayBuffer 可以通过TypedArrays
或DataView
操作ArrayBuffer
是内存上一段连续的二进制数据,Blob
是一个封装二进制数据的整体,可以封装ArrayBuffer
数据,可以位于磁盘、高速缓存内存和其他不同位置
File
继承了 Blob
,并将其扩展使其支持用户系统上的文件,File
对象还包含 lastModified
、name
等属性
const file = new File([Blob | ArrayBuffer], fileName);
哪些场景会生成 File?
input[file]
选择的文件- 拖拽生成的
dataTransfer
对象
base64
base64
是一种基于 64 个可打印字符来表示二进制数据的表示方法,常用于在处理文本数据的场合,表示、传输和存储一些二进制数据
可以通过 atob
和 btoa
这两个 api 处理解码和编码 base64 字符串
base64 转成 File:
function dataURLtoFile(dataurl, filename) {
const arr = dataurl.split(",");
// 将 base64 编码转为字符串
const byteStr = atob(arr[1]);
const mimeStr = dataURI.split(",")[0].split(":")[1].split(";")[0];
let n = byteStr.length;
// 创建初始化为0的长度为n的无符号整型数组
const u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = byteStr.charCodeAt(n);
}
return new File([u8arr], filename, {
type: mimeStr,
});
}
或者可以先转成 blob,再转成 File:
function dataURItoBlob(dataURI) {
const arr = dataurl.split(",");
// 将 base64 编码转为字符串
const byteStr = atob(arr[1]);
const mimeStr = dataURI.split(",")[0].split(":")[1].split(";")[0];
const ab = new ArrayBuffer(byteStr.length);
const ia = new Uint8Array(ab);
for (var i = 0; i < byteStr.length; i++) {
ia[i] = byteStr.charCodeAt(i);
}
return new Blob([ab], { type: mimeStr });
}
const file = new File(dataURItoBlob(dataURL));
FileReader
FileReader
是浏览器提供的一个 API,用于读取文件内容。通过 FileReader,我们可以通过异步方式读取文件,并将文件内容转换为可用的数据形式,比如文本数据或二进制数据
const reader = new FileReader();
reader.readAsDataURL(Blob | File);
// .readAsText()
// .readAsArrayBuffer()
应用场景
下载文件
如果请求函数是基于 axios:
- axios 对象需要配置
responseType: 'blob'
- 前端接收到数据后,使用
Blob
对象来接收二进制流,并且创建 a 标签进行下载;或者将二进制数据转成 base64 再下载
function saveBlob(content, fileName) {
let downLink = document.createElement("a");
downLink.download = fileName;
let blob = new Blob([content]);
// 生成 BlobURL
downLink.href = URL.createObjectURL(blob);
document.body.appendChild(downLink);
downLink.click();
document.body.removeChild(downLink);
}
上传文件
借助 FormData,我们可以上传二进制数据
// 可以先将文件转成 Blob
const file = new Blob();
const formData = new FormData();
formData.add("user", "JacksonZhou");
formData.add("file", file);
axios.post("url", formData);
不论是 Blob/File 还是 ArrayBuffer 对象,浏览器最终还是会将其转成字节流再进行传输
分片上传
blob.slice()
可以用于实现大文件的分片上传,这里简单带过,后续有时间再深入研究下 ~
const chunkSize = 100000;
const url = "https://file.zhouweibin.top/upload";
async function selectFile(e) {
const file = e.target.files[0];
await uploadFile(file);
}
async function uploadFile(file) {
for (let start = 0; start < file.size; start += chunkSize) {
const chunk = file.slice(start, start + chunkSize + 1);
const formData = new FormData();
formData.append("data", chunk);
await fetch(url, { method: "post", body: formData }).then((res) => {
console.log(res);
});
}
}
文件预览
- 图片预览:
File -> FileReader -> Blob -> objectUrl -> img
文件处理
- 拼接两个音频文件:
data -> ArrayBuffer -> TypedArray -> 拼接成一个 TypedArray -> ArrayBuffer -> Blob -> Object URL
- 图片压缩:
canvas.toBlob(callback, mimeType, qualityArgument)
- 图片灰度化,需要操作到图片像素数据
function draw() {
const ctx = canvas.getContext("2d");
ctx.drawImage(image, 0, 0, 230, 230);
// 基础像素数据
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
const grayscale = function () {
for (let i = 0; i < data.length; i += 4) {
const avg = (data[i] + data[i + 1] + data[i + 2]) / 3;
data[i] = avg;
data[i + 1] = avg;
data[i + 2] = avg;
}
ctx.putImageData(imageData, 0, 0);
};
}
其他文件操作方法
File System API
。一组用于在 Web 应用程序中访问和操作本地文件系统的 JavaScript API。它允许 Web 应用程序创建、读取、写入、复制、移动和删除文件和目录,以及查询文件系统的状态和属性- 更快速且安全的本地文件读写 - OPFS