Ajax 概述
背景
-
在了解 AJAX 之前我们可以简单的认为
JS 能力有限
,因为在此之前的 WEB 平台提供所有的 API 都只停留在 单击 的阶段 -
这样就会造成一些无法实现的功能,例如:
- 无法在实现用户登录功能时,当用户输入邮箱地址显示用户对应的头像
- 无法在实现用户注册功能时,当用户输入邮箱地址或者用户名就提示是否存在
- 无法在实现留言板功能时,实时看到最新的用户留言
-
这些都因为一个问题——数据存放在服务端,无法通过已知的 API 获取
已知发送请求的方式
- 地址栏输入地址,回车,刷新
- 特定元素的 href 或 src 属性
- 表达提交
- 这些方案都是我们无法通过或者很难通过代码的方式进行编程操作的
需求
- 对服务端发出请求并接受服务端返回的响应
- 如果我们可以通过 JS 直接发送网络请求,那么 WEB 的可能就会更多,随之能够实现的功能也会更多,至少不再是只能开发 单机游戏
Goole Suggest
- AJAX(Asynchronous Javascript And XML),最早出现在 2005年的 Goole Suggest
- 它不是像 HTML、JS、CSS这样的一种“正式的”技术
- 他是在浏览器端进行网络编程(发送请求、接受响应)的技术方案
- 它使我们可以通过 JS 直接获取服务端最新的内容而不必重新加载页面
- 让 WEB 更能接近桌面应用的用户体验
Asynchronous Javascript And XML
- AJAX 就是浏览器提供的一套 API,可以通过 JS 调用,从而实现通过代码控制请求与响应。实现通过 JS 进行网络编程
- XML:最早在客户端与服务端之间传递数据时采用的数据格式
应用场景
- 按需获取数据
- 对用户数据校验
- 自动更新页面内容
- 提升用户体验,无刷新的体验
体验 Ajax
$.ajax({
url:"https://jsonplaceholder.typicode.com/users",
type:"GET",
dataType:"json",
data:{id:1},
success:function (data) {
console.log(data);
}
})
原生 Ajax
发送 ajax 请求步骤
- 创建 XMLHttpRequest 类型的对象
- 准备发送,打开与一个网址之间的连接
- 执行发送动作
- 指定 xhr 状态变化事件处理函数
// 创建一个 XMLHttpRequest 类型的对象 --- 相当于打开了一个浏览器
var xhr = new XMLHttpRequest();
// 打开一个与网址之间的连接 --- 相当于在地址栏输入网址
xhr.open("GET","https://jsonplaceholder.typicode.com/users");
// 通过连接发送一次请求 --- 相当于点击回车或者超链接
xhr.send(null);
// 指定 xhr 状态变化事件处理函数 --- 相当于处理网页呈现后的操作
xhr.onreadystatechange = function () {
// 通过判断 xhr 的 readyState,确定此次请求是否完成
if (this.readyState === 4) {
console.log(this.responseText);
}
}
原生 Ajax 详解 —— xhr 对象
- AJAX API 中核心提供的是一个 XMLHttpRequest 类型,所有的 AJAX 操作都需要使用到这个类型
// IE6兼容
var xhr = null;
if (window.XMLHttpRequest) {
xhr = new XMLHttpRequest();
} else {
xhr = new ActiveXObject("Microsoft.XMLHTTP");
}
原生 Ajax 详解 —— open 方法
- 本质上 XMLHttpRequest 就是 JS 在 WEB 平台中发送 HTTP 请求的手段,所以我们发送出去的请求仍然是 HTTP 请求,同样符合 HTTP 约定的格式
- 语法:xhr.open(method, url);
- method:要是用的 HTTP 方法,比如 GET、POST、PUT、DELETE
- url:要向其发送请求的 URL 地址,字符串格式
// 1. 创建一个 XMLHttpRequest 类型的对象
var xhr = null;
if (window.XMLHttpRequest) {
xhr = new XMLHttpRequest();
} else {
xhr = new ActiveXObject("Microsoft.XMLHTTP");
}
// 2. open() 方法开启请求
xhr.open("GET", "https://jsonplaceholder.typicode.com/users");
原生 Ajax 详解 —— send 方法和请求头
- 用于发送 HTTP 请求
- 语法:xhr.send(body)
- body:在 xhr 请求中要发送的数据体,根据请求头中的类型进行传参
- 如果是 GET 方法,无需设置数据体,可以传 null 或者不传参
setRequestHeader()方法设置请求头
- 此方法必须在open()方法和send()之间调用
- 语法:xhr.setRequestHeader(header, value);
- header:一般设置"Content-Type",传输数据类型,即服务器需要我们传送的数据类型
- value:具体的数据类型,常用"application/x-www-form-urlencoded"和"application/json"
// 1. 创建一个 XMLHttpRequest 类型的对象
var xhr = null;
if (window.XMLHttpRequest) {
xhr = new XMLHttpRequest();
} else {
xhr = new ActiveXObject("Microsoft.XMLHTTP");
}
// 2. open() 方法开启请求
// xhr.open("GET", "https://jsonplaceholder.typicode.com/users?id=1");
xhr.open("POST", "https://jsonplaceholder.typicode.com/users");
// 2.5 设置请求头
// 一般get方法不需要设置,而 post 方法必须设置
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded")
// 3. send() 方法发送一次请求
// 如果是 get 方法请求,不需要再 send 中传参数,他如果想传参数,直接写在网址上
// xhr.send(null);
xhr.send("name=harry");
原生 Ajax 详解 —— 响应状态分析
- readyState 属性返回一个 XMLHttpRequest 代理当前所处的状态,由于 readystatechange 事件是在 xhr 对象状态变化时触发(不单是在得到响应时)也就意味着这个时间会被处罚多次,所以我们有必要了解每一个状态值代表的含义
readyState | 状态描述 | 说明 |
---|---|---|
0 | UNSENT | 代理 xhr 被创建,但尚未调用 open() 方法 |
1 | OPENED | open() 方法已经被调用,建立了连接 |
2 | HEADERS_RECEIVED | send() 方法已经被调用,并且已经可以获取状态行和响应头 |
3 | LOADING | 响应体下载中,responseText 属性可能已经包含部分数据 |
4 | DONE | 响应体下载完成,可以直接使用 responseText |
// 1. 创建一个 XMLHttpRequest 类型的对象
var xhr = null;
if (window.XMLHttpRequest) {
xhr = new XMLHttpRequest();
} else {
xhr = new ActiveXObject("Microsoft.XMLHTTP");
}
console.log("UNSENT", xhr.readyState);
// 2. open() 方法开启请求
// xhr.open("GET", "https://jsonplaceholder.typicode.com/users?id=1");
xhr.open("POST", "https://jsonplaceholder.typicode.com/users");
// 2.5 设置请求头
// 一般get方法不需要设置,而 post 方法必须设置
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded")
// 3. send() 方法发送一次请求
// 如果是 get 方法请求,不需要再 send 中传参数,他如果想传参数,直接写在网址上
// xhr.send(null);
xhr.send("name=harry");
// 4. 指定回调函数,处理得到的数据
xhr.onreadystatechange = function () {
// 通过判断 xhr 的 readyState,确定此次请求是否完成
if (this.readyState === 2) {
console.log("HEADERS_RECEIVED", xhr.readyState);
} else if (this.readyState === 3) {
console.log("LOADING", xhr.readyState);
} else if (this.readyState === 4) {
console.log("DONE", xhr.readyState);
}
}
同步和异步
- 同步:一个人在同一个时刻只能做一件事,在执行一些耗时的操作(不需要看管)不去做别的事,只是等待
- 异步:在执行一些耗时的操作(不需要看管)去做别的事,而不是等待
AJAX中的实现
- xhr.open() 方法第三个参数要求传入的是一个 boolean 值,其作用就是设置此次请求是否采用异步方式执行
- 默认为 true 异步,如果需要同步执行可以通过 false 实现
- 如果采用同步方式执行,则代码会卡死在 xhr.send() 这一步
// 1. 创建一个 XMLHttpRequest 类型的对象
var xhr = null;
if (window.XMLHttpRequest) {
xhr = new XMLHttpRequest();
} else {
xhr = new ActiveXObject("Microsoft.XMLHTTP");
}
// 2. open() 方法开启请求
// 设置同步或者异步
xhr.open("GET", "https://jsonplaceholder.typicode.com/users", false);
// 3. send() 方法发送一次请求
// 如果是 get 方法请求,不需要再 send 中传参数,他如果想传参数,直接写在网址上
xhr.send(null);
// 4. 指定回调函数,处理得到的数据
xhr.onreadystatechange = function () {
// 通过判断 xhr 的 readyState,确定此次请求是否完成
if (this.readyState === 4) {
console.log(this.responseText);
}
};
// ajax 后面的代码
console.log("After Ajax");
建议
- 为了让事件可以更加可靠(一定触发),在发送请求 send() 之前,一定是先注册 readystatechange
- 不论是同步或异步都能触发成功
- 了解同步模式即可,切记不要使用同步模式
// 1. 创建一个 XMLHttpRequest 类型的对象
var xhr = null;
if (window.XMLHttpRequest) {
xhr = new XMLHttpRequest();
} else {
xhr = new ActiveXObject("Microsoft.XMLHTTP");
}
// 2. open() 方法开启请求
// 设置同步或者异步
xhr.open("GET", "https://jsonplaceholder.typicode.com/users", false);
// 4. 指定回调函数,处理得到的数据
xhr.onreadystatechange = function () {
// 通过判断 xhr 的 readyState,确定此次请求是否完成
if (this.readyState === 4) {
console.log(this.responseText);
}
};
// 3. send() 方法发送一次请求
// 如果是 get 方法请求,不需要再 send 中传参数,他如果想传参数,直接写在网址上
xhr.send(null);
// ajax 后面的代码
console.log("After Ajax");
xml 数据格式
- 一种数据描述手段
- 老掉牙的东西简单演示一下,不在这里浪费时间,基本现在项目不用了
- 淘汰的原因:数据冗余太多
<?xml verson-"1.0" encoding="utf-8" ?> <!-- 必须要书写,语法规范 -->
<booklist>
<book>
<name>三国演义</name>
<author>罗贯中</author>
<cate>古典名著</cate>
</book>
<book>
<name>西游记</name>
<author>吴承恩</author>
<cate>古典名著</cate>
</book>
<book>
<name>红楼梦</name>
<author>曹雪芹</author>
<cate>古典名著</cate>
</book>
</booklist>
<!--
xml 就是一种数据格式
元数据:用来描述数据的数据(其实就是标签)
这种数据的缺陷:
1. 元数据占用的数据量比较大,不利于大量数据的网络传输
2. 在其他语言中,比如JS解析内部数据,方法比较复杂,不方便使用
-->
json格式数据
- JavaScript Object Notation, JavaScript 对象表示法
- 也是一种数据描述手段,类似于 JS 字面量方式
- 服务端采用 JSON 格式返回数据,客户端按照 JSON 格式解析数据
JSON 格式的数据与 JS 对象的区别
- JSON 数据不需要存到变量中
- 结束时不需要写分号
- JSON 数据中的属性名必须加引号
代码对比
- JS 数据
var obj = {
name: "tom",
age: 18,
cp: {
name: "herry",
age: 18
}
};
- JSON 数据
{
"name": "tom",
"age": 18,
"cp": {
"name": "herry",
"age": 18
}
}
- JSON 格式的字符串
var str = "{'name':'tom','age':80}"
JSON 对象的方法
- 使用 JSON 对象的 parse 方法可以将 JSON 格式的字符串转换为对象格式,具有了属性和方法,方便我们在 JS 中进行使用
var obj = {
name: "tom",
age: 18,
cp: {
name: "herry",
age: 18
}
};
var str = "{'name':'tom','age':80}";
console.log(obj);
console.log(JSON.stringify(obj));
console.log(JSON.parse(str));
var strObj = JSON.parse(str);
console.log(strObj.name);
console.log(strObj.age);
注意
- 不管是 JSON 也好,还是 XML,只是在 AJAX 请求过程中用到,并不代表它们与 AJAX 之间有必然的联系,他们只是数据协议罢了
- 不管服务端是采用 XML 还是采用 JSON 本质上都是将数据返回给客户端
- 服务端应该根据相应内容的格式设置一个合理的 Content-Type
json-server 的使用
平时我们也会自己写一些数据,通过 Ajax 获取,所以需要在本地搭建一个临时服务器
- json-server 是一个 Node 模块,运行 Express 服务器,你可以指定一个 json 文件作为 api 的数据源
- 也就是说,我们可以使用它快速搭建一个 web 服务器
- 网址:https://github.com/typicode/json-server
json 文件书写方法
后续代码示例中的连接都是通过json-server将下列代码部署后产生的连接
{
"users": [
{ "id": 1, "name": "tom", "age": 19, "class": 1 },
{ "id": 2, "name": "jerry", "age": 18, "class": 2 },
{ "id": 3, "name": "lucy", "age": 19, "class": 1 }
],
"posts": [
{ "id": 1, "userId": 1, "title": "javascript", "content": "js 是一门非常好学语言" },
{ "id": 2, "userId": 1, "title": "jquery", "content": "jq 是一门非常好学语言" },
{ "id": 3, "userId": 2, "title": "html", "content": "html 是一门非常好学语言" },
{ "id": 4, "userId": 3, "title": "css", "content": "css 是一门非常好学语言" }
],
"comments": [
{ "id": 1, "postId": 1, "content": "good" },
{ "id": 2, "postId": 3, "content": "better" },
{ "id": 3, "postId": 4, "content": "best" }
]
}
原生 Ajax 应用 —— GET 方法
- 通常再一次 GET 请求过程中,参数传递都是通过 URL 地址中的
?
参数传递 - 一般在 GET 请求中,无需设置请求头
- 无需设置响应体,可以传 null 或者干脆不传
var xhr = xhr = new XMLHttpRequest();
xhr.open("GET", "https://locahost:3000/users?age=19");
xhr.send(null);
xhr.onreadystatechange = function () {
if (this.readyState === 4) {
console.log(this.responseText);
}
};
原生 Ajax 应用 —— POST 方法
- POST 请求过程中,都是采用请求体承载需要提交的数据
- 需要设置请求头中的 Content-Type,以便于服务端接收数据
- 需要提交到服务端的数据可以通过 send 方法的参数传递
var xhr = xhr = new XMLHttpRequest();
xhr.open("POST", "https://locahost:3000/users");
// xhr.setRequestHeader("Content-Type","application/x-www-form-urlencoded");
xhr.setRequestHeader("Content-Type","application/json");
// xhr.send("name=lily&age=19&class=2");
// xhr.send(`
// 'name': 'lulu',
// 'age': 18,
// 'class': 2
// `);
xhr.send(JSON.stringify({
'name': 'mm',
'age': 18,
'class': 2
}));
xhr.onreadystatechange = function () {
if (this.readyState === 4) {
console.log(this.responseText);
}
};
处理响应数据渲染
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
* {
margin: 0;
padding: 0;
}
table {
width: 300px;
margin: 30px auto;
font: 14px/28px "微软雅黑";
color: #333;
border-collapse: collapse;
}
td,
th {
width: 100px;
}
</style>
</head>
<body>
<table border="1" id="box">
<tr>
<th>学号</th>
<th>姓名</th>
<th>年龄</th>
<th>班级</th>
</tr>
</table>
<script>
// 获取元素
var box = document.getElementById("box");
var xhr = new XMLHttpRequest();
// 发送 GET 请求
xhr.open("GET", "http://localhost:3000/users");
xhr.send(null);
xhr.onreadystatechange = function () {
if (this.readyState === 4) {
// 将获取的响应体的字符串转为对象
var data = JSON.parse(this.responseText)
// console.log(data);
var str = "";
// 循环遍历数组
for (var i = 0; i < data.length; i++) {
// 进行字符串拼接
// str += "<tr><td>" + data[i].id + "</td><td>" + data[i].name + "</td><td>" + data[i].age + "</td><td>" + data[i].class + "</td></tr>";
// 使用 模板字符串 进行拼接
str += `<tr>
<td>${data[i].id}</td>
<td>${data[i].name}</td>
<td>${data[i].age}</td>
<td>${data[i].class}</td>
</tr>`
}
box.innerHTML += str;
}
};
</script>
</body>
</html>
封装 Ajax 函数
封装 Ajax 函数
- 这里主要是为了了解封装的过程,一般情况下在开发中都是使用第三方提供的 AJAX 库,因为他们可能更加严谨
- 为了在后续的开发过程中可以更方便的使用这套 API,一般的做法都是将其封装到一个函数中以便调用