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>