其实很早就开始酝酿这一篇了,无奈老是发现有缺漏的地方,遂努力恶补前端+后端+底层相关知识。今天终于能够发表了。 html
--跟生孩子同样啊。前端
谈到文件上传,不得不提 form,中文名叫表单。它能够包含一个用来选择文件的东东,叫作 file。jquery
<form name="uploadForm" action="/upload" method="post" enctype="multipart/form-data"> file:<input type="file" name="anyname"/> </form>
action 表示表单的数据发送的目标地址,method 表示发送表单所使用的 http 方法(get / post),enctype表示数据的编码方式,对于文件上传,必须为 multipart/form-data。ajax
具体的定义参见 form。chrome
下面是对应的页面,能够看到,有一个提示选择文件的按钮json
点击按钮,就能够选择文件啦。后端
小贴士:文件选择好以后,能够经过 FileReader 进行预览,或者简单的编辑。api
简单的上传,只须要提交对应的 form 就能够了。是否是很简单,O(∩_∩)O哈哈哈~。浏览器
上面介绍的都太简单粗暴肤浅了,实际项目中老板,客户100%会投反对票。由于实在是太简陋了。服务器
浏览器提供的原生控件实在是丑的不忍心看,能够本身画一个好看的按钮。
.chooseFile{ min-width: 30px; min-height: 15px; width: 106px; height: 29px; background-color: #B6E2C9; color: black; font-family: monospace; font-weight: 400; border-color: white; border-radius: 17px; padding: 5px; text-align: center; vertical-align: middle; cursor: pointer; }
记得把原来的form隐藏掉。
接下来你须要作的是给这个按钮绑定 click listener ,当它被点击时,触发 form 中的 file 的 click 事件。
有些时候,但愿上传时不刷新当前页面。可是使用 form 是避免不了页面刷新的。怎么办?
第一个想出这个办法的确定是个头脑灵活的家伙--使用隐藏的 iframe 上传。
原理是,在当前页面(父页面)中添加 iframe,iframe 的页面(子页面)中包含 form 和相关的函数(验证,预处理等等)。当用户在父页面点击选择文件的按钮时,去触发子页面中 file 控件的 click 事件。
当用户提交时,提交子页面中的 form。这时,子页面跳转,而父页面没有刷新。
这个方案有个缺点,就是须要先后端协同工做。
当须要使用回调函数来处理上传完成后后端返回的数据时,须要和后端预先达成约定,如,回调函数名,参数列表,等等。这对先后端彻底分离的开发场景(好比,你只是开发前端UI)是一个挑战。(出现全栈工程师的缘由,是否是就是由于前端工程师想把这些依赖可是却又没法彻底控制的工做给抢过来?)
好比,父页面须定义回调函数
function uploadSuccess (result){ ... }
后端须对action(上面form中定义的/upload)返回html,html包含对回调函数的调用,以及制定参数。
<html> ... <script> window.uploadSuccess('xxxxxx'); </sript> ... </html>
固然,若是你是全栈工程师,这都不算事。本身一我的搞,还约定个啥。
你说文件上传这么常见的功能,咋就不用直接用 js 搞定呢? 非要牵扯什么 form,什么 iframe,烦?
客官,看来你须要的是 FormData。
FormData 容许经过 js 构造 form ,而后经过 ajax 方式上传。为了方便,这里使用 jquery 的 ajax。
var data = new FormData(); data.append('file', fileObj); $.ajax({ url: '/upload', type: 'POST', data: data, cache: false, dataType: 'json', processData: false, // Don't process the files contentType: false, // Set content type to false as jQuery will tell the server its a query string request success: function(data, textStatus, jqXHR) { console.log(JSON.stringify(data, null, 4)); }, error: function(jqXHR, textStatus, errorThrown) { //jqXHR may have no responseJSON in old jquery console.log(JSON.stringify(jqXHR.responseJSON, null, 4)); } });
须要注意的是,processData 必须指定为false,不然,jquery 会尝试格式化formData,这会引发一些错误。
一些低版本的浏览器可能对 FormData 没有提供支持,因此实际项目中要谨慎使用哦。
有时候,咱们须要对文件进行譬如大小,类型(经过扩展名),名称的验证,只有符合预期的才容许上传。
前端获取这三个属性很是简单。
var file = uploadForm.anyname.files[0]; console.log(file.name); console.log(file.size); console.log(file.type);
更详细的介绍 file api
相对前端来讲,因为涉及到 http 报文的细节,因此稍微复杂一点(意思就是说,我讲的颇有多是片面的,错误的)。
http 报文,也就是你从浏览器的 network 调试窗口看到的 request 信息,它主要包括 header 和 body 两部分。header 中包含 content-length,也就是发送数据的长度,通常能够依次做为对文件大小的判断。若是后端检测到它大于预设的最大限制,则返回错误给前端。
http 的 body 部分会为上传文件的数据的开始和结尾插入边界,例如,chrome
------WebKitFormBoundarycKtZKQMmA6QfpeMW Content-Disposition: form-data; name="file"; filename="bt.jpg" Content-Type: image/jpeg ------WebKitFormBoundarycKtZKQMmA6QfpeMW--
而且,在文件内容以前,是文件的元数据,例如名词,类型,还有大小。
后端能够根据边界的检验,识别上传的文件,读取元数据中的文件属性,从而为验证提供数据。
有不少文件上传框架会将文件写入临时文件夹后,再作验证。实际上是很是没有必要的。彻底能够在 http 数据开头的一部分(数据并非一块儿传送,而是相似于流的方式)抵达服务器时就完成验证,从而尽早的返回错误,避免没必要要的数据操做(所谓优化--能不作,尽可能不作。)。
也许叫xxx大全会好一点,不过本人孤傲的不肯意拾人牙慧,只要叫作 二三事 了。所谓 二三,实际上是一堆事。有叙述,有感叹,有建议。固然,也有吐槽。
除了 file 表单,file对象还能够从拖拽事件中获取。
e.dataTransfer.files
http body中,上传文件的边界能够由程序指定
var boundary = 'fdfrefdrerefdfd'; xhr.setRequestHeader("Content-Type", "multipart/form-data, boundary="+boundary); // simulate a file MIME POST request. xhr.setRequestHeader("Content-Length", fileSize); var body = ''; body += "--" + boundary + "\r\n"; body += "Content-Disposition: form-data; name=\""+dropbox.getAttribute('name')+"\"; filename=\"" + fileName + "\"\r\n"; body += "Content-Type: "+fileType+"\r\n\r\n"; body += fileData + "\r\n"; body += "--" + boundary + "--\r\n"; xhr.sendAsBinary(body);