Better

Ethan的博客,欢迎访问交流

如何在web应用中使用文件

最近在手机端碰到需要批量上传文件的需求,从而引发新一轮关于HTML5提供的文件相关的API的学习,同时对Cordova的File插件API结合Web API实现JS不能操作文件系统的问题。

Blob

Blob对象表示不可变的类似文件对象的原始数据。Blob表示不一定是JavaScript原生形式的数据。File接口基于Blob,继承了blob的功能并将其扩展使其支持用户系统上的文件。

  • 要从其他非blob对象和数据构造一个Blob,请使用 Blob() 构造函数。
  • 要创建包含另一个blob数据的子集blob,请使用slice()方法。——不同浏览器上需要带前缀使用
  • 要获取用户文件系统上文件的Blob对象,请参阅File文档。

构造函数

  • 语法:Blob(blobParts[, options])
  • 示例:var blob = new Blob([JSON.stringify(debug, null, 2)],{type : 'application/json'});

属性

  • Blob.size
  • Blob.type

使用 Blob 创建一个指向类型数组的URL

  • 语法:var url = URL.createObjectURL(blob);
  • 描述:你可以像使用一个普通URL那样使用它,比如用在img.src上。

从 Blob 中提取数据

var reader = new FileReader();
reader.addEventListener("loadend", function() {
   // reader.result 包含转化为类型数组的blob
});
reader.readAsArrayBuffer(blob);

Gecko 备注:特权许可

要使用 chrome 代码,JSM 和 Bootstrap 作用域,你必须像这样导入它:Cu.importGlobalProperties(['Blob']);

File

文件(File) 接口提供有关文件的信息,并允许网页中的 JavaScript 访问其内容。

File对象来源

  1. 用户在一个 <input> 元素上选择文件后返回的 FileList 对象
    • 如果你想让用户选择多个文件,只需在input元素上使用multiple属性
    • 通过accept="image/*"属性设置选择文件类型
  2. 自由拖放操作生成的 DataTransfer 对象
  3. 来自 HTMLCanvasElement 上的 mozGetAsFile() API。
  4. 在Gecko中,特权代码可以创建代表任何本地文件的File对象,而无需用户交互

File 对象是特殊类型的 Blob,且可以用在任意的 Blob 类型的 context 中。比如如下都能处理 Blob 和 File。

  1. FileReader
  2. URL.createObjectURL(), createImageBitmap()
  3. XMLHttpRequest.send()

构造函数

  • File()——返回一个新构建的文件对象(File)。
  • new File(bits, name[, options]);

属性

  • File.lastModified
  • File.name
  • File.size
  • File.type

FileReader

FileReader对象允许Web应用程序异步读取存储在用户计算机上的文件(或原始数据缓冲区)的内容,使用 File 或 Blob 对象指定要读取的文件或数据。

构造函数

  • FileReader()——返回一个新构造的FileReader。

方法

  • void abort();
  • void readAsArrayBuffer(in Blob blob); ——result属性中将包含一个ArrayBuffer对象以表示所读取文件的内容.
  • void readAsBinaryString(in Blob blob);——result属性中将包含所读取文件的原始二进制数据.
  • void readAsDataURL(in Blob blob);——result属性中将包含一个data: URL格式的字符串以表示所读取文件的内容.
  • void readAsText(in Blob blob, [optional] in DOMString encoding);——result属性中将包含一个字符串以表示所读取的文件内容.

属性

  • error——在读取文件时发生的错误. 只读.
  • readyState——表明FileReader对象的当前状态. 值为State constants中的一个. 只读
  • result——读取到的文件内容.这个属性只在读取操作完成之后才有效,并且数据的格式取决于读取操作是由哪个方法发起的. 只读.

事件

  • onabort
  • onerror
  • onload——当读取操作成功完成时调用.
  • onloadend——当读取操作完成时调用,不管是成功还是失败.该处理程序在onload或者onerror之后调用.
  • onloadstart
  • onprogress——在读取数据过程中周期性调用.

显示用户选择的图片的缩略图

function handleFiles(files) {
  for (var i = 0; i < files.length; i++) {
    var file = files[i];
    var imageType = /^image\//;
    if (!imageType.test(file.type)) {
      continue;
    }
    var img = document.createElement("img");
    img.classList.add("obj");
    // 个图片添加了file属性使它具有 File;这样做可以让我们拿到稍后需要实际上传的图片。
    img.file = file;
    preview.appendChild(img); // Assuming that "preview" is the div output where the content will be displayed.
    var reader = new FileReader();
    reader.onload = (function(aImg) { return function(e) { aImg.src = e.target.result; }; })(img);
    reader.readAsDataURL(file);
  }
}

根据文件类型使用不同方式读取

var reader = new FileReader(); //new一个FileReader实例
if (/text+/.test(file.type)) { //判断文件类型,是不是text类型
    reader.onload = function() {
        $('body').append('<pre>' + this.result + '</pre>');
    }
    reader.readAsText(file);
} else if (/image+/.test(file.type)) { //判断文件是不是imgage类型
    reader.onload = function() {
        $('body').append('<img src="' + this.result + '"/>');
    }
    reader.readAsDataURL(file);
}

FormData

通过FormData对象可以组装一组用XMLHttpRequest发送请求的键/值对。它可以更灵活方便的发送表单数据,因为可以独立于表单使用。如果你把表单的编码类型设置为multipart/form-data ,则通过FormData传输的数据格式和表单通过submit() 方法传输的数据格式相同

如何创建一个对象

  • 空构造函数——var formData = new FormData();
  • 通过HTML表单创建FormData对象——var formData = new FormData(someFormElement);此时同样可以通过append方法附加额外的数据到FormData对象里

方法

  • void append(DOMString name, Blob value, optional DOMString filename);
    • 有些服务器实现要求传递filename
    • 指定文件的文件名,当value参数被指定为一个Blob对象或者一个File对象时,该文件名会被发送到服务器上,对于Blob对象来说,这个值默认为"blob".
  • void append(DOMString name, DOMString value);
  • FormData 对象的字段类型可以是 Blob, File, 或者 string: 如果它的字段类型不是Blob也不是File,则会被转换成字符串类型

提交方式

一个 html <form> 可以用以下四种方式发送:

  • 使用 POST 方法,并设置 enctype 属性为 application/x-www-form-urlencoded (默认)
    • 参数格式:foo=bar&baz=The+first+line.
  • 使用 POST 方法,并设置 enctype 属性为 text/plain
    • foo=bar baz=The first line.
  • 使用 POST 方法,并设置 enctype 属性为 multipart/form-data
    • 会自动生成boundary值
  • 使用 GET 方法(这种情况下 enctype 属性会被忽略)
    • 下面这样的字符串将被简单的附加到 URL:?foo=bar&baz=The%20first%20line.%0AThe%20second%20line.

优化file input元素表现

file input元素原生样式表现十分丑陋,正常都不会使用它的原生样式,有没有什么办法优化呢?

  • 通过click()方法使用隐藏的file input元素
  • 使用label元素来触发一个隐藏的input元素对应的事件
    • 通过label的for属性绑定input元素id
    • 或者直接将input元素放置在label标签内

URL

直接代码说话吧

// 这是一个实验中的功能
var objectURL = window.URL.createObjectURL(fileObj);
// 用来释放一个之前通过调用 URL.createObjectURL() 创建的已经存在的 URL 对象。当你结束使用某个 URL 对象时,应该通过调用这个方法来让浏览器知道不再需要保持这个文件的引用了。
window.URL.revokeObjectURL(objectURL);

安全

JS为了安全,是严格限制js直接访问本地文件,所以不能实现创建或者读取本地文件,当然可能在IE可以通过ActiveObject对象直接操作,但其他浏览器不支持,所以会有兼容性问题。

因为如果JS可以获取客户端机器资源,那你的电脑何谈安全?任何一个网页都能发现你机器上的隐私了。所以JS是不能访问本地资源的。

因此通过文件路径是无法创建File或Blob对象的。File只能通过上述章节的几种方式得到。

手机环境

最近手机需要做批量离线上传,我只能得到文件在手机上的路径,有没有办法转成File对象,然后使用FormData对象进行ajax上传呢?答案是肯定的,手机端我们是拥有文件操作能力的,只不过不是通过JS,而是底层代码,针对Cordova应用,可以使用File插件。具体实现和细节如下:

function uploadPics() {
    var promises = [];
    var fd = new FormData();
    images.forEach(function(i) {
        var def = $q.defer();
        // 解析本地文件系统,参数路径
        window.resolveLocalFileSystemURL(i, function(fileEntry) {
            fileEntry.file(function(file) {
                console.dir(file);
                // 此时的File并不是JS中的File对象,通过file.toString()可以得到,我们需要进行读取转换
                var reader = new FileReader();
                reader.onloadend = function(e) {
                    var imgBlob = new Blob([this.result], { type:file.type});
                    // 第三个可缺省参数对于某些服务端实现很重要
                    fd.append('file'+(images.indexOf(i)+1), imgBlob, file.name);
                    def.resolve();
                };
                reader.readAsArrayBuffer(file);
            }, function(e) {
                console.log('error getting file', e);
            });            
        }, function(e) {
            console.log('Error resolving fs url', e);
        });
        defs.push(def.promise);
    });

    Promise.all(promises).then(function() {
        var request = new XMLHttpRequest();
        // 实际情况肯定需要文件对象外的其他参数,可以继续使用fd.append()
        request.open('POST', '/upload');
        request.send(fd);
    });
}

资料



留言