文章目录
- 学习链接
- Blob
- 创建
- 演示
- 分片
- 演示
- File
- input
- 手动拖拽
- fetch 从后端获取流
- 前端代码
- 后端代码
- window.showOpenFilePicker
- Filereader
- 示例1
- 示例2
- ArrayBuffer
- 创建buffer
- TypedArray读写buffer
- DataView读写buffer
- 与Blob对比
- Blob Url & DataUrl
- 示例1
- 示例2
学习链接
Blog & File B站视频
今天一次性给你讲清楚:File、Blob、FileReader、ArrayBuffer、Base64
web前端文件上传可选择的4种方式
URL.createObjectURL和URL.revokeObjectURL
Blob
Blob 全称为 binary large object
,即二进制大对象。blob对象本质上是js中的一个对象,里面可以储存大量的二进制编码格式的数据。Blob 对象一个不可修改,从Blob中读取内容的唯一方法是使用 FileReader。
创建
Blob() 构造函数返回一个新的 Blob 对象。blob 的内容由参数数组中给出的值的串联组成。
var aBlob = new Blob( array, options );
-
其有两个参数:
-
array:是一个由ArrayBuffer, ArrayBufferView, Blob, DOMString 等对象构成的 Array ,或者其他类似对象的混合体,它将会被放进 Blob。DOMStrings 会被编码为 UTF-8。
-
options 是一个可选的BlobPropertyBag字典,它可能会指定如下两个属性:
- type,默认值为 “”,它代表了将会被放入到 blob 中的数组内容的 MIME 类型。
- endings,默认值为"transparent",用于指定包含行结束符\n的字符串如何被写入。它是以下两个值中的一个:“native”,代表行结束符会被更改为适合宿主操作系统文件系统的换行符,或者 “transparent”,代表会保持 blob 中保存的结束符不变 非标准
-
演示
这个 blob 对象上有两个属性:
- size:Blob对象中所包含数据的大小(字节);
- type:字符串,认为该Blob对象所包含的 MIME 类型。如果类型未知,则为空字符串。
分片
Blob 对象内置了 slice() 方法用来将 blob 对象分片
(注意:Blob对象是不可直接修改的,所以分片返回的是一个新的Blob对象
)
-
其有三个参数:
-
start:设置切片的起点,即切片开始位置。默认值为 0,这意味着切片应该从第一个字节开始;
-
end:设置切片的结束点,会对该位置之前的数据进行切片。默认值为blob.size;
-
contentType:设置新 blob 的 MIME 类型。如果省略 type,则默认为 blob 的原始值。
-
演示
File
File 接口基于 Blob,继承了 blob 的功能并将其扩展以支持用户系统上的文件。File 对象是特殊类型的 Blob,比Blob要多一些文件相关的属性。
File() 构造器创建新的 File 对象实例:var myFile = new File(bits, name[, options]);
- bits:一个包含ArrayBuffer,ArrayBufferView,Blob,或者 DOMString 对象的 Array — 或者任何这些对象的组合(所以可以这样创建File,new File([blob],‘test.zip’))。
- name:表示文件名称,或者文件路径。
- options: 可选,选项对象,包含文件的可选属性。可用的选项如下:
- type: DOMString,表示将要放到文件中的内容的 MIME 类型。默认值为 “” 。
- lastModified: 数值,表示文件最后修改时间的 Unix 时间戳(毫秒)。默认值为 Date.now()。
在 JavaScript 中,主要有两种方法来获取 File 对象:
-
<input> 元素上选择文件后返回的 FileList 对象;
-
文件拖放操作生成的 DataTransfer 对象;
-
window.showOpenFilePicker
-
fetch 从后端获取
input
- 但是,注意一下,每次change的时候,需要把这个input的value置为空,接着,仍然选择同一个文件时,也会触发这个change事件
- 可以添加hidden属性,让这个input标签隐藏掉。或者,写css样式让它隐藏。然后,手动用js调用这个input的click事件。
手动拖拽
<!DOCTYPE html>
<html lang="en">
<head><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><style>.box {width: 300px;height: 300px;border: 1px solid red;}</style><script>window.onload = () => {let box = document.querySelector('.box')box.ondragover = (e) => {e.preventDefault();}box.ondrop = (e) => {e.preventDefault();let files = e.dataTransfer.filesconsole.log(files);}}</script>
</head>
<body><div class="box"></div>
</body>
</html>
fetch 从后端获取流
从这里,我们可以看到都可以从后端拿到Blob对象了,那就可以操作这个Blob了,前端就可以创建个a标签,添加上download属性,然后用js模拟点击这个a标签,然后把这个a标签移除掉。
前端代码
<!DOCTYPE html>
<html lang="en"><head><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><style>.box {width: 300px;height: 300px;border: 1px solid red;}</style><script>window.onload = () => {let btn = document.querySelector('#btn')btn.onclick = () => {fetch('http://127.0.0.1:8083/file/downloadFile?filename=avatar3.png').then(response => {console.log(response);return response.blob()// 注意,只能读取一次}).then(result=>{console.log(result);})}btn2.onclick = () => {fetch('http://127.0.0.1:8083/test.json').then(response => {console.log(response);return response.json() // 注意,只能读取一次}).then(result=>{console.log(result);})}}</script>
</head><body><button id="btn">获取avatar3.png</button><button id="btn2">获取test.json</button>
</body></html>
后端代码
@GetMapping("downloadFile")
public void downloadFile(@RequestParam("filename") String filename) throws Exception {// 告知浏览器这是一个字节流,浏览器处理字节流的默认方式就是下载// 意思是未知的应用程序文件,浏览器一般不会自动执行或询问执行。浏览器会像对待,// 设置了HTTP头Content-Disposition值为attachment的文件一样来对待这类文件,即浏览器会触发下载行为response.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_OCTET_STREAM_VALUE);// ,该响应头指示回复的内容该以何种形式展示,是以内联的形式(即网页或者网页的一部分),还是以附件的形式下载并保存到本地。response.setHeader(HttpHeaders.CONTENT_DISPOSITION,"attachment;fileName="+ URLEncoder.encode(filename, "UTF-8"));File file = new File("d:/Projects/practice/test-springboot/src/main/resources/file/" + filename);ServletOutputStream ros = response.getOutputStream();FileInputStream fis = new FileInputStream(file);byte[] bytes = new byte[2 * 1024];int len = 0;while ((len = fis.read(bytes)) != -1) {ros.write(bytes, 0, len);}ros.flush();ros.close();}@GetMapping("test.json")
public Object test2() {HashMap<String, Object> data = new HashMap<>();data.put("halo", "world");return data;
}
window.showOpenFilePicker
<!DOCTYPE html>
<html lang="en"><head><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><script>window.onload = () => {document.getElementById('openImageFile').addEventListener('click', async () => {const handle = await showOpenFilePicker({multiple: true,types: [{description: '图片',accept: { 'image/png': ['.jpg', '.png'] }}]})if (handle && handle.length) {const handleFile = handle[0]const file = await handleFile.getFile()console.log(file);}}, false)}</script>
</head><body><button id="openImageFile">打开图片</button>
</body></html>
Filereader
通过上面我都知道了blob是不可修改也是无法读取里面的内容的。无法读取里面的内容肯定是不可行的。所以Filereader就提供了读取blob里面内容的方法
FileReader对象提供了以下方法来加载文件:
- readAsArrayBuffer()∶读取指定Blob中的内容,完成之后,result属性中保存的将是被读取文件的ArrayBuffer数据对象;
- readAsBinaryString()∶读取指定Blob中的内容,完成之后,result属性中将包含所读取文件的原始二进制数据;
- readAsDataURL()︰读取指定Blob 中的内容,完成之后,result属性中将包含一个data: URL格式的 Base64字符串以表示所读取文件的内容。
- readAsText()︰读取指定Blob 中的内容,完成之后,result属性中将包含一个字符串以表示所读取的文件内容。
示例1
将文件读取为base64 的字符串
<!DOCTYPE html>
<html lang="en"><head><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><script>window.onload = () => {let iptFile = document.querySelector('#iptFile')iptFile.onchange = (e) => {console.log(e.target.files);let file = iptFile.files[0]let fileReader = new FileReader()fileReader.readAsDataURL(file)fileReader.onload = function(e) {console.log(e.target.result);}}}</script>
</head><body><input type="file" id="iptFile" name="file"/>
</body></html>
示例2
将存入的字符串的blob对象,读出来里面的内容
ArrayBuffer
我们可以把它理解为特殊的数组,特殊在哪里呢?
- ArrayBuffer 本身就是一个黑盒,不能直接读写所存储的数据,需要借助以下视图对象来读写
- TypedArray只是一个概念,实际使用的是那9个对象
创建buffer
new ArrayBuffer(bytelength)
- 参数:它接受一个参数,即 bytelength,表示要创建数组缓冲区的大小(以字节为单位。)
TypedArray读写buffer
DataView读写buffer
与Blob对比
-
ArrayBuffer 与 Blob 有啥区别呢?根据 ArrayBuffer 和 Blob 的特性,Blob 作为一个整体文件,适合用于传输;当需要对二进制数据进行操作时(比如要修改某一段数据时),就可以使用 ArrayBuffer。
-
通过ArrayBuffer创建Blob,然后通过FileReader读取里面的内容
Blob Url & DataUrl
-
可以使用 File 或 Blob 生成对应的 Url(除了使用现成的file对象,我们也可以用上面的方法,自己创建File对象,然后传进去。)
-
URL.createObjectURL(blob) ; (blob url)
- URL.revokeObjectURL:URL.revokeObjectURL()方法会释放一个通过URL.createObjectURL()创建的对象URL. 当你要已经用过了这个对象URL,然后要让浏览器知道这个URL已经不再需要指向对应的文件的时候,就需要调用这个方法.(可参考:URL.createObjectURL和URL.revokeObjectURL)
-
FileReader.readAsDataURL(file); (data url)
-
-
生成的Url在一些a标签、img标签、iframe标签中可以使用。
- 这样就可以实现比如,上传图片前,先预览图片的效果了
示例1
<!DOCTYPE html>
<html lang="en"><head><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><script>window.onload = () => {let iptFile = document.querySelector('#iptFile')let a = document.querySelector('#a')let img = document.querySelector('#img')iptFile.onchange = (e) => {console.log(e.target.files);let file = iptFile.files[0]let blobUrl = URL.createObjectURL(file) ; console.log(blobUrl);a.href = blobUrlimg.src = blobUrl}}</script>
</head><body><input type="file" id="iptFile" name="file"/><a href="#" id="a" download>link</a> <!-- 设置target='_blank'可以打开一个新页面; 加上download属性后,点击a标签可以下载,不加download的话,如果浏览器支持查看该文件,将会预览这个文件,如果不支持查看,将会下载这个文件;可以使用js模拟点击a标签,来触发下载 --><img src="" id="img" alt="err" style="width:50px;height:50px;">
</body></html>
示例2
与上面的效果一致,只不过换成了FileReader
<!DOCTYPE html>
<html lang="en"><head><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><script>window.onload = () => {let iptFile = document.querySelector('#iptFile')let a = document.querySelector('#a')let img = document.querySelector('#img')iptFile.onchange = (e) => {console.log(e.target.files);let file = iptFile.files[0]let fileReader = new FileReader()fileReader.onload = (e) => {let dataUrl = fileReader.resulta.href = dataUrlimg.src = dataUrl}fileReader.readAsDataURL(file)}}</script>
</head><body><input type="file" id="iptFile" name="file"/><a href="#" id="a" download>link</a> <!-- 设置target='_blank'可以打开一个新页面; 加上download属性后,点击a标签可以下载 --><img src="" id="img" alt="err" style="width:50px;height:50px;">
</body></html>