[go: up one dir, main page]

Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

超好用的Blob对象! #12

Open
akira-cn opened this issue Aug 5, 2019 · 4 comments
Open

超好用的Blob对象! #12

akira-cn opened this issue Aug 5, 2019 · 4 comments

Comments

@akira-cn
Copy link
Owner
akira-cn commented Aug 5, 2019

我们在《如何在浏览器中处理二进制数据?》这一篇中提到了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中讨论交流。

@fisker
Copy link
fisker commented Nov 26, 2019

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

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

@hax
Copy link
hax commented Nov 29, 2019

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

@fisker
Copy link
fisker commented Nov 29, 2019

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

@haozi
Copy link
haozi commented Mar 8, 2020

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

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants