一、原生Ajax
AJAX: 全称为Asynchronous Javascript And XML,就是异步的 JS 和 XML
- 它可以使用 JSON,XML,HTML 和 text 文本等格式发送和接收数据
- AJAX 不是新的编程语言,而是一种将现有的标准组合在一起使用的新方式
所用依赖
"dependencies": {
"@koa/router": "^12.0.0",
"axios": "^0.27.2",
"body-parser": "^1.19.0",
"cookie-parser": "^1.4.5",
"express": "^4.17.1",
"express-session": "^1.17.2",
"koa": "^2.13.4",
"koa-body": "^5.0.0",
"koa-logger": "^3.2.1",
"koa-static": "^5.0.0",
"koa2-cors": "^2.0.6",
"mongodb": "^4.4.0",
"nprogress": "^0.2.0",
"qs": "^6.11.0"
}
1.原生Ajax经典步骤
-
Ajax发送请求的经典步骤
- 第一步:创建网络请求的AJAX对象(使用XMLHttpRequest)
- 第二步:监听XMLHttpRequest对象状态的变化,或者监听onload事件(请求完成时触发)
- 第三步:配置网络请求(通过open方法)
- 第四步:发送send网络请求
-
XMLHttpRequest的state(状态)
在一次网络请求中看到状态发生了很多次变化,这是因为对于一次请求来说包括如下的状态- 0 UNSENT 代理被创建,但尚未调用 open() 方法。
- 1 OPENED open() 方法已经被调用。
- 2 HEADERS_RECEIVED send() 方法已经被调用,并且头部和状态已经可获得。
- 3 LOADING 下载中, responseText 属性已经包含部分数据。
- 4 DONE 下载操作已完成。
-
注意
- 这个状态并非是HTTP的相应状态,而是记录的XMLHttpRequest对象的状态变化
- http响应状态通过status获取
前端代码
<!--
前端能发请求的方式:
// ================= get
浏览器的地址栏
a标签的href
img标签src
script标签的src
link标签的href
postman工具也可以发请求
表单
ajax
// ================= post
postman工具也可以发请求
表单
ajax
AJAX:Asynchronous Javascript And XML(异步JavaScript和XML)
并不是一个新技术,是多门技术的结合体,如:js bom json...
作用:向后端发出请求,后端给出响应
XML:
HTML:
<div> <p> <span> ...
XML:
<pig> <dog> <student>...
在10年前,是前后端通信的一种数据格式
现在,前后端通信主要是以json为主
-----------------------------------
ajax发请求的经典步骤:
1)创建一个ajax对象 XMLHttpRequest
2)监听ajax对象的状态变化 或 监听onload事件
3)配置网络请求
4)发出请求
-->
<script>
// 1)创建一个ajax对象 XMLHttpRequest 属于BOM中的内容
let xhr = new XMLHttpRequest();
console.log(xhr);
console.log(xhr.readyState); // ajax刚创建出来的时状态是0
// 2)监听ajax对象的状态变化
xhr.onreadystatechange = function() {
// console.log("==>", xhr.readyState);
// if(xhr.readyState === 4){
// console.log("-------------");
// console.log(xhr.response);
// console.log("-------------");
// }
if (xhr.readyState === XMLHttpRequest.DONE) {
console.log("-------------");
console.log(xhr.response);
console.log("-------------");
}
}
// 3)配置网络请求
xhr.open("get", "http://127.0.0.1:3000/get")
console.log(xhr.readyState); // 调用完open方法后,ajax的状态就变成1
// 4)发出请求 send(请求体) 请求体就是给后端传递数据
// get请求没有所谓的请求体 只有post请求才能请求体
// 你发出了get请求,send() ()中不需要写任何内容
xhr.send()
console.log(xhr.readyState); // ajax的状态就变成1
</script>
后端代码
const Koa = require("koa");
const cors = require("koa2-cors");
const logger = require("koa-logger");
const Router = require("@koa/router");
const koaBody = require("koa-body");
const app = new Koa();
const router = new Router();
app.use(cors());
app.use(logger());
app.use(koaBody());
router.get("/get", async (ctx, next) => {
ctx.status = 200;
ctx.body = "hello Ajax";
});
app.use(router.routes())
router.allowedMethods();
app.listen(3000, () => {
console.log("running in http://127.0.0.1:3000");
});
2.xhr对象的load事件
除了onreadystatechange还有其他的事件可以监听
- loadstart:请求开始
- progress: 一个响应数据包到达,此时整个 response body 都在 response 中
- abort:调用 xhr.abort() 取消了请求
- error:发生连接错误,例如,域错误。不会发生诸如 404 这类的 HTTP 错误
- load:请求成功完成,我们也可以使用load来获取数据
- timeout:由于请求超时而取消了该请求(仅发生在设置了 timeout 的情况下)
- loadend:在 load,error,timeout 或 abort 之后触发
<script>
let xhr = new XMLHttpRequest();
// xhr.onreadystatechange = function () {
// if (xhr.readyState === 4) {
// console.log(xhr.response);
// }
// }
xhr.onload = function() {
console.log(xhr.response);
}
xhr.open("get", "http://127.0.0.1:3000/get");
xhr.send();
</script>
后端代码同上
3.响应JSON数据, 指定响应类型
XMLHttpRequest response 属性返回响应的正文内容
- 返回的类型取决于responseType的属性设置
- 通过responseType可以设置获取数据的类型
- 早期通常服务器返回的数据是普通的文本和XML,所以我们通常会通过responseText、 responseXML来获取响应结果
- 目前服务器基本返回的都是json数据,直接设置为json即可
前端代码
<script>
let xhr = new XMLHttpRequest();
xhr.onload = function() {
console.log(xhr.response);
console.log(xhr.response.name);
console.log(xhr.response.culture);
console.log(typeof xhr.response);
}
// 期望后端返回json数据,如果不作这样的配置,得到的是纯文本字符串
xhr.responseType = "json";
xhr.open("get", "http://127.0.0.1:3000/json");
xhr.send();
</script>
后端代码
const Koa = require("koa");
const cors = require("koa2-cors");
const logger = require("koa-logger");
const Router = require("@koa/router");
const koaBody = require("koa-body");
const app = new Koa();
const router = new Router();
app.use(cors());
app.use(logger());
app.use(koaBody());
router.get("/json", async (ctx, next) => {
ctx.status = 200;
ctx.type = "json";
ctx.body = {
name: "码路教育",
culture: "为学员服务",
};
});
app.use(router.routes())
router.allowedMethods();
app.listen(3000, () => {
console.log("running in http://127.0.0.1:3000");
});
4.响应XML数据
前端代码
<script>
let xhr = new XMLHttpRequest();
xhr.onload = function() {
// console.log(xhr.response);
// 得到xml数据
console.log(xhr.responseXML);
}
// xhr.responseType = "xml" // 不OK
xhr.open("get", "http://127.0.0.1:3000/xml");
xhr.send();
</script>
后端代码
const Koa = require("koa");
const cors = require("koa2-cors");
const logger = require("koa-logger");
const Router = require("@koa/router");
const koaBody = require("koa-body");
const app = new Koa();
const router = new Router();
app.use(cors());
app.use(logger());
app.use(koaBody());
router.get("/xml", async (ctx, next) => {
ctx.status = 200;
ctx.type = "xml"
ctx.body = `
<content>
<name>码路教育</name>
<culture>为学员服务</culture>
</content>
`;
});
app.use(router.routes())
router.allowedMethods();
app.listen(3000, () => {
console.log("running in http://127.0.0.1:3000");
});
5.响应的状态status
- XMLHttpRequest的state是用于记录xhr对象本身的状态变化,并非针对于HTTP的网络请求状态
- 如果我们希望获取HTTP响应的网络状态,可以通过status和statusText来获取
前端代码
<script>
let xhr = new XMLHttpRequest();
xhr.onload = function() {
// console.log(xhr.response);
// xhr.status,得到响应的状态码
console.log(xhr.status);
}
// xhr.open("post", "http://127.0.0.1:3000/status/500");
// xhr.open("post", "http://127.0.0.1:3000/status/503");
xhr.open("post", "http://127.0.0.1:3000/status/404");
xhr.send();
</script>
后端代码
const Koa = require("koa");
const cors = require("koa2-cors");
const logger = require("koa-logger");
const Router = require("@koa/router");
const koaBody = require("koa-body");
const app = new Koa();
const router = new Router();
app.use(cors());
app.use(logger());
app.use(koaBody());
router.post("/status/500", async (ctx, next) => {
ctx.status = 500;
ctx.body = 500;
});
router.post("/status/503", async (ctx, next) => {
ctx.status = 503;
ctx.body = 503;
});
router.post("/status/403", async (ctx, next) => {
ctx.status = 403;
ctx.body = 403;
});
router.post("/status/404", async (ctx, next) => {
ctx.status = 404;
ctx.body = {
ok: 1
};
});
app.use(router.routes())
router.allowedMethods();
app.listen(3000, () => {
console.log("running in http://127.0.0.1:3000");
});
6.GET请求传递参数
常见的传递给服务器数据的方式有如下几种
- 方式一:GET请求的query参数
- 方式二:POST请求 x-www-form-urlencoded 格式
- 方式三:POST请求 FormData 格式
- 方式四:POST请求 JSON 格式
前端代码
<script>
let xhr = new XMLHttpRequest();
xhr.onload = function() {
console.log(xhr.response);
}
xhr.responseType = "json"
// query传参 在谷歌浏览器的控制台有一个payload 表示query传递的参数
// querystring 查询字符串
// xhr.open("get", "http://127.0.0.1:3000/get?name=wc&age=18&address=bj");
// xhr.send();
// params传参
// 在谷歌浏览器的控制台中没有payload 但是也给后端传递了参数
xhr.open("get", "http://127.0.0.1:3000/get/wc/18/bj");
xhr.send();
</script>
后端代码
const Koa = require("koa");
const cors = require("koa2-cors");
const logger = require("koa-logger");
const Router = require("@koa/router");
const koaBody = require("koa-body");
const app = new Koa();
const router = new Router();
app.use(cors());
app.use(logger());
app.use(koaBody());
router.get("/get", async (ctx, next) => {
ctx.status = 200;
ctx.body = ctx.query;
});
router.get("/get/:name/:age/:address", ctx => {
ctx.status = 200;
ctx.body = ctx.params;
})
app.use(router.routes())
router.allowedMethods();
app.listen(3000, () => {
console.log("running in http://127.0.0.1:3000");
});
7. post请求传递参数
前端代码
<script>
let xhr = new XMLHttpRequest();
xhr.onload = function() {
console.log(xhr.response);
}
// xhr.responseType = "json"
xhr.open("post", "http://127.0.0.1:3000/post");
// 如果不设置请求求,默认Content-Type是text/plain
// 此时谷歌浏览器调度面板中的payload显示Request Data
// Content-Type: text / plain; charset = UTF - 8
// 如果配置了请求头是application/x-www-form-urlencoded
// 此时谷歌浏览器调度面板中的payload显示Form Data
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded")
// post传参,需要放在请求体中
// ()中请求体
xhr.send("name=xq&age=28&address=zz");
</script>
后端代码
const Koa = require("koa");
const cors = require("koa2-cors");
const logger = require("koa-logger");
const Router = require("@koa/router");
const koaBody = require("koa-body");
const app = new Koa();
const router = new Router();
app.use(cors());
app.use(logger());
app.use(koaBody());
router.post("/post", async (ctx, next) => {
ctx.status = 200;
ctx.body = ctx.request.body;
});
app.use(router.routes())
router.allowedMethods();
app.listen(3000, () => {
console.log("running in http://127.0.0.1:3000");
});
8. formData传递参数
FormData.append()
https://developer.mozilla.org/zh-CN/docs/Web/API/FormData/append
- FormData 接口的append() 方法 会添加一个新值到 FormData 对象内的一个已存在的键中,如果键不存在则会添加该键。
- FormData.set 和 append() 的区别在于,如果指定的键已经存在, FormData.set 会使用新值覆盖已有的值,而 append() 会把新值添加到已有值集合的后面。
前端代码
formData.append(name, value);
formData.append(name, value, filename);
<meta charset="utf-8">
<form class="info">
<input type="text" name="username">
<input type="text" name="pwd">
</form>
<button class="send">发送请求</button>
<script>
let formEle = document.querySelector(".info")
let sendEle = document.querySelector(".send")
sendEle.onclick = function() {
let xhr = new XMLHttpRequest();
xhr.onload = function() {
console.log(xhr.response);
}
xhr.open("post", "http://127.0.0.1:3000/post");
// formData传参,在谷歌浏览器的控制台中payload中显示form Data
// 但是不一样的是 当点view source
// formData是一个容器
// let formData = new FormData();
// formData.append("name", "z3")
// formData.append("age", "18")
// xhr.send(formData)
let formData = new FormData(formEle);
xhr.send(formData)
}
</script>
后端代码
const Koa = require("koa");
const cors = require("koa2-cors");
const logger = require("koa-logger");
const Router = require("@koa/router");
// 之前: body-parser
// 现在: koa-body
const koaBody = require("koa-body");
const app = new Koa();
const router = new Router();
app.use(cors());
app.use(logger());
app.use(koaBody({
multipart: true
}));
router.post("/post", async (ctx, next) => {
ctx.status = 200;
ctx.body = ctx.request.body;
});
app.use(router.routes())
router.allowedMethods();
app.listen(3000, () => {
console.log("running in http://127.0.0.1:3000");
});
9. 传递多种形式的参数
前端代码
<script>
let xhr = new XMLHttpRequest();
xhr.onload = function() {
console.log(xhr.response);
}
xhr.responseType = "json"
xhr.open("post", "http://127.0.0.1:3000/post/wc/18?score=100&id=001");
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded")
xhr.send("name=xq&age=28&address=zz");
</script>
后端代码
const Koa = require("koa");
const cors = require("koa2-cors");
const logger = require("koa-logger");
const Router = require("@koa/router");
// 之前: body-parser
// 现在: koa-body
const koaBody = require("koa-body");
const app = new Koa();
const router = new Router();
app.use(cors());
app.use(logger());
app.use(koaBody({
multipart: true
}));
router.post("/post/:name/:age", async (ctx, next) => {
ctx.status = 200;
let postData = ctx.request.body;
let paramsData = ctx.params;
let queryData = ctx.query;
console.log("postData:", postData);
console.log("paramsData:", paramsData);
console.log("queryData:", queryData);
ctx.body = ctx.request.body;
});
app.use(router.routes())
router.allowedMethods();
app.listen(3000, () => {
console.log("running in http://127.0.0.1:3000");
});
10.总结
get传参:
query传参
/get?name=wc&age18
后端: ctx.query
在谷歌浏览器控制台中有一个payload面板
payload面板中显示 Query Stirng 查询字符串
params传参
/get/wc/18/bj
后端: /get/:name/:age/:address
ctx.params
在谷歌浏览器控制台中没有payload面板
post传参:
send中直接传递请求体:
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded")
send(name=wc&age=18&address=bj)
后端: 配置body-parser 或 koa-body
后端: ctx.request.body
在谷歌浏览器控制台中有一个payload面板
payload面板中显示 Form Data 点开view source
name=wc&age=18&address=bj
如果没有配置
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded")
谷歌浏览器控制台中有一个payload面板
payload面板中显示 Request Data
把数据放到FormData容器:
let formData = new FormData();
向容器中放数据:
1)formData.append("username","wc")
2)在new FormData()直接指定表单 这样表单中的数据就是收集到容器
谷歌浏览器控制台中有一个payload面板,payload面板中显示 Form Data,点开view source 和 send中传递请求体是不一样的
如果是post传参,能不能传递query参数和params参数?
答:可以
11. 检测用户名是否存在
前端代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h2>用户注册</h2>
<form method="post">
<ul>
<li>用户名:<input type="text" name="username" id="user"><span id="msg"></span></li>
<li>密码:<input type="text" name="pwd"></li>
<li>确认密码:<input type="text" name="repwd"></li>
<li><input type="submit" value="注册"></li>
</ul>
</form>
<script>
let user = document.querySelector("#user")
let msg = document.querySelector("#msg")
user.onblur = function () {
// this 表示事件源 this.value 表示输入框中的内容
let username = this.value;
let xhr = new XMLHttpRequest();
xhr.onload = function () {
let res = xhr.response;
if (res.code == 0) {
msg.innerHTML = res.msg;
msg.style.color = "red"
} else {
msg.innerHTML = res.msg;
msg.style.color = "green"
}
}
xhr.responseType = "json"
xhr.open("post", "http://127.0.0.1:3000/check");
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded")
xhr.send(`username=${username}`)
}
</script>
</body>
</html>
后端代码
const Koa = require("koa");
const cors = require("koa2-cors");
const logger = require("koa-logger");
const Router = require("@koa/router");
const koaBody = require("koa-body");
const app = new Koa();
const router = new Router();
app.use(cors());
app.use(logger());
app.use(koaBody());
// 模拟从数据库中取到的用户信息
let users = ["wc", "xq", "admin"];
router.post("/check", (ctx) => {
let username = ctx.request.body.username.trim();
if (users.find(user => user === username)) {
ctx.body = {
code: 0,
msg: "对不起,该用户名已经被注册,请换个用户名"
}
} else {
ctx.body = {
code: 1,
msg: "恭喜你,该用户可以使用"
}
}
})
app.use(router.routes())
router.allowedMethods();
app.listen(3000, () => {
console.log("running in http://127.0.0.1:3000");
});
12. 省市区三级联动
前端代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h2>省市区三级联动</h2>
<label for="">请选择省份:</label>
<select id="province">
<option value="">--省份--</option>
</select>
<select id="city">
<option value="">--市区--</option>
</select>
<select id="country">
<option value="">--区县--</option>
</select>
<script>
let province = document.getElementById("province")
let city = document.getElementById("city")
// 请求所有的省份
let xhr = new XMLHttpRequest(); // 第一步:创建xhr对象
xhr.open("get", "http://127.0.0.1:3000/province"); // 第二步:建立连接
xhr.send(null); // 第三步:发出请求 null可写可不写
xhr.responseType = "json";
// 一上来,就是获取所有的省
xhr.onload = function() {
let provinceData = xhr.response;
let str = `<option value="">--省份--</option>`;
provinceData.forEach(item => {
str += `<option value="${item}">${item}</option>`
})
province.innerHTML = str;
}
province.onchange = function() {
xhr.open("get", "http://127.0.0.1:3000/city?province=" + this.value)
xhr.send(null);
xhr.onload = function() {
let cityData = xhr.response;
let str = ` <option value="">--市区--</option>`;
cityData.forEach(item => {
str += `<option value="${item}">${item}</option>`
})
city.innerHTML = str;
}
city.onchange = function () {
console.log(this.value);
xhr.open("get", "http://127.0.0.1:3000/country?province=" + province + "&city=" + this.value);
xhr.send(null)
xhr.onload = function () {
let countryData = xhr.response;
let str = `<option value="">--区县--</option>`;
countryData.forEach(item => {
str += `<option value="${item}">${item}</option>`
})
country.innerHTML = str;
}
}
}
</script>
</body>
</html>
后端代码
const Koa = require("koa");
const cors = require("koa2-cors");
const logger = require("koa-logger");
const Router = require("@koa/router");
const koaBody = require("koa-body");
const app = new Koa();
const router = new Router();
app.use(cors());
app.use(logger());
app.use(koaBody());
// data表示服务器端的所有的数据
let data = require("./cityData.min.json")
// 响应所有的省份
router.get("/province", (ctx) => {
let province = [];
data.forEach(item => {
// item代表每一个省的所有数据
province.push(item.n)
})
// json有两种形式:1)对象的形式 2)数组的形式
ctx.body = province; // 响应给xhr一个json字符串 ["北京市","河南省","河北省"...]
})
// 根据省份,响应对应的市
router.get("/city", (ctx) => {
let province = ctx.query.province;
let cities = [];
data.forEach(item => {
if (item.n === province) {
item.s.forEach(item1 => {
cities.push(item1.n)
})
}
})
ctx.body = cities
})
// 根据省份,响应对应的市,对应区
router.get("/country", (ctx) => {
let province = ctx.query.province;
let city = ctx.query.city;
let countrys = []
let arr = data.filter(item => {
return item.n === province
})[0].s
arr.forEach(item => {
if (item.n === city) {
item.s && item.s.forEach(item1 => {
countrys.push(item1.n)
})
}
})
ctx.body = countrys
})
app.use(router.routes())
router.allowedMethods();
app.listen(3000, () => {
console.log("running in http://127.0.0.1:3000");
});
14. 传统ajax分页
前端代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
* {
margin: 0;
padding: 0;
}
li {
list-style: none;
}
body {
background: #eee;
}
.wrapper {
background: #fff;
width: 970px;
margin: 20px auto;
padding: 15px;
}
h1 {
text-align: center;
border-bottom: 1px solid #ddd;
padding-bottom: 20px;
}
li {
margin: 20px 0;
border-bottom: 1px dotted #eee;
padding-bottom: 20px;
}
p {
line-height: 25px;
}
</style>
<!-- CDN -->
<script src="https://libs.baidu.com/jquery/1.11.3/jquery.min.js"></script>
</head>
<body>
<div class="wrapper">
<h1>新闻列表(AJAX普通分页)
<script>
document.write(new Date().toLocaleString())
</script>
</h1>
<ul>
</ul>
<div class="footer">
<p>总共有<span id="total"></span>条新闻,每页显示<span id="pagesize"></span>条,
当前是<span id="page"></span>/<span id="size"></span>页
<a href="#" id="prev">上一页</a>
<a href="#" id="next">下一页</a>
</p>
</div>
</div>
<script>
let page = 1;
let pageSize = 3;
// axios promise
function getNewsList(page, pageSize) {
$.get("http://127.0.0.1:3000/news", {
page,
pageSize
}, res => {
let str = "";
res.news.forEach(item => {
str += `
<li>
<h2>${item.title}</h2>
<p class="time">${item.time}</p>
<p class="summary">${item.summary}</p>
</li>
`
})
$("ul").html(str)
$("#total").html(res.total)
$("#pagesize").html(res.pageSize)
$("#page").html(res.page)
$("#size").html(res.size)
})
}
getNewsList(page, pageSize)
$("#prev").click(function(e) {
e.preventDefault();
if (page > 1) {
getNewsList(--page, pageSize)
}
})
$("#next").click(function(e) {
e.preventDefault();
if (page < $("#size").html()) {
getNewsList(++page, pageSize)
}
})
</script>
</body>
</html>
后端代码
const Koa = require("koa");
const cors = require("koa2-cors");
const logger = require("koa-logger");
const Router = require("@koa/router");
const koaBody = require("koa-body");
const MongoClient = require('mongodb').MongoClient
const app = new Koa();
const router = new Router();
app.use(cors());
app.use(logger());
// app.use(koaBody());
const client = new MongoClient('mongodb://127.0.0.1:27017')
// 链接服务端
client.connect()
console.log('链接成功')
// 获取数据库
const db = client.db('newsDB')
// 获取集合
const grade1 = db.collection('news')
router.get("/news", async ctx => {
let page = ctx.query.page || 1;
(page <= 0) && (page = 1)
let pageSize = ctx.query.pageSize;
let r = await grade1
.find()
.skip((page - 1) * pageSize)
.limit(+pageSize)
.toArray()
let total = await grade1.find().count();
let size = Math.ceil(total / pageSize) // size表示一共有多少页
ctx.body = {
news: r,
total: total,
pageSize,
page,
size
}
// 关闭客户端的链接
// client.close()
})
app.use(router.routes())
router.allowedMethods();
app.listen(3000, () => {
console.log("running in http://127.0.0.1:3000");
});
[
{
"title": "news标题001",
"time": "2022-08-12",
"summary": "刚刚收官的《大考》《底线》和《胡同》后,《追光者》《我们这十年》正在播出。《麓山之歌》《大山的女儿》又开始重播。"
},
{
"title": "news标题002",
"time": "2022-08-12",
"summary": "刚刚收官的《大考》《底线》和《胡同》后,《追光者》《我们这十年》正在播出。《麓山之歌》《大山的女儿》又开始重播。"
},
{
"title": "news标题003",
"time": "2022-08-12",
"summary": "刚刚收官的《大考》《底线》和《胡同》后,《追光者》《我们这十年》正在播出。《麓山之歌》《大山的女儿》又开始重播。"
}
]
15. 点击加载更多
前端代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
* {
margin: 0;
padding: 0;
}
li {
list-style: none;
}
body {
background: #eee;
}
.wrapper {
background: #fff;
width: 970px;
margin: 20px auto;
padding: 15px;
}
h1 {
text-align: center;
border-bottom: 1px solid #ddd;
padding-bottom: 20px;
}
li {
margin: 20px 0;
border-bottom: 1px dotted #eee;
padding-bottom: 20px;
}
p {
line-height: 25px;
}
</style>
<script src="https://libs.baidu.com/jquery/1.11.3/jquery.min.js"></script>
</head>
<body>
<div class="wrapper">
<h1>新闻列表(ajax点击加载更多分页)
<script>
document.write(new Date().toLocaleString())
</script>
</h1>
<ul>
</ul>
<div class="footer" style="text-align: center;">
<button id="btn" style="width: 120px; height: 60px; font-size: 20px;">加载更多</button>
</div>
</div>
<script>
let page = 1;
let pageSize = 3;
function getNewsList(page, pageSize) {
$.get("http://127.0.0.1:3000/news", {
page,
pageSize
}, res => {
if (res.news.length) {
let str = "";
res.news.forEach(item => {
str += `
<li>
<h2>${item.title}</h2>
<p class="time">${item.time}</p>
<p class="summary">${item.summary}</p>
</li>
`
})
$("ul").append(str)
}
})
}
getNewsList(page, pageSize)
$("#btn").click(function(e) {
getNewsList(++page, pageSize)
})
</script>
</body>
</html>
后端代码同上
16. 滚动加载更多
前端代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<style>
* {
margin: 0;
padding: 0;
}
li {
list-style: none;
}
body {
background: #eee;
}
.wrapper {
background: #fff;
width: 970px;
margin: 20px auto;
padding: 15px;
}
h1 {
text-align: center;
border-bottom: 1px solid #ddd;
padding-bottom: 20px;
}
li {
margin: 20px 0;
border-bottom: 1px dotted #eee;
padding-bottom: 20px;
}
p {
line-height: 25px;
}
</style>
<script src="https://libs.baidu.com/jquery/1.11.3/jquery.min.js"></script>
<!-- underscore中别人封装了非常多的工具函数 -->
<!-- 其中就有节流函数:具体可以看一下每日一题 -->
<script src="https://cdn.jsdelivr.net/npm/underscore@1.13.4/underscore-umd-min.js"></script>
</head>
<body>
<div class="wrapper">
<h1>新闻列表(AJAX普通分页)
<script>
document.write(new Date().toLocaleString())
</script>
</h1>
<ul>
</ul>
<div class="footer" style="text-align: center;">
<img src="" alt="" width="40px">
</div>
</div>
<script>
let page = 1;
let pageSize = 6;
let load = true;
function getNewsList(page, pageSize) {
// 开启loading效果
$(".footer img").attr("src", "./imgs/timg.gif")
$.get("http://127.0.0.1:3000/news", { page, pageSize }, res => {
if (res.news.length) {
let str = "";
res.news.forEach(item => {
str += `
<li>
<h2>${item.title}</h2>
<p class="time">${item.time}</p>
<p class="summary">${item.summary}</p>
</li>
`
})
$("ul").append(str)
load = true;
} else {
$(".footer").html("------------- 我是有底线 -------------")
load = false;
}
})
}
getNewsList(page, pageSize);
// 当滚动滚动条时,就会触发,高频触发
// 如何降低频率?
// 答:节流
// document.onscroll = function () {
// console.log("-------");
// }
// _是underscore中提供的一个对象
// throttle 就是节流函数
// document.onscroll = _.throttle(function () {
// console.log("-------");
// }, 1000)
document.onscroll = _.throttle(function () {
let st = $(window).scrollTop(); // 卷上去的高度
let ch = $(window).height(); // 一屏的高度
let dh = $(document).height(); // 整个内容的高度
if ((st + ch) >= (dh - 10) && load) {
getNewsList(++page, pageSize);
}
}, 500)
</script>
</body>
</html>
后端代码同上
13. 延迟时间timeout和取消请求
在网络请求的过程中,为了避免过长的时间服务器无法返回数据,通常我们会为请求设置一个超时时间:timeout
- 当达到超时时间后依然没有获取到数据,那么这个请求会自动被取消掉
- 默认值为0,表示没有设置超时时间
<script>
let xhr = new XMLHttpRequest();
xhr.open("get", "http://httpbin.org/delay/5")
xhr.send();
// 设置超时时间
xhr.timeout = 3000; // 如果请求超过3秒就断开了(也就是自动取消请求)
xhr.onload = function() {
console.log(xhr.response);
}
</script>
<button>取消请求</button>
<script>
let xhr = new XMLHttpRequest();
xhr.open("get", "http://httpbin.org/delay/5");
xhr.send();
// xhr.timeout = 3000; // 如果超过3秒自动取消请求
xhr.onload = function() {
console.log(xhr.response);
}
let btn = document.getElementsByTagName("button")[0];
btn.onclick = function() {
// 手动取消请求
xhr.abort();
}
</script>
二、axios
1.axios
axios是什么?
- 前端最流行的ajax请求库
- react/vue官方都推荐使用axios发ajax请求
- 文档: https://github.com/axios/axios
功能特点
- 基于xhr + promise的异步ajax请求库
- 浏览器端/node端都可以使用
- 支持 Promise API
- 拦截请求和响应
- 转换请求和响应数据
- 支持请求取消
- 批量发送多个请求
axios请求方式
- axios(config): 通用/最本质的发任意类型请求的方式
- axios(url[, config]): 可以只指定url发get请求
- axios.request(config): 等同于axios(config)
- axios.get(url[, config]): 发get请求
- axios.delete(url[, config]): 发delete请求
- axios.post(url[, data, config]): 发post请求
- axios.put(url[, data, config]): 发put请求
- axios.defaults.xxx: 请求的默认全局配置
- axios.interceptors.request.use(): 添加请求拦截器
- axios.interceptors.response.use(): 添加响应拦截器
- axios.create(config): 创建一个新的axios(它没有下面的功能)
- axios. Cancel(): 用于创建取消请求的错误对象
- axios. CancelToken(): 用于创建取消请求的token对象
- axios.isCancel(): 是否是一个取消请求的错误
- axios.all(promises): 用于批量执行多个异步请求
- axios.spread(): 用来指定接收所有成功数据的回调函数的方法
常见的配置选项
- 请求地址 url: ‘/user’
- 请求类型 method: ‘get’
- 根求路径 baseURL: ‘http://www.mt.com/api’
- 请求前的数据处理 transformRequest:function(data){}
- 请求后的数据处理 transformResponse: function(data){}
- 自定义的请求头 headers:{‘x-Requested-With’:‘XMLHttpRequest’}
- URL查询对象 params:{ id: 12 }
- 查询对象序列化函数 paramsSerializer: function(params){ }
- request body data: { key: ‘aa’}
- 超时设置 timeout: 1000
2.发送request请求
前端代码
<!-- <script src="../node_modules/axios/dist/axios.min.js"></script> -->
<script src="https://cdn.bootcdn.net/ajax/libs/axios/0.21.1/axios.min.js"></script>
<script>
// 利用axios发请求,得到的结果是一个promise
// 要得到成功的结果,就是需要.then
// 真实的数据是在data中,其它的数据,都是axios帮我们封装好的
// let axRes = axios.request({
// url: "http://127.0.0.1:3000/get",
// method: "get"
// })
// axRes.then(res => {
// console.log("data:", res.data);
// })
axios.request({
url: "http://127.0.0.1:3000/get",
method: "get"
}).then(res => {
console.log("data:", res.data);
})
</script>
后端代码
const Koa = require("koa");
const cors = require("koa2-cors");
const logger = require("koa-logger");
const Router = require("@koa/router");
const koaBody = require("koa-body");
const app = new Koa();
const router = new Router();
app.use(cors());
app.use(logger());
app.use(koaBody());
router.get("/get", async (ctx, next) => {
ctx.status = 200;
ctx.body = "hello axios";
});
app.use(router.routes())
router.allowedMethods();
app.listen(3000, () => {
console.log("running in http://127.0.0.1:3000");
});
3. 发送get请求
<script src="https://cdn.bootcdn.net/ajax/libs/axios/0.21.1/axios.min.js"></script>
<script>
// axios.get("http://127.0.0.1:3000/get").then(res => {
// console.log(res.data);
// })
(async function() {
let res = await axios.get("http://127.0.0.1:3000/get")
console.log(res);
})()
</script>
后端代码同上
4. 发送get请求并传参
前端代码
<script src="https://cdn.bootcdn.net/ajax/libs/axios/0.21.1/axios.min.js"></script>
<script>
(async function() {
// query传参
// let res = await axios.get("http://127.0.0.1:3000/get?a=1&b=2")
// console.log(res.data);
// params传参
let res = await axios.get("http://127.0.0.1:3000/get", {
params: {
name: "wc",
age: 18,
address: "bj"
}
})
console.log(res.data);
})()
</script>
后端代码
const Koa = require("koa");
const cors = require("koa2-cors");
const logger = require("koa-logger");
const Router = require("@koa/router");
const koaBody = require("koa-body");
const app = new Koa();
const router = new Router();
app.use(cors());
app.use(logger());
app.use(koaBody());
router.get("/get", async (ctx, next) => {
ctx.status = 200;
ctx.body = ctx.query;
});
router.get("/get/:name/:age/:address", ctx => {
ctx.status = 200;
ctx.body = ctx.params;
})
app.use(router.routes())
router.allowedMethods();
app.listen(3000, () => {
console.log("running in http://127.0.0.1:3000");
});
5. 发送post请求
前端代码
<script src="https://cdn.bootcdn.net/ajax/libs/axios/0.21.1/axios.min.js"></script>
<!-- querystring -->
<script src="../node_modules/qs/dist/qs.js"></script>
<script>
(async function() {
// post传递参数
// 默认是json格式传递 Content-Type: application/json;charset=UTF-8
// let res = await axios.post("http://127.0.0.1:3000/post",{
// a:1,
// b:2,
// c:3
// })
// console.log(res.data);
// post传递参数2
// 设置请求头 axios配置请求头
// axios 是一个对象,对象也叫实例
// 默认情况下,我们可以使用人家提供给我们的实例,人家帮我们提供的实例,很多配置都是定死的
// 我们能不能自己去创建一个实例?
// 答:可以
// 如果我们想自己去配置一个更加灵活的axios实例,或我需要向不同的服务器发请求?
// 此时,我们就可以自己去创建axios实例
let res = await axios.post("http://127.0.0.1:3000/post", qs.stringify({
a: 1,
b: 2
}), {
headers: {
"Content-Type": "application/x-www-form-urlencoded"
}
})
console.log(res.data);
})()
</script>
后端代码
const Koa = require("koa");
const cors = require("koa2-cors");
const logger = require("koa-logger");
const Router = require("@koa/router");
const koaBody = require("koa-body");
const app = new Koa();
const router = new Router();
app.use(cors());
app.use(logger());
app.use(koaBody());
router.post("/post", async (ctx, next) => {
ctx.status = 200;
ctx.body = ctx.request.body;
});
app.use(router.routes())
router.allowedMethods();
app.listen(3000, () => {
console.log("running in http://127.0.0.1:3000");
});
6. 其它
<script>
// axios.get()
// axios.post()
// axios.request()
// ....
// axios发送多个请求
axios.all([
axios.get("http://httpbin.org/get"),
axios.post("http://httpbin.org/post")
]).then(res => {
console.log("res:", res);
})
</script>
<script>
// 设置axios的baseURL,如果设置了,后面的请求会自动添加baseURL
let baseURL = "http://httpbin.org"
axios.defaults.baseURL = baseURL;
axios.defaults.timeout = 3000; // 设置超时时间 如果超时了 自动取消请求
axios.defaults.headers = { // 给请求添加请求头
a: 1,
b: 2
}
axios.get("/get").then(res => {
console.log("res:", res);
})
</script>
7. axios的创建实例
为什么要创建axios的实例呢
- 当我们从axios模块中导入对象时, 使用的实例是默认的实例
- 当给该实例设置一些默认配置时, 这些配置就被固定下来了
- 但是后续开发中, 某些配置可能会不太一样
- 比如某些请求需要使用特定的baseURL或者timeout等
- 这个时候, 我们就可以创建新的实例, 并且传入属于该实例的配置信息
面试题
- 需求: 项目中有部分接口需要的配置与另一部分接口需要的配置不太一样, 如何处理
- 解决: 创建2个新axios, 每个都有自己特有的配置, 分别应用到不同要求的接口请求中
注意点
- 根据指定配置创建一个新的axios, 也就就每个新axios都有自己的配置
- 新axios只是没有取消请求和批量发请求的方法, 其它所有语法都是一致的
<script src="https://cdn.bootcdn.net/ajax/libs/axios/0.21.1/axios.min.js"></script>
<script>
// axios.get
// 创建自己的实例 {}叫配置对象,这里面,就可以书写自己的配置
let axios1 = axios.create({
baseURL: "http://127.0.0.1:3000",
timeout: 3000,
headers: {}
})
axios1.get("/news", {
params: {
a: 1
}
}).then(res => {
console.log("res:", res);
})
let axios2 = axios.create({
baseURL: "http://127.0.0.1:5000",
timeout: 5000,
headers: {}
})
axios1.get("/students", {
params: {
a: 1
}
}).then(res => {
console.log("res:", res);
})
</script>
8. 请求和响应拦截器
axios的也可以设置拦截器:拦截每次请求和响应
- axios.interceptors.request.use(请求成功拦截, 请求失败拦截)
- axios.interceptors.response.use(响应成功拦截, 响应失败拦截)
前端代码
<script src="https://cdn.bootcdn.net/ajax/libs/axios/0.21.1/axios.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/nprogress/0.2.0/nprogress.min.js"></script>
<link href="https://cdn.bootcdn.net/ajax/libs/nprogress/0.2.0/nprogress.min.css" rel="stylesheet">
<button>发送请求</button>
<script>
let btn = document.querySelectorAll("button")[0];
// 给默认实例添加请求拦截器
// 这样写,相当于,这个拦截器什么也没有做
// axios.interceptors.request.use(config => {
// return config;
// })
// 所谓的做一些事,就是做一些配置
axios.interceptors.request.use(config => {
config.headers.Authorization = `fsadfsadf6789f87s6adf68asodfihasjkdfasdf7678fasd67f8asdfas`;
NProgress.start(); // 显示进度条
return config;
}, err => {
// 失败的回调,直接响应失败的promsie
return Promise.reject(err)
})
axios.interceptors.response.use(response => {
NProgress.done(); // 关闭进度条
return response.data; // 仅仅把data过滤出来
}, err => {
// 失败的回调,直接响应失败的promsie
return Promise.reject(err)
})
btn.onclick = function() {
axios.get("http://httpbin.org/delay/5").then(res => {
console.log(res);
})
}
</script>
面试题:拦截器函数/ajax请求/请求的回调函数的调用顺序
- 说明: 调用axios()并不是立即发送ajax请求, 而是需要经历一个较长的流程
- 流程: 请求拦截器2 => 请求拦截器1 => 发ajax请求 => 响应拦截器1 => 响应拦截器2 => 请求的回调
- 注意: 此流程是通过promise串连起来的, 请求拦截器传递的是config, 响应拦截器传递的是response
9. 取消请求
基本流程
- 配置cancelToken对象
- 缓存用于取消请求的cancel函数
- 在后面特定时机调用cancel函数取消请求
- 在错误回调中判断如果error是cancel, 做相应处理
实现功能:点击按钮, 取消某个正在请求中的请求
- 在请求一个接口前, 取消前面一个未完成的请求
<!-- axios源码 JS高级 -->
<script src="https://cdn.bootcdn.net/ajax/libs/axios/0.21.1/axios.min.js"></script>
<button>发送请求</button>
<button>取消请求</button>
<script>
let btns = document.querySelectorAll("button");
let cancel;
// 在请求一个接口前,取消前面没有完成的请求
btns[0].onclick = async function() {
let result = await axios("http://httpbin.org/delay/5", {
cancelToken: new axios.CancelToken(c => {
cancel = c;
})
});
console.log("result:", result);
}
btns[1].onclick = function() {
cancel(); // 手动取消上面的请求
}
</script>
10. Axios二次封装
功能点
- 统一进行请求配置: 基础路径/超时时间等
- 请求过程中loading提示
- 请求可能需要携带token数据
- 请求成功的value不再是response, 而是response.data
- 请求失败/出错统一进行处理, 每个请求可以不用单独处理
<!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>
<script src="https://cdn.bootcdn.net/ajax/libs/axios/0.21.1/axios.min.js"></script>
<script src="../node_modules/nprogress/nprogress.js"></script>
<link rel="stylesheet" href="../node_modules/nprogress/nprogress.css">
<!-- <script src="https://cdn.bootcdn.net/ajax/libs/nprogress/0.2.0/nprogress.min.js"></script>
<link href="https://cdn.bootcdn.net/ajax/libs/nprogress/0.2.0/nprogress.min.css" rel="stylesheet"> -->
</head>
<body>
<script>
// 一般会自己创建一个axios实例 instance是实例的意思
let instance = axios.create({
// 在这里,可以做很多配置
// baseURL是配置基础路径
baseURL: "http://127.0.0.1:3000",
timeout: 8000
})
// 目前在请求拦截器中配置了:
// 1)开启Nprogress进度条
// 2)在请求头中添加token
instance.interceptors.request.use(config => {
let token = localStorage.getItem("token") || "fasdfasdfsadf";
config.headers["token"] = token;
NProgress.start();
return config;
}, err => {
return Promise.reject(err)
})
// 目前在响应拦截器中配置了:
// 1)关闭Nprogress进度条
// 2)过滤出data数据
instance.interceptors.response.use(response => {
NProgress.done();
return response.data;
}, err => {
NProgress.done();
return Promise.reject(err)
})
// 后面发请求,就可以使用自己的实例
instance.get("http://httpbin.org/delay/5").then(res => {
console.log(res);
})
</script>
</body>
</html>
11. JSONP原理
前端代码
<script src="https://cdn.bootcdn.net/ajax/libs/axios/0.21.1/axios.min.js"></script>
<button id="btn">发送ajax请求</button>
<script>
let btn = document.getElementById("btn");
function jsonp(options) {
let callBackName = "wangcai";
window[callBackName] = function(data) {
if (data != null) {
options.success(data)
} else {
options.fail()
}
}
let url = options.url + "?callBack=" + callBackName
let scriptEle = document.createElement("script");
scriptEle.src = url;
document.body.append(scriptEle)
}
btn.onclick = function() {
jsonp({
url: "http://localhost:3000/",
success: function(data) {
console.log("data:", data);
},
fail: function(err) {
console.log("数据请求失败了");
}
})
}
</script>
后端代码
const Koa = require("koa");
const cors = require("koa2-cors");
const logger = require("koa-logger");
const Router = require("@koa/router");
const koaBody = require("koa-body");
const app = new Koa();
const router = new Router();
app.use(cors());
app.use(logger());
app.use(koaBody());
router.get("/", (ctx) => {
let cb = ctx.query.callBack;
// console.log(cb);
// 后端返回函数调用字符串
ctx.body = `${cb}(${JSON.stringify({ a: 1, b: 2 })})`
})
app.use(router.routes())
router.allowedMethods();
app.listen(3000, () => {
console.log("running in http://127.0.0.1:3000");
});
12. 百度联想词案例
前端JS代码
;
(function() {
// 接口地址:https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su?wd=
let searchInput = document.getElementsByClassName("J_searchInput")[0];
let wdList = document.getElementsByClassName("J_wdList")[0];
let listWrap = wdList.parentNode;
searchInput.addEventListener("input", function() {
let val = this.value.trim();
if (val.length > 0) {
getDatas(val, "setDatas");
} else {
wdList.innerHTML = "";
listWrap.style.display = "none";
}
})
// 调接口,获取数据
function getDatas(value, callbackName) {
let oScript = document.createElement("script");
oScript.src = "https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su?wd=" + value + "&cb=" + callbackName
document.body.append(oScript)
}
window.setDatas = function(data) {
// console.log("data:",data);
// render是渲染的意思 List是列表的意思
renderList(data)
}
// <li class="wd-item">
// <a href="https://www.baidu.com/s?wd={{wdLink}}" target="_blank" class="wd-lk">{{wd}}</a>
// </li>
// 渲染数据到页面上
function renderList(data) {
var data = data.s;
let len = data.length;
let list = "";
if (len) {
data.forEach(item => {
list += `
<li class="wd-item">
<a href="https://www.baidu.com/s?wd=${item}" target="_blank" class="wd-lk">${item}</a>
</li>
`
})
wdList.innerHTML = list;
listWrap.style.display = "block";
} else {
wdList.innerHTML = "";
listWrap.style.display = "none";
}
}
})()
开源接口:
https://docs.tenapi.cn/
https://www.free-api.com/
https://api.aa1.cn/