AJAX详解

3,793 阅读4分钟

客户端与服务器的连接请参考https://juejin.cn/post/6844903944125153288

1.什么是AJAX?

async javascript and xml : 异步的JS和XML;

此处的异步指的是:局部刷新(对应的是全局刷新);

XML : 可扩展的标记语言,用自己定义的标签来存储数据的

在很早以前,我们基于AJAX和服务器进行交互的数据格式一般都是以XML格式为主,因为它能够清晰的展示出对应的数据和结构层级;

但后来随着JSON数据格式的到来, 它不仅比XML更清晰展示数据的结构,而且同样的数据存储,JSON更加轻量,也方便解析和相关的操作,所以现在前后端的数据交互都以JSON格式数据为主;

// ===XML===
![](https://user-gold-cdn.xitu.io/2019/9/17/16d3de27498ce477?w=1486&h=716&f=png&s=368962)
<?xml version="1.0" encoding="UTF-8"?>
<root>
	<student>
		<name>张三</name>
		<age>25</age>
		<score>
			<english>95</english>
		</score>
	</student>
	<student>
		<name>张三</name>
		<age>25</age>
	</student>
	<student>
		<name>张三</name>
		<age>25</age>
	</student>
</root>

//===JSON===
[{
	"name": "张三",
	"age": 25,
	"score": {
		"english": 95
	}
}, {
	"name": "张三",
	"age": 25
}, {
	"name": "张三",
	"age": 25
}]

那我们接下来先来看看全部刷新和局部刷新

1.全局刷新

2.局部刷新

3.当代项目开发的整个架构模型

2.AJAX的基础操作

1.创建AJAX实例

let xhr = new XMLHttpRequest;
// IE低版本浏览器中用的是new ActiveXObject(),
可以看看高程中JS惰性编程思想,关于XHR的兼容处理;

2.打开URL(配置发送请求的信息)

* method:  HTTP请求方式
* url: 请求地址(API接口地址)
* async :  设置同步或者异步,默认是TRUE异步,FALSE同步
* user-name: 传递给服务器的用户名;
* user-pass:    传递给服务器的密码;

xhr.open('GET','./json/xxx.json',true); //  默认异步

3.监听AJAX状态

// 监听AJAX的状态,在状态为xxx的时候,获取服务器响应的内容;
// AJAX状态码:0 1 2 3 4
// 当HTTP状态码为2或者3开头,并且AJAX状态码为4时,获取响应内容;
xhr.onreadystatechange=function(){
   if(xhr.readyState===4 && /^(2|3)\d{2}$/.test(xhr.status)){
      let result = xhr.responseText; // =>响应内容
   }
}

=> AJAX 状态码
`xhr.readyState` 获取状态码
* unsend 0 :未发送(创建一个XHR,初始状态是0)
* opend  1 :  已经打开(执行了xhr.open);
* headers_received  2  :  响应头信息 已经返回给客户端(发送请求后,服务器会一次返回响应头和响应主体的信息)
* loading  3 :等待服务器返回响应内容
* done  4: 响应主体信息已经返回给客户端

4.发送请求

send中放的是请求主体的内容;

//SEND中放的是请求主体的内容
xhr.send(null);

AJAX任务:发送一个请求给服务器,从服务器获取到对应的内容,从send后开始,到xhr.readystate === 4 的时候算任务结束;

HTTP的请求方式

  • GET 系列
    • GET
    • DELETE 一般应用于告诉服务器,从服务器上删除点东西
    • HEAD 只想获取响应头内容,告诉服务器响应主体内容不要
    • OPTIONS 试探性请求,发个请求给服务器,看看服务器能不能接收到,能不能返回
  • POST系列
    • POST
    • PUT 和 DELETE对应,一般只是想让服务器把我传递的信息存储到服务器上(一般应用于文件和大型数据内容)

=> 真实项目中用对应的请求方式,会使请求变得更加明确(语义化),不遵循这些方式也可以,最起码浏览器在语法上是允许的;但是这些是开发者们相互约定俗称的规范

get 和 post 的区别?

GET系列一般用于从服务器获取信息,POST系列一般用于给服务器推送信息,但是不论GET和POST都可以把信息传递给服务器,也能从服务器获取到结果,只不过是谁多谁少的问题

  • GET:给的少,拿的多
  • POST:给的多,拿的少
  • 客户端怎么把信息传递给服务器?

    • 问号传参 xhr.open('GET','/getdata?xxx=xxx&xxx=xxx')
    • 设置请求头 xhr.setRequestHeader([key],[value])
    • 设置请求主体 xhr.send(请求主体信息)
  • 服务器怎么把信息返回给客户端?

    • 通过响应头
    • 通过响应主体(大部分信息都是基于响应主体返回的)

get 和 post 的本质区别

GET系列传递给服务器信息的方式一般采用:问号传参 POST系列传递给服务器信息的方式一般采用:设置请求主体

1.GET传递给服务器的内容比POST少,因为URL有最长大小限制(IE一般限制2KB,其余浏览器一般限制4-8K,超过长度的部分自动被浏览器截取了)

xhr.open('GET','/list?name=lanlan&age=17...');
xhr.send('...') // 请求主体中传递的内容理论上没有大小限制,
// 但是真实项目中,为了保证传输的速度,我们会自己限制一些

2.GET会产生缓存(缓存不是自己可控制的),因为请求的地址(尤其是问号传参的信息一样),浏览器有时候会认为你要和上次请求的数据一样,拿的是上一次信息; 这种缓存我们不期望有,我们期望的缓存是自己可控的,所以真实项目中,如果一个地址,GET请求多次,我们要去除这个缓存

xhr.open('GET','/list?name=lanlan&age=17...');
xhr.open('GET','/list?name=lanlan&age=17...');

**解决办法设置随机数或者时间戳**
xhr.open('GET','/list?name=lanlan&age=17...'+Math.random());
xhr.open('GET','/list?name=lanlan&age=17...'+Math.random());

3.GET相比较POST来说不安全: GET是基于问号传参传递给服务器内容,有一种技术叫做URL劫持,这样别人可以获取或者篡改传递信息;而POST基于请求主体传递信息,不容易被劫持;

汇总XHR的属性和方法及事件

  • xhr.response / xhr.responseText / xhr.responseXML

  • xhr.status / xhr.statusText

  • xhr.timeout

  • xhr.withCredentials

  • xhr.abort()

  • xhr.getAllResponseHeaders()

  • xhr.getResponseHeader([key])

  • xhr.open()

  • xhr.overrideMimeType()

  • xhr.send()

  • xhr.setRequestHeader()

一些简单方法的使用;

let xhr = new XMLHttpRequest;
console.log(xhr.readyState); //=>0
xhr.open('GET', 'json/data.json');
// console.log(xhr.readyState); //=>1
xhr.onreadystatechange = function () {
	// console.log(xhr.readyState); //=>2 3 4
	if (!/^(2|3)\d{2}$/.test(xhr.status)) return;
	if (xhr.readyState === 2) {
		//=>获取响应头信息
		//获取的服务器时间是标准的日期格式对象(GMT格林尼治时间)
		//new Date()能把格林尼治时间转换为北京时间
		let serverTime = xhr.getResponseHeader('Date');
		// console.log(new Date(serverTime));
	}
	if (xhr.readyState === 4) {
		//=>获取响应主体信息:我们一般用responseText,因为服务器返回的信息一般都是JSON格式的字符串,如果返回的是XML格式,我们用responseXML...
		// xhr.responseXML
		// xhr.response
		// xhr.responseType
		// console.log(xhr.responseText);
	}
}
xhr.send(null);


// ====setTimeout===
let xhr = new XMLHttpRequest;
xhr.timeout = 1; //=>设置AJAX等待时间,超过这个时间算AJAX延迟
xhr.ontimeout = function () {
	console.log('请求超时~~'); //如果超过时间就会输出;
	xhr.abort(); //=>手动中断AJAX的请求
}
xhr.withCredentials = true; //=>在跨域请求中是否允许携带证书(携带COOKIE)
xhr.open('GET', 'json/data.json');
xhr.send();

// 设置请求头
let xhr = new XMLHttpRequest;
xhr.open('GET', 'json/data.json');
xhr.setRequestHeader('AAA', '蓝蓝');
// Uncaught TypeError: Failed to execute 'setRequestHeader' on 'XMLHttpRequest': Value is not a valid ByteString. //`设置的请求头信息值不能是中文  可以通过encodeURIComponent解码
xhr.setRequestHeader('AAA', encodeURIComponent('蓝蓝'));
xhr.onreadystatechange = function () {
	if (!/^(2|3)\d{2}$/.test(xhr.status)) return;
	if (xhr.readyState === 4) {
		console.log(xhr.getRequestHeader);
	}
}
xhr.send(null); 

4.AJAX中的同步异步

//=>AJAX任务的异步
 let xhr = new XMLHttpRequest;
xhr.open('GET', 'json/data.json');
//=>设置事件绑定之前状态1
xhr.onreadystatechange = function () {
	console.log(xhr.readyState); //=>2 3 4
}
xhr.send(null); 

let xhr = new XMLHttpRequest;
xhr.open('GET', 'json/data.json');
xhr.send(null);
xhr.onreadystatechange = function () {
	console.log(xhr.readyState); //=>2 3 4
}


//=>AJAX的同步
 let xhr = new XMLHttpRequest;
xhr.open('GET', 'json/data.json', false);
xhr.onreadystatechange = function () {
	console.log(xhr.readyState); //=>4 使用AJAX同步编程,不能在状态码为2的时候获取到响应头的信息,但是状态码为4的时候也是可以获取到头和主体信息
}
xhr.send(null); 

let xhr = new XMLHttpRequest;
xhr.open('GET', 'json/data.json', false);
xhr.send(null);//=>执行后,只有状态码为4才会继续处理下面的代码
//=>状态码为4的时候绑定的,而状态不会在变了,所以方法不会执行
xhr.onreadystatechange = function () {
    console.log(xhr.readyState);
}

JQ中的AJAX

/*
 * $.ajax() 基于原生JS的AJAX四步操作进行封装
 *    $.ajax([URL],[OPTIONS])
 *    $.ajax([OPTIONS])  URL在配置项中(推荐)
 *    $.get/post/getJSON/getScript()
 *    ......
 * 配置项信息
 *    url:请求的API接口地址
 *    method:HTTP请求方式,默认GET
 *    data:传递给服务器的信息,默认null(可以是字符串,可以是对象,而且如果GET系列请求,JQ会自动把信息拼接到URL的末尾,基于问号传参传递给服务器;如果是POST请求,JQ会基于请求主体,把信息传递给服务器)
 *    dataType:预设服务器返回的结果格式(服务器返回的一般都是JSON格式的字符串,如果我们设置了DATA-TYPE,JQ会根据设置的类型,把服务器返回的结果处理为对应的格式),支持的内容text / json / xml / html / script / jsonp(跨域) =>不影响服务器返回的结果,只是把服务器返回的结果进行二次处理
 *    async:是否为异步操作,默认是TRUE,代表异步操作
 *    cache:缓存处理,只对GET系列请求有作用,默认是TRUE不处理缓存,当我们设置FALSE后,JQ帮我们在URL的末尾设置一个随机数
 *    contentType:设置传递给服务器内容的格式类型 默认是"application/x-www-form-urlencoded"
 *        客户端传递给服务器信息的格式(类型一般都是字符串),常用的:
 *        form-data表单数据:JSON格式 '{"name":"xxx","lx":1}'
 *        x-www-form-urlencoded:name=xxx&lx=1
 *        raw:纯文本格式
 *    headers:设置请求头信息,他是一个对象
 *    timeout:设置超时的时间
 *    success:回调函数,当数据请求成功执行,方法中的参数就是从服务器获取的结果
 *    error:回调函数,数据请求失败执行,方法中的参数是错误信息
 */
$.ajax({
	url: 'http://yapi.demo.qunar.com/mock/95100/project/list',
	method: 'POST',
	data: {
		name: 'lanlan',
		lx: 'teacher'
	},
	dataType: 'json',
	async: true,
	cache: false,
	headers: {},
	success: (result, status, xhr) => {
		//=>xhr:是JQ帮我们处理过的AJAX实例
		console.log(result, status, xhr);
	}
});

倒计时小例子

<div id="box"></div>
	<script>
		//new Date()获取客户端本地当前时间(不能拿它做重要依据,因为用户可以随意修改)
		/* 
		 * 倒计时抢购需要从服务器获取当前时间  AJAX
		 *    问题:时间差(从服务器把时间给客户端,到客户端获取到这个信息,中间经历的时间就是时间差,而时间差是不可避免的,我们应尽可能减少这个误差)
		 *    - 从响应头获取时间(AJAX异步)
		 *    - 基于HEAD请求(只获取响应头信息)
		 */
		let target = new Date('2019/09/14 13:27:00'),
			now = null,
			timer = null;

		//=>从服务器获取时间:获取到时间后再做其他的事情
		function func(callback) {
			let xhr = new XMLHttpRequest;
			xhr.open('HEAD', 'json/data.json', true);
			xhr.onreadystatechange = function () {
				if (!/^(2|3)\d{2}$/.test(xhr.status)) return;
				if (xhr.readyState === 2) {  //从响应头中拿到时间
					now = new Date(xhr.getResponseHeader('Date'));
					callback && callback();
				}
			}
			xhr.send(null);
		}

		//=>开启倒计时模式
		function computed() {
			let spanTime = target - now;
			if (spanTime <= 0) {
				//=>到抢购时间:结束定时器
				clearInterval(timer);
				timer = null;
				box.innerHTML = "开抢~~";
				return;
			}
			let hours = Math.floor(spanTime / (60 * 60 * 1000));
			spanTime -= hours * 60 * 60 * 1000;
			let minutes = Math.floor(spanTime / (60 * 1000));
			spanTime -= minutes * 60 * 1000;
			let seconds = Math.floor(spanTime / 1000);
			box.innerHTML =
				`距离抢购还剩 ${hours<10?'0'+hours:hours}:${minutes<10?'0'+minutes:minutes}:${seconds<10?'0'+seconds:seconds}`;

			//=>每一次计算完,我们需要让NOW在原来的基础上加上一秒(第一次从服务器获取到时间,后期直接基于这个时间自己加即可,不要每隔一秒重新从服务器拿)
			now = new Date(now.getTime() + 1000);
		}
		func(() => {
			//=>已经从服务器获取时间了
			computed();
			timer = setInterval(computed, 1000);
		});
	</script>