在 Web 开发中,当我们处理文件时(创建,上传,下载),经常会遇到二进制数据。另一个典型的应用场景是图像处理。
这些都可以通过 JavaScript 进行处理,而且二进制操作性能更高。
不过,在 JavaScript 中有很多种二进制数据格式,会有点容易混淆。仅举几个例子:
- ArrayBuffer,Uint8Array,DataView,Blob,File 及其他。
ArrayBuffer 二进制数组
基本的二进制对象是 ArrayBuffer —— 对固定长度的连续内存空间的引用。
let buffer =newArrayBuffer(16);// 创建一个长度为 16 的 bufferalert(buffer.byteLength);// 16
它会分配一个 16 字节的连续内存空间,并用 0 进行预填充。
注意:ArrayBuffer 并不是一个数组,它只是一个连续的内存区域,仅仅用来暂存一下数据。
操作 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
DataView 是在 ArrayBuffer 上的一种特殊的超灵活“未类型化”视图。上面类型化的眼镜,一戴上视角就固定了,所有的数据都已固定格式解析。而 DataView 这幅眼镜,视角是可调的,戴上后,想要什么视角就能调什么视角。
- 对于类型化的数组,构造器决定了其格式。整个数组应该是统一的。第 i 个数字是 arr[i]。
- 通过 DataView,我们可以使用 .getUint8(i) 或 .getUint16(i) 之类的方法访问数据。我们在调用方法时选择格式,而不是在构造的时候。
TextDecoder 和 TextEncoder
内建的
TextDecoder
对象在给定缓冲区(buffer)和编码格式(encoding)的情况下,允许将值读取为实际的 JavaScript 字符串。
- 例如,我们收到了一个包含文本数据的文件,TextDecoder 可以解析为文本。
TextEncoder
做相反的事情 —— 将字符串转换为字节。
Blob
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 组成
Blob 由一个可选的字符串 type()和 blobParts 组成。blobParts 部分通常是一系列其他 Blob 对象,字符串 或 BufferSource。
构造函数的语法为:
newBlob(blobParts, options);
- blobParts 是 Blob/BufferSource/String 类型的值的数组。
- options 可选对象: - type —— Blob 类型,通常是 MIME 类型,例如 image/png,- endings —— 是否转换换行符,使 Blob 对应于当前操作系统的换行符(\r\n 或 \n)。默认为 “transparent”(啥也不做),不过也可以是 “native”(转换)。
// 从字符串创建 Bloblet blob =newBlob(["<html>…</html>"],{type:'text/html'});// 请注意:第一个参数必须是一个数组 [...]// 从类型化数组(typed array)和字符串创建 Bloblet hello =newUint8Array([72,101,108,108,111]);// 二进制格式的 "hello"let blob =newBlob([hello,' ','world'],{type:'text/plain'});
Blob 操作
Blob 对象是不可改变的。
我们无法直接在 Blob 中更改数据,但我们可以通过 slice 获得 Blob 的多个部分,从这些部分创建新的 Blob 对象,将它们组成新的 Blob,等。就像字符串一样,无法只能更改内容,但可以拼接、删除得到一个新字符串。
用 slice 方法来提取 Blob 片段:
blob.slice([byteStart],[byteEnd],[contentType]);
- byteStart —— 起始字节,默认为 0。
- byteEnd —— 最后一个字节(不包括,默认为最后)。
- contentType —— 新 blob 的 type,默认与源 blob 相同。
参数值类似于 array.slice,也允许是负数。
Blob 用作 URL 请求的内容
URL.createObjectURL(blob)
可以为在内存中的 Blob 创建一个唯一的 URL。URL 形式为
blob:<origin>/<uuid>
。
浏览器内部将 URL 和 Blob 形成映射关系。通过这个短短的 URL 就可以访问到 Blob 了。这个 URL 用到, 等标签,Blob 数据就会作为请求的内容被下载。
<!-- download 特性(attribute)强制浏览器下载而不是导航 --><adownload="hello.txt"href='#'id="link">Download</a><script>let blob =newBlob(["Hello, world!"],{type:'text/plain'});
link.href =URL.createObjectURL(blob);</script>
点击 a 标签链接就会下载一个名为 hello.txt 的文件,文件内容为 hello, world!
有一个问题,Blob 始终存在内存中,当确认使用完后,我们可以手动将指向它的引用,也就是创建的 URL 断开。
URL.revokeObjectURL(url)
从内部映射中移除引用。
Blob 转成 base64
除了使用
URL.createObjectURL
转成 URL 进行映射内存下载外,还可以将 Blob 数据转换为 base64-编码的字符串数据。
- 这种编码将二进制数据表示为一个由 0 到 64 的 ASCII 码组成的字符串,非常安全且“可读“。 - 这个字符串本身就是数据,数据在字里行间。
- 并且编码适用面更广,可以在任何地方使用这种 url,尤其是可以在
data-url
上使用。 - Data URL,即前缀为 data: 协议的 URL,其允许内容创建者向文档中嵌入小文件。
我们使用内建的
FileReader
对象来将 Blob 转换为 base64。它可以将 Blob 中的数据读取为多种格式。
let link = document.createElement('a');
link.download ='hello.txt';let blob =newBlob(['Hello, world!'],{type:'text/plain'});let reader =newFileReader();
reader.readAsDataURL(blob);// 将 Blob 转换为 base64 并调用 onload
reader.onload=function(){
link.href = reader.result;// data url
link.click();};
两种下载文件的方式对比:
URL.createObjectURL(blob):
- 如果介意内存,我们需要撤销(revoke)它们。
- 直接访问 Blob,无需“编码/解码”
Blob 转换为 data url:
- 无需撤销(revoke)任何操作。
- 对大的 Blob 进行编码时,性能和内存会有损耗。
Image 转换为 blob 上传
我们可以创建一个图像(image)的、图像的一部分、或者甚至创建一个页面截图的 Blob。这样方便将其上传至其他地方。
Blob 转换为 ArrayBuffer
Blob 构造器允许从几乎任何东西创建 blob,包括任何 BufferSource。
但是,如果我们需要执行低级别的处理时,我们可以从
blob.arrayBuffer()
中获取最低级别的 ArrayBuffer:
// 从 bolb 获取 arrayBufferconst bufferPromise =await blob.arrayBuffer();// 或
blob.arrayBuffer().then(buffer=>/* 处理 ArrayBuffer */);
Blob 转换为 Stream
什么是 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 和 FileReader
File 对象继承自 Blob,并扩展了与文件系统相关的功能。虽然 Blob 相对 ArrayBuffer 已经是高级对象了,但用起来还是不够方便,所以就有了更高级的对象。
获取 file 对象
有两种方式获取文件对象:
- 手动构造函数创建:
new File(fileParts, fileName, [options])
- fileParts —— Blob/BufferSource/String 类型值的数组。- fileName —— 文件名字符串。- options —— 可选对象: - lastModified —— 最后一次修改的时间戳(整数日期)。 - 操作系统提供,比如从 或拖放或其他浏览器接口来获取文件的时候,操作系统就会提供文件的信息。
由于 File 是继承自 Blob 的,所以 File 对象具有相同的属性,附加:
- name —— 文件名,
- lastModified —— 最后一次修改的时间戳。
举个例子:我们从 中获取 File 对象的方式:
<input type="file" onchange="showFile(this)"><script>functionshowFile(input){let file = input.files[0];alert(`File name: ${file.name}`);// 例如 my.pngalert(`Last modified: ${file.lastModified}`);// 例如 1552830408824}</script>
FileReader
FileReader 是一个对象,其唯一目的是从 Blob(因此也从 File)对象中读取数据。
可以读取为以下三种格式:
- 字符串(readAsText)。
- ArrayBuffer(readAsArrayBuffer)。
- data url,base-64 编码(readAsDataURL)。
但是,在很多情况下,我们不必读取文件内容。就像我们处理 blob 一样,我们可以使用 URL.createObjectURL(file) 创建一个短的 url,并将其赋给 或 。这样,文件便可以下载文件或者将其呈现为图像,作为 canvas 等的一部分。
而且,如果我们要通过网络发送一个 File,那也很容易:像 XMLHttpRequest 或 fetch 等网络 API 本身就接受 File 对象。
版权归原作者 卡列尼娜翠花 所有, 如有侵权,请联系我们删除。