BLOB (binary large object),二进制大对象,是一个可以存储二进制文件的容器。Blob 对象表示一个不可变、原始数据的类文件对象。它的数据可以按文本或二进制的格式进行读取,也可以转换成 ReadableStream 来用于数据操作。Blob 表示的不一定是JavaScript原生格式的数据。File 接口基于Blob,继承了 blob 的功能并将其扩展使其支持用户系统上的文件。
- blob文档:https://developer.mozilla.org/zh-CN/docs/Web/API/Blob
- 在计算机中,BLOB常常是数据库中用来存储二进制文件的字段类型。
- 我们可以使用 Blob 对象隐藏真实的资源路径,在一定程度上可以起到数据的加密性,更多的是为了干扰爬虫。比如日常使用的一些音频,视频,图片,我们都可以使用其 Blob 二进制数据流来表征数据,而非使用 uri,就像经常用到的 image src 的 dataUrl。
1、Blob对象
Blob对象指的是字节序列,并且具有size属性,是字节序列中的字节总数,和一个type属性,它是小写的ASCII编码的字符串表示的媒体类型字节序列。
- size:以字节数返回字节序列的大小。获取时,符合要求的用户代理必须返回一个FileReader或一个FileReaderSync对象可以读取的总字节数,如果Blob没有要读取的字节,则返回0 。
- type:小写的ASCII编码字符串表示媒体类型Blob。在获取时,用户代理必须Blob以小写形式返回a类型的ASCII编码字符串,这样当它转换为字节序列时,它是可解析的MIME类型,或者是空字符串(0字节)如果是类型无法确定。
1.1 构造函数
创建blob对象本质上和创建一个其他对象的方式是一样的,都是使用Blob() 的构造函数来进行创建。 构造函数接受两个参数:
- 第一个参数为一个数据序列,格式可以是ArrayBuffer, ArrayBufferView, Blob, DOMString
- 第二个参数是一个包含以下两个属性的对象
- type: MIME的类型,
- endings: 决定第一个参数的数据格式。默认值为"transparent",用于指定包含行结束符n的字符串如何被写入。 它是以下两个值中的一个: “native”,表示行结束符会被更改为适合宿主操作系统文件系统的换行符; “transparent”,表示会保持blob中保存的结束符不变。
var data1 = "a";
var blob1 = new Blob([data1]);
console.log(blob1); //输出:Blob {size: 1, type: ""}
var debug = {hello: "world"};
var blob = new Blob([JSON.stringify(debug, null, 2)],{type : 'application/json'});
console.log(blob) // 输出 Blob(22) {size: 22, type: "application/json"}
// 创建一个8字节的ArrayBuffer,在其上创建一个每个数组元素为2字节的“视图”
var abf = new ArrayBuffer(8)
var abv = new Int16Array(abf)
var bolb_ArrayBuffer = new Blob(abv, {type : 'text/plain'})
console.log(bolb_ArrayBuffer) //输出 Blob(4) {size: 4, type: "text/plain"}
1.2 slice方法
Blob对象有一个slice方法,返回一个新的 Blob对象,包含了源 Blob对象中指定范围内的数据。
slice(start, end, contentType)
- start: 可选,代表 Blob 里的下标,表示第一个会被会被拷贝进新的 Blob 的字节的起始位置。如果传入的是一个负数,那么这个偏移量将会从数据的末尾从后到前开始计算。
- end: 可选,代表的是 Blob 的一个下标,这个下标-1的对应的字节将会是被拷贝进新的Blob 的最后一个字节。如果你传入了一个负数,那么这个偏移量将会从数据的末尾从后到前开始计算。
- contentType: 可选,给新的 Blob 赋予一个新的文档类型。这将会把它的 type 属性设为被传入的值。它的默认值是一个空的字符串。
var data = "abcdef";
var blob1 = new Blob([data]);
var blob2 = blob1.slice(0,3);
console.log(blob1); //输出:Blob {size: 6, type: ""}
console.log(blob2); //输出:Blob {size: 3, type: ""}
2、利用Blob进行文件分片上传
2.1 分片上传原理
我们在进行文件上传的时候,因为服务器的限制,会限制每一次上传到服务器的文件大小不会很大,这个时候我们就需要把一个需要上传的文件进行切割,然后分别进行上传到服务器。
分片上传需要解决两个问题:
- 怎么切割?
- 怎么得知当前传输的进度?
首先解决怎么切割的问题。因为File文件对象是继承于Blob对象的,因此File文件对象也拥有slice这个方法,我们可以使用这个方法将任何一个File文件进行切割。
Blob对象的slice方法可以用于文件分片上传:
- 分片与并发结合,将一个大文件分割成多块,并发上传,极大地提高大文件的上传速度。
- 当网络问题导致传输错误时,只需要重传出错分片,而不是整个文件。另外分片传输能够更加实时的跟踪上传进度。
分片上传逻辑如下:
- 获取要上传文件的File对象,根据chunk(每片大小)对文件进行分片
- 通过post方法轮循上传每片文件,其中url中拼接querystring用于描述当前上传的文件信息;post body中存放本次要上传的二进制数据片段
- 接口每次返回offset,用于执行下次上传
2.2 分片上传代码
initUpload();
//初始化上传
function initUpload() {
var chunk = 100 * 1024; //每片大小
var input = document.getElementById("file"); //input file
input.onchange = function (e) {
var file = this.files[0];
var query = {};
var chunks = [];
if (!!file) {
var start = 0;
//文件分片
for (var i = 0; i < Math.ceil(file.size / chunk); i++) {
var end = start + chunk;
chunks[i] = file.slice(start , end);
start = end;
}
// 采用post方法上传文件
// url query上拼接以下参数,用于记录上传偏移
// post body中存放本次要上传的二进制数据
query = {
fileSize: file.size,
dataSize: chunk,
nextOffset: 0
}
upload(chunks, query, successPerUpload);
}
}
}
// 执行上传
function upload(chunks, query, cb) {
var queryStr = Object.getOwnPropertyNames(query).map(key => {
return key + "=" + query[key];
}).join("&");
var xhr = new XMLHttpRequest();
xhr.open("POST", "http://xxxx/opload?" + queryStr);
xhr.overrideMimeType("application/octet-stream");
//获取post body中二进制数据
var index = Math.floor(query.nextOffset / query.dataSize);
getFileBinary(chunks[index], function (binary) {
if (xhr.sendAsBinary) {
xhr.sendAsBinary(binary);
} else {
xhr.send(binary);
}
});
xhr.onreadystatechange = function (e) {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
var resp = JSON.parse(xhr.responseText);
// 接口返回nextoffset
// resp = {
// isFinish:false,
// offset:100*1024
// }
if (typeof cb === "function") {
cb.call(this, resp, chunks, query)
}
}
}
}
}
// 每片上传成功后执行
function successPerUpload(resp, chunks, query) {
if (resp.isFinish === true) {
alert("上传成功");
} else {
//未上传完毕
query.offset = resp.offset;
upload(chunks, query, successPerUpload);
}
}
// 获取文件二进制数据
function getFileBinary(file, cb) {
var reader = new FileReader();
reader.readAsArrayBuffer(file);
reader.onload = function (e) {
if (typeof cb === "function") {
cb.call(this, this.result);
}
}
}
2.3 分片上传完整示例
改代码中还有我们常说的断点续传功能,说下简单思路:
- 拿到文件,保存文件唯一标识,如md5;
- 切割文件,分段上传;
- 每次上传一段,根据唯一性标识判断文件上传进度,直到文件的全部片段上传完毕。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>upload</title>
<script src="https://cdn.bootcss.com/jquery/3.2.1/jquery.min.js"></script>
</head>
<body>
<input type="file" name="file" id="file">
<button id="upload" onClick="upload()">upload</button>
<script type="text/javascript">
var bytesPerPiece = 1024 * 1024; // 每个文件切片大小定为1MB .
var totalPieces;
//发送请求
function upload() {
var blob = document.getElementById("file").files[0];
var start = 0;
var end;
var index = 0;
var filesize = blob.size;
var filename = blob.name;
//计算文件切片总数
totalPieces = Math.ceil(filesize / bytesPerPiece);
while(start < filesize) {
end = start + bytesPerPiece;
if(end > filesize) {
end = filesize;
} // 匹配最后一个分片的情况
var chunk = blob.slice(start,end);//切割文件
var sliceIndex= blob.name + index;
var formData = new FormData();
formData.append("file", chunk, sliceIndex);
$.ajax({
url: 'http://localhost:9999/test.php',
type: 'POST',
cache: false,
data: formData,
processData: false,
contentType: false,
}).done(function(res){
}).fail(function(res) {
});
start = end;
index++;
}
}
</script>
</body>
</html>
3、使用Blob实现文件下载
3.1 Blob URL
blob协议的url使用时就像平时使用的url一样,可以作为图片请求地址,也可以作为文件请求地址。格式如下:
blob:http://XXX
生产中是很少会将一个资源以二进制流的方式返回给前端进行 Blob“加密”的,因为服务端需要将相应的资源打开读取,会消耗对应的内存,比如一个视频如果50M,那我们服务端返回其二进制数据时,就要消耗 50M 的服务器内存将文件载入,然后返回给前端,这代价略大,而且网络传输相应速度也不允许,如果返回个 500M 的二进制数据,估计就会有很大的卡顿了。
要使用 blob 来表征数据资源,需做到以下两点:
- 服务端返回的为资源的二进制数据
- 前端接收到二进制数据后,使用 URL.createObjectURL(blobData) 方法将服务端返回的二进制数据转换为 blob 的 url 资源挂载到相应的资源对象。
1)new Blob
new Blob() 存放二进制资源
2)URL.createObjectURL(blob)
URL.createObjectURL(blob) 会将 Blob存放的二进制资源转为url地址,这个URL的生命周期仅存在于它被创建的这个文档里,新的对象URL指向执行的File对象或者是Blob对象。
语法:
objectURL = URL.createObjectURL(blob || file);
参数:
- File对象或者Blob对象
File对象,就是一个文件,比如我用input type="file"标签来上传文件,那么里面的每个文件都是一个File对象.
Blob对象,就是二进制数据,比如通过new Blob()创建的对象就是Blob对象.又比如,在XMLHttpRequest里,如果指定responseType为blob,那么得到的返回值也是一个blob对象.
注意点:
- 每次调用createObjectURL的时候,一个新的URL对象就被创建了.即使你已经为同一个文件创建过一个URL. 如果你不再需要这个对象,要释放它,需要使用URL.revokeObjectURL()方法. 当页面被关闭,浏览器会自动释放它,但是为了最佳性能和内存使用,当确保不再用得到它的时候,就应该释放它.
3)URL.revokeObjectURL(url)
URL.revokeObjectURL(url) 释放一个通过URL.createObjectURL()创建的对象URL,当你要已经用过了这个对象URL,然后要让浏览器知道这个URL已经不再需要指向对应的文件的时候,就需要调用这个方法。
- 具体的意思就是说,一个对象URL,使用这个url是可以访问到指定的文件的,但是我可能只需要访问一次,一旦已经访问到了,这个对象URL就不再需要了,就需要被释放掉,被释放掉以后这个对象URL就不再指向指定的文件了.
比如一张图片,我创建了一个对象URL,然后通过这个对象URL,我页面里加载了这张图,既然已经被加载,并且不需要再次加载这张图,那我就把这个对象URL释放,然后这个URL就不再指向这张图了.
语法:
window.URL.revokeObjectURL(objectURL);
参数:
- objectURL 是一个通过URL.createObjectURL()方法创建的对象URL.
以上两个方法不支持低版本的浏览器。
3.2 Blob下载
大家都知道 a 标签是超链接,点击会跳转至 href 指定的地址。当我们给 a 标签加上 download 属性后,则会去 href 指定的url下载文件(这里一定是同源的)。Blob下载思路:
- 首先通过
document.createElement('a')
创建一个a标签,用 link 来接受 - 通过
link.setAttribute('download', 'filename.html')
或者直接用link.download = 'filename.html'
去给 a 标签添加 download 属性 - 通过
window.URL.createObjectURL
将 blob二进制资源 转为 url地址并赋给link.href - 然后执行
link.click()
即可下载
3.2.1 下载示例1
let html = `
<div>
<div>
<p style="color: red;font-size: 20px;">张三</p>
</div>
</div>
`
const link = document.createElement('a')
link.setAttribute('download', 'zhangsan.html')
// link.download = 'zhangsan.html'
link.href = window.URL.createObjectURL(new Blob([html], { type: 'text/html' }))
link.click()
这段代码即可直接将 html 判断下载,下载的文件名称就是 zhangsan.html
3.2.2 下载示例2
<body>
<input type="file" id="fileInput">
<script>
window.onload = function () {
fileInput.onchange = function (e) {
console.log(e.target.files[0])
const link = document.createElement('a')
link.href = window.URL.createObjectURL(e.target.files[0])
// link.setAttribute('download', '1111.html')
link.download = '1111.html'
link.click()
}
}
</script>
</body>
这个demo是 咱们去本地选中一个html文件,通过 e.target.files[0]
拿到的就是继承自 Blob 的二进制对象(所以不需要 new Blob()), 即可下载该文件 。
3.2.3 示例3-项目中的导出功能方法封装
export function download (url, params) {
$http({
method: 'get',
url: url,
params: params,
responseType: 'blob' //arraybuffer
}).then(response => {
if (!response) {
return
}
let link = document.createElement('a')
link.href = window.URL.createObjectURL(new Blob([response.data]))
link.target = '_blank'
let filename = response.headers['content-disposition']
link.download = decodeURI(filename) // 下载的文件名称
document.body.appendChild(link) // 添加创建的 a 标签 dom节点
link.click() // 下载
document.body.removeChild(link) // 移除节点
}).catch(error => {})
}
上面有用到decodeURI, 这里就简单介绍一下 decodeURL 和 encodeURI
- encodeURI() 函数可把字符串作为 URI 进行编码
- decodeURI() 函数可对 encodeURI() 函数编码过的 URI 进行解码
例子:
let a = '李四'
encodeURI(a) //'%E6%9D%8E%E5%9B%9B'
let a = encodeURI('李四') // '%E6%9D%8E%E5%9B%9B'
decodeURI(a) // '李四'
3.2.4 下载文件示例4
下面是一个下载文件的示例,直接调用即可实现文件下载
// file是要下载的文件(blob对象)
downloadHandler: function (file, fileName) {
let link = document.createElement('a')
link.href = window.URL.createObjectURL(file)
link.download = fileName
link.click()
window.URL.revokeObjectURL(link.href)
if (navigator.userAgent.indexOf('Firefox') > -1) {
const a = document.createElement('a')
a.addEventListener('click', function (e) {
a.download = fileName
a.href = URL.createObjectURL(file)
})
let e = document.createEvent('MouseEvents')
e.initEvent('click', false, false)
a.dispatchEv
ent(e)
}
}
在从后台获取的数据接口中把返回类型设置为blob
var x = new XMLHttpRequest();
x.responseType = 'blob'; // 返回一个blob对象
3.2.5 下载文件示例5,释放对象URL
通过ajax获取一张图片,显示在页面里。该例子在createObjectURL获取到对象资源后使用revokeObjectURL释放了该对象URL。
- 指定返回的数据格式为blob二进制数据.
- 通过返回的图片二进制数据来创建一个对象URL.
- 当图片加载完成后释放对象URL.
html:
<body>
<button id="getPic">获取图片的Blob数据</button>
</body>
js:
//获取图片Blob数据
document.getElementById('getPic').onclick = function(e){
$.ajax({
type:'GET',
url:'img.png',
resDataType:'blob',
imgType:'png',
success:function(resText,resXML){
var img = document.createElement('img');
var objectUrl = window.URL.createObjectURL(resText);
img.src = objectUrl;
img.onload = function(){
window.URL.revokeObjectURL(objectUrl);
};
document.body.appendChild(img);
},
fail:function(err){
console.log(err)
}
});
e.preventDefault();
}
4、Blob URL 和 Data URL的区别
Blob URL:
Data URL:
- Blob URL的长度一般比较短,但Data URL因为直接存储图片base64编码后的数据,往往很长,如上图所示,浏览器在显示Data URL时使用了省略号(…)。当显式大图片时,使用Blob URL能获取更好的可能性。
- Blob URL可以方便的使用XMLHttpRequest获取源数据,比如设置XMLHttpRequest返回的数据类型为blob
- Blob URL 只能在当前应用内部使用,把Blob URL复制到浏览器的地址栏中,是无法获取数据的。Data URL相比之下,就有很好的移植性,可以在任意浏览器中使用。
评论区