JS中的二进制:File、Blob、FileReader、ArrayBuffer、Base64


关系图

  • JavaScript 提供了一些 API 来处理文件或原始文件数据,例如:File、Blob、FileReader、ArrayBuffer、base64 等,他们之间的区别和联系如下:
    File、Blob、FileReader、ArrayBuffer、base64

Blob

  • Blob 全称为 binary large object ,即二进制大对象,它是 JavaScript 中的一个对象,表示原始的类似文件的数据。
  • 实际上,Blob 对象是包含有只读原始数据的类文件对象。简单来说,Blob 对象就是一个不可修改的二进制文件。

Blob 创建

new Blob(array, options);

// 创建时需要传入两个参数:array, options
//    array:由 ArrayBuffer、ArrayBufferView、Blob、DOMString 等对象构成的
//    options:可选的 BlobPropertyBag 字典,它可能会指定如下两个属性
//        type:默认值为 "",表示将会被放入到 blob 中的数组内容的 MIME 类型
//        endings:默认值为"transparent",用于指定包含行结束符\n的字符串如何被写入,不常用

// 例如:
const blob = new Blob(["Hello World"], {type: "text/plain"});
console.log(blob); // Blob {size: 11, type: 'text/plain'}

// 可以使用 URL.createObjectURL() 方法将将其转化为一个 URL,如果是图片类型可以直接引用url,文本类型可以直接在iframe中加载
let url = URL.createObjectURL(blob);
  • 常见的MIME类型

    MIME类型 描述
    text/plain 纯文本文档
    text/html HTML文档
    text/javascript JavaScript文件
    text/css CSS文件
    application/json JSON文件
    application/pdf PDF文件
    application/xml XML文件
    image/jpeg JPEG图像
    image/png PNG图像
    image/gif GIF图像
    image/svg+xml SVG图像
    audio/mpeg MP3文件
    video/mpeg MP4文件

Blob 分片

  • Blob 对象内置了 slice() 方法用来将 blob 对象分片
    const blob = instanceOfBlob.slice([start [, end [, contentType]]]};
    // start:设置切片的起点,即切片开始位置。默认值为 0
    // end:设置切片的结束点,会对该位置之前的数据进行切片。默认值为blob.size
    // contentType:设置新 blob 的 MIME 类型。如果省略 type,则默认为 blob 的原始值
    
    const iframe = document.getElementsByTagName("iframe")[0];
    const blob = new Blob(["Hello World"], {type: "text/plain"});
    const subBlob = blob.slice(0, 5);
    iframe.src = URL.createObjectURL(subBlob);
    // 此时页面会显示"Hello"

File

  • 文件(File)接口提供有关文件的信息,并允许网页中的 JavaScript 访问其内容。实际上,File 对象是特殊类型的 Blob,且可以用在任意的 Blob 类型的 context 中。Blob 的属性和方法都可以用于 File 对象
  • 注意:File 对象中只存在于浏览器环境中,在 Node.js 环境中不存在
  • 在 JavaScript 中,主要有两种方法来获取 File 对象
    • <input> 元素上选择文件后返回的 FileList 对象
    • 文件拖放操作生成的 DataTransfer 对象

input

<input type="file" id="fileInput" multiple="multiple">
const fileInput = document.getElementById("fileInput");
fileInput.onchange = (e) => {
  console.log(e.target.files); // 输出一个 FileList 数组
}

// 每个 File 对象都包含文件的一些属性,这些属性都继承自 Blob 对象:
//   lastModified:引用文件最后修改日期,为自1970年1月1日0:00以来的毫秒数
//   lastModifiedDate:引用文件的最后修改日期
//   name:引用文件的文件名
//   size:引用文件的文件大小
//   type:文件的媒体类型(MIME)
//   webkitRelativePath:文件的路径或 URL

文件拖放

  • 这种获取 File 对象的方式就是通过 ondrop 和 ondragover 两个拖放 API 实现的
    <!-- 定义拖放区域 -->
    <div id="drop-zone"></div>
    const dropZone = document.getElementById("drop-zone");
    dropZone.ondragover = (e) => {
        e.preventDefault(); // 阻止默认事件
    }
    dropZone.ondrop = (e) => {
        e.preventDefault(); // 阻止默认事件
        const files = e.dataTransfer.files;
        console.log(files); // 输出FileList数组
    }

FileReader

  • FileReader 是一个异步 API,用于读取文件并提取其内容以供进一步使用。FileReader 可以将 Blob 读取为不同的格式
  • 注意:FileReader 仅用于以安全的方式从用户(远程)系统读取文件内容,不能用于从文件系统中按路径名简单地读取文件

基本使用

const reader = new FileReader();
// 对象常用属性:error(读取文件错误)、result(返回文件内容)、readyState(状态)

// FileReader 对象提供了以下方法来加载文件:
//     reader.readAsArrayBuffer():读取指定 Blob 中的内容,完成之后,result 属性中保存的将是被读取文件的 ArrayBuffer 数据对象
//     reader.readAsBinaryString():读取指定 Blob 中的内容,完成之后,result 属性中将包含所读取文件的原始二进制数据
//     reader.readAsDataURL():读取指定 Blob 中的内容,完成之后,result 属性中将包含一个data: URL 格式的 Base64 字符串以表示所读取文件的内容
//     reader.readAsText():读取指定 Blob 中的内容,完成之后,result 属性中将包含一个字符串以表示所读取的文件内容

事件处理

  • FileReader 对象常用的事件如下:
    • abort:该事件在读取操作被中断时触发
    • error:该事件在读取操作发生错误时触发
    • load:该事件在读取操作完成时触发
    • progress:该事件在读取 Blob 时触发
  • 这些方法可以加上前置 on 后在HTML元素上使用,比如onload、onerror、onabort、onprogress
  • 由于FileReader对象继承自EventTarget,因此还可以使用 addEventListener() 监听上述事件
<input type="file" id="fileInput">
  • 读取文本文档(如.txt文件)
    // 首先创建了一个 FileReader 对象,当文件上传成功时,使用 readAsText() 方法读取 File 对象,当读取操作完成时打印读取结果
    const fileInput = document.getElementById("fileInput");
    const reader = new FileReader();
    fileInput.onchange = (e) => {
        reader.readAsText(e.target.files[0]);
    }
    reader.onload = (e) => {
        console.log(e.target.result); // 打印文本内容
    }
  • 读取二进制文件(如图片、pdf)
    const fileInput = document.getElementById("fileInput");
    const reader = new FileReader();
    fileInput.onchange = (e) => {
      reader.readAsDataURL(e.target.files[0]);
    }
    reader.onload = (e) => {
      console.log(e.target.result); // 打印base64编码的 URL
    }
  • 当上传大文件时,可以通过 progress 事件来监控文件的读取进度
    const reader = new FileReader();
    reader.onprogress = (e) => {
      if (e.loaded && e.total) {
        const percent = (event.loaded / event.total) * 100;
        console.log(`上传进度: ${Math.round(percent)} %`);
      }
    });
    // progress 事件提供了两个属性:loaded(已读取量)和total(需读取总量)

ArrayBuffer

  • ArrayBuffer 对象用来表示通用的、固定长度的原始二进制数据缓冲区。ArrayBuffer 的内容不能直接操作,只能通过 DataView 对象或 TypedArrray 对象来访问。这些对象用于读取和写入缓冲区内容。

    • TypedArray:用来生成内存的视图,通过9个构造函数,可以生成9种数据格式的视图。
    • DataViews:用来生成内存的视图,可以自定义格式和字节序。

    ArrayBuffer

  • TypedArray视图和 DataView视图的区别主要是字节序,前者的数组成员都是同一个数据类型,后者的数组成员可以是不同的数据类型。

  • ArrayBuffer 与 Blob 有啥区别呢?根据 ArrayBuffer 和 Blob 的特性,Blob 作为一个整体文件,适合用于传输;当需要对二进制数据进行操作时(比如要修改某一段数据时),就可以使用 ArrayBuffer

ArrayBuffer的方法和属性

  • new ArrayBuffer()
    new ArrayBuffer(bytelength)
    
    // 参数:它接受一个参数,即 bytelength,表示要创建数组缓冲区的大小(以字节为单位。);
    // 返回值:返回一个新的指定大小的ArrayBuffer对象,内容初始化为0。
  • ArrayBuffer.isView()
    // ArrayBuffer 上有一个 isView()方法,它的返回值是一个布尔值,如果参数是 ArrayBuffer 的视图实例则返回 true,例如类型数组对象或 DataView 对象;否则返回 false。简单来说,这个方法就是用来判断参数是否是 TypedArray 实例或者 DataView 实例
    const buffer = new ArrayBuffer(16);
    ArrayBuffer.isView(buffer)   // false
    
    const view = new Uint32Array(buffer);
    ArrayBuffer.isView(view)     // true

TypedArray的属性和方法

  • TypedArray 对象一共提供 9 种类型的视图,每一种视图都是一种构造函数,如上图
  • 这些构造函数生成的对象统称为 TypedArray 对象。它们和正常的数组很类似,都有length 属性,都能用索引获取数组元素,所有数组的方法都可以在类型化数组上面使用
  • 使用 new TypedArray() 方式
    new Int8Array(length);
    new Int8Array(typedArray);
    new Int8Array(object);
    new Int8Array(buffer [, byteOffset [, length]]);

DataView的属性和方法

  • DataView 视图是一个可以从 二进制 ArrayBuffer 对象中读写多种数值类型的底层接口,使用它时,不用考虑不同平台的字节序问题
  • DataView视图提供更多操作选项,而且支持设定字节序。本来,在设计目的上,ArrayBuffer对象的各种TypedArray视图,是用来向网卡、声卡之类的本机设备传送数据,所以使用本机的字节序就可以了;而DataView视图的设计目的,是用来处理网络设备传来的数据,所以大端字节序或小端字节序是可以自行设定的
  • new DataView()
    new DataView(buffer [, byteOffset [, byteLength]])  
    
    // buffer:一个已经存在的 ArrayBuffer 对象,DataView 对象的数据源。
    // byteOffset:可选,此 DataView 对象的第一个字节在 buffer 中的字节偏移。如果未指定,则默认从第一个字节开始。
    // byteLength:可选,此 DataView 对象的字节长度。如果未指定,这个视图的长度将匹配 buffer 的长度。

Object URL

  • Object URL(MDN定义名称)又称Blob URL(W3C定义名称),是HTML5中的新标准。它是一个用来表示File Object 或Blob Object 的URL

  • 其实 Blob URL/Object URL 是一种伪协议,允许将 Blob 和 File 对象用作图像、二进制数据下载链接等的 URL 源。

  • 对于 Blob/File 对象,可以使用 URL构造函数的 createObjectURL() 方法创建将给出的对象的 URL。这个 URL 对象表示指定的 File 对象或 Blob 对象。我们可以在<img><script> 标签中或者 <a><link> 标签的 href 属性中使用这个 URL

    <input type="file" id="fileInput" />
    <img id="preview" />
    // 使用 URL.createObjectURL() 将File 对象转化为一个 URL
    const fileInput = document.getElementById("fileInput");
    const preview = document.getElementById("preview");
    
    fileInput.onchange = (e) => {
      preview.src = URL.createObjectURL(e.target.files[0]);
      console.log(preview.src);
    };
    // 使用createObjectURL()方法创建一个data URL 时,就需要使用revokeObjectURL()方法从内存中清除它来释放内存
    // 虽然浏览器会在文档卸载时自动释放 Data URL,但为了提高性能,我们应该使用revokeObjectURL()来手动释放它
    // revokeObjectURL()方法接受一个Data URL 作为其参数,返回undefined
    const objUrl = URL.createObjectURL(new File([""], "filename"));
    console.log(objUrl);
    URL.revokeObjectURL(objUrl);

Base64

  • Base64 是一种基于64个可打印字符来表示二进制数据的表示方法。Base64 编码普遍应用于需要通过被设计为处理文本数据的媒介上储存和传输二进制数据而需要编码该二进制数据的场景。这样是为了保证数据的完整并且不用在传输过程中修改这些数据
  • 在 JavaScript 中,有两个函数被分别用来处理解码和编码 base64 字符串:
    • atob():解码,解码一个 Base64 字符串
    • btoa():编码,从一个字符串或者二进制数据编码一个 Base64 字符串
      btoa("JavaScript")       // 'SmF2YVNjcmlwdA=='
      atob('SmF2YVNjcmlwdA==') // 'JavaScript'
  • 使用
    // 使用toDataURL()方法把 canvas 画布内容生成 base64 编码格式的图片
    const canvas = document.getElementById('canvas'); 
    const ctx = canvas.getContext("2d");
    const dataUrl = canvas.toDataURL();
    <input type="file" id="fileInput" />
    <img id="preview" />
    // 使用readAsDataURL()方法把上传的文件转为base64格式的data URI,比如上传头像展示
    const fileInput = document.getElementById("fileInput");
    const preview = document.getElementById("preview");
    const reader = new FileReader();
    
    fileInput.onchange = (e) => {
      reader.readAsDataURL(e.target.files[0]);
    };
    
    reader.onload = (e) => {
      preview.src = e.target.result;
      console.log(e.target.result);
    };
  • 一些小的图片都可以使用 base64 格式进行展示,img标签和background的 url 属性都支持使用base64 格式的图片,这样做也可以减少 HTTP 请求

Base64转码/解码

  • Base64编码和解码可以通过内置的window.btoa()和window.atob()函数来实现
  • btoa()和atob()函数只能处理纯文本数据。如果字符串包含二进制数据,需要先将二进制数据转换为Base64字符串,然后再进行编码
  • 对于非ASCII字符(如中文字符),btoa()和atob()函数可能无法正确处理,在这种情况下,需要先将字符串转换为UTF-8编码的二进制数据,然后再进行Base64编码和解码
    // UTF-8 编码的字符串转换为 Base64
    function utf8ToBase64(str) {
      return btoa(unescape(encodeURIComponent(str)));
    }
    
    // Base64 转换为 UTF-8 编码的字符串
    function base64ToUtf8(base64) {
      return decodeURIComponent(escape(atob(base64)));
    }
    
    // 示例
    var utf8String = "Hello";
    var base64Encoded = utf8ToBase64(utf8String);
    console.log(base64Encoded); // 输出: SGVsbG8=
    
    var base64Decoded = base64ToUtf8(base64Encoded);
    console.log(base64Decoded); // 输出: Hello

格式转化

  • ArrayBuffer → blob

    const blob = new Blob([new Uint8Array(buffer, byteOffset, length)]);
  • ArrayBuffer → base64

    const base64 = btoa(String.fromCharCode.apply(null, new Uint8Array(arrayBuffer)));
  • base64 → blob

    const base64toBlob = (base64Data, contentType, sliceSize) => {
      const byteCharacters = atob(base64Data);
      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;
    }
  • blob → ArrayBuffer

    function blobToArrayBuffer(blob) { 
      return new Promise((resolve, reject) => {
          const reader = new FileReader();
          reader.onload = () => resolve(reader.result);
          reader.onerror = () => reject;
          reader.readAsArrayBuffer(blob);
      });
    }
  • blob → base64

    function blobToBase64(blob) {
      return new Promise((resolve) => {
        const reader = new FileReader();
        reader.onloadend = () => resolve(reader.result);
        reader.readAsDataURL(blob);
      });
    }
  • blob → Object URL

    const objectUrl = URL.createObjectURL(blob);

  目录