1. AJAX简介
1.1 什么是AJAX
ajax的全拼为async javascript and xml异步的JS和XML
1.2 xml(可扩展的标记语言)
作用是用来存储数据的(通过自己扩展的标记名称清晰地展示出数据结构)
ajax之所以成为异步的js和xml,主要原因是:当初最开始使用ajax实现客户端和服务端数据通信的时候,传输数据格式一般都是xml格式的数据,我们把它称之为异步js和xml(现在一般都是基于JSON格式来进行数据传输的)
比如传输数据格式如下
<?xml version="1.0" encoding="UTF-8" ?>
<root>
<student>
<name>张三</name>
<age>25</age>
<score>
<english>90</english>
<math>100</math>
<chinese>97</chinese>
</score>
</student>
<student>
<name>张三</name>
<age>25</age>
<score>
<english>90</english>
<math>100</math>
<chinese>97</chinese>
</score>
</student>
</root>
1.3 异步的JS
这里的异步不是说ajax只能基于异步进行请求(虽然建议都使用异步编程),这里的异步指的是局部刷新
2 前后端分离
前后端分离的导火线就是全局刷新和局部刷新
2.1 全局刷新(前后端不分离)
全局刷新:当页面中只有一个部分数据需要改变时,都需要服务端把整个页面全部渲染,最后客户端浏览器整体刷新页面看到效果
- 在非完全前后端分离的项目中,前端开发只需要完成页面的制作,并且把一些基础的人机交互效果使用js完成即可
- 页面中需要动态呈现内容的部分,都是交给后台开发人员做数据绑定和基于服务器进行渲染的(服务器渲染) 优势
- 动态战士的数据在页面的源代码中课件,有利于SEO优化推广(有利于搜索引擎的收录和抓取)
- 从服务器端获取的结果就已经是最后要呈现的结果了,不需要客户端做额外的事情,所以页面加载速度快(前提是服务器端处理的速度够快),所以类似京东,淘宝这类网站,首屏数据一般都是经由服务器端渲染的
劣势
- 如果页面中存在需要实时更新的数据,每一次想要展示最新数据,页面都需要重新的刷新一次,这样肯定不行
- 都交给服务器端做数据渲染,服务器端的压力大,如果服务器处理不过来,页面显现的速度会更慢
- 这种模式不利于开发,(开发效率低)
2.2 局部刷新(前后端分离)
前后端完全分离的项目,页面中需要动态绑定的数据是交给客户端完成渲染的
过程
- 向服务器发送AJAX请求
- 把从服务器端获取的数据解析进行处理,拼接成我们需要的HTML字符串
- 把拼接好的字符串替换页面中某一部分的内容(局部刷新),页面整体不需要重新加载,局部渲染即可
优势
- 我们可以根据需求,任意修改页面中某一部分的内容(例如实时刷新),整体页面不刷新,性能好,体验好(所有表单验证,需要实施刷新的需求都要局域AJAX实现)
- 有利于开发,提高开发效率
- 前后端分离,后端不需要考虑前端是如何呈现的,前端也不需要考虑后台使用什么技术,真正意义上实现了技术划分
- 可以同时进行开发:项目开发开始,首先指定前后端数据交互的接口文档(文档中包含了,调取那个接口或者那些数据等协议规范),后台把接口写好(目前很多公司也需要前端自己拿NODE来模拟这些接口
弊端
- 不利于SEO优化:第一次从服务器端获取的内容不包含需要动态绑定的数据,所以页面的源代码中没有这些内容,不利于SEO收录,后期经过JS添加到页面中的内容,并不会卸载页面的源代码中(是源代码不是页面结构)
- 交由客户端进行渲染,首先需要把页面呈现,然后再通过JS的异步AJAX请求获取数据,然后数据绑定,浏览器把动态数据增加的部分重新渲染,无形中浪费了一些时间,没有服务器端渲染页面呈现的速度快
3 AJAX参数详解
3.1 创建一个AJAX对象
步骤
- 创建AJAX对象
- 打开请求地址
- 监听AJAX状态改变,获取相应信息
- 发送AJAX请求
代码如下
// 创建AJAX对象
let xhr = new XMLHttpRequest(); //不兼容IE6及更低版本的浏览器(IE6:ActiveXObject)
//打开请求地址(可理解为一些基础配置,但是并没有发送请求)
xhr.open([method],[url],[async],[user_name],[user_password]);
// 监听AJAX状态改变,获取相应信息(获取响应头信息,获取响应主体信息)
xhr.onreadystatechange=()=>{
if(xhr.readyState === 4 && xhr.status === 200){
let result = xhr.responseText;//获取相应主题中的内容
}
}
// 发送AJAX请求(括号中的内容就是请求的主题内容)
xhr.send(null);
3.2 配置(open)参数
xhr.open([method],[url],[async],[user_name],[user_password])
3.2.1 [method]请求参数
请求方式
- GET系列的请求
- get: 从服务端获取数据
- delete: 从服务器上删除某些数据
- POST请求方式
- post: 提交数据
- put: 向服务器中增减指定的数据
- 无论是哪一种请求方式,客户端都可以把信息传递给服务器,服务器也可以把信息返回给客户端
- GET系列一般以获取为主(给的少,拿得多)
- POST系列一般以推送为主(给的多,拿得少)
GET VS POST
- GET请求传递给服务器的内容一般没有POST请求传递给服务器的多
- 原因:GET请求传给服务器内容一般都是基于
url地址问号传参来实现的,而POST请求一般都是基于设置请求主体来实现的。各浏览器都有自己的关于URL最大长度限制(谷歌:8K,火狐:7k,IE:2K)超过限制长度的部分,浏览器会自动截取掉,导致传递给服务器的数据缺失。理论上POST请求通过主体传递是没有大小限制的,真实项目中为了保证传输的速率,我们也会限制大小(例如:上传的资料或者图片我们会做大小的限制)
- 原因:GET请求传给服务器内容一般都是基于
- GET请求很容易出现你缓存(这个缓存不可控:一般我们不需要),而POST不会出现缓存(除非做特殊处理)
setTimeout(()=>{
$.ajax({
url:'getList?lx=news',
...
success=>{
// 第一次请求数据回来,间隔一分钟后,浏览器又发送一次请求,
// 但是发送新的请求,不管是地址还是传递的参数都和上一次一样
// 浏览器很有可能会把上一次数据获取,而不是获取最新数据
}
},60000);
})
- 解决方案: 每一次重新请求的时候,在URL的末尾追加一个随机数,保证每一次请求的地址不完全一致,就可以避免是从缓存中读取的数据
setTimeout(()=>{
$.ajax({
url:'getList?lx=news&_='+Math.random(),
...
success=>{
}
},60000);
})
- GET请求没有POST请求安全(POST也并不是十分安全,只是相对安全)
- 原因:还是因为GET是URL传参给服务器(有一种比较简单的黑客技术:URL劫持,也就是可以把客户端传递给服务器的数据劫持掉,导致信息泄露)
3.2.2 [url]请求地址
请求后台接口的地址(API接口)
3.2.3 [async]异步
- ASYNC异步
- SYNC同步
- 设置当前AJAX请求时异步还是同步的,不写默认是异步(TRUE)
- 如果设置为FALSE,则代表当前请求时同步
3.2.4 [user_name],[user_password]用户名和密码
- 这两个参数一般不用
- 如果请求的URL地址所在的服务器设定了访问权限,则需要我们提供可通信的用户名密码才可以(一般服务器都是可以允许匿名访问的)
3.3 AJAX状态码
描述当前AJAX操作的状态的xhr.readyState
- 0 :UNSENT 未发送,只要创建一个AJAX对象,默认就是0
- 1 :HEADERS_RECEIVED当前AJAX的请求已经发送,并且已经接收到服务器端返回的响应头信息了
- 3 :LOADING响应主体内容正在返回的路上
- 4 :DONE响应主体内容已经返回给客户端
3.4 HTTP网络状态
HTTP网络状态码:记录了当前服务器返回的状态(xhr.status)
200: 成功,一个完整的HTTP事务完成(以2开头的状态码一般都是成功)
以3开头的一般也都是成功,只不过服务器做了特殊处理
301:Moved Permanently 永久重定向
302:Moved temporarily 临时重定向(新的HTTP版本中任务307是临时重定向)一般用于服务器负载均衡:当前服务器处理不了,我们把当前请求临时交给其他的服务器处理(一般图片请求经常出现302,很多公司都有单独的图片服务器)
304: Not Modified 从浏览器缓存中获取数据把一些不经常更新的文件或者内容缓存到浏览器中,下一次从缓存中获取,减轻服务器压力,也提高页面加载速度
一般4开头的都是失败,而且浏览器的问题偏大
400:请求参数错误
401:无权限访问
404:访问地址不存在
以5开头的一般都是失败,而且服务器的问题偏大
500:Internal Server Error未知的服务器错误
503:Service Unavailable服务器负载
3.5 AJAX参数及方法
运行下面代码查看AJAX属性及方法
let xhr = new XMLHttpRequest();
dir(xhr)
结果如图
3.5.1 相关属性
- readState:存储的是当前AJAX的状态码
- response/responseText/responseXML:都是用来接受服务器返回的响应主体中的内容,只是根据服务器返回内容格式不一样,我们使用不同的属性接收即可
- responseText是最常用的,接收到的内容格式是字符串格式的(一般服务器返回的数据都是JSON格式字符串)
- responseXML偶尔会用到,如果服务器端返回的是XML文档数据,我们需要使用这个属性接收
- starus:记录了服务器返回的HTTP状态码
- statusText:返回状态码的描述
- timeout:设置当前AJAX请求的超时时间,假设我们设置的时间为3000(MS),从AJAX请求发送开始,3秒后响应主体内容还没有返回,浏览器就会把当前的AJAX请求任务强制断开
3.5.2 相关方法
- abort():强制中断AJAX请求
- getAllResponseHeaders():获取指定属性名的响应头信息,例如xhr.getResponseHeader(‘date’)获取响应头中存储的服务器时间
- open():打开一个URL地址
- overrideMimeType():重写数据的MIME类型
- send():发送AJAX请求(括号中书写的内容是客户端基于请求主体把信息传递给服务器)
- setRequesHeader(key,value):设置请求头信息(可以是设置的自定义请求头)
3.5.3 相关事件
- onabort:当AJAX中断,会触发这个事件
- onreadystatechange:当AJAX状态发生改变,会触发这个事件
- ontimeout:当AJAX请求超时,会触发这个事件
3.5.4 代码示例
let xhr = new XMLHttpRequest();
xhr.open('get','url?_='+Math.random(),true);
// xhr.setRequestHeader('cookie','培训')设置请求头内容不能出现中文
// 并且header的key不能使cookie
// 请求头的设置必须在OPEN之后SEND之前
xhr.setRequestHeader('aaa','xxx');
// 设置请求超时时间
xhr.timeout = 10;
// 当请求超时触发的方法
xhr.ontimeout = ()=>{
console.log('请求已超时');
xhr.abort();
}
// 当请求状态发生改变是触发方法
xhr.onreadystatechange=()=>{
let {readyState:state,status}=xhr
// 如果跳过这个if没有return则说明成功了
if(!/^(2|3)\d{2}$)/.test(status)) return;
// 在状态为2的时候就可以获取响应头信息
if (state === 2){
let headerAll = xhr.getAllResponseHeaders(),
//获取的是服务器时间是格林尼治时间(相比于北京时间差了8小时),通过new Date可以把这个时间转换为北京时间
serverDate = xhr.getResponseHeader('data');
console.log(headerAll,new Date(serverDate));
return;
}
// 在状态为4的时候响应主体已经回来了
if (state === 4){
//获取的结果一般是JSON字符串(可以使用JSON.PARSE把其转换为JSON对象)
let valueText = xhr.responseText,
//获取到的结果是SML格式的数据(可以通过XML的一些常规操作获取存储的信息)
//如果服务器返回的是XML文档,responseText获取的结果是字符串而responseXML获取的是标准XML文档
valueXML = xhr.responseXML;
console.log(valueText,valueXML);
return;
}
}
// 发送请求
xhr.send('name=zxt&age=28&sex=man');
3.6 AJAX同步异步详解
同步与异步的差别在于xhr.open的时候是否添加异步参数
3.6.1 同步
对比以下两段代码
let xhr = new XMLHttpRequest();
xhr.open('get','temp.json',false);
xhr.onreadystatechange = ()=>{
console.log(xhr.readyState);// 只输出一次结果
}
xhr.send();
let xhr = new XMLHttpRequest();
xhr.open('get','temp.json',false);
xhr.send();
xhr.onreadystatechange = ()=>{
console.log(xhr.readyState); // 一次也不会输出
}
- 两段代码xhr.send(); 放的位置不同导致差异
- 原因:
- [同步]开始发送AJAX请求,开启AJAX任务,在任务没有完成之前,什么事情都做不了,下面绑定事件也做不了
- 绑定方法之前状态已经为4了,此时AJAX的状态不会再改变成其他值,所以事件永远不会触发
- 使用AJAX同步编程,不要把SEND放在事件监听前,这样我们无法在绑定的方法中获取到响应主题内容
3.6.1 异步
对比下面两段代码
let xhr = new XMLHttpRequest();
xhr.open('get','temp.json');
xhr.onreadystatechange = ()=>{
console.log(xhr.readyState);
}
xhr.send();
// 输出结果分别是2,3,4
let xhr = new XMLHttpRequest();
xhr.onreadystatechange = ()=>{
console.log(xhr.readyState);
}
xhr.open('get','temp.json');
xhr.send();
// 输出结果分别是1,2,3,4
- 两段代码的区别为xhr.onreadystatechange的位置不同
- 第一段代码先open设置为异步,此时还没有进行监听
- 等监听是状态已经改变为2了
4 JS中常用的编(解)码方法
4.1 正常编码解码
4.1.1 escape/unescape
- window自带的方法
- 主要是把中文汉字进行编码和解码
- 一般只有JS语言支持,也经常应用于前端页面通信的时候中文编码问题
let str = '培训@163.com'
escape(str);
// 显示"%u57F9%u8BAD@163.com"
unescape('%u57F9%u8BAD@163.com')
// 显示"培训@163.com"
4.1.2 encodeURI/decodeURI
- window自带的方法
- 把中文汉字进行编码和解码
let str = '培训@163.com';
encodeURI(str); // "%E5%9F%B9%E8%AE%AD@163.com"
decodeURI('%E5%9F%B9%E8%AE%AD@163.com')// "培训@163.com"
4.1.3 encodeURIComponent/encodeURIComponent
let str = '培训@163.com';
encodeURIComponent(str);// "%E5%9F%B9%E8%AE%AD%40163.com"
encodeURIComponent('%E5%9F%B9%E8%AE%AD%40163.com');// "培训@163.com"
4.1.4 encodeURI区别encodeURIComponent
- encodeURI不处理特殊字符\
- encodeURIComponent处理特殊字符\
- 我们URL问号传递参数的时候,我们传递的参数还是一个URL或者包含很多特殊字符,此时为了不影响主要的URL,我们需要吧传递的参数值进行编码,使用encodeURI不能编码一些特殊字符,所以只能使用encodeURIComponent处理
4.2 加密方式进行编码解码
- 可逆转加密(一般都是团队自己定义的规则)
- 不可逆加密(一般都是基于MD5加密完成的)
- MD5加密是不可逆的,网上解析是通过匹配大数据,能解析出常用的加密,不常用的是无法解析的
5 AJAX的实现
5.1 Jquery中的AJAX
$.ajax({
url:'xxxx', // 请求的API地址
method:'get',// 请求方式GET/POST。。,在老的版本中使用的是type使用type和method实现的是相同效果
dataType:'json',// dataType知识我们预设获取结果的类型,不会影响服务器的返回
// (服务器一般给我们返回的都是JSON格式字符串),如果我们预设的是json,那么类库中将把服务器返回的字符串转换为json对象
// 如果我们预设的是text(默认值),我们把服务器获取的结果直接拿过来即可,我们预设的值还可以是xml等
cache:false, // 设置是否清除缓存,只对GET系列请求有作用,默认是TRUE不清除缓存,手动设置为FALSE,JQ类库
// 会在请求URL的末尾追加一个随机数来清除缓存
data:null, //我们通过DATA可以吧一些信息传递给服务器;GET系列请求会把DAT中的内容拼接在URL的末尾,
// 通过问号传参的方式传递给服务器,POST系列请求会把内容放在请求主体中传递给服务器,DATA的值可以设置为两种格式
//字符串,对象,如果是字符串,设置的值是什么传递给服务器的就是什么,如果设置的对象,
// JQ会把对象变为xxx=xxx&xxx=xxx这样的字符串传递给服务器
async:true,// 设置同步或者异步,默认是TRUE代表异步,FALSE是同步
success:function() {
// 当AJAX请求成功(readyState===4&status是2或3开头的)
// 请求成功后JQ会把传递的回调函数执行,并且把获取的结果当做实参传递给回调函数(result就是我们从服务器获取的结果)
},
error:function() {
// 请求错误触发回调函数
},
complate:function() {
// 不管是否请求错误还是正确,都会触发回调函数(他是完成)
}
})
5.2 手动封装AJAX
~function() {
class ajaxClass{
init(){
let xhr = new XMLHttpRequest();
xhr.onreadystatechange = () =>{
if(!/^[23]\d{2}$/.test(xhr.status)) return;
if(xhr.readyState === 4){
let result = xhr.responseText;
switch (this.dataType.toUpperCase()){
case 'TEXT':
case 'HTML':
break;
case 'JSON':
result = JSON.parse(result);
case 'XML':
result = xhr.responseXML;
}
this.success(result);
}
};
// 处理data
if(this.data !== null){
this.formatData();
if(this.isGet){
this.url += `${this.querySymbol()}`;
}
}
// 是否去除缓存
this.isGet ? this.cacheFn():null;
xhr.open(this.method,this.url,this.async);
xhr.send(this.data);
}
formatData(){
if(Object.prototype.toString.call(this.data)==='[object,object]'){
let obj = this.data,
str = '';
for(let key in obj){
if(obj.hasOwnProperty(key)){
str += `${key}=${obj[key]}&`;
}
}
str = str.replace(/&$/g,'')
this.data = str;
}
}
// 是否清楚缓存
cacheFn() {
!this.cache?this.url += `${this.querySymbol()}_=${Math.random()}`:null;
}
// 判断结尾是否为?
querySymbol(){
return this.url.indexOf('?')>-1?'&':'?';
}
}
window.ajax = function({
url = null,
method = 'GET',
type = 'GET',
data = null,
dataType = 'JSON',
cache = true,
async = true,
success = null
} = {}){
let example = new ajaxClass();
example.url = url;
example.method = type === null?method:type;
example.data = data;
example.dataType = dataType;
example.cache = cache;
example.async = async;
example.success = typeof success === 'function'?success: new Function();
example.isGet = /^(GET|DELETE|HEAD)$/i.test(example.method);
example.init();
}
}();