在一般的Web开发中,很少会用到Blob,但Blob可以满足一些场景下的特殊需求。Blob,Binary Large Object的缩写,代表二进制类型的大对象。Blob的概念在一些数据库中有使用到,例如,MYSQL中的BLOB类型就表示二进制数据的容器。在Web中,Blob类型的对象表示不可变的类似文件对象的原始数据,通俗点说,就是Blob对象是二进制数据,但它是类似文件对象的二进制数据,因此可以像操作File对象一样操作Blob对象,实际上,File继承自Blob。
Blob定义(来自MDN)
Blob
对象表示一个不可变、原始数据的类文件对象。它的数据可以按文本或二进制的格式进行读取,也可以转换成 ReadableStream
来用于数据操作。
Blob 表示的不一定是JavaScript原生格式的数据。File
接口基于Blob
,继承了 blob 的功能并将其扩展使其支持用户系统上的文件。
要从其他非blob对象和数据构造一个 Blob
,请使用 Blob()
构造函数。要创建一个 blob 数据的子集 blob,请使用 slice()
方法。要获取用户文件系统上的文件对应的 Blob
对象,请参阅 File
文档。
接受 Blob
对象的API也被列在 File
文档中。
Blob基本用法
创建
可以通过Blob的构造函数创建Blob对象:
Blob(blobParts[, options])
参数说明:
blobParts: 数组类型, 数组中的每一项连接起来构成Blob对象的数据,数组中的每项元素可以是ArrayBuffer(二进制数据缓冲区), ArrayBufferView,Blob,DOMString
。或其他类似对象的混合体。
options: 可选项,字典格式类型,可以指定如下两个属性:
- type,默认值为
""
,它代表了将会被放入到blob中的数组内容的MIME类型。 - endings, 默认值为”transparent”,用于指定包含行结束符
\n
的字符串如何被写入。 它是以下两个值中的一个: “native”,表示行结束符会被更改为适合宿主操作系统文件系统的换行符; “transparent”,表示会保持blob中保存的结束符不变。
举个栗子:
var data1 = "a";
var data2 = "b";
var data3 = "<div style='color:red;'>This is a blob</div>";
var data4 = { "name": "abc" };
var blob1 = new Blob([data1]);
var blob2 = new Blob([data1, data2]);
var blob3 = new Blob([data3]);
var blob4 = new Blob([JSON.stringify(data4)]);
var blob5 = new Blob([data4]);
var blob6 = new Blob([data3, data4]);
console.log(blob1); //输出:Blob {size: 1, type: ""}
console.log(blob2); //输出:Blob {size: 2, type: ""}
console.log(blob3); //输出:Blob {size: 44, type: ""}
console.log(blob4); //输出:Blob {size: 14, type: ""}
console.log(blob5); //输出:Blob {size: 15, type: ""}
console.log(blob6); //输出:Blob {size: 59, type: ""}
size代表Blob
对象中所包含数据的字节数。这里要注意,使用字符串和普通对象创建Blob时的不同,blob4使用通过JSON.stringify
把data4对象转换成json字符串,blob5则直接使用data4创建,两个对象的size分别为14和15。blob4的size等于14很容易理解,因为JSON.stringify(data4)的结果为:"{"name":"abc"}"
,正好14个字节(不包含最外层的引号)。blob5的size等于15是如何计算而来的呢?实际上,当使用普通对象创建Blob对象时,相当于调用了普通对象的toString()方法得到字符串数据,然后再创建Blob对象。所以,blob5保存的数据是"[object Object]"
,是15个字节(不包含最外层的引号)。
slice方法
Blob对象有一个slice方法,返回一个新的Blob
对象,包含了源Blob对象中制定范围内的数据。
参数说明:
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: ""}
通过slice方法,从blob1中创建出一个新的blob对象,size等于3。
Blob对象能够添加到表单中,作为上传数据使用:
const content = '<a id="a"><b id="b">hey!</b></a>';
const blob = new Blob([content], {type: 'text/xml'});
formData.append('webmasterfile', blob);
Blob使用场景
分片上传
前面已经说过,File继承字Blob,因此我们可以调用slice方法对大文件进行分片上传。代码:
function uploadFile(file) {
var chunkSize = 1024 * 1024; //每片1M大小
var totalSize = file.size;
var chunkQuantity = Math.ceil(totalSize/chunkSize); //分片总数
var offset = 0; //偏移量
var reader = new FileReader();
reader.onload = function(e) {
var xhr = new XMLHttpRequest();
xhr.open("POST", url);
xhr.overrideMineType("application/octet-stream");
xhr.onreadstatechange = function() {
if(xhr.readyState === 4 && xhr.status ===200) {
++offset;
if(offset === chunkQuantity) {
alerrt("上传完成");
} else if(offset === chunckQuantity-1) {
blob = file.slice(offset*chunkSize, totalSize);
reader.readAsBinaryString(blob);
} else {
blob = file.slice(offset*chunkSize, (offset+1)*chunckSize);
reader.readAsBinaryString(blob);
}
}else {
alert("上传出错");
}
}
if(xhr.sendAsBinary) {
xhr.sendAsBinary(e.target.result);
} else {
xhr.send(e.target.result);
}
}
var blob = file.slice(0, chunkSize);
reader.readAsBinaryString(blob);
}
这段代码还可以进一步丰富,比如显示当前上传进度,使用多个XMLHttpRequest对象并行上传对象(需要传递分片数据得位置参数给服务器端)等。
Blob URL
Blob URL是blob协议得URL,它的格式如下:
blob:http://xxx
Blob URL可以通过URL.createObjectURL(blob)
创建。
var objecturl = window.URL.createObjectURL(blob);
上面的代码会对二进制数据生成一个 URL,这个 URL 可以放置于任何通常可以放置 URL 的地方,比如 img 标签的 src 属性。需要注意的是,即使是同样的二进制数据,每调用一次 URL.createObjectURL()
方法,就会得到一个不一样的 URL。
这个 URL 的存在时间,等同于网页的存在时间,一旦网页刷新或卸载,这个 URL 就失效。(File 和 Blob 又何尝不是这样呢)除此之外,也可以手动调用 URL.revokeObjectURL 方法,使 URL 失效。
在绝大部分场景下,我们可以像使用Http协议得URL一样使用Blob URL。常见得场景有: 作为文件得下载地址和作为图片资源地址。
那么Blob URL和Data URL有什么区别呢?
- Blob URL得长度一般比较短,但Data URL因为直接存储图片base64编码后得数据,往往很长。当显示大图片时,使用Blob URL更优。
- Blob URL可以方便的使用XMLHttpRequest获取源数据,例如:
var blobUrl = URL.createObkectURL(new Blob(['Test'], {type: 'text/plain'}));
var xhr = new XMLHttpRequest();
//如果是指xhr.responseType = 'blob',将返回一个Blob对象,而不是文本;
//xhr.responseType = 'blob';
xhr.onload = function() {
alert(xhr.responseText);
}
xhr.open('get', blobUrl);
xhr.send();
对于Data URL, 并不是所有浏览器都支持通过XMLHttpRequest获取源数据的。
- Blob URL只能在当前应用内部使用,把Blob URL复制到浏览器的地址栏中,是无法获取数据的。Data URL相比之下,就有很好的移植性,你可以在任意浏览器使用。
除了可以用作图片资源的网络地址,Blob URL也可以用作其他资源的网络地址,例如html文件、json文件等,为了保证浏览器能正确的解析Blob URL返回的文件类型,需要在创建Blob对象时指定相应的type:
//创建HTML文件的Blob URL
var data = "<div style='color:red;'This is a blob</div>";
var blob = new Blob([data], {type: 'text/html'}); // 'application/json'
var blobUrl = URL.createObjectURL(blob);
js获取上传文件内容
<div id="app">
<a @click="chooseFile">选择文件</a>
<!-- 使用 accept 属性可以限定 文件选择的格式 -->
<input type="file" id="file" accept="text/plain" @change="fileInfo(getFileContent)">
<p>{{fileName}}</p>
<p>{{fileContent}}</p>
</div>
<script src="//static.tianyichuxin.com/assets/vuejs/vue@2.6.14/vue.global.js"></script>
<script type="text/javascript">
var vm = new Vue({
el: "#app",
data() {
return {
file: {},
fileName: '',
fileContent: ''
}
},
mounted() {
//监听按键
document.onkeydown = function(ev) {
console.log(ev)
if (ev.code == "Escape") {
console.log(ev.code)
}
}
//jquery
// $(document).keydown(function(ev) {
// console.log(ev)
// })
},
methods: {
chooseFile() {
// 弹出文件选择框
let input = document.getElementById('file')
input.click()
},
fileInfo(callback) {
// 获取input标签选择的文件,并选择第一条
let resultFile = document.getElementById('file').files[0]
console.log(resultFile)
// 如果文件存在
if (resultFile) {
// 获取文件信息
this.file = resultFile
// 获取文件名
this.fileName = resultFile.name
// 使用 FileReader 来读取文件
let reader = new FileReader()
// 读取纯文本文件,且编码格式为 utf-8
reader.readAsText(resultFile, 'UTF-8')
// 读取文件,会触发 onload 异步事件,可使用回调函数 来获取最终的值.
reader.onload = function(e) {
console.log(e)
let fileContent = e.target.result;
console.log(fileContent)
// 若为回调函数,则触发回调事件
if (callback && (typeof callback === 'function')) {
console.log(callback)
callback(fileContent)
}
}
}
},
getFileContent(fileContent) {
this.fileContent = fileContent
}
}
});
</script>