赞
踩
在 Web 开发中,当我们处理文件时(创建,上传,下载),经常会遇到二进制数据。另一个典型的应用场景是图像处理。
这些都可以通过 JavaScript 进行处理,而且二进制操作性能更高。
不过,在 JavaScript 中有很多种二进制数据格式,会有点容易混淆。仅举几个例子:
基本的二进制对象是 ArrayBuffer —— 对固定长度的连续内存空间的引用。
let buffer = new ArrayBuffer(16); // 创建一个长度为 16 的 buffer
alert(buffer.byteLength); // 16
它会分配一个 16 字节的连续内存空间,并用 0 进行预填充。
注意:ArrayBuffer 并不是一个数组,它只是一个连续的内存区域,仅仅用来暂存一下数据。
ArrayBuffer
是核心对象,是所有的基础,是原始的二进制数据。
如果要操作 ArrayBuffer,我们需要使用“视图”对象。
视图对象本身并不存储任何东西。它是一副“眼镜”,透过它来解释存储在 ArrayBuffer 中的字节。同样的东西用不同的方式来解析是不同的。
比如同样一个 16 字节 ArrayBuffer 中的二进制数据可以解释为 16 个“小数字”,或 8 个更大的数字(每个数字 2 个字节),或 4 个更大的数字(每个数字 4 个字节),或 2 个高精度的浮点数(每个数字 8 个字节)。
这里默认提供了几副固定了视角的眼镜:
Uint8Array
—— 将 ArrayBuffer 中的每个字节视为 0 到 255 之间的单个数字(每个字节是 8 位,因此只能容纳那么多)。这称为 “8 位无符号整数”。Uint16Array
—— 将每 2 个字节视为一个 0 到 65535 之间的整数。这称为 “16 位无符号整数”。Uint32Array
—— 将每 4 个字节视为一个 0 到 4294967295 之间的整数。这称为 “32 位无符号整数”。Float64Array
—— 将每 8 个字节视为一个 5.0x10-324 到 1.8x10308 之间的浮点数。DataView 是在 ArrayBuffer 上的一种特殊的超灵活“未类型化”视图。上面类型化的眼镜,一戴上视角就固定了,所有的数据都已固定格式解析。而 DataView 这幅眼镜,视角是可调的,戴上后,想要什么视角就能调什么视角。
内建的 TextDecoder
对象在给定缓冲区(buffer)和编码格式(encoding)的情况下,允许将值读取为实际的 JavaScript 字符串。
TextEncoder
做相反的事情 —— 将字符串转换为字节。
arrayBuffer,Uint8Array 及其他 BufferSource 是“二进制数据”,而 Blob
则表示“具有类型的二进制数据”,比 arrBuffer 层级更高。
Blob 带类型,所以很容易解析成对应文件,可以将 Blob 看成某种文件的二进制形式。比如图片的二进制形式。
又因为 Blob 的类型通常是 MIME 类型,所以 Blob 对象很适用于在浏览器中上传/下载。
XMLHttpRequest,fetch 等进行 Web 请求的方法可以自然地使用 Blob,当然也可以使用其他类型的二进制数据。
我们可以轻松地在 Blob 和低级别的二进制数据类型之间进行转换:
new Blob(...)
构造函数从一个类型化数组(typed array)中创建 Blob。blob.arrayBuffer()
从 Blob 中取回 arrayBuffer,然后在其上创建一个视图(view),用于低级别的二进制处理。当我们需要处理大型 blob 时,将其转换为 stream
非常有用。你可以轻松地从 blob 创建 ReadableStream
。Blob 接口的 stream()
方法返回一个 ReadableStream,其在被读取时返回 blob 中包含的数据。
Blob 由一个可选的字符串 type()和 blobParts 组成。blobParts 部分通常是一系列其他 Blob 对象,字符串 或 BufferSource。
构造函数的语法为:
new Blob(blobParts, options);
// 从字符串创建 Blob
let blob = new Blob(["<html>…</html>"], {type: 'text/html'});
// 请注意:第一个参数必须是一个数组 [...]
// 从类型化数组(typed array)和字符串创建 Blob
let hello = new Uint8Array([72, 101, 108, 108, 111]); // 二进制格式的 "hello"
let blob = new Blob([hello, ' ', 'world'], {type: 'text/plain'});
Blob 对象是不可改变的。
我们无法直接在 Blob 中更改数据,但我们可以通过 slice 获得 Blob 的多个部分,从这些部分创建新的 Blob 对象,将它们组成新的 Blob,等。就像字符串一样,无法只能更改内容,但可以拼接、删除得到一个新字符串。
用 slice 方法来提取 Blob 片段:
blob.slice([byteStart], [byteEnd], [contentType]);
参数值类似于 array.slice,也允许是负数。
URL.createObjectURL(blob)
可以为在内存中的 Blob 创建一个唯一的 URL。URL 形式为 blob:<origin>/<uuid>
。
浏览器内部将 URL 和 Blob 形成映射关系。通过这个短短的 URL 就可以访问到 Blob 了。这个 URL 用到, 等标签,Blob 数据就会作为请求的内容被下载。
<!-- download 特性(attribute)强制浏览器下载而不是导航 -->
<a download="hello.txt" href='#' id="link">Download</a>
<script>
let blob = new Blob(["Hello, world!"], {type: 'text/plain'});
link.href = URL.createObjectURL(blob);
</script>
点击 a 标签链接就会下载一个名为 hello.txt 的文件,文件内容为 hello, world!
有一个问题,Blob 始终存在内存中,当确认使用完后,我们可以手动将指向它的引用,也就是创建的 URL 断开。URL.revokeObjectURL(url)
从内部映射中移除引用。
除了使用URL.createObjectURL
转成 URL 进行映射内存下载外,还可以将 Blob 数据转换为 base64-编码的字符串数据。
data-url
上使用。
我们使用内建的 FileReader
对象来将 Blob 转换为 base64。它可以将 Blob 中的数据读取为多种格式。
let link = document.createElement('a');
link.download = 'hello.txt';
let blob = new Blob(['Hello, world!'], {type: 'text/plain'});
let reader = new FileReader();
reader.readAsDataURL(blob); // 将 Blob 转换为 base64 并调用 onload
reader.onload = function() {
link.href = reader.result; // data url
link.click();
};
两种下载文件的方式对比:
URL.createObjectURL(blob):
Blob 转换为 data url:
我们可以创建一个图像(image)的、图像的一部分、或者甚至创建一个页面截图的 Blob。这样方便将其上传至其他地方。
Blob 构造器允许从几乎任何东西创建 blob,包括任何 BufferSource。
但是,如果我们需要执行低级别的处理时,我们可以从 blob.arrayBuffer()
中获取最低级别的 ArrayBuffer:
// 从 bolb 获取 arrayBuffer
const bufferPromise = await blob.arrayBuffer();
// 或
blob.arrayBuffer().then(buffer => /* 处理 ArrayBuffer */);
什么是 stream 流?
流会将你想要从网络接受的资源分成一个个小的分块,然后按位处理它。现在浏览器就是这么干的,看视频边缓冲边继续看。
以前,如果我们想要处理某种资源(如视频、文本文件等),我们必须下载完整的文件,等待它反序列化成适当的格式,比如视频数据转成 mp4 格式,然后在完整地接收到所有的内容后再进行处理播放。
当 blob 很大时,我们就可以转成 stream,更好的上传和下载。
Blob 接口里的 stream() 方法返回一个 ReadableStream,在被读取时可以返回 Blob 中包含的数据。
// 从 blob 获取可读流(readableStream) const readableStream = blob.stream(); const stream = readableStream.getReader(); while (true) { // 对于每次迭代:value 是下一个 blob 数据片段 let { done, value } = await stream.read(); if (done) { // 读取完毕,stream 里已经没有数据了 console.log('all blob processed.'); break; } // 对刚从 blob 中读取的数据片段做一些处理 console.log(value); }
File 对象继承自 Blob,并扩展了与文件系统相关的功能。虽然 Blob 相对 ArrayBuffer 已经是高级对象了,但用起来还是不够方便,所以就有了更高级的对象。
有两种方式获取文件对象:
new File(fileParts, fileName, [options])
由于 File 是继承自 Blob 的,所以 File 对象具有相同的属性,附加:
举个例子:我们从 中获取 File 对象的方式:
<input type="file" onchange="showFile(this)">
<script>
function showFile(input) {
let file = input.files[0];
alert(`File name: ${file.name}`); // 例如 my.png
alert(`Last modified: ${file.lastModified}`); // 例如 1552830408824
}
</script>
FileReader 是一个对象,其唯一目的是从 Blob(因此也从 File)对象中读取数据。
可以读取为以下三种格式:
但是,在很多情况下,我们不必读取文件内容。就像我们处理 blob 一样,我们可以使用 URL.createObjectURL(file) 创建一个短的 url,并将其赋给 或 。这样,文件便可以下载文件或者将其呈现为图像,作为 canvas 等的一部分。
而且,如果我们要通过网络发送一个 File,那也很容易:像 XMLHttpRequest 或 fetch 等网络 API 本身就接受 File 对象。
https://zh.javascript.info/
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。