在[發送表單數據]()一文中,HTML表單能夠聲明式地發送一個HTTP請求。但表單也能夠用JavaScript來準備一個HTTP請求。本文將探索如何作到這點。前端
隨着開放式Web應用的出現,如今提供HTML表單、而不是文字表單供用戶填寫的作法,已經愈加廣泛了。而愈來愈多的開發者也得到了數據傳輸的控制權。html5
標準的HTML表單提交操做會加載數據發送到的URL,這就意味着瀏覽器的窗口中會進行整個頁面的從新加載。而若是避免了頁面的從新加載,就會避免頁面的閃爍和網絡延遲,進而提供更順暢的用戶體驗。node
在許多現代的UI設計中,HTML表單只是用來收集用戶的輸入。當用戶要發送數據時,Web應用會進行控制,並在後臺異步地發送數據,只更新UI中須要更改的部分。git
能異步地發送任意數據的技術稱爲AJAX,表示「異步的JavaScript和XML」。github
AJAX使用了XMLHttpRequest(XHR)DOM對象,它能夠創建HTTP請求、發送請求並處理結果。web
注意: 老的AJAX技術可能不是用XMLHttpRequest。好比JSONP和eval()函數結合起來使用。雖然該方法可行,但不推薦使用它,由於其存在嚴重的安全問題。因此除非爲了兼容那些特別老舊、不支持XMLHttpRequest或JSON的瀏覽器,仍是避免使用該技術。segmentfault
因爲歷史緣由,XMLHttpRequest本是設計用來獲取和發送交換格式爲XML的數據的。但如今JSON取代了XML,有着更廣泛的使用。後端
不過XML和JSON都不符合做爲表單數據請求的編碼。表單數據(application/x-www-form-urlencoded
)是用於構造鍵值對的URL編碼列表的,如果要傳輸二進制數據,HTTP請求會被重塑爲 multipart/form-data
。
若你能掌控前端(運行在瀏覽器上的代碼)和後端(運行在服務器上的代碼),你就能發送JSON或XML、併爲所欲爲地處理它們。
但若是你使用的是第三方服務,這就沒那麼容易了,由於某些服務只接受表單數據。固然也有使用表單數據處理起來更方便的狀況,好比數據是鍵值對或二進制數據時,用現成的後端工具就能處理它們、不須要額外的代碼。
那麼,具體該如何發送數據呢?
目前有三種方式來發送表單數據,既有老舊的技術、也有新特性FormData對象,接下來就來深刻了解下它們。
發送表單數據最古老的方法,是用DOM API創建一個表單,而後發送數據到一個隱藏的<iframe>
。爲了訪問你提交內容的處理結果,應檢索下<iframe>
的內容。
警告:避免使用該技術。在使用第三方服務時,該技術有安全風險,由於它會致使你面臨腳本注入攻擊。若你使用HTTPS,使用
<ifrme>
發送表單還會影響同源策略,並致使內容被髮送到一個沒法訪問的<ifrme>
中。固然若是你要兼容很老舊的瀏覽器,這項技術可能就是你惟一的選擇了。
下面是一個例子:
<button onclick="sendData({test:'ok'})">Click Me!</button>
// 建立一個iFrame來發送咱們的數據 var iframe = document.createElement("iframe"); iframe.name = "myTarget"; // 將iFrame添加到文檔流中 window.addEventListener("load", function () { iframe.style.display = "none"; document.body.appendChild(iframe); }); // 用來發送數據的函數 // 須要一個參數,是一個由鍵值對組成的對象 function sendData(data) { var name, form = document.createElement("form"), node = document.createElement("input"); // 定義響應加載時的行爲 iframe.addEventListener("load", function () { alert("Yeah! Data sent."); }); form.action = "http://www.cs.tut.fi/cgi-bin/run/~jkorpela/echo.cgi"; form.target = iframe.name; for(name in data) { node.name = name; node.value = data[name].toString(); form.appendChild(node.cloneNode()); } // 要發送數據,表單得添加到文檔流中 form.style.display = "none"; document.body.appendChild(form); form.submit(); // 表單一發送就移除它 document.body.removeChild(form); }
結果以下:
效果
XMLHttpRequest是發送HTTP請求最安全和可靠的方式。要想用XMLHttpRequest發送表單數據,得先用URL編碼要發送的數據,並遵循表單數據請求的規範。
注意: 若想了解更多關於XMLHttpRequest,這幾篇文章可能對你有用:An introductory article to AJAX,以及一個關於使用XMLHttpRequest的高級教程。
來重構下咱們先前的例子:
<button type="button" onclick="sendData({test:'ok'})">Click Me!</button>
如你所見,HTML部分並未真的有所改變,但JavaScript部分就徹底不一樣了:
function sendData(data) { var XHR = new XMLHttpRequest(); var urlEncodedData = ""; var urlEncodedDataPairs = []; var name; // 將data對象轉爲一個URL編碼的鍵值對數組 for(name in data) { urlEncodedDataPairs.push(encodeURIComponent(name) + '=' + encodeURIComponent(data[name])); } // 將鍵值對組合成一個字符串,並把全部通過URL編碼的空格替換爲'+'號;以符合瀏覽器表單提交的行爲 // the '+' character; matches the behaviour of browser form submissions. urlEncodedData = urlEncodedDataPairs.join('&').replace(/%20/g, '+'); // 定義成功的數據提交後會發生什麼 XHR.addEventListener('load', function(event) { alert('Yeah! Data sent and response loaded.'); }); // 定義失敗的狀況會發生什麼 XHR.addEventListener('error', function(event) { alert('Oups! Something goes wrong.'); }); // 配置請求 XHR.open('POST', 'https://example.com/cors.php'); // 添加表單數據POST請求所需的HTTP請求頭 XHR.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); // 最後,發送數據 XHR.send(urlEncodedData); }
結果以下:
效果
注意: 若你想使用此處的XMLHttpRequest方法發送數據到第三方網站,也會受到同源策略的限制。要實現跨域請求,還須要CORS和HTTP訪問控制。
手動構建HTTP請求是挺麻煩的,幸運的是,最近的一項XMLHttpRequest標準提供了一個處理表單請求的簡便方法--FormData對象。
FormData對象可被用來創建要傳輸的表單數據,或者從表單元素中獲取數據來管理如何發送。要注意FormData對象是「只寫」的,這意味着你能夠改變它們、但不能訪問它們的內容。
使用FormData對象的方法會在[使用FormData對象]()一文中詳細介紹,下面有兩個例子:
<button type="button" onclick="sendData({test:'ok'})">Click Me!</button>
如今你該很熟悉這個HTML的樣子了。
function sendData(data) { var XHR = new XMLHttpRequest(); var FD = new FormData(); // 將數據添加到FormData對象中 for(name in data) { FD.append(name, data[name]); } // 定義成功的數據提交後會發生什麼 XHR.addEventListener('load', function(event) { alert('Yeah! Data sent and response loaded.'); }); // 定義失敗的狀況會發生什麼 XHR.addEventListener('error', function(event) { alert('Oups! Something went wrong.'); }); // 配置請求 XHR.open('POST', 'https://example.com/cors.php'); // 發送FormData對象,HTTP頭會自動設置 XHR.send(FD); }
結果以下:
效果
你也能夠將FormData對象綁定到一個表單元素上,這樣作會建立一個FormData對象來表示表單中的數據。
HTML是典型的表單:
<form id="myForm"> <label for="myName">Send me your name:</label> <input id="myName" name="name" value="John"> <input type="submit" value="Send Me!"> </form>
但JavaScript會接管表單的提交操做:
window.addEventListener("load", function () { function sendData() { var XHR = new XMLHttpRequest(); // 綁定FormData對象和表單元素 var FD = new FormData(form); // 定義成功的數據提交後會發生什麼 XHR.addEventListener("load", function(event) { alert(event.target.responseText); }); // 定義失敗的狀況會發生什麼 XHR.addEventListener("error", function(event) { alert('Oups! Something goes wrong.'); }); // 配置請求 XHR.open("POST", "https://example.com/cors.php"); // 發送的數據是用戶在表單中提供的 XHR.send(FD); } // 訪問表單元素 var form = document.getElementById("myForm"); // 並接管其submit事件 form.addEventListener("submit", function (event) { event.preventDefault(); sendData(); }); });
結果以下:
效果
若你在一個含有<input type="file">
組件的表單中使用FormData對象,那麼數據會被自動處理。但要手動發送二進制數據的話,還有不少額外工做要作。
現代Web有不少二進制數據源:好比FileReader、Canvas、WebRTC。但不幸的是,某些老舊瀏覽器不能訪問二進制數據或者須要複雜的方法才能實現。這些遺留的問題已不在本文討論範圍以內。若你想了解更多關於FileReader API,請閱讀Using files from web applications。
用FormData來發送二進制是很直接的,使用append()
方法就行了。但要手動作到這點,就須要一些技巧了。
在下面的例子中,咱們會用來FileReader API來訪問二進制數據,而後手動創建多部分的表單數據請求。
<form id="myForm"> <p> <label for="i1">text data:</label> <input id="i1" name="myText" value="Some text data"> </p> <p> <label for="i2">file data:</label> <input id="i2" name="myFile" type="file"> </p> <button>Send Me!</button> </form>
如你所見,HTML用了標準的<form>
,這沒什麼神奇的。「神奇」的部分在JavaScript裏:
// 因爲咱們要訪問DOM結點,因此得在頁面加載完後才初始化腳本 window.addEventListener('load', function () { // 這些變量用來存儲表單數據 var text = document.getElementById("i1"); var file = { dom : document.getElementById("i2"), binary : null }; // 使用FileReader API來訪問文件內容 var reader = new FileReader(); // 因爲FileReader是異步的,因此得在其完成文件讀取後才存儲結果 reader.addEventListener("load", function () { file.binary = reader.result; }); // 在頁面加載時,若已經選擇了文件就直接讀取它 if(file.dom.files[0]) { reader.readAsBinaryString(file.dom.files[0]); } // 不然在用戶選擇文件時再讀取它 file.dom.addEventListener("change", function () { if(reader.readyState === FileReader.LOADING) { reader.abort(); } reader.readAsBinaryString(file.dom.files[0]); }); // sendData是本例的主要函數 function sendData() { // 若已經選擇了文件,就等瀏覽器讀取完 // 不然就延遲本函數的執行 if(!file.binary && file.dom.files.length > 0) { setTimeout(sendData, 10); return; } // 要構建多部分的表單數據請求,須要一個XMLHttpRequest實例 var XHR = new XMLHttpRequest(); // 須要一個分隔符來定義請求體的每部分 var boundary = "blob"; // 將請求體存爲一個字符串 var data = ""; // 若用戶選擇了文件 if (file.dom.files[0]) { // 開啓請求體的新部分 data += "--" + boundary + "\r\n"; // 該部分描述爲表單數據 data += 'content-disposition: form-data; ' // 定義表單數據的名字 + 'name="' + file.dom.name + '"; ' // 提供真實的文件名 + 'filename="' + file.dom.files[0].name + '"\r\n'; // 提供文件的MIME類型 data += 'Content-Type: ' + file.dom.files[0].type + '\r\n'; // 元數據和真實數據部分間有一個空行 data += '\r\n'; // 往請求體裏添加二進制數據 data += file.binary + '\r\n'; } // 文本數據的組織更加簡單 // 開啓請求體的新部分 data += "--" + boundary + "\r\n"; // 描述爲表單數據並命名 data += 'content-disposition: form-data; name="' + text.name + '"\r\n'; // 元數據和真實數據部分間有一個空行 data += '\r\n'; // 往請求體裏添加文本數據 data += text.value + "\r\n"; // 完成了全部部分,就「閉合」請求體 data += "--" + boundary + "--"; // 定義成功的數據提交後會發生什麼 XHR.addEventListener('load', function(event) { alert('Yeah! Data sent and response loaded.'); }); // 定義失敗的狀況會發生什麼 XHR.addEventListener('error', function(event) { alert('Oups! Something went wrong.'); }); // 配置請求 XHR.open('POST', 'https://example.com/cors.php'); // 添加必要的HTTP請求頭來處理多部分表單數據的POST請求 XHR.setRequestHeader('Content-Type','multipart/form-data; boundary=' + boundary); // 最後,發送數據 XHR.send(data); } // 訪問表單 var form = document.getElementById("myForm"); // 接管submit事件 form.addEventListener('submit', function (event) { event.preventDefault(); sendData(); }); });
結果以下:
效果
瀏覽器的不一樣,致使經過JavaScript發送表單數據能夠簡單或很困難。FromData對象是一般的解決方案,並且咱們應該堅決果斷地在老舊瀏覽器上使用其polyfill:
這個gist用Web Workers來提供FromData
HTML5-formdata致力於提供FromData對象的polyfill,但這得依賴File API
這個polyfill提供了FromData的大部分新方法(entries, keys, values和對for...of的支持)