XMLHttpRequest 類
XMLHttpRequest 為 ajax 的核心。
var xhr = new XMLHttpRequest()
- XMLHttpRequest 本質(zhì)及構(gòu)成
本質(zhì)為一個(gè)函數(shù);
typeof XMLHttpRequest ; // 'function'
原型鏈關(guān)系:XMLHttpRequest “繼承”于 XMLHttpRequestEventTarget,XMLHttpRequestEventTarget “繼承”于 EventTarget ;
var xhr = new XMLHttpRequest();
xhr instanceof XMLHttpRequest; // true
xhr instanceof XMLHttpRequestEventTarget ; // true
xhr instanceof EventTarget ; // true
流程解析
前端測(cè)試代碼如下:
var xhr = new XMLHttpRequest()
console.log(xhr.readyState); // 此時(shí)的 readyState 為0
var eventKeys = [
'abort',
'error',
'load',
'loadend',
'loadstart',
'progress',
'readystatechange',
'timeout'
]
eventKeys.forEach(key => {
xhr[`on${key}`] = function () {
console.log(`-------request is ${key}-------`)
console.log(`request is on${key}`)
log()
console.log('\n\n')
}
});
xhr.open('get', 'http://localhost:9000/', true);
xhr.send()
function log() {
console.log('readyState=', xhr.readyState);
console.log('status=', xhr.status);
console.log('statusText', xhr.statusText);
}
nodejs 服務(wù)端代碼如下:
const Koa = require('koa');
const app = new Koa();
function response() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('hello world')
}, 10000);
})
}
app.use(async ctx => {
ctx.set("Access-Control-Allow-Origin", "*");
ctx.set('Access-Control-Allow-Methods', 'PUT, GET, POST, DELETE, OPTIONS');
ctx.set("Access-Control-Allow-Headers", "X-Requested-With");
ctx.set('Access-Control-Allow-Headers', 'Content-Type');
var res = await response()
ctx.body = res;
})
app.listen(9000)
根據(jù)打印信息,成功的普通請(qǐng)求概括如下:
- readyState 經(jīng)歷四個(gè)值: 0 -》1 -》 2 -》 3 -》 4
- 綁定事件觸發(fā)順序及對(duì)應(yīng)的 readyState 是:onreadystatechange(1) -》onloadstart(1) -》 onreadystatechange(2) -》onreadystatechange(3)-》onprogress(3)-》onreadystatechange(4)-》onload(4)-》onloadend(4)
- status :0 和 200
- statusText:空 和 OK
通過(guò) readyState 看請(qǐng)求周期
- var xhr = new XMLHttpRequest(); // xhr.readyState = 0
- xhr.open(‘get’,’url’,true); // readyState = 0 =》 readyState = 1 ,ononreadystatechange 觸發(fā)
- xhr.send(); // => xhr.onloadstart() 觸發(fā),readyState 不變?yōu)?
- readyState = 1 =》 readyState = 2,onreadystatechange 觸發(fā),服務(wù)器端已經(jīng)接收到請(qǐng)求。
- readyState = 2 =》 readyState = 3,onreadystatechange 觸發(fā),并觸發(fā) onprogress 事件,表示響應(yīng)正在加載中。。。
- readyState = 3 =》 readyState = 4,onreadystatechange 觸發(fā),并觸發(fā) onload 事件。
- 觸發(fā) onloadend 事件。
實(shí)例解析
var xhr = new XMLHttpRequest()
xhr.onload = function () {
console.log(xhr.response, xhr.responseText)
}
xhr.open('get', 'http://localhost:9000/', true)
xhr.send();
- 普通的 post 請(qǐng)求(Form Data,請(qǐng)求參數(shù)為 key value 形式)
var xhr = new XMLHttpRequest();
xhr.onload = function () {
console.log(xhr.response, xhr.responseText)
}
xhr.open('post', 'http://localhost:9000/', true)
xhr.setRequestHeader('content-type', 'application/x-www-form-urlencoded')
xhr.send('name=tom&age=18');
設(shè)置請(qǐng)求的格式為 x-www-form-urlencoded , 且傳遞的數(shù)據(jù)為字符串,用 & 連接不同的字段。
設(shè)置請(qǐng)求頭,xhr.setRequestHeader( key,val )
- 普通的 post 請(qǐng)求 (請(qǐng)求體為 json 格式)
var xhr = new XMLHttpRequest();
xhr.onload = function () {
console.log(xhr.response, xhr.responseText)
}
xhr.open('post', 'http://localhost:9000/', true)
xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
xhr.send(JSON.stringify({ name: 'tom', age: 18 }));
- 設(shè)置超時(shí)請(qǐng)求( xhr.timeout=2000 )
var xhr = new XMLHttpRequest()
xhr.ontimeout = function(){
console.log(xhr.status,xhr.readyState,xhr.responseText)
console.log('請(qǐng)求超時(shí)')
}
xhr.onloadend = function(){
console.log(xhr.status,xhr.readyState,xhr.responseText)
console.log('請(qǐng)求結(jié)束')
}
xhr.onreadystatechange =function(){
console.log('readyState change',xhr.readyState)
}
xhr.timeout = 2000
xhr.open('get', 'http://localhost:9000/', true);
xhr.send();
超出設(shè)置的 timeout 時(shí)間的請(qǐng)求,結(jié)束時(shí),并不會(huì)觸發(fā) onerror 和 onabort 以及 onload ,只會(huì)觸發(fā) ontimeout 和 onloadend;
readyState 從 0-》1-》4
onload 和 onloadend 的區(qū)別是,onloadend 不管請(qǐng)求成功與否都會(huì)觸發(fā),而 onload 只有請(qǐng)求成功結(jié)束時(shí)觸發(fā)。
- setRequestHeader 的用法,可以在 Request Header 中添加字段及值
客戶端
var xhr = new XMLHttpRequest()
xhr.onload = function () {
console.log('請(qǐng)求成功')
}
xhr.open('post', 'http://localhost:9000/', true);
xhr.setRequestHeader('X-user','tom')
xhr.setRequestHeader('X-token','ACDFWE@123123')
xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
xhr.send();
服務(wù)器端(nodejs),設(shè)置允許傳遞的請(qǐng)求頭字段,多個(gè)字段用逗號(hào)隔開。
app.use(async ctx => {
let origin = ctx.request.header.origin
ctx.set("Access-Control-Allow-Origin", origin);
ctx.set('Access-Control-Allow-Methods', 'PUT, GET, POST, DELETE, OPTIONS');
ctx.set("Access-Control-Allow-Headers", "X-user,X-token,Content-Type");
var res = await response()
ctx.body = res;
})
跨越時(shí),需要設(shè)置 Access-Control-Allow-Origin 為發(fā)送請(qǐng)求的客戶端的域名。
字段不區(qū)分大小寫。
- withCredentials 的用法,請(qǐng)求頭中帶 cookie 信息
客戶端設(shè)置 xhr 實(shí)體 withCredentials 屬性為 true
var xhr = new XMLHttpRequest()
xhr.onload = function () {
console.log('請(qǐng)求成功')
}
xhr.open('post', 'http://localhost:9000/', true);
xhr.withCredentials = true; // here
xhr.send();
服務(wù)端,設(shè)置允許
app.use(async ctx => {
let origin = ctx.request.header.origin
ctx.set("Access-Control-Allow-Origin", origin);
ctx.set('Access-Control-Allow-Methods', 'PUT, GET, POST, DELETE, OPTIONS');
ctx.set("Access-Control-Allow-Headers", "x-user,x-token,content-type");
ctx.set("Access-Control-Allow-Credentials", true); // here
var res = await response()
ctx.body = res;
})
效果則是:
- Response Headers 中多了一條:Access-Control-Allow-Credentials: true
- Request Headers 中多了一條:Cookie:本地的 cookie 字符串
而且此時(shí)跨域的 options 請(qǐng)求好像也不發(fā)了
var xhr = new XMLHttpRequest()
xhr.open('post', 'http://localhost:9000/', true);
xhr.send();
setTimeout(() => {
xhr.abort();
}, 3000);
手動(dòng) abort 必須在請(qǐng)求完成(onloadend)之前。
周期中能觸發(fā)的事件有:onreadystatechange ,onloadstart,onprogress,onabort,onloadend
- overrideMimeType 和 自定義返回類型
var xhr = new XMLHttpRequest()
xhr.onload = function(){
console.log(xhr.response)
}
xhr.open('post', 'http://localhost:9000/', true);
xhr.overrideMimeType('text/plain; charset=x-user-defined');
// 或
// xhr.responseType = 'blob';
xhr.send();
- upload 實(shí)體使用,實(shí)現(xiàn)上傳進(jìn)度獲取
xhr.onprogress = updateProgress;
xhr.upload.onprogress = updateProgress;
function updateProgress(event) {
if (event.lengthComputable) {
var completedPercent = event.loaded / event.total;
}
}
- 簡(jiǎn)單地封裝 ajax 請(qǐng)求
// ajax 方法定義
function ajax(options) {
var { url, method, data, headers } = options;
var lowerMethod = method.toLowerCase();
var _isGet = lowerMethod === 'get';
var _isPost = lowerMethod === 'post';
function _dataStringify(_data) {
return Object.keys(_data).map(key => `${key}=${_data[key]}`).join('&')
}
// 拼接 data
let requestData = null
if (_isGet) {
url += `?${_dataStringify(data)}`
}
else if (_isPost) {
requestData = options.format === 'json'
? JSON.stringify(data)
: _dataStringify(data)
}
return new Promise((resolve, reject) => {
var xhr = new XMLHttpRequest();
xhr.withCredentials = Boolean(options.withCredentials);
xhr.onload = function () {
if (xhr.status == 200 || xhr.status == 304) {
resolve(xhr.responseText);
}
else {
reject('請(qǐng)求錯(cuò)誤')
}
}
// 綁定自定義 handler
for (var ev in options.handlers) {
var _prevHandler = xhr[ev];
xhr[ev] = function () {
_prevHandler();
options.handlers[ev]();
}
}
xhr.open(method, url, true);
// 設(shè)置 header
for (var attr in headers) {
xhr.setRequestHeader(attr, headers[attr]);
}
xhr.send(requestData);
})
}
// ajax 使用
ajax({
url: 'http://localhost:9000',
method: 'POST',
format: 'json',
data: {
name: 'tom'
},
headers: {
// 'Content-Type': 'application/x-www-form-urlencoded',
'Content-Type': 'application/json',
'x-token': 'xxxxx'
},
withCredentials: true,
handlers: {
'onload': function () {
console.log('自定義onload')
}
}
})
.then(res => {
console.log(res,222222)
})
.catch(err => {
console.log(err)
})
示例并不完整,可以把文件上傳的 FormData 方式添加進(jìn)去,還有超時(shí)等功能。
|