[js进阶]用原生js实现一个ajax?
XMLHttpRequest基本使用方法
都说js是单线程的,那js是怎么实现异步请求的呢?
其实仔细了解了js的运行机制后,才发现,js会专门使用一个子线程来处理请求操作(XMLHttpRequest),只是因为这些线程都是主线程开出来的,是完全听命于主线程的,所以才这么认为js是单线程的(单主线程)。
1、readyState:返回当前文档的载入状态
0:(未初始化)还没有调用send()方法
1:(载入)已调用send()方法,正在发送请求
2:(载入完成)send()方法执行完成,已经接收到全部响应内容
3:(交互)正在解析响应内容
4:(完成)响应内容解析完成,可以在客户端调用了
在通过Ajax向服务器发送请求的过程中,XMLHttpRequest
对象的状态会发生多次变化。由于 readystatechange
事件是在 xhr
对象状态变化时触发(不单是在得到响应时),也就意味着这个事件会被触发多次。XMLHttpRequest
的主要状态事件如下:
var xhr = new XMLHttpRequest()
xhr.open('GET', '/time')
xhr.onload = function () {
// onload readyState => 4
// 只在请求完成时触发
console.log(this.readyState)
}
xhr.onprogress = function () {
// onprogress readyState => 3
// 只在请求进行中触发
console.log(this.readyState)
}
xhr.onloadstart = function () {
// onloadstart readyState => 1
// 开始发送请求的时候触发
console.log(this.readyState)
}
xhr.onloadend = function () {
// onloadend readyState => 4
// 请求响应过程结束的时候触发
console.log(this.readyState)
}
xhr.onreadystatechange = function () {
// 请求响应状态每次改变都会触发
}
xhr.send(null)
2、status:HTTP状态码
1XX:信息性状态码 ,表示接收的请求正在处理
2XX:成功状态码 , 表示请求正常处理
3XX:重定向状态码 ,表示需要附加操作来完成请求
4XX:客户端错误状态 ,表示服务器无法处理请求
5XX:服务器错误状态 ,表示服务器处理请求出错
3、 get和post
GET请求的参数需要放在queryString中
POST的请求数据一般是放在formData中(form表单),当然,还有其他的方式,比如payload中的json等
4、同步请求和异步请求
当async属性的值为false时是同步的,Ajax请求将整个浏览器锁死,只有ajax请求返回结果后,才执行ajax后面的alert语句。
当async属性的值为true时是异步的,即不会等待ajax请求返回的结果,会直接执行ajax后面的alert语句。
实现一个简单的ajax
-
支持get、post两种请求方式
-
支持同步、异步
-
支持自定义传data的格式(get默认为queryString、post默认为formData)
-
支持超时机制
-
兼容ie6等早期版本
其中一个ajax的使用示例如下:
$.ajax({
type: "GET",
url: "www.baidu.com/s",
data: { wd: "test" },
contentType: "application/json;charset=utf-8",
dataType: "json", // 传回来的数据格式
success: function (data) {
// 更新视图
// 关闭加载动画等操作
}
});
contentType: 告诉服务器,我要发什么类型的数据
dataType:告诉服务器,我要想什么类型的数据,如果没有指定,那么会自动推断是返回 XML,还是JSON,还是script,还是String。
思路
首先用伪码来描述一下大致逻辑
// TODO: 将字典(obj键值对)转化为拼接queryString的函数方法。
function formatParams(data) {
...
return formatParams
}
// TODO: 创建一个兼容性高的xhr
function createXMLHttpRequest(){
...
return xhr
}
// 根据options进行同步或异步请求
function myAjax(options){
读取options配置
xhr = createXMLHttpRequest()
// 发送请求部分
if(是get请求){
// 这里的options.async为true或false true为异步 false为同步
xhr.open("GET", options.url + "?" + formatParams(options.data), options.async);
}else if(是post请求){
xhr.open("POST", options.url, options.async);
contentType = options.contentType || "application/x-www-form-urlencoded"
xhr.setRequestHeader("Content-type", contentType);
xhr.send(params);
}
// 设置超时放弃请求机制
setTimeout(function () {
if(xhr状态未变成完成请求状态){
// 中断请求
xhr.abort();
}
}, options.timeout)
// 请求完成后执行的操作 执行用户传入的success fail钩子函数
xhr.onreadystatechange = function () {
if (xhr状态变成完成了请求状态) {
if (状态码成功) {
执行成功的钩子函数
} else {
执行失败的钩子函数
}
}
}
}
实现
浏览器兼容判断
function createXMLHttpRequest() {
var xhr;
// 适用于大多数浏览器,以及IE7和IE更高版本
try{
xhr = new XMLHttpRequest();
} catch (e) {
// 适用于IE6
try {
xhr = new ActiveXObject("Msxml2.XMLHTTP");
} catch (e) {
// 适用于IE5.5,以及IE更早版本
try{
xhr = new ActiveXObject("Microsoft.XMLHTTP");
} catch (e){}
}
}
return xhr;
}
格式化请求参数
// TODO: 格式化请求参数
function formatParams(data) {
var arr = [];
for (var key in data) {
arr.push(encodeURIComponent(key) + "=" + encodeURIComponent(data[key]));
}
return arr.join("&");
}
myAjax包装
function myAjax(options) {
//调用函数时如果options没有指定,就给它赋值{},一个空的Object
options = options || {};
// 请求格式GET、POST,默认为GET
options.type = (options.type || "GET").toUpperCase();
//响应数据格式,默认json
options.dataType = options.dataType || "json";
//Post请求时data的格式,默认为formData
options.contentType = options.contentType || "application/x-www-form-urlencoded";
// 同步请求还是异步请求 true为异步请求async 默认true
options.async = options.async || true;
// 默认超时时间 3分钟
options.timeout = options.timeout || (1000 * 60 * 3)
// 。。。。其它配置项暂时不扩展
var xhr;
xhr = new XMLHttpRequest();
//启动并发送一个请求
if (options.type == "GET") {
xhr.open("GET", options.url + "?" + formatParams(options.data), options.async);
xhr.send(null);
} else if (options.type == "POST") {
xhr.open("POST", options.url, options.async);
// 设置请求头
xhr.setRequestHeader("Content-Type", options.contentType);
// 判断请求体
if (options.contentType == "application/x-www-form-urlencoded") {
xhr.send(formatParams(options.data));
} else {
xhr.send(options.data);
}
}
// 设置有效时间
setTimeout(function () {
if (xhr.readySate != 4) {
xhr.abort();
}
}, options.timeout)
// 接收
// options.success成功之后的回调函数 options.error失败后的回调函数
//xhr.responseText,xhr.responseXML 获得字符串形式的响应数据或者XML形式的响应数据
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
var status = xhr.status;
if (status >= 200 && status < 300 || status == 304) {
options.success && options.success(xhr.responseText, xhr.responseXML);
} else {
options.error && options.error(status);
}
}
}
}
完整代码(含实验)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title></title>
<style>
main {
width: 900px;
height: 900px;
border: 1px red solid;
margin: 0 auto;
position: relative
}
main div {
width: 200px;
height: 200px;
border: 1px red solid;
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
margin-left: auto;
margin-right: auto;
margin-top: auto;
margin-bottom: auto;
}
</style>
</head>
<body>
<main>
<div>
<button id="id_button_01">点我发送请求(jquery封装的ajax)</button>
<button id="id_button_02">点我发送请求(自己封装的ajax)</button>
</div>
<span>发起请求次数(jquery封装的ajax):<p id="id_text_01"></p></span>
<span>完成请求次数(jquery封装的ajax):<p id="id_text_02"></p></span>
<span>发起请求次数(自己封装的ajax):<p id="id_text_03"></p></span>
<span>完成请求次数(自己封装的ajax):<p id="id_text_04"></p></span>
</main>
</body>
<script src="https://lib.sinaapp.com/js/jquery/2.0.2/jquery-2.0.2.min.js"></script>
<script>
var counter01 = counter02 = counter03 = counter04 = 0;
var eText01 = document.getElementById("id_text_01");
var eText02 = document.getElementById("id_text_02");
var eText03 = document.getElementById("id_text_03");
var eText04 = document.getElementById("id_text_04");
var eButton01 = document.getElementById("id_button_01");
var eButton02 = document.getElementById("id_button_02");
//TODO: 格式化请求参数
function formatParams(data) {
var arr = [];
for (var key in data) {
arr.push(encodeURIComponent(key) + "=" + encodeURIComponent(data[key]));
}
return arr.join("&");
}
// TODO: 创建一个兼容性高的xhr
function createXMLHttpRequest() {
var xhr;
// 适用于大多数浏览器,以及IE7和IE更高版本
try {
xhr = new XMLHttpRequest();
} catch (e) {
// 适用于IE6
try {
xhr = new ActiveXObject("Msxml2.XMLHTTP");
} catch (e) {
// 适用于IE5.5,以及IE更早版本
try {
xhr = new ActiveXObject("Microsoft.XMLHTTP");
} catch (e) { }
}
}
return xhr;
}
function myAjax(options) {
//调用函数时如果options没有指定,就给它赋值{},一个空的Object
options = options || {};
// 请求格式GET、POST,默认为GET
options.type = (options.type || "GET").toUpperCase();
//响应数据格式,默认json
options.dataType = options.dataType || "json";
//Post请求时data的格式,默认为formData
options.contentType = options.contentType || "application/x-www-form-urlencoded";
// 同步请求还是异步请求 true为异步请求async 默认true
options.async = options.async || true;
// 默认超时时间 3分钟
options.timeout = options.timeout || (1000 * 60 * 3)
// 。。。。其它配置项暂时不扩展
var xhr;
xhr = new XMLHttpRequest();
//启动并发送一个请求
if (options.type == "GET") {
xhr.open("GET", options.url + "?" + formatParams(options.data), options.async);
xhr.send(null);
} else if (options.type == "POST") {
xhr.open("POST", options.url, options.async);
// 设置请求头
xhr.setRequestHeader("Content-Type", options.contentType);
// 判断请求体
if (options.contentType == "application/x-www-form-urlencoded") {
xhr.send(formatParams(options.data));
} else {
xhr.send(options.data);
}
}
// 设置有效时间
setTimeout(function () {
if (xhr.readySate != 4) {
xhr.abort();
}
}, options.timeout)
// 接收
// options.success成功之后的回调函数 options.error失败后的回调函数
//xhr.responseText,xhr.responseXML 获得字符串形式的响应数据或者XML形式的响应数据
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
var status = xhr.status;
if (status >= 200 && status < 300 || status == 304) {
options.success && options.success(xhr.responseText, xhr.responseXML);
} else {
options.error && options.error(status);
}
}
}
}
eButton01.onclick = function () {
console.log("发起了一个请求");
counter01++;
eText01.innerHTML = counter01;
$.ajax({
url: "test.txt",
type: 'get',
data: {
wd: '特朗普',
},
dataType: 'html',
timeout: 50000,
success: function (res) {
console.log("发送请求成功");
console.log(res);
counter02++;
eText02.innerHTML = counter02;
},
error: function (e) {
//异常处理
console.log(e);
}
})
}
eButton02.onclick = function () {
console.log("发起了一个请求");
counter03++;
eText03.innerHTML = counter03;
myAjax({
url: "test.txt",
type: "get",
data: {
wd: '特朗普',
},
dataType: 'html',
timeout: 50000,
success: function (res) {
console.log("发送请求成功");
console.log(res);
counter04++;
eText04.innerHTML = counter04;
},
error: function (e) {
//异常处理
console.log(e);
}
})
}
</script>
</html>
结果
可以看到实现得很成功。
结语
之后我将继续深入研究用原生js实现promise。
参考资料
https://www.w3school.com.cn/ajax/ajax_xmlhttprequest_create.asp w3school xmlhttprequest
https://www.w3school.com.cn/jquery/ajax_ajax.asp jQuery ajax - ajax() 方法
https://www.cnblogs.com/qing-5/p/11368009.html [原生js实现ajax封装]
https://www.cnblogs.com/colima/p/5339227.html [原生js实现Ajax]
https://www.jb51.net/article/119605.htm 深入讲解xhr(XMLHttpRequest)/jsonp请求之abort
《JavaScript权威指南》