身为一个前端开发者,对ajax这一块需要非常的熟悉,而且现在一直在提的前后端分离,ajax起着非常重要的作用,本文将简单介绍一下ajax,并附上原生js的封装
what is Ajax?
Ajax即 Asynchronous JavaScript and XML ,翻译过来就是异步的JavaScript和XML,个人的理解即是一种与后端交互且不需要刷新整个页面的技术,以前前端要发数据给后端需要使用form表单来提交数据,每一次的提交就要刷新整个页面,而使用ajax,则可以在不刷新页面的同时发送数据给后端,并从后端接收数据,举个最常见的例子,登陆注册,不使用ajax的话,如果要把数据传给后端做验证,需要通过form表单,输入用户名,密码,然后提交,然后整个页面刷新,如果登入失败则返回登入页面,重新登陆,这时你以前在输入框里输入的账号和密码都一并消失(因为你刷新页面了),而如果使用ajax来对后端进行交互的话,则简单的多了,输入用户名,密码,然后利用ajax将数据传给后端,后端验证完数据,在给ajax一个响应,登陆是否成功,整个过程没有页面的刷新,除此之外使用ajax还可以动态验证账号是否存在,密码是否正确等等,当然这些的前端部分都是通过js来实现的。
ajax的最简单的理解就是请求和获得响应,即我前端使用ajax以GET或POST的方式向服务端请求数据,请求成功后服务端响应数据,然后前端接收到响应。
ajax的原生封装
不得不说关于ajax的库或npm包多的数不胜数,比如jquery,axios等,个人觉得axios比较好用,毕竟人家是专门做ajax的,不过我们还是得知道那些库的基本原理,所以我们来简单的实现一个自己的ajax库,首先所有的操作都依赖于XMLHttpRequest对象,该对象的基本api请看这里:https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest
来看一个基本的例子,
针对GET请求,其中url是你请求数据的路由
var xhr = new XMLHttpRequest();
//open(请求方法,请求的路由,是否异步,用户名,密码)
xhr.open("GET",url,true);
//可以使用timeout方法设置超时,即超过该时间没有取得响应则取消这次的请求
xhr.timeout = 2000;//
xhr.send();
//监听响应,onreadystatechange是一个事件
xhr.onreadystatechange = function () {
//这里的this就是上面的xhr
if(this.readyState === 4 && this.status === 200) {//这代表响应成功
console.log(this.responseText);//所有响应的数据都在responseText里,这是个string类型
}
};
对于get请求如果想要传数据给后端的话,需要将数据添加到url末尾,即url?data1=aaa&data2=bbb这种形式
针对POST请求
var xhr = new XMLHttpRequest();
//open(请求方法,请求的路由,是否异步,用户名,密码)
xhr.open("POST",url,true);
//可以使用timeout方法设置超时,即超过该时间没有取得响应则取消这次的请求
xhr.timeout = 2000;
//这里需要设置请求头,不然后端可能不认识
xhr.setRequestHeader("Content-type","application/x-www-form-urlencoded");
//使用send发送数据,数据格式为data1=5&data2=3
xhr.send("name=dpf&age=8");
//监听响应,onreadystatechange是一个事件
xhr.onreadystatechange = function () {
//这里的this就是上面的xhr
if(this.readyState === 4 && this.status === 200) {//这代表响应成功
console.log(this.responseText);//所有响应的数据都在responseText里,这是个string类型
}
};
有了这些基础,我们就可以自己封装一个ajax库了
ajax.js
!function (window, factory) {
//将factory的返回值(ajax函数)加到全局对象的ajax属性里
window.ajax = factory();
}(this, function () {
//将对象转换为querystring,例如{a:1,b:2} => a=1&b=2
function queryString(object) {
var string = "";
if (!isObject(object)) return string
for (var key in object) {
if (object.hasOwnProperty(key)) {
if (typeof object[key] === "object") {
string += key + "=" + JSON.stringify(object[key]) + "&"
} else {
string += key + "=" + object[key] + "&"
}
}
}
return string.substr(0, string.length - 1)
}
//判断是否为string
function isString(str) {
return typeof str === "string"
}
//判断是否为对象
function isObject(obj) {
return obj && typeof obj === "object" && Object.prototype.toString.call(obj) === "[object Object]"
}
//判断是否为函数
function isFunction(fn) {
return typeof fn === 'function';
}
//ajax函数url: 路由, config:配置对象, callback回调函数
function ajax(url, config, callback) {
if (!isString(url) || !isObject(config)) throw new Error("arguments error");
var data = config.data || null; //发给后端的数据
var method = config.method || "GET"; //方法GET or POST
var async = config.async || true; //是否异步
var dataType = config.dataType || "json"; //期望接收的数据类型
var timeout = config.timeout || Infinity; //超时
var isGet = Boolean(method === "GET"); //是否为GET
var isPost = Boolean(method === "POST"); //是否为POST
var qsData = queryString(data); //将传入的数据对象转换为字符串形式
var xhr = new XMLHttpRequest();
url = ajax.baseUrl ? ajax.baseUrl + url : url;//路由为基路由与url相加
xhr.type = dataType; //给xhr设置个type属性
xhr.timeout = timeout; //设置超时
xhr.open(method,
isGet && qsData ? (url += "?" + qsData) : url //如果是GET方法且data不为null,则将data对象转换为字符串形式然后加到url末尾
);
isPost && xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); //如果是POST方法,执行&&后面的代码
xhr.send(isPost ? qsData : null); //如果是POST方法,将数据发送出去
xhr.onreadystatechange = function () {
//得到响应
if (this.readyState === 4 && this.status === 200) {
switch (this.type) {
case "json": {
isFunction(callback) && callback(JSON.parse(this.responseText))
break
}
case "text": {
isFunction(callback) && callback(this.responseText)
break
}
case "xml": {
isFunction(callback) && callback(this.responseXML)
break
}
default: {
break
}
}
}
}
}
//便捷方法
ajax.get = function (url, data, callback) {
return this(url, {
method: "GET",
async: true,
data: data,
dataType: "json"
}, isFunction(callback) && callback)
};
//便捷方法
ajax.post = function (url, data, callback) {
return this(
url, {
method: "POST",
async: true,
data: data,
dataType: "json"
},
isFunction(callback) && callback
)
};
//设置基路由
ajax.baseUrl = "";
return ajax
});
使用node.js编写个后端来测试ajax函数
test.js
const http = require("http");
const queryString = require("querystring");
const url = require("url");
const app = http.createServer((req, res) => {});
//处理post数据
async function getPostData(req) {
let data = "";
for await (let chunk of req) {//最新的异步迭代器
data += chunk;
}
return queryString.parse(data);
}
app.on("request", async (req, res) => {
let method = req.method;
let pathname = url.parse(req.url).pathname;
res.writeHead(200, {"Content-type": "application/json","Access-Control-Allow-Origin":"*"});
switch (pathname) {
case "/get/api": {
if (method === "GET") {
let reqData = queryString.parse(url.parse(req.url).query);
res.end(JSON.stringify({err: false, reqData, data: [1, 2, 4]}));
}
break
}
case "/post/api": {
if (method === "POST") {
let reqData = await getPostData(req);
res.end(JSON.stringify({err: false, reqData, data: [2, 2, 2, 2, 2]}))
}
break
}
default : {
break
}
}
});
app.listen(3000, () => console.log("listen in 3000"));
做一个简单的测试
a.html
<!DOCTYPE html>
<html lang="en">
<head>
<title>Title</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="ajax.js"></script>
</head>
<body>
</body>
<script>
!function (window) {
var ajax = window.ajax;
ajax.baseUrl = "http://localhost:3000";
ajax("/get/api", {
method: "GET",
async: true,
data: {name: "dpf", sex: "max"},
dataType: "json"
}, console.log);
ajax.get("/get/api", {name: "aaa", sex: "ppp"}, console.log);
setTimeout(function () {
ajax("/post/api", {
method: "POST",
data: {name: "dhq", sex: "man", age: 10}
}, console.log);
ajax.post("/post/api", {name: "aaa", sex: "fuxnn"}, console.log);
}, 1000);
}(this)
</script>
</html>
浏览器打开a.html,打开控制台,可以看到响应的数据
上面是使用了回调的形式来接收响应的数据,还可使用es6的Promise对象,生成器,async/await来写,其实原理都一样,都是使用XMLHttpRequest对象,更多的写法可以参考我的这篇博客:https://mp.csdn.net/postedit/83105098(JS实现异步的5种方式)
也可以使用es6的class语法封装成一个类,下面是将上面的ajax代码变为一个类的代码
ajax_class.js
!function (window) {
function queryString(object) {
let string = "";
if (object === null || typeof(object) !== "object") return string;
Object.keys(object).forEach(key => {
if (typeof object[key] === "object") {
string += key + "=" + JSON.stringify(object[key]) + "&"
} else {
string += key + "=" + object[key] + "&"
}
});
return string.substr(0, string.length - 1)
}
class Ajax {
constructor({baseUrl = "", timeout = Infinity,async = true} = {}) {
Object.assign(this,{baseUrl,timeout,async});
this._xhr = new XMLHttpRequest();
}
static create({baseUrl = "", timeout = Infinity,async = true} = {}){
return new Ajax({baseUrl,timeout,async})
}
ajax(url,{data = null, method = "GET", dataType = "json", timeout = null} = {}) {
//返回一个Promise对象
return new Promise((resolve, reject) => {
this._xhr.timeout = timeout || this.timeout;
let isGET = Boolean(method === "GET");
let isPOST = Boolean(method === "POST");
let qsData = queryString(data);
url = this.baseUrl + url;
this._xhr.open(method,
isGET && qsData ? `${url}?${qsData}` : url,
this.async
);
isPOST && (this._xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded"));
this._xhr.send(isPOST ? qsData : null);
this._xhr.onreadystatechange = () => {
if (this._xhr.readyState === 4) {
if (this._xhr.status === 200) {
switch (dataType) {
case "json":{
resolve(JSON.parse(this._xhr.responseText));
break
}
case "text":{
resolve(this._xhr.responseText);
break
}
case "xml":{
resolve(this._xhr.responseXML);
break
}
}
} else {
reject(new Error("it has error"))
}
}
}
})
}
get(url,data = {}) {
return this.ajax(url,{data})
}
post(url,data = {}) {
return this.ajax(url,{data,method:"POST"})
}
}
window.Ajax = Ajax;
}(this);
使用的话只需这样就ok了
async function main() {
const ajax = Ajax.create({baseUrl:"http://localhost:3000"});
let data = await ajax.get("/get/api",{name:"dpf",sex:"AAAA"});
console.log(data);
}
main();