前端二进制ArrayBuffer、TypedArray、DataView、Blob、File、Base64、FileReader一次性搞清楚

8,232 阅读13分钟

ArrayBuffer

ArrayBuffer对象代表储存二进制数据的一段内存,它不能直接读写,只能通过视图进行操作。

// 创建一个长度为 16 的 buffer 它会分配一个 16 字节(byte)的连续内存空间,并用 0 进行预填充。
const buffer1 = new ArrayBuffer(16);

对于位、字节、字不太理解的可以先看文章末尾扩展部分的介绍。

TypedArray

TypedArray是一组构造函数,一共包含九种类型,每一种都是一个构造函数。

TypedArray的构造函数接受三个参数,第一个ArrayBuffer(其实还可以是数组、视图这里不细说)对象,第二个视图开始的字节号(默认0),第三个视图结束的字节号(默认直到本段内存区域结束)。

名称占用字节描述
Int8Array18位有符号整数
Uint8Array18位无符号整数
Uint8ClampedArray18位无符号整型固定数组(数值在0~255之间)
Int16Array216位有符号整数
Uint16Array216位无符号整数
Int32Array432 位有符号整数
Uint32Array432 位无符号整数
Float32Array432 位 IEEE 浮点数
Float64Array864 位 IEEE 浮点数
// Uint8Array —— 将 ArrayBuffer 中的每个字节(8位)视为 0 到 255 之间的单个数字(每个字节是 8 位)。这称为 “8 位无符号整数”。
// Uint16Array —— 将每 2 个字节(16位)视为一个 0 到 65535 之间的整数。这称为 “16 位无符号整数”。
// Uint32Array —— 将每 4 个字节(32位)视为一个 0 到 4294967295 之间的整数。这称为 “32 位无符号整数”。
// Float64Array —— 将每 8 个字节(64位)视为一个 5.0x10-324 到 1.8x10308 之间的浮点数。

const uint8 = new Uint8Array(buffer1);
const uint16 = new Uint16Array(buffer1);
const uint32 = new Uint32Array(buffer1);
const float64 = new Float64Array(buffer1);

我们可以对其进行操作

console.log(uint8);

uint8[0] = 255;

console.log(uint8);

前后对比发现第0位被我们设置值了

image.png

如果我想再想获取到ArrayBuffer怎么办呢?

可以通过 obj.buffer 获取,比如上面的例子我们就可以使用uint8.buffer获取到原始ArrayBuffer

DataView

DataView 就是一种更灵活的视图,DataView视图支持除Uint8ClampedArray以外的八种类型。DataView比使用TypedArray更方便,只需要简单的创建一次就能进行各种转换。

// 可以转成各种格式
const dataView1 = new DataView(buffer1);
// 接受三个参数,1.字节序号,2.写入的数据,3.写入方式(true:小端/false|undeifined:大端)
dataView1.setFloat64(0, 25, false); // 在第一个字节以大端字节序写入一个值为25的32位整数
console.log(dataView1);
console.log(dataView1.getUint8(0)); // 64
console.log(dataView1.getUint16(0)); // 16441
console.log(dataView1.getUint32(0)); // 1077477376
console.log(dataView1.getFloat64(0)); // 25

如果我想再想获取到ArrayBuffer怎么办呢?

可以通过 obj.buffer 获取,比如上面的例子我们就可以使用dataView1.buffer获取到原始ArrayBuffer

Blob

Blob 对象表示一个不可变、原始数据的类文件对象。

// 构造函数
const blob = new Blob(array, options)
  • array 是一个由ArrayBuffer, ArrayBufferView, Blob, DOMString 等对象构成的数组,DOMStrings会被编码为UTF-8。

  • options 是一个可选,它可能会指定如下两个属性:

    • type,默认值为 "",内容的MIME类型。
    • endings,默认值为"transparent",用于指定包含行结束符\n的字符串如何被写入。 它是以下两个值中的一个: "native",代表行结束符会被更改为适合宿主操作系统文件系统的换行符,或者 "transparent",代表会保持blob中保存的结束符不变
const blob1 = new Blob(['hello randy'], { type: "text/plain" });

实例属性、方法

属性

const blob = new Blob(["hello", "randy"], { type: "text/plain" });
// 输出的对象有如下属性
// size: 10;
// type: "text/plain";
console.log(blob);

方法

  • slice()Blob 中截取一部分并返回一个新的 Blob(用法同数组的 slice)
  • arrayBuffer() 返回一个以二进制形式展现的 promise
  • stream() 返回一个ReadableStream对象
  • text() 返回一个文本形式的 promise
// 转成stream
console.log(blob.stream());

// 转成Arraybuffer
blob.arrayBuffer().then((res) => {
  console.log(res);
});

// 转成文本
blob.text().then((res) => {
  console.log(res);
});

blob url

简单的理解一下就是将一个fileBlob类型的对象转为UTF-16的字符串,并保存在当前操作的document下,存储在内存中。

类似这样一个链接

blob:http://localhost:3000/53acc2b6-f47b-450f-a390-bf0665e04e59

生成blob url使用的方法是URL.createObjectURL(file/blob)。清除方式只有页面unload()事件或者使用URL.revokeObjectURL(objectURL)手动清除 。

这在前端下载中经常会用到。

export const downloadFile = async (params, fileName) => {
  // 我们使用axios设置接口返回类型 responseType: "blob", 所以这里从后端返回的是blob。
  const results = await download(params);
  
  const a = document.createElement("a");
  a.download = fileName + ".xlsx";
  // 生成blob url。这里可以使用Blob对象或者File对象
  a.href = window.URL.createObjectURL(results);
  a.style.display = "none";
  document.body.appendChild(a);
  a.click();
  // 释放内存
  window.URL.revokeObjectURL(a.href);
  document.body.removeChild(a);
};

File

File 描述文件信息的一个对象,可以让 JavaScript 访问文件信息。File 继承于 Blob

const file = new File(array, name[, options])
  • array 是一个由ArrayBuffer, ArrayBufferView, Blob, DOMString 等对象构成,DOMStrings会被编码为UTF-8。

  • name 表示文件名称,或者文件路径。

  • options 是一个可选,它可能会指定如下两个属性:

    • type,默认值为 "",内容的MIME类型。
    • lastModified: 数值,表示文件最后修改时间的 Unix 时间戳(毫秒)。默认值为 Date.now()。

实例属性、方法

属性

  • type 类型 常见的MIME 类型
  • size 大小、单位为字节
  • name 文件名称
  • lastModified 最后修改时间(时间戳)
  • lastModifiedDate 最后修改时间
const file1 = new File(["文件对象"], "test", { type: "text/plain" });
// 输出的对象有如下属性
// lastModified: 1640589621358
// lastModifiedDate: Mon Dec 27 2021 15:20:21 GMT+0800 (中国标准时间) {}
// name: "test"
// size: 12
// type: "text/plain"
// webkitRelativePath: ""
console.log(file1);

方法

  • slice()Blob 中截取一部分并返回一个新的 Blob(用法同数组的 slice)
  • arrayBuffer() 返回一个以二进制形式展现的 promise
  • stream() 返回一个ReadableStream对象
  • text() 返回一个文本形式的 promise
// 转成stream
console.log(file1.stream());

// 转成Arraybuffer
file1.arrayBuffer().then((res) => {
  console.log(res);
});

// 转成文本
file1.text().then((res) => {
  console.log(res);
});

Base64

定义

Base64是一种编码格式,在前端经常会碰到,格式是 data:[<mediatype>][;base64],<data>

除了使用工具进行Base64编码外,js还内置了两个方法能进行字符串的Base64的编码和解码。

const str1 = "hello randy";

// 编码
const b1 = window.btoa(str1);
console.log(b1); // aGVsbG8gcmFuZHk=

// 解码
const str2 = window.atob(b1);
console.log(str2); // hello randy

优点

  1. 可以将二进制数据(比如图片)转化为可打印字符,方便传输数据。
  2. 对数据进行简单的加密,肉眼是安全的。
  3. 如果是在html或者css处理图片,可以减少http请求。

缺点

  1. 内容编码后体积变大, 至少大1/3。因为是三字节变成四个字节,当只有一个字节的时候,也至少会变成三个字节。
  2. 编码和解码需要额外工作量。

FileReader

FileReader 对象允许 Web 应用程序异步读取存储在用户计算机上的文件(或原始数据缓冲区)的内容.

属性

属性描述
FileReader.error一个DOMException,表示在读取文件时发生的错误 。
FileReader.result返回文件的内容。只有在读取操作完成后,此属性才有效,返回的数据的格式取决于是使用哪种读取方法来执行读取操作的。
FileReader.readyState表示FileReader状态的数字。0 还没有加载任何数据。1 数据正在被加载。2 已完成全部的读取请求。

方法

需要注意的是 ,无论读取成功或失败,方法并不会返回读取结果,这一结果存储在 result属性中。

方法名描述
FileReader.abort()中止读取操作。在返回时,readyState 属性为 DONE。
FileReader.readAsArrayBuffer()将读取的内容转成ArrayBuffer。
FileReader.readAsBinaryString()将读取的内容转成二进制数据。
FileReader.readAsDataURL()将读取的内容转成并将其编码为 base64 的 data url。 格式是 data:[<mediatype>][;base64],<data>
FileReader.readAsText()将数据读取为给定编码(默认为 utf-8 编码)的文本字符串。

事件

事件描述
FileReader.onabort处理 abort 事件。该事件在读取操作被中断时触发。
FileReader.onerror处理 error 事件。该事件在读取操作发生错误时触发。
FileReader.onload处理 load 事件。该事件在读取操作完成时触发。
FileReader.onloadstart处理 loadstart 事件。该事件在读取操作开始时触发。
FileReader.onloadend处理 loadend 事件。该事件在读取操作结束时(要么成功,要么失败)触发。
FileReader.onprogress处理 progress 事件。该事件在读取Blob时触发。

例子

const blob3 = new Blob(["hello", "randy"], { type: "text/plain" });

const fileReader = new FileReader();

fileReader.readAsDataURL(blob3);

fileReader.onload = () => {
  console.log(fileReader);
  // 通过fileReader获取结果
  // fileReader.result 是结果(如果成功)
  // fileReader.error 是 error(如果失败)。
};

相互转换

Blob和File的互相转换

Blob转File

const blob1 = new Blob(["blob文件"], { type: "text/plain" });
// blob转file
const file2 = new File([blob1], "test2", { type: blob1.type });
console.log("file2: ", file2);

File转Blob

const file1 = new File(["文件对象"], "test", { type: "text/plain" });
// file转blob
const blob2 = new Blob([file1], { type: file1.type });
console.log("blob2: ", blob2);

File、Blob、img转Base64

Blob转Base64

// Blob转Base64
const blob = new Blob(["hello", "randy"], { type: "text/plain" });

const fileReader = new FileReader();

fileReader.readAsDataURL(blob);

fileReader.onload = () => {
  console.log(fileReader.result); // "data:text/plain;base64,aGVsbG9yYW5keQ=="
};

File转Base64

// File转Base64
const file1 = new File(["文件对象"], "test", { type: "text/plain" });

const fileReader = new FileReader();

fileReader.readAsDataURL(file1);

fileReader.onload = () => {
  console.log(fileReader.result); // "data:text/plain;base64,5paH5Lu25a+56LGh"
};

img转Base64

// 本地图片转base64,注意链接是本地链接不能是网络地址。
const img2base64 = (imgUrl) => {
  let image = new Image();
  image.src = imgUrl;
  return new Promise((resolve) => {
    image.onload = () => {
      let canvas = document.createElement("canvas");
      canvas.width = image.width;
      canvas.height = image.height;
      var context = canvas.getContext("2d");
      context.drawImage(image, 0, 0, image.width, image.height);
      let dataUrl = canvas.toDataURL("image/png");
      resolve(dataUrl);
    };
  });
};

img2base64("../vue2/src/assets/logo.png").then((res) => {
  console.log(res);
});

Base64转Blob、File

Base64转Blob

function dataURLtoBlob(dataurl) {
  // `data:[<mediatype>][;base64],<data>`
  var arr = dataurl.split(","),
    mime = arr[0].match(/:(.*?);/)[1],
    bstr = atob(arr[1]),
    n = bstr.length,
    u8arr = new Uint8Array(n);
  while (n--) {
    u8arr[n] = bstr.charCodeAt(n);
  }
  return new Blob([u8arr], { type: mime });
}

Base64转File

function dataURLtoFile(dataurl, filename) {
  //将base64转换为文件
  var arr = dataurl.split(","),
    mime = arr[0].match(/:(.*?);/)[1],
    bstr = atob(arr[1]),
    n = bstr.length,
    u8arr = new Uint8Array(n);
  while (n--) {
    u8arr[n] = bstr.charCodeAt(n);
  }
  return new File([u8arr], filename, { type: mime });
}

扩展

位、字节、字

位(bit)、字节(byte)、字(word)是计算机数据存储的单位。位是最小的存储单位,每一个位存储一个1位的二进制码,一个字节由8位组成。而字通常为16、32或64个位组成。

是最基本的概念,在计算机中,由于只有逻辑0和逻辑1的存在,因此很多东西、动作、数字都要表示为一串二进制的字码例如: 1001 0000 1101等等。其中每一个逻辑0或者1便是一个位。例如这个例子里的1000 1110共有八个位,它的英文名字叫(bit),是计算机中最基本的单位。

字节,是由八个位组成的一个单元,也就是8个bit组成1个Byte。字节有什么用呢? 在计算机科学中,用于表示ASCII字符,便是运用字节来记录表示字母和一些符号~例如字符A便用 “0100 0001”来表示。

代表计算机处理指令或数据的二进制数位数,是计算机进行数据存储和数据处理的运算的单位。对于32位计算机与64位计算机,字的大小往往不同。32位计算机:1字=32位=4字节,64位计算机:1字=64位=8字节

所以1字节就是8位。1字有可能是2字节、4字节、8字节。

字符集、编码

字符集即是文字符号和二进制的一种映射关系。因为电脑只认识二进制,所以就需要把我们平时使用的字符来用二进制表示并存储到电脑。常用的字符集有ASCII、unicode、GB2312/GBK

比如在ASCII字符集中,大写字母A对应的ASCII码是65,二进制就是01000001;阿拉伯数字0对应的ASCII码是48,二进制就是110000;还有空格32、回车13等等一些键盘上所见的符号。

由于ASCII美国信息交换标准代码ASCII起初只规定了128个字符 (0-9的数字,A-Z大写和小写英文字母,以及一些特殊字符。),就能代表了所有键盘上的普通符号、阿拉伯数字和英文字母。对于英文国家来说能满足日常使用,所以他们把每个字节的第一位置置0,只需要使用后面7位就足够了。(也就是只需要使用7位,2的7次方,能表示128个不同字符)。

但是其他欧洲国家比如法语、葡萄牙语等无法用128个字符表示完全,所以他们就把第一个空闲0使用了起来。因此欧洲国家用一个字节(8位可表示256个字符)也能实现。所以ASCII有128和256两种版本。

随着计算机在全球的发展和普及开来,其他语言,比如中文、日文,单字节256个字符肯定没法完全表示。所以就出现了一个全球统一的编码规则叫unicode,它的作用是把各个语言的字符映射为二进制和ASCII的目的一样。

由于unicode编码都是两字节的,偏僻的可能用了四字节。也就是说至少会用到16位的内存空间,所以如果我们需要编码的文本都是英文的,那么用unicode是不是很浪费,明明一个字节就可以,却用了两字节。因此就出现了后面的编码

常见的编码有UFT-8、UTF-16、UTF-32。我们来说说平时使用的最多的UTF-8UTF-8编码把一个Unicode字符根据不同的数字大小编码成1-6个字节,常用的英文字母被编码成1个字节,汉字通常是3个字节,只有很生僻的字符才会被编码成4-6个字节。如果你要传输的文本包含大量英文字符,用UTF-8编码就能节省空间。

所以总结就是:

ASCII、unicode、GB2312/GBK都是是字符集,定义每个字符对应的数字。UTF-8、UTF-16等是unicode字符集的编码格式,定义“字符对应的数字”如何以二进制的方式存储。

encodeURI、encodeURIComponent、escape

介绍完字符集以及编码,我们再来说说js中对字符串进行编码的三个方法encodeURI、encodeURIComponent、escape

escape该方法不会对 ASCII 字母和数字进行编码,也不会对下面这些 ASCII 标点符号进行编码: * @ - _ + . / 。 其他所有的字符都会被转义序列替换。

// 编码
escape('编码randy123*@-_+./') // %u7F16%u7801randy123*@-_+./

// 解码
unescape('%u7F16%u7801randy123*@-_+./') // 编码randy123*@-_+./

注意: escape() 函数已经从 Web 标准中删除,所以尽量不使用该函数,可以使用 encodeURI 或 encodeURIComponent 代替。

encodeURI 和 encodeURIComponent 这两种方法都是对URL进行编码的,唯一的区别是编码的范围不一样。

encodeURI方法不会对下列字符编码 :ASCII字母、数字、~!@#$&*()=:/,;?+'

encodeURIComponent 方法不会对下列字符编码: ASCII字母、数字、~!*()'

// 编码
encodeURI("编码randy~!@#$&*()=:/,;?+'") // %E7%BC%96%E7%A0%81randy~!@#$&*()=:/,;?+'

// 解码
decodeURI("%E7%BC%96%E7%A0%81randy~!@#$&*()=:/,;?+'") // 编码randy~!@#$&*()=:/,;?+'

// 编码
encodeURIComponent("编码randy~!*()'"); // %E7%BC%96%E7%A0%81randy~!*()'

// 解码
decodeURIComponent("%E7%BC%96%E7%A0%81randy~!*()'"); // 编码randy~!*()'

由此可见,encodeURIComponent 编码范围比 encodeURI 的大。

参考文章

JavaScript中 FileReader 对象详解

Blob、ArrayBuffer、File、FileReader和FormData的区别

初探 Js 中的 Blob、ArrayBuffer、File 、FileRender、FormData

后记

本文为笔者个人学习笔记,如有谬误,还请告知,万分感谢!如果本文对你有所帮助,还请点个赞~~