Blob对象应用实战

背景

Blob 对象是 二进制大型对象binary large object )的简称,顾名思义用来表示二进制数据内容。Blob 对象数据可以按文本或二进制格式进行读取,而且是不可变的。

数据可以来源于文件,因此 Blob 可以用来读写文件,比如读写一个图片文件的内容。操作文件通常采用 File 对象,File 继承了 Blob 的数据读写功能,并在此基础上扩展实现文件系统相关功能。

本文先讲解 Blob 对象相关接口,再讲解一些典型应用场景,最终彻底掌握 Blob 对象用法。

接口

构造函数

浏览器原生提供 Blob() 构造函数,用来创建 Blob 对象实例,接口如下:

1
const blob = Blob(blobParts[, options])
  • blobParts 参数必填,它是一个由字符串或二进制对象组成的数组;
  • options 参数是可选的,它是一个配置对象,传递一些额外的属性,比如 type ,表示数据的 MIME 类型;

举个例子,fragments 数组保存一些 HTML 片段,创建一个 Blob 对象将它们串联起来:

1
2
3
4
5
6
const fragments = [
  '<h1>小菜学编程</h1>',
  '<p>Blob对象应用实战</p>',
];

const html = new Blob(fragments, {type: 'text/html'});

这就是 Blob 对象通过数组指定数据内容的好处,就算数据由若干片段串联而成,也无须手工拼接。

下面这个例子创建一个 Blob 对象,保存 data 对象的 JSON 序列化数据:

1
2
const data = {"domain": "fasionchan.com"};
const blob = new Blob([JSON.stringify(data)], {type: "application/json"});

属性

  • Blob.size ,所包含数据的大小,只读;
  • Blob.type ,表示数据 MIME 类型的字符串,类型未知则为空字符串,只读;

方法

  • Blob.slice([start[, end[, contentType]]]) ,返回包含指定范围数据的新 Blob 对象;
  • Blob.stream() ,返回一个能够读取 Blob 数据内容的 ReadableStream
  • Blob.text() ,返回 Promise ,获得包含 Blob 所有数据的 UTF-8 格式 USVString
  • Blob.arrayBuffer() ,返回 Promise ,获得包含 Blob 所有数据的二进制格式 ArrayBuffer

File对象

文件操作是 Blob 对象的典型使用场景,为此 JS 提供了 File 对象。它拥有 Blob 对象的全部功能,但增加了 namelastModifiedDate 等文件相关属性。我们来看一个例子:

See the Pen for-var by fasionchan (@fasionchan) on CodePen.

浏览器提供的 input 文件选择器可供用户选择文件,用户选好后浏览器会执行 onchange 属性指定的代码。通过 this.files 可以取得用户选好的文件,这是一个 File 对象数组。

在上述例子中选择一个文件,即可在 console 中看到 File 对象结构大致如下:

1
2
3
4
5
6
7
8
{
  name: 'hacker.png',
  type: "image/png",
  lastModified: 1581922676000,
  lastModifiedDate: "Mon Feb 17 2020 14:57:56 GMT+0800 (中国标准时间)",
  webkitRelativePath: '',
  size: 662338,
}

有了 File 对象,我们就在 JS 中操作用户选择的文件,比如读取文件内容并上传。

AJAX

AJAX 请求中,如果指定 responseType 属性为 blob ,便可以将请求响应的数据,以 Blob 对象的形式返回。我们来看一个简单的例子:

See the Pen for-var by fasionchan (@fasionchan) on CodePen.

这段代码通过 AJAX 请求数据,通过 responseType 属性指定返回 Blob 对象:

1
2
3
4
{
  size: 4306,
  type: 'application/json'
}

Blob 对象表明服务器返回的是 JSON 数据,总共 4306 字节,调用 Blob 对象即可读取这些数据。

应用场景

URL生成

图片预览场景通常需要先在网页上展示用户选择的图片,效果没问题后再上传。可问题是:图片还没上传,哪来的 URL 呢?img 标签如何显示图片呢?

好在浏览器允许使用 URL.createObjectURL() 方法,为 Blob 对象生成一个临时的 URL ,这样就可以通过 URL 访问 Blob 对象中的数据。这在需要通过 URL 访问 Blob 数据的场景中非常有用,img 图片就是一个典型的例子。

这个例子演示了如何调用 URL.createObjectURL()Blob 对象生成 URL 以实现图片预览:

See the Pen for-var by fasionchan (@fasionchan) on CodePen.

请看代码,用户选择图片后,浏览器将调用 imagesSeleted 函数,并将 File 对象数组传进来:

  1. 先检查数据长度并取出代表用户选定图片的 File 对象;
  2. 调用 URL.createObjectURL() 方法为 File 对象生成 URLFile 对象也是一种 Blob 对象);
  3. 创建 img 标签,并实现 onload 回调函数,在图片加载完毕后将图片插入网页,并销毁图片的临时 URL
  4. 设置 img 标签 src 属性指向图片的临时 URL ,加载完毕后执行 onload 回调函数;

您可随意选择一张图片,观察 console 中的输出。可以看到 URLblob:// 开头,表明这对应一个 Blob 对象。URL 中包含一个唯一标识符,用来唯一确定内存中的 Blob 对象。

浏览器处理 Blob URL 跟普通 URL 一样,请求成功,返回状态码 200 ,也支持下载。如果 Blob 对象不存在,则返回状态码 404

文件下载

Blob 对象支持生成 URL ,这意味着我们可以将内存中的数据以文件的形式下载下来。这个例子用 JS 生成了一份 JSON 数据,并通过 a 标签触发下载:

See the Pen for-var by fasionchan (@fasionchan) on CodePen.

这段代码先构造一个 Blob 对象,内容是 plain/text 文本。然后调用 downloadBlob 函数将其下载为文件,该函数先为 Blob 对象生成一个 URL ,再创建一个 a 标签触发下载。

通过 a 标签动态触发文件下载,其实跟 Blob 对象关系不大,理论上任何 URL 都可以通过这种方式触发下载。Blob 对象支持生成 URL ,因此支持下载为文件,这个用法很常见。

文件上传

有时我们需要将内存中的数据,以文件的方式上传到服务器。这时可以将数据转成 Blob 对象,因为 Blob 对象可作为文件放在表单中提交:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// 原数据
const data = {
  domain: "fasionchan.com"
};

// 做JSON序列化并转成Blob对象
const blob = new Blob(
  [JSON.stringify(data, null, 2)],
  {type: "application/json"},
);

// 创建表单数据,并将Blob对象当做文件添加进去
const form = new FormData();
form.append("file", blob, "data.json");

// 调用axios发起请求,提交表单
axios.post(url, form);

这段代码将 Blob 对象作为文件添加到表单,append 方法第一个参数是字段名,服务端通过它来取文件;第二个参数是要上传的 Blob 对象;第三个参数是文件名,告诉服务端文件原名是什么。

当然了,我们也可以将 Blob 数据进一步包装成 File 对象来上传,不过兼容性稍差一些:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// 原数据
const data = {
  domain: "fasionchan.com"
};

// 做JSON序列化并转成Blob对象
const blob = new Blob(
  [JSON.stringify(data, null, 2)],
  {type: "application/json"},
);

const file = new File([blob], "data.json");

// 创建表单数据,并将File对象当做文件添加进去
const form = new FormData();
form.append("file", file);

// 调用axios发起请求,提交表单
axios.post(url, form);

File 对象上传跟用 Blob 对象类似,只不过 File 对象可以保存文件名,因而不用传给 append

数据读取

想要读取 Blob 对象中的数据,可以创建一个 FileReader 对象,然后调用 readAsText 方法即可:

See the Pen for-var by fasionchan (@fasionchan) on CodePen.

请看代码,当用户选好文件后,将执行 readBlob 函数读取内容。只要是 Blob 对象,都可以通过这种方式读取。由于 File 对象也是 Blob 对象中的一种,因此 readBlob 也能处理。

全部源码

文件选择器返回File对象

1
<input type="file" name="files" accept="image/*" multiple onchange="console.log(this.files)" />

AJAX请求返回Blob对象

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
function getBlob(url, callback) {
  const xhr = new XMLHttpRequest();
  xhr.open("GET", url);
  xhr.responseType = "blob";
  xhr.onload = function () {
    callback(xhr.response);
  };
  xhr.send(null);
}

const dataUrl = "https://cors.fasionchan.com/data/BookSections.json";
getBlob(dataUrl, function (data) {
  console.log(data); // Blob
});

为Blob对象生成URL

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
function imagesSeleted(fileBlobs) {
  if (!fileBlobs.length) {
    return;
  }

  const fileBlob = fileBlobs[0];
  console.log(fileBlob);

  // generate url
  const url = URL.createObjectURL(fileBlob);
  console.log(url);

  const $img = document.createElement('img');
  $img.onload = function() {
    this.width = 240;
    document.getElementById("preview").appendChild($img);
    URL.revokeObjectURL(this.src);
  };
  $img.src = url;
}

下载Blob对象

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
function downloadBlob(blob, fileName) {
  const url = URL.createObjectURL(blob);

  const $a = document.createElement("a");
  $a.setAttribute("href", url);
  $a.setAttribute("download", fileName);
  $a.click();
}

function example() {
  const blob = new Blob(["hello world!"], {type: "plain/text"});
  downloadBlob(blob, "foo.txt");
}

读取Blob对象数据

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
function readBlob(blob) {
  // 创建FileReader对象
  const reader = new FileReader();
  // 读取Blob对象数据
  reader.readAsText(blob);

  // 注册回调处理读取结果
  reader.onload = function() {
    // 获取读到的数据
    const text = reader.result;
    console.log("file data:", text);
  };

  // 注册回调函数,处理读取错误
  reader.onerror = function(e) {
    console.log("reader error:", e);
  }
}

订阅更新,获取更多学习资料,请关注我们的公众号:

【随笔】系列文章首发于公众号【小菜学编程】,敬请关注: