在 web 应用中对文件的处理是非常常见的功能,比如上传,下载,传输等。
Input file 标签
<input type="file" id="file" placeholder="please upload file" />
const fileEl = document.getElementById('file');
const file = fileEl.files[0] // 返回一个 File 对象
File 属性
- name : 名称
- size : 大小 (字节数)
- type : mime 类型
- lastModified : 时间戳,最近修改时间
其实只有 name ,lastModified 是只属于 File ,其他的属性和方法都是继承 Blob
上传文件
const formData = new FormData();
const file = fileInput.files[0]
formData.append('name', file.name);
formData.append('file', file);
// 设置 Content-Type : 'multipart/form-data'
axios.post(`xxxx`, formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
})
在上传文件到服务器时,需要通过借助 FormData
对象,来传输二进制对象。 同时需要设置 Content-Type : 'multipart/form-data'
那后台接收到 Content-Type : 'multipart/form-data'
类型的请求是如何处理的? 如何区分一般属性 name
和 文件对象 file
我们可以通过 chrome network
找到这个请求数据, 如下
// request header : content-type
"content-type": "multipart/form-data; boundary=----WebKitFormBoundarytmx9WCwEKpK3dqOd",
// request payload
------WebKitFormBoundarytmx9WCwEKpK3dqOd
Content-Disposition: form-data; name="name"
browser-EventLoop.png
------WebKitFormBoundarytmx9WCwEKpK3dqOd
Content-Disposition: form-data; name="file"; filename="xxx.png"
Content-Type: image/png
------WebKitFormBoundarytmx9WCwEKpK3dqOd--
后台在收到请求的请求体数据时,( nodejs 可以通过 data
事件来接收数据 ) 收到的是二进制数据流,然后反序列化,在根据 content-type
进一步分析。然后再根据请求体中的 Content-Disposition
字段分析每个字段的类型。得到 name
是一个普通字符串,file
是一个文件对象。
File -> Blob
Blob
对象表示一个不可变、原始数据的类文件对象。 通常一般使用 Blob
对象来处理文件对象
new Blob(blobParts, options)
构造函数blobParts
:ArrayBuffer、TypedArray、DataView、Blob、字符串
任意组合options-type
: mime 类型,默认是空
const bb = new Blob(['hello world'],{ 'type': 'text/plain' }) bb.size // 11
- 属性:
- size: 大小 (字节数)
- type:mime 类型
- 方法:
arrayBuffer()
: 返回Promise<ArrarBuffer>
bytes()
: 返回Promise<Uint8Array>
text()
: 返回Promise<String>
stream()
: 返回ReadableStream
slice()
: 类似数组切割,返回一个blob
Blob
是 Binary Large Object
的简称,是用于存储超大的二进制对象,是对 ArrayBuffer
对象的一个补充。
我们知道 V8 在执行 javascript 代码时,对在堆内存中实例化对象,并存储,执行相应逻辑。 但是在遇到 Blob, ArrayBuffer
这种大量二进制数据,一般会借助外部内存,比如浏览器内存, 将 Blob
存储在浏览器内存中,不占用 V8 的堆内存。
chrome 提供了 chrome://blob-internals/
来查看当前应用的 Blob
对象
// 在 chrome 控制台执行 new Blob 操作
const bb = new Blob(['hello world'],{ 'type': 'text/plain' })
// 可以在 chrome://blob-internals/ 看到新增了一个 length 11 的 blob 数据
fa5c6af4-7f5e-48bc-843c-4995822a818b
Refcount: 1
Status: BlobStatus::DONE: Blob built with no errors.
Content Type: text/plain
Type: data
Length: 11
// 通过 input 标签选择一个文件时
<input type="file" id="file" placeholder="please upload file" />
// 可以看到新增一个 图片的 Blob 数据,这里 Path 对应的磁盘上的一个文件
5c087d92-9c88-4924-b7bd-d036f8cb36d7
Refcount: 1
Status: BlobStatus::DONE: Blob built with no errors.
Content Type: image/png
Type: file
Path: /Users/xxxxx/Desktop/project/imgs/xxxx.png
Modification Time: 19:03:40
其他相关转换成 blob 的 api
canvas.toBlob
: 将 canvas 画布数据转换成blob
对象数据用于传输。response.blob()
: fetch api 中可以返回blob
数据
fetch('xxx')
.then(response => response.blob())
.then(blob => {
});
和 arrayBuffer
相互转换
blob.arrayBuffer
: blob -> arrayBuffernew Blob()
: arrayBuffer -> blob , 需要借助 TypedArray
var ar = new ArrayBuffer(1);
var uar = new Uint8Array(ar);
uar[0] = 65;
var bb = new Blob([uar], {type: 'text/plain'});
bb.text().then((r) => console.log(r)); // 'A'
File -> FileReader
FileReader
是一个读取文件的api , 但是只能仅仅读取 input
和 拖拽的文件,不能通过制定路径来读取文件。
用法:
<input type="file" id="file" onchange="previewFile()" />
const previewFile = () => {
const fileInput = document.getElementById('file');
const file = fileInput.files[0];
const reader = new FileReader();
reader.onload = (res) => {
console.log(reader.result);
// 如果选择的是纯文本文件,就会直接输出其内容
};
reader.readAsText(file);
};
readAsArrayBuffer(blob)
:reader.result
是一个ArrayBuffer
readAsDataURL(blob)
:reader.result
是一个base64编码的data: URL
,类似data:image/jpeg;base64,/xxxx
readAsText(blob)
:reader.result
是一个文本字符串
上面的 Blob
的也有类似的api arrayBuffer(), text()
, 返回的是一个 Promise
。 FileReader
则是采用事件驱动的形式, 通过不同的 api 读取 Blob
或者File
对象,其 result
返回不同的类型数据。
事件类型
load
: 成功读取文件error
: 读取失败loadend
: 读取完成文件,不论成功失败loadstart
: 读取开始progress
: 读取过程中触发,可以计算百分比,(不是固定字节数,固定百分比触发)
使用场景: 一般用于读取大文件显示进度。 或者事件驱动类型读取文件内容。 其他场景,可以直接使用 Blob
的 api 转换成 字符串或者 ArrayBuffer
。 还有一个同步Api接口 FileReaderSync
File -> createObjectURL
createObjectURL
可以将 blob
对象转成一个 Blob Url
, 格式 blob:<origin>/<uuid>
。 有些类似 DATA URL
// 预览图片
const previewFile = () => {
const fileInput = document.getElementById('file');
const file = fileInput.files[0];
const img = document.createElement('img');
img.id = 'img1';
img.src = URL.createObjectURL(file);
// 这里返回的是一个 blob url
// blob:http://localhost:3000/d3a200c5-0ce9-4593-9185-60f8a1af1083
img.onload = () => {
URL.revokeObjectURL(img.src);
};
const files = document.getElementById('files');
files.append(img);
};
上面例子,实现了一个预览图片的功能,为什么防止内存泄露,这里使用 revokeObjectURL
释放了 Blob Url
。 如果不释放可以在浏览器直接输入这个 Url
,返回的就是图片内容。
如何获取图片资源,比如 图片的 blob 数据,或者 arraybuffer 数据,或者 纯文本字符串内容 ???
// 没有 URL.revokeObjectURL 释放 blob url
const img1 = document.getElementById('img1')
fetch(img1.src).then(res=>res.arrayBuffer()).then(console.log) // 输出 arrayBuffer
fetch(img1.src).then(res=>res.blob()).then(console.log) // 输出 blob
fetch(text.src).then(res=>res.text()).then(console.log) // 如果是纯文本,如.txt .md 输出文本字符串
// 使用 URL.revokeObjectURL 释放 blob url
const canvas = document.createElement('canvas')
canvas.width = img.width;
canvas.height = img.height;
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0, img.width, img.height);
canvas.toBlob(console.log) // 输出 blob
canvas.toDataURL(console.log) // 输出 data url , base64 编码
代码片段
<input type="file" id="file" onchange="previewFile()" />
<div id="files"></div>
const previewFile = () => {
const fileInput = document.getElementById('file');
const file = fileInput.files[0];
// showPdf(file);
// showText(file);
showImg(file);
};
const showImg = (file) => {
const img = document.createElement('img');
img.id = 'img1';
img.src = URL.createObjectURL(file);
img.onload = () => {
URL.revokeObjectURL(img.src);
};
const files = document.getElementById('files');
files.append(img);
};
const showText = (file) => {
const reader = new FileReader();
reader.onload = (res) => {
const files = document.getElementById('files');
files.innerText = reader.result;
};
reader.readAsText(file);
};
const showPdf = (blob) => {
const blobUrl = URL.createObjectURL(blob);
const iframe = document.createElement('iframe');
iframe.setAttribute('src', blobUrl);
const files = document.getElementById('files');
files.append(iframe);
URL.revokeObjectURL(blobUrl);
};