.检测浏览器对FileReader兼容性的方法:
if(window.FileReader) {
var fr=new FileReader();
// add your code here
}
else {
alert("Not supported by your browser!");
}
方法二:检测FileReader类型
if(typeof FileReader==='undefined'){
alert('您的浏览器不支持图片上传,请升级您的浏览器');
return false;
}
2.调用fileReader对象的方法
FileReader实例拥有四个方法, 其中三个是用来读取文件, 另一个是用来中断读取的。需要注意的是, 无论读取成功或是失败, 方法并不会返回读取结果,
这一结果(储存在result属性中)要用FileReader处理事件去获取;
方法名 参数 描述
abort none 中断读取
readAsBinaryString file 将文件转化为二进制码
readAsDataURL file 读取文件内容, 结果用data:url的字符串形式表示
readAsText file,[encoding] 将文件读取为文本
readAsText:该方法有两个参数, 其中第二个参数是文本的编码方式, 默认值为 UTF-8。这个方法非常容易理解, 将文件以文本方式读取, 读取的结果即是这个文本文件中的内容。
readAsText(file,encoding)可按指定编码方式读取文件, 但读取文件的单位是字符, 故对于文本文件, 只要按规定的编码方式读取即可; 而对于媒体文件(图片、音频、视频),
其内部组成并不是按字符排列, 故采用readAsText读取, 会产生乱码, 因此不是最理想的读取文件的方式。
readAsBinaryString:该方法将文件读取为二进制字符串, 通常我们将它传送到后端, 后端可以通过这段字符串存储文件。
readAsDataURL:这是例子程序中用到的方法, 该方法将文件读取为一段以 data: 开头的字符串, 这段字符串的实质就是 Data URL, Data URL是一种将小文件直接嵌入文档的方案。
这里的小文件通常是指图像与 html 等格式的文件。控制台为当前所传文件的base64编码表示。
由于媒体文件的src属性, 可以通过采用网络地址或base64的方式显示,因此我们可以利用readAsDataURL实现对图片的预览。
3.处理事件
FileReader 包含了一整套完成的事件模型,用于捕获读取文件时的状态,下面这个表格归纳了这些事件。
事件 描述
onabort 中断时触发
onerror 出错时触发
onload 文件读取成功完成时触发
onloadend 读取完成时触发,无论读取成功或失败
onloadstart 读取开始时触发
onprogress 读取中
result 返回文件的内容。只有在读取操作完成后,此属性才有效,返回的数据的格式取决于是使用哪种读取方法来执行读取操作的。
readyState 提供 FileReader 读取操作时的当前状态。
家好,我是 Echa。
好久没跟粉丝们细聊JavaScript那点事了。做一名全栈工程师,JS基础还是要打牢,这样的话不管底层业务逻辑以及第三方框架怎么变化,都离不开基础。本文文章属于基础篇,阅读有点乏味枯燥,但一定能学到知识。创作不易,喜欢的老铁们加个关注,点个赞,后面会持续更新干货,速速收藏,谢谢!
JavaScript 提供了一些 API 来处理文件或原始文件数据,例如:File、Blob、FileReader、ArrayBuffer、base64 等。下面就来看看它们都是如何使用的,它们之间又有何区别和联系!
上面的这个图是在线工具画的,打开就可以用的网址如下:
https://excalidraw.com/
Blob 全称为 binary large object ,即二进制大对象,它是 JavaScript 中的一个对象,表示原始的类似文件的数据。下面是 MDN 中对 Blob 的解释:
Blob 对象表示一个不可变、原始数据的类文件对象。它的数据可以按文本或二进制的格式进行读取,也可以转换成 ReadableStream 来用于数据操作。
实际上,Blob 对象是包含有只读原始数据的类文件对象。简单来说,Blob 对象就是一个不可修改的二进制文件。
可以使用 Blob() 构造函数来创建一个 Blob:
new Blob(array, options);
其有两个参数:
常见的 MIME 类型如下:
下面来看一个简单的例子:
const blob=new Blob(["Hello World"], {type: "text/plain"});
这里可以成为动态文件创建,其正在创建一个类似文件的对象。这个 blob 对象上有两个属性:
下面来看打印结果:
const blob=new Blob(["Hello World"], {type: "text/plain"});
console.log(blob.size); // 11
console.log(blob.type); // "text/plain"
注意,字符串"Hello World"是 UTF-8 编码的,因此它的每个字符占用 1 个字节。
到现在,Blob 对象看起来似乎我们还是没有啥用。那该如何使用 Blob 对象呢?可以使用 URL.createObjectURL() 方法将将其转化为一个 URL,并在 Iframe 中加载:
<iframe></iframe>
const iframe=document.getElementsByTagName("iframe")[0];
const blob=new Blob(["Hello World"], {type: "text/plain"});
iframe.src=URL.createObjectURL(blob);
除了使用Blob()构造函数来创建blob 对象之外,还可以从 blob 对象中创建blob,也就是将 blob 对象切片。Blob 对象内置了 slice() 方法用来将 blob 对象分片,其语法如下:
const blob=instanceOfBlob.slice([start [, end [, contentType]]]};
其有三个参数:
下面来看例子:
const iframe=document.getElementsByTagName("iframe")[0];
const blob=new Blob(["Hello World"], {type: "text/plain"});
const subBlob=blob.slice(0, 5);
iframe.src=URL.createObjectURL(subBlob);
此时页面会显示"Hello"。
文件(File)接口提供有关文件的信息,并允许网页中的 JavaScript 访问其内容。实际上,File 对象是特殊类型的 Blob,且可以用在任意的 Blob 类型的 context 中。Blob 的属性和方法都可以用于 File 对象。
注意:File 对象中只存在于浏览器环境中,在 Node.js 环境中不存在。
在 JavaScript 中,主要有两种方法来获取 File 对象:
首先定义一个输入类型为 file 的 input 标签:
<input type="file" id="fileInput" multiple="multiple">
这里给 input 标签添加了三个属性:
下面来给 input 标签添加 onchange 事件,当选择文件并上传之后触发:
const fileInput=document.getElementById("fileInput");
fileInput.onchange=(e)=> {
console.log(e.target.files);
}
当点击上传文件时,控制台就会输出一个 FileList 数组,这个数组的每个元素都是一个 File 对象,一个上传的文件就对应一个 File 对象:
每个 File 对象都包含文件的一些属性,这些属性都继承自 Blob 对象:
通常,我们在上传文件时,可以通过对比 size 属性来限制文件大小,通过对比 type 来限制上传文件的格式等。
另一种获取 File 对象的方式就是拖放 API,这个 API 很简单,就是将浏览器之外的文件拖到浏览器窗口中,并将它放在一个成为拖放区域的特殊区域中。拖放区域用于响应放置操作并从放置的项目中提取信息。这些是通过 ondrop 和 ondragover 两个 API 实现的。
下面来看一个简单的例子,首先定义一个拖放区域:
<div id="drop-zone"></div>
然后给这个元素添加 ondragover 和 ondrop 事件处理程序:
const dropZone=document.getElementById("drop-zone");
dropZone.ondragover=(e)=> {
e.preventDefault();
}
dropZone.ondrop=(e)=> {
e.preventDefault();
const files=e.dataTransfer.files;
console.log(files)
}
注意:这里给两个 API 都添加了 e.preventDefault(),用来阻止默认事件。它是非常重要的,可以用来阻止浏览器的一些默认行为,比如放置文件将显示在浏览器窗口中。
当拖放文件到拖放区域时,控制台就会输出一个 FileList 数组,该数组的每一个元素都是一个 File 对象。这个 FileList 数组是从事件参数的 dataTransfer 属性的 files 获取的:
可以看到,这里得到的 File 对象和通过 input 标签获得的 File 对象是完全一样的。
FileReader 是一个异步 API,用于读取文件并提取其内容以供进一步使用。FileReader 可以将 Blob 读取为不同的格式。
注意:FileReader 仅用于以安全的方式从用户(远程)系统读取文件内容,不能用于从文件系统中按路径名简单地读取文件。
可以使用 FileReader 构造函数来创建一个 FileReader 对象:
const reader=new FileReader();
这个对象常用属性如下:
FileReader 对象提供了以下方法来加载文件:
可以看到,上面这些方法都接受一个要读取的 blob 对象作为参数,读取完之后会将读取的结果放入对象的 result 属性中。
FileReader 对象常用的事件如下:
当然,这些方法可以加上前置 on 后在HTML元素上使用,比如onload、onerror、onabort、onprogress。除此之外,由于FileReader对象继承自EventTarget,因此还可以使用 addEventListener() 监听上述事件。
下面来看一个简单的例子,首先定义一个 input 输入框用于上传文件:
<input type="file" id="fileInput">
接下来定义 input 标签的 onchange 事件处理函数和FileReader对象的onload事件处理函数:
const fileInput=document.getElementById("fileInput");
const reader=new FileReader();
fileInput.onchange=(e)=> {
reader.readAsText(e.target.files[0]);
}
reader.onload=(e)=> {
console.log(e.target.result);
}
这里,首先创建了一个 FileReader 对象,当文件上传成功时,使用 readAsText() 方法读取 File 对象,当读取操作完成时打印读取结果。
使用上述例子读取文本文件时,就是比较正常的。如果读取二进制文件,比如png格式的图片,往往会产生乱码,如下:
那该如何处理这种二进制数据呢?readAsDataURL() 是一个不错的选择,它可以将读取的文件的内容转换为 base64 数据的 URL 表示。这样,就可以直接将 URL 用在需要源链接的地方,比如 img 标签的 src 属性。
对于上述例子,将 readAsText 方法改为 readAsDataURL():
const fileInput=document.getElementById("fileInput");
const reader=new FileReader();
fileInput.onchange=(e)=> {
reader.readAsDataURL(e.target.files[0]);
}
reader.onload=(e)=> {
console.log(e.target.result);
}
这时,再次上传二进制图片时,就会在控制台打印一个 base64 编码的 URL,如下:
下面来修改一下这个例子,将上传的图片通过以上方式显示在页面上:
<input type="file" id="fileInput" />
<img id="preview" />
const fileInput=document.getElementById("fileInput");
const preview=document.getElementById("preview");
const reader=new FileReader();
fileInput.onchange=(e)=> {
reader.readAsDataURL(e.target.files[0]);
};
reader.onload=(e)=> {
preview.src=e.target.result;
console.log(e.target.result);
};
当上传大文件时,可以通过 progress 事件来监控文件的读取进度:
const reader=new FileReader();
reader.onprogress=(e)=> {
if (e.loaded && e.total) {
const percent=(event.loaded / event.total) * 100;
console.log(`上传进度: ${Math.round(percent)} %`);
}
});
progress 事件提供了两个属性:loaded(已读取量)和total(需读取总量)。
ArrayBuffer 对象用来表示通用的、固定长度的原始二进制数据缓冲区。ArrayBuffer 的内容不能直接操作,只能通过 DataView 对象或 TypedArrray 对象来访问。这些对象用于读取和写入缓冲区内容。
ArrayBuffer 本身就是一个黑盒,不能直接读写所存储的数据,需要借助以下视图对象来读写:
TypedArray视图和 DataView视图的区别主要是字节序,前者的数组成员都是同一个数据类型,后者的数组成员可以是不同的数据类型。
那 ArrayBuffer 与 Blob 有啥区别呢?根据 ArrayBuffer 和 Blob 的特性,Blob 作为一个整体文件,适合用于传输;当需要对二进制数据进行操作时(比如要修改某一段数据时),就可以使用 ArrayBuffer。
下面来看看 ArrayBuffer 有哪些常用的方法和属性。
ArrayBuffer 可以通过以下方式生成:
new ArrayBuffer(bytelength)
ArrayBuffer()构造函数可以分配指定字节数量的缓冲区,其参数和返回值如下:
ArrayBuffer 实例上有一个 byteLength 属性,它是一个只读属性,表示 ArrayBuffer 的 byte 的大小,在 ArrayBuffer 构造完成时生成,不可改变。来看例子:
const buffer=new ArrayBuffer(16);
console.log(buffer.byteLength); // 16
ArrayBuffer 实例上还有一个 slice 方法,该方法可以用来截取 ArrayBuffer 实例,它返回一个新的 ArrayBuffer ,它的内容是这个 ArrayBuffer 的字节副本,从 begin(包括),到 end(不包括)。来看例子:
const buffer=new ArrayBuffer(16);
console.log(buffer.slice(0, 8)); // 16
这里会从 buffer 对象上将前8个字节生成一个新的ArrayBuffer对象。这个方法实际上有两步操作,首先会分配一段指定长度的内存,然后拷贝原来ArrayBuffer对象的置顶部分。
ArrayBuffer 上有一个 isView()方法,它的返回值是一个布尔值,如果参数是 ArrayBuffer 的视图实例则返回 true,例如类型数组对象或 DataView 对象;否则返回 false。简单来说,这个方法就是用来判断参数是否是 TypedArray 实例或者 DataView 实例:
const buffer=new ArrayBuffer(16);
ArrayBuffer.isView(buffer) // false
const view=new Uint32Array(buffer);
ArrayBuffer.isView(view) // true
TypedArray 对象一共提供 9 种类型的视图,每一种视图都是一种构造函数。如下:
元素类型化数组字节描述Int8Int8Array18 位有符号整数Uint8Uint8Array18 位无符号整数Uint8CUint8ClampedArray18 位无符号整数Int16Int16Array216 位有符号整数Uint16Uint16Array216 位无符号整数Int32Int32Array432 位有符号整数Uint32Uint32Array432 位无符号整数Float32Float32Array432 位浮点Float64Float64Array864 位浮点
来看看这些都是什么意思:
这些构造函数生成的对象统称为 TypedArray 对象。它们和正常的数组很类似,都有length属性,都能用索引获取数组元素,所有数组的方法都可以在类型化数组上面使用。
那类型化数组和数组有什么区别呢?
下面来看看 TypedArray 都有哪些常用的方法和属性。
TypedArray 的语法如下(TypedArray只是一个概念,实际使用的是那9个对象):
new Int8Array(length);
new Int8Array(typedArray);
new Int8Array(object);
new Int8Array(buffer [, byteOffset [, length]]);
可以看到,TypedArray 有多种用法,下面来分别看一下。
let view=new Int8Array(16);
view[0]=10;
view[10]=6;
console.log(view);
输出结果如下:
这里就生成了一个 16个元素的 Int8Array 数组,除了手动赋值的元素,其他元素的初始值都是 0。
const view=new Int8Array(new Uint8Array(6));
view[0]=10;
view[3]=6;
console.log(view);
输出结果如下:
const view=new Int8Array([1, 2, 3, 4, 5]);
view[0]=10;
view[3]=6;
console.log(view);
输出结果如下:
需要注意,TypedArray视图会开辟一段新的内存,不会在原数组上建立内存。当然,这里创建的类型化数组也能转换回普通数组:
Array.prototype.slice.call(view); // [10, 2, 3, 6, 5]
这种方式有三个参数,其中第一个参数是一个ArrayBuffer对象;第二个参数是视图开始的字节序号,默认从0开始,可选;第三个参数是视图包含的数据个数,默认直到本段内存区域结束。
const buffer=new ArrayBuffer(8);
const view1=new Int32Array(buffer);
const view2=new Int32Array(buffer, 4);
console.log(view1, view2);
输出结果如下:
每种视图的构造函数都有一个 BYTES_PER_ELEMENT 属性,表示这种数据类型占据的字节数:
Int8Array.BYTES_PER_ELEMENT // 1
Uint8Array.BYTES_PER_ELEMENT // 1
Int16Array.BYTES_PER_ELEMENT // 2
Uint16Array.BYTES_PER_ELEMENT // 2
Int32Array.BYTES_PER_ELEMENT // 4
Uint32Array.BYTES_PER_ELEMENT // 4
Float32Array.BYTES_PER_ELEMENT // 4
Float64Array.BYTES_PER_ELEMENT // 8
BYTES_PER_ELEMENT 属性也可以在类型化数组的实例上获取:
const buffer=new ArrayBuffer(16);
const view=new Uint32Array(buffer);
console.log(Uint32Array.BYTES_PER_ELEMENT); // 4
TypedArray 实例的 buffer 属性会返回内存中对应的 ArrayBuffer对象,只读属性。
const a=new Uint32Array(8);
const b=new Int32Array(a.buffer);
console.log(a, b);
输出结果如下:
TypeArray 实例的 slice方法可以返回一个指定位置的新的 TypedArray实例。
const view=new Int16Array(8);
console.log(view.slice(0 ,5));
输出结果如下:
const view=new Int16Array(8);
view.length; // 8
view.byteLength; // 16
说完 ArrayBuffer,下面来看看另一种操作 ArrayBuffer 的方式:DataView。DataView 视图是一个可以从 二进制 ArrayBuffer 对象中读写多种数值类型的底层接口,使用它时,不用考虑不同平台的字节序问题。
DataView视图提供更多操作选项,而且支持设定字节序。本来,在设计目的上,ArrayBuffer对象的各种TypedArray视图,是用来向网卡、声卡之类的本机设备传送数据,所以使用本机的字节序就可以了;而DataView视图的设计目的,是用来处理网络设备传来的数据,所以大端字节序或小端字节序是可以自行设定的。
DataView视图可以通过构造函数来创建,它的参数是一个ArrayBuffer对象,生成视图。其语法如下:
new DataView(buffer [, byteOffset [, byteLength]])
其有三个参数:
来看一个例子:
const buffer=new ArrayBuffer(16);
const view=new DataView(buffer);
console.log(view);
打印结果如下:
DataView 实例有以下常用属性:
const buffer=new ArrayBuffer(16);
const view=new DataView(buffer);
view.buffer;
view.byteLength;
view.byteOffset;
打印结果如下:
DataView 实例提供了以下方法来读取内存,它们的参数都是一个字节序号,表示开始读取的字节位置:
下面来看一个例子:
const buffer=new ArrayBuffer(24);
const view=new DataView(buffer);
// 从第1个字节读取一个8位无符号整数
const view1=view.getUint8(0);
// 从第2个字节读取一个16位无符号整数
const view2=view.getUint16(1);
// 从第4个字节读取一个16位无符号整数
const view3=view.getUint16(3);
DataView 实例提供了以下方法来写入内存,它们都接受两个参数,第一个参数表示开始写入数据的字节序号,第二个参数为写入的数据:
Object URL(MDN定义名称)又称Blob URL(W3C定义名称),是HTML5中的新标准。它是一个用来表示File Object 或Blob Object 的URL。在网页中,我们可能会看到过这种形式的 Blob URL:
其实 Blob URL/Object URL 是一种伪协议,允许将 Blob 和 File 对象用作图像、二进制数据下载链接等的 URL 源。
对于 Blob/File 对象,可以使用 URL构造函数的 createObjectURL() 方法创建将给出的对象的 URL。这个 URL 对象表示指定的 File 对象或 Blob 对象。我们可以在<img>、<script> 标签中或者 <a> 和 <link> 标签的 href 属性中使用这个 URL。
来看一个简单的例子,首先定义一个文件上传的 input 和一个 图片预览的 img:
<input type="file" id="fileInput" />
<img id="preview" />
再来使用 URL.createObjectURL() 将File 对象转化为一个 URL:
const fileInput=document.getElementById("fileInput");
const preview=document.getElementById("preview");
fileInput.onchange=(e)=> {
preview.src=URL.createObjectURL(e.target.files[0]);
console.log(preview.src);
};
可以看到,上传的图片转化成了一个 URL,并显示在了屏幕上:
那这个 API 有什么意义呢?可以将Blob/File对象转化为URL,通过这个URL 就可以实现文件下载或者图片显示等。
当我们使用createObjectURL()方法创建一个data URL 时,就需要使用revokeObjectURL()方法从内存中清除它来释放内存。虽然浏览器会在文档卸载时自动释放 Data URL,但为了提高性能,我们应该使用revokeObjectURL()来手动释放它。revokeObjectURL()方法接受一个Data URL 作为其参数,返回undefined。下面来看一个例子:
const objUrl=URL.createObjectURL(new File([""], "filename"));
console.log(objUrl);
URL.revokeObjectURL(objUrl);
Base64 是一种基于64个可打印字符来表示二进制数据的表示方法。Base64 编码普遍应用于需要通过被设计为处理文本数据的媒介上储存和传输二进制数据而需要编码该二进制数据的场景。这样是为了保证数据的完整并且不用在传输过程中修改这些数据。
在 JavaScript 中,有两个函数被分别用来处理解码和编码 base64 字符串:
btoa("JavaScript") // 'SmF2YVNjcmlwdA=='
atob('SmF2YVNjcmlwdA==') // 'JavaScript'
那 base64 的实际应用场景有哪些呢?其实多数场景就是基于Data URL的。比如,使用toDataURL()方法把 canvas 画布内容生成 base64 编码格式的图片:
const canvas=document.getElementById('canvas');
const ctx=canvas.getContext("2d");
const dataUrl=canvas.toDataURL();
除此之外,还可以使用readAsDataURL()方法把上传的文件转为base64格式的data URI,比如上传头像展示或者编辑:
<input type="file" id="fileInput" />
<img id="preview" />
const fileInput=document.getElementById("fileInput");
const preview=document.getElementById("preview");
const reader=new FileReader();
fileInput.onchange=(e)=> {
reader.readAsDataURL(e.target.files[0]);
};
reader.onload=(e)=> {
preview.src=e.target.result;
console.log(e.target.result);
};
效果如下,将图片(二进制数据)转化为可打印的字符,也便于数据的传输:
另外,一些小的图片都可以使用 base64 格式进行展示,img标签和background的 url 属性都支持使用base64 格式的图片,这样做也可以减少 HTTP 请求。
看完这些基本的概念,下面就来看看常用格式之间是如何转换的。
const blob=new Blob([new Uint8Array(buffer, byteOffset, length)]);
const base64=btoa(String.fromCharCode.apply(null, new Uint8Array(arrayBuffer)));
const base64toBlob=(base64Data, contentType, sliceSize)=> {
const byteCharacters=atob(base64Data);
const byteArrays=[];
for (let offset=0; offset < byteCharacters.length; offset +=sliceSize) {
const slice=byteCharacters.slice(offset, offset + sliceSize);
const byteNumbers=new Array(slice.length);
for (let i=0; i < slice.length; i++) {
byteNumbers[i]=slice.charCodeAt(i);
}
const byteArray=new Uint8Array(byteNumbers);
byteArrays.push(byteArray);
}
const blob=new Blob(byteArrays, {type: contentType});
return blob;
}
function blobToArrayBuffer(blob) {
return new Promise((resolve, reject)=> {
const reader=new FileReader();
reader.onload=()=> resolve(reader.result);
reader.onerror=()=> reject;
reader.readAsArrayBuffer(blob);
});
}
function blobToBase64(blob) {
return new Promise((resolve)=> {
const reader=new FileReader();
reader.onloadend=()=> resolve(reader.result);
reader.readAsDataURL(blob);
});
}
const objectUrl=URL.createObjectURL(blob);
昨天下午被问到一个问题:oss 对象存储里边由于有些图片被共享,导致上传了很多的重复的图片或者文件,有没有办法在上传之前判断一下这个文件是否被上传过,如果上传过直接去后端拿存储的地址行不行。
当时被问到的时候,第一反应是根据file的文件类型名称和大小生成一个MD5,后来被否决了,假如文件改了名字的话,这个文件还是会被上传上去
然后通过一天的调研,学习了这个之前没有用过的FileReader对象,顺便被他的其他方法给吸引住了,今天这里分享一下
转载链接:https://segmentfault.com/a/1190000022113605
FileReader 对象允许Web应用程序异步读取存储在用户计算机上的文件(或原始数据缓冲区)的内容,使用File或Blob
Blob 对象表示一个不可变、原始数据的类文件对象。Blob 表示的不一定是JavaScript原生格式的数据。
File 接口基于Blob,继承了 blob 的功能并将其扩展使其支持用户系统上的文件。") 对象指定要读取的文件或数据。
其中File对象可以是来自用户在一个 input 元素用于为基于Web的表单创建交互式控件,以便接受来自用户的数据; 可以使用各种类型的输入数据和控件小部件,具体取决于设备和user agent元素上选择文件后返回的FileList对象,也可以来自拖放操作生成的DataTransfer对象,还可以是来自在一个HTMLCanvasElement上执行mozGetAsFile()方法后返回结果。(MDN)
说白了就是FileReader对象可以对内存中的数据进行操作
然后需要知道一个重点就是
也就是说他是不可以直接用本地的路径去读取文件的,可以请求后端的资源,来读取对应的文件,或者前端以一个比较安全的方式读取文件,常见的比如说input的文件上传
打印一下 如图
EMPTY: 0
LOADING: 1
DONE: 2
这三个是对象实例的状态,分别是未读取文件,正在读取和读取完毕
readyState: [Exception: TypeError: Illegal invocation at FileReader.invokeGetter (<anonymous>:1:142)]
result: [Exception: TypeError: Illegal invocation at FileReader.invokeGetter (<anonymous>:1:142)]
error: [Exception: TypeError: Illegal invocation at FileReader.invokeGetter (<anonymous>:1:142)]
然后这三个属性
onloadstart: [Exception: TypeError: Illegal invocation at FileReader.invokeGetter (<anonymous>:1:142)]
onprogress: [Exception: TypeError: Illegal invocation at FileReader.invokeGetter (<anonymous>:1:142)]
onload: [Exception: TypeError: Illegal invocation at FileReader.invokeGetter (<anonymous>:1:142)]
onabort: [Exception: TypeError: Illegal invocation at FileReader.invokeGetter (<anonymous>:1:142)]
onerror: [Exception: TypeError: Illegal invocation at FileReader.invokeGetter (<anonymous>:1:142)]
onloadend: [Exception: TypeError: Illegal invocation at FileReader.invokeGetter (<anonymous>:1:142)]
然后之前说了FileReader所有的操作都是异步的,所以你并不能像下边这样获取返回值
let fileReader=new FileReader()
let url=fileReader.readAsDataURL(file.file)
console.log(url)
这样是打印不出来的,你需要在他自身的处理事件上边回调获取
let fileReader=new FileReader()
fileReader.readAsDataURL(file.file)
fileReader.onload=()=>{
console.log(fileReader.result)
}
回调结果在对象实例的result属性上边,上边有说过
这个方法获取的结果是原始二进制数据,不能直接使用,还需要做一些转换或者使用标签什么得才能用,打印出来大概是这样的
好,看到这里,之前没有接触过的同学是不是脑瓜子嗡嗡的。。没关系 我昨天我也嗡嗡的。。。
简单扼要的说一下就是说,上边列出来的这九个构造函数,都会根据你传进去的参数,生成一个对应的数组,然后这些数组统称为TypeArray视图,这个数组包含了所有的数组的方法和属性,你可以像数组一样去操作他们,一会我会在下边打印一下他们的结果,看一下就知道了
这个是之前的时候搞得一个读取文件的方法,里边用到了FileReader的readAsText方法,不多说废话了,直接附上代码和效果图
export default function readFile(model) {
return new Promise((resolve)=> {
// 谷歌
if (window.FileReader) {
// 获取文件流
let file=model.currentTarget ? model.currentTarget.files[0] : model;
// 创建FileReader实例
let reader=new FileReader();
// 读文件
reader.readAsText(file);
reader.onload=()=> {
resolve(reader.result)
}
}
//支持IE 7 8 9 10
else if (typeof window.ActiveXObject !='undefined') {
let xmlDoc;
xmlDoc=new ActiveXObject("Microsoft.XMLDOM");
xmlDoc.async=false;
resolve(xmlDoc.load(model))
}
//支持FF
else if (document.implementation && document.implementation.createDocument) {
let xmlDoc;
xmlDoc=document.implementation.createDocument("", "", null);
xmlDoc.async=false;
resolve(xmlDoc.load(model))
}
})
}
//安装依赖
npm i zjsmethods -S
~~~~
// 页面引入并使用
import { _readFile } from "zjsmethods"
_readFile(file).then(res=>{
console.log(res)
})
上边说了那么一大堆,终于要进入正题了哈,直接看第一版代码
const reader=new FileReader();
reader.readAsArrayBuffer(file.file);
reader.onload=()=> {
let u8Arr=new Uint8Array(reader.result)
console.log(u8Arr)
console.log(md5(u8Arr))
}
ok 没得问题,结果如下
正在我觉得如此简单的时候,意外发生了,在我用比较小的文件的时候,只有1M 左右,但是当我上传了一个视频做测试的时候大概有两个G,浏览器崩溃了。。崩溃了。。了
然后我展开了 之前比较小文件的字节数组,大概有这么大
原因是readAsArrayBuffer在读取文件的时候会先把整个文件加载到内存中,那么如果文件太大,内存就不够用了,浏览器进程就会崩溃。
既然整个加载不行,那么我们选择把一个文件分段加载,后来我觉得10M一段比较稳妥,于是改成了当文件小于10M的时候平均分成10段,如果大于10M ,那么每10M 分成一段 直到分完为止,同样为了避免加密的时候数据太多造成卡顿,在生成标识的时候放弃用整个数组生成标识,采取固定规则的最大10M 数据生成标识
async vaildArrayBuffer(){
const reader=new FileReader();
while(this.whileNumber--){
this.start=this.end
this.end=this.end+this.whileMax
let { start,end,sliceEnd,file}=this
reader.readAsArrayBuffer(file.slice(start,end));
reader.onload=()=> {
new Uint8Array(reader.result)
.slice(0, sliceEnd)
.join('')
}
}
}
这个时候又出了一个小插曲,在调用的时候reader被提示,正在进行文件读取,也是就一个reader在做读取文件操作的时候不能同事读取两个,于是乎刚开始的时候我高估了读取的速度放在了回调里边读取文件,代价就是我在电脑前面眼巴巴的看了控制台大概5分钟,后来改成了promise包裹,最后整理出的代码如下
/*
* @Date: 2020-03-22 16:36:37
* @information: 最后更新时间
*/
import md5 from 'md5'
export default class vaileFile{
constructor(file){
this.file=file
// 每次截取多少二进制
this.whileMax=Math.floor(file.size / 10 > 10240 * 1024 ? 10240 * 1024 : file.size / 10);
// 循环截取多少次
this.whileNumber=file.size <=10240 * 1024 ? 10 : Math.ceil(file.size/this.whileMax)
// 二进制的截取长度,超出10M后 每10M 截取一部分,最多10M
this.sliceEnd=Math.floor(1024 * 10240 / file.size * 100 / this.whileNumber * this.whileMax)
this.sliceEnd=this.whileNumber>10?this.sliceEnd:10240 * 1024
// 转换二进制的长度
this.start=0
this.end=0;
}
/**
* @Author: 周靖松
* @Date: 2020-03-22 15:53:07
* @information: 校验文件唯一
*/
async vaildArrayBuffer(){
let promiseArr=[]
while(this.whileNumber--){
this.start=this.end
this.end=this.end+this.whileMax
let { start,end,sliceEnd,file}=this
let promiseArrayBuffer=new Promise((resolve,reject)=>{
const reader=new FileReader();
reader.readAsArrayBuffer(file.slice(start,end));
reader.onload=()=> {
resolve(
new Uint8Array(reader.result)
.slice(0, sliceEnd)
.join('')
)
}
})
promiseArr.push(promiseArrayBuffer)
}
return md5((await Promise.all(promiseArr)).join(''))
}
}
大功告成,上传的文件后会生成一个md5 ,复制文件,文件改名字,都可以识别是之前的文件
然后写一个README.md 说明一下使用方法
### _vaileFile ,//使用文件二进制校验文件唯一性
当有业务需要上传oss 对象存储的时候,为了避免同一个文件(视频,音频,图片,压缩包等),有可能其他人复制或者改名字等等,造成文件重复上传,大量占用空间,写了一个校验文件的方法
//安装依赖
npm i zjsmethods -S
//引入这个类
import { _vaileFile } from 'zjsmethods'
// 然后在你需要判断oss 是否有该文件的时候
new _vaileFile('file对象').vaildArrayBuffer().then(res=>{
console.log(res)
// 继续上传 或者 向后端请求已经存在的文件url
})
// new 这个类之后 有一个vaildArrayBuffer 方法 他返回一个promise ,里边返回值是一个md5的字符串,这个是这个文件的唯一标识
喜欢的点个赞吧,有不足之处欢迎斧正
*请认真填写需求信息,我们会在24小时内与您取得联系。