前言
在上一篇文章 由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 Aloadx File one of the pjalugins one of the pupload 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"] 類型的表單不可以嗎?
使用FormData來提交不帶有 input[type="file"] 類型的表單但是使用編碼類型為 x-www-form-urlencoded 又會怎麼樣呢?
那麼我們使用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 :
{ '------WebKitFormBoundary5Kj2oSfHZKrZjjjsx; ': '"user"rnrnddrn------WebKitFormBoundary5Kj2oSfHZKrZjYjsrnContent-Disposition: form-data; name="email"rnrndddrn------WebKitFormBoundary5Kj2oSfHZKrZjrndddrn------WebKitFormBoundary5Kj2oSfHZKrZjrndddrn------WebKitFormBoundary5Kj2oSfH伺服器如何解析呢? ? ?這純粹給自己添堵。
如果不使用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) data選項傳遞進來的數據,如果是一個物件(技術上講只要不是字串),都會處理轉換成一個查詢字串,以配合預設內容類型"application/x-www-form-urlencoded"。如果要傳送 DOM 樹資訊或其它不希望轉換的訊息,請設定為 false。
1.3、FormData的API
FormData.append(): 追加一個新的值到已存在的key中或新增一個新的key;
FormData.delete():刪除一個鍵值對FormData.entries(): 傳回一個迭代器以便可以遍歷物件裡面的鍵值對
FormData.keys():傳回一個迭代器以允許遍歷所有鍵值對的鍵FormData.set( ): 修改一個已存在的key中的值或增加新的鍵值對
FormData.values(): 傳回一個迭代器以允許遍歷所有鍵值對的值 2. 關於input[type= "file"]
關於這個類型的input有幾點還是需要提一下,要不下次我自己又忘掉了:
使用multiple 屬性可以一次性選擇多個文件,使用accept屬性可以執行對應的MIME類型。
$(element).files得到的是一個file數組,你可以取得上傳檔案的名字以及上傳檔案的個數: .name 和 length 。
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之後,就需要自己編碼很多類型的表單安裝的了。這類的package很多,我自己選擇用了 multiparty 這個middleware。具體的使用方法可以參考官網:https://github.com/expressjs/node-multiparty。
使用事件監聽的形式
使用回調形式
. files中的欄位是根據你在表單中提供的name 的形式來組織的,以第一小節的前端程式碼來說,那麼此時的結果應該是:
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!'); }); });