历史上 JavaScript 是没有读写二进制数据能力的,但随着 es5 中 Blob 对象的引入以及 es6 中 ArrayBuffer 对象、TypedArray 和 DataView 对象的规范化, JS 处理二进制数据的能力大幅度增强,也能直接处理文件流,网络流等二进制 Buffer 数据了。
- Buffer:Buffer 是
Node.js
中的一个内置模块,用于处理二进制数据。
主要用于在服务器端处理数据。 - ArrayBuffer:是 JavaScript 中的内置对象,用于表示通用的、固定长度的原始二进制数据缓冲区,不能直接读写,但可以使用 TypedArray 对象或 DataView 对象进行操作。通常用于在浏览器中进行底层的二进制数据处理。
- Blob:是 JavaScript 中的内置对象,用于表示二进制大对象的接口,可以包含各种类型的数据,包括文本和二进制数据。通常用于处理文件数据、通过 Fetch API 发送二进制数据等。
- Flie:是 JavaScript 中的内置对象,继承自 Blob,是 Blob 的子类,添加了一些额外的文件相关属性。通常用于处理文件数据。
ArrayBuffer:
ArrayBuffer 代表存储二进制数据的一段内存,它不能直接读写,只能通过 TypedArray 视图和 DataView 视图来读写。
视图的作用是以指定格式解读二进制数据。
创建 ArrayBuffer:
通过 ArrayBuffer 构造函数可以创建一段存放二进制数据的连续内存区域。
let buf = new ArrayBuffer(32) // 创建了一个长度为 32 个字节的连续内存区域。由于没有赋初始值,每一个字节单元的值都默认是 0。
实例属性:
ArrayBuffer.prototype.byteLength
:只读属性,返回所分配的内存区域的字节长度。
实例方法:
ArrayBuffer.prototype.slice(start, len)
:用来拷贝自身的一部分生成一个新的 ArrayBuffer 对象并返回。参数 start 为开始的位置;len 为拷贝的长度。
静态方法:
ArrayBuffer.isView()
:返回一个布尔值,表示参数是否为 ArrayBuffer 的视图实例。也就是,是否为 TypedArray 实例或 DataView 实例。
视图:
ArrayBuffer 在内存中存储的是一段未加工的二进制数据,这一段二进制数据本身只是内存中存储的0、1数据,通过不同的格式来解读就会赋予它不同的意义,这就是视图。
ArrayBuffer 有两种视图,一种是 TypedArray 视图,另一种是 DataView 视图,两者的区别主要是字节序,前者的数组成员都是同一个数据类型,后者的数组成员可以是不同的数据类型。
TypedArray:
TypedArray 对象一共提供 9 种类型的视图,每一种视图都是一种构造函数:
- 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。由于两个视图对应的是同一段内存,一个视图修改底层内存,会影响到另一个视图。
TypedArray 提供的 9 中构造函数的用法:
TypedArray(buffer, byteOffset=0, length)
:第一个参数 buffer 为可以底层 ArrayBuffer、数组或类数组、数字、ArrayBuffer 视图;第二个参数 byteOffset 为开始字节序号,默认为 0,第三个参数 length 为长度,默认直到本段内存区域结束。返回生成的对象,统称为 TypedArray 对象。
// 创建一个 8 字节的 ArrayBuffer
let buf = new ArrayBuffer(8)
// 创建一个指向 buf 的 Int32 视图,开始于字节 0,直到缓冲区的末尾
let typedAry = new Int32Array(buf)
属性:
TypedArray.prototype.buffer
:返回整段内存区域对应的 ArrayBuffer 对象。TypedArray.prototype.byteLength
:返回 TypedArray 数组占据的内存长度,单位为字节。TypedArray.prototype.byteOffset
:返回 TypedArray 数组从底层 ArrayBuffer 对象的哪个字节开始。TypedArray.prototype.length
:表示TypedArray 数组含有多少个成员。byteLength 是字节长度,length 是成员长度。
实例方法:
TypedArray.prototype.set()
:用于复制 TypedArray 数组,也就是将一段内容完全复制到另一段内存。TypedArray.prototype.subarray(start, end)
:对于 TypedArray 数组的一部分,再建立一个新的视图。TypedArray.prototype.slice()
:返回一个指定位置的新的 TypedArray 实例。
静态方法:
TypedArray.of()
:TypedArray 数组的所有构造函数,都有一个静态方法 of(),用于将参数转为一个 TypedArray 实例。- T
ypedArray.from()
:接受一个可遍历的数据结构(比如数组)作为参数,返回一个基于这个结构的 TypedArray 实例。
TypedArray 类型化数组和正常数组的异同点:
这 9 个构造函数生成的对象,统称为 TypedArray 对象。它们很像正常数组,都有 length 属性,都能用方括号运算符 []
获取单个元素,所有数组的方法,在这些类型化数组上面都能使用。两者的差异主要在以下方面:
TypedArray 数组也可以转换回普通数组:
var normalArray = Array.prototype.slice.call(typedArray)
。
- TypedArray 数组的所有成员,都是同一种类型和格式。
- TypedArray 数组的成员是连续的,不会有空位。
- TypedArray 类型化数组成员的默认值为0(比如:new Array(10) 返回一个正常数组,里面没有任何成员,只是10个空位;new Uint8Array(10) 返回一个类型化数组,里面 10 个成员都是0)。
- TypedArray 数组只是一层视图,本身不储存数据,它的数据都储存在底层的 ArrayBuffer 对象之中,要获取底层对象必须使用 buffer 属性。
DataView 对象:
不同于类型化数组,一个数组只能存放同一类型的数据,DataView 可以在内存中存放不同类型的数据。
let buf = new ArrayBuffer(32)
let dataView = new DataView(buf)
dataView.getUnt8(0) // 建立 DataView 视图,然后以不带符号的 8 位整数格式,读取第一个元素
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)
ArrayBuffer 和 Blob 的区别:
- ArrayBuffer 更底层,就是一段纯粹的内存上的二进制数据,可以对其任何一个字节进行单独的修改,也可以根据需要以指定的形式读取指定范围的数据。
- Blob 就是将一段二进制数据进行了一个封装,得到的就是一个整体,可以看到它整体属性大小、类型,可以对其进行分割,但不能了解它的细节。
- Blob 可以接收一个 ArrayBuffer 作为参数生成一个 Blob 对象,此行为就相当于对 ArrayBuffer 数据做一个封装,之后就是以整体的形式展现了。
- 由于 ArrayBuffer 和 Blob 的特性,Blob 作为一个整体文件,适合用于传输;而只有需要关注细节的时候(比如要修改一段数据),才需要用到 ArrayBuffer。
ArrayBuffer 和 JS 原生数组的区别:
- ArrayBuffer 初始化后固定大小,而 JS 数组可以自由增减。
- 数组放在堆中,ArrayBuffer 把数据放在栈中。
- ArrayBuffer 没有 push/pop 等数组的方法。
- ArrayBuffer 读写要借助 TypeArray/DataView。
Blob:
一个 Blob 对象就是一个包含只读原始数据的类文件对象。
创建 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,可以通过 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 } })
File:
File 对象继承自 Blob 对象,在 Blob 的基础上增加了一些属性。
File.prototype instanceof Blob // true
最常见的 File 对象来自 <input type='file' />
选择的 FileList 对象或者是使用拖拽操作搞出的 DataTransfer 对象。
<input type="file"/>
let input = document.getElementsByTagName('input')[0]
input.addEventListener('change', ()=>{
console.log(input.files)
})