akira-cn / FE_You_dont_know

分享在前端开发中,你不知道的JavaScript、CSS和HTML趣味知识,增加你的知识面。

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

超好用的Blob对象!

akira-cn opened this issue · comments

commented

我们在《如何在浏览器中处理二进制数据?》这一篇中提到了Blob对象。

👉🏻 Blob 是 Binary Large Object 的缩写,Blob 对象表示一个不可变、原始数据的类文件对象。

实际上这是一个从ES5开始就逐步被浏览器支持的特性,它让我们能够比较方便地处理文件式的二进制数据。

Blob对象被浏览器“视同文件”。

一个最直接的应用例子是,当我们需要在网页中预览本地图片时,我们不必将图片上传到服务器上再通过<img>标签加载(在早期,受限于浏览器,很多程序员选择这么做)。

<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);

上面的代码相当于在页面上动态插入了一个<script>标签,加载了一个文件内容为console.log('hello')的JS文件。

你可以会问,这么做有什么意义?我们直接将jsCode写在<script>标签中加载,效果不也一样?上面的代码等价于:

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中讨论交流。

importCode 不需要创建 script 啊, 可以直接 import 的

function importCode(code) {
  const blob = new Blob([code], {type: "text/javascript"});
  return import(URL.createObjectURL(blob));
}

嗯,也可以直接用data协议。
import(`data:text/javascript,${code}`)

@hax import(`data:text/javascript,${code}`) 不安全 有些字符 需要 encode

嗯,也可以直接用data协议。
import(`data:text/javascript,${code}`)

字符串会额外增加内存开销吧