FileList对象、file对象、FileReader对象
HTML 5中,通过添加multiple属性,file控件内允许一次放置多个文件。控件内的每一个用户选择的文件都是一个file对象,而FileList对象则为这些file对象的列表,代表用户选择的所有文件。file对象有两个属性,name属性表示文件名,不包括路径,lastModifiedDate属性表示文件的最后修改日期。
1.File 对象
- File 对象代表一个文件,用来读写文件信息。它继承了 Blob 对象,或者说是一种特殊的 Blob 对象,所有可以使用 Blob 对象的场合都可以使用它。
- 浏览器原生提供一个File()构造函数,用来生成 File 实例对象。
- File 对象没有自己的实例方法,由于继承了 Blob 对象,因此可以使用 Blob 的实例方法slice()。
- File 对象,由于继承了 Blob 对象,因此可以使用 Blob 的属性,同时有自己的俩个属性lastModified和name。
var file;
for(var i=0; i<document.getElementById('file').files.length; i++) {
file = document.getElementById('file').files[i];
console.log(file.name);
}
ArrayBuffer对象
ArrayBuffer
对象、TypedArray
视图和DataView
视图是JavaScript操作二进制数据的接口。
简单说,ArrarBuffer
代表原始的二进制数据,TypedArray
视图用来读写简单类型的二进制数据,DataView
视图用来读写复杂类型的二进制数据。例如,new uint8Array(data)
代表新建一个操作二进制数据的TypedArray视图,真正的二进制数据还是保存在ArrayBuffer对象中。
WebSocket对象的binaryType属性用来设置WebSocket接收到的是Blob还是ArrayBuffer类型,默认为Blob。
JS里的ArrayBuffer
ArrayBuffer 类型化数组,类型化数组是JavaScript操作二进制数据的一个接口。最初为了满足JavaScript与显卡之间大量的、实时的数据交换,它们之间的数据通信必须是二进制的,而不能是传统的文本格式的背景下诞生的。
分配内存
类型化数组是建立在ArrayBuffer对象的基础上的。它的作用是,分配一段可以存放数据的连续内存区域。
var bf = new ArrayBuffer(40); // 生成了字节长度为40的内存区域
//通过提供的 byteLength 属性返回分配字节的长度
console.log(bf.byteLength); // 40
ArrayBuffer对象有一个slice方法,允许将内存区域的一部分,拷贝生成一个新的ArrayBuffer对象。
const bf = new ArrayBuffer(40);
const newBf = bf.slice(0, 10); // 从0 - 9 不包括 10
- 上面代码拷贝buffer对象的前10个字节,生成一个新的ArrayBuffer对象。
- slice方法其实包含两步,第一步是先分配一段新内存,第二步是将原来那个ArrayBuffer对象拷贝过去。slice方法接受两个参数,第一个参数表示拷贝开始的字节序号,第二个参数表示拷贝截止的字节序号。如果省略第二个参数,则默认到原ArrayBuffer对象的结尾。
- 除了slice方法,ArrayBuffer对象不提供任何直接读写内存的方法,只允许在其上方建立视图,然后通过视图读写。
视图的生成
ArrayBuffer作为内存区域,可以存放多种类型的数据。不同数据有不同的存储方式,这就叫做“视图”。目前,JavaScript提供以下类型的视图:
- Int8Array:8位有符号整数,长度1个字节。
- Uint8Array:8位无符号整数,长度1个字节。
- Int16Array:16位有符号整数,长度2个字节。
- Uint16Array:16位无符号整数,长度2个字节。
- Int32Array:32位有符号整数,长度4个字节。
- Uint32Array:32位无符号整数,长度4个字节。
- Float32Array:32位浮点数,长度4个字节。
- Float64Array:64位浮点数,长度8个字节。
每一种视图都有一个BYTES_PER_ELEMENT常数,表示这种数据类型占据的字节数。
Int8Array.BYTES_PER_ELEMENT // 1
Uint8Array.BYTES_PER_ELEMENT // 1
每一种视图都是一个构造函数,有多种方法可以生成:
// 浏览器控制台输出:
> Int32Array
> function Int32Array() { [native code] }
在ArrayBuffer对象之上生成视图
同一个ArrayBuffer对象之上,可以根据不同的数据类型,建立多个视图。
// 创建一个8字节的ArrayBuffer
var b = new ArrayBuffer(8);
// 创建一个指向b的Int32视图,开始于字节0,直到缓冲区的末尾
var v1 = new Int32Array(b);
// 创建一个指向b的Uint8视图,开始于字节2,直到缓冲区的末尾
var v2 = new Uint8Array(b, 2);
// 创建一个指向b的Int16视图,开始于字节2,长度为2
var v3 = new Int16Array(b, 2, 2);
上面代码在一段长度为8个字节的内存(b)之上,生成了三个视图:v1、v2和v3。视图的构造函数可以接受三个参数:
- 第一个参数:视图对应的底层ArrayBuffer对象,该参数是必需的。
- 第二个参数:视图开始的字节序号,默认从0开始。
- 第三个参数:视图包含的数据个数,默认直到本段内存区域结束。
值得注意的是:v1、v2和v3是重叠:v1[0]是一个32位整数,指向字节0~字节3;v2[0]是一个8位无符号整数,指向字节2;v3[0]是一个16位整数,指向字节2~字节3。只要任何一个视图对内存有所修改,就会在另外两个视图上反应出来。
直接生成
视图还可以不通过ArrayBuffer对象,直接分配内存而生成。
var f64a = new Float64Array(8);
f64a[0] = 10;
f64a[1] = 20;
f64a[2] = f64a[0] + f64a[1];
上面代码生成一个8个成员的Float64Array数组(共64字节),然后依次对每个成员赋值。这时,视图构造函数的参数就是成员的个数。可以看到,视图数组的赋值操作与普通数组的操作毫无两样。
将普通数组转为视图数组
var typedArray = new Uint8Array( [ 1, 2, 3, 4 ] );
也可以将视图直接转化为数组
Array.from(typeArray);
视图的操作
建立了视图以后,就可以进行各种操作了。这里需要明确的是,视图其实就是普通数组,语法完全没有什么不同,只不过它直接针对内存进行操作,而且每个成员都有确定的数据类型。所以,视图就被叫做“类型化数组”
普通数组的操作方法和属性,对类型化数组完全适用。
var buffer = new ArrayBuffer(16);
var int32View = new Int32Array(buffer);
for (var i=0; i<int32View.length; i++) {
int32View[i] = i*2;
}
普通数组的操作方法和属性,对类型化数组完全适用。
var buffer = new ArrayBuffer(16);
var int32View = new Int32Array(buffer);
for (var i=0; i<int32View.length; i++) {
int32View[i] = i*2;
}
buffer属性
类型化数组的buffer属性,返回整段内存区域对应的ArrayBuffer对象。该属性为只读属性。
var bf = new Uint8Array([1,2,3,4]);
bf.buffer; // ArrayBuffer {}
byteLength属性和byteOffset属性
byteLength属性返回类型化数组占据的内存长度,单位为字节。byteOffset属性返回类型化数组从底层ArrayBuffer对象的哪个字节开始。这两个属性都是只读属性。
var b = new ArrayBuffer(8);
var v1 = new Int32Array(b);
var v2 = new Uint8Array(b, 2);
var v3 = new Int16Array(b, 2, 2);
v1.byteLength // 8
v2.byteLength // 6
v3.byteLength // 4
v1.byteOffset // 0
v2.byteOffset // 2
v3.byteOffset // 2
注意将byteLength属性和length属性区分,前者是字节长度,后者是成员长度。
set方法
类型化数组的set方法用于复制数组,也就是将一段内容完全复制到另一段内存。
var a = new Uint8Array(8);
var b = new Uint8Array(8);
b.set(a);
上面代码复制a数组的内容到b数组,它是整段内存的复制,比一个个拷贝成员的那种复制快得多。set方法还可以接受第二个参数,表示从b对象哪一个成员开始复制a对象。
var a = new Uint16Array(8);
var b = new Uint16Array(10);
b.set(a,2)
上面代码的b数组比a数组多两个成员,所以从b[2]开始复制。
subarray方法
subarray方法是对于类型化数组的一部分,再建立一个新的视图。
var a = new Uint16Array(8);
var b = a.subarray(2,3);
a.byteLength // 16
b.byteLength // 2
subarray方法的第一个参数是起始的成员序号,第二个参数是结束的成员序号(不含该成员),如果省略则包含剩余的全部成员。所以,上面代码的a.subarray(2,3),意味着b只包含a[2]一个成员,字节长度为2。
ArrayBuffer与字符串的互相转换
ArrayBuffer转为字符串,或者字符串转为ArrayBuffer,有一个前提,即字符串的编码方法是确定的。假定字符串采用UTF-16编码(JavaScript的内部编码方式),可以自己编写转换函数。
// ArrayBuffer转为字符串,参数为ArrayBuffer对象
function ab2str(buf) {
return String.fromCharCode.apply(null, new Uint16Array(buf));
}
// 字符串转为ArrayBuffer对象,参数为字符串
function str2ab(str) {
var buf = new ArrayBuffer(str.length*2); // 每个字符占用2个字节
var bufView = new Uint16Array(buf);
for (var i=0, strLen=str.length; i<strLen; i++) {
bufView[i] = str.charCodeAt(i);
}
return buf;
}
复合视图
由于视图的构造函数可以指定起始位置和长度,所以在同一段内存之中,可以依次存放不同类型的数据,这叫做“复合视图”。
var buffer = new ArrayBuffer(24);
var idView = new Uint32Array(buffer, 0, 1);
var usernameView = new Uint8Array(buffer, 4, 16);
var amountDueView = new Float32Array(buffer, 20, 1);
面代码将一个24字节长度的ArrayBuffer对象,分成三个部分:
- 字节0到字节3:1个32位无符号整数
- 字节4到字节19:16个8位整数
- 字节20到字节23:1个32位浮点数
应用示例
Ajax
传统上,服务器通过Ajax操作只能返回文本数据。XMLHttpRequest 第二版允许服务器返回二进制数据,这时分成两种情况。如果明确知道返回的二进制数据类型,可以把返回类型(responseType)设为arraybuffer
;如果不知道,就设为blob。xhr.responseType = 'arraybuffer';
如果知道传回来的是32位整数,可以像下面这样处理。
xhr.onreadystatechange = function () {
if (req.readyState === 4 ) {
var arrayResponse = xhr.response;
var dataView = new DataView(arrayResponse);
var ints = new Uint32Array(dataView.byteLength / 4);
xhrDiv.style.backgroundColor = "#00FF00";
xhrDiv.innerText = "Array is " + ints.length + "uints long";
}
}
Canvas
网页Canvas元素输出的二进制像素数据,就是类型化数组。
var canvas = document.getElementById('myCanvas');
var ctx = canvas.getContext('2d');
var imageData = ctx.getImageData(0,0, 200, 100);
var typedArray = imageData.data;
需要注意的是,上面代码的typedArray虽然是一个类型化数组,但是它的视图类型是一种针对Canvas元素的专有类型Uint8ClampedArray。这个视图类型的特点,就是专门针对颜色,把每个字节解读为无符号的8位整数,即只能取值0~255,而且发生运算的时候自动过滤高位溢出。这为图像处理带来了巨大的方便。举例来说,如果把像素的颜色值设为Uint8Array类型,那么乘以一个gamma值的时候,就必须这样计算:
u8[i] = Math.min(255, Math.max(0, u8[i] * gamma));
因为Uint8Array类型对于大于255的运算结果(比如0xFF+1),会自动变为0x00,所以图像处理必须要像上面这样算。这样做很麻烦,而且影响性能。如果将颜色值设为Uint8ClampedArray类型,计算就简化许多。pixels[i] *= gamma;
Uint8ClampedArray类型确保将小于0的值设为0,将大于255的值设为255。注意,IE 10不支持该类型。
File
如果知道一个文件的二进制数据类型,也可以将这个文件读取为类型化数组。
reader.readAsArrayBuffer(file);
下面以处理bmp文件为例。假定file变量是一个指向bmp文件的文件对象,首先读取文件。
var reader = new FileReader();
reader.addEventListener("load", processimage, false);
reader.readAsArrayBuffer(file);
然后,定义处理图像的回调函数:先在二进制数据之上建立一个DataView视图,再建立一个bitmap对象,用于存放处理后的数据,最后将图像展示在canvas元素之中。
function processimage(e) {
var buffer = e.target.result;
var datav = new DataView(buffer);
var bitmap = {};
// 具体的处理步骤
}
具体处理图像数据时,先处理bmp的文件头。具体每个文件头的格式和定义,请参阅有关资料。
bitmap.fileheader = {};
bitmap.fileheader.bfType = datav.getUint16(0, true);
bitmap.fileheader.bfSize = datav.getUint32(2, true);
bitmap.fileheader.bfReserved1 = datav.getUint16(6, true);
bitmap.fileheader.bfReserved2 = datav.getUint16(8, true);
bitmap.fileheader.bfOffBits = datav.getUint32(10, true);
接着处理图像元信息部分。
bitmap.infoheader = {};
bitmap.infoheader.biSize = datav.getUint32(14, true);
bitmap.infoheader.biWidth = datav.getUint32(18, true);
bitmap.infoheader.biHeight = datav.getUint32(22, true);
bitmap.infoheader.biPlanes = datav.getUint16(26, true);
bitmap.infoheader.biBitCount = datav.getUint16(28, true);
bitmap.infoheader.biCompression = datav.getUint32(30, true);
bitmap.infoheader.biSizeImage = datav.getUint32(34, true);
bitmap.infoheader.biXPelsPerMeter = datav.getUint32(38, true);
bitmap.infoheader.biYPelsPerMeter = datav.getUint32(42, true);
bitmap.infoheader.biClrUsed = datav.getUint32(46, true);
bitmap.infoheader.biClrImportant = datav.getUint32(50, true);
最后处理图像本身的像素信息。
var start = bitmap.fileheader.bfOffBits;
bitmap.pixels = new Uint8Array(buffer, start);
2.FileList 对象
- FileList对象是一个类似数组的对象,代表一组选中的文件,每个成员都是一个 File 实例。它主要出现在两个场合。文件控件节点()的files属性,返回一个 FileList 实例。拖拉一组文件时,目标区的DataTransfer.files属性,返回一个 FileList 实例。
- 文件控件的files属性是一个 FileList 实例。(证明了Blob、File、FileList三者的关系)
- FileList 的实例属性主要是length
- FileList 的实例方法主要是item(),用来返回指定位置的实例。
ws.binaryType = 'arraybuffer';
3.FileReader 对象
1、FileReader 对象用于读取 File 对象或 Blob 对象所包含的文件内容。
2、浏览器原生提供一个FileReader构造函数,用来生成 FileReader 实例。
3、FileReader 有以下的实例属性。
- FileReader.error:读取文件时产生的错误对象
- FileReader.readyState:整数,表示读取文件时的当前状态。一共有三种可能的状态,0表示尚未加载任何数据,1表示数据正在加载,2表示加载完成。
- FileReader.result:读取完成后的文件内容,有可能是字符串,也可能是一个 ArrayBuffer 实例。
- FileReader.onabort:abort事件(用户终止读取操作)的监听函数。
- FileReader.onerror:error事件(读取错误)的监听函数。
- FileReader.onload:load事件(读取操作完成)的监听函数,通常在这个函数里面使用result属性,拿到文件内容。
- FileReader.onloadstart:loadstart事件(读取操作开始)的监听函数。
- FileReader.onloadend:loadend事件(读取操作结束)的监听函数。
- FileReader.onprogress:progress事件(读取操作进行中)的监听函数。
/ HTML 代码如下
// <input type="file" οnchange="onChange(event)">
function onChange(event) {
var file = event.target.files[0];
var reader = new FileReader();
reader.onload = function (event) {
console.log(event.target.result)
};
reader.readAsText(file);
}
//上面代码中,每当文件控件发生变化,就尝试读取第一个文件。如果读取成功(load事件发生),就打印出文件内容。
FileReader 有以下实例方法。
- FileReader.abort():终止读取操作,readyState属性将变成2。
- FileReader.readAsArrayBuffer():以 ArrayBuffer 的格式读取文件,读取完成后result属性将返回一个 ArrayBuffer 实例。
- FileReader.readAsBinaryString():读取完成后,result属性将返回原始的二进制字符串。
- FileReader.readAsDataURL():读取完成后,result属性将返回一个 Data URL 格式(Base64 编码)的字符串,代表文件内容。对于图片文件,这个字符串可以用于元素的src属性。注意,这个字符串不能直接进行 Base64 解码,必须把前缀data:/;base64,从字符串里删除以后,再进行解码。
- FileReader.readAsText():读取完成后,result属性将返回文件内容的文本字符串。该方法的第一个参数是代表文件的 Blob 实例,第二个参数是可选的,表示文本编码,默认为 UTF-8。
/* HTML 代码如下
<input type="file" οnchange="previewFile()">
<img src="" height="200">
*/
function previewFile() {
var preview = document.querySelector('img');
var file = document.querySelector('input[type=file]').files[0];
var reader = new FileReader();
if (file) {
reader.readAsDataURL(file);
}
reader.addEventListener('load', function () {
preview.src = reader.result;
}, false);
}
//上面代码中,用户选中图片文件以后,脚本会自动读取文件内容,然后作为一个 Data URL 赋值给<img>元素的src属性,从而把图片展示出来。
4、Blob对象
Blob对象概述
Blob(Binary Large Object)表示二进制类型的大对象。在数据库管理系统中,将二进制数据存储为一个单一个体的集合。Blob 对象表示一个不可变、原始数据的类文件对象。Blob 表示的不一定是JavaScript原生格式的数据。File 接口基于Blob,继承了 blob 的功能并将其扩展使其支持用户系统上的文件。
生成Blob
要从其他非blob对象和数据构造一个 Blob,需要使用 Blob() 构造函数。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,用于指定包含行结束符\n的字符串如何被写入。 它是以下两个值中的一个: “native”,代表行结束符会被更改为适合宿主操作系统文件系统的换行符,或者 “transparent”,默认值,代表会保持blob中保存的结束符不变
使用字符串构造一个blob对象
var debug = {hello: "world"};
var blob = new Blob([JSON.stringify(debug)], {type : 'application/json'});
生成的blob对象如下:
Blob {
size: 17
type: “application/json”
proto: Blob
}
Blob 对象含有两个属性:size 和 type。其中 size 属性用于表示数据的大小(以字节为单位),type 是
MIME 类型的字符串。MIME(Multipurpose Internet Mail Extensions)多用途互联网邮件扩展类型。是设定某种扩展名的文件用一种应用程序来打开的方式类型,当该扩展名文件被访问的时候,浏览器会自动使用指定应用程序来打开。多用于指定一些客户端自定义的文件名,以及一些媒体文件打开方式。
方法
slice()
Blob.slice() 方法用于创建一个包含源 Blob的指定字节范围内的数据的新 Blob 对象。
语法
var blob = instanceOfBlob.slice([start [, end [, contentType]]]};
参数
- start 可选 表示被会被拷贝进新的 Blob 的字节的起始位置。如果传入的是一个负数,那么这个偏移量将会从数据的末尾从后到前开始计算。举例来说, -10 将会是 Blob 的倒数第十个字节。它的默认值是0, 如果你传入的start的长度大于源 Blob 的长度,那么返回的将会是一个长度为0并且不包含任何数据的一个 Blob 对象。end 可选
- end-1的对应的字节将会是被拷贝进新的Blob 的最后一个字节。如果传入了一个负数,那么这个偏移量将会从数据的末尾从后到前开始计算。举例来说, -10 将会是 Blob 的倒数第十个字节。它的默认值就是它的原始长度(size).
- contentType 可选 给新的 Blob 赋予一个新的文档类型。这将会把它的 type 属性设为被传入的值。它的默认值是一个空的字符串。
stream()
返回一个ReadableStream对象,读取它将返回包含在Blob中的数据。
var stream = blob.stream();
text()
返回一个 Promise 对象且包含 blob 所有内容的 UTF-8 格式的 USVString。
Unicode 标量值( Unicode scalar values ):字符的代号。
在JavaScript中返回时, USVString 映射到 String 。它通常仅用于执行文本处理的 API,需要一串 unicode 标量值才能进行操作。
arrayBuffer()
返回一个 Promise 对象且包含 blob 所有内容的二进制格式的 ArrayBuffer。
FileReader.readAsArrayBuffer() 这个方法与之类似,但 arrayBuffer() 返回一个 promise 对象,而不是像 FileReader 一样返回一个基于事件的 API。
Blob 使用场景
分片上传
File 对象是特殊类型的 Blob,可以用在任意的 Blob 类型的上下文中。针对大文件传输的场景,我们可以使用 slice 方法对大文件进行切割,然后分片进行上传。
存储下载数据
从互联网上下载的数据可以存储到 Blob 对象中。 例如,在一些需要鉴权的图片接口中,我们可以使用fetch的方式,将鉴权信息附在请求里,下载得到blob对象,然后使用下面的方法,将blob作为url使用。或者在前端直接通过构建Blob对象进行前端文件下载。
axios.get('https://xxxxxx', {responseType: 'blob'})
.then(res => {
let url = URL.createObjectURL(res.data)
let a = document.createElement('a')
a.setAttribute('download', '图片')
a.href = url
a.click()
})
Blob 用作 URL
Blob 可以很容易的作为 、 或其他标签的 URL。Blob URL/Object URL 是一种伪协议,允许 Blob 和 File 对象用作图像,下载二进制数据链接等的 URL 源。
在浏览器中,我们使用 URL.createObjectURL 方法来创建 Blob URL,该方法接收一个 Blob 对象,并为其创建一个唯一的 URL。这个 URL 的生命周期和创建它的窗口中的 document 绑定。这个新的URL 对象表示指定的 File 对象或 Blob 对象。相当于这个方法创建了一个传入对象的内存引用地址
其形式为 blob:/,在chrome中生成对应的示例如下:
"blob:chrome-extension://lecdifefmmfjnjjinhaennhdlmcaeeeb/0f36aeda-9351-4e38-9379-93efc9360f6b"
示例
将页面中的配置信息下载下来供用户方便使用。
const config = {
name: 'lsqy',
password: 'yourpassword',
ak: 'XXXXXXXXXX',
sk: 'XXXXXXXXXX'
}
// 生成blob对象
const blobContent = new Blob(
[JSON.stringify(config, null, 2)],
{type : 'application/json'}
);
// 构建下载链接
const blobUrl = window.URL.createObjectURL(blobContent)
const lnk = document.createElement('a')
link.download = filename
link.href = blobUrl
// 触发点击
eleLink.click()
当你结束使用某个 URL 对象之后,应该通过调用URL.revokeObjectURL()这个方法来让浏览器知道不用在内存中继续保留对这个文件的引用了。如果你拿到了一个blobURL,想要重新转成blob,只能将此url当作下载链接重新下载
axios.get('blob:XXX', {responseType: 'blob'})
.then(res => {
// use res.data
})
Blob 转换为 Base64
Base64 是一种基于 64 个可打印字符来表示二进制数据的表示方法,它常用于在处理文本数据的场合,表示、传输、存储一些二进制数据,包括 MIME 的电子邮件及 XML 的一些复杂数据。
绝大多数现代浏览器都支持一种名为 Data URLs 的特性,允许使用 base64 对图片或其他文件的二进制数据进行编码,将其作为文本字符串嵌入网页中。
Data URLs 由四个部分组成:前缀(data:)、指示数据类型的 MIME 类型、如果非文本则为可选的 base64 标记、数据本身:
data:[][;base64],
mediatype 是个 MIME 类型的字符串,例如 “image/jpeg” 表示 JPEG 图像文件。如果被省略,则默认值为 text/plain;charset=US-ASCII。
如果数据是文本类型,你可以直接将文本嵌入(根据文档类型,使用合适的实体字符或转义字符)。如果是二进制数据,你可以将数据进行 base64 编码之后再进行嵌入。比如嵌入一张图片:
<img alt="logo" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUg...">
在编写 HTML 网页时,对于一些简单图片,通常会选择将图片内容直接内嵌在网页中,从而减少不必要的网络请求,如果你使用webpack打包的话,可以启用 file-loader 和 url-loader,配置小于某个大小的图片使用Data URL潜入到网页中。
使用base64还可以实现上传图片时的本地预览功能
readBlob(event) {
const self = this;
const reader = new FileReader();
reader.onload = function () {
const base64 = this.result;
// todo 这里将base64赋值给 img 标签的 src,用于本地预览
// img.src = base64
self.uploadImage(base64); // 上传base64数据到服务器
};
reader.readAsDataURL(event.target.files[0]);
}
对于 FileReader 对象来说,除了支持把 Blob/File 对象转换为 Data URL 之外,它还提供了 readAsArrayBuffer() 和 readAsText() 方法,用于把 Blob/File 对象转换为其它的数据格式。
Blob 与 ArrayBuffer
- Blob和ArrayBuffer都能存储二进制数据。Blob相对而言储存的二进制数据大(如File文件对象)。
- ArrayBuffer对象表示原始的二进制数据缓冲区,即在内存中分配指定大小的二进制缓冲区(容器),用于存储各种类型化数组的数据,是最基础的原始数据容器,无法直接读取或写入, 需要通过具体视图来读取或写入,即TypedArray对象或DataView对象对内存大小进行读取或写入;Blob对象表示一个不可变、原始数据的类文件对象。
- ArrayBuffer 是存在内存中的,可以直接操作。而 Blob 可以位于磁盘、高速缓存内存和其他不可用的位置。
- 可以相互转换。
Blob => ArrayBuffer
let blob = new Blob([1,2,3,4])
let reader = new FileReader();
reader.onload = function(result) {
console.log(result);
}
reader.readAsArrayBuffer(blob);
ArrayBuffer => Blob
let blob = new Blob([buffer])
blob与复制粘贴
粘贴 有时会遇到 在输入框拦截图片进行上传的场景,这时候就是监听paste事件,并获取剪切板内的文件
handlePaste (e) {
if (this.paste) {
this.uploadFiles(e.clipboardData.files);
}
}
我们拿到的files就是基于blob的file类型。你可以使用FileReader的所有方法将blib变成你想要的样子
复制 有时候我们需要点击按钮或右键菜单触发一个复制事件,将文本或图片扔进剪切板里。这时候我们也需要生成一个blob对象 如果是文本对象
"text/html": new Blob(["<i>Markup</i> <b>text</b>. Paste me into a rich text editor."], { type: "text/html" }),
"text/plain": new Blob(["Fallback markup text. Paste me into a rich text editor."], { type: "text/plain" })
如果是图片等文件类型数据,就需要自己fetch请求下载图片为blob,然后扔到剪切板里
new ClipboardItem({
[blob.type]: blob
})
File System API使用步骤
步骤一:
开始首先我们需要通过请求一个LocalFile对象来得到HTML5文件系统的访问,使用window.requetFileSystem全局方法:window.requestFileSystem(type, size, successCallback, opt_errorCallback)前俩个参数,你指定需要的生命周期类型和文件系统的大小。一个持久性的(Persistent)文件系统非常适合长期保存用户数据。浏览器不会删除,除非用户特意要求。一个临时性(Temporary)的文件系统非常适合web应用来缓存数据,但是在浏览器删除文件系统后任然可以操作。size用来指定字节大小,一般指定有效的最大访问存储大小。第三个参数是一个回调函数(callback),当用户代理成功的提供了一个文件系统后触发。它的主要参数是一个FileSystem对象。
并且我们可以添加一个可选的callback函数,用来在出错的时候调用,或者请求被拒绝的时候。参数是一个FileError对象。虽然这个对象是可选的,最好还是捕捉这些错误,因为很多地方可能会出错。文件系统得到这些方法依赖于最初包含的document。所有的document或者web应用来自于同一个最初来源共享一个文件系统。两个document或者应用来自于不同的来源完全不同并且不可联系。一个文件系统严格被限制访问一个应用,不能访问另外一个应用保存的数据。同时也对于其它的文件独立。这是一件好事:让文件访问不相干的系统文件资源,例如,操作系统的文件,完全没有必要,也不安全。我们看看这个例子:
window.requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem;
window.requestFileSystem(window.TEMPORARY, 5*1024*1024, initFS, errorHandler);
function initFS(fs){
alert("Welcome to Filesystem! It's showtime :)"); // Just to check if everything is OK :)
// place the functions you will learn bellow here
}function errorHandler(){
console.log('An error occured');
}
这里我们创建而来一个临时的5M文件系统存储。提供了一个成功的callback函数,用阿里操作我们的文件系统。并且添加了一个错误处理,用来处理错误。这里errorhandler()方法非常具有一般性。 如果你想的话,你可以创建一个优化版本,显示给用户更加详细的error信息。
function errorHandler(err){
var msg = 'An error occured: ';
switch (err.code) {
case FileError.NOT_FOUND_ERR:
msg += 'File or directory not found';
break;
case FileError.NOT_READABLE_ERR:
msg += 'File or directory not readable';
break;
case FileError.PATH_EXISTS_ERR:
msg += 'File or directory already exists';
break;
case FileError.TYPE_MISMATCH_ERR:
msg += 'Invalid filetype';
break;
default:
msg += 'Unknown Error';
break;
};
console.log(msg);
};
这个你得到的文件对象拥有一个name(一个唯一的文件系统名称,由浏览器赋值)并且ROOT属性参考文件系统的ROOT目录。这是一个DirectoryEntry对象,可以嵌套使用。每一个文件目录都可以包含文件,由FileEntry对象标示。DirectoryEntry对象定义使用路径名称得到DirectoryEntry和FileEntry的方法(如果不存在路径名,会创建新的目录)。
DirectoryEntry同时定义了createReader()工厂方法用来返回一个DirectoryReader对象用来列出一个文件夹。FileEntry类定义了一个得到File对象的方。你可以使用FileReader对象来读取文件。FileEntry定义了另外一个方法用来返回一个FileWriter对象,你可以将内容写到文件中。听起来是不是有点儿复杂?通过下面的例子我们会更清楚的理解。
步骤二:处理文件夹
很显然,第一件我们需要做的事就是创建一些目录。虽然ROOT目录已经村存在,你不希望把所有的文件都保存在那里。文件夹使用DirectoryEntry对象来创建。在下面的例子中我们将在ROOT文件夹中创建一个文件夹:Documentsfs.root.getDirectory(‘Documents’, {create: true}, function(dirEntry) {
alert(‘You have just created the ’ + dirEntry.name + ’ directory.’);
}, errorHandler);
getDiretory()方法用来读和创建目录。作为第一个参数,你可以传递一个名字或者路径来寻找或者创建。我们设计第二个参数为true,因为我们需要创建一个目录 - 不是读一个已存在的目录。当然我们在最后添加了一个错误的callback方法。这里我们创建了一个目录,接着我们创建一个子目录。
这个方法类似除了一下一点,我们修改第一个参数为”Documents/Music“。很简单是不是,如果你想创建一个子目录,Sky,使用俩个父目录那么怎么做呢? 如果你使用Documents/Music/Nature/Sky作为路径参数,你会得到错误,因为你不能创建一个没有父目录的目录。解决方式是一个一个的创建。但是这样很低效并且麻烦。更好的解决方式:创建一个方法用来自动创建目录:
function createDir(rootDir, folders) {
rootDir.getDirectory(folders[0], {create: true}, function(dirEntry) {
if (folders.length) {
createDir(dirEntry, folders.slice(1));
}
}, errorHandler);
};createDir(fs.root, 'Documents/Images/Nature/Sky/'.split('/'));
使用这个小技巧,我们只需要提供完整的路径就能自动为我们创建文件夹。接下来我们需要检查我们的文件系统。我们创建一个DirectoryReader对象,使用ReadEntries()方法来读取目录中的内容。
fs.root.getDirectory('Documents', {}, function(dirEntry){<br>
var dirReader = dirEntry.createReader();
dirReader.readEntries(function(entries) {<br>
for(var i = 0; i < entries.length; i++) {
var entry = entries[i];
if (entry.isDirectory){
console.log('Directory: ' + entry.fullPath);
}
else if (entry.isFile){
console.log('File: ' + entry.fullPath);
}
}
}, errorHandler);
}, errorHandler);
在以上代码中,isDirectory和isFile属性用来得到不同的输出文件或者文件夹。而且我们使用fullPath属性来得到完整的输入内容,而不是仅仅名字。这里有两种方式来删除一个DirectoryEntry:remove()和removeRecursively()。第一个删除需要被删除文件夹为空,否则会得到错误。
fs.root.getDirectory('Documents/Music', {}, function(dirEntry) {
dirEntry.remove(function(){
console.log('Directory successfully removed.');
}, errorHandler);
}, errorHandler);
fs.root.getDirectory('Documents/Music', {}, function(dirEntry) {
dirEntry.removeRecursively(function(){
console.log('Directory successufully removed.');
}, errorHandler);
}, errorHandler);
步骤三:处理文件
上面我们介绍了如何创建目录,下面我们介绍如何创建文件。以下例子在ROOT目录创建了一个空的文件
gbin1.txt。fs.root.getFile('gbin1.txt', {create: true, exclusive: true}, function(fileEntry) {
alert('A file ' + fileEntry.name + ' was created successfully.');
}, errorHandler);
getFile方法的第一个参数可以是绝对或者相对路径,但是必须是合法的。例如,没有父目录创建一个文件会得到一个错误。第二个参数是一个对象说明,如果文件不存在的话描述功能行为。在这个例子中:create:true表示如果文件不存则创建一个文件,如果存在则抛出错误(exclusive:true)。否则如果create:false,简单取得文件并返回。
fs.root.getFile('gbin1.txt', {create: false}, function(fileEntry) {
fileEntry.createWriter(function(fileWriter) {
window.BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder;
var bb = new BlobBuilder();
bb.append('Filesystem API is awesome!');
fileWriter.write(bb.getBlob('text/plain'));
}, errorHandler);
}, errorHandler);
以上代码中,我们返回了text.txt文件,创建了FileWriter对象。我们然后通过创建一个新的BlobBuilder对象添加内容并且使用了FileWriter的write()方法。调用getFile()方法只会返回FileEntry对象。并不返回文件的内容。因此,如果我们想读出文件内容,我们需要使用File对象和FileReader对象。
fs.root.getFile('test.txt', {}, function(fileEntry) {
fileEntry.file(function(file) {
var reader = new FileReader();
reader.onloadend = function(e) {
alert(this.result);
};
reader.readAsText(file);
}, errorHandler);
}, errorHandler);
我们已经写入了文件一些内容,但是如果以后添加更多内容呢?为了添加内容到已存在的文件,又需要调用FileWriter。我们可以使用seek()方法重新将writer添加到文件。seek接受字节偏移(byte offset)这个参数,并且设置file writer的位置。
fs.root.getFile('test.txt', {create: false}, function(fileEntry) {
fileEntry.createWriter(function(fileWriter) {
fileWriter.seek(fileWriter.length);
window.BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder;
var bb = new BlobBuilder();
bb.append('Yes, it is!');
fileWriter.write(bb.getBlob('text/plain'));
}, errorHandler);
}, errorHandler);
如果需要删除文件,我们调用entry.remove()。一个参数是一个没有参数的回调函数,当文件被成功删除后调用。第二个参数是一个可选的错误回调函数。fs.root.getFile(‘test.txt’, {create: false},
function(fileEntry) {
fileEntry.remove(function() {
console.log('File successufully removed.');
}, errorHandler);
}, errorHandler);
步骤四:
处理文件和目录FileEntry和DirectoryEntry分享同一个API方法来拷贝,移动和重命名。这里有俩个方法你可以使用进行操作:copyTo()和moveTo()。他们都接受同样的参数:
copyTo(parentDirEntry, opt_newName, opt_successCallback, opt_errorCallback);
moveTo(parentDirEntry, opt_newName, opt_successCallback, opt_errorCallback);
第一个参数是目标父目录,即你希望拷贝到的位置。第二个参数是可选的新文件名字,实际只有拷贝到同一个目录才需要。否则得到错误信息。其它俩个参数前面介绍过了。我们看一个实例,下面我们拷贝gbin1.txt从Root到Documents目录:
function copy(currDir, srcEntry, destDir) {
currDir.getFile(srcEntry, {}, function(fileEntry) {
currDir.getDirectory(destDir, {}, function(dirEntry) {
fileEntry.copyTo(dirEntry);
}, errorHandler);
}, errorHandler);
}
copy(fs.root, 'test.txt', 'Documents/');下面是一个移动gbin1.txt到Document的例子:function move(currDir, srcEntry, dirName) {
currDir.getFile(srcEntry, {}, function(fileEntry) {
currDir.getDirectory(dirName, {}, function(dirEntry) {
fileEntry.moveTo(dirEntry);
}, errorHandler);
}, errorHandler);
}
move(fs.root, 'gbin1.txt', 'Documents/');下面是重命名的例子:function rename(currDir, srcEntry, newName) {
currDir.getFile(srcEntry, {}, function(fileEntry) {
fileEntry.moveTo(currDir, newName);
}, errorHandler);
}
rename(fs.root, 'gbin1.txt', 'gbtags.txt');