Ajax 简单介绍

Ajax 即 “Asynchronous Javascript And XML”(异步 JavaScript 和 XML)

它是一项技术,提高了用户的体验,为什么这么说呢?

场景

举个栗子,如果我们做个登录的功能,当用户输入完姓名后,我需要去请求后台,校验一下数据库中是否重名,如果没有 ajax,那么只能通过跳转进行后端访问,这个体验太糟糕了。

我们想的是:能否不需要刷新页面就可以访问后台接口?所以 Ajax 应运而生。

实例:验证用户名输入

根据刚才的场景,我们来写一下代码。顺便简单介绍 Ajax 的使用

前端:

  • 首先新建一个 XMLHttpRequest 对象

    1
    let xhr = new XMLHttpRequest();
  • 然后配置请求参数 xhr.open();,其中 true 是异步,false 是同步,后面会说到

    1
    xhr.open("get", "/checkUserName", true); //true是异步,false是同步
  • 接着接受返还值

    1
    2
    3
    xhr.onload = function () {
    let res = JSON.parse(xhr.responseText);
    };
  • 发送服务器

    1
    xhr.send();

前端校验用户名的主要代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
document.querySelector(".inputStyle").onblur = function () {
let xhr = new XMLHttpRequest();
xhr.open("get", `/checkUserName?username=${this.value}`, false);
xhr.onload = function () {
let res = JSON.parse(xhr.responseText);
console.log(res);
if (res.status === 1) {
document.querySelector(".exchange").style.color = "green";
} else {
document.querySelector(".exchange").style.color = "red";
}
document.querySelector(".exchange").innerHTML = res.info;
};
xhr.send();
};

后端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
router.get("/checkUserName", async (ctx) => {
let ctxUserName = ctx.query.username;
let row = usersData.find((i) => i.username === ctxUserName);
if (row) {
ctx.body = {
status: 1,
info: "用户名正确",
};
} else {
ctx.body = {
status: 0,
info: "用户名错误",
};
}
});

先感受一下 ajax


网络请求

GET

使用如下:

1
2
3
4
5
6
let xhr = new XMLHttpRequest();
xhr.open("get", `/checkUserName?username=${this.value}`, true);
xhr.onload = function () {
console.log(xhr.responseText);
};
xhr.send();

注意点

  • get 通过 parmas 传参
  • get 和 querystring 的问题,通过 url 传参

传参方式

如果 get 是这样携带参数的话

1
2
3
// 这里传递了一个数字: 3
// 就是参数也用斜杠 / 给隔开了
xhr.open("get", "/getText/3", false);

后台就需要这样去接收,我声明了一个叫 id 的变量(这个变量名字也可以随便取),它来接受前端传来的 3

1
2
3
4
5
6
7
router.get("/getText/:id", (ctx) => {
console.log(ctx.query); // 收不到
console.log(ctx.params); // 收得到
ctx.body = {
info: "请求成功",
};
});

注意:要写这个冒号和名称,不然前端会报错 404

在这里插入图片描述


POST

前端发送数据是要声明编码格式,设置http正文头格式

1
2
3
4
// 发送数据时候需要设置http正文头格式;
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); //默认编码
xhr.setRequestHeader("Content-type", "multipart/form-data"); //二进制编码
xhr.setRequestHeader("Content-type", "application/json"); //json编码

以下是个栗子 🌰

1
2
3
4
5
6
7
8
9
10
document.querySelector("#btn").onclick = function () {
let xhr = new XMLHttpRequest();
xhr.open("post", "/postText", false);
xhr.onload = function () {
console.log(xhr.responseText);
};
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
let data = `username=王五&age=20`;
xhr.send(data);
};

后端接收,需要先引入 koa-body 框架,然后通过 ctx.request.body 来获取参数

1
2
3
4
5
6
router.post("/postText", (ctx) => {
console.log(ctx.request.body);
ctx.body = {
info: "请求成功",
};
});

在这里插入图片描述

前端也可以获得到后端返回的 头部信息

1
2
let allRes = xhr.getAllResponseHeaders(); // 获取响应头,有些属性是获取不到的,可以查看 w3c 的文档
let contentType = xhr.getResponseHeader("content-type");

栗子如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
document.querySelector("#btn").onclick = function () {
let xhr = new XMLHttpRequest();
xhr.open("post", "/postText", false);
xhr.onload = function () {
console.log(xhr.responseText);
let allRes = xhr.getAllResponseHeaders(); // 获取响应头,有些属性是获取不到的,可以查看 w3c 的文档
console.log(allRes);
let contentType = xhr.getResponseHeader("content-type");
console.log(contentType);
};
let data = { username: "王五", age: 20 };
xhr.send(data);
};

编码格式(自测笔记)

application/x-www-form-urlencoded

form 表单默认的编码格式如下,如果想设置其他值,在 form 标签上可以这样设置。

1
enctype = "application/x-www-form-urlencoded";

在这里插入图片描述
传递数据的样子是这样的:

1
let data = `username=王五&age=20`;
multipart/form-data

如果是上传文件的话可以用

1
xhr.setRequestHeader("Content-Type", "multipart/form-data"); //二进制编码
application/json

如果是 JSON 的话

1
xhr.setRequestHeader("content-type", "application/json");

那么数据的样子就要是这个

1
2
3
4
5
let data = JSON.stringify({
username: "王五",
age: 20,
});
xhr.send(data);

总结

  • post 是密文传输,
    • 他也可以通过 url 来传递参数,
    • 但是这样传递就失去了密文传输的意义了,没有大小显示,
    • 服务器可能会显示,如果没有限制的话,那就是无穷大了。
    • 在 http 正文传参,需要(必须)设置编码格式
  • get
    • 明文传输
    • 有大小显示

其他知识补充

  • Koa 在 ctx.body 的时候会把对象自动帮我们 JSON.stringify()

Ajax 的同步和异步

实验

来个实验就很明了

实验:我在页面上放两个按钮,注意我点的顺序:我都是先点按钮1 然后再点按钮2,其中两个按钮的逻辑如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let btns = document.querySelectorAll("button");
// 按钮1 是个网络请求
btns[0].onclick = () => {
let xhr = new XMLHttpRequest();
xhr.open("get", "/getText/3", true);
xhr.onload = function () {
console.log(xhr.responseText);
};
xhr.send();
};
// 按钮2 不需要网路请求,直接输出
btns[1].onclick = () => {
console.log("我是按钮2");
};

逻辑介绍完了,此时我在设置同步和异步

同步情况

同步:不建议,会卡进程的,我改成同步的效果如下:

在这里插入图片描述

可以看到输出的顺序,等按钮 1 的 Ajax 请求结束后才执行的 按钮 2

异步情况

异步的情况如下:
在这里插入图片描述

Ajax 默认设置的是异步,也就是你不写参数的话,默认是异步的(你可以自行测试)。

测试的时候可以用 3g 网,浏览器里能调整。

在这里插入图片描述

xhr.onload 和 onreadystatechange

原先的请求成功,可以有两种写法

其中onreadystatechange 的写法如下

1
2
3
4
5
6
7
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
if (xhr.status == 200) {
console.log(xhr.responseText);
}
}
};

onreadystatechange:存有处理服务器响应的函数,每当 readyState 改变时,onreadystatechange 函数就会被执行。

readyState 状态信息

readyState:存有服务器响应的状态信息。

  • 0: 请求未初始化(代理被创建,但尚未调用 open() 方法)
  • 1: 服务器连接已建立(open方法已经被调用)
  • 2: 请求已接收(send方法已经被调用,并且头部和状态已经可获得)
  • 3: 请求处理中(下载中,responseText 属性已经包含部分数据)
  • 4: 请求已完成,且响应已就绪(下载操作已完成)

status 常用状态码

HTTP状态码 描述
100 继续。继续响应剩余部分,进行提交请求
200 成功
301 永久移动。请求资源永久移动到新位置
302 临时移动。请求资源零时移动到新位置
304 未修改。请求资源对比上次未被修改,响应中不包含资源内容
401 未授权,需要身份验证
403 禁止。请求被拒绝
404 未找到,服务器未找到需要资源
500 服务器内部错误。服务器遇到错误,无法完成请求
503 服务器不可用。临时服务过载,无法处理请求

如何返还并接收 xml

后台返还一个 xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
router.get("/xml", (ctx) => {
ctx.set("content-type", "text/xml");
ctx.body = `<?xml version ='1.0' encoding='utf-8' ?>
<books>
<nodejs>
<name>nodejs实战</name>
<price>56元</price>
</nodejs>
<react>
<name>react入门</name>
<price>50元</price>
</react>
</books>
`;
});

前端接收 xml

1
2
3
4
5
6
7
8
9
10
11
12
13
document.querySelector("button").onclick = function () {
let xhr = new XMLHttpRequest();
xhr.open("get", "/xml", true);
xhr.onload = function () {
// 这里有三种返还方式
// console.log(xhr.responseText);
console.log(xhr.responseXML);
// console.log(xhr.response); // 原始数据
let name = xhr.responseXML.getElementsByTagName("name")[0].innerHTML;
console.log(name);
};
xhr.send();
};

如果后端没有声明返还的文件是 xml,也就是 ctx.set('content-type', 'text/xml') 这个,那么前端可以重新设置头

就是你觉得哎后端 返回来一堆东西优点像 xml 啊,但是后端它没设置,那么咱们前端也可以设置

1
xhr.overrideMimeType("text/xml"); // 前端设置 content-type 类型

举个使用场景的栗子:

1
2
3
4
5
6
7
8
9
10
document.querySelector("button").onclick = function () {
let xhr = new XMLHttpRequest();
xhr.open("get", "/xml", true);
xhr.overrideMimeType("text/xml"); // 前端设置 content-type 类型
xhr.onload = function () {
let name = xhr.responseXML.getElementsByTagName("name")[0].innerHTML;
console.log(name);
};
xhr.send();
};

利用 FormData 实现文件上传

简写上传

主要利用的也是 ajax,这里引入了新的对象 new FormData()

概括流程:我们要把文件数据获取并 appendFormData 这个对象中去,然后把 formdata 发送到服务器中。

前端这边代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
document.querySelector("button").onclick = function () {
let files = document.querySelector(".myfile").files[0]; // 获取文件
console.log(files); // 类数组
let form = new FormData(); // 他会自动帮我设置 content-type
form.append("img", files);
form.append("name", "测试");

let xmlHttpReq = new XMLHttpRequest();
xmlHttpReq.open("post", "/upload", true);
xmlHttpReq.onload = function () {
console.log(xmlHttpReq.responseText);
};
xmlHttpReq.send(form);
};

后端接收注意要在后端这里允许上传文件

1
2
3
4
5
app.use(
KoaBody({
multipart: true,
})
);

路由如下:

1
2
3
4
router.post("/upload", (ctx) => {
console.log(ctx.request.body); // 接收文字
console.log(ctx.request.files); // 接收文件信息
});

后端处理文件

node 会帮我们把文件存储到临时地址,我们可以通过 fs 模块拿到文件,然后写到自己想要的位置

后端检测文件夹是否存在,并且转存文件到指定目录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
router.post("/upload", (ctx) => {
try {
console.log(ctx.request.body);
console.log(ctx.request.files);
let imgPath = ctx.request.files.img.path;
console.log(imgPath);
// 这个路径相当于 index.js 运行时的
// 检测目标目录下是否有 名为 xx 的文件夹。如果没有就创建一个
// 判断 images 文件夹是否存在,images/ 这个斜杠也是可以带的
fs.exists("static/images/", (isExists) => {
console.log("是否存在 img 文件夹", isExists);
if (isExists === false) {
fs.mkdirSync("static/images");
}
let imgName = ctx.request.files.img.name;
let fileData = fs.readFileSync(imgPath);
fs.writeFileSync("static/images/" + imgName, fileData);
});
ctx.body = {
info: "上传成功",
};
} catch (err) {
console.log(err);
}
});

前端 xhr.upload 上传钩子函数

大概有如下几个钩子(比较常用的)

1
2
3
4
5
6
7
8
9
10
11
12
xhr.upload.onprogress = (event) => {
console.log("上传过程");
};
xhr.upload.onload = () => {
console.log("上传成功");
};
xhr.upload.onloadend = () => {
console.log("上传完成");
};
xhr.upload.onabort = () => {
console.log("取消上传");
};

onprogress 这个函数是在上传过程中不断循环被执行的,其中有事件因子 event,里面会有上传中的信息

如果想要监控速度和进度的话,可以在上传的过程中计算出来

利用钩子函数计算下载速度和进度

思路就是求出一段时间的下载量和一段时间,然后做除法

在这里插入图片描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
let oldDataSize;
let oldTime;
xhr.onload = function () {
let responseText = xhr.responseText;
console.log("上传成功", responseText);
};
xhr.upload.onloadstart = () => {
console.log("上传开始!");
oldLoaded = 0;
oldTime = new Date();
};
xhr.upload.onprogress = (enent) => {
let duringLoaded = (event.loaded - oldLoaded) / 1024;
let duringTime = (new Date() - oldTime) / 1000; // 时间戳,默认单位是毫秒

// 记录旧的数据,下次循环的时候需要用的
oldTime = new Date();
oldLoaded = event.loaded;

console.log("上传中");
console.log(event);
};

代码 git 地址

高级-前后端交互

1
https://gitee.com/lovely_ruby/DailyPractice/tree/main