我们在《如何在浏览器中处理二进制数据?》这一篇中提到了Blob对象。
?? Blob 是 Binary Large Object 的缩写,Blob 对象表示一个不可变、原始数据的类文件对象。
实际上这是一个从ES5开始就逐步被浏览器支持的特性,它让我们能够比较方便地处理文件式的二进制数据。
Blob对象被浏览器“视同文件”。
一个最直接的应用例子是,当我们需要在网页中预览本地图片时,我们不必将图片上传到服务器上再通过标签加载(在早期,受限于浏览器,很多程序员选择这么做)。
<img id="imagePreview">
<input id="imageSelector" type="file" accept=".png,.jpg,.jpeg,.gif">
const imageSelector = document.getElementById('imageSelector');
imageSelector.addEventListener('change', (event) => {
const file = event.target.files[0];
console.log(file instanceof Blob);
});
这个file是一个Blob类型的实例。实际上,更准确地说,file是继承自Blob类型的File类型的实例。
我们拿到这个file实例之后,可以通过URL.createObjectURL()将它转换为URL并加载到图片中去,这样我们就实现了图片的本地加载和预览。
const imageSelector = document.getElementById('imageSelector');
const imagePreview = document.getElementById('imagePreview');
imageSelector.addEventListener('change', (event) => {
const file = event.target.files[0];
const url = URL.createObjectURL(file);
imagePreview.src = url;
});
实际上,Blob对象可以手工创建,比如:
var debug = {hello: "world"};
var blob = new Blob([JSON.stringify(debug)],
{type : 'application/json'});
console.log(blob); // [object Blob]{size: 17, type: "application/json"}
如果把这个blob对象放到HTTP请求中发送给服务端,相当于向服务器提交了一份内容为{"hello":"world"}的JSON文件。
const jsCode = "console.log('hello')";
const blob = new Blob([jsCode], {type: "text/javascript"});
const script = document.createElement('script');
document.body.appendChild(script);
script.src = URL.createObjectURL(blob);
上面的代码相当于在页面上动态插入了一个标签,加载了一个文件内容为console.log('hello')的JS文件。
你可以会问,这么做有什么意义?我们直接将jsCode写在标签中加载,效果不也一样?上面的代码等价于:
const jsCode = "console.log('hello')";
const script = document.createElement('script');
script.textContent = jsCode;
document.body.appendChild(script);
但是别忘了,我们现在浏览器支持ES Modules,使用Blob可以方便地实现通过代码来动态创建模块:
<script type="module">
function importCode(code) {
const blob = new Blob([code], {type: "text/javascript"});
const script = document.createElement('script');
document.body.appendChild(script);
script.setAttribute('type', 'module');
script.src = URL.createObjectURL(blob);
return import(script.src);
}
const code = `
export default {
foo: 'bar',
}
`;
importCode(code).then((m) => {
console.log(m.default); // {foo: 'bar'}
});
script>
在一些应用中,我们把canvas对象用toDataURL()给转成base64,然后传输给服务器处理。但是实际上,canvas除了可以toDataURL,也可以直接用toBlob转成二进制对象。如果你的服务器能直接处理二进制数据,那没必要转base64,直接传二进制,不但传输的数据更小,服务器也不需要额外转换,处理起来更快。
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
const width = 256;
const height = 256;
canvas.width = width;
canvas.height = height;
const bufferData = new Uint8ClampedArray(width * height * 4);
for(let i = 0; i < 256; i++) {
for(let j = 0; j < 256; j++) {
const idx = i * 256 + j;
bufferData[idx * 4] = i;
bufferData[idx * 4 + 1] = 255 - i;
bufferData[idx * 4 + 3] = 255;
}
}
context.putImageData(new ImageData(bufferData, width, height), 0, 0);
canvas.toBlob((blob) => {
const image = new Image();
image.width = width;
image.height = height;
document.body.append(image);
image.src = URL.createObjectURL(blob);
});
上面的代码直接创建一个256 * 256的图片并转换成Blob,添加到img标签中。
除了File API,浏览器的Fetch API也是可以拿二进制数据的,在允许跨域的情况下,我们可以直接将二进制文件拿过来处理。
const url = 'https://p2.ssl.qhimg.com/t01d376809c079520f3.jpg';
(async function() {
const res = await fetch(url);
const blob = await res.blob();
const bitmap = await createImageBitmap(blob);
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
canvas.width = bitmap.width;
canvas.height = bitmap.height;
context.drawImage(bitmap, 0, 0, bitmap.width, bitmap.height);
const imageData = context.getImageData(0, 0, bitmap.width, bitmap.height);
for(let i = 0; i < bitmap.width * bitmap.height; i++) {
imageData.data[i * 4] = 0;
}
context.putImageData(imageData, 0, 0);
canvas.toBlob((blob) => {
const img = new Image();
img.src = URL.createObjectURL(blob);
document.body.appendChild(img);
});
}());
除了以上这些用法以外,Blob还有许多有用之处,可以和ArrayBuffer、TypedArray、ImageBitmap以及其他二进制对象一同使用,我们在后续有机会再继续讨论。
以上是这一篇的所有内容,大家对Blob对象有什么想法或使用经验,欢迎在issue中讨论交流。