likes
comments
collection
share

前端二进制和文件那些事看这篇就够了!

作者站长头像
站长
· 阅读数 18

一、思考

  • 图片的src为什么有时候是 ”https://xxx...“,有时候是 ”data: image/xxx; base64, xxx...“,有时候又是 ”blob:https://xxx“ ?有什么区别?

  • 处理文件时候经常会跟blob和FileReader打交道,它们到底是什么?有什么作用?

  • 前端能否将文件先请求后缓存到本地,等用户想下载时点击按钮快速下载?

二、前置知识

在了解前端二进制相关知识前,先简单了解几个文件相关概念 File、FileList、FileReader。

File对象

File 是一个文件对象,用来存储文件的内容。(它基于Blob接口,这里先不用了解Blob是什么,后文会详细展开)

FileList

FileList 是一组文件对象,通常来自于HTML input 元素的 files 属性,简单说就是 [ File, File, File, ... ] 。

FileReader

FileReader 对象允许 Web 应用程序异步读取 File 或 Blob数据,以其他形式输出。

了解前置知识后,进入正文

三、前端二进制由来

随着浏览器的流行,人们会希望它可以运行复杂的3D应用程序,于是诞生了一种 javaScript APIWebGL,然而早期版本的 WebGL 会因为js的数组在内存中的格式与原生数组之间不匹配,出现性能问题。为了解决这个问题,后来出现了 TypedArray(定型数组),让js运行时使用它可以分配、读取和写入数组,这个数组可以直接传给底层图形驱动程序 API,也可以直接从底层获取到。而这些定型数组引用的基本单位就是 ArrayBuffer(预分配内存)

四、ArrayBuffer

介绍

ArrayBuffer 对象用来表示通用的、固定长度的原始二进制数据缓冲区。数据在内存中是以二进制形式存放的,所以简单说ArrayBuffer就是一片内存

创建方式

1.构造函数生成

const buf = new ArrayBuffer(16); // 在内存中分配 16 字节

这个 buf 对象所储存的就是我们在内存中分配给它的大小为16字节的内存,不过我们没法对它进行直接操作,必须通过视图 TypedArrayDataView 去操作它,这里就不详细展开。

2.fetch把请求的响应数据转化为ArrayBuffer

request(item,index){
    fetch('/file',{
        method: 'get',
        responseType: 'arraybuffer'
    }).then(res => {     
        return res.arrayBuffer(); // 响应内容转为ArrayBuffer
    }).then(arraybuffer => {
		//...
    })
}   

3.FileReader.readAsArrayBuffer()将文件读取为ArrayBuffer

const reader = new FileReader();

reader.onload = function(e) {
  const arrayBuffer = reader.result;
}

reader.readAsArrayBuffer(file); // 文件读取结果返回ArrayBuffer

用法

当我们拥有一片内存空间后怎么使用呢?这时候就涉及到 blob(类文件对象)。

五、Blob

介绍

Blob 对象表示一个不可变、原始数据的类文件对象。简单说,Blob构造函数能把 ArrayBuffer对象储存的内存空间包装成一个新的对象,如下:

const buf = new ArrayBuffer(16);
const blob = new Blob([buf]);

这个blob对象就可以进行本地读取文件、文件下载等操作。同时,Blob构造函数还能把DOMString对象也做同样的包装,如下:

const fileParts = ['<div>DOMString</div>']; // 一个包含 DOMString 的数组
const blob = new Blob(fileParts, {type : 'text/html'}); // 得到 blob

当我们拥有一个类文件对象后有什么用?一般用法见下文

用法

本地读取文件

最常用的方式是转化为链接后本地访问,将blob转发为可访问的链接地址,在浏览器中访问

1.FileReader.readAsDataURL()将blob转为Data URL

const fileParts = ['<div>DOMString</div>']; // 一个包含 DOMString 的数组
const blob = new Blob(fileParts, {type : 'text/html'}); // 得到 blob

const reader = new FileReader();
reader.onload = function(e) {
  const dataUrl= reader.result;
  console.log(dataUrl)// 输出 'data:text/html;base64,PGRpdj5ET01TdHJpbmc8L2Rpdj4='
}
reader.readAsDataURL(blob); // 将blob对象转为Data URL

这个 Data URLdata: 协议 + MIME 类型 + blob转为base64的编码 组成,我们可以访问下这个链接,如下:

前端二进制和文件那些事看这篇就够了!

2.URL.createObjectURL()将blob转为Object URL

const fileParts = ['<div>DOMString</div>']; // 一个包含 DOMString 的数组
const blob = new Blob(fileParts, {type : 'text/html'}); // 得到 blob

const objectURL =  URL.createObjectURL(blob)
console.log(objectURL) // 输出 'blob:null/890d9407-829b-4333-97c5-fb05d5938dbf'

这个 Object URL格式为 blob: + origin + uuid,同样的,我们可以访问下这个链接,如下:

前端二进制和文件那些事看这篇就够了!

不过 Object URL相比Data URL来说,它是一个临时的链接,当我们把当前文档关掉或者使用URL.revokeObjectURL()清除url对blob的映射, Object URL就会访问不了,会从浏览器中收到 404 错误,如下:

前端二进制和文件那些事看这篇就够了!

文件下载

Data URL 和 Object URL 都可以利用a标签,将本地访问链接对应的文件内容下载到本地

// 创建类文件对象
const fileParts = ['<div>DOMString</div>']; 
const blob = new Blob(fileParts, {type : 'text/html'}); 

// 转化为本地访问链接
const objectURL =  URL.createObjectURL(blob)

// 使用a标签将文件下载到本地
const a = document.createElement('a');
a.style.display = 'none';
a.href = objectURL ;
a.download = name;
document.body.appendChild(a);
a.click();

// 触发下载行为后,记得清除a标签和Object URL的映射,释放内存
document.body.removeChild(a);
URL.revokeObjectURL(objectURL); 

如图:

前端二进制和文件那些事看这篇就够了!

下载后打开文件看看,如下:

前端二进制和文件那些事看这篇就够了!

六、总结

不管是从本地读取下载文件,还是从远程请求获取文件,前端都可以通过 ArrayBuffer、Blob、File、FileReader 这些对象之间的灵活转换和处理,让文件以二进制数据流的形式保存在内存中,之后面对不同场景做不同的文件操作。

前端二进制和文件那些事看这篇就够了!