在 JavaScript 中从 Base64 字符串创建 BLOB

新手上路,请多包涵

我在字符串中有 Base64 编码的二进制数据:

const contentType = 'image/png';
const b64Data = 'iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==';

我想创建一个 blob: 包含此数据的 URL 并将其显示给用户:

const blob = new Blob(????, {type: contentType});
const blobUrl = URL.createObjectURL(blob);

window.location = blobUrl;

我一直无法弄清楚如何创建 BLOB。

在某些情况下,我可以通过使用 data: URL 来避免这种情况:

const dataUrl = `data:${contentType};base64,${b64Data}`;

window.location = dataUrl;

然而,在大多数情况下, data: URL 非常大。


如何在 JavaScript 中将 Base64 字符串解码为 BLOB 对象?

原文由 Jeremy 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 838
2 个回答

atob 函数会将 Base64 编码的字符串解码为一个新字符串,其中每个二进制数据字节都有一个字符。

 const byteCharacters = atob(b64Data);

每个字符的代码点 (charCode) 将是字节的值。我们可以通过使用 .charCodeAt 方法为字符串中的每个字符应用它来创建一个字节值数组。

 const byteNumbers = new Array(byteCharacters.length);
 for (let i = 0; i < byteCharacters.length; i++) {
 byteNumbers[i] = byteCharacters.charCodeAt(i);
 }

您可以通过将这个字节值数组传递给 Uint8Array 构造函数,将其转换为真正的类型化字节数组。

 const byteArray = new Uint8Array(byteNumbers);

反过来,可以通过将其包装在数组中并将其传递给 Blob 构造函数来将其转换为 BLOB。

 const blob = new Blob([byteArray], {type: contentType});

上面的代码有效。然而,通过在更小的切片中处理 byteCharacters 而不是一次全部处理,可以稍微提高性能。在我的粗略测试中,512 字节似乎是一个很好的切片大小。这为我们提供了以下功能。

 const b64toBlob = (b64Data, contentType='', sliceSize=512) => {
 const byteCharacters = atob(b64Data);
 const byteArrays = [];

 for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
 const slice = byteCharacters.slice(offset, offset + sliceSize);

 const byteNumbers = new Array(slice.length);
 for (let i = 0; i < slice.length; i++) {
 byteNumbers[i] = slice.charCodeAt(i);
 }

 const byteArray = new Uint8Array(byteNumbers);
 byteArrays.push(byteArray);
 }

 const blob = new Blob(byteArrays, {type: contentType});
 return blob;
 }

 const blob = b64toBlob(b64Data, contentType);
 const blobUrl = URL.createObjectURL(blob);

 window.location = blobUrl;

完整示例:

 const b64toBlob = (b64Data, contentType='', sliceSize=512) => {
 const byteCharacters = atob(b64Data);
 const byteArrays = [];

 for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
 const slice = byteCharacters.slice(offset, offset + sliceSize);

 const byteNumbers = new Array(slice.length);
 for (let i = 0; i < slice.length; i++) {
 byteNumbers[i] = slice.charCodeAt(i);
 }

 const byteArray = new Uint8Array(byteNumbers);
 byteArrays.push(byteArray);
 }

 const blob = new Blob(byteArrays, {type: contentType});
 return blob;
 }

 const contentType = 'image/png';
 const b64Data = 'iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==';

 const blob = b64toBlob(b64Data, contentType);
 const blobUrl = URL.createObjectURL(blob);

 const img = document.createElement('img');
 img.src = blobUrl;
 document.body.appendChild(img);

原文由 Jeremy 发布,翻译遵循 CC BY-SA 4.0 许可协议

这是一个更简单的方法,没有任何依赖项或库。

它需要新的获取 API。 ( 我可以用吗?

 var url = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=="

fetch(url)
.then(res => res.blob())
.then(console.log)

通过这种方法,您还可以轻松获取 ReadableStream、ArrayBuffer、文本和 JSON。

(仅供参考,这也适用于 Node 中的 node-fetch

作为一个功能:

 const b64toBlob = (base64, type = 'application/octet-stream') =>
  fetch(`data:${type};base64,${base64}`).then(res => res.blob())

但我建议您首先 不要使用 base64 。有更好的方法来发送和接收二进制数据。 JSON 并不总是最好的选择。它占用更多带宽并浪费处理时间(解码)编码内容。使用例如 canvas.toBlob 而不是 canvas.toDataURL 并使用 FormData 发送二进制文件。您还可以返回多部分有效负载并使用来自服务器响应的 await response.formData() 对其进行解码。 FormData 可以双向使用。


我对 Jeremy 的 ES6 同步版本做了一个简单的性能测试。

同步版本会暂时阻塞 UI。保持 devtool 打开会降低获取性能

 document.body.innerHTML += '<input autofocus placeholder="try writing">'
// get some dummy gradient image
var img=function(){var a=document.createElement("canvas"),b=a.getContext("2d"),c=b.createLinearGradient(0,0,1500,1500);a.width=a.height=3000;c.addColorStop(0,"red");c.addColorStop(1,"blue");b.fillStyle=c;b.fillRect(0,0,a.width,a.height);return a.toDataURL()}();

async function perf() {

  const blob = await fetch(img).then(res => res.blob())
  // turn it to a dataURI
  const url = img
  const b64Data = url.split(',')[1]

  // Jeremy Banks solution
  const b64toBlob = (b64Data, contentType = '', sliceSize=512) => {
    const byteCharacters = atob(b64Data);
    const byteArrays = [];

    for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
      const slice = byteCharacters.slice(offset, offset + sliceSize);

      const byteNumbers = new Array(slice.length);
      for (let i = 0; i < slice.length; i++) {
        byteNumbers[i] = slice.charCodeAt(i);
      }

      const byteArray = new Uint8Array(byteNumbers);

      byteArrays.push(byteArray);
    }

    const blob = new Blob(byteArrays, {type: contentType});
    return blob;
  }

  // bench blocking method
  let i = 500
  console.time('blocking b64')
  while (i--) {
    await b64toBlob(b64Data)
  }
  console.timeEnd('blocking b64')

  // bench non blocking
  i = 500

  // so that the function is not reconstructed each time
  const toBlob = res => res.blob()
  console.time('fetch')
  while (i--) {
    await fetch(url).then(toBlob)
  }
  console.timeEnd('fetch')
  console.log('done')
}

perf()

原文由 Endless 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题