文章目录
Ajax 简单介绍
Ajax 即 “Asynchronous Javascript And XML”(异步 JavaScript 和 XML)
它是一项技术,提高了用户的体验,为什么这么说呢?
场景
举个栗子,如果我们做个登录的功能,当用户输入完姓名后,我需要去请求后台,校验一下数据库中是否重名,如果没有 ajax,那么只能通过跳转进行后端访问,这个体验太糟糕了。
我们想的是:能否不需要刷新页面就可以访问后台接口?所以 Ajax 应运而生。
实例:验证用户名输入
根据刚才的场景,我们来写一下代码。顺便简单介绍 Ajax
的使用
前端:
-
首先新建一个
XMLHttpRequest
对象let xhr = new XMLHttpRequest();
-
然后配置请求参数
xhr.open();
,其中true
是异步,false
是同步,后面会说到xhr.open("get","/checkUserName",true); //true是异步,false是同步
-
接着接受返还值
xhr.onload = function(){ let res = JSON.parse(xhr.responseText); }
-
发送服务器
xhr.send();
前端校验用户名的主要代码如下:
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();
}
后端:
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
使用如下:
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
是这样携带参数的话
// 这里传递了一个数字: 3
// 就是参数也用斜杠 / 给隔开了
xhr.open('get', '/getText/3', false);
后台就需要这样去接收,我声明了一个叫 id
的变量(这个变量名字也可以随便取),它来接受前端传来的 3
router.get('/getText/:id', ctx => {
console.log(ctx.query); // 收不到
console.log(ctx.params); // 收得到
ctx.body = {
info: '请求成功'
}
})
注意:要写这个冒号和名称,不然前端会报错 404
的
POST
前端发送数据是要声明编码格式,设置http
正文头格式
// 发送数据时候需要设置http正文头格式;
xhr.setRequestHeader("Content-type","application/x-www-form-urlencoded"); //默认编码
xhr.setRequestHeader("Content-type","multipart/form-data"); //二进制编码
xhr.setRequestHeader("Content-type","application/json"); //json编码
以下是个栗子🌰
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
来获取参数
router.post('/postText', ctx => {
console.log(ctx.request.body);
ctx.body = {
info: '请求成功'
}
})
前端也可以获得到后端返回的 头部信息
let allRes = xhr.getAllResponseHeaders(); // 获取响应头,有些属性是获取不到的,可以查看 w3c 的文档
let contentType = xhr.getResponseHeader('content-type')
栗子如下:
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
标签上可以这样设置。
enctype="application/x-www-form-urlencoded"
传递数据的样子是这样的:
let data = `username=王五&age=20`
multipart/form-data
如果是上传文件的话可以用
xhr.setRequestHeader("Content-Type","multipart/form-data"); //二进制编码
application/json
如果是 JSON
的话
xhr.setRequestHeader("content-type","application/json");
那么数据的样子就要是这个
let data = JSON.stringify({
username:"王五",
age:20
})
xhr.send(data);
总结
- post 是密文传输,
- 他也可以通过 url 来传递参数,
- 但是这样传递就失去了密文传输的意义了,没有大小显示,
- 服务器可能会显示,如果没有限制的话,那就是无穷大了。
- 在http 正文传参,需要(必须)设置编码格式
- get
- 明文传输
- 有大小显示
其他知识补充
- Koa 在 ctx.body 的时候会把对象自动帮我们
JSON.stringify()
Ajax 的同步和异步
实验
来个实验就很明了
实验:我在页面上放两个按钮,注意我点的顺序:我都是先点
按钮1
然后再点按钮2
,其中两个按钮的逻辑如下:
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
的写法如下
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
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
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
啊,但是后端它没设置,那么咱们前端也可以设置
xhr.overrideMimeType('text/xml') // 前端设置 content-type 类型
举个使用场景的栗子:
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()
概括流程:我们要把文件数据获取并
append
到FormData
这个对象中去,然后把formdata
发送到服务器中。
前端这边代码如下:
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)
}
后端接收注意要在后端这里允许上传文件
app.use(KoaBody({
multipart: true
}))
路由如下:
router.post('/upload', ctx => {
console.log(ctx.request.body); // 接收文字
console.log(ctx.request.files); // 接收文件信息
})
后端处理文件
node
会帮我们把文件存储到临时地址,我们可以通过 fs
模块拿到文件,然后写到自己想要的位置
后端检测文件夹是否存在,并且转存文件到指定目录
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 上传钩子函数
大概有如下几个钩子(比较常用的)
xhr.upload.onprogress = (event) => {
console.log('上传过程');
}
xhr.upload.onload = () => {
console.log('上传成功');
}
xhr.upload.onloadend = () => {
console.log('上传完成');
}
xhr.upload.onabort = () => {
console.log('取消上传');
}
onprogress
这个函数是在上传过程中不断循环被执行的,其中有事件因子 event
,里面会有上传中的信息
如果想要监控速度和进度的话,可以在上传的过程中计算出来
利用钩子函数计算下载速度和进度
思路就是求出一段时间的下载量和一段时间,然后做除法
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 地址
在高级-前后端交互
中
https://gitee.com/lovely_ruby/DailyPractice/tree/main