Axios原理
Axios原理
以下为学习过程中的极简提炼笔记,以供重温巩固学习
学习准备
准备工作
html+css+JavaScript 3剑客都懂一点,已看完前面两节,熟悉axios和bootstrap两个基于AJAX的插件
学习目的
了解AJAX原理,前面axios、bootstarp相当于插件
XMLHttpRequest
AJAX其实是指XMLHttpRequest这个对象(XHR),用于与服务器交互,XMLHttpRequest是Axios插件的原理
可以理解为,axios实际上是以XMLHttpRequest为核心的一个插件,实际上axios内核就是对XMLHttpRequest这个对象相关的代码进行功能封装库
(ajax并不等于axios,我们使用的axios的内部,实际上对XHR对象/原理 的封装)
学习XMLHttpRequest目的
学习XHR,目的为了:
在不需要引入库调用的交互场景(axios相对大)、功能单一场景,有更多与服务器数据通信的方式
静态网站项目中,与服务器交互少,为了缩小代码体积(与服务器交互只有一到两处,可以采用XHR对象,几行代码实现与服务器交互)
了解axios内部原理
优化代码,精简代码时需要(除去引入的库)
面试会问
使用 XMLHttpRequest
步骤:
创建 XMLHttpRequest 对象,借助浏览器提供的XMLHttpRequest()的构造函数,new新建xhr对象
调用xhr对象内置的open方法,来设置请求方法与请求url地址
给xhr对象,绑定loadend事件(意为加载完成),负责监测此次请求和响应完成的动作,完成后会触发回调函数
通过调用内置的方法send(),向服务器发起请求,服务器响应动作,会被loadend事件捕获,在回调函数中,通过访问XHR对象中固定的.response属性,拿到服务器返回的结果
XMLHttpRequest 使用步骤示例
<script>
// 步骤1,利用浏览器内置的XMLHttpRequest()函数,创建对象
const xhr = new XMLHttpRequest()
// 步骤2,配置请求方法和请求url地址
xhr.open('请求方法','请求url地址')
// 步骤3,监听loadend事件,接收响应结果
xhr.addEventListener('loadend',()=> {
// 响应结果(步骤4执行结果)
console.log(xhr.response)
})
// 步骤4,发起请求调用
xhr.send()
</script>
案例:获取并展示所有省份名字
<html>
<body>
<p class='my-p'></p>
<script>
/*
* 目标:使用XMLHttpRequest对象与服务器通信
* 1.创建XMLHttpRequest 对象
* 2.配置请求方法和请求url地址
* 3.监听loadend 事件,接收响应结果
* 4.发起请求
*/
// 1.创建XMLHttpRequest对象
const xhr = new XMLHttpRequest()
// 2.配置请求方法和请求url地址,根据接口文档写接口配置
xhr.open('GET','http://hmajax.itheima.net/api/province')
// 3.监听loadend事件,接收响应结果
xhr.addEventListener('loadend',()=>{
// 接收服务器返回的响应结果,打印出来是个对象结构的json字符串
console.log(xhr.response)
// 在axios中,封装了json字符串转对象的功能,原生XHR没有此功能,需要对响应结果做后续处理
// json字符串,通过JSON.parse方法,转对象,定义一个常量,并打印确认
const data = JSON.parse(xhr.response)
console.log(data)
// 拿到数组,转字符串
console.log(data.list.join('<br>'))
document.querySelector('.my-p').innerHTML = data.list.join('<br>')
})
// 4.发起请求
xhr.send()
</script>
</body>
</html>
备注:
以前axios请求的结果,里面有很多对key和value,包括data,去哪了?
axios内部把结果转化完以后,挂载到结果对象(有很多其他key和value)的data属性下
这里响应结果为简单对象的json字符串,还需要自己转化
转化后使用它(取出数据,拼接展示)
XMLHttpRequest 查询参数
定义:浏览器提供给服务器的额外信息,让服务器返回浏览器想要的数据
(额外的、接口特定的信息)语法:http://xxxx.com/xxx/xxx?参数名1=值1&参数名2=值2
即,当通过url调用某个数据接口时,需要将相应的数据接口查询参数,拼入url携带到服务器上
- 原生的XHR对象传递多个查询参数:url?参数名1=值1&参数名2=值2&参数名n=值n
</html>
<body>
<p class="city-p"></p>
<script>
/**
*目标:使用XHR携带查询参数,展示某个省下属的城市列表
*/
const xhr = new XMLHttpRequest()
xhr.open('GET','http://hmajax.itheima.net/api/city?pname=辽宁省')
// 绑定加载结束事件+事件处理函数
xhr.addEventListener('loadend',() => {
console.log(xhr.response)
const data = JSON.parse(xhr.response)
console.log(data)
document.querySelector('.city-p').innerHTML = data.list.join('<br>')
})
xhr.send()
</script>
</body>
<html>
地区查询案例
需求:输入省份和城市名字,查询地区列表
先通过接口文档确认的请求地址: http://hmajax.itheima.net/api/area?参数名1=值1&参数名2=值2
js实现思路:组织一个对象,遍历对象属性和值,转成字符串使用&链接
js内置的api:URLSearchParams对象
特点:
- 可将普通JS对象,转成查询参数字符串格式
- 在API内部,会将字符串,使用&符连接起来
使用:
- 借助浏览器内置的 URLSearchParams() 构造函数,来新建查询参数对象
- 往查询参数对象中传入普通对象,参数名就是接口文档/后端要求的名字,值就从页面上获取自用户输入
- 调用内置的toString()方法,实现转换
<body>
<script>
// 1. 创建URLSearchParams对象
const paramsObj = new URLSearchParams({
参数名1:值1,
参数名2:值2,
参数名n:值n,
})
// 2. 生成指定格式查询参数 字符串
const queryString = paramsObj.toString()
// 结果:参数名1=值1&参数名2=值2
</script>
</body>
<!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>案例_地区查询</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css">
<style>
:root {
font-size: 15px;
}
body {
padding-top: 15px;
}
</style>
</head>
<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', () => {
// 2. 收集省份和城市名字,通过调试,找到并通过类名,获取输入框数据
const pname = document.querySelector('.province').value
const cname = document.querySelector('.city').value
// 3. 组织查询参数字符串(通过接口文档,确定需要传递的参数,参数的位置query和格式类型string先确认)
const qObj = {
pname,
cname
}
// 查询参数字符串 <---转成---< 查询参数对象
const paramsObj = new URLSearchParams(qObj)
// 生成指定格式查询参数 字符串
const queryString = paramsObj.toString()
console.log(queryString)
// 4. 使用XHR对象,传递到服务器,查询地区列表
const xhr = new XMLHttpRequest()
xhr.open('GET', `http://hmajax.itheima.net/api/area?${queryString}`)
// 绑定加载完成的事件
xhr.addEventListener('loadend', () => {
console.log(xhr.response)
// json字符串,通过JSON.parse方法,转对象,定义一个常量,并打印确认
const data = JSON.parse(xhr.response)
console.log(data)
// 取出list数组的值,通过map映射,地区的名字=>映射成li
const htmlStr = data.list.map(areaName => {
return `<li class="list-group-item">${areaName}</li>`
}).join('')
console.log(htmlStr)
document.querySelector('.list-group').innerHTML = htmlStr
})
xhr.send()
})
</script>
</body>
</html>
打印查询参数对象转化得来的查询参数字符串,出现的%××%××%××不是乱码,它叫做url编码
为什么要进行url编码:
- 因为url网址有个规定,只能出现英文数字以及特殊符号
那为什么浏览器上边的搜索框/地址栏有中文:
- 因为浏览器会对其进行格式化,可显示中文
但是当在代码中,和服务器沟通时,传递的url网址需要在网络进行传输,就会进行一种编码(url编码)
可通过url解码网站解码,如https://tool.chinaz.com/tools/urlencode.aspx
XMLHttpRequest 数据提交
需求:通过XHR提交用户名和密码,完成注册功能
先看后端提供的接口文档,确认接口信息
- 方法:post
- url;http://hmajax.itheima.net/api/register
- 通过原生XHR对象的open方法携带
确认携带参数与业务类型
- 请求体参数
- 业务类型为application/json字符串
核心:
- 请求头设置Content-Type: application/json (相当于告诉后端传的类型)
- 请求体携带JSON字符串
核心语法代码:
<body>
<script>
const xhr = new XMLHttpRequest()
xhr.open('请求方法','请求url网址') //通过接口文档写入
xhr.addEventListener('loadend',() => {
console.1og(xhr.response)
})
//请求头信息,调用XHR对象内置的setRequestHeader()方法,2个参数:请求头属性名/业务类型+属性值字符串
//告诉服务器,我传递的内容类型,是JSON字符串
xhr.setRequestHeader ('Content-Type','application/json')
//准备数据并转成JSON字符串(对象结构的json字符串)
const user = { username: 'itheima007 ', password: '7654321' }
const userStr = JSON.stringify(user)
//发送请求体数据
xhr.send(userstr)
</script>
</body>
<!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>XMLHttpRequest_数据提交</title>
</head>
<body>
<button class="reg-btn">注册用户</button>
<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: 'itheima007',
password: '7654321'
}
// 对象结构的json字符串
const userStr = JSON.stringify(userObj)
// 设置请求体,发起请求
xhr.send(userStr)
})
</script>
</body>
</html>
备注:
- 可以在浏览器调试的 网络>Fetch/XHR>标头 看到我们的请求头设置
- 可以在 网络>载荷 看到我们的请求体携带的 JSON字符串 数据
- 重复提交会被服务器返回的信息提醒,“账号被占用”
认识 Promise
面试相对高频
定义:Promise对象用于表示一个异步操作的最终完成(或失败)及其结果值,可管理异步操作的代码
异步代码:耗时的,且不会阻止代码继续执行,包括setTimeout、setinterval,Ajax也是异步代码
使用Promise对象管理Ajax的好处:
- 逻辑更清晰
- 了解axios函数内部运作机制(axios中的then和catch)
- 能解决回调函数地狱问题(通过Promise的链式调用解决)
每一个Promise对象内,都内置了 resolve 和 reject 函数
Promise使用示例代码:
<body>
<script>
//1.利用浏览器内置的构造函数,创建Promise对象
const p = new Promise((resolve, reject)=>{
//2.执行异步任务-并传递结果
//成功调用:resolve(值)触发then()执行
//失败调用:reject(值)触发catch()执行
})
// 3.接收结果
p.then(result => {
//成功
})
.catch(error => {
//失败
})
</script>
</body>
<!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>认识Promise</title>
</head>
<body>
<script>
/**
* 目标:使用Promise管理异步任务
*/
// 1. 创建Promise对象,传入一个回调函数,作为管理的异步任务,形参是promise的两个函数
const p = new Promise((resolve, reject) => {
// 2. 执行异步代码/异步任务(这里使用setTimeout模拟ajax
setTimeout(() => {
// resolve('模拟AJAX请求-成功结果')
reject(new Error('模拟AJAX请求-失败结果'))
}, 2000)
})
// 3. 获取结果,通过回调函数接收结果
p.then(result => {
console.log(result)
}).catch(error => {
console.log(error)
})
</script>
</body>
</html>
总结:
- 什么是Promise?
- 表示(管理)一个异步操作最终状态和结果值的对象
- 为什么学习Promise?
- 成功和失败状态,可以关联对应处理程序
- 了解axios内部原理
认识 Promise 的三种状态
作用:了解Promise对象如何关联成功或失败的处理函数,以及代码执行顺序
概念:一个Promise对象,必然处于以下几种状态之一
Promise具有三种状态
待定(pending):初始状态,既没有被兑现,也没有被拒绝
已兑现(fulfilled):意味着,操作成功完成
已拒绝(rejected):意味着,操作失败
- 首先,在 new Promise() 的时候,会得到一个Promise对象
- 此时处于 pending待定 状态
- 状态的英文字符串是Promise对象内的一种管理标记
- 此时,Promise内管理的异步任务开始执行
- 在将来有结果以后,如是成功的,则调用 resolve() 函数
- 此时Promise对象的状态,将会被修改为 fulfilled已兑现 状态
- 同时发起对 .then() 中传入的回调函数
- 并将 resolve() 的结果,传入回调函数中执行回调
- 如Promise对象内的异步任务失败,则调用 reject() 函数
- 此时Promise对象的状态,将会被修改为 rejected()已拒绝 状态
- 同时发起对 .catch() 中传入的回调函数
注意:Promise对象 一旦被兑现/拒绝,就已经是一个敲定结果,状态无法再被改变
- Promise状态有什么用?
- 状态改变后,调用关联的处理函数
备注:
- 实际上,Promise的fulfilled状态,对标axios的.then(result);rejected状态,对标axios的.catch(error)
<!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>认识Promise</title>
</head>
<body>
<script>
/**
* 目标:基于上一节代码,打印Promise对象状态
*/
// 1. 创建Promise对象,传入一个回调函数,作为管理的异步任务,形参是promise的两个函数
// 对象创建后,状态就是处于pending待定状态,但时间比较短,调试中看到的时间2s
const p = new Promise((resolve, reject) => {
// promise对象创建时,会先执行异步任务(构造函数传入的回调函数里的代码会立刻执行,先于console.log(p))
// 2. 执行异步代码/异步任务(这里使用setTimeout模拟ajax
setTimeout(() => {
// 当 resolve()=> 被调用后,状态为 fulfilled已兑现 =>.then
// resolve('模拟AJAX请求-成功结果')
// 当 reject()=> 被调用后,状态为 rejected已拒绝 =>.catch
reject(new Error('模拟AJAX请求-失败结果'))
}, 2000)
})
// 打印Promise对象,在浏览器中看状态
console.log(p) // 浏览器打印结果展开(2秒内)为:prototype:promise;promiseState:pending;promiseResult:undefined
// 如果等到resolve结果打印出来再查看,状态变为fullfilled
// 3. 获取结果,通过回调函数接收结果
p.then(result => {
console.log(result)
})
.catch(error => {
console.log(error)
})
</script>
</body>
</html>
案例 使用Promise + XHR 获取省份列表
需求:使用 Promise 管理 XHR (AJAX)网络请求异步任务,获取省份列表,并展示到页面上
步骤:
- 创建 Promise 对象
- 执行 XHR 异步代码,获取省份列表
- 关联成功或失败函数,做后续处理
<!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>案例_使用Promise+XHR_获取省份列表</title>
</head>
<body>
<p class="my-p"></p>
<script>
/**
* 目标:使用Promise管理XHR请求省份列表
* 1. 创建Promise对象
* 2. 执行XHR异步代码,获取省份列表
* 3. 关联成功或失败函数,做后续处理
*/
// 1. 创建Promise对象
const p = new Promise((resolve, reject) => {
// 2. 执行XHR异步代码,获取省份列表
const xhr = new XMLHttpRequest()
xhr.open('GET', 'http://hmajax.itheima.net/api/province')
xhr.addEventListener('loadend', () => {
// 如果请求结果直接在promise构造函数的参数回调函数中打印
// console.log(xhr) 可见打印有 status状态码:200成功
// console.log(xhr.response) 请求结果打印
// xhr如何判断响应成功还是失败的?
// 2xx开头的都是成功响应状态码(大于200并且小于300)
if (xhr.status >= 200 && xhr.status < 300) {
resolve(JSON.parse(xhr.response))
} else {
reject(new Error(xhr.response))
}
})
xhr.send()
})
// 3. 关联成功或失败函数,做后续处理
p.then(result => {
console.log(result)
document.querySelector('.my-p').innerHTML = result.list.join('<br>')
})
.catch(error => {
// 错误对象要用console.dir详细打印
console.dir(error)
// 服务器返回错误提示消息,插入到p标签显示
document.querySelector('.my-p').innerHTML = error.message
})
</script>
</body>
</html>
promise对象的一个好处:异步结果有成功与失败
- 调用resolve和reject关联结果处理函数,在then和catch回调中做后续处理
xhr如何判断响应成功还是失败?
- 通过响应状态码
- 打印xhr对象,我们可以看到status属性
- 如果响应状态码是2××,就说明响应成功
XHR + Promise 自定义功能封装
实际上,当我们掌握了axios的两大核心 XHR 和 Promise 后,可以自行封装一个类似axios的AJAX性质工具调用
封装简易版 axios
需求:基于 Promise + XHR 封装 myAxios函数,获取省份列表展示
步骤:
定义 myAxios函数 ,接收配置对象config形参,在函数内返回Promise对象 (config形参即:原来的axios,在使用时,是以一个
axios({url:'目标资源地址',method:'post',data:{参数名:值,}})
这样的对象来做传参使用的,那么实际上,需要配置一个形参,来作为配置对象传入) (Promise对象即:原来的axios,在使用时,是通过.then((result) => {//对服务器返回的数据做后续处理})
和.catch((error) => {//处理错误}))
来接收成功或失败的处理结果的,实际上就是分别对应了Promise对象中内置的两个方法fulfilled()已兑现
和rejected()已拒绝
状态)借助XHR原生对象,发起请求,将默认的请求方法设置为GET,类似axios中method请求方法选项
请求发起后,从服务器获取结果,判断状态码,决定是成功/失败,进而调用关联的resolve/reject处理程序
调用封装好的myAxios()函数,类似axios一样使用
- 简易版 axios 骨架
<body>
<script>
function myAxios (config){
return new Promise((resolve, reject) => {
//XHR请求
// 调用成功/失败的处理程序
})
}
myAxios({
ur1: '目标资源地址'
})
.then(result =>{
})
.catch(error =>{
})
</script>
</body>
<!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>封装_简易axios函数_获取省份列表</title>
</head>
<body>
<p class="my-p"></p>
<script>
/**
* 目标:封装_简易axios函数_获取省份列表
* 1. 定义myAxios函数,接收配置对象,返回Promise对象
* 2. 发起XHR请求,默认请求方法为GET
* 3. 调用成功/失败的处理程序
* 4. 使用myAxios函数,获取省份列表展示
*/
// 1. 定义myAxios函数,接收配置对象,返回Promise对象
// (通过config形参,模拟axios()函数中的多个配置对象,将来属性包括url、method、deta、params等)
function myAxios(config) {
// 函数调用后,返回给使用者的promise对象
return new Promise((resolve, reject) => {
// 2. 发起XHR请求,默认请求方法为GET
const xhr = new XMLHttpRequest()
// 封装请求方法,对config对象中的属性作判断,确认是否传入,如未传入,应设默认值为‘get’,通过逻辑或实现
xhr.open(config.method || 'GET', config.url)
// loadend事件为函数本质事件,非传入参数
xhr.addEventListener('loadend', () => {
// 3. 调用成功/失败的处理程序
// 原生XHR通过响应状态码判断响应成功/失败
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)
document.querySelector('.my-p').innerHTML = result.list.join('<br>')
})
.catch(error => {
console.log(error)
document.querySelector('.my-p').innerHTML = error.message
})
</script>
</body>
</html>
备注:
- 由于.then方法属于promise对象的实现,因此在调用myAxios函数时就返回了,所以能直接使用,链式编程
- promise()构造函数中,会自动调用参数函数(即resolve和reject),原型链中有.then和.catch方法,会依据参数函数的调用结果情况,再对应调用其中一个方法,因而不会调用另一个方法
封装支持传递查询参数的 类axios 函数
需求:修改 myAxios函数,以支持传递查询参数,获取“辽宁省”,“大连市”对应地区列表展示
步骤:
- myAxios函数调用后,传入params选项(对象结构的选项)
- 基于URLSearchParams转换查询参数字符串,在XHR中携带该查询参数,转成参数名=值,并拼接
- 使用自己封装的myAxios函数展示地区列表
<body>
<script>
function myAxios (config){
return new Promise((resolve, reject) => {
// 基于URLSearchParams转换查询参数字符串,在XHR中携带该查询参数,转成参数名=值,并拼接
// 调用成功/失败的处理程序
})
}
myAxios({
ur1: '目标资源地址',
params: {
参数名1: 值1,
参数名2: 值2
}
})
.then(result =>{
})
.catch(error =>{
})
</script>
</body>
<!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>封装_简易axios函数_获取地区列表</title>
</head>
<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()
// 集成带 传递查询参数(params选项) 的功能
// 1. 判断有params选项,携带查询参数
if (config.params) {
// 2. 使用URLSearchParams转换,并携带到url上
const paramsObj = new URLSearchParams(config.params)
// 定义常量,接收查询参数字符串
const queryString = paramsObj.toString()
// 把查询参数字符串,拼接在url?后面
config.url += `?${queryString}`
}
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()
})
}
// 3. 使用myAxios函数,获取地区列表
myAxios({
url: 'http://hmajax.itheima.net/api/area',
params: {
pname: '辽宁省',
cname: '大连市'
}
}).then(result => {
console.log(result)
document.querySelector('.my-p').innerHTML = result.list.join('<br>')
})
</script>
</body>
</html>
封装支持传递请求体数据的 类axios 函数
需求:修改myAxios函数支持传递请求体数据,完成注册用户功能
步骤:
- myAxios函数调用后,判断外面是否传入data选项
- 转换数据类型,在send方法中发送
- 使用自己封装的myAxios函数完成注册用户功能
<body>
<script>
function myAxios (config) {
return new Promise((resolve, reject) =>{
// XHR请求 > 判断当前调用,是否含data选项,携带请求体
// 调用成功/失败的处理程序
})
}
myAxios({
ur1: '目标资源地址',
// 调用时传入的data
data: {
参数名1: 值1,
参数名2: 值2
}
})
</script>
</body>
<!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>封装_简易axios函数_注册用户</title>
</head>
<body>
<button class="reg-btn">注册用户</button>
<script>
/**
* 目标:封装_简易axios函数_注册用户
* 1. 判断有data选项,携带请求体
* 2. 转换数据类型,在send中发送
* 3. 使用myAxios函数,完成注册用户
*/
function myAxios(config) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
if (config.params) {
const paramsObj = new URLSearchParams(config.params)
const queryString = paramsObj.toString()
config.url += `?${queryString}`
}
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中发送
const jsonStr = JSON.stringify(config.data)
// 原生XHR请求体提交对象时,还需要标明对象业务类型
// 因此,json字符串还需要设置额外的信息,告诉服务器,指定信息业务类型
xhr.setRequestHeader('Content-Type', 'application/json')
xhr.send(jsonStr)
} else {
// 如果没有请求体数据,正常的发起请求
// (此步发送请求体数据,因此判断工作放在此步之前)
xhr.send()
}
})
}
// 点击按钮,触发调用自己封装的函数
document.querySelector('.reg-btn').addEventListener('click', () => {
// 3. 使用myAxios函数,完成注册用户
myAxios({
url: 'http://hmajax.itheima.net/api/register',
method: 'POST',
data: {
username: 'itheima999',
password: '666666'
}
})
// 返回的promise对象,拿到对象内部的异步任务将来成功/失败结果
.then(result => {
console.log(result)
})
.catch(error => {
console.dir(error)
})
})
</script>
</body>
</html>
综合案例 - 天气预报
先根据效果/需求,分析步骤:
- 打开网页,先默认获取北京市天气数据,并展示
- 搜索城市列表,展示,关键字输入及备选结果显示
- 点击城市,获取并显示对应天气数据
步骤梳理:
- 有两次重复逻辑:分别是默认,以及搜索后,需要获取并展示,因此通过封装函数,实现复用调用
- 输入关键字时,匹配候选城市
- 绑定input事件,获取关键字,监测输入框input事件检测实时改变情况,获取用户在输入框输入的字
- 调用后端接口,获取展示城市列表数据,展示在候选位置
- 点击候选的城市,触发调用
- 通过编码实现调用,因此需要先获取到点击时的所选城市code值;检测搜索列表点击事件,获取城市code编码
- 复用获取展示城市天气的函数
综合案例 - 天气预报 - 引入的、预封装的css及js
<!DOCTYPE html>
<html lang="zh-CN ">
<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">
<link rel="stylesheet" href="./css/reset.css">
<link rel="stylesheet" href="./css/index.css">
<title>案例_天气预报</title>
</head>
<body>
<div class="container" > ...
<!-- 此部分没有拿到信息 -->
</div>
<!--自己封装myAxios函数-->
<script src="./js/my-axios.js"></script>
<!--搜索框+下拉菜单出现逻辑-->
<script src="./js /search.js"></script>
<!--核心js -->
<script src="./js/index.js"></script>
</body>
综合案例 - 天气预报 - ./js/index.js
<body>
<script>
/**
* 目标1:默认显示-北京市天气
* 1.1 获取北京市天气数据
* 1.2 数据展示到页面
*/
// 封装--获取并渲染城市天气函数
function getWeather(cityCode) {
// 1.1 获取北京市天气数据
myAxios({
url: 'http://hmajax.itheima.net/api/weather',
params: {
city: cityCode
}
})
.then(result => {
// 自己封装的axios,没有key:value对,直接是后端服务器返回的数据结果,养成打印数组字段确认的习惯
console.log(result)
const wObj = result.data
// 1.2 数据展示到页面
// 数据展示顺序>>>>一般是上到下,左到右
// 通过模板字符串中插入数据,覆盖原标签结构实现
// 阳历和农历日期
const dateStr = `<span class="dateShort">${wObj.date}</span>
<span class="calendar">农历
<span class="dateLunar">${wObj.dateLunar}</span>
</span>`
document.querySelector('.title').innerHTML = dateStr
// 城市名字
document.querySelector('.area').innerHTML = wObj.area
// 当天气温
const nowWStr = `<div class="tem-box">
<span class="temp">
<span class="temperature">${wObj.temperature}</span>
<span>°</span>
</span>
</div>
<div class="climate-box">
<div class="air">
<span class="psPm25">${wObj.psPm25}</span>
<span class="psPm25Level">${wObj.psPm25Level}</span>
</div>
<ul class="weather-list">
<li>
<img src="${wObj.weatherImg}" class="weatherImg" alt="">
<span class="weather">${wObj.weather}</span>
</li>
<li class="windDirection">${wObj.windDirection}</li>
<li class="windPower">${wObj.windPower}</li>
</ul>
</div>`
document.querySelector('.weather-box').innerHTML = nowWStr
// 当天天气
const twObj = wObj.todayWeather
const todayWStr = `<div class="range-box">
<span>今天:</span>
<span class="range">
<span class="weather">${twObj.weather}</span>
<span class="temNight">${twObj.temNight}</span>
<span>-</span>
<span class="temDay">${twObj.temDay}</span>
<span>℃</span>
</span>
</div>
<ul class="sun-list">
<li>
<span>紫外线</span>
<span class="ultraviolet">${twObj.ultraviolet}</span>
</li>
<li>
<span>湿度</span>
<span class="humidity">${twObj.humidity}</span>%
</li>
<li>
<span>日出</span>
<span class="sunriseTime">${twObj.sunriseTime}</span>
</li>
<li>
<span>日落</span>
<span class="sunsetTime">${twObj.sunsetTime}</span>
</li>
</ul>`
document.querySelector('.today-weather').innerHTML = todayWStr
// 7日天气预报数据展示
// 在大数组里面的7个对象,遍历每个对象,然后取出每个对象的数据嵌入每个小标签,再把大标签映射回来
const dayForecast = wObj.dayForecast
// map遍历映射这个数组
const dayForecastStr = 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('')
// console.log(dayForecastStr)
document.querySelector('.week-wrap').innerHTML = dayForecastStr
})
}
// 默认进入网页-就要获取天气数据(北京市城市编码:'110100')
getWeather('110100')
/**
* 目标2:根据关键字,搜索匹配城市列表
* 2.1 绑定input事件,获取关键字,只要用户增删字符,都会触发事件
* 2.2 获取展示城市列表数据
*/
// 2.1 绑定input事件,获取关键字,只要用户增删字符,都会触发事件,事件处理函数中获取关键字
// 通过 事件对象.target(e.target)拿到触发该事件的目标元素(此处即:输入框),再拿输入框里面的值e.target.value
// 监测输入框可以改变的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)
// 通过result.data拿到数组,然后将数组里面的对象映射成结构
const liStr = result.data.map(item => {
// 3.1 接着下面的3.1回头,给选定点击的小li,加城市编码
return `<li class="city-item" data-code="${item.code}">${item.name}</li>`
}).join('')
console.log(liStr)
document.querySelector('.search-list').innerHTML = liStr
})
})
/**
* 目标3:切换城市天气
* 3.1 绑定城市点击事件,获取城市code值
* 3.2 调用获取并展示天气的函数
*/
// 3.1 绑定城市点击事件,获取城市code值
// 通过事件委托,给动态标签(城市候选列表小li)绑定点击事件
// 找到一直都在的ul标签.search-list,然后绑定点击事件,加事件处理函数,加接收处理对象
document.querySelector('.search-list').addEventListener('click', e => {
// 判断点击了小li/事件源,才执行
if (e.target.classList.contains('city-item')) {
// 只有点击城市li才会走这里
// dataset自定义属性.code
//e.target.dataset与e.currentTarget.dataset的作用:获取标签中定义的值,定义方法 data-*=某个值
// 区别:
// e.target.dataset是指获取当前点击dom的值,若没有对应的值则取的是undefined
// e.currentTarget 是指注册了监听点击事件的组件,会获取有事件的那个元素
const cityCode = e.target.dataset.code
console.log(cityCode)
// 3.2 调用获取并展示天气的函数
getWeather(cityCode)
}
})
</script>
</body>
</html>