前端对于文件流的下载

3,267 阅读5分钟

废话

相信做过一些管理系统的小伙伴大部分都会遇到这样一个需求,那就是导出一些像excel、word、pdf这样的文件,之前有写过React下载后端返回文件流形式的Excel文件(blob)这样一篇文章,但是感觉只是让它跑起来了,没有深究其原理,这次就想肩抗华伦天奴,手撕巴黎世家,揭开维多利亚的秘密,探索挪威的森林

Stream概念

想象一下,你编写了一个文件,你可以将它保存在硬盘上,也可以将它保存到U盘上,这样简单的操作,其实就是数据的传输。
数据的传输,也就是数据的流动,既然出现了这个流动,那就会有出方向和入方向,就像大部分河流一样,在某个地方冒出了涓涓细流,到最后流入大海(这属于地理知识我们在这里不做过多讨论...),在很多时候,流(Stream)我们代指字节流(Byte Steram),也就是长长的一串字节,除了字节流,我们还可能有视频流、音频流、数据流等等......

流按照处理数据的单位,可以分为字节流和字符流。字节流的处理单位是字节,通常用来处理二进制文件,例如音乐、图片文件等。而字符流的处理单位是字符,因为Java采用Unicode编码,Java字符流处理的即为Unicode字符,所以在操作汉字、国际化等方面,字符流具有优势。流的概念有点抽象,我们不做过多的探讨,感兴趣的小伙伴可以自行学习

MIME概念

媒体类型(通常称为 Multipurpose Internet Mail Extensions 或 MIME 类型 )是一种标准,用来表示文档、文件或字节流的性质和格式。它在IETF RFC 6838中进行了定义和标准化。
互联网号码分配机构(IANA)是负责跟踪所有官方MIME类型的官方机构,您可以在媒体类型页面中找到最新的完整列表。

MIME它的全名叫多用途互联网邮件扩展(Multipurpose Internet Mail Extensions),最初是为了将纯文本格式的电子邮件扩展到可以支持多种信息格式而定制的。后来被应用到多种协议里,包括我们常用的HTTP协议。
MIME的常见形式是一个主类型加一个子类型,用斜线分隔。比如text/htmlapplication/javascriptimage/png等。

由于一些历史问题,百度百科上面关于MIME的介绍并不是完全正确,可以看看参考链接里的知乎那个了解一下  
我们不能教李彦宏怎么做百度,不能教张小龙怎么做微信,也不能教库克怎么做iPhone

Blob

概念-什么是Blob

Blob(Binary Large Object)对象表示一个不可变、原始数据的类文件对象。它的数据可以按文本或二进制的格式进行读取,也可以转换成 ReadableStream 来用于数据操作。
Blob 表示的不一定是JavaScript原生格式的数据。File 接口基于Blob,继承了 blob 的功能并将其扩展使其支持用户系统上的文件。

MDN官方的定义来看,我们大概可以知道这个Blob是用来干啥的了,它是一个用来读取或操作二进制数据的玩意,这我们就可以来下载后端给我们的文件流了啊

用法

构造函数

var Blob = new Blob( array, options )
  • array 是一个由ArrayBuffer(二进制数据缓冲区)、ArrayBufferView(二进制数据缓冲区的array-like视图)、Blob、DOMString等对象构成的Array,或者其他类似对象的混合体,它将会被放进Blob。DOMStrings会被编码为UTF-8。

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

  • type,默认值为 "",它代表了将会被放入到blob中的数组内容的MIME类型。

    • endings,默认值为"transparent",用于指定包含行结束符n的字符串如何被写入。 它是以下两个值中的一个:
    • "native",代表行结束符会被更改为适合宿主操作系统文件系统的换行符,或者 "transparent",代表会保持blob中保存的结束符不变。 🌰 :
var say = {hello: "world"}
var blob = new Blob([JSON.stringify(say, null, 2)],{type : 'application/json'})

URl对象

我们想要下载这个文件需要通过创建URL对象指定文件的下载链接

创建构造函数

创建新的URL表示指定的File对象或者Blob对象。

objectURL = window.URL.createObjectURL(blob)

释放对象

这个方法不会像JavaScript的垃圾回收机制那样帮你处理一些事情,在每次调用createObjectURL方法时,都会创建一个新的 URL 对象,即使你已经用相同的对象作为参数创建过。当不再需要这些 URL 对象时,每个对象必须通过调用 URL.revokeObjectURL方法来释放。浏览器会在文档退出的时候自动释放它们,但是为了获得最佳性能和内存使用状况,你应该在安全的时机主动释放掉它们。

window.URL.revokeObjectURL(objectURL)

使用a标签下载

生成一个a标签

const link = document.createElement('a')

指定下载链接

link.href = window.URL.createObjectURL(blob)

设置文件名

link.download = fileName

触发事件

link.click()

下载

    const foo = {
      hello: "world"
    };
    const blob = new Blob([JSON.stringify(foo)], {
      type: 'application/vnd.ms-excel;charset=utf-8'
    });
    const fileName = `${new Date().valueOf()}.xls`;
    const link = document.createElement('a');
    link.href = window.URL.createObjectURL(blob);
    link.download = fileName;
    link.click();
    window.URL.revokeObjectURL(link.href);

兼容IE

在IE中要使用window.navigator.msSaveOrOpenBlob来处理Blob对象。

window.navigator.msSaveOrOpenBlob(blob, fileName);

整体代码

/*res是请求后返回的文件流,name是传入的文件名*/
export const downloadExcel = (res,name) => {
  const blob = new Blob([res],{type: 'application/vnd.ms-excel'})
  const fileName = `${name}.xlsx`
  if ('download' in document.createElement('a')) {
    const elink = document.createElement('a')
    elink.download = fileName
    elink.style.display = 'none'
    elink.href = URL.createObjectURL(blob)
    document.body.appendChild(elink)
    elink.click()
    URL.revokeObjectURL(elink.href)
    document.body.removeChild(elink)
  } else {
    navigator.msSaveBlob(blob, fileName)
  }
}

参考链接:
MDN-Blob
W3C-Blob
MIME 类型
现代 JavaScript 教程 -Blob
常见 MIME 类型列表
知乎-既然有文件后缀名,为何还需要MIME类型?
前端利用Blob对象创建指定文件并下载