基本介紹
XmlHttpRequest
XmlHttpRequest是JavaScript中原生的,歷史悠久的一種傳送網路請求的方案。
基本上所有前端框架對於網路請求的部分都是基於它來完成的。
在本章節中我們將瞭解它的基本用法,並且會實現一個與jQuery.ajax功能百分之九十相似的網路請求元件。
我是沒有讀過jQuery.ajax原始碼的,並且我也不是一個專精前端的程式設計師,所以後面的程式碼寫的可能不太好,僅和大家在此做個交流。
後端程式碼
為了方便後續程式碼測試,我們後端採用Python-flask框架來完成。
- IP:localhost
- PORT:5700
程式碼如下:
from logging import debug, error
from flask import Flask
from flask import request
from flask import make_response
from flask import jsonify
app = Flask(__name__)
@app.after_request # 解決CORS跨域請求
def cors(response):
response.headers['Access-Control-Allow-Origin'] = "*"
if request.method == "OPTIONS":
# 允許的請求頭
response.headers["Access-Control-Allow-Headers"] = "Origin,Content-Type,Cookie,Accept,Token,authorization,user_head"
return response
@app.route("/get", methods=["get"])
def get():
user_head = request.headers.get("user_head")
user_params = request.args
print(user_params)
return jsonify(user_params, user_head)
@app.route("/post", methods=["post"])
def post():
user_head = request.headers.get("user_head")
user_params = request.form
print(user_params)
return jsonify(user_params, user_head)
@app.route("/json", methods=["post"])
def json():
user_head = request.headers.get("user_head")
user_params = request.json
return jsonify(user_params, user_head)
@app.route("/file", methods=["post"])
def file():
file_obj = request.files.get("avatar")
if file_obj is None:
return make_response(jsonify("unload avatar"), 400)
file_name = file_obj.filename
file_obj.save(file_name)
return jsonify("upload avatar success!!")
if __name__ == "__main__":
app.run(host="localhost", port=5700, debug=True)
open()
open()方法用於建立並返回一個請求物件(單純建立,並不傳送)。
引數釋義:
- method:請求方式
- url:請求地址
- async:是否非同步請求
注意:如果method為GET方式,則需要我們手動在url中新增引數
請求相關
send()
send()方法用於傳送一次網路請求。
引數釋義:
- body:method為POST時攜帶的請求體資料,必須是字串型別
注意:如果method為GET方式,則直接xhr.send(null)即可,因為GET請求沒有請求體
setRequestHeader()
setRequestHeader()方法用於設定請求頭。
引數釋義:
- header:請求頭的key
- value:請求頭的value
注意:每次只能設定一組請求頭鍵值對,如果要設定多組請求頭鍵值對請多次呼叫該方法
abort()
abort()方法用於取消本次網路請求。
響應相關
getAllResponseHeaders()
getAllResponseHeaders()方法用於獲取所有響應頭資料。
返回的資料型別為字串。
getResponseHeader()
getResponseHeader()方法用於獲取響應頭中指定header的value。
返回的資料型別為字串。
status
status屬性用於返回服務端響應的HTTP響應狀態碼。
以下是常見的HTTP響應狀態碼:
- 200:請求成功
- 404:請求失敗,請求的資源錯誤
- 500:請求失敗,伺服器內部出現錯誤
- 302:重定向
- 304:快取優先
statesText
statesText屬性用於返回服務端響應的狀態文字。
如OK、NotFound等字樣。
responseText
responseText屬性用於返回響應體主體內容。
返回的資料型別為字串。
回撥函式
readyState
readyState屬性用於返回一個整數型別的狀態值,它表明當前xhr物件的請求狀態。
共有5種形態:
- 0:未初始化,尚未呼叫open()方法
- 1:已初始化,呼叫了open()方法,但還未呼叫send()方法
- 2:已傳送,呼叫了send()方法,但還未收到服務端響應
- 3:已接收,已經收到了部分服務端響應資料
- 4:已完成,已經收到了全部服務端響應資料
readystatechange
readystatechange事件用於設定xhr物件的readState狀態發生改變時所執行的回撥函式。
簡單使用
contentType說明
contentType是HTTP協議中的一個請求頭,它包含了瀏覽器告知後端伺服器本次傳送資料的格式。
它和<form>表單的enctype引數類似,共有以下一些常見值的選項:
常見的媒體格式型別如下:
- text/html : HTML格式
- text/plain :純文字格式
- text/xml : XML格式
- image/gif :gif圖片格式
- image/jpeg :jpg圖片格式
- image/png:png圖片格式
以application開頭的媒體格式型別:
- application/xhtml+xml :XHTML格式
- application/xml: XML資料格式
- application/atom+xml :Atom XML聚合格式
- application/json: JSON資料格式
- application/pdf:pdf格式
- application/msword : Word文件格式
- application/octet-stream : 二進位制流資料(如常見的檔案下載)
- application/x-www-form-urlencoded : <form enctype=“”>中預設的enctype,form表單資料被編碼為key/value格式傳送到伺服器(表單預設的提交資料的格式)
另外一種常見的媒體格式是上傳檔案之時使用的:
- multipart/form-data : 在表單中進行檔案上傳時,就需要使用該格式
我們接下來會使用2種最主流的格式進行試驗:
- application/x-www-form-urlencoded (預設的GET和POST資料格式)
- application/json(前端需要傳送的JSON格式資料)
GET請求
傳送GET請求:
"use strict";
let xhr = new XMLHttpRequest();
// 繫結回撥函式
xhr.addEventListener("readystatechange", () => {
switch (xhr.readyState) {
case 0:
console.log("未初始化,尚未呼叫open()方法");
break
case 1:
console.log("已初始化,呼叫了open()方法,但還未呼叫send()方法");
break
case 2:
console.log("已傳送,呼叫了send()方法,但還未收到服務端響應");
break
case 3:
console.log("已接收,已經收到了部分服務端響應資料");
break
default:
console.log("已完成,已經收到了全部服務端響應資料");
if (xhr.statusText === "OK") {
console.log("請求成功");
console.log(xhr.status);
console.log(xhr.statusText);
console.log(xhr.responseText);
} else {
console.log("請求失敗");
console.log(xhr.status);
console.log(xhr.statusText);
console.log(xhr.responseText);
}
}
})
// 請求方式,請求地址與引數,是否開啟非同步提交 提交的資料格式為url編碼
xhr.open("GET", "http://localhost:5700/get?username=Jack&userage=18", true);
// 設定請求頭,提交的資料格式為url編碼
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset-UTF-8');
xhr.setRequestHeader('user_head', 'HELLO WORLD');
// 傳送請求
xhr.send(null);
POST請求
傳送POST請求:
"use strict";
let xhr = new XMLHttpRequest();
// 繫結回撥函式
xhr.addEventListener("readystatechange", () => {
switch (xhr.readyState) {
case 0:
console.log("未初始化,尚未呼叫open()方法");
break
case 1:
console.log("已初始化,呼叫了open()方法,但還未呼叫send()方法");
break
case 2:
console.log("已傳送,呼叫了send()方法,但還未收到服務端響應");
break
case 3:
console.log("已接收,已經收到了部分服務端響應資料");
break
default:
console.log("已完成,已經收到了全部服務端響應資料");
if (xhr.statusText === "OK") {
console.log("請求成功");
console.log(xhr.status);
console.log(xhr.statusText);
console.log(xhr.responseText);
} else {
console.log("請求失敗");
console.log(xhr.status);
console.log(xhr.statusText);
console.log(xhr.responseText);
}
}
})
// 請求方式,請求地址與引數,是否開啟非同步提交
xhr.open("POST", "http://localhost:5700/post", true);
// 設定請求頭,提交的資料格式為url編碼
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset-UTF-8');
xhr.setRequestHeader('user_head', 'HELLO WORLD');
// 傳送請求,提交的資料格式為url編碼
xhr.send("username=Jack&userage=18");
JSON格式
JSON格式的資料只能由POST請求發起,並且contentType應設定為appliction/json。
示例如下:
"use strict";
let xhr = new XMLHttpRequest();
// 繫結回撥函式
xhr.addEventListener("readystatechange", () => {
switch (xhr.readyState) {
case 0:
console.log("未初始化,尚未呼叫open()方法");
break
case 1:
console.log("已初始化,呼叫了open()方法,但還未呼叫send()方法");
break
case 2:
console.log("已傳送,呼叫了send()方法,但還未收到服務端響應");
break
case 3:
console.log("已接收,已經收到了部分服務端響應資料");
break
default:
console.log("已完成,已經收到了全部服務端響應資料");
if (xhr.statusText === "OK") {
console.log("請求成功");
console.log(xhr.status);
console.log(xhr.statusText);
console.log(xhr.responseText);
} else {
console.log("請求失敗");
console.log(xhr.status);
console.log(xhr.statusText);
console.log(xhr.responseText);
}
}
})
// 請求方式,請求地址與引數,是否開啟非同步提交
xhr.open("POST", "http://localhost:5700/json", true);
// 設定請求頭
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.setRequestHeader('user_head', 'HELLO WORLD');
// 傳送請求
xhr.send(JSON.stringify({ "k1": "v1", "k2": "v2" }));
瞭解jQuery.ajax的原理
實現程式碼
原生的XmlHttpRequest所提供的方法太過於底層,因此我們可以對其進行封裝。
這裡以jQuery的Ajax為藍本,實現一個類似功能的小元件,也是為了方便後期更加了解jQuery.Ajax()做準備。
我們準備實現的功能:
- 實現傳送GET請求
- 實現傳送POST請求
- 實現更加方便的新增請求頭
- 實現傳送JSON格式的請求資料
- 實現自動反序列化JSON格式的響應資料
- 實現檔案上傳
實現如下:
"use strict";
class Ajax {
constructor({ url, contentType, headers, data, beforeSend, dataFilter, success, error, complete, dataType = "", processData = true, type, method = type, traditional = false }) {
// url:傳送請求的地址,String型別
// type:傳送請求的方式,String型別,它和method都可以使用
// method:傳送請求的方式,String型別,它和type都可以使用
// contentType:資料編碼格式,相當於form表單的enctype,String型別
// headers:設定的請求頭,Object型別
// dataType:接收的響應資料型別,如果是json則自動進行反序列化,String型別
// data:傳送的資料,post請求方式傳送在請求體內,get請求傳送會將其新增在url後,Object型別
// processData:如果為true,瀏覽器將採用application/x-www-form-urlencoded方式對資料進行編碼,如果為false則按照傳入的contentType為準,bool型別
// traditional:如果前端傳遞的是一個Array,如{"k1":[1,2,3,4]}則需要新增一個屬性traditional:true,否則後端將接收不到該引數。(實際上接受的時候要使用request.POST.get("k1[]"))來進行接受,這是有問題的
this.xhr = new XMLHttpRequest();
this.url = url;
this.type = (type || method).toUpperCase();
this.method = method.toUpperCase();
this.headers = headers;
this.contentType = processData && !contentType ? "application/x-www-form-urlencoded" : contentType;
this.data = data;
this.dataType = dataType.toLowerCase();
this.traditional = traditional;
// beforeSend 在傳送請求之前呼叫,並且傳入一個XMLHttpRequest作為引數。
// dataFilter 在請求成功之後呼叫。傳入返回的資料以及"dataType"引數的值。並且必須返回新的資料(可能是處理過的)傳遞給success回撥函式,也就是說它會在success與error之前呼叫
// success 在請求成功之後呼叫。傳入返回後的資料,以及包含成功程式碼的字串
// error 在請求出錯時呼叫。傳入XMLHttpRequest物件,描述錯誤型別的字串以及一個異常物件(如果有的話)
// complete 當請求完成之後呼叫這個函式,無論成功或失敗。傳入XMLHttpRequest物件,以及一個包含成功或錯誤程式碼的字串
this.beforeSend = beforeSend || function (xhr) { };
// 該函式預設會自動的根據dataType物件responseText進行解碼
this.dataFilter = dataFilter || function (responseText, dataType) {
switch (dataType) {
case "application/json": case "json":
// 新增,this.xhr.responseJSON屬性
this.xhr.responseJSON = JSON.parse(responseText);
return this.xhr.responseJSON;
default:
return responseText;
}
};
this.success = success || function (responseText, statusText) { };
this.error = error || function (responseText, statusText) { };
this.complete = complete || function (xhr, statusText) { };
// 繫結回撥函式
this.bindStateChangeEvent();
// 設定傳送的資料併傳送
this.setRequestData();
// 設定傳送方式
this.setXhrOpen();
// 設定請求頭
this.setRequestHeader();
// 傳送請求
this.sendRequest();
}
bindStateChangeEvent() {
let resultText = "";
let statusText = "";
// 繫結回撥函式,這裡採用匿名函式使this指向Object,否則this將指向xhr
this.xhr.addEventListener("readystatechange", () => {
switch (this.xhr.readyState) {
case 1:
this.beforeSend(this.xhr);
break
case 4:
resultText = this.dataFilter(this.xhr.responseText, this.dataType || this.xhr.getResponseHeader("Content-Type").toLowerCase());
if (this.xhr.statusText === "OK") {
statusText = "success";
this.success(resultText, statusText);
} else {
statusText = "error";
this.error(this.xhr, "error");
}
this.complete(this.xhr, statusText);
break
}
});
}
setRequestData() {
// 判斷是否要進行url編碼,有2種情況:
// 1.contentType是application/x-www-form-urlencoded時需要url編碼
// 2.傳入的物件不是serialize()方法已經序列化好後的物件時不需要url編碼
if (this.contentType === "application/x-www-form-urlencoded" && !Object.isFrozen(this.data)) {
this.data = this.urlEncode(this.data);
}
}
urlEncode() {
return Object.entries(this.data).reduce((prev, cur, index, array) => {
// key
let key = cur[0];
// value:2種情況,如果value是普通字串,則直接進行encodeURIComponent編碼處理,如果是陣列將按照 k1=v1&k2=v2 的方式進行編碼,並且判斷是否傳入traditional
let value = this.checkValueIsArray(cur);
if (array.length - 1 === index) {
// 判斷是否是最後一位,如果是最後一位就加上&
// traditional:如果前端傳遞的是一個Array,如{"k1":[1,2,3,4]}則需要新增一個屬性traditional:true,否則後端將接收不到該引數。(實際上接受的時候要使用request.POST.get("k1[]"))來進行接受,這是有問題的
return !this.traditional && cur[1] instanceof Array ? prev += `${key}[]=${value}` : prev += `${key}=${value}`;
} else {
return !this.traditional && cur[1] instanceof Array ? prev += `${key}[]=${value}&` : prev += `${key}=${value}&`;
}
}, "");
}
checkValueIsArray(cur) {
if (cur[1] instanceof Array) {
cur[1].forEach((value, index, array) => {
if (index != array.length - 1) {
value = encodeURIComponent(value);
array[index] = value + "&";
} else {
array[index] = String(value);
}
})
// traditional:如果前端傳遞的是一個Array,如{"k1":[1,2,3,4]}則需要新增一個屬性traditional:true,否則後端將接收不到該引數。(實際上接受的時候要使用request.POST.get("k1[]"))來進行接受,這是有問題的,這裡主要對陣列中的第2個元素開始及其之後的所有元素進行url編碼格式轉換
return this.traditional ? cur[1].join(`${cur[0]}=`) : cur[1].join(`${cur[0]}[]=`)
}
else {
return encodeURIComponent(cur[1])
}
}
setXhrOpen() {
// 如果請求方式是GET請求,則在url後加上編碼後的data,否則將只傳遞this.url
// true引數為開啟非同步呼叫,也是預設的選項
this.xhr.open(
this.method,
this.method === "GET" ? this.url.concat("?", this.data) : this.url,
true
);
}
setRequestHeader() {
// 在不上傳檔案的時候,請求頭Content-Type是必須的
for (let [k, v] of Object.entries(this.headers)) {
this.xhr.setRequestHeader(k, v);
}
if (this.contentType) {
this.xhr.setRequestHeader("Content-Type", this.contentType);
}
}
sendRequest() {
// 傳送請求時,如果請求方式為GET將使用this.xhr.send(null),否則將使用this.xhr.send(this.data)
this.xhr.send(this.method === "GET" ? null : this.data);
}
}
function getAjax(ajaxSettings) {
return new Ajax(ajaxSettings);
}
function serialize(selector) {
// 整體思路:先獲取一個包含所有表單項物件,再將物件進行url編碼
let formNode = document.querySelector(selector);
let haveValueNodeObject = {};
Array.prototype.forEach.call(formNode.elements, (element, index, lst) => {
switch (true) {
// 多選或者單選select都將組成列表
case element.tagName === "SELECT":
Array.prototype.forEach.call(element.options, (option, index, array) => {
if (option.selected) {
haveValueNodeObject[element.name] ? haveValueNodeObject[element.name].push(option.value) : haveValueNodeObject[element.name] = [option.value];
}
})
break
// 多選,組成列表
case element.type === "checkbox":
if (element.checked) {
haveValueNodeObject[element.name] ? haveValueNodeObject[element.name].push(element.value) : haveValueNodeObject[element.name] = [element.value];
}
break
// 單選
case element.type === "radio":
if (element.checked) {
haveValueNodeObject[element.name] = element.value
}
break
// 其他的專案,注意檔案物件不獲取
default:
if (element.name && element.type !== "file") {
haveValueNodeObject[element.name] = element.value || "";
}
}
}
)
// 凍結物件,代表已經經過序列化了,在傳送資料時已避免url二次編碼
// 這裡是傳入一個物件並且繼承Ajax的原型物件,因為這樣做才能在編碼時使用checkValueIsArray方法
return Object.freeze(Ajax.prototype.urlEncode.call(
{ data: haveValueNodeObject, traditional: true, __proto__: Ajax.prototype },
));
}
function serializeArray(selector) {
// 提取form表單中的資料,並將其構建為一個name:value的陣列 [{name:value}, {name:value}, {name:value}]
let formNode = document.querySelector(selector);
let haveValueArray = [];
Array.prototype.forEach.call(formNode.elements, (element, index, lst) => {
switch (true) {
case element.tagName === "SELECT":
Array.prototype.forEach.call(element.options, (option, index, array) => {
if (option.selected) {
haveValueArray.push({ "name": element.name, "value": option.value });
}
})
break
case element.type === "checkbox" || element.type === "radio":
if (element.checked) {
haveValueArray.push({ "name": element.name, "value": element.value });
}
break
default:
if (element.name && element.type !== "file") {
haveValueArray.push({ "name": element.name, "value": element.value });
}
}
}
)
return haveValueArray;
}
window.$ = { ajax: getAjax, serialize, serializeArray };
支援的上傳格式
由於我們JavaScript的原生Ajax高度按照jQuery.ajax作為藍本。所以常見的特性要與其保持一致。
如支援的原生上傳資料格式實現:
- 僅支援上傳k-v的物件,不支援上傳陣列
- 前端上傳的資料中,不允許出現物件巢狀的形式。如{"k1":{"k1-1":v1}},這樣只會得到{“k1” : “object”}
- 如果前端傳遞的是一個Array,如 {"k1":[1, 2, 3, 4]} 則需要新增一個屬性 traditional:true,否則後端將接收不到該引數。(實際上接受的時候要使用request.POST.get("k1[]"))來進行接受,這是有問題的
如果你上傳的資料是json格式,當然就不會出現這些問題了。
如何傳送GET請求
傳送GET請求示例如下:
// 傳送GET請求
$.ajax({
url: "http://localhost:5700/get",
method: "get",
headers: {
"user_head": "Hello World",
},
data: { "name": "Jack", "age": 18 },
dataType: "json",
success: (responseText, statusText) => {
console.log(responseText);
console.log(statusText);
},
error: (responseText, statusText) => {
console.log(responseText);
console.log(statusText);
},
complete: (xhr, statusText) => {
console.log("此處可獲取響應頭");
}
})
如何傳送POST請求
傳送POST請求示例如下:
// 傳送post請求
$.ajax({
url: "http://localhost:5700/post",
method: "post",
headers: {
"user_head": "Hello World",
},
data: { "name": "Jack", "age": 18 },
dataType: "json",
success: (responseText, statusText) => {
console.log(responseText);
console.log(statusText);
},
error: (responseText, statusText) => {
console.log(responseText);
console.log(statusText);
},
complete: (xhr, statusText) => {
console.log("此處可獲取響應頭");
}
})
如何傳送JSON資料
傳送JSON格式資料示例如下:
// 傳送json格式資料
$.ajax({
url: "http://localhost:5700/json",
method: "post",
headers: {
"user_head": "Hello World",
},
data: JSON.stringify({ "name": "Jack", "age": 18 }), // 1.手動進行JSON格式化
dataType: "json",
contentType: "application/json", // 2.請求頭中宣告本次傳送的是JSON格式資料
success: (responseText, statusText) => {
console.log(responseText);
console.log(statusText);
},
error: (responseText, statusText) => {
console.log(responseText);
console.log(statusText);
},
complete: (xhr, statusText) => {
console.log("此處可獲取響應頭");
}
})
如何提交form表單的資料
針對form表單提交的資料,我們可以使用封裝好的2個函式serialize()和serializeArray()。
-
serialize():提取form表單中的資料項,並對其做url編碼處理,返回一個字串,注意,它不會提取檔案選擇框
-
serializeArray():提取form表單中的資料,並將其構建為一個name:value的陣列,注意,它不會提取檔案選擇框,最終格式為 [{name:value}, {name:value}, {name:value}],
示例如下,如果是serialize()則直接提交即可:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<form action="#" id="register">
<div>
<label for="username">使用者名稱</label>
<input type="text" id="username" name="username" required>
</div>
<div>
<label for="password">密碼</label>
<input type="password" id="password" name="password" required>
</div>
<div>
<label for="male">男</label>
<input type="radio" name="gender" value="male" id="male" required>
<label for="female">女</label>
<input type="radio" name="gender" value="female" id="female">
</div>
<div>
<label for="basketball">籃球</label>
<input type="checkbox" name="hobby" value="basketball" id="basketball" required>
<label for="football">足球</label>
<input type="checkbox" name="hobby" value="football" id="football">
<label for="volleyball">排球</label>
<input type="checkbox" name="hobby" value="volleyball" id="volleyball">
</div>
<div>
<label for="city">城市</label>
<select name="city" id="city" multiple>
<option value="beijing">北京</option>
<option value="shanghai">上海</option>
<option value="shenzhen">深圳</option>
</select>
</div>
<div>
<label for="avatar">上傳頭像</label>
<input type="file" name="avatar" id="avatar">
</div>
<div>
<button type="button">提交</button>
</div>
</form>
</body>
<script src="./demo.js"></script>
<script>
document.querySelector("button").addEventListener("click", (event) => {
$.ajax({
url: "http://localhost:5700/post",
method: "post",
headers: {
"user_head": "Hello World",
},
data: $.serialize("#register"),
dataType: "json",
success: (responseText, statusText) => {
console.log(responseText);
console.log(statusText);
},
error: (responseText, statusText) => {
console.log(responseText);
console.log(statusText);
},
complete: (xhr, statusText) => {
console.log("此處可獲取響應頭");
}
})
console.log($.serialize("#register"));
// username=%E4%BA%91%E5%B4%96&password=123&gender=male&hobby=basketball&hobby=football&city=shanghai&city=shenzhen
})
</script>
</html>
如果是serializeArray(),需要使用appliction/json的方式進行提交,因為該方法返回的是一個陣列:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<form action="#" id="register">
<div>
<label for="username">使用者名稱</label>
<input type="text" id="username" name="username" required>
</div>
<div>
<label for="password">密碼</label>
<input type="password" id="password" name="password" required>
</div>
<div>
<label for="male">男</label>
<input type="radio" name="gender" value="male" id="male" required>
<label for="female">女</label>
<input type="radio" name="gender" value="female" id="female">
</div>
<div>
<label for="basketball">籃球</label>
<input type="checkbox" name="hobby" value="basketball" id="basketball" required>
<label for="football">足球</label>
<input type="checkbox" name="hobby" value="football" id="football">
<label for="volleyball">排球</label>
<input type="checkbox" name="hobby" value="volleyball" id="volleyball">
</div>
<div>
<label for="city">城市</label>
<select name="city" id="city" multiple>
<option value="beijing">北京</option>
<option value="shanghai">上海</option>
<option value="shenzhen">深圳</option>
</select>
</div>
<div>
<label for="avatar">上傳頭像</label>
<input type="file" name="avatar" id="avatar">
</div>
<div>
<button type="button">提交</button>
</div>
</form>
</body>
<script src="./demo.js"></script>
<script>
document.querySelector("button").addEventListener("click", (event) => {
$.ajax({
url: "http://localhost:5700/json",
method: "post",
headers: {
"user_head": "Hello World",
},
data: JSON.stringify($.serializeArray("#register")), // 1.手動進行JSON格式化
dataType: "json",
contentType: "application/json", // 2.請求頭中宣告本次傳送的是JSON格式資料
success: (responseText, statusText) => {
console.log(responseText);
console.log(statusText);
},
error: (responseText, statusText) => {
console.log(responseText);
console.log(statusText);
},
complete: (xhr, statusText) => {
console.log("此處可獲取響應頭");
}
})
console.log($.serializeArray("#register"));
// Array(7)
// 0: {username: "雲崖"}
// 1: {password: "123"}
// 2: {gender: "male"}
// 3: {hobby: "basketball"}
// 4: {hobby: "football"}
// 5: {city: "shanghai"}
// 6: {city: "shenzhen"}
})
</script>
</html>
如何進行檔案上傳
如果要傳送檔案,我們需要藉助FormData物件進行資料提交,以下是注意事項。
-
在<form>表單中上傳檔案,必須要將enctype設定為multipart/form-data。
-
但是在使用XmlHttpRequest物件上傳檔案時,並不需要指定 contentType為multipart/form-data 格式,所以不新增contentType請求頭。
-
contentType應設定為false,即不使用任何資料格式,不使用任何編碼
-
processData應設定為false,不讓瀏覽器做任何資料格式的編碼
示例如下,我們使用FormData搭配serializeArray()方法實現一個真正意義上的非同步提交表單:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<form action="#" id="register">
<div>
<label for="username">使用者名稱</label>
<input type="text" id="username" name="username" required>
</div>
<div>
<label for="password">密碼</label>
<input type="password" id="password" name="password" required>
</div>
<div>
<label for="male">男</label>
<input type="radio" name="gender" value="male" id="male" required>
<label for="female">女</label>
<input type="radio" name="gender" value="female" id="female">
</div>
<div>
<label for="basketball">籃球</label>
<input type="checkbox" name="hobby" value="basketball" id="basketball" required>
<label for="football">足球</label>
<input type="checkbox" name="hobby" value="football" id="football">
<label for="volleyball">排球</label>
<input type="checkbox" name="hobby" value="volleyball" id="volleyball">
</div>
<div>
<label for="city">城市</label>
<select name="city" id="city" multiple>
<option value="beijing">北京</option>
<option value="shanghai">上海</option>
<option value="shenzhen">深圳</option>
</select>
</div>
<div>
<label for="avatar">上傳頭像</label>
<input type="file" name="avatar" id="avatar">
</div>
<div>
<button type="button">提交</button>
</div>
</form>
</body>
<script src="./demo.js"></script>
<script>
document.querySelector("button").addEventListener("click", (event) => {
// 獲取上傳的檔案物件
let fileNode = document.querySelector("#avatar");
let fileObj = fileNode.files[0];
// 使用FormData用於偽造form表單提交的資料
let fd = new FormData();
// 新增檔案
fd.append(fileNode.name, fileObj);
// 新增其他表單項
$.serializeArray("#register").forEach((obj, index, array) => {
fd.append(obj.name, obj.value);
});
// 傳送json格式資料
$.ajax({
url: "http://localhost:5700/file",
method: "post",
headers: {
"user_head": "Hello World",
},
data: fd, // 直接傳送ForData物件即可
dataType: "json",
contentType: false, // 必須設定為false
processData: false, // 必須設定為false
success: (responseText, statusText) => {
console.log(responseText);
console.log(statusText);
},
error: (xhr, statusText) => {
console.log(xhr.responseText);
console.log(statusText);
},
complete: (xhr, statusText) => {
console.log(statusText);
}
})
})
</script>
</html>