在上一篇文章 由form表單來說說前后臺數據之間的交互 講解了一些瀏覽器和服務器在表單之間的聯系,貌似感覺已經是掌握了form表單,但現實是殘酷的,在最近的一個項目中才發現form表單還有一個大塊知識,在上篇文章只是點了一下的。這塊內容用的地方還蠻多的,那就是文件上傳。
1、FormData
上篇文章 中提到組織表單的方法是使用jquery的 serializeArray 函數, 但是這個方法對于 input[type="file"] 是無效的 ,也就是說它無法將文件的內容整合到form表單中去,相關問題可以參考jquery的官方issue:https://bugs.jquery.com/ticket/2656。 其中的解釋是:
The serialize method can't get the contents of the file so I don't understand why it would need to serialize the file form field. You'd have to use one of the Ajax File upload plugins to get the contents via AJAX.
那么我們是否可以不使用插件來獲取提交的表單呢?of course!
1.1、FormData的作用
這就是我們要介紹的 FormData 。根據 FormData 的MDN解釋:
FormData接口提供了一種簡單的方式去構建鍵值對來表示它們的字段和值,并且可以很容易地通過`XMLHttpRequest.send()`發送給服務器。它使用了和表單的編碼類型設置為`multipart/form-data`一樣的格式。
FormData對象可以使用`for...of`的形式來遍歷而不是使用`entries()`:`for (var p of myFormData)`等價于` for (var p of myFormData.entries())`
這樣的解釋讓我至少明白了兩點:
FormData可以用來處理帶有 multipart/form-data 編碼類型的表單,一般都是帶有 input[type="file"] 的表單;
FormData里面字段的檢查可以通過 for...in 來檢查。( 這個對象在瀏覽器中用console.log是打印不出來的 )
那么就有童鞋肯定會問:
使用FormData來提交不帶有 input[type="file"] 類型的表單不可以嗎?
使用FormData來提交不帶有 input[type="file"] 類型的表單但是使用編碼類型為 x-www-form-urlencoded 又會怎么樣呢?
如果我不使用FormData的話就不能提交帶有 input[type="file"] 的表單了嗎?
那么我們使用demo來解釋這兩個問題:
可以的,此時編碼類型是 multipart/form-data ,也即是表單的提交方式大致會是這樣的:
我們可以看到這種編碼類型的表單與眾不同的。服務器端如果使用使用express4以上的版本的話需要安裝額外的middleware來處理這類型的請求,否則你會在 req.body 、 req.param 、 req.query 中沒有發現任何你的表單數據。這些后面會講。那么為什么我們依然不提倡使用這種方法來提交那些簡單的表單(大部分網站都是如此):
你肯定發現了我們提交的表單就是簡簡單單幾個字符,但是加上那些boundary之后造成表單數據變大了,也就是說即使是最有效率的二進制編碼也比我們直接將表單數據寫到MIME頭部花的時間來得長!
Tips:但是 x-www-form-urlencoded 處理那些不是字母數字的時候便顯得有些吃力了,因為瀏覽器都會將那些非字母數字的轉譯為 %HH ,也就是說每一個非字母數字都會由3個字節來替換,這對于表單很長的時候便很不友好了,于是才有了 multipart/form-data 的出現。
回答第二個問題,如果是那樣的話,那么我們在服務端(express4)中就可以看到 req.body :
{ '------WebKitFormBoundary5Kj2oSfHZKrZjYjs\r\nContent-Disposition: form-data; name': '"user"\r\n\r\ndd\r\n------WebKitFormBoundary5Kj2oSfHZKrZjYjs\r\nContent-Disposition: form-data; name="email"\r\n\r\nddd\r\n------WebKitFormBoundary5Kj2oSfHZKrZjYjs--\r\n' }
看吧,這樣你讓服務器如何解析呢???這純粹給自己添堵。
如果不使用FormData的haunted也是可以提交的,可以使用純AJAX來實現,具體細節參考:https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Using XMLHttpRequest#Submitting forms and uploading_files
1.2、結論
綜上所述,當我們在使用表單的時候含有 input[type="file"] 或者含有很多非字母數字的時候,我們需要使用FormData來提交表單,并且編碼類型必須是 multipart/form-data 。那么大致使用的范例是:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>testing form group</title> <script type="text/JavaScript" src="http://cdn.bootcss.com/jquery/3.1.0/jquery.min.js"></script> <script type="text/JavaScript"> function onSubmit(){ var myForm = document.getElementById('form'); var formData = new FormData(myForm); for (var p of formData){ console.log(p); } $.ajax({ type: 'POST', url: '/get', data: formData, dataType: 'json', processData: false, contentType: false, success: function(data){console.log(data)}, error: function(jqXHR){console.log(jqXHR)}, }) } </script> </head> <body> <form action="" method="post" name='info' id='form'> <input type="text" name="user" /> <input type="text" name="email" /> <input type="file" name="file" /> <input type="file" name="file1" /> </form> <button type="button" name='submit' onclick="onSubmit()">提交</button> </body> </html>
注意
我們的$.ajax中配置了processData: false和contentType: false是為了阻止jquery進一步處理數據:
processData (默認: true)
類型: Boolean
默認情況下,通過data選項傳遞進來的數據,如果是一個對象(技術上講只要不是字符串),都會處理轉化成一個查詢字符串,以配合默認內容類型 "application/x-www-form-urlencoded"。如果要發送 DOM 樹信息或其它不希望轉換的信息,請設置為 false。
1.3、FormData的API
FormData.append(): 追加一個新的值到已存在的key中或者添加一個新的key;
FormData.delete(): 刪除一個鍵值對
FormData.entries(): 返回一個迭代器以便可以遍歷對象里面的鍵值對
FormData.get():返回第一個給定key的值
FormData.getAll():返回給定key的所有值的數組
FormData.has():判斷FormData對象是否有給定的鍵值對
FormData.keys():返回一個迭代器以允許遍歷所有鍵值對的鍵
FormData.set(): 修改一個已存在的key中的值或者添加新的鍵值對
FormData.values(): 返回一個迭代器以允許遍歷所有鍵值對的值
2. 關于input[type="file"]
關于這個類型的input有幾點還是需要提一下,要不下次我自己又忘掉了:
使用 multiple 屬性可以一次性選擇多個文件,使用accept屬性可以執行對應的 MIME類型 。
$(element).files得到的是一個file數組,你可以獲取上傳文件的名字以及上傳文件的個數: .name 和 length 。
說到上傳文件,有時候你可能需要去校驗文件名稱或者后綴,那么這時候的正則表達式便派上用場了,如果校驗一個格式為 xxx-vx.x.{json|yaml} (比如 bower-v0.1.json )的文件,會用到的正則表達式是:
var reg = /^\w+\-v\d+\.{1}\d+\.(json|yaml)$/i; /*Check if the user has not selected uploaded file*/ if ($(Element).val() === ''){ finalRes.delete('file'); } else { var fileCount = $(Element)[0].files.length; for (var i = 0; i < fileCount; i++) { if (reg.test($(Element)[0].files[i].name )){ console.log('match'); }else{ alert('上傳的文件名格式不正確'); return; } } }
那么去掉后綴的時候你可以使用 replace(/.(json|yaml)$/, '') 來去掉后綴名。 4. 清空表單的一個好的函數是:
function clearAllFields(){ $(':input','#project-info') .not(':button, :submit, :reset, :hidden') .val('') .removeAttr('checked') .removeAttr('selected'); }
注意
$(element)表示的是你上傳文件的input的標簽。
finalRes是你new FormData之后的表單值
3、Express服務器端的處理
從Express4.x之后,移出了很多的middleware之后,對于處理 multipart/form-data 編碼類型的表單需要自己安裝的了。這類的package很多,我自己選擇使用了 multiparty 這個middleware。具體的使用方法可以參考官網:https://github.com/expressjs/node-multiparty。
該middleware的使用方法有兩種:
使用事件監聽的形式
使用回調形式
我在項目中使用了回調的形式:
router.post('/get', function(req, res, next) { var form = new multiparty.Form(); form.parse(req, function(err, fields, files) { if (fields === undefined || files === undefined){ console.log('client send empty data in posting new project'); return res.status(200).json({err:"請求數據為空,請重新提交", status:'failure'}); } console.log(fields, files); console.log('Upload completed!'); }); });
其中的fields和files中的字段是根據你在表單中提供的 name 的形式來組織的,以第一小節的前端代碼來說,那么此時的結果應該是:
{ user: [ 'test' ], email: [ 'test1' ] } { file: [ { fieldName: 'file', originalFilename: 'test.html', path: '/home/private/test/QForTTWBipWSPSTpKsUGlRHE.html', headers: [Object], size: 876 } ], file1: [ { fieldName: 'file1', originalFilename: 'test1.html', path: '/home/private/test/aT5T2B_pkkxEVv5OUzjjCxIB.html', headers: [Object], size: 558 } ] }
這時默認文件已經被上傳到了默認的文件夾,根據官網的解釋,如果沒有在初始化的時候配置 uploadDir ,那么將會上傳到系統的 os.tmpdir() 。
至于事件類型的實現方式也是類似的,可以參考其官網給的demo。
聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。TEL:177 7030 7066 E-MAIL:11247931@qq.com