pfan123 / Articles

经验文章
169 stars 25 forks source link

Blob 与 ArrayBuffer #91

Open pfan123 opened 3 years ago

pfan123 commented 3 years ago

Blob 与 ArrayBuffer

早期 JS 一直没有比较好的可以直接处理二进制的方法。而 Blob 的存在,允许我们可以通过JS直接操作二进制数据。ArrayBuffer 是 Blob 数据格式的组成部分。

Blob

Blob(Binary Large Object) 表示二进制类型的大对象。Blob 的概念在一些数据库中有使用到,如 MYSQL 中的 BLOB 类型就表示二进制数据的容器。在 JavaScript 中 Blob 类型的对象表示不可变的类似文件对象的原始数据,会存储在内存中。

File 接口基于 Blob ,继承了 Blob 的功能并将其扩展使其支持用户系统上的文件。

Blob 由一个可选的字符串 type (通常是 MIME 类型)和 blobParts 组成:

img

构造 Blob

const blob = new Blob(['我是Blob'],{type: 'text/html'}); 

Blob 属性

Blob 对象含有两个属性:size 和 type。其中 size 属性用于表示数据的大小(以字节为 单位), type 是 MIME 类型的字符串。

blob.size   //Blob大小(以字节为单位)

blob.type   //Blob的MIME类型,如果是未知,则是“ ”(空字符串)          

Blob URL/Object URL

Blob URL/Object URL 是一种伪协议,允许 Blob 和 File 对象用作图像,下载二进制数据链接等URL源。在浏览器中,我们使用 URL.createObjectURL 方法来创建 Blob URL,该方法接收一个 Blob 对象,并为其创建一个唯一的 URL,其形式为 blob:<origin>/<uuid> ,如下:

blob:https://example.org/40a5fb5a-d56d-4a33-b4e2-0acf6a8e5f641

浏览器内部为每个通过 URL.createObjectURL 生成的 URL 存储了一个 URL → Blob 映射。生成的 URL 仅在当前文档打开的状态下才有效。

const blobURL = URL.createObjectURL(blob);

Blob URL 实际上会存在有副作用。存储的 URL → Blob 映射,会导致驻留在内存中的 Blob 对象,无法被浏览器释放。在文档卸载时会映射自动清除,Blob 对象也会随之被释放。但如果应用程序寿命很⻓,就会出现长期占用内存的情况。

针对这个问题,可以调用 URL.revokeObjectURL(url) 方法,从内部映射中删除引用,从而允许删除 Blob(如果没有其他引用),并释放内存。

window.URL.revokeObjectURL(blobURL);          

Blob 常见使用场景

function upload(blobOrFile) {

  const xhr = new XMLHttpRequest();

  xhr.open('POST', '/server', true);

  xhr.onload = function(e) { ... };

  xhr.send(blobOrFile);

}

document.querySelector('input[type="file"]').addEventListener('change', function(e) {

  const blob = this.files[0];

  const BYTES_PER_CHUNK = 1024 * 1024; // 1MB chunk sizes.

  const SIZE = blob.size;

  let start = 0;

  let end = BYTES_PER_CHUNK;

  while(start < SIZE) {

    upload(blob.slice(start, end));

    start = end;

    end = start + BYTES_PER_CHUNK;

  }

}, false)
const video = document.getElementById('video');

const obj_url = window.URL.createObjectURL(blob);  // ArrayBuffer Blob

video.src = obj_url;

video.play()

window.URL.revokeObjectURL(obj_url);

在"被污染"的画布中调用以下方法将会抛出安全错误:

使用 Blob、URL.createObjectURL 优化后,减少一次请求:

service worker 脚本的 URL, 不支持 Blob/String URL,Create service worker from Blob/String URL

const workerContent = `self.addEventListener('message', function (evt) {

    const num1 = evt.data.num1;

    const num2 = evt.data.num2;

    const result = num1 + num2;

    self.postMessage({result: result});

}, false)`;

const workerBlob = new Blob([workerContent], { type:'text/javascript' });

const workerUrl = window.URL.createObjectURL(workerBlob);

const worker = new Worker(workerUrl);

window.URL.revokeObjectURL(workerUrl);

worker.addEventListener('message', function(evt) {

    console.log(`[main] result is: ${evt.data.result}.`);

}, false);

worker.postMessage({num1: 20, num2: 10});

ArrayBuffer

ES2015 推出之后,JavaScript 开始支持在原始二进制缓冲区中读取和写入数据,这个缓冲区被称为 ArrayBuffer 。

ArrayBuffer 对象用来表示通用的、固定长度的原始二进制数据缓冲区。它是一个字节数组,通常在其他语言中称为“byte array”。

我们不能直接操作 ArrayBuffer 的内容,而是要通过 TypedArray 对象或 DataView 对象来操作,它们会将缓冲区中的数据表示为特定的格式,并通过这些格式来读写缓冲区的内容。

ArrayBuffer 目前似乎应用的场景不多,但了解它,可以让我们理解 JavaScript 如何直接操作字节模块。

可尝试利用 ArrayBuffer 与 WebAssembly 进行更高效率的交互

TypedArray 操作 ArrayBuffer

TypedArray 允许程序以统一的类型数组的形式访问缓冲区,例如 Int8Array、Int16Array 或 Float32Array 。

const buffer = new ArrayBuffer(32);
const array = new Int16Array(buffer);

for (let i = 0; i < array.length; i++) {
  array[i] = i * i;
}

DataView 操作 ArrayBuffer

DataView 允许更细粒度的数据访问。 我们可以通过为每种值类型提供专门的 getter 和 setter 来选择从缓冲区读取和写入的值的类型,这使得 DateView 可用于序列化数据结构。

const buffer = new ArrayBuffer(32);

const view = new DataView(buffer);

const person = { age: 42, height: 1.76 };

view.setUint8(0, person.age);

view.setFloat64(1, person.height);

console.log(view.getUint8(0)); // 输出: 42

console.log(view.getFloat64(1)); // 输出: 1.76

Buffer

Buffer是 Node.JS 中用于操作 ArrayBuffer 的视图,是 TypedArray 的一种。

Buffer 类是 JavaScript 的 Uint8Array 类的子类,且继承时带上了涵盖额外用例的方法。 只要支持 Buffer 的地方,Node.js API 都可以接受普通的 Uint8Array

创建一个Buffer 对象时,JavaScript会根据请求内存的大小,分成两种方式来分配内存。

Blob 对象与 ArrayBuffer 对象对比

Blob 对象与 ArrayBuffer 对象拥有各自的特点,它们之间的区别如下:

document.querySelector('input[type="file"]').addEventListener('change', function(e) {

  const blob = this.files[0];

  const fileType = blob.slice(0, 4);

  const reader = new FileReader();

  reader.readAsArrayBuffer(fileType);

  reader.onload = function (e) {

    let buffer = reader.result;

    let view = new DataView(buffer);

    let magic = view.getUint32(0, false);

    switch(magic) {

      case 0x89504E47: file.verified_type = 'image/png'; break;

      case 0x47494638: file.verified_type = 'image/gif'; break;

      case 0x25504446: file.verified_type = 'application/pdf'; break;

      case 0x504b0304: file.verified_type = 'application/zip'; break;

    }

  };

}, false)
function GET(url, callback) {

let xhr = new XMLHttpRequest();

xhr.open('GET', url, true);

xhr.responseType = 'arraybuffer'; // or xhr.responseType = "blob"; xhr.send();

xhr.onload = function(e) { if (xhr.status != 200) {

alert("Unexpected status code " + xhr.status + " for " + url);

      return false;

    }

callback(new Uint8Array(xhr.response)); // or new Blob([xhr.response]); };

}

Other Resources

ArrayBuffer 和 Buffer 有何区别?

Node.js 中的缓冲区(Buffer)究竟是什么?