提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
- 一、AJAX 入门——让数据活起来
- 二、AJAX 综合案例
- 三、AJAX 原理
- 四、AJAX进阶
一、AJAX 入门——让数据活起来
1.1AJAX 概念和 axios 使用
1.1.1什么是 AJAX [ˈeɪdʒæks] ?
定义:https://developer.mozilla.org/zh-CN/docs/Web/Guide/AJAX
概念:AJAX 是浏览器与服务器进行数据通信的技术
1.1.2怎么用 AJAX ?
- 先使用 axios [æk‘sioʊs] 库axios库:https://axios-http.com/zh/,与服务器进行数据通信
⚫ 基于 XMLHttpRequest 封装、代码简单、月下载量在 14 亿次
⚫ Vue、React 项目中都会用到 axios- 再学习 XMLHttpRequest 对象的使用,了解 AJAX 底层原理
1.1.3axios 使用
语法:
- 引入 axios.js:https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js
- 使用 axios 函数
✓ 传入配置对象
✓ 再用 .then 回调函数接收结果,并做后续处理
1.1.4运用axios
需求:请求目标资源地址,拿到省份列表数据,显示到页面
目标资源地址:http://hmajax.itheima.net/api/province
获取的结果存在data里面的list里面
取data的list里面的值console.log(result.data.list)得到一个数组包含省份
<body>
<p class="province"></p>
<!-- axios库地址:https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js
省份数据地址:http://hmajax.itheima.net/api/province
需求:通过使用axios库,获取省份列表数据,展示到页面上
1.引入axios
-->
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<!-- 2.使用axios函数 -->
<script>
axios({
//url告诉axios去哪个服务器地址获取数据
url:'http://hmajax.itheima.net/api/province'
}).then(result => {
console.log(result); //返回一个对象
console.log(result.data.list); //返回包含省份的数组
console.log(result.data.list.join('<br>')); //数组转字符串
//把省份列表渲染到页面上
document.querySelector('.province').innerHTML = result.data.list.join('<br>')
})
</script>
</body>
1.1.5 总结
- AJAX 有什么用?
➢ 浏览器和服务器之间通信,动态数据交互- AJAX 如何学:
➢ 先掌握 axios 使用
➢ 再了解 XMLHttpRequest 原理- 这一节 axios 体验步骤?
➢ 引入 axios 库
➢ 使用 axios 语法
1.2认识 URL
1.2.1认识 URL
1.2.2什么是 URL?
概念:URL 就是统一资源定位符,简称网址,用于访问网络上的资源
1.2.3URL 的组成
1.2.3.1协议
http 协议:超文本传输协议,规定浏览器和服务器之间传输数据的格式
1.2.3.2域名
域名:标记服务器在互联网中方位
1.2.3.3资源路径
**资源路径:**标记资源在服务器下的具体位置
1.2.4案例:获取 - 新闻列表
需求:使用 axios 从服务器拿到新闻列表数据
目标资源地址:http://hmajax.itheima.net/api/news
<body>
<!--
新闻数据地址: http://hmajax.itheima.net/api/news
-->
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
axios({
url:'http://hmajax.itheima.net/api/news'
}).then(result => {
console.log(result);
console.log(result.data.data);
})
</script>
</body>
1.2.5总结
- URL 是什么:
➢ 统一资源定位符,网址,用于访问服务器上资源- 请解释这个 URL,每个部分作用?
➢ http://hmajax.itheima.net/api/news
➢ 协议://域名/资源路径
1.3URL 查询参数
1.3.1
定义:浏览器提供给服务器的额外信息,让服务器返回浏览器想要的数据
语法:http://xxxx.com/xxx/xxx?参数名1=值1&参数名2=值2
?,&
1.3.2axios-查询参数
**语法:**使用 axios 提供的 params 选项
注意:axios 在运行时把参数名和值,会拼接到 url?参数名=值
<body>
<p class="city"></p>
<!-- axios库地址:https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js
城市数据地址:http://hmajax.itheima.net/api/city
需求:通过使用axios库,获取省份列表数据,展示到页面上
1.引入axios
-->
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
axios({
url:'http://hmajax.itheima.net/api/city',
params:{
pname:'广东省'
}
}).then(result =>{
console.log(result); //返回一个对象
console.log(result.data.list); //返回包含城市的数组
console.log(result.data.list.join('<br>')); //数组转字符串
//把城市列表渲染到页面上
document.querySelector('.city').innerHTML = result.data.list.join('<br>')
})
</script>
</body>
1.3.3总结
- URL 查询参数有什么作用?
➢ 浏览器提供给服务器额外信息,获取对应的数据- axios 要如何携带查询参数?
➢ 使用 params 选项,携带参数名和值
1.3.4案例 地区查询
需求:根据输入的省份名字和城市名字,查询地区并渲染列表
步骤 地区查询
首先:确定 URL 网址和参数说明
• 查询某个省内某个城市的所有地区: http://hmajax.itheima.net/api/area
• 参数名:
pname:省份名字或直辖市名字,比如北京、福建省、辽宁省…
cname:城市名字,比如北京市、厦门市、大连市…
完整:http://hmajax.itheima.net/api/area?pname=北京&cname=北京市
<body>
<div class="container">
<form id="editForm" class="row">
<!-- 输入省份名字 -->
<div class="mb-3 col">
<label class="form-label">省份名字</label>
<input type="text" value="北京" name="province" class="form-control province" placeholder="请输入省份名称" />
</div>
<!-- 输入城市名字 -->
<div class="mb-3 col">
<label class="form-label">城市名字</label>
<input type="text" value="北京市" name="city" class="form-control city" placeholder="请输入城市名称" />
</div>
</form>
<button type="button" class="btn btn-primary sel-btn">查询</button>
<br><br>
<p>地区列表: </p>
<ul class="list-group">
<!-- 示例地区 -->
<li class="list-group-item">东城区</li>
</ul>
</div>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
/*
获取地区列表: http://hmajax.itheima.net/api/area
查询参数:
pname: 省份或直辖市名字
cname: 城市名字
*/
//需求:根据省份和城市的名字,查询地区列表
//1.查询按钮--绑定点击事件
document.querySelector('.sel-btn').addEventListener('click',()=>{
console.log('点击');
//获取用户输入省份和城市名字
let provinceName = document.querySelector('.province').value
let cityName = document.querySelector('.city').value
console.log(provinceName,cityName);
axios({
url:'http://hmajax.itheima.net/api/area',
params:{
pname:provinceName,
cname:cityName
}
}).then(result =>{
//对获取的数据进行渲染处理
console.log(result.data.list);//得到一个数组
//数组映射
//map()函数遍历数组,()里面添加对每个元素的操作。最后返回一个数组
let cityLi = result.data.list.map(areaName => `<li class="list-group-item">${areaName}</li>`)
console.log(cityLi); //map返回的是一个字符串
//字符串拼接,加入ul的内容里面
document.querySelector('.list-group').innerHTML = cityLi.join('')
})
})
</script>
</body>
1.4常用请求方法和数据提交
1.4.1常用请求方法
请求方法:对服务器资源,要执行的操作
1.4.2数据提交
场景:当数据需要在服务器上保存
1.4.3axios 请求配置
url:请求的 URL 网址
method:请求的方法,GET可以省略(不区分大小写)
data:提交数据
1.4.4数据提交-注册账号
需求:通过 axios 提交用户名和密码,完成注册功能
注册用户 URL 地址:http://hmajax.itheima.net/api/register
请求方法:POST
参数名:
username 用户名(中英文和数字组成,最少 8 位)
password 密码(最少 6 位)
<body>
<button class="btn">注册用户</button>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
/*
注册用户: http://hmajax.itheima.net/api/register
请求方法: POST
参数名:
username: 用户名 (中英文和数字组成, 最少8位)
password: 密码 (最少6位)
目标: 点击按钮, 通过axios提交用户和密码, 完成注册
*/
//监听按钮
document.querySelector('.btn').addEventListener('click',()=>{
axios({
url:'http://hmajax.itheima.net/api/register',
//提交数据
//指定请求方法
method:'post',
data:{
username:'shaojunhao02',
password:'12345678'
}
}).then(result =>{
console.log(result.data.message);
})
})
</script>
</body>
重复注册报错提示账号被占用
1.4.5总结
- 请求方法表明对服务器资源的操作,最为常用的2个是?
➢ POST 提交数据,GET 查询数据- axios 的核心配置?
➢ url:请求 URL 网址
➢ method:请求方法,GET 可以省略(不区分大小写)
➢ params:查询参数
➢ data:提交数据
1.4.6axios 错误处理
语法:在 then 方法的后面,通过点语法调用 catch 方法,传入回调函数并定义形参
处理:注册案例,重复注册时通过弹框提示用户错误原因
需求:捕获错误信息并弹窗显示
<body>
<button class="btn">注册用户</button>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
/*
注册用户: http://hmajax.itheima.net/api/register
请求方法: POST
参数名:
username: 用户名 (中英文和数字组成, 最少8位)
password: 密码 (最少6位)
目标: 点击按钮, 通过axios提交用户和密码, 完成注册
*/
document.querySelector('.btn').addEventListener('click', () => {
axios({
url: 'http://hmajax.itheima.net/api/register',
// 指定请求方法
method: 'post',
data: {
username: 'shaojunhao02',
password: '12345678'
}
}).then(result => {
//console.log(result)//
}).catch(error =>{
//在then方法的后面,通过点语法调用catch方法,传入回调函数并定义形参
console.log(error);
alert(error.response.data.message) //账号被占用
})
})
</script>
</body>
1.5HTTP协议-报文(浏览器是怎么把内容发送给服务器的)
1.5.1HTTP 协议-请求报文
HTTP 协议:规定了浏览器发送及服务器返回内容的格式
请求报文:浏览器按照 HTTP 协议要求的格式,发送给服务器的内容
1.5.2请求报文的格式
请求报文的组成部分有:
- 请求行:请求方法,URL,协议
- 请求头:以键值对的格式携带的附加信息,比如:Content-Type
- 空行:分隔请求头,空行之后的是发送给服务器的资源
- 请求体:发送的资源
请求报文的组成部分有:- 请求行:请求方法,URL,协议
- 请求头:以键值对的格式携带的附加信息,比如:Content-Type
- 空行:分隔请求头,空行之后的是发送给服务器的资源
- 请求体:发送的资源
1.5.3浏览器里查看报文的格式
开发者工具–>网络面板–>Fetch/XHR–>左边找到想看的某一次请求–>右边的详细面板–>标头headers看–>载荷Rayload看数据
1.5.4总结
- 浏览器发送给服务器的内容叫做请求报文
- 请求报文的组成:
- 通过 Chrome 的网络面板查看请求报文
1.5.5请求报文-错误排查
需求:通过请求报文排查错误原因,并修复
输入正确的用户名和密码无法登录
• 用户名:itheima007
• 密码:7654321
1.5.6HTTP 协议-响应报文(对应请求报文)
HTTP 协议:规定了浏览器发送及服务器返回内容的格式
响应报文:服务器按照 HTTP 协议要求的格式,返回给浏览器的内容
- 响应行(状态行):协议、HTTP 响应状态码、状态信息
- **响应头:**以键值对的格式携带的附加信息,比如:Content-Type
- 空行:分隔响应头,空行之后的是服务器返回的资源
- 响应体:返回的资源
1.5.7HTTP 响应状态码
HTTP 响应状态码:用来表明请求是否成功完成
比如:404(服务器找不到资源)
浏览器中查看
1.5.8总结
- 响应报文的组成:
响应行(状态行):协议、HTTP 响应状态码、状态信息
响应头:以键值对的格式携带的附加信息,比如 Content-Type
空行:分隔响应头,空行之后的是返回给浏览器的资源
响应体:返回的资源
2. HTTP 响应状态码用来表明请求是否成功完成
1.6接口文档
接口文档:描述接口的文章
**接口:**使用 AJAX 和服务器通讯时,使用的 URL,请求方法,以及参数
传送门:AJAX 阶段接口文档https://apifox.com/apidoc/shared-1b0dd84f-faa8-435d-b355-5a8a329e34a8
总结:
- 接口文档:
由后端提供的描述接口的文章
1.7案例 - 用户登录
- 点击登录时,判断用户名和密码长度
- 提交数据和服务器通信
- 提示信息
1.7.1案例-用户登录
<body>
<div class="container">
<h3>欢迎-登录</h3>
<!-- 登录结果-提示框 -->
<div class="alert alert-success" role="alert">
提示消息
</div>
<!-- 表单 -->
<div class="form_wrap">
<form>
<div class="mb-3">
<label for="username" class="form-label">账号名</label>
<input type="text" class="form-control username">
</div>
<div class="mb-3">
<label for="password" class="form-label">密码</label>
<input type="password" class="form-control password">
</div>
<button type="button" class="btn btn-primary btn-login"> 登 录 </button>
</form>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
// 需求1:点击登录时,用户名和密码长度判断,并提交数据和服务器通信
// 需求2: 封装提示框函数,重复调用,满足需求
//2.1获取提示框
const alert = document.querySelector('.alert')
//2.2封装函数:
function alertFun(msg,isSuccess){
//1.显示提示框
alert.classList.add('show')
//2.设置不同提示文字
alert.innerHTML = msg
//3.背景颜色
// if(isSuccess){
// //样式中已经默认添加成功 先删除再添加
// alert.classList.remove('alert-danger')
// alert.classList.add('alert-success')
// }else{
// alert.classList.remove('alert-success')
// alert.classList.add('alert-danger')
// }
//三元表达式 判断背景颜色用哪个
const bgcstyle = isSuccess ? 'alert-success':'alert-danger'
alert.classList.add(bgcstyle)
//问题:多次添加后会两个都添加导致类名冲突,解决,可以在提示框消失后移除类名
//4.过两秒后提示框消失
setTimeout(()=>{
alert.classList.remove('show')
alert.classList.remove(bgcstyle)
},2000)
}
//1.1登录绑定点击事件
document.querySelector('.btn-login').addEventListener('click',()=>{
//获取用户名和密码
const username = document.querySelector('.username').value
const password = document.querySelector('.password').value
//判断长度
if(username.length<8){
alertFun('用户名必须大于等于8位',false)
console.log('用户名必须大于等于8位');
//如果不符合,直接提前结束
return
}
if(password.length<6){
alertFun('密码必须大于等于6位',false)
console.log('密码必须大于等于6位');
//如果不符合,直接提前结束
return
}
//axios提交用户名和密码
axios({
url:'http://hmajax.itheima.net/api/login',
method:'post',
data:{
username,
password
}
}).then(result => {
alertFun(result.data.message,true)
console.log(result.data.message);
}).catch(error =>{ //返回错误信息
alertFun(error.response.data.message,false)
console.log(error.response.data.message);
})
})
</script>
</body>
1.8form-serialize 插件
第一个参数传:表单对象
//表单元素设置的name属性的值会作为serialize返回的对象的属性名
//建议name属性的值,最好和接口文档参数名一致
第二个参数传:配置对象一般只需{hash:true,empty:true}
//hash 设置获取数据结构 true:获取JS对象结构 false:查询字符串
//empty 设置是否获取空置
返回一个对象:
需要用哪个再取哪个对象的值
<body>
<form action="javascript:;" class="example-form">
<input type="text" name="uname">
<br>
<input type="text" name="pwd">
<br>
<input type="button" class="btn" value="提交">
</form>
<!--
目标:在点击提交时,使用form-serialize插件,快速收集表单元素值
-->
<!-- 引入插件文件 -->
<script src="./lib/form-serialize.js"></script>
<script>
document.querySelector('.btn').addEventListener('click', () => {
//2.使用serialize函数,快速收集表单数据
//第1个参数传:表单对象
//表单元素设置的name属性的值会作为serialize返回的对象的属性名
//建议name属性的值,最好和接口文档参数名一致
//第2个参数传:配置对象 ,一般只需{hash:true,empty:true}
//hash 设置获取数据结构 true:获取JS对象结构 false:查询字符串
//empty 设置是否获取空置
const form= document.querySelector('.example-form')
const data = serialize(form,{hash:true,empty:true})
console.log(data);//{uname: '11', pwd: '22'}
//上面的uname和pwd是根据表单元素的name属性值来获取的
})
</script>
</body>
1.8.1案例-用户登录
使用 form-serialize 插件,收集用户名和密码
<body>
<div class="container">
<h3>欢迎-登录</h3>
<!-- 登录结果-提示框 -->
<div class="alert alert-success" role="alert">
提示消息
</div>
<!-- 表单 -->
<div class="form_wrap">
<form class="login-form">
<div class="mb-3">
<label for="username" class="form-label">账号名</label>
<input type="text" class="form-control username" name="username">
</div>
<div class="mb-3">
<label for="password" class="form-label">密码</label>
<input type="password" class="form-control password" name="password">
</div>
<button type="button" class="btn btn-primary btn-login"> 登 录 </button>
</form>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<!-- 引入插件文件 -->
<script src="./lib/form-serialize.js"></script>
<script>
// 需求1:点击登录时,用户名和密码长度判断,并提交数据和服务器通信
// 需求2: 封装提示框函数,重复调用,满足需求
//2.1获取提示框
const alert = document.querySelector('.alert')
//2.2封装函数:
function alertFun(msg,isSuccess){
//1.显示提示框
alert.classList.add('show')
//2.设置不同提示文字
alert.innerHTML = msg
//3.背景颜色
// if(isSuccess){
// //样式中已经默认添加成功 先删除再添加
// alert.classList.remove('alert-danger')
// alert.classList.add('alert-success')
// }else{
// alert.classList.remove('alert-success')
// alert.classList.add('alert-danger')
// }
//三元表达式 判断背景颜色用哪个
const bgcstyle = isSuccess ? 'alert-success':'alert-danger'
alert.classList.add(bgcstyle)
//问题:多次添加后会两个都添加导致类名冲突,解决,可以在提示框消失后移除类名
//4.过两秒后提示框消失
setTimeout(()=>{
alert.classList.remove('show')
alert.classList.remove(bgcstyle)
},2000)
}
//1.1登录绑定点击事件
document.querySelector('.btn-login').addEventListener('click',()=>{
//获取用户名和密码
// const username = document.querySelector('.username').value
// const password = document.querySelector('.password').value
const form = document.querySelector('.login-form')
const data = serialize(form,{hash:true,empty:true})
console.log(data);//{username: '', password: ''}
//对象结构
const {username,password} = data
//判断长度
if(username.length<8){
alertFun('用户名必须大于等于8位',false)
console.log('用户名必须大于等于8位');
//如果不符合,直接提前结束
return
}
if(password.length<6){
alertFun('密码必须大于等于6位',false)
console.log('密码必须大于等于6位');
//如果不符合,直接提前结束
return
}
//axios提交用户名和密码
axios({
url:'http://hmajax.itheima.net/api/login',
method:'post',
data:{
username,
password
}
}).then(result => {
alertFun(result.data.message,true)
console.log(result.data.message);
}).catch(error =>{ //返回错误信息
alertFun(error.response.data.message,false)
console.log(error.response.data.message);
})
})
</script>
</body>
二、AJAX 综合案例
2.1案例 - 图书管理
2.1.1Bootstrap 弹框
2.1.1.1通过属性控制弹框的显示或隐藏
<body>
<!--
目标:使用Bootstrap弹框
1. 引入bootstrap.css 和 bootstrap.js
2. 准备弹框标签,确认结构
3. 通过自定义属性,控制弹框的显示和隐藏
-->
<!-- 3. 通过bootstrp自定义属性,控制弹框的显示和隐藏 -->
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target=".mybox">
显示弹框
</button>
<!-- 从bootstrp获取弹框标签
bootstrap的modal弹框:添加modal类名(默认隐藏)
-->
<div class="modal mybox" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<!-- 弹框头部 -->
<div class="modal-header">
<h5 class="modal-title">Modal title</h5>
<!-- 绑定关闭属性data-bs-dismiss="modal" -->
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<!-- 弹框中间 -->
<div class="modal-body">
<p>Modal body text goes here.</p>
</div>
<!-- 弹框底部 -->
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary">Save changes</button>
</div>
</div>
</div>
</div>
<!-- 引入bootstrap.js -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/js/bootstrap.min.js"></script>
</body>
2.1.1.2通过JS控制弹框的显示或隐藏
<body>
<!--
目标:使用JS控制弹框,显示和隐藏
1. 创建弹框对象
2. 调用弹框对象内置方法
.show() 显示
.hide() 隐藏
-->
<button type="button" class="btn btn-primary edit-btn">
编辑姓名
</button>
<div class="modal name-box" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">请输入姓名</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form action="">
<span>姓名:</span>
<input type="text" class="username">
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
<button type="button" class="btn btn-primary save-btn">保存</button>
</div>
</div>
</div>
</div>
<!-- 引入bootstrap.js -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/js/bootstrap.min.js"></script>
<script>
const modalDOM = document.querySelector('.name-box')
const modal = new bootstrap.Modal(modalDOM)
//编辑姓名->点击->赋予默认姓名->弹框显示
document.querySelector('.edit-btn').addEventListener('click',()=>{
document.querySelector('.username').value = '默认姓名'
//2.显示弹框
modal.show()
})
//保存->点击->获取姓名打印->弹框隐藏
document.querySelector('.save-btn').addEventListener('click',()=>{
const username= document.querySelector('.username').value
console.log(username);
//3.隐藏弹框
modal.hide()
})
</script>
</body>
2.1.2图书管理 - 渲染列表
2.1.3图书管理 - 新增图书
2.1.4图书管理 - 删除图书
2.1.4 图书管理 - 编辑图书
2.1.5核心步骤 - 渲染数据
2.1.6核心步骤 - 新增数据
2.1.7核心步骤 - 删除数据
2.1.8核心步骤 - 编辑数据
/**
* 目标1:渲染图书列表
* 1.1 获取数据
* 1.2 渲染数据
*/
//每一次操作都要渲染列表,所以把渲染列表封装成一个函数
const creator = '张三'
function getBookList(){
//1.获取数据
axios({
url:'http://hmajax.itheima.net/api/books',
params:{
creator
}
}).then(result => {
console.log(result.data.data); //返回一个数组
//渲染数据
const bookList = result.data.data
const htmlStr = bookList.map((item,index) => {
return `<tr>
<td>${index+1}</td>
<td>${item.bookname}</td>
<td>${item.author}</td>
<td>${item.publisher}</td>
<td data-id = ${item.id}>
<span class="del">删除</span>
<span class="edit">编辑</span>
</td>
</tr>`}).join('')
console.log(htmlStr);
document.querySelector('.list').innerHTML = htmlStr
})
}
//网页加载运行后,或许并渲染列表一次
getBookList()
/*
需求二:新增图书
2.1新增弹框->显示和隐藏
2.2收集表单数据,并提交到服务器保存
2.3刷新图书列表
*/
//点击关闭和添加都是直接显示或者隐藏,可以通过属性控制弹框
//但是点击保存还需要存数据到服务器,所以要通过JS控制隐藏
const addModalDom = document.querySelector('.add-modal') //不要写在点击监听里面
const addModal = new bootstrap.Modal(addModalDom)
//保存按钮
document.querySelector('.add-btn').addEventListener('click',()=>{
//获取表单值通过serialize插件
const addForm = document.querySelector('.add-form')
const data = serialize(addForm,{hash:true,empty:true})
console.log(data); //{bookname: '111', author: '222', publisher: '333'}
//对象解构
const { bookname,author,publisher } = data
axios({
url:'http://hmajax.itheima.net/api/books',
//提交用post
method:'post',
data:{
bookname,
author,
publisher,
creator
}
}).then(result => {
console.log(result.data.message); //添加图书成功
//对结果渲染
getBookList()
//保存成功后再次新增上一次的数据还在表单中,需要对表单进行重置
addForm.reset()
//隐藏
addModal.hide()
})
})
// const editModalDom = document.querySelector('.edit-modal') //不要写在点击监听里面
// const editModal = new bootstrap.Modal(editModalDom)
/*
需求三:删除
3.1绑定点击事件->获取图书id
3.2提交图书id->调用删除接口删除图书
3.3刷新图书列表
*/
//删除元素->绑定点击(注意:删除是动态的,不能直接绑定,用事件委托绑定上级)
document.querySelector('.list').addEventListener('click',e=>{
//获取触发事件的目标元素
//判断点击的是删除还是编辑,所以不能用 e.target.tagName = span
if(e.target.classList.contains('del')){
console.log('点击删除');
//怎么获取id?再添加列表的时候给他一个自定义属性 data-id = ${item.id}
//获取图书id(自定义属性id)
const theId = e.target.parentNode.dataset.id
console.log(theId);
//调用删除接口
axios({
//在路径上传参
url:`http://hmajax.itheima.net/api/books/${theId}`,
method:'DELETE'
}).then(result => {
//对结果重新渲染
getBookList()
})
}
/*
需求4:点击编辑,跳出弹框,点击修改,修改完成,隐藏弹框
*/
const editModalDom = document.querySelector('.edit-modal') //不要写在点击监听里面
const editModal = new bootstrap.Modal(editModalDom)
//判断点击的是否为编辑元素
if(e.target.classList.contains('edit')){
console.log('点击编辑');
//2.点击编辑,显示弹框
editModal.show()
//获取图书id(自定义属性id)
const theId = e.target.parentNode.dataset.id
//获取当前编辑图书数据->回显到编辑表单中
axios({
url:`http://hmajax.itheima.net/api/books/${theId}`
}).then(result => {
console.log(result);
const bookObj = result.data.data
//数据对象“属性”和标签“类名一致”
//遍历数组对象,使用属性去获取对应的标签,快速赋值
const keys = Object.keys(bookObj)
console.log(keys); // ['id', 'bookname', 'author', 'publisher']
keys.forEach(key => {
document.querySelector(`.edit-form .${key}`).value = bookObj[key]
})
})
document.querySelector('.edit-btn').addEventListener('click',()=>{
//获取表单值通过serialize插件
const editForm = document.querySelector('.edit-form')
const editdata = serialize(editForm,{hash:true,empty:true})
console.log(editdata.id);//{id: '', bookname: '', author: '', publisher: ''}
//对象解构
const { id,bookname,author,publisher} = editdata
axios({
url:`http://hmajax.itheima.net/api/books/${id}`,
method:'PUT',
data:{
author,
bookname,
publisher,
creator
}
}).then(result => {
console.log(result.data.message); //修改图书成功
//对结果重新渲染
getBookList()
})
//隐藏弹框
editModal.hide()
})
}
})
2.2图片上传
本地文件上传到服务器
从服务器获取url显示到页面
<body>
<!-- 文件选择元素 -->
<input type="file" class="upload">
<img class="my-img" src="" alt="">
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
/**
* 目标:图片上传,显示到网页上
* 1. 获取图片文件
* 2. 使用 FormData 携带图片文件
* 3. 提交到服务器,获取图片url网址使用
*/
//1.获取文件选择元素->绑定chang改变事件
document.querySelector('.upload').addEventListener('change',e => {
console.log(e.target); //<input type="file" class="upload">
console.log(e.target.files);
//拿到用户选择的图片文件
console.log(e.target.files[0]);
console.log(e.target.files[0].name);
//提交到服务器保存(使用FormData内置对象)携带图片文件
const fd = new FormData()
//img是接口文档规定的参数名
//append是FormData内置对象的内置属性
fd.append('img',e.target.files[0])
//3.提交到服务器
axios({
url:'http://hmajax.itheima.net/api/uploadimg',
method:'POST',
//传的数据是fd
data: fd
}).then(result => {
console.log(result);
//取出图片url网址,用img标签加载显示
const imgUrl = result.data.data.url
document.querySelector('.my-img').src = imgUrl
})
})
</script>
</body>
图片返回的url网址
img的src是图片返回的url网址
2.3案例 - 网站换肤
/**
* 目标:网站-更换背景
* 1. 选择图片上传,设置body背景
* 2. 上传成功时,"保存"图片url网址
* 3. 网页运行后,"获取"url网址使用
* */
//1. 选择图片上传,设置body背景
document.querySelector('.bg-ipt').addEventListener('change',e=>{
console.log(e.target.files[0]);
//要求传递表单数据类型
const fd = new FormData()
fd.append('img',e.target.files[0])
//提交到服务器
axios({
url:'http://hmajax.itheima.net/api/uploadimg',
method:'POST',
//传的数据是fd
data: fd
}).then(result => {
console.log(result);
//图片在服务器上的地址
const imgUrl = result.data.data.url
//获取body,然后更改背景
document.body.style.backgroundImage = `url(${imgUrl})`
//2. 上传成功时,"保存"图片url网址 localStorage本地存储,并且关闭不回丢失
localStorage.setItem('bgimg',imgUrl)
})
})
//3. 网页运行后,"获取"url网址使用
const bgUrl = localStorage.getItem('bgimg')
console.log(bgUrl);
//bug:第一次打开网页时没有上传图片所以会有找不到图片的错误
//解决:逻辑与 //当bgUrl为空即false时不执行后面的代码
//bgUrl && document.body.style.backgroundImage = `url(${bgUrl})`
//又因为逻辑运算符优先级高于等号赋值运算符
//解决:括号包起来
bgUrl && (document.body.style.backgroundImage = `url(${bgUrl})`)
2.4案例 - 个人信息设置
2.4.1个人信息设置
2.4.2个人信息设置 - 信息渲染
//刚打开就要渲染默认信息,所以先请求信息
axios({
url:'http://hmajax.itheima.net/api/settings',
method:'GET',
params:{
creator:'张三'
}
}).then(result => {
console.log(result);
})
2.4.3个人信息设置 - 头像修改
2.4.4个人信息设置 - 信息修改
2.4.5个人信息设置 - 提示框
/**
* 需求1:信息渲染
* 1.1 获取用户的数据
* 1.2 回显数据到标签上
* */
//刚打开就要渲染默认信息,所以先请求信息
axios({
url:'http://hmajax.itheima.net/api/settings',
method:'GET',
params:{
creator:'张三'
}
}).then(result => {
console.log(result);
console.log(result.data.data);//{avatar: 'http://hmajax.itheima.net/avatar/avatar1.png', nickname: 'itheima', email: 'itheima@itcast.cn', desc: '我是张三', gender: 0}
const userObj = result.data.data
//1.2 回显数据到标签上
//数据对象“属性”和标签“类名一致”
//遍历数组对象,使用属性去获取对应的标签,快速赋值
const keys = Object.keys(userObj)
console.log(keys); // ['avatar', 'nickname', 'email', 'desc', 'gender']
keys.forEach(key => {
//性别和头像要特殊处理
if(key === 'avatar'){
//赋予默认的头像
document.querySelector('.prew').src = userObj.avatar
}else if(key === 'gender'){
//赋予默认性别,
//获取性别单选框[男radio元素,女radio元素]伪数组
//需要改标签添加checked属性
const genderList = document.querySelectorAll('.gender')
//获取性别数字:0:男,1女
const gNum = userObj[key]
//console.log(gNum);
//通过性别的数字作为下标,找到对应性别单选框,设置选中状态
genderList[gNum].checked = true
}else{
document.querySelector(`.${key}`).value = userObj[key]
}
})
})
/**
* 需求2:修改头像
* 2.1 获取头像文件
* 2.2 提交服务器并更新头像
* */
//change事件
document.querySelector('.upload').addEventListener('change',e=>{
//2.1获取头像文件
console.log(e.target.files[0]);
//提交的是表单数据
const fd = new FormData() //表单数据对象
//img是接口文档规定的参数名
//append是FormData内置对象的内置属性
fd.append('avatar',e.target.files[0])
fd.append('creator','张三')
//提交到服务器
axios({
url:'http://hmajax.itheima.net/api/avatar',
method:'PUT',
//上传数据用data
data: fd
}).then(result => {
console.log(result);
//上传图片后还需要从服务器获取图片渲染
const imgUrl = result.data.data.avatar
//把新的头像回显到页面上
document.querySelector('.prew').src = imgUrl
})
})
/**
* 需求3:提交表单
* 3.1 收集表单信息
* 3.2 提交服务器保存
* */
document.querySelector('.submit').addEventListener('click',()=>{
//3.1 收集表单信息,准备数据
const userForm = document.querySelector('.user-form')
const userObj = serialize(userForm,{hash:true,empty:true})
console.log(userObj);
//3.2提交到服务器保存
//接口文档规定需要5个参数,userObj还差一个creator
userObj.creator = '张三'
//接口文档规定gender要传integer类型的数据。性别字数字符串转成
userObj.gender = + userObj.gender
//3.2 提交服务器保存
axios({
url:'http://hmajax.itheima.net/api/settings',
method:'PUT',
//内容格式: JSON
data:userObj
}).then(result => {
//创建对象,显示操作成功
const toastDom = document.querySelector('.toast')
//用的是bootstrap内置的Toast属性
const toast = new bootstrap.Toast(toastDom)
//显示弹框
toast.show()
})
})
三、AJAX 原理
3.1XMLHttpRequest
3.1.1AJAX原理 - XMLHttpRequest
定义:
**关系:**axios 内部采用 XMLHttpRequest 与服务器交互
**好处:**掌握使用 XHR 与服务器进行数据交互,了解 axios 内部原理
3.1.2使用 XMLHttpRequest
步骤:
1. 创建 XMLHttpRequest 对象(new)
2. 配置请求方法和请求 url 地址
3. 监听 loadend 事件,接收响应结果
4. 发起请求
3.1.3总结:
- AJAX 原理是什么?
➢ XMLHttpRequest 对象- 为什么学习 XHR?
➢ 有更多与服务器数据通信方式
➢ 了解 axios 内部原理- XHR 使用步骤?
➢ 创建 XHR 对象
➢ 调用 open 方法,设置 url 和请求方法
➢ 监听 loadend 事件,接收结果
➢ 调用 send 方法,发起请求
3.1.4XMLHttpRequest - 查询参数
定义:浏览器提供给服务器的额外信息,让服务器返回浏览器想要的数据
语法:http://xxxx.com/xxx/xxx?参数名1=值1&参数名2=值2
3.1.5案例 地区查询
需求:输入省份和城市名字,查询地区列表
请求地址:http://hmajax.itheima.net/api/area?参数名1=值1&参数名2=值2
//3.组织查询参数字符串(把参数转成字符串写入请求地址中)
const qObj = {
pname:provinceName,
cname:cityName
}
//4.查询参数对象->查询参数字符串
const paramsObj = new URLSearchParams(qObj)
const queryString = paramsObj.toString()
console.log(queryString); //pname=%E5%8C%97%E4%BA%AC&cname=%E5%8C%97%E4%BA%AC%E5%B8%82
<body>
<div class="container">
<form id="editForm" class="row">
<!-- 输入省份名字 -->
<div class="mb-3 col">
<label class="form-label">省份名字</label>
<input type="text" value="北京" name="province" class="form-control province" placeholder="请输入省份名称" />
</div>
<!-- 输入城市名字 -->
<div class="mb-3 col">
<label class="form-label">城市名字</label>
<input type="text" value="北京市" name="city" class="form-control city" placeholder="请输入城市名称" />
</div>
</form>
<button type="button" class="btn btn-primary sel-btn">查询</button>
<br><br>
<p>地区列表: </p>
<ul class="list-group">
<!-- 示例地区 -->
<li class="list-group-item">东城区</li>
</ul>
</div>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
/**
* 目标: 根据省份和城市名字, 查询对应的地区列表
*/
//需求:根据省份和城市的名字,查询地区列表
//1.查询按钮--绑定点击事件
document.querySelector('.sel-btn').addEventListener('click', () => {
console.log('点击');
//获取用户输入省份和城市名字
let provinceName = document.querySelector('.province').value
let cityName = document.querySelector('.city').value
console.log(provinceName, cityName);
//3.组织查询参数字符串
const qObj = {
pname:provinceName,
cname:cityName
}
//4.查询参数对象->查询参数字符串
const paramsObj = new URLSearchParams(qObj)
const queryString = paramsObj.toString()
console.log(queryString); //pname=%E5%8C%97%E4%BA%AC&cname=%E5%8C%97%E4%BA%AC%E5%B8%82
/**
* 目标:使用XMLHttpRequest对象与服务器通信
* 1. 创建 XMLHttpRequest 对象
* 2. 配置请求方法和请求 url 地址
* 3. 监听 loadend 事件,接收响应结果
* 4. 发起请求
*/
//1. 创建 XMLHttpRequest 对象
const xhr = new XMLHttpRequest()
xhr.open('GET', `http://hmajax.itheima.net/api/area?${queryString}`)
//3. 监听 loadend 事件,接收响应结果
xhr.addEventListener('loadend', () => {
console.log(xhr.response);
console.log(JSON.parse(xhr.response));
console.log(JSON.parse(xhr.response).list);//包含各地区的一个数组
//遍历数组,添加标签返回一个字符串
let cityLi = JSON.parse(xhr.response).list.map(areaName => `<li class="list-group-item">${areaName}</li>`)
console.log(cityLi);//map返回的是一个字符串
//列表的上一级添加内容
document.querySelector('.list-group').innerHTML = cityLi.join('')
})
//4. 发起请求
xhr.send()
})
</script>
</body>
3.1.6XMLHttpRequest - 数据提交
需求:通过 XHR 提交用户名和密码,完成注册功能
核心:
请求头设置 Content-Type:application/json
请求体携带 JSON 字符串
```javascript
<script>
/**
* 目标:使用xhr进行数据提交-完成注册功能
*/
document.querySelector('.reg-btn').addEventListener('click', () => {
const xhr = new XMLHttpRequest()
xhr.open('POST','http://hmajax.itheima.net/api/register')
xhr.addEventListener('loadend',()=>{
console.log(xhr.response);
})
//设置请求头-告诉服务器内容类型(JSON字符串)
xhr.setRequestHeader('Content-Type','application/json')
//准备提交数据
const userObj = {
username:'itheima00007',
password:'7654321'
}
//要求传的是JSON字符串,所以要把传的数据转成json字符串
const userStr = JSON.stringify(userObj)
//设置请求体,发起请求
xhr.send(userStr)
})
</script>
服务器返回的结果
3.2Promise(承诺)
定义:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise
Promise对象用于表示一个异步操作的最终完成(或失败)及其结果
总结:
- 什么是 Promise?
➢ 表示(管理)一个异步操作最终状态和结果值的对象- 为什么学习 Promise?
➢ 成功和失败状态,可以关联对应处理程序
➢ 了解 axios 内部原理- Promise 使用步骤?
Promise - 三种状态:
作用:了解Promise对象如何关联的处理函数,以及代码执行顺序
**概念:**一个Promise对象,必然处于以下几种状态之一
✓ 待定(pending) :初始状态,既没有被兑现,也没有被拒绝
✓ 已兑现(fulfilled) :意味着,操作成功完成
✓ 已拒绝(rejected) :意味着,操作失败
**注意:**Promise对象一旦被兑现/拒绝就是已敲定了,状态无法再被改变
<body>
<script>
/**
* 目标:使用Promise管理异步任务
*/
//1.创建promise对象 (处于pending-待定状态)
const p = new Promise((resolve, reject) => {
//Promise对象创建时,这里的代码都会执行了
//2.执行异步代码
setTimeout(()=>{
//(resolve()=> fulfilled状态-已兑现 => then() )
//resolve('模拟AJAX请求-成功结果') //成功
//(reject()=> rejected状态-已拒绝 => catch() )
reject(new Error('模拟AJAX请求-失败结果')) //失败
},2000)
})
//3.获取结果
p.then(result => {
console.log(result); //模拟AJAX请求-成功结果
}).catch(error => {
console.log(error); //Error: 模拟AJAX请求-失败结果
})
console.log(p);
</script>
</body>
总结:
- Promise 对象有哪 3 种状态?
➢ 待定 pending
➢ 已兑现 fulfilled
➢ 已拒绝 rejected- Promise 状态有什么用?
➢ 状态改变后,调用关联的处理函数
3.2.1案例: 使用Promise + XHR 获取省份列表
**需求:**使用 Promise 管理 XHR 获取省份列表,并展示到页面上
步骤:
- 创建 Promise 对象
- 执行 XHR 异步代码,获取省份列表
- 关联成功或失败函数,做后续处理
<body>
<p class="my-p"></p>
<script>
/**
* 目标:使用Promise管理XHR请求省份列表
* 1. 创建Promise对象
* 2. 执行XHR异步代码,获取省份列表
* 3. 关联成功或失败函数,做后续处理
*/
//1. 创建Promise对象
const promise = new Promise((resolve,reject) => {
//2. 执行XHR异步代码,获取省份列表
const xhr = new XMLHttpRequest()
xhr.open('GET','http://hmajax.itheima.net/api/province')
//监听
xhr.addEventListener('loadend',()=>{
//xhr如何判断响应成功还是失败的?
//2xx开头的都是成功响应状态码
console.log(xhr); //xhr有个属性是:status:200
if(xhr.status >=200 && xhr.status <300){
//成功获取的数据
resolve(JSON.parse(xhr.response))
}else{
//失败获取的数据
reject(new Error(xhr.response))
}
//console.log(xhr.response);
})
xhr.send()
})
//3. 关联成功或失败函数,做后续处理
promise.then(result => {
console.log(result);
//result.list包含数组省份
document.querySelector('.my-p').innerHTML = result.list.join('<br>')
}).catch(error => {
console.log(error);
//错误对象要用console.dir详细打印
console.dir(error);
document.querySelector('.my-p').innerHTML = error.message
})
</script>
</body>
3.3封装简易版 axios
3.3.1 封装_简易axios_获取省份列表
需求:基于 Promise + XHR 封装 myAxios 函数,获取省份列表展示
步骤:
- 定义 myAxios 函数,接收配置对象,返回 Promise 对象
- 发起 XHR 请求,默认请求方法为 GET
- 调用成功/失败的处理程序
- 使用 myAxios 函数,获取省份列表展示
<body>
<script>
/**
* 目标:封装_简易axios函数_获取省份列表
* 1. 定义myAxios函数,接收配置对象,返回Promise对象
* 2. 发起XHR请求,默认请求方法为GET
* 3. 调用成功/失败的处理程序
* 4. 使用myAxios函数,获取省份列表展示
*/
//1. 定义myAxios函数,接收配置对象,返回Promise对象
function myAxios(config){
return new Promise((resolve,reject) => {
//2. 发起XHR请求,默认请求方法为GET
const xhr = new XMLHttpRequest()
xhr.open(config.method || 'GET',config.url)
xhr.addEventListener('loadend',()=>{
//3. 调用成功/失败的处理程序
if(xhr.status >=200 && xhr.status < 300){
resolve(JSON.parse(xhr.response))
}else{
reject(new Error(xhr.response))
}
})
//发起请求
xhr.send()
})
}
//4. 使用myAxios函数,获取省份列表展示
myAxios({
url:'http://hmajax.itheima.net/api/province'
}).then(result => {
console.log(result);
}).catch(error => {
console.log(error);
})
</script>
</body>
3.3.1 封装_简易axios_获取省份列表
需求:修改 myAxios 函数支持传递查询参数,获取"辽宁省","大连市"对应地区列表展示
步骤:
- myAxios 函数调用后,判断 params 选项
- 基于 URLSearchParams 转换查询参数字符串
- 使用自己封装的 myAxios 函数展示地区列表
<body>
<p class="my-p"></p>
<script>
/**
* 目标:封装_简易axios函数_获取地区列表
* 1. 判断有params选项,携带查询参数
* 2. 使用URLSearchParams转换,并携带到url上
* 3. 使用myAxios函数,获取地区列表
*/
function myAxios(config) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
//下面需要用到url,所以在这之前要判断是否又pramse属性,并对config.params处理拼接到url后面再传入
if(config.params){
//2. 使用URLSearchParams转换,并携带到url上
const paramsObj = new URLSearchParams(config.params)
const querySring = paramsObj.toString()
//把查询参数字符串拼接在url?后面
config.url += `?${querySring}`
}
xhr.open(config.method || 'GET', config.url)
xhr.addEventListener('loadend', () => {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(JSON.parse(xhr.response))
} else {
reject(new Error(xhr.response))
}
})
xhr.send()
})
}
//查询地区列表
myAxios({
url:'http://hmajax.itheima.net/api/area',
params:{
pname:'广东省',
cname:'茂名市'
}
}).then(result => {
console.log(result);
})
</script>
</body>
3.3.1 封装_简易axios_注册用户
需求:修改 myAxios 函数支持传递请求体数据(data),完成注册用户功能
步骤:
- myAxios 函数调用后,判断 data 选项
- 转换数据类型,在 send 方法中发送
- 使用自己封装的 myAxios 函数完成注册用户功能
<body>
<p class="my-p"></p>
<button class="btn">注册用户</button>
<script>
/**
* 目标:封装_简易axios函数_注册用户
* 1. 判断有data选项,携带请求体
* 2. 转换数据类型,在send中发送
* 3. 使用myAxios函数,完成注册用户
*/
function myAxios(config) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
//下面需要用到url,所以在这之前要判断是否又pramse属性,并对config.params处理拼接到url后面再传入
if(config.params){
//2. 使用URLSearchParams转换,并携带到url上
const paramsObj = new URLSearchParams(config.params)
const querySring = paramsObj.toString()
//把查询参数字符串拼接在url?后面
config.url += `?${querySring}`
}
xhr.open(config.method || 'GET', config.url)
xhr.addEventListener('loadend', () => {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(JSON.parse(xhr.response))
} else {
reject(new Error(xhr.response))
}
})
//1. 判断有data选项,携带请求体
//如果有请求体数据就传
if(config.data){
//2. 转换数据类型,在send中发送,转成json字符串 Body 参数 (application/json)
const jsonStr = JSON.stringify(config.data)
xhr.setRequestHeader('Content-Type','application/json')
xhr.send(jsonStr)
}else{ //没有请求体数据的话就传空
xhr.send()
}
})
}
//查询地区列表
document.querySelector('.btn').addEventListener('click',()=>{
myAxios({
url:'http://hmajax.itheima.net/api/register',
method:'POST',
data:{
username:'12345678910111',
password:'654321'
}
}).then(result => {
console.log(result);
}).catch(error =>{
//一般使用dir来打印错误
console.dir(error)
})
})
</script>
</body>
3.4案例 - 天气预报
步骤:
- 获取北京市天气数据,展示
- 搜索城市列表,展示
- 点击城市,显示对应天气数据
天气预报 - 搜索城市列表
需求:根据关键字,展示匹配城市列表
步骤:
- 绑定 input 事件,获取关键字
- 获取展示城市列表数据
天气预报 - 展示城市天气
需求:展示用户搜索查看的城市天气
步骤:
- 检测搜索列表点击事件,获取城市 code 值
- 复用获取展示城市天气函数
/**
* 目标1:默认显示-北京市天气
* 1.1 获取北京市天气数据
* 1.2 数据展示到页面
*/
//获取并渲染城市 天气函数
function getWeather(cityCode){
//1.1 获取北京市天气数据
myAxios({
url:'http://hmajax.itheima.net/api/weather',
params:{
city:cityCode
}
}).then(result => {
console.log(result);
const weatherObj = result.data
//1.2 数据展示到页面
//阳历和农历日期,都在一个div里面,所以用模板字符串不用频繁获取
const dataStr = `<span class="dateShort">${weatherObj.date}</span>
<span class="calendar">农历
<span class="dateLunar">${weatherObj.dateLunar}</span>
</span>`
document.querySelector('.title').innerHTML = dataStr
document.querySelector('.area').innerHTML = weatherObj.area
const weatherBox = `<div class="tem-box">
<span class="temp">
<span class="temperature">${weatherObj.temperature}</span>
<span>°</span>
</span>
</div>
<div class="climate-box">
<div class="air">
<span class="psPm25">${weatherObj.psPm25}</span>
<span class="psPm25Level">${weatherObj.psPm25Level}</span>
</div>
<ul class="weather-list">
<li>
<img src=${weatherObj.weatherImg} class="weatherImg" alt="">
<span class="weather">${weatherObj.weather}</span>
</li>
<li class="windDirection">${weatherObj.windDirection}</li>
<li class="windPower">${weatherObj.windPower}</li>
</ul>
</div>
`
document.querySelector('.weather-box').innerHTML = weatherBox
//当日天气
const todayWStr = `
<div class="range-box">
<span>今天:</span>
<span class="range">
<span class="weather">${weatherObj.todayWeather.weather}</span>
<span class="temNight">${weatherObj.todayWeather.temNight}</span>
<span>-</span>
<span class="temDay">${weatherObj.todayWeather.temDay}</span>
<span>℃</span>
</span>
</div>
<ul class="sun-list">
<li>
<span>紫外线</span>
<span class="ultraviolet">${weatherObj.todayWeather.ultraviolet}</span>
</li>
<li>
<span>湿度</span>
<span class="humidity">${weatherObj.todayWeather.humidity}</span>%
</li>
<li>
<span>日出</span>
<span class="sunriseTime">${weatherObj.todayWeather.sunriseTime}</span>
</li>
<li>
<span>日落</span>
<span class="sunsetTime">${weatherObj.todayWeather.sunsetTime}</span>
</li>
</ul>
`
document.querySelector('.today-weather').innerHTML = todayWStr
//7天天气预报
const dayForecast = weatherObj.dayForecast
const sevenWStr = dayForecast.map(item => {
return ` <li class="item">
<div class="date-box">
<span class="dateFormat">${item.dateFormat}</span>
<span class="date">${item.date}</span>
</div>
<img src=${item.weatherImg} alt="" class="weatherImg">
<span class="weather">${item.weather}</span>
<div class="temp">
<span class="temNight">${item.temNight}</span>-
<span class="temDay">${item.temDay}</span>
<span>℃</span>
</div>
<div class="wind">
<span class="windDirection">${item.windDirection}</span>
<span class="windPower">${item.windPower}</span>
</div>
</li>`
}).join('<br>')
console.log(sevenWStr);
document.querySelector('.week-wrap').innerHTML = sevenWStr
})
}
//默认进入网页-就要获取天气数据,接口文档规定传的是城市编码(北京市城市编码:'110100')
getWeather('110100')
/*
需求二:搜索城市列表
2.1绑定input事件,获取关键字
2.2获取展示城市列表数据
*/
//2.1绑定input事件(监听输入),获取关键字
document.querySelector('.search-city').addEventListener('input',(e)=>{
console.log(e.target.value);
//2.2获取展示城市列表数据
myAxios({
url:'http://hmajax.itheima.net/api/weather/city',
params:{
city:e.target.value
}
}).then(result => {
console.log(result);
console.log(result.data);//包含所有含有"北"字地区的数组
const cityItem = result.data.map(item => {
return `<li class="city-item" data-code='${item.code}'>${item.name}</li>`
}).join('')
document.querySelector('.search-list').innerHTML = cityItem
})
})
/*
需求三:切换城市天气
3.1给li绑定城市点击事件,获取城市code值
li是动态的,想给li绑定事件需要用到事件委托
3.2获取获取并展示天气的函数
*/
//3.1给li绑定城市点击事件,获取城市code值(li是动态的,想给li绑定事件需要用到事件委托)
document.querySelector('.search-list').addEventListener('click',e => {
if(e.target.classList.contains('city-item')){
//在绑定input事件时给li添加data-code自定义属性,这一步可以获取自定义 属性的code的值
//然后得到是哪个城市,再调用渲染函数传递值
const cityCode = e.target.dataset.code
//3.2获取获取并展示天气的函数
getWeather(cityCode)
}
})
四、AJAX进阶
4.1同步代码和异步代码
4.1.1同步代码和异步代码
同步代码:https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/Asynchronous/Introducing#%E5%90%8C%E6%AD%A5%E7%BC%96%E7%A8%8B
异步代码:https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/Asynchronous/Introducing
同步代码:逐行执行,需原地等待结果后,才继续向下执行
异步代码:调用后耗时,不阻塞代码继续执行(不必原地等待),在将来完成后触发一个回调函数
4.1.2同步和异步
4.1.3总结
- 什么是同步代码?
➢ 逐行执行,原地等待结果后,才继续向下执行- 什么是异步代码?
➢ 调用后耗时,不阻塞代码执行,将来完成后触发回调函数- JS 中有哪些异步代码?
➢ setTimeout / setInterval
➢ 事件
➢ AJAX- 异步代码如何接收结果?
➢ 依靠回调函数来接收
4.2回调函数地狱和 Promise 链式调用
4.2.1回调函数地狱
需求:展示默认第一个省,第一个城市,第一个地区在下拉菜单中
概念:在回调函数中嵌套回调函数,一直嵌套下去就形成了回调函数地狱
缺点:可读性差,异常无法捕获,耦合性严重,牵一发动全身
<body>
<form>
<span>省份:</span>
<select>
<option class="province"></option>
</select>
<span>城市:</span>
<select>
<option class="city"></option>
</select>
<span>地区:</span>
<select>
<option class="area"></option>
</select>
</form>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
/**
* 目标:演示回调函数地狱
* 需求:获取默认第一个省,第一个市,第一个地区并展示在下拉菜单中
* 概念:在回调函数中嵌套回调函数,一直嵌套下去就形成了回调函数地狱
* 缺点:可读性差,异常无法获取,耦合性严重,牵一发动全身
*/
//1.获取默认第一个省份的名字
axios({url:'http://hmajax.itheima.net/api/province'}).then(result => {
const pname = result.data.list[0]
document.querySelector('.province').innerHTML = pname
//2.获取默认第一个城市的名字
axios({url:'http://hmajax.itheima.net/api/city',params:{pname:pname}}).then(result =>{
console.log(result);
const cname = result.data.list[0]
document.querySelector('.city').innerHTML = cname
//3.获取默认第一个地区的名字
axios({url:'http://hmajax.itheima.net/api/area1',params:{pname:pname,cname:cname}}).then(result => {
console.log(result);
document.querySelector('.area').innerHTML = result.data.list[0]
})
})
}).catch(error => {
console.log(error);
})
</script>
</body>
可以看到获取地区url错误在外层的catch无法获取,并且代码可读性也很差,稍微不留神就会弄错
4.2.2总结
- 什么是回调函数地狱?
➢ 在回调函数一直向下嵌套回调函数,形成回调函数地狱- 回调函数地狱问题?
➢ 可读性差
➢ 异常捕获困难
➢ 耦合性严重
4.2.3Promise - 链式调用
概念:依靠 then() 方法会返回一个新生成的 Promise 对象特性,继续串联下一环任务,直到结束
细节:then() 回调函数中的返回值,会影响新生成的 Promise 对象最终状态和结果
好处:通过链式调用,解决回调函数嵌套问题
<body>
<script>
/**
* 目标:掌握Promise的链式调用
* 需求:把省市的嵌套结构,改成链式调用的线性结构
*/
//1.创建Promise对象-模拟请求省份名字
const p= new Promise((resolve,reject) => {
setTimeout(() => {
resolve('北京')
},2000)
})
//2.获取省份名字
const p2 = p.then(result => {
console.log(result);
//3.创建Promise对象-模拟请求城市名字
//return 的Promise对象最终状态和结果会影响新的promise对象
return new Promise((resolve,reject) => {
setTimeout(() => {
resolve(result + '--北京市')
},2000)
})
})
//then()原地的结果是一个新的Promise对象
console.log(p2 === p); //false ,p2不等于p,说明他俩值虽然相同但是地址不同,p2是新new的一个对象
//4.获取里面的城市名字
const p3 = p2.then(result => {
console.log(result);
//5.创建Promise对象-模拟请求地区名字
return new Promise((resolve,reject) => {
setTimeout(()=>{
resolve(result + '--东城区')
})
})
})
//获取里面的地区名字
p3.then(result => {
console.log(result);
})
</script>
</body>
4.2.4总结
- 什么是 Promise 的链式调用?
➢ 使用 then 方法返回新 Promise 对象特性,一直串联下去- then 回调函数中,return 的值会传给哪里?
➢ 传给 then 方法生成的新 Promise 对象- Promise 链式调用有什么用?
➢ 解决回调函数嵌套问题
4.2.5Promise 链式应用
目标:使用 Promise 链式调用,解决回调函数地狱问题
做法:每个 Promise 对象中管理一个异步任务,用 then 返回 Promise 对象,串联起来
<body>
<form>
<span>省份:</span>
<select>
<option class="province"></option>
</select>
<span>城市:</span>
<select>
<option class="city"></option>
</select>
<span>地区:</span>
<select>
<option class="area"></option>
</select>
</form>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
/**
* 目标:把回调函数嵌套代码,改成Promise链式调用结构
* 需求:获取默认第一个省,第一个市,第一个地区并展示在下拉菜单中
*/
//1.得到-获取省份Promise对象
let pname = ''
axios({url:'http://hmajax.itheima.net/api/province'}).then(result => {
//console.log(result);
pname = result.data.list[0]
document.querySelector('.province').innerHTML = pname
//2.得到-获取城市的promise对象
return axios({url:'http://hmajax.itheima.net/api/city',params:{ pname }})
}).then(result => {
//获取城市列表
console.log(result);
const cname = result.data.list[0]
document.querySelector('.city').innerHTML = cname
//2.得到-获取地区的promise对象
//注意:这里的pname跟上面的panme是兄弟,不能直接访问,可以给panme一个全局变量
return axios({url:'http://hmajax.itheima.net/api/area',params:{ pname , cname}})
}).then(result => {
console.log(result);
document.querySelector('.area').innerHTML = result.data.list[0]
})
</script>
</body>
4.3async 和 await 使用(异步编程的终极解决方案)
4.3.1async函数和await
定义:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/async_function
概念: 在 async 函数内,使用 await 关键字取代 then 函数,等待获取 Promise 对象成功状态的结果值
<body>
<form>
<span>省份:</span>
<select>
<option class="province"></option>
</select>
<span>城市:</span>
<select>
<option class="city"></option>
</select>
<span>地区:</span>
<select>
<option class="area"></option>
</select>
</form>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
/**
* 目标:掌握async和await语法,解决回调函数地狱
* 概念:在async函数内,使用await关键字,获取Promise对象"成功状态"结果值
* 注意:await必须用在async修饰的函数内(await会阻止"异步函数内"代码继续执行,原地等待结果)
*/
//定义async修饰函数
async function getData(){
const pObj = await axios({url:'http://hmajax.itheima.net/api/province'})
const pname = pObj.data.list[0]
console.log(pname);
const cObj = await axios({url:'http://hmajax.itheima.net/api/city',params:{pname}})
const cname = cObj.data.list[0]
console.log(cname);
const areaObj = await axios({url:'http://hmajax.itheima.net/api/area',params:{pname,cname}})
const areaname = areaObj.data.list[0]
console.log(areaname);
document.querySelector('.province').innerHTML = pname
document.querySelector('.city').innerHTML = cname
document.querySelector('.area').innerHTML = areaname
}
getData()
</script>
</body>
4.3.2async函数和await_捕获错误
如果try里的某行代码报错后,try中剩余的代码就不会执行了
<body>
<form>
<span>省份:</span>
<select>
<option class="province"></option>
</select>
<span>城市:</span>
<select>
<option class="city"></option>
</select>
<span>地区:</span>
<select>
<option class="area"></option>
</select>
</form>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
/**
* 目标:掌握async和await语法,解决回调函数地狱
* 概念:在async函数内,使用await关键字,获取Promise对象"成功状态"结果值
* 注意:await必须用在async修饰的函数内(await会阻止"异步函数内"代码继续执行,原地等待结果)
*/
//定义async修饰函数
async function getData(){
try{
const pObj = await axios({url:'http://hmajax.itheima.net/api/province11'})
const pname = pObj.data.list[0]
console.log(pname);
const cObj = await axios({url:'http://hmajax.itheima.net/api/city',params:{pname}})
const cname = cObj.data.list[0]
console.log(cname);
const areaObj = await axios({url:'http://hmajax.itheima.net/api/area',params:{pname,cname}})
const areaname = areaObj.data.list[0]
console.log(areaname);
document.querySelector('.province').innerHTML = pname
document.querySelector('.city').innerHTML = cname
document.querySelector('.area').innerHTML = areaname
}catch(error){
//({url:'http://hmajax.itheima.net/api/province11'})
//如果try里的某行代码报错后,try中剩余的代码就不会执行了
console.dir(error); //M
}
}
getData()
</script>
</body>
4.4事件循环-EventLoop
4.4.1认识 - 事件循环(EventLoop)
好处:掌握 JavaScript 是如何安排和运行代码的
运行结果都是1 3 2
4.4.2事件循环(EventLoop)
原因:JavaScript 单线程(某一刻只能执行一行代码),为了让耗时代码不阻塞其他代码运行,设计了事件循环模型
4.4.3事件循环 - 执行过程
定义:执行代码和收集异步任务的模型,在调用栈空闲,反复调用任务队列里回调函数的执行机制,就叫事件循环
4.4.4总结
- 什么是事件循环?
➢ 执行代码和收集异步任务,在调用栈空闲时,反复调用任务队列里
回调函数执行机制- 为什么有事件循环?
➢ JavaScript 是单线程的,为了不阻塞 JS 引擎,设计执行代码的模型- JavaScript 内代码如何执行?
➢ 执行同步代码,遇到异步代码交给宿主浏览器环境执行
➢ 异步有了结果后,把回调函数放入任务队列排队
➢ 当调用栈空闲后,反复调用任务队列里的回调函
4.4.5事件循环 - 练习
使用模型,分析代码执行过程
1,5,3,2,4
xhr.addEventListener('loadend', () => {
//注意:绑定监听是异步
console.log(4)
})
4.4.6宏任务与微任务
宏任务与微任务
ES6 之后引入了 Promise 对象, 让 JS 引擎也可以发起异步任务
异步任务分为:
✓ 宏任务:由浏览器环境执行的异步代码
✓ 微任务:由 JS 引擎环境执行的异步代码
Promise本身是同步的,而then和catch回调函数是异步的
先微任务再宏任务
4.4.6宏任务与微任务–执行顺序
注意:1.Promise本身是同步的,而then和catch回调函数是异步的
2.先微任务再宏任务
4.4.7总结
- 什么是宏任务?
➢ 浏览器执行的异步代码
➢ 例如:JS 执行脚本事件,setTimeout/setInterval,AJAX请求完成
事件,用户交互事件等- 什么是微任务?
➢ JS 引擎执行的异步代码
➢ 例如:Promise对象.then()的回调- JavaScript 内代码如何执行?
➢ 执行第一个 script 脚本事件宏任务,里面同步代码
➢ 遇到 宏任务/微任务 交给宿主环境,有结果回调函数进入对应队列
➢ 当执行栈空闲时,清空微任务队列,再执行下一个宏任务,从1再来
4.4.8事件循环 - 经典面试题
4.5Promise.all 静态方法
4.5.1Promise.all 静态方法
概念:合并多个 Promise 对象,等待所有同时成功完成(或某一个失败),做后续逻辑
语法:
**案例需求:**同时请求“北京”,“上海”,“广州”,“深圳”的天气并在网页尽可能同时显示
<body>
<ul class="my-ul"></ul>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
/**
* 目标:掌握Promise的all方法作用,和使用场景
* 业务:当我需要同一时间显示多个请求的结果时,就要把多请求合并
* 例如:默认显示"北京", "上海", "广州", "深圳"的天气在首页查看
* code:
* 北京-110100
* 上海-310100
* 广州-440100
* 深圳-440300
*/
//1.请求城市的天气,得到Promise对象
//axios获取的是一个Promise对象
const bjPromise = axios({url:'http://hmajax.itheima.net/api/weather',params:{city:'110100'}})
const shPromise = axios({url:'http://hmajax.itheima.net/api/weather',params:{city:'310100'}})
const gzPromise = axios({url:'http://hmajax.itheima.net/api/weather',params:{city:'440100'}})
const szPromise = axios({url:'http://hmajax.itheima.net/api/weather',params:{city:'440300'}})
//2.使用promise.all合并多个promise对象
const p = Promise.all([bjPromise,shPromise,gzPromise,szPromise])
p.then(result => {
//结果数组的顺序跟合并时顺序是一致的
console.log(result); //返回一个数组
const re = result.map(item => {
return `<li>${item.data.data.area}---${item.data.data.weather}</li>`
})
document.querySelector('.my-ul').innerHTML = re
}).catch(error => {
console.log(error);
})
</script>
</body>
4.6案例 - 商品分类
<body>
<!-- 大容器 -->
<div class="container">
<div class="sub-list">
<div class="item">
<h3>分类名字</h3>
<ul>
<li>
<a href="javascript:;">
<img src="http://zhoushugang.gitee.io/erabbit-client-pc-static/uploads/img/category%20(9).png" />
<p>巧克力</p>
</a>
</li>
<li>
<a href="javascript:;">
<img src="http://zhoushugang.gitee.io/erabbit-client-pc-static/uploads/img/category%20(9).png" />
<p>巧克力</p>
</a>
</li>
<li>
<a href="javascript:;">
<img src="http://zhoushugang.gitee.io/erabbit-client-pc-static/uploads/img/category%20(9).png" />
<p>巧克力</p>
</a>
</li>
</ul>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
/**
* 目标:把所有商品分类“同时”渲染到页面上
* 1. 获取所有一级分类数据
* 2. 遍历id,创建获取二级分类请求
* 3. 合并所有二级分类Promise对象
* 4. 等待同时成功后,渲染页面
*/
//1.获取所有一级分类数据
axios({
url:'http://hmajax.itheima.net/api/category/top',
}).then(result => {
console.log(result);
//2. 遍历id,创建获取二级分类请求
const secPromiseList = result.data.data.map(item => {
return axios({
url:'http://hmajax.itheima.net/api/category/sub',
params:{
id:item.id //传递一级分类的id
}
})
})
console.log(secPromiseList); //[二级分类请求的Promise对象,9个]
//3. 合并所有二级分类Promise对象
const p = Promise.all(secPromiseList)
p.then(result => {
console.log(result);
//4. 等待同时成功后,渲染页面
const htmlStr = result.map(item => {
const dataObj = item.data.data
return `<div class="item">
<h3>${dataObj.name}</h3>
<ul>
${dataObj.children.map(item => {
return `<li>
<a href="javascript:;">
<img src=${item.picture}>
<p>${item.name}</p>
</a>
</li>`
})}
</ul>
</div>`
}).join('')
document.querySelector('.sub-list').innerHTML = htmlStr
})
})
</script>
</body>
4.7案例 - 学习反馈
/**
* 目标1:完成省市区下拉列表切换
* 1.1 设置省份下拉菜单数据
* 1.2 切换省份,设置城市下拉菜单数据,清空地区下拉菜单
* 1.3 切换城市,设置地区下拉菜单数据
*/
//1.1 设置省份下拉菜单数据
axios({
url:'http://hmajax.itheima.net/api/province'
}).then(result => {
console.log(result);
const pStr = result.data.list.map(pname => {
return `<option value="${pname}">${pname}</option>`
}).join('')
document.querySelector('.province').innerHTML = `<option value="">省份</option>` + pStr
})
//1.2 切换省份,设置城市下拉菜单数据,清空地区下拉菜单
document.querySelector('.province').addEventListener('change',async e => {
//获取用户选择的省份名字
console.log(e.target.value);
//在 async 函数内,使用 await 关键字取代 then 函数,**等待**获取 Promise 对象**成功状态的结果值
const result = await axios({
url:'http://hmajax.itheima.net/api/city',
params:{
pname:e.target.value
}
})
console.log(result);
const cStr = result.data.list.map(cname => {
return `<option value=${cname}>${cname}</option>`
}).join('')
document.querySelector('.city').innerHTML = `<option value="">城市</option>` + cStr
//清空地区数据
document.querySelector('.area').innerHTML = `<option value="">地区</option>`
})
//1.3 切换城市,设置地区下拉菜单数据
//检测城市改变
document.querySelector('.city').addEventListener('change',async e => {
//获取用户选择的城市名字
console.log(e.target.value);
//在 async 函数内,使用 await 关键字取代 then 函数,**等待**获取 Promise 对象**成功状态的结果值
const result = await axios({
url:'http://hmajax.itheima.net/api/area',
params:{
pname:document.querySelector('.province').value,
cname:e.target.value
}
})
console.log(result);
const areaStr = result.data.list.map(aname => {
return `<option value=${aname}>${aname}</option>`
}).join('')
document.querySelector('.area').innerHTML = `<option value="">地区</option>` + areaStr
//清空地区数据
//document.querySelector('.area').innerHTML = `<option value="">地区</option>`
})
/*需求二:收集数据提交保存
2.1监听点击事件
2.2依靠 插件收集表单数据
3.2基于axios提交保存,显示结果
*/
//2.1监听点击事件
document.querySelector('.submit').addEventListener('click',async ()=>{
//2.2依靠 插件收集表单数据
const from = document.querySelector('.info-form')
const data = serialize(from,{ hash:true,empty:true})
console.log(data);
try{
//3.2基于axios提交保存,显示结果
const result = await axios({
url:'http://hmajax.itheima.net/api/feedback',
method:'POST',
data:data
})
console.log(result);
alert(result.data.message)
}catch(error) {
console.dir(error);
alert(error.response.data.message)
}
})