前端文件操作
浏览器由于安全性要求,不允许JS直接获取主机的文件系统上的文件,只能通过HTML标签input[type=file]来由用户选择可以被浏览器操作的文件。
那么input[type=file]标签选择的文件,会被如何保存呢?
<!DOCTYPE html>
<html lang="en">
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<input type="file"/>
</body>
</html>
选择一个文件
在控制台输入
发现打印的DOM元素对象input中有一个特殊的属性files
files属性的类型是FileList,从名字就可以看出它是一个类数组,类数组元素是File类型对象
那么如果input[type=file]标签选择多个文件(标签加一个multiple属性),它的files中应该会包含多个File对象元素
而从打印结果可以看出File类型对象就是我们选择的文件
File是啥
File - Web API 接口参考 | MDN (mozilla.org)
MDN关于File的介绍:
文件(
File
)接口提供有关文件的信息,并允许网页中的 JavaScript 访问其内容。
如何产生File对象:
通常情况下,
File
对象是来自用户在一个
<input>
元素上选择文件后返回的
FileList
对象,也可以是来自由拖放操作生成的
DataTransfer
对象,或者来自
HTMLCanvasElement
上的
mozGetAsFile
() API。
File对象的属性
lastModified
|
只读。返回当前
File
对象所引用文件最后修改时间的毫秒数。
|
lastModifiedDate
|
只读。返回当前
File
对象所引用文件最后修改时间的
Date
对象。(废弃)
|
name
|
只读。返回当前
File
对象所引用文件的名字。
|
size
|
只读。返回当前
File
对象所引用文件的大小。
|
type
|
只读。返回当前
File
对象所引用文件的MIME类型
|
webkitRelativePath
|
只读。返回
File
相关的 path 或 URL。(不推荐)
|
其中
lastModifiedDate,webkitRelativePath
不推荐使用
剩下的属性也比较好理解,都是文件常见的信息,比如文件名,文件类型,文件大小,文件最后修改时间,需要注意这些File对象属性都是只读的。
File对象的方法
File对象没有定义任何方法,它的方法都继承自Blob。
原型链如下
可以发现File实例对象可以沿着原型链访问到Blob.prototype上的方法。
Blob是什么
Blob - Web API 接口参考 | MDN (mozilla.org)
MDN对于Blob介绍是:
Blob
对象表示一个不可变、原始数据的
类文件对象
。
啥叫类文件对象?
Blob翻译过来是 Binary Larger Object,即二进制大对象。即Blob对象的组成是二进制数据。
而我们知道文件也是二进制编码的数据。
那么为什么Blob叫类文件呢,而不是文件呢?
其实Blob在这里代表的是比文件更加底层的概念。
我们知道文件是由二进制数据组成的,那么我们随意编写一些二进制数据,它能代表一个文件吗?答案是不能。文件的二进制数据具有各种特殊标识,来帮助外部理解一堆二进制数据是一个文件。
Blob代表的其实就是文件的切片。
我们知道文件的二进制数据组合在一起才能代表文件,而文件的二进制数据被分段后,比如分成十段,那么每一段都无法表示文件。但是每一段又都是文件的必不可少的组成部分。
此时就需要Blob来表示这些文件切片。所以说Blob是一个类文件对象。它不是文件,但是却和文件有千丝万缕的关系。
在浏览器API中,Blob是File的父类,这很好理解,父类是基类,子类是扩展类,我们可以说文件File是一堆二进制数据,是一个特殊的Blob,它可以当成Blob。但是它又比Blob多了一层含义。
Blob对象的属性:
size
|
只读。
Blob
对象中所包含数据的大小(字节)。
|
type
|
只读。一个字符串,表明该
Blob
对象所包含数据的 MIME 类型。如果类型未知,则该值为空字符串。
|
有人说Blob对象咋还有文件才有的MIME类型呢?它不就是一个二进制数据对象吗?
还是要再说明一下,Blob对象相当于File对象的切片,如何标识一个文件切片属于什么文件类型是有必要的,它涉及都二进制数据的解析。
Blob对象的方法:
Blob构造函数
Blob(blobParts[, options])
返回一个新创建的
Blob
对象,其内容由参数中给定的数组串联组成。
其中blobParts参数是一个数组,数组中的每一项元素连接起来构成Blob对象的数据,数组中的每项元素可以是
ArrayBuffer
,
ArrayBufferView
,
Blob
,
DOMString
。
options:可选项,字典格式类型,可以指定如下两个属性:
-
type,默认值为
""
,它代表了将会被放入到blob中的数组内容的MIME类型。
-
endings,默认值为"transparent",用于指定包含行结束符
\n
的字符串如何被写入。 它是以下两个值中的一个: "native",表示行结束符会被更改为适合宿主操作系统文件系统的换行符; "transparent",表示会保持blob中保存的结束符不变。
File,Blob的slice方法
那么现在了解Blob,而File继承了Blob的所有方法,且进行了拓展,可以直接操作文件,比如文件分片slice
从打印结果可以看出,file经过slice得到文件切片file_cp,而file_cp就不再是File类型了,而是Blob类型。
File,Blob的text方法
file.text()返回一个promise,该promise对象的结果是 blob所有内容的UTF-8格式的
USVString
。
即file.text()将二进制编码的数据 转成了 utf-8编码的文本字符串。
不知道意义何在.....,就像你用Notepad++打开一张png格式的图片一样。
File,Blob的arrayBuffer方法
file.arrayBuffer()
file.arrayBuffer()返回一个promise对象,其结果是 blob所有内容的二进制格式的
ArrayBuffer
ArrayBuffer是啥
那么啥是ArrayBuffer呢?
ArrayBuffer - JavaScript | MDN (mozilla.org)
ArrayBuffer
对象用来表示通用的、固定长度的原始二进制数据缓冲区。
它是一个字节数组,通常在其他语言中称为“byte array”。
你不能直接操作
ArrayBuffer
的内容,而是要通过 TypeArray 或
DataView
对象来操作,它们会将缓冲区中的数据表示为特定的格式,并通过这些格式来读写缓冲区的内容。
为什么ArrayBuffer中的数据无法直接操作呢?
因为ArrayBuffer数组的元素都是字节,即八位二进制数,且是二进制编码的,我们无法将ArrayBuffer字节数组的字节元素取出来,也无法覆盖字节元素。因为我们输入的都是字符,而不是字节。
而我们知道八位二进制数 可以转成 其他类型数据,最常见就是十进制数值,而我们是可以输入十进制数值的,所以可以将ArrayBuffer字节数组转为TypeArray类型数组,常见的TypeArray数组有:
由于ArrayBuffer都是字节元素,即每个元素都是无符号8位2进制,所以我们通常是将ArrayBuffer转为Unit8Array来进行处理
此时我们就可以对字节数组转成的Uint8Array数组进行元素操作了
对Uint8Array修改完后,再将其转为ArrayBuffer,则很简单,Uint8Array对象上就有一个buffer属性,可以获取到之前的ArrayBuffer实例
另外TypeArray没有新增,删除元素的操作,因为TypeArray的数组长度是固定的,无法改变的。
所以这么一看ArrayBuffer除了可以转为TypeArray修改字节元素外,也没啥,关键谁没事改字节数啊,文件的字节被改了,可能都无法解析了。
File,Blob的stream方法
file.stream()
返回一个能读取blob内容的
ReadableStream
。
那么什么是ReadableStream呢?
ReadableStream - Web API 接口参考 | MDN (mozilla.org)
流操作API
中的
ReadableStream
接口呈现了一个可读取的二进制流操作。
啥叫二进制流?
我们知道程序上的各种数据在计算机底层上都是二进制形式的,而程序中可识别数据互相传递,就意味着二进制形式数据的传输,即一串010101在各个程序中传输,而这些传输都是有方向性,就像水流一样,总是朝着一个方向流动,不可能出现一股水流出现两个方向。所以二进制数据的传输也是如此,所以二进制数据传输,也叫做流。
那么流的方向有哪些呢?
一个程序需要外部传入数据,那就底层的二进制数据就是朝着该程序输入,也叫输入流。
一个程序向外部提供数据,那么底层二进制数据就是朝程序外输出,也叫输出流。
具体判断是输入流,还是输出流的要点是,数据源是谁,如果从数据源读取数据,那么相当于数据源对外输出,就是输出流,如果向数据源写入数据,那么相当于输入流。
所以一个文件的输出流一般和读操作相关,输入流和写操作相关。
另外有一个关于流的概念,那就是流的组成。
我们知道流是指二进制数据的流动,那么流的组成就是二进制数据。但是二进制数据有不同的形式:
比如 0000000000001111111111111100000000000000,和 00000000 00001111 11111111 11000000 00000000
二者二进制数都相同,都表示40位的二进制数,但是第二个按照8位一组,进行了分组。
我们知道 8 bit = 1 Byte,即8位二进制数 相当于一个字节。
所以在传输中的流有了两个概念,比特(bit)流,和字节(Byte)流,字节流就是将比特流按照8位一组进行分组后的二进制数据。
而在字节之上就是字符了,字符指的就是人类可以识别的文字,而字节是计算机可以识别的文字。
人们制定了各种编码方式,将字节组合起来代表一个人类世界的字符。
比较有名的编码有:ASCII码(英语系编码),GBK码(汉语系编码),UTF-8码(世界所有语言编码)
由于英语只有26字母以及一些常用的英语符号,所以ASCII码只需要一个字节(相当于8位二进制数,可以有255种对应关系)就可以代表所有英语系常用字符。
而汉语有成千上万的文字,即使按照偏旁部首进行分类,也有很多,所以GBK字符需要两个字节(相当于16位二进制数,可以有 2^16 - 1 = 65535种对应关系)
而想要表示世界上所有语言,UTF-8的字符需要三个字节(相当于24位二进制数,即2^24-1种对应关系。)
而无论操作系统,还是浏览器,还是程序编写工具,还是记事本,它们都是直接面向人类的,所以他们需要将字节 按照对应编码规则 转化为 字符。
所以字节流 在进行分组,即按照编码规则分组,比如UTF-8编码,将三个字节分为一组,形成字符流。
而无论是比特流,还是字节流,还是字符流,它们都有流向,即可以分为输入流和输出流。
如果是基于文件的读取和写入的话,那么也可以叫做 读取流(输出流) 和 写入流(输入流)。
ReadableStream
是啥
那么
ReadableStream
接口呈现了一个可读取的二进制流操作。
其实就是说 以字节流的方式读取文件中二进制数据 的 操作
ReadableStream的属性
locked
|
只读。locked 返回这个可读流是否被一个
读取器锁定
|
ReadableStream对象的方法
cancel
|
取消读取流,读取方发出一个信号,表示对这束流失去兴趣。可以传入 reason 参数表示取消原因,这个原因将传回给调用方。
|
getIterator
|
创建一个异步的 ReadableStream 迭代器并将流锁定于其上。一旦流被锁定,其他读取器将不能读取它,直到它被释放。
|
getReader
|
创建一个读取器并将流锁定于其上。一旦流被锁定,其他读取器将不能读取它,直到它被释放。
|
pipeThrough
|
提供将当前流管道输出到一个 transform 流或 writable/readable 流对的链式方法。
|
pipeTo
|
将当前 ReadableStream 管道输出到给定的
WritableStream (en-US)
,并返回一个 promise,输出过程成功时返回 fulfilled,在发生错误时返回 rejected。
|
tee
|
tee
方法(tee本意是将高尔夫球放置在球座上)
tees
了可读流,返回包含两个
ReadableStream
实例分支的数组,每个元素接收了相同的传输数据。
|
看了一下ReadableStream的各种方法,水很深,暂时搁置一下,后面有时间再分析把。
File,Blob,ArrayBuffer小结
以上,我们了解了File,Blob,ArrayBuffer的一些基本概念和功能。
我们知道了File,Blob,ArrayBuffer都是二进制数据对象,
其中File是文件级别二进制对象,Blob和ArrayBuffer都是普通二进制对象
但是Blob相当于File的切片,即文件切片,是类文件二进制对象。File也可以看成一种特殊的Blob。
而Blob只有四个方法 slice,text,arrayBuffer,stream,File也继承了这四个方法,可以对文件对象进行操作。
但是Blob提供的操作都是宏观的,而ArrayBuffer可以提供更加微观的,基于字节的操作。
ArrayBuffer是一个字节数组,数组元素就是字节,相当于八位二进制数,ArrayBuffer本身无法直接对数组元素进行操作,需要转成TypeArray后才能进行字节元素的修改,另外需要注意的是TypeArray一旦初始化就无法改变自身字节数,即ArrayBuffer虽然可以间接的修改数组中的字节元素,但是无法增加和删除字节元素。
File,Blob,ArrayBuffer转换
三者之间有如下转换关系
文件的BASE64编码
当我们通过input[type=file].files[0]获取到用户选择的文件File对象时,前端一般需要将该File对象上传到服务器,我们知道form表单(enctype=multipart/form-data)可以上传File对象,XMLHttpRequest(Content-Type=multipart/form-data;boundary=随机值)也可以上传File对象,这两种方式都是将文件以二进制数据进行传输。那么有没有办法将文件以文本进行传输呢?
我们知道文件除了二进制编码外,还可以base64编码
BASE64是一种编码方式,通常用于把二进制数据编码为可写的字符形式的数据。这是一种可逆的编码方式。
那么如何实现将文件从二进制编码转为BASE64编码呢?这就要靠FileReader了
FileReader
FileReader - Web API 接口参考 | MDN (mozilla.org)
FileReader
对象允许Web应用程序
异步读取
存储在用户计算机上的文件(或原始数据缓冲区)的内容,使用
File
或
Blob
对象指定要读取的文件或数据。
FileReader对象可以读取File或Blob中的二进制数据,考虑到文件可能很大,所以读取操作是异步的。
FileReader对象有一个属性readyState,有三个取值
当FileReader对象读取二进制数据完成后,就会将读取的内容放在自身的result属性上。注意result属性无法同步获取,只能异步获取。
浏览器会监听FileReader对象的readyState属性值的变化,当发生变化时就会触发FileReader对象身上的事件监听处理程序
onloadstart
|
该事件在读取操作开始时触发
|
onprogress
|
该事件在读取
Blob
时触发
|
onload
|
该事件在读取操作完成时触发
|
onloadend
|
该事件在读取操作结束时(要么成功,要么失败)触发
|
另外FileReader对象还考虑了读取过程发生异常时,
另外FileReader对象还可以终止读取,此时FileReader对象可以调用abort()方法,该方法调用后就会触发自身的onabort事件
onabort
|
当FileReader对象调用了abort()方法后触发
|
以上就是FileReader异步处理过程。
下面是FileReader读取二进制数据的操作,FileReader对象可以将读取的二进制数据转化为其他类型的数据,这里有三个方法
readAsArrayBuffer
|
开始读取指定的
Blob
中的内容, 一旦完成, result 属性中保存的将是被读取文件的
ArrayBuffer
数据对象.
|
readAsDataURL
|
开始读取指定的
Blob
中的内容。一旦完成,
result
属性中将包含一个
data:
URL格式的Base64字符串以表示所读取文件的内容。
|
readAsText
|
开始读取指定的
Blob
中的内容。一旦完成,
result
属性中将包含一个字符串以表示所读取的文件内容。
|
以上是准备工作,通过onloadend监听读取的异步结果result
读取file文件的二进制数据,并将读取内容转化为ArrayBuffer字节数组
读取file文件的二进制数据,并将读取内容转化为文本字符串,采用默认的UTF8编码
读取file文件的二进制数据,并将读取内容转化为BASE64编码的字符串
BASE64编码实现了文件可以基于文本字符串来传输。
前端文件操作浏览器由于安全性要求,不允许JS直接获取主机的文件系统上的文件,只能通过HTML标签input[type=file]来由用户选择可以被浏览器操作的文件。那么input[type=file]标签选择的文件,会被如何保存呢?<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible"
Blob
,Binary Large Object的缩写,二进制
类型
的大对象,代表不可改变的原始数据
在计算机中,
BLOB
常常是数据库中用来存储二进制
文件
的字段
类型
。
Blob
基本用法
Blob
对象
Blob
对象指的是字节序列,并且具有size属性,是字节序列中的字节总数,和一个type属性,它是小写的ASCII编码的字符串表示的媒体
类型
字节序列。
size:以字节数返回字节序列的大小。获取时,符合要求的用户代理必须返回一个
FileReader
或一个
FileReader
Sync对象可以读取的总字节数,如果
Blob
没有要读取的字节,则返回0 。
type:小写的ASCII编码字符串
Stream 将
类型
化数组和
Array
Buffer
s转换为
Buffer
对象
var to
Buffer
Stream = require('to
buffer
-stream');
somewhere.pipe(to
Buffer
Stream)
使用进行转换,避免复制。
有用与使用 ,其中浏览器可以使用(unaugmented)发射数据Uint8
Array
(等),但增强对象
类型
是期望的。 另请参阅 、 。
麻省理工学院
这几天在开发过程中,遇到了下载
文件
问题,其中服务端返回二进制
文件
流,需要
前端
自己对二进制
文件
流进行转换,用到了new
Blob
()方式,便上网查阅相关资料。
2、
Blob
对象
2.1、概念介绍
Blob
全称:Binary Large Object(二进制大型对象)
Blob
对象是一个
前端
的一个专门用于支持
文件
操作
的二进制对象,表示一个二进制
文件
的数据内容。通常用来读写
文件
,比如一个图片
文件
的内容就可以通过
Blob
对象读写。
在JS中,有两个构造函数:
File
和
Blob
,而
File
继承了
npm install fetch-
blob
从 2x 升级到 3x 从 2 更新到 3 应该是轻而易举的,因为
blob
规范没有太多变化。 主要版本的主要原因是编码标准。 - 内部 WeakMaps 被私有字段替换 - 内部
Buffer
.from 被替换为 TextEncoder/Decoder - 内部缓冲区被替换为 Uint8
Array
s - CommonJS 被替换为 ESM - 通过调用
blob
.stream()返回的节点流被替换为 whatwg 流 - (阅读“与其他
blob
的区别”了解更多信息。) 与其他
Blob
的区别
不同的NodeJS
buffer
.
Blob
(添加于:v15.7.0)和浏览器原生斑点这polyfilled版本无法通过PostMessage的发送
这个
blob
版本
var oReq = new XMLHttpRequest();
oReq.open(POST, url, true);
oReq.onload = function (oEvent) {
// Uploaded.
var
blob
= new
Blob
(['abc123'], {type: 'text/plain'});
oReq.send(
blob
);
var my
Array
= new Arr
该库在与
Array
Buffer
s之间对base64进行编码和解码
encode(
buffer
) -将
Array
Buffer
编码为base64字符串
encode(str) -将base64字符串解码为
Array
Buffer
通过NPM安装:
npm install blueimp-canvas-to-
blob
这会将JavaScript
文件
相对于当前目录安装在./node_modules/blueimp-canvas-to-
blob
/js/ ,您可以从中将它们复制到Web服务器提供的
文件
夹中。
接下来,在HTML标记中包含缩小JavaScript Canvas to
Blob
脚本:
< script src =" js/canvas-to-
blob
.min.js " > </ script >
或者,包括非缩小版本:
< script src =" js/canvas-to-
blob
.js " > </ script >
您可以按照与
在Python中,
前端
通过
Blob
对象获取到的
文件
可以通过以下步骤转存为
文件
:
1. 首先,
前端
通过HTML的
File
API获取到的
Blob
对象,可以通过ajax传递给后端(Python)。
2. 在Python后端,我们需要使用Flask等框架来接收
前端
传递的
Blob
数据。
3. 在后端Python代码中,获取到的
Blob
数据一般被表示为字节流。我们可以使用Python的标准库中的`io`模块来进行
文件
操作
。
4. 首先,我们需要使用`io.BytesIO()`创建一个BytesIO对象,用于存储
Blob
数据。
```python
import io
from flask import Flask, request
app = Flask(__name__)
@app.route('/save_
blob
', methods=['POST'])
def save_
blob
():
blob
_data = request.data # 获取
前端
传递的
Blob
数据
# 将
Blob
数据存储到BytesIO对象中
file
_stream = io.BytesIO(
blob
_data)
# 打开一个
文件
用于保存
Blob
数据
with open('
blob
_
file
.txt', 'wb') as f:
f.write(
file
_stream.getvalue())
return '
文件
已保存'
if __name__ == '__main__':
app.run()
5. 当
前端
发起POST请求到`/save_
blob
`接口时,后端会将
前端
传递的
Blob
数据保存到`
blob
_
file
.txt`
文件
中。
需要注意的是,以上只是一个简单的例子,根据具体的业务需求,你可能需要进行更多的错误处理、
文件
名的处理等。
这样,
前端
通过
Blob
对象获取到的
文件
就可以转存为
文件
并在CSDN或其他平台中展示或使用了。