前言
在日常工作中,我经常使用Jquery的Ajax来获取接口数据。这几天有一个的官网要制作,由于网站比较小,有一些与服务器通信的接口处理,并没有涉及到复杂的交互功能。为了可以减少加载Jquery库的时间,也不用负担Jquery复杂的逻辑处理带来的性能消耗。我决定不使用jquery,自己写了一个原生的Ajax函数。
需求整理
一般来说,前端与服务器的通信是使用XHR对象的。我做的官网是有几个异域的接口,然而XHR对象是没有跨域功能的。所以我把JSONP整合进来。
接下来,我们来看看整体功能图:
流程图
输入参数
首先,我们定义一个Ajax函数,并设置了一些输入参数。
1 2 3 4 5 6 7 8 9 10 11 12 13
|
function ajax(options){
var url = options.url || "", //请求的链接 type = (options.type || "get").toLowerCase(), //请求的方法,默认为get data = options.data || null, //请求的数据 contentType = options.contentType || "", //请求头 dataType = options.dataType || "", //请求的类型 async = options.async === undefined && true, //是否异步,默认为true. timeOut = options.timeOut, //超时时间。 before = options.before || function(){}, //发送之前执行的函数 error = options.error || function(){}, //错误执行的函数 success = options.success || function() {}; //请求成功的回调函数 }
|
参数表:
参数 | 默认值 | 描述 | 可选值 |
url |
“” |
请求的链接 |
string |
type |
get |
请求的方法 |
get,post |
data |
null |
请求的数据 |
object,string |
contentType |
“” |
请求头 |
string |
dataType |
“” |
请求的类型 |
jsonp |
async |
true |
是否异步 |
blooean |
timeOut |
undefined |
超时时间 |
number |
before |
function(){} |
发送之前执行的函数 |
function |
error |
function(){} |
请求报错执行的函数 |
function |
success |
function(){} |
请求成功的回调函数 |
function |
编码
一般来说,发送到后端的数据,若是包括中文或某些标点符号时,就要对发送的数据进行编码了。
- 如果data为字符串,通过&分割,对键名与键值分别编码
- 如果data为对象,把键值转化为字符串,再进行编码
- 由于encodeURIComponent不对+编码,所以我们用replace方法手动编码
- 若是使用get方法或JSONP,则数据是通过URL参数的方法传到后台,所以我们手动添加数据到URL
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
|
//编码数据 function setData() { var name, value; if (data) { if (typeof data === "string") { data = data.split("&"); for (var i = 0, len = data.length; i < len; i++) { name = data[i].split("=")[0]; value = data[i].split("=")[1]; data[i] = encodeURIComponent(name) + "=" + encodeURIComponent(value); } data = data.replace("/%20/g", "+"); } else if (typeof data === "object") { var arr = []; for (var name in data) { var value = data[name].toString(); name = encodeURIComponent(name); value = encodeURIComponent(value); arr.push(name + "=" + value); } data = arr.join("&").replace("/%20/g", "+"); } //若是使用get方法或JSONP,则手动添加到URL中 if (type === "get" || dataType === "jsonp") { url += url.indexOf("?") > -1 ? data : "?" + data; } } }
|
XMLHttpRequerst
- 创建XHR对象,并针对IE进行兼容性处理
- 调用XHR的open方法,设置请求的方法,请求的链接,是否异步
- 设置请求头
- 添加监听,如果成功则执行success函数,报错则执行error函数
- 调用XHR的send方法,发送数据。如果是get方法,我们已经通过setData方法把数据添加到URL中了,所以这里data设置为null
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
|
function createXHR() { //由于IE6的XMLHttpRequest对象是通过MSXML库中的一个ActiveX对象实现的。 //所以创建XHR对象,需要在这里做兼容处理。 function getXHR() { if (window.XMLHttpRequest) { return new XMLHttpRequest(); } else { //遍历IE中不同版本的ActiveX对象 var versions = ["Microsoft", "msxm3", "msxml2", "msxml1"]; for (var i = 0; i < versions.length; i++) { try { var version = versions[i] + ".XMLHTTP"; return new ActiveXObject(version); } catch (e) {} } } } //创建对象。 xhr = getXHR(); xhr.open(type, url, async); //设置请求头 if (type === "post" && !contentType) { //若是post提交,则设置content-Type 为application/x-www-four-urlencoded xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8"); } else if (contentType) { xhr.setRequestHeader("Content-Type", contentType); } //添加监听 xhr.onreadystatechange = function() { if (xhr.readyState === 4) { if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {
success(xhr.responseText); } else { error(xhr.status, xhr.statusText); } } }; //发送请求 xhr.send(type === "get" ? null : data); }
|
JSONP
- 创建script标签
- 设置回调函数名称
- 监听回调函数
- 设置URL,并添加到文档中
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
// 创建JSONP function createJsonp() { var script = document.createElement("script"), timeName = new Date().getTime() + Math.round(Math.random() * 1000), callback = "JSONP_" + timeName;
window[callback] = function(data) { document.body.removeChild(script); success(data); } script.src = url + (url.indexOf("?") > -1 ? "" : "?") + "callback=" + callback; script.type = "text/javascript"; document.body.appendChild(script); }
|
超时设置
- 设置一个全局的定时器标识,用来在回调函数中清除定时器
- JSONP
- 传入两个参数,一个是回调函数名,一个是script标签
- 超时之后,移除监听函数,移除script标签
- XHR
- 超时之后,调用XHR的abort方法,停止请求
- 由于执行abort()方法后,有可能触发onreadystatechange事件,所以设置一个timeout_bool标识,来忽略中止触发的事件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
var timeout_flag = null, //定时器标识 timeout_bool = false;//是否请求超时 //设置请求超时 function setTime(callback, script) { if (timeOut !== undefined) { timeout_flag = setTimeout(function() { if (dataType === "jsonp") { delete window[callback]; document.body.removeChild(script);
} else { timeout_bool = true; xhr && xhr.abort(); } console.log("timeout");
}, timeOut); } }
|
添加超时函数,并设置在回调成功后移除定时器
JSONP
1 2 3 4 5 6 7 8 9 10 11 12
|
// 创建JSONP function createJsonp() { …… window[callback] = function(data) { clearTimeout(timeout_flag); document.body.removeChild(script); success(data); } …… document.body.appendChild(script); setTime(callback, script); }
|
XHR
function createXHR() {
……
//添加监听
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (timeOut !== undefined) {
//由于执行abort()方法后,有可能触发onreadystatechange事件,
//所以设置一个timeout_bool标识,来忽略中止触发的事件。
if (timeout_bool) {
return;
}
clearTimeout(timeout_flag);
}
……
}
};
//发送请求
xhr.send(type === "get" ? null : data);
setTime(); //请求超时
}
全部代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138
|
function ajax(options) { //编码数据 function setData() { var name, value; if (data) { if (typeof data === "string") { data = data.split("&"); for (var i = 0, len = data.length; i < len; i++) { name = data[i].split("=")[0]; value = data[i].split("=")[1]; data[i] = encodeURIComponent(name) + "=" + encodeURIComponent(value); } data = data.replace("/%20/g", "+"); } else if (typeof data === "object") { var arr = []; for (var name in data) { var value = data[name].toString(); name = encodeURIComponent(name); value = encodeURIComponent(value); arr.push(name + "=" + value); } data = arr.join("&").replace("/%20/g", "+"); } //若是使用get方法或JSONP,则手动添加到URL中 if (type === "get" || dataType === "jsonp") { url += url.indexOf("?") > -1 ? data : "?" + data; } } } // JSONP function createJsonp() { var script = document.createElement("script"), timeName = new Date().getTime() + Math.round(Math.random() * 1000), callback = "JSONP_" + timeName;
window[callback] = function(data) { clearTimeout(timeout_flag); document.body.removeChild(script); success(data); } script.src = url + (url.indexOf("?") > -1 ? "" : "?") + "callback=" + callback; script.type = "text/javascript"; document.body.appendChild(script); setTime(callback, script); } //设置请求超时 function setTime(callback, script) { if (timeOut !== undefined) { timeout_flag = setTimeout(function() { if (dataType === "jsonp") { delete window[callback]; document.body.removeChild(script);
} else { timeout_bool = true; xhr && xhr.abort(); } console.log("timeout");
}, timeOut); } }
// XHR function createXHR() { //由于IE6的XMLHttpRequest对象是通过MSXML库中的一个ActiveX对象实现的。 //所以创建XHR对象,需要在这里做兼容处理。 function getXHR() { if (window.XMLHttpRequest) { return new XMLHttpRequest(); } else { //遍历IE中不同版本的ActiveX对象 var versions = ["Microsoft", "msxm3", "msxml2", "msxml1"]; for (var i = 0; i < versions.length; i++) { try { var version = versions[i] + ".XMLHTTP"; return new ActiveXObject(version); } catch (e) {} } } } //创建对象。 xhr = getXHR(); xhr.open(type, url, async); //设置请求头 if (type === "post" && !contentType) { //若是post提交,则设置content-Type 为application/x-www-four-urlencoded xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8"); } else if (contentType) { xhr.setRequestHeader("Content-Type", contentType); } //添加监听 xhr.onreadystatechange = function() { if (xhr.readyState === 4) { if (timeOut !== undefined) { //由于执行abort()方法后,有可能触发onreadystatechange事件, //所以设置一个timeout_bool标识,来忽略中止触发的事件。 if (timeout_bool) { return; } clearTimeout(timeout_flag); } if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {
success(xhr.responseText); } else { error(xhr.status, xhr.statusText); } } }; //发送请求 xhr.send(type === "get" ? null : data); setTime(); //请求超时 }
var url = options.url || "", //请求的链接 type = (options.type || "get").toLowerCase(), //请求的方法,默认为get data = options.data || null, //请求的数据 contentType = options.contentType || "", //请求头 dataType = options.dataType || "", //请求的类型 async = options.async === undefined && true, //是否异步,默认为true. timeOut = options.timeOut, //超时时间。 before = options.before || function() {}, //发送之前执行的函数 error = options.error || function() {}, //错误执行的函数 success = options.success || function() {}; //请求成功的回调函数 var timeout_bool = false, //是否请求超时 timeout_flag = null, //超时标识 xhr = null; //xhr对角 setData(); before(); if (dataType === "jsonp") { createJsonp(); } else { createXHR();
|