XMLHttpRequest
类
XMLHttpRequest
为 ajax
的核心。
- 使用
var xhr = new XMLHttpRequest()
XMLHttpRequest
本质及构成
本质为一个函数;
typeof XMLHttpRequest ; // 'function'
原型链关系:XMLHttpRequest
“继承”于 XMLHttpRequestEventTarget
,XMLHttpRequestEventTarget
“继承”于 EventTarget
;
var xhr = new XMLHttpRequest();
xhr instanceof XMLHttpRequest; // true
xhr instanceof XMLHttpRequestEventTarget ; // true
xhr instanceof EventTarget ; // true
- 实例化时属性,
xhr
上属性
事件绑定
onabort =》中止请求时触发
onerror =》请求错误时触发
onload =》请求成功时触发
onloadend =》请求不管成功失败中止都将最后触发
onloadstart => 客户端开始发出请求,readyState=1,status=0,
onprogrress =》服务器已经响应,处理请求中触发
onreadystatechange =》 readyState 改变时触发
ontimeout =》超时触发
响应体相关
readyState =》初始值为0,在 new XMLHttpRequest() 的时候
response
responseText
responseType
responseURL
responseXML
状态相关
status
readyState
statusText
其他
timeout =》设置超时请求的时间,默认为0,不计超时
upload =》 XMLHttpRequestLoad 实例
withCredentials =》Request Header 是否带 cookie,需服务端配合。
第一层原型,XMLHttpRequest
属性和方法
属性
状态
UNSENT :0
OPENED:1
HEADERS_RECEIVED:2
LOADING:3
DONE:4
方法
abort =》中止请求
getAllResponseHeaders =》 获取所有 headers 头部信息,并非所有都能获取
getResponseHeader(‘key’) =》获取 key 的 header 信息,并非所有都能获取
open(method,url,async) 打开一个请求,此时 xhr.readyState 为 1
overrideMimeType
send(data?) =》发送请求
其他
设置了
xhr
的所有属性的getter
和setter
第二层原型,XMLHttpRequestEventTarget
仅设置了
xhr
的 事件绑定中的方法的getter
和setter
第三层原型,EventTarget
addEventListener
dispatchEvent
removeEventListener
流程解析
前端测试代码如下:
var xhr = new XMLHttpRequest()
console.log(xhr.readyState); // 此时的 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
服务端代码如下:
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)
根据打印信息,成功的普通请求概括如下:
readyState 经历四个值: 0 -》1 -》 2 -》 3 -》 4
绑定事件触发顺序及对应的 readyState 是:onreadystatechange(1) -》onloadstart(1) -》 onreadystatechange(2) -》onreadystatechange(3)-》onprogress(3)-》onreadystatechange(4)-》onload(4)-》onloadend(4)
status :0 和 200
statusText:空 和 OK
通过 readyState
看请求周期
var xhr = new XMLHttpRequest(); // xhr.readyState = 0
xhr.open(‘get’,’url’,true); // readyState = 0 =》 readyState = 1 ,ononreadystatechange 触发
xhr.send(); // => xhr.onloadstart() 触发,readyState 不变为1
readyState = 1 =》 readyState = 2,onreadystatechange 触发,服务器端已经接收到请求。
readyState = 2 =》 readyState = 3,onreadystatechange 触发,并触发 onprogress 事件,表示响应正在加载中。。。
readyState = 3 =》 readyState = 4,onreadystatechange 触发,并触发 onload 事件。
触发 onloadend 事件。
实例解析
普通的 get 请求
var xhr = new XMLHttpRequest()
xhr.onload = function () {
console.log(xhr.response, xhr.responseText)
}
xhr.open('get', 'http://localhost:9000/', true)
xhr.send();
普通的 post
请求(Form Data
,请求参数为 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');
设置请求的格式为
x-www-form-urlencoded
, 且传递的数据为字符串,用&
连接不同的字段。设置请求头,
xhr.setRequestHeader( key,val )
普通的 post
请求 (请求体为 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 }));
设置超时请求( xhr.timeout=2000
)
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('请求结束')
}
xhr.onreadystatechange =function(){
console.log('readyState change',xhr.readyState)
}
xhr.timeout = 2000
xhr.open('get', 'http://localhost:9000/', true);
xhr.send();
超出设置的
timeout
时间的请求,结束时,并不会触发onerror
和onabort
以及onload
,只会触发ontimeout
和onloadend
;
readyState
从0-》1-》4
onload
和onloadend
的区别是,onloadend
不管请求成功与否都会触发,而onload
只有请求成功结束时触发。
setRequestHeader
的用法,可以在 Request Header
中添加字段及值
客户端
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
为发送请求的客户端的域名。字段不区分大小写。
withCredentials
的用法,请求头中带 cookie
信息
客户端设置 xhr
实体 withCredentials
属性为 true
var xhr = new XMLHttpRequest()
xhr.onload = function () {
console.log('请求成功')
}
xhr.open('post', 'http://localhost:9000/', true);
xhr.withCredentials = true; // here
xhr.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;
})
效果则是:
Response Headers
中多了一条:Access-Control-Allow-Credentials: true
Request Headers
中多了一条:Cookie
:本地的 cookie 字符串
而且此时跨域的 options 请求好像也不发了
abort
手动终止请求
var xhr = new XMLHttpRequest()
xhr.open('post', 'http://localhost:9000/', true);
xhr.send();
setTimeout(() => {
xhr.abort();
}, 3000);
手动
abort
必须在请求完成(onloadend
)之前。周期中能触发的事件有:
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
实体使用,实现上传进度获取
xhr.onprogress = updateProgress;
xhr.upload.onprogress = updateProgress;
function updateProgress(event) {
if (event.lengthComputable) {
var completedPercent = event.loaded / event.total;
}
}
简单地封装 ajax
请求
// 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
方式添加进去,还有超时等功能。