
类型 FileList 包含一组 File 对象。通常 FileList 对象可以从表单中的文件域(<input type=”file” .../>)中拿取。Blob 对象代表浏览器所能读取的一组原始二进制流。Blob 对象中,属性 size 表示流的大小。函数 slice() 可以将一个长的 Blob 对象分割成小块。File 对象继承自 Blob 对象,在 Blob 对象基础上增加了和 File 相关的属性。其中,属性 name 表示文件的名字,这个名字去掉了文件的路径信息,而只保留了文件名。属性 type 表示文件的 MIME 类型。属性 urn 则代表这个文件的 URN 信息。为完成文件读取的操作,一个 FileReader 对象实例会关联 File 或 Blob 对象,并提供三种不同的文件读取函数以及 6 种事件。
文件读取函数具体内容:
readAsBinaryString() 读取文件内容,读取结果为一个 binary string。文件每一个 byte 会被表示为一个 [0..255] 区间内的整数。函数接受一个 File 对象作为参数。
readAsText() 读取文件内容,读取结果为一串代表文件内容的文本。函数接受一个 File 对象以及文本编码名称作为参数。
readAsDataURL 读取文件内容,读取结果为一个 data: 的 URL。DataURL 由 RFC2397 定义。
文件读取事件具体内容:
事件名称 事件说明
Onloadstart 文件读取开始时触发。
Progress 当读取进行中时定时触发。事件参数中会含有已读取总数据量。
Abort 当读取被中止时触发。
Error 当读取出错时触发。
Load 当读取成功完成时触发。
Loadend 当读取完成时,无论成功或者失败都会触发。
三、File API 简单示例
接下来我们用一个简单的例子展示 File API 的基本用法。这个示例包含两个代码文件,index.html 包含 Web 端的 HTML 代码和处理上传的 JavaScript 代码;upload.jsp 包含服务器端接收上传文件的代码。请参见附件中的 sourcecode.zip。在这个例子中,我们将显示一个传统的带有 File 选择域的表单。当用户选择文件,点击提交后,我们使用 File API 读取文件内容,并通过 XMLHttpRequest 对象,用 Ajax 的方式将文件上传到服务器上。图 2 展示了运行中的演示截图。

我们逐步展示其中的代码。清单 1 给出了代码的 HTML 部分。
清单 1 示例代码的 HTML 部分
<body><h1>File API Demo</h1><p><!-- 用于文件上传的表单元素 --><form name="demoForm" id="demoForm" method="post" enctype="multipart/form-data"action="javascript: uploadAndSubmit();"><p>Upload File: <input type="file" name="file" /></p><p><input type="submit" value="Submit" /></p></form><div>Progessing (in Bytes): <span id="bytesRead"></span> / <span id="bytesTotal"></span></div></p></body>可以看到,我们用普通的 <form> 标签来包含一个传统的 <input type=”file” … /> 元素。在 <form> 中还有一个 submit 元素。在 <form> 之外有一些 <span> 元素用来表示已读取和总共的数据量。<form> 的 action 属性指向了一个 JavaScript 函数 uploadAndSubmit()。这个函数完成了读取文件并上传的过程。函数代码见清单 2。
function uploadAndSubmit() {var form = document.forms["demoForm"];if (form["file"].files.length > 0) {// 寻找表单域中的 <input type="file" ... /> 标签 var file = form["file"].files[0];// try sendingvar reader = new FileReader(); reader.onloadstart = function() {// 这个事件在读取开始时触发 console.log("onloadstart");document.getElementById("bytesTotal").textContent = file.size;}reader.onprogress = function(p) {// 这个事件在读取进行中定时触发 console.log("onprogress");document.getElementById("bytesRead").textContent = p.loaded;} reader.onload = function() { // 这个事件在读取成功结束后触发 console.log("load complete");} reader.onloadend = function() { // 这个事件在读取结束后,无论成功或者失败都会触发 if (reader.error) {console.log(reader.error);} else {document.getElementById("bytesRead").textContent = file.size;// 构造 XMLHttpRequest 对象,发送文件 Binary 数据 var xhr = new XMLHttpRequest();xhr.open(/* method */ "POST",/* target url */ "upload.jsp?fileName=" + file.name/*, async, default to true */);xhr.overrideMimeType("application/octet-stream");xhr.sendAsBinary(reader.result);xhr.onreadystatechange = function() {if (xhr.readyState == 4) {if (xhr.status == 200) {console.log("upload complete");console.log("response: " + xhr.responseText);}}}}} reader.readAsBinaryString(file);} else {alert ("Please choose a file.");}}在这个函数中,首先我们找到含有 <input type=”file” … /> 元素的 <form>,并找到含有上传文件信息的 <input> 元素。如 <input> 元素中不含有文件,说明用户没有选择任何文件,此时将报错。 var form = document.forms["demoForm"]; if (form["file"].files.length > 0){var file = form["file"].files[0]; … … }else{alert ("Please choose a file.");}这里,从 form[“file”].files 返回的对象类型即为提到的 FileList。我们从中拿取第一个元素。之后,我们构建 FileReader 对象: reader.onloadstart = function(){console.log("onloadstart");document.getElementById("bytesTotal").textContent = file.size;}在 onprogress 事件触发时,更新页面上已读取数据量的 <span> 元素。参见清单 5清单 5 onprogress 事件 reader.onprogress = function(p) {console.log("onloadstart");document.getElementById("bytesRead").textContent = p.loaded;}最后的 onloadend 事件中,如果没有错误,我们将读取文件内容,并通过 XMLHttpRequest 的方式上传。 reader.onloadend = function(){if (reader.error){console.log(reader.error);}else{// 构造 XMLHttpRequest 对象,发送文件 Binary 数据 var xhr = new XMLHttpRequest();xhr.open(/* method */ "POST",/* target url */ "upload.jsp?fileName=" + file.name/*, async, default to true */);xhr.overrideMimeType("application/octet-stream");xhr.sendAsBinary(reader.result); … … }}按照 File API 的规范,我们也可以将事件 onloadend 的处理拆分为事件 error 以及事件 load 的处理。 <%@ page import="java.io.*" %><% BufferedInputStream fileIn = newBufferedInputStream(request.getInputStream()); String fn = request.getParameter("fileName"); byte[] buf = new byte[1024];//接收文件上传并保存到 d:File file = new File("d:/" + fn); BufferedOutputStream fileOut = new BufferedOutputStream(newFileOutputStream(file)); while (true) { // 读取数据 int bytesIn = fileIn.read(buf, 0, 1024); System.out.println(bytesIn); if (bytesIn == -1){break;}else{fileOut.write(buf, 0, bytesIn);} } fileOut.flush(); fileOut.close(); out.print(file.getAbsolutePath());%>在这段 JSP 代码中,我们从 POST 请求中接受文件名字以及二进制数据。将二进制数据写入到服务器的“D:”路径中。并返回文件的完整路径。以上代码可以在最新的 Firefox 3.6 中调试通过。<div id="container"><span>Drag and drop files here to upload.</span><ul id="fileList"></ul></div>

拖拽目标创建好之后,我们需要侦听其对应的事件 dragenter,dragover 和 drop。在 dragenter 事件处理函数里,我们只是简单地清除文件列表,然后取消 dragenter 事件的传播,表示我们接收该事件。更加妥当的作法是判断 DataTransfer 里的数据是否为文件,这里我们假设所有拖拽源都是文件。dragover 事件里我们取消该事件,使用默认的拖拽显示效果。在 drop 事件里我们注册了 handleDrop 事件处理函数来获取文件信息并上传文件。清单 9 展示了这些事件处理函数。
清单 9 设置事件处理函数
function addDNDListeners(){var container = document.getElementById("container");var fileList = document.getElementById("fileList");// 拖拽进入目标对象时触发 container.addEventListener("dragenter", function(event){fileList.innerHTML ="";event.stopPropagation();event.preventDefault();}, false);// 拖拽在目标对象上时触发 container.addEventListener("dragover", function(event){event.stopPropagation();event.preventDefault();}, false);// 拖拽结束时触发 container.addEventListener("drop", handleDrop, false);}window.addEventListener("load", addDNDListeners, false);处理 drop 事件 function handleDrop(event){ // 获取拖拽的文件列表 var files = event.dataTransfer.files;event.stopPropagation();event.preventDefault();var fileList = document.getElementById("fileList");// 展示文件缩略图,文件名和上传进度,上传文件 for (var i = 0; i < files.length; i++){var file = files[i];var li = document.createElement("li");var progressbar = document.createElement("div");var img = document.createElement("img");var name = document.createElement("span");progressbar.className = "progressBar";img.src = files[i].getAsDataURL();img.width = 32;img.height = 32;name.innerHTML = file.name;li.appendChild(img);li.appendChild(name);li.appendChild(progressbar);fileList.appendChild(li);uploadFile(file, progressbar)}}上传文件 function uploadFile(file, progressbar){var xhr = new XMLHttpRequest();var upload = xhr.upload; var p = document.createElement("p");p.textContent = "0%";progressbar.appendChild(p);upload.progressbar = progressbar;// 设置上传文件相关的事件处理函数 upload.addEventListener("progress", uploadProgress, false);upload.addEventListener("load", uploadSucceed, false);upload.addEventListener("error", uploadError, false);// 上传文件 xhr.open("POST", "upload.jsp?fileName="+file.name);xhr.overrideMimeType("application/octet-stream");xhr.sendAsBinary(file.getAsBinary());}function uploadProgress(event){if (event.lengthComputable){ // 将进度换算成百分比 var percentage = Math.round((event.loaded * 100) / event.total);console.log("percentage:" + percentage);if (percentage < 100){event.target.progressbar.firstChild.style.width = (percentage*2) + "px";event.target.progressbar.firstChild.textContent = percentage + "%";}}}function uploadSucceed(event){event.target.progressbar.firstChild.style.width = "200px";event.target.progressbar.firstChild.textContent = "100%";}function uploadError(error){alert("error: " + error);}
本文通过对 File API 规范的讲解,以及两个展示其使用方法的例子,为大家提前揭示了作为未来 HTML5 重要组成部分的 JavaScript File API 的全貌。利用它,结合其他 HTML5 的新特性,比如 Drag&Drop,我们可以利用纯 JavaScript 方案,为用户提供更好使用体验的 Web 应用,与此同时,这样的一致化方案也使我们避免了以往跨浏览器支持所花费的巨大代价。相信 File API 的出现和广泛应用,将会是未来的 Web 2.0 应用的大势所趋。