Ajax 具体实现与异步理解
Ajax具体实现
前面有介绍过,使用原生的 Ajax 大致包括以下几步骤:
1. 创建 XMLHttpRequest 对象 2. 发出 HTTP 请求 3. 接收服务器传回的数据 4. 更新网页数据
接下来我们来具体看一下每一个部分。
Ajax 技术的核心就是 XMLHttpRequest 对象(简称 XHR)。这是由微软首先引入的一个特性,是一种支持异步请求的技术。(后来其他浏览器开发商也都提供了相同的功能实现。)
简而言之,XMLHttpRequest 用于与服务器交换数据。这意味着用户操作页面后,可以不必刷新页面也能够取得新的数据。
1. 创建XMLHttpRequest
对象
创建一个XMLHttpRequest
对象,也叫实例化一个XMLHttpRequest
对象。因为XMLHttpRequest()
本身是一个构造函数。
let xhr = new XMLHttpRequest();
IE5 是第一款引入XMLHttpRequest
对象的浏览器。在 IE5 和 IE6 中,XHR 对象是通过 MSXML 库中的一个 ActiveX 对象实现的。
let xhr = new ActiveXObject('Microsoft.XMLHTTP');
而 IE7+ 及其他标准浏览器都支持原生的XMLHttpRequest
对象。为了应对所有浏览器,下面是创建XMLHttpRequest
对象的兼容写法。
let xhr;
if(window.XMLHttpRequest){
// IE7+, Firefox, Chrome, Opera, Safari 浏览器执行代码
xhr = new XMLHttpRequest();
} else {
// IE6, IE5 浏览器执行代码
xhr = new ActiveXObject( 'Microsoft.XMLHTTP' );
}
2. 打开连接
XMLHttpRequest
的实例对象提供了一个open()
方法用来打开客户端与服务端之间的连接。该方法规定了请求的类型、请求的路径以及是否异步处理请求。语法结构如下:
xhr.open(method, url, async);
参数说明:
method:发送请求的方式。取值有 GET 和 POST(不区分大小写,建议使用大写)。
url:请求资源的位置路径。
async:控制是否异步处理请求。取值有 true(异步)和 false(同步),默认为 true。
一般来讲,都会产用异步的方式来发送请求,不然 Ajax 将变得毫无意义。
3. 发送请求
XMLHttpRequest
的实例对象提供了一个send()
方法用于客户端向服务端发送请求。
xhr.send();
请求参数
在向服务端发送请求的同时,是可以传递数据至服务端的。而请求方式的不同,数据的传递方式也不同。
GET 请求:传递的数据是跟在open()
方法中的url
后面。
xhr.open("GET","/users/isUser?username=zhangsan&pwd=123");
xhr.send(null);
POST 请求:传递的数据是放在send()
方法的参数中。调用send()
方法之前需要设定Content-Type
头信息,模拟 HTTP 的 POST 方法发送一个表单,这样服务器才会知道如何处理上传的内容。
参数的提交格式和 GET 方法中url
的写法一样。设置头信息前必须先调用open()
方法。
xhr.open("POST","login.php",true);
xhr.setRequestHeader("Content-Type","application/x-www-form-urlencoded;charset=UTF-8");
xhr.send("username=zhangsan&pwd=123");
和 POST 相比,GET 的请求方式更简单也更快,并且在大部分情况下都能使用。但是,在以下情况中,请使用 POST 请求:
- 无法使用缓存文件(更新服务器上的文件或者数据库)
- 向服务器发送大量数据(POST 没有数据量的限制)
- 发送包含未知字符的用户输入时,POST 比 GET 更稳定也更可靠
4. 处理服务器返回的消息
一个完整的 HTTP 响应由状态码、响应头集合和响应主体组成。在收到响应后,可以通过 XHR 对象的属性来获取响应内容。常用的属性有以下 4 个:
responseText: 作为响应主体被返回的文本(文本形式)
responseXML: 如果响应的内容类型是 text/xml 或 application/xml,这个属性中将保存着响应数据的 XML DOM 文档(document 形式)
status: HTTP 状态码(数字形式)
statusText: HTTP 状态说明(文本形式)
另外,第 2 步打开连接时,选择处理请求的方式不同,第 4 步处理服务器返回消息的方式也有所不同。
同步处理请求
当处理请求的方式为同步时,直接使用XMLHttpRequest
的实例对象的responseText
属性用来接收服务端返回的消息。
xhr.responseText;
但是不推荐使用同步处理请求的方式。使用这种方式会导致 JavaScript 代码要一直等到服务器响应就绪后才继续执行,如果服务器繁忙或缓慢,应用程序就会挂起或停止。但是对于一些小型的请求,也是可以的。
异步处理请求
当处理请求的方式为异步时,需要通过XMLHttpRequest
的实例对象提供的onreadystatechange
事件来监听并处理服务器返回的消息。
xhr.onreadystatechange = function(){
// ...
}
从“请求准备发送”到最后“请求发送成功”,中间执行了一系列的任务,分别为:
请求未初始化(0):即还没有调用 send() 方法;
服务器连接已建立(1):即已调用 send() 方法,正在发送请求;
请求已接收(2):即 send() 方法执行完成;
请求处理中(3):即正在解析响应内容;
请求已完成(4):且响应已就绪,即响应内容解析完成,可以在客户端进行调用了;
每执行一个任务,XMLHttpRequest
对象的状态值readyState
都会发生改变。只要readyState
属性发生改变时,就会触发onreadystatechange
事件。
按照任务顺序,状态值readyState
依次从 0 到 4 发生改变。
只有当状态值为 4 时,才表示请求完成。请求完成后,判断请求状态,状态码status
为 200 时表示请求成功。只有请求完成并且成功了,才能处理服务端返回的消息。
xhr.onreadystatechange = function(){
if(xhr.readyState == 4 && xhr.status == 200){
let text = xhr.responseText;
console.log( text );
}
}
5. 完整代码
我们将上面分解的每一步整合在一起,就是一个完整的 Ajax 请求代码了。
// 创建 XHR 对象
let xhr;
if (window.XMLHttpRequest) {
// IE7+, Firefox, Chrome, Opera, Safari 浏览器执行代码
xhr = new XMLHttpRequest();
} else {
// IE6, IE5 浏览器执行代码
xhr = new ActiveXObject('Microsoft.XMLHTTP');
}
// 2. 打开连接
xhr.open("get", "/users/isPhoneExist?phone=12313212311"); // 第三个参数默认 true,表示异步;get 请求的参数跟在路径后面。
// 3. 发送请求
xhr.send(); // send 方法默认没有参数。
//xhr.send("phone=123&pwd=123"); // 请求是 post,且想要传参,send 方法才有参数。
// 4. 通过事件监听并处理服务器返回的消息
xhr.onreadystatechange = function () {
if (xhr.readyState == 4 && xhr.status == 200) { // 服务器正常的响应数据回来
let text = xhr.responseText; // 得到的 text 是字符串
// info.innerHTML = text; // 处理响应数据
}
}
最后,我将演示使用 Ajax 发送请求,然后使用 Mock 来拦截 Ajax 请求的示例,如下:
<body>
<button id="btn">点击我从服务器获取数据</button>
<span id="test"></span>
<script>
btn.onclick = function () {
// 首先第 1 步 创建 xhr
let xhr = null;
if (window.XMLHttpRequest) {
xhr = new XMLHttpRequest();
} else {
xhr = new ActiveXObject('Microsoft.XMLHTTP');
}
// 接下来来做第 2 步 发送请求
xhr.open('GET', '/getStudent', true);
xhr.send(null);
// 第 3 步 监听 readyState 的值 当值为 4 的时候说明数据已经回来
// 可以将数据通过JS添加到页面上面
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
test.innerHTML = JSON.parse(xhr.responseText).name;
}
}
}
// 接下来我们使用 Mock 来截取 Ajax 请求
Mock.mock(/getStudent/, 'get', function () {
return Mock.mock({
'name': '@cname'
});
});
</script>
</body>
效果:点击按钮后会由 Mock 随机产生一条数据填入到 span 元素里面
再比如下面的例子:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
table{
border:1px solid #ddd;
border-collapse: collapse;
border-spacing: 0;
}
td,th{
border:1px solid #ddd;
padding:20px;
line-height: 40px;
}
</style>
</head>
<body>
<button id="btn">点击访问数据</button>
<br>
<table>
<thead>
<tr>
<th>姓名</th>
<th>性别</th>
<th>电话</th>
<th>成绩</th>
</tr>
</thead>
<tbody id="mybody">
</tbody>
</table>
</body>
<script src="https://cdn.bootcdn.net/ajax/libs/Mock.js/1.0.0/mock-min.js"></script>
<script>
let btn = document.getElementById("btn");
let mybody = document.getElementById("mybody");
Mock.mock(/users/,"get",function(){
return Mock.mock({
"users|10-15":[{
"username":"@cname()",
"sex|1":["男","女"],
"tel":/^1[3-9]\d{9}$/,
"score|1-100":1
}]
}).users
})
btn.onclick = function(){
let xhr = new XMLHttpRequest();
xhr.open("get","/users");
xhr.send();
xhr.onreadystatechange = function(){
if(xhr.readyState === 4 && xhr.status === 200){
let txt = xhr.responseText; //获取返回的json格式的字符串
// console.log(txt,typeof txt);
//将字符串转换为json格式
let users = JSON.parse(txt);
console.log(users);
for (let i = 0; i < users.length; i++) {
let user = users[i];
let str = `<tr>
<td>${user.username}</td>
<td>${user.sex}</td>
<td>${user.tel}</td>
<td>${user.score}</td>
</tr>`
mybody.innerHTML += str
}
}
}
}
</script>
</html>
对于异步的理解
上面的代码中,我们直接是在onreadystatechange
回调函数中操作的代码,那思考一个问题,那能不能在回调函数中获取数据,然后再到外部操作这个数据呢?比如下的情况
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
table{
border:1px solid #ddd;
border-collapse: collapse;
border-spacing: 0;
}
td,th{
border:1px solid #ddd;
padding:20px;
line-height: 40px;
}
</style>
</head>
<body>
<button id="btn">点击访问数据</button>
<br>
<table>
<thead>
<tr>
<th>姓名</th>
<th>性别</th>
<th>电话</th>
<th>成绩</th>
</tr>
</thead>
<tbody id="mybody">
</tbody>
</table>
</body>
<script src="https://cdn.bootcdn.net/ajax/libs/Mock.js/1.0.0/mock-min.js"></script>
<script>
let btn = document.getElementById("btn");
let mybody = document.getElementById("mybody");
let users = null;
Mock.mock(/users/,"get",function(){
let data = Mock.mock({
"users|10-15":[{
"username":"@cname()",
"sex|1":["男","女"],
"tel":/^1[3-9]\d{9}$/,
"score|1-100":1
}]
}).users
return data;
})
btn.onclick = function(){
let xhr = new XMLHttpRequest();
xhr.open("get","/users");
xhr.send();
xhr.onreadystatechange = function(){
if(xhr.readyState === 4 && xhr.status === 200){
let txt = xhr.responseText; //获取返回的json格式的字符串
console.log(txt);
//转换为json对象格式
users = JSON.parse(txt);
//如果数据是ajax异步获取的
//那么处理数据的操作,就必须放在ajax的异步操作中
setHTML(users);
}
}
}
function setHTML(users){
for (let i = 0; i < users.length; i++) {
let user = users[i];
let str = `<tr>
<td>${user.username}</td>
<td>${user.sex}</td>
<td>${user.tel}</td>
<td>${user.score}</td>
</tr>`
mybody.innerHTML += str
}
}
//这里直接调用会报错...
//因为数据是通过ajax异步获取的,这里是同步代码
//users在这个时候还没有值
// setHTML(users);
// let str = "hello";
// //setTimeout 指定毫秒数之后运行代码
// //这个函数本身就是异步的
// setTimeout(function(){
// str = "hello 美女";
// },0);
// console.log(str);
</script>
</html>
注意看上面例子中,注释的部分
Comments