XMLHttpRequest 為 ajax 的核心。
var xhr = new XMLHttpRequest()
本質(zhì)為一個函數(shù);
typeof XMLHttpRequest ; // 'function'
原型鏈關系:XMLHttpRequest “繼承”于 XMLHttpRequestEventTarget,XMLHttpRequestEventTarget “繼承”于 EventTarget ;
var xhr = new XMLHttpRequest();xhr instanceof XMLHttpRequest; // truexhr instanceof XMLHttpRequestEventTarget ; // truexhr instanceof EventTarget ; // true
實例化時屬性,xhr 上屬性
事件綁定
onabort =》中止請求時觸發(fā)
ontimeout =》超時觸發(fā)
響應體相關
readyState =》初始值為0,在 new XMLHttpRequest() 的時候
responseXML
狀態(tài)相關
status
statusText
其他
第一層原型,XMLHttpRequest 屬性和方法
send(data?) =》發(fā)送請求
其他
設置了 xhr 的所有屬性的 getter 和 setter
第二層原型,XMLHttpRequestEventTarget
僅設置了 xhr 的 事件綁定中的方法的 getter 和 setter
第三層原型,EventTarget
前端測試代碼如下:
var xhr = new XMLHttpRequest()console.log(xhr.readyState); // 此時的 readyState 為0var 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 服務端代碼如下:
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ù)打印信息,成功的普通請求概括如下:
通過 readyState 看請求周期
var xhr = new XMLHttpRequest()xhr.onload = function () { console.log(xhr.response, xhr.responseText)}xhr.open('get', 'http://localhost:9000/', true)xhr.send();
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');
設置請求的格式為
x-www-form-urlencoded
, 且傳遞的數(shù)據(jù)為字符串,用&
連接不同的字段。設置請求頭,xhr.setRequestHeader( key,val )
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 }));
var xhr = new XMLHttpRequest()xhr.ontimeout = function(){ console.log(xhr.status,xhr.readyState,xhr.responseText) console.log('請求超時')}xhr.onloadend = function(){ console.log(xhr.status,xhr.readyState,xhr.responseText) console.log('請求結(jié)束')}xhr.onreadystatechange =function(){ console.log('readyState change',xhr.readyState)}xhr.timeout = 2000xhr.open('get', 'http://localhost:9000/', true);xhr.send();
超出設置的 timeout 時間的請求,結(jié)束時,并不會觸發(fā) onerror 和 onabort 以及 onload ,只會觸發(fā) ontimeout 和 onloadend;
readyState 從 0-》1-》4
onload 和 onloadend 的區(qū)別是,onloadend 不管請求成功與否都會觸發(fā),而 onload 只有請求成功結(jié)束時觸發(fā)。
客戶端
var xhr = new XMLHttpRequest()xhr.onload = function () { console.log('請求成功')}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();
服務器端(nodejs),設置允許傳遞的請求頭字段,多個字段用逗號隔開。
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;})
跨越時,需要設置
Access-Control-Allow-Origin
為發(fā)送請求的客戶端的域名。字段不區(qū)分大小寫。
客戶端設置 xhr 實體 withCredentials 屬性為 true
var xhr = new XMLHttpRequest()xhr.onload = function () { console.log('請求成功')}xhr.open('post', 'http://localhost:9000/', true);xhr.withCredentials = true; // herexhr.send();
服務端,設置允許
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;})
效果則是:
而且此時跨域的 options 請求好像也不發(fā)了
var xhr = new XMLHttpRequest()xhr.open('post', 'http://localhost:9000/', true);xhr.send();setTimeout(() => { xhr.abort();}, 3000);
手動 abort 必須在請求完成(onloadend)之前。
周期中能觸發(fā)的事件有:onreadystatechange ,onloadstart,onprogress,onabort,onloadend
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();
xhr.onprogress = updateProgress;xhr.upload.onprogress = updateProgress;function updateProgress(event) { if (event.lengthComputable) { var completedPercent = event.loaded / event.total; } }
// 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('請求錯誤') } } // 綁定自定義 handler for (var ev in options.handlers) { var _prevHandler = xhr[ev]; xhr[ev] = function () { _prevHandler(); options.handlers[ev](); } } xhr.open(method, url, true); // 設置 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 方式添加進去,還有超時等功能。