本篇文章将会涵盖Ajax的的创建、使用、与服务器的交互、ajax封装,ajax的send发送类型,请求头与响应头,readystatechange来进行讨论。
1.浅谈Ajax
1.1、web渲染方式
在谈Ajax之前,先说一下web的渲染方式,页面渲染方式分为ssr—服务端渲染,与scr—客户端渲染:
ssr(服务端渲染):
简单理解就是浏览器发送请求后,服务器把客户端网页和数据在后台渲染解析,之后把渲染后的结果返回客户端。也就是说,网页是跳转式的,会造成页面的刷新
csr(客户端渲染):
浏览器发送页面请求,服务器返回的是一个模板页面,浏览器从上至下解析过程中需要发送ajax请求获取数据,最后再调用模板引擎(art-template等)渲染HTML结构,并把渲染后的结果添加到页面指定容器中。。。。网上给出的解释,就是不跳转刷新页面。
ssr与csr各有优缺点,根据需求来决定怎么用吧,具体不谈了,毕竟今天主要是Ajax。
1.2、Ajax
说了这么一大堆,其实就是想说Ajax是csr客户端渲染,使用Ajax可以实现:
- 不刷新页面更新网页
- 在页面加载后从服务器请求与接收数据
- 在后台向服务器发送数据
- 用于在后台与服务器交换数据
创建Ajax顺序
我们与服务器通信的API就是XMLHttpRequest(为什么叫XML,可以去搜一下挺有意思的)
- 新建XMLHttpRequest对象
- let xhr = new XMLHttpRequest();
- IE7以下 let xhr = new ActiveXOBject('Microsoft.XMLHTTP');
- 打开要发送的地址通道,
- xhr.open('GET',地址,同步/异步)
- 给XMLHttpRequest对象增加侦听器,用来侦听各种情况,包括各种链接状态
- xhr.addEventListener('load',侦听函数)
- 如果没有要发送的内容,直接send()
- xhr.send();
2.Ajax创建与使用
先看一下Ajax的使用,再进行详细分析
首先用node创建一个服务器,用于处理Ajax使用get/post发送的内容
const http = require('http');
//querystring中有node自带的解析字符串的方法,不过现在这个方法已经弃用,暂时先用着
const querystring = require('querystring')
http.createServer(async function(req,res){
res.writeHead(200,{
"Content-Type":"text/html;charset=utf-8",
//设置ajax跨域能够被谁访问,*代表所有(先这样写)
"Access-Control-Allow-Origin":"*"
});
let body,params;
params=req.url.split('?')[1];
//如果params有内容,则是get发送;接收到的信息需要解码,再通过自带的解析字符串方法进行解析
if(params&¶ms.trim()) params=querystring.parse(decodeURIComponent(params));
//req.method:获取请求方式
if(req.method.toLowerCase()==='post'){
body = await getData(req);
try {
body = JSON.parse(body);
} catch (e) {
body = querystring.parse(decodeURIComponent(body))
}
}
//服务器返回给请求客户端的内容,如果不是传递的字符串,就需要使用JSON.stringify转换为JSON字符串型
//返回转换好的内容,测试是否获取成功
params ? res.end(JSON.stringify(params)) : res.end(JSON.stringify(body));
}).listen(4010);
//处理由post传入的数据
function getData(req) {
return new Promise(function (resolve, reject) {
var data = ""
//监听事件,data为数据接收过程的事件,参数就是接收的post传过来的内容
req.on("data", function (_data) {
//传入的数据是一个Buffer二进制流,可以使用强转为字符串,转换格式
data += _data;
});
//接收完成后,将数据返回出去
req.on("end", function () {
resolve(data);
})
})
}
2.1、Ajax使用GET向服务端发送
var xhr = new XMLHttpRequest();
//load先暂时使用,稍后使用readystatechange
xhr.addEventListener('load',loadHandler);
xhr.open('GET','http://10.9.46.232:4010?age=22');//open为地址栏传参
xhr.send();//
function loadHandler(e){
console.log(xhr.response);//打印服务器返回的值
//{"age":"22"}
}
2.2、Ajax使用POST向服务器发送内容
let xhr = new XMLHttpRequest();
xhr.addEventListener('load',loadHandler);
xhr.open('POST','http://10.9.46.232:4010?age=30');
xhr.send("tel=15153808888&email=121@qq.com");
function loadHandler(e){
console.log(xhr.response);
}
3.send可以发送的数据类型
这的send就是xhr中的send,可以发送多种数据类型,其中有一些易于理解的就只列出不进行讨论了
3.1、DOMString data——文本数据(忽略)
3.2、Document data——文档对象(忽略)
3.3、ArrayBuffer data——类型化数组
ArrayBuffer是不能直接进行使用的,需要使用Uint8Array、Uint16Array等进行传送,并且post传送的内容虽然不会暴露在地址栏中,但是还是可以在网络中查找到发送的内容,所以,可以在客户端发送之前进行一个小小的加密,到服务端再进行解密
Uint8Array
正整形8位数组,可以当做一个ASCII码数组,最大只到255,输入非数值就是0,非整数会去掉小数部分,输入负数就会产生借位(-1 就是 255),用来传递数字、字符
小案例:
//index.html
//str为要发送的数据
let str ='i love javascript';
//取出每一位,转换为UniCode编码格式,并使用异或100小小加密
let arr=str.split('').map(item=>{
return item.charCodeAt(0)^100;
})
//将数组存入Uint8Array中
arr =new Uint8Array(arr);
var xhr=new XMLHttpRequest();
xhr.addEventListener("load",loadHandler);
xhr.open("POST","http://localhost:4010");
xhr.send(arr)//通过小黄人(xhr)发出
function loadHandler(e){
console.log(xhr.response);
}
//node服务器中,使用上边那个node服务器接收post数据就可,只需要把获取数据的内容修改一下,我直接书写要修改的内容
function getData(req) {
return new Promise(function (resolve, reject) {
var data = "";
req.on("data", _data => {
// Unit8Array解析和异或
for (var i = 0; i < _data.length; i++) {
//发送过来的数据,为二进制流数据,首先进行异或100解密,再使用fromCharCode将UniCode格式转换
data += String.fromCharCode(_data[i] ^ 100);
}
req.on("end", () => {
console.log(data)
resolve(data);
})
})
})
}
//网络截取到的post发送数据内容:DD
//服务解析后的内容:i love javascript
Unit16Array
可以用来发送汉字,正整形16位数组,将字符串转换为这种数组,发送到服务端,再由服务端进行解析,发送过去的数据每一个会被拆分成两个 8 位的,并且位置会颠倒,例如'一',发送过去就是 00 4e,传送过去的都会被转换成8位一组的十六进制数据
案例:
//index.html
var str="你好我是中文格式";
var arr=str.split("").map(item=>{
// 将字符串中的字符转换为Unicode编码格式的数据,并进行异或操作
return item.charCodeAt(0)^100;
})
arr=new Uint16Array(arr);
var xhr=new XMLHttpRequest();
xhr.addEventListener("load",loadHandler);
xhr.open("POST","http://localhost:4020");
xhr.send(arr)
function loadHandler(e){
console.log(xhr.response);
}
//node服务器接收
for (var i = 0; i < _data.length; i += 2) {//二进制流
//汉字接收有些麻烦,解释起来太多了,可以分解打印看一下
data += String.fromCharCode(parseInt("0x" + _data[i + 1].toString(16).padStart(2, 0) + _data[i].toString(16).padStart(2, 0), 16) ^ 100);
}
3.4、Blob data——二进制大对象
文件发送类型,像form表单中的file发送,可以使用files获取到file中的文件列表,并通过Blob data发送,但是服务器解析困难,后期可以使用fileReader进行处理。。。
3.5、FormData data——数据对象
获取的数据时form中的每一项的name值对应的value所组成的迭代器,Ajax可直接send发送,但是服务器在解析后,会多出许多其他信息,未来有插件进行解决FormData解析问题,现阶段,因为是迭代器,可取出内容,转换为对象进行发送
还有一点,在未来,使用Vue和react不允许使用,因为Vue和react是操作虚拟Dom的,不可直接操作Dom,所以,不能使用FormData
现阶段设置FormData内容,
let o ={a:1,b:2,c:3}
let fd = new FormData();
for(var key in o){
fd.set(key,o[key]
}
fd中的方法:delete,entries,forEach,get,getAll,has,heys,set,values
4.请求头,响应头
嫖一下大神的图,进行解释,感谢感谢。。。
- 请求头的设置,必须放在open之后,send之前,请求头中的数据比send更早到达服务器,可以将一些数据更早的稿纸服务器,来验证是否可以继续发送后面的数据
- xhr.setRequestHeader(请求头名称,请求头值);
xhr.open("POST","http://localhost:4020"); xhr.setRequestHeader("Content-Type","application/x-www-form-urlencoded"); xhr.send();
- 服务器获取请求头
- req.headers;
- 打印内容即为设置的请求头,headers相当于一个对象,里面有很多信息,包括传过来的所有请求头,可以将指定的值取出来
- 发送非指定请求头会报错,想要发送,必须在服务器设置允许请求头跨域
res.writeHead(200,{ "Content-Type":"text/html;charset=utf-8", "Access-Control-Allow-Origin":"*",//Ajax跨域设置 "Access-Control-Allow-Headers":"*",// 请求头跨域 })
设置自定义请求头
自定义的请求头,首字母必须以X开头,并且用-连接单词,每个单词首字母大写
服务端获取指定的请求头
console.log(req.headers["x-ids"]);
传过来接收的时候都是小写字母
设置自定义响应头
res.writeHead(200,{ "Content-Type":"text/html;charset=utf-8", "Access-Control-Allow-Origin":"*",//Ajax允许跨域 "Access-Control-Allow-Headers":"*",// 请求头跨域 "Access-Control-Expose-Headers":["X-Name","X-Age"],// 允许发送的响应头名称,没有的设置了也发不过去 "X-Name":"xiaowang", // 自定义响应头,命名规则和请求头一样 "X-Age":25 })
客户端接收响应头
// Ajax中侦听的 load 事件的处理函数 function loadHandler(e){ console.log(xhr.getAllResponseHeaders()); // 所有的响应头,不包括那几个Access 的权限设置的响应头 console.log(xhr.getResponseHeader("x-age")) // 单独获得其中的一个部分 } //getResponseHeader获取头部指定的属性值 //getAllResponseHeaders获取所有头部属性 //setRequestHeader 设置头部属性,必须在open以后,send以前写入 //如果需要获取服务端返回的自定义响应头,需要服务器设置 res.setHeader("X-Session-Code", "aaa"); res.setHeader(Access-Control-Expose-Headers","x-Session-Code")
5.readystatechange事件
load事件为加载完成后执行,ajax发送请求是有过程的,所以,使用load不能满足需求,可使用readystatechange,请求一般分为4步,第2步时,响应头到达
-
使用这个事件代替load事件
xhr.addEventListener("readystatechange",readystateHandler);
// 我们认为请求消息一共有 5 步
值 状态 描述
0 UNSENT 代理被创建,但尚未调用open()方法
1 OPENED open()方法已经被调用
2 HEADERS_RECEIVED send()方法已经被调用,并且头部和状态已经可获得
3 LOADING 下载中;responseText 属性已经包函部分数据
4 DONE 下载操作已完成
// 这个事件在通信的时候一共会触发四次
1. 发送请求,请求头被发送
2. 响应头消息传输过来
3. 下载响应消息,服务端发送的数据
4. 下载完成,load 事件就是在这一步触发
status状态:1XX:信息,2XX:成功,3XX:重定向,4XX:客户端错误,5XX:服务器错误
function readystateHandler(e){
// console.log(xhr.status,xhr.readyState,xhr.getAllResponseHeaders())
if(xhr.status===200 && xhr.readyState===2){ // status 响应头中的状态码, readyState 触发事件时所处的状态
console.log(xhr.getAllResponseHeaders());
// xhr.abort();
}else if(xhr.status===200 && xhr.readyState===4){
console.log("下载完成",xhr.response)
}else if(xhr.status!==200 && xhr.readyState===2){
}
console.log("aa")
}
6.Ajax封装
async function init() {
// var res=await ajax("http://localhost:4020");
var res = await ajax("http://localhost:4020", { method: "post", body: JSON.stringify({ a: 10 }) })
console.log(res)
}
// ajax封装,传入参数:地址,对象:请求方式,默认get,内容,请求头,
function ajax(url, { method = "get", body = null, headers = null } = { method: "get" }) {
return new Promise(function (resolve, reject) {
// 创建ajax,在open中放入请求方式与地址
var xhr = new XMLHttpRequest();
xhr.open(method, url);
// 请求头,写在open之后,send之前,如果有请求头,则拿出设置请求头
if (headers) Object.keys(headers).forEach(k => {
xhr.setRequestHeader(k, headers[k]);
})
// 如果body存在,发送出body,如果不存在不发送
body ? xhr.send(body) : xhr.send();
xhr.onreadystatechange = function () {
// 请求已经完成并且成功
if (xhr.readyState === 4 && xhr.status === 200) {
var d;
try {
d = JSON.parse(xhr.response);
} catch (e) {
d = xhr.response;
}
resolve(d)
} else if (xhr.readyState === 2 && ~~(xhr.status / 100) === 4) {
reject(xhr.status);
}
}
xhr.onerror = function (e) {
reject(e)
}
})
}