历史上 JavaScript 是没有读写二进制数据能力的,但随着 ES5 中 Blob 对象的引入以及 ES6 中 ArrayBuffer 对象、TypedArray 和 DataView 对象的规范化, JS 处理二进制数据的能力大幅度增强,也能直接处理文件流,网络流等二进制 Buffer 数据了。
ArrayBuffer:
ArrayBuffer 代表存储二进制数据的一段内存,它不能直接读写,只能通过 TypedArray 视图和 DataView 视图来读写。通常用于在浏览器中进行底层的二进制数据处理。
创建 ArrayBuffer:
通过 ArrayBuffer 构造函数可以创建一段存放二进制数据的连续内存区域。
let buf = new ArrayBuffer(32) // 创建了一个长度为 32 个字节的连续内存区域。由于没有赋初始值,每一个字节单元的值都默认是 0。
实例属性:
byteLength
:只读属性,返回所分配的内存区域的字节长度。
实例方法:
slice(start, len)
:用来拷贝自身的一部分生成一个新的 ArrayBuffer 对象并返回。参数 start 为开始的位置;len 为拷贝的长度。
静态方法:
ArrayBuffer.isView()
:返回一个布尔值,表示参数是否为 ArrayBuffer 的视图实例。也就是,是否为 TypedArray 实例或 DataView 实例。
视图:
视图的作用是以指定格式解读二进制数据。
ArrayBuffer 在内存中存储的是一段未加工的二进制数据,本身只是0 和 1 ,通过不同的格式来解读就会赋予它不同的意义,这就是视图。
ArrayBuffer 有两种视图,一种是 TypedArray 视图,另一种是 DataView 视图,两者的区别主要是前者的数组成员都是同一个数据类型,后者的数组成员可以是不同的数据类型。
TypedArray:
TypedArray 对象一共提供 9 种类型的视图。每一种视图都是一种构造函数。接受三个参数,第一个参数可以为底层 ArrayBuffer、数组或类数组、数字;第二个参数为开始字节序号,默认为 0;第三个参数为长度,默认直到本段内存区域结束。返回生成的对象,统称为 TypedArray 对象。
- Int8Array:8位有符号整数,长度1个字节。
- Uint8Array:8位无符号整数,长度1个字节。
- Uint8ClampedArray:8位无符号整数,长度1个字节,溢出处理不同。
- Int16Array:16位有符号整数,长度2个字。
- Uint16Array:16位无符号整数,长度2个字节。
- Int32Array:32位有符号整数,长度4个字节。
- Uint32Array:32位无符号整数,长度4个字节。
- Float32Array:32位浮点数,长度4个字节。
- Float64Array:64位浮点数,长度8个字节。
let buf = new ArrayBuffer(32)
let int32 = new Int32Array(buf)
let uint8 = new Uint8Array(buf)
console.log(int32[0]) // 0
console.log(uint8[0]) // 0
// 还可以直接修改
int32[0] = 1
console.log(int32[0]) // 1
console.log(uint8[0]) // 1。由于两个视图对应的是同一段内存,一个视图修改底层内存,会影响到另一个视图。
实例属性:
buffer
:返回整段内存区域对应的 ArrayBuffer 对象。byteLength
:返回 TypedArray 数组占据的内存长度,单位为字节。byteOffset
:返回 TypedArray 数组从底层 ArrayBuffer 对象的哪个字节开始。length
:表示TypedArray 数组含有多少个成员。byteLength 是字节长度,length 是成员长度。
实例方法:
set()
:用于复制 TypedArray 数组,也就是将一段内容完全复制到另一段内存。subarray(start, end)
:截取 TypedArray 数组的一部分,再建立一个新的视图。slice()
:返回一个指定位置的新的 TypedArray 实例。
静态方法:
of()
:TypedArray 数组的所有构造函数,都有一个静态方法of()
,用于将参数转为一个 TypedArray 实例。from()
:接受一个可遍历的数据结构(比如数组)作为参数,返回一个基于这个结构的 TypedArray 实例。
DataView:
DataView 视图可以在内存中存放不同类型的数据。DataView 视图本身也是构造函数,接受一个 ArrayBuffer 对象作为参数,生成视图。
let buffer = new ArrayBuffer(32)
let dv = new DataView(buffer)
读取内存的方法:
都接受两个参数:第一个参数都是一个字节序号(不能是负数,否则会报错),表示从哪个字节开始读取;如果一次读取两个或两个以上字节,就必须明确数据的存储方式,到底是小端字节序还是大端字节序,默认情况下,DataView 的 get 方法使用大端字节序解读数据,如果需要使用小端字节序解读,必须在 get 方法的第二个参数指定 true。
- getInt8:读取 1 个字节,返回一个 8 位整数。
- getUint8:读取 1 个字节,返回一个无符号的 8 位整数。
- getInt16:读取 2 个字节,返回一个 16 位整数。
- getUint16:读取 2 个字节,返回一个无符号的 16 位整数。
- getInt32:读取 4 个字节,返回一个 32 位整数。
- getUint32:读取 4 个字节,返回一个无符号的 32 位整数。
- getFloat32:读取 4 个字节,返回一个 32 位浮点数。
- getFloat64:读取 8 个字节,返回一个 64 位浮点数。
let buffer = new ArrayBuffer(32)
let dv = new DataView(buffer)
// 从第1个字节读取一个8位无符号整数
var v1 = dv.getUint8(0);
// 小端字节序
var v1 = dv.getUint16(1, true)
写入内存的方法:
都接受三个参数:第一个参数是字节序号,表示从哪个字节开始写入;第二个参数为写入的数据;对于那些写入两个或两个以上字节的方法,需要指定第三个参数,false 表示使用大端字节序写入,true 表示使用小端字节序写入,默认是大端字节写入。
- setInt8:写入 1 个字节的 8 位整数。
- setUint8:写入 1 个字节的 8 位无符号整数。
- setInt16:写入 2 个字节的 16 位整数。
- setUint16:写入 2 个字节的 16 位无符号整数。
- setInt32:写入 4 个字节的 32 位整数。
- setUint32:写入 4 个字节的 32 位无符号整数。
- setFloat32:写入 4 个字节的 32 位浮点数。
- setFloat64:写入 8 个字节的 64 位浮点数。
// 在第1个字节,以大端字节序写入值为25的32位整数
dv.setInt32(0, 25, false)
Blob:
Blob 表示一个包含只读原始数据的、不可变的类文件对象。通常用于通过 Fetch API 发送二进制数据等。
创建 Blob:
通过 new Blob(dataArray, option)
创建 Blob 对象。
- dataArray:是一个数组,包含了要添加到 Blob 对象中的数据,可以是任意多个 ArrayBuffer、ArrayBufferView、 Blob 或者 DOMString对象。
- option:是一个对象,用于设置 Blob 对象的一些属性。
let div = "<div>我是div</div>"
let blob = new Blob([div], {type:'text/html'}) // Blob(13) {size: 13, type: "text/html"}
实例属性:
- size:只读,Blob 对象包含的数据大小(字节)。
- type:只读,该 Blob 对象所包含的 MIME 类型。
实例方法:
slice(start, end)
:用来对 Blob 进行分割,返回一个新的 Blob 对象,包含了源 Blob 对象中指定范围内的数据。
Blob URL:
Blob URL 是 Blob 协议的 URL,可以通过 window.URL.createObjectURL(blob)
创建,它的格式如下:blob:http://XXX
。
Blob URL 和 DataURL 的区别:
- Blob URL的长度一般比较短,但 Data URL 因为直接存储图片 Base64 编码后的数据,往往很长。
当显示大图片时,使用 Blob URL 能获取更好的性能。
- Blob URL可以方便的使用 XMLHttpRequest 获取源数据,对于 Data URL,并不是所有浏览器都支持通过 XMLHttpRequest 获取源数据的。
- Blob URL 只能在当前应用内部使用,把 Blob URL 复制到浏览器的地址栏中,是无法获取数据的;Data URL 相比之下,就有很好的移植性,可以在任意浏览器中使用。
通过操作 Blob 可以实现的功能:
-
文件下载:通过
window.URL.createObjectURL()
方法,接收一个Blob(File)对象,将其转化为 Blob URL;然后赋给a.download
属性,以用来实现文件的下载。<a id="download">文件下载</a> let blob = new Blob(['Hello World']) let url = window.URL.createObjectURL(blob) let a = document.getElementById('download') a.download = 'hellworld.txt' a.href = url
-
图片显示:通过
window.URL.createObjectURL()
方法,接收一个Blob(File)对象,将其转化为Blob URL;然后赋给img.src
属性,以用来实现图片的本地显示。<input type="file"/> <img id="img" style="width:200px;height:200px"/> let input = document.getElementsByTagName('input')[0] input.addEventListener('change', ()=>{ let url = window.URL.createObjectURL(input.files[0]) let img = document.getElementById('img') img.src= url })
-
文件分片上传:通过
Blob.slice(start, end)
可以分割大 Blob 为多个小 Blob。<input type="file"/> let input = document.getElementsByTagName('input')[0] input.addEventListener('change', ()=>{ let blob = input.files[0] let CHUNK_SIZE = 20 let SIZE = blob.size let start = 0 let end = CHUNK_SIZE while( start < SIZE) { console.log(blob.slice(start, end)) start = end end = start + CHUNK_SIZE } })
ArrayBuffer 和 Blob 的区别:
- ArrayBuffer 更底层,就是一段纯粹的内存上的二进制数据,可以对其任何一个字节进行单独的修改,也可以根据需要以指定的形式读取指定范围的数据;Blob 就是将一段二进制数据进行了一个封装,得到的就是一个整体,可以看到它整体属性大小、类型,可以对其进行分割,但不能了解它的细节。
- Blob 可以接收一个 ArrayBuffer 作为参数生成一个 Blob 对象,此行为就相当于对 ArrayBuffer 数据做一个封装,之后就是以整体的形式展现了。
- 由于 ArrayBuffer 和 Blob 的特性,Blob 作为一个整体文件,适合用于传输;而只有需要关注细节的时候(比如要修改一段数据),才需要用到 ArrayBuffer。
File:
File 提供有关文件的信息。File 对象是特殊类型的 Blob,所以继承了 Blob 所有的方法和属性,同时又有自己独特的属性和方法。通常用于处理文件数据。
File.prototype instanceof Blob // true
最常见的 File 对象来自 <input type='file' />
选择的 FileList 对象。
<input type="file"/>
let input = document.getElementsByTagName('input')[0]
input.addEventListener('change', ()=>{
console.log(input.files)
})