Ajax 是浏览器中的技术:用来实现客户端网页请求服务器的数据。 它的英文全称是 Asynchronous Javascript And XML

一、 五种请求方式

请求方式 具体含义
POST 向服务器新增数据
GET 从服务器获取数据
DELETE 删除服务器上的数据
PUT 更新服务器上的数据(全部更新:跟新某用户的全部信息)
PATCH 更新服务器上的数据(部分更新:更新用户的部分信息,比如手机号等)

二、 axios

目前前端圈流行的Ajax的js库,中文网:http://www.axios-js.com/

原生代码比较复杂,先来讨论axios库。

2.1 axios 的基础语法

axios({
      // 请求的url地址
      url: 'url地址',
      // 请求方式: get post delete put patch
      method: 'get'
    }).then(function (形参) {
      // 该回调函数会在请求成功的时候会来执行
      // 形参 result(对象) 里面包含有 服务器响应回来的结果(data属性中)
    })
  <!-- 必须先导入 axios 的库文件,然后就可以调用 axios() 函数了 -->
  <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
  <script>
    // =================================== 目标 ===================================
    // 发起 GET 请求,获取图书列表的数据

    axios({
      // 请求的url地址
      url: 'yourUrl',
      // 请求方式: get post delete put patch
      method: 'get'
    }).then(function (result) {
      // 该回调函数会在请求成功的时候会来执行
      // 形参 result(对象) 里面包含有 服务器响应回来的结果(data属性中)
      console.log('该回调函数会在请求成功的时候会来执行')
      console.log(result)
      console.log(result.data) // 服务器响应回来的结果
    })
  </script>
</body>

2.2 GET的查询参数

刚才查询回来的是所有图书的列表数据,如果想指定查询的条件,可以通过 params 选项来指定查询的参数:

axios({
  
  url: 'yourUrl',
  method: 'get',
    //通过params选项指定查询参数
  params:{
    id:2,
    author: "曹雪芹"
  }
}).then(function (result) {
  console.log('该回调函数会在请求成功的时候会来执行')
  console.log(result)
  console.log(result.data) // 服务器响应回来的结果
})

查询参数的本质

在使用 Ajax 发起 GET 请求时的参数,会以 ?键=值 的形式拼接到 URL 地址的末尾。

params 选项里面设置多个关键词时候,多个查询参数之间使用 & 符号进行分隔。

就相当于uri请求地址变为:(了解)

yourURL?id=1&author="曹雪芹"

2.3 js的编码问题

JavaScript提供了两个编码和解码的函数

编码: encodeURI()

解码:decodeURI()

console.log(encodeURI('西游记'))    //%E8%A5%BF%E6%B8%B8%E8%AE%B0
console.log(decodeURI('%E8%A5%BF%E6%B8%B8%E8%AE%B0'))    //西游记

2.4 GET的案例

2.4.1 获取相关信息

<!-- 导入axios -->
<script src="./lib/axios.js"></script>
<script>
  const list = document.querySelector('#news-list')

  // 步骤:
  // 1. 获取数据 ==> 通过 axios 发送GET请求
  //           请求地址: *****
  // 2. 渲染页面
  axios({
    url:'yourURL/api/news',
    method:'get'
  }).then(function (res) {
    console.log(res)
    console.log(res.data)
    console.log(res.data.code)    //对象.data 获取到服务器响应回来的数据
    console.log(res.data.data)    //对象.data.data  获取到服务器相应回来的文章相应的信息
  })
</script>

2.4.2 赋值解构

目的是快速取值

赋值解构的回顾:

<body>
  <script>
    // 解构赋值:目的: 快速的取值 

    const obj = {
      username: 'zs',
      age: 18,
      car: {
        name: '劳斯莱斯',
        price: 1000
      },
      house: {
        name: '新疆海景房',
        price: 5
      }
    }

    // ========================= 取出对象obj的car和house属性 ========================= 
    // let car = obj.car
    // let house = obj.house
    // console.log(car)
    // console.log(car.name)
    // console.log(car.price)
    // console.log(house)
    // console.log(house.name)
    // console.log(house.price)

    // ========================= 解构赋值写法 =========================
    // let {car, house} = obj
    // console.log(car, house)

    // =========================  重命名 =========================
    // let { car: car2 } = obj
    // console.log(car2)
    // console.log(car) // 原来的car变量名无法使用的


    // =========================  深层解构 =========================
    let { car: { name, price } } = obj
    console.log(name, price)
    console.log(car)// 原来的car变量名无法使用的


    // let { house: { name, price } } = obj
    // console.log(name, price)

    // 对car 和 house 都深层解构,因为name,price都同名了,就必须重命名
    // let { house: { name, price }, car: { name: carName, price: carPrice } } = obj
    // console.log(name, price, carName, carPrice)


    // ========================= 解构赋值还可以用于在函数参数上 =========================
    // function fn(info) {
    //   console.log(info)
    //   let { car, house } = info
    //   console.log(car, house)
    // }

    // 改良fn函数写法
    // function fn({ car: { name, price }, house }) {
    //   console.log(name, price, house)
    // }

    // fn(obj)
    

    // function fn2([x, y, z]) {
    //   console.log(x, y, z)
    // }

    // fn2([10, 20, 30])
  </script>
</body>

所以新闻案例的解构赋值:

通过请求,从服务器返回了这一堆东西,用形参result表示:

  1. 主要取得的是data部分的值,如果只需要data部分
{data} = result
axios({
  url: 'youURL/api/news',
  method: 'get'
}).then(({data:res}) => {
  //{data:res } 解构出来的data,并重命名为
  console.log(res)
})

主要是得到以下内容:

  1. 服务器返回的信息的结构是: result{data:{code,msg,data}} ,所以,解构出data的数据要按照这个结构来

    {data:{code,mag,data}}
    
    // 以下代码是在深层解构
    axios({
      url: 'yourURL/api/news',
      method: 'get'
    }).then(({data:{code,msg,data}}) => {
      console.log(code)
      console.log(msg)
      console.log(data)
    })
    

返回以下内容:

2.4.3 实现新闻列表的功能

<script>
  const list = document.querySelector('#news-list')

  // 步骤:
  // 1. 获取数据 ==> 通过 axios 发送GET请求
  //           请求地址: yourURL/api/news
  // 2. 渲染页面

  axios({
    url: 'yourURL/api/news/api/news',
    method: 'get'
  }).then(({ data: res }) => {
         //获得data对象,并改名为res,为res对象:res:{code,data[],msg}
    console.log(res)
    if (res.code ===200){
      let htmlStr = ``
      res.data.forEach(items =>{         //遍历res.data[]数组
        htmlStr +=`
        <div class="news-item">
          <img class="thumb" src="http://www.xxxx.com:3009${items.img}" alt="" />
         <div class="right-box">
            <!-- 新闻标题 -->
            <h1 class="title">${items.title}</h1>
            <div class="footer">
              <div>
                <!-- 新闻来源 -->
                <span>${items.source}</span>  
                <!-- 发布日期 -->
                <span>${items.time}</span>
              </div>
              <!-- 评论数量 -->
              <span>评论数:${items.cmtcount}</span>
            </div>
          </div>
         </div>

        `
      })
      list.innerHTML = htmlStr
    }else {
      alert(res.msg)
    }

  })

</script>

2.4.4 数组的map方式

回顾数组的map方法,映射得到新数组。

 let arr = ['苹果', '榴莲', '梨子', '香蕉']

// 数组的map方法 映射得到新数组

 let arr2 = arr.map(item => `<li>${item}</li>`)
  console.log(arr2)
// arr2 = ['<li>苹果</li>', '<li>榴莲</li>', '<li>梨子</li>', '<li>香蕉</li>']

2.4.5 将数组拼接为字符串

join('拼接字符') 回顾

// 数组变字符串 ==> join() 把数组的每一项拼接成字符串
 let arr3 = ['哈哈', '小明', '嘻嘻']
 console.log(arr3.join('-'))   // '哈哈-小明-嘻嘻'
 console.log(arr3.join('❤️'))   // '哈哈❤️小明❤️嘻嘻'
 console.log(arr3.join(''))   // '哈哈小明嘻嘻'

2.4.6 使用map实现新闻列表案例

  axios({
    url: 'yourURL/api/news',
    method: 'get'
  }).then(({ data: res }) => {
    // {data: res} 解构出来data,并且重命名为res(这对应的就是服务器响应回来的数据)
    // console.log(res)
    // console.log(res.code)
    // console.log(res.msg)
    // console.log(res.data)

    if (res.code === 200) {
      // 获取成功了 ==> 遍历res.data 数组,将其展示到页面中
      list.innerHTML = res.data.map(items =>`
        <div class="news-item">
          <img class="thumb" src="http://www.liulongbin.top:3009${items.img}" alt="" />
         <div class="right-box">
            <!-- 新闻标题 -->
            <h1 class="title">${items.title}</h1>
            <div class="footer">
              <div>
                <!-- 新闻来源 -->
                <span>${items.source}</span>  
                <!-- 发布日期 -->
                <span>${items.time}</span>
              </div>
              <!-- 评论数量 -->
              <span>评论数:${items.cmtcount}</span>
            </div>
          </div>
         </div>

        `).join('')    //得到新数组,并调用joib方法,将新数组拼接成字符串
    } else {
      // 获取失败了
      // alert('获取失败了')
      alert(res.msg)
    }
  })
</script>

2.5 POST的案例

2.5.1 axios发送post请求

语法:

axios({
        url:'http://yourPOSTURL',
        method:'post',
        data:{
          //要传给服务器的数据,具体要看api文档,查看对应的键值对
          //对于params data里面写啥,具体看后面学习的接口文档
        },
    params: {
      //查询链接 params选项可选,按需求使用该选项
    },
      }).then(function(形参){
        console.log(形参)   //这里的形参一般是服务器返回的信息,根据需要应用即可
      })   

案例:

<body>
  <button id="btnPOST">新增数据</button>

  <script src="./lib/axios.js"></script>
  <script>
    // =================================== 点击按钮 ,发送 post请求 ===================================
    // 地址  http://yourPOSTURL
    // 参数  bookname 书名、 author 作者、 publisher 出版社
    document.querySelector('#btnPOST').addEventListener('click',()=>{
      axios({
        url:'http://yourPOSTURL',
        method:'post',
        data:{
          bookname:'巴巴托斯',
          author:'barbatos',
          publisher: '提瓦特出版社'
        }
      }).then(({data:res}) => alert(res.msg))
    })

  </script>
</body>

通过 Chrome 浏览器的 Network 网络请求面板,可以发现 POST 请求提交的数据,并没有拼接到URL 地址的末尾。POST 为了能够提交大量的数据,所以没有把数据拼接到 URL 的末尾;而是放到了独立的“请求体”中。

注意:在浏览器中,GET 请求比较特殊:它只有查询参数,没有请求体

2.5.2 get和post的请求对比

get请求方式:数据写在 params(查询参数,数据放到了url地址后面)

post请求方式:数据写在data(数据在请求体)

当请求方式为post的时候,既可以带params查询参数,也可以带data请求体数据

// 注意点:
//  对于 axios 在使用的时候,method url data params 这些都是固定的配置,单词不能写错
//  对于params data里面写啥,具体看后面学习的接口文档
 axios({
  method: 'post',
  url: 'yourPOSTURL',

  // 查询参数
  params: {
    a: 1,
    b: 2
  },

  // 1. 当请求方式为post的时候,既可以带params查询参数,也可以带data请求体数据
  // 2. 当请求方式为get的时候,可以写params查询参数,写不写data按照实际需求
  data: {
    c: 3,
    d: 4
  }
}).then(({ data: res }) => {
  console.log(res)
}) 


//Request URL: yourPOSTURL?a=1&b=2

登录案例:

<body>
  <div class="login-box">
    <div class="form-group">
      <label for="username">Account</label>
      <!-- 账号 -->
      <input type="text" class="form-control" id="username" autocomplete="off" />
      <small id="emailHelp" class="form-text text-muted">The available account is <strong>admin</strong></small>
    </div>
    <div class="form-group">
      <!-- 密码 -->
      <label for="password">Password</label>
      <input type="password" class="form-control" id="password" />
      <small id="emailHelp" class="form-text text-muted">The available password is <strong>123456</strong></small>
    </div>
    <button type="submit" class="btn btn-primary" id="btnLogin">Submit</button>
  </div>

  <script src="./lib/axios.js"></script>

  <script>
    // 请求方式 POST
    // 地址 yourPOSTURL
    // 参数:  username 用户名     password  密码  ==> 请求体参数,写在data中

    // 步骤
    //  1. 给按钮注册click
    //  2. 获取输入框的值 ==> value属性
    //  3. 使用axios发送请求,之后根据响应结果,做提示

    // 1. 
    document.querySelector('#btnLogin').addEventListener('click', function () {
      // 2. 
      let username = document.querySelector('#username').value
      let password = document.querySelector('#password').value
      // console.log(username, password)

      // 3. 
      axios({
        method: 'post', // post 无论大小写都是可以的
        url: 'yourPOSTURL',
        data: {
          // 对象属性简写
          username,
          password
        }
      }).then(({ data: res }) => {
        // 请求成功

        console.log(res) // res 服务器响应回来的数据

        // 200 是业务状态码(不是写死的,从响应体数据里面去看)
        if (res.code === 200) {
          // 登录成功
          alert('恭喜你,登录成功了,稍后跳转去首页')
          // location.href = 'http://www.jd.com'
        } else {
          // 登录失败
          alert(res.msg)
        }
      })
    })
  </script>
</body>

三、请求报文 & 响应报文

  • 请求报文规定了客户端以什么格式把数据发送给服务器
  • 响应报文规定了服务器以什么格式把数据响应给客户端

3.1 状态响应码

  • 200 ok 请求成功
  • 404 not found
  • 500 服务异常
  • 常见状态码:

3.2 区分相应状态码和业务码

  1. 所处的位置不同:
    • 在状态行中所包含的状态码,叫做“响应状态码”
    • 在响应体的数据中所包含的状态码,叫做“业务状态码”
  2. 表示的结果不同:
    • 响应状态码只能表示这次请求的成功与否(成功或失败)
    • 业务状态码用来表示这次业务处理的成功与否
  3. 通用性不同:
    • 响应状态码是由 http 协议规定的,具有通用性。每个不同的状态码都有其标准的含义,不能乱用.
    • 业务状态码是后端程序员自定义的,不具有通用性。

3.3 接口与接口文档

使用 Ajax 请求数据时,被请求的 URL 地址,就叫做数据接口(简称:接口或 API 接口)。同时,每个接口必须有对应的请求方式。

接口文档就是接口的使用说明书,它是我们调用接口的依据。

接口文档的接口信息

3.4 postman接口测试工具

案例:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <meta http-equiv="X-UA-Compatible" content="ie=edge" />
  <link rel="stylesheet" href="css/reset.css" />
  <link rel="stylesheet" href="css/main.css" />
  <title>聊天机器人</title>
</head>

<body>
  <div class="wrap">
    <!-- 头部 Header 区域 -->
    <div class="header">
      <h3>小思同学</h3>
      <img src="img/person01.png" alt="icon" />
    </div>
    <!-- 中间 聊天内容区域 -->
    <div class="main">
      <ul class="talk_list" style="top: 0px;" id="talk_list">
        <!-- 机器人 -->
        <li class="left_word">
          <img src="img/person01.png" /> <span>嗨,最近想我没有?</span>
        </li>
        <!-- 我 -->
        <!-- <li class="right_word">
          <img src="img/person02.png" /> <span>嗨,最近想我没有?</span>
        </li> -->
      </ul>
    </div>
    <!-- 底部 消息编辑区域 -->
    <div class="footer">
      <img src="img/person02.png" alt="icon" />
      <input type="text" placeholder="说的什么吧..." class="input_txt" id="ipt" />
      <input type="button" value="发 送" class="input_sub" id="btnSend" />
    </div>
  </div>
  <script type="text/javascript" src="./js/axios.js"></script>
  <audio src="" id="voice" autoplay style="display: none;"></audio>
  <script>
    let ipt = document.querySelector('#ipt')
    let btnSend = document.querySelector('#btnSend')
    let ul = document.querySelector('#talk_list')
    let voice = document.querySelector('#voice')

    // 发布功能
    btnSend.addEventListener('click', sendValue)
    ipt.addEventListener('keyup',(e)=>{
      if (e.key === 'Enter') {btnSend.click()}     //敲击回车键发送内容
    })

    function sendValue() {
        // webapis 知识点
        // 自己说的话,显示到页面中(ul)
        // 步骤
        //  1. 获取ipt的内容
        //  2. 创建li ==> document.createElement()
        //  3. 设置内容 ==> innerHTML
        //  4. 添加到ul中 ==> appendChild

        let content = ipt.value
        //  console.log(content);
        ipt.value = ''
        let li = document.createElement('li')
        li.className = 'right_word'
        li.innerHTML=`
                <img src="img/person02.png" /> <span>${content}</span>
            `
        ul.appendChild(li)
        ul.scrollTop = ul.scrollHeight

        // 2.发送Ajax请求来获取机器人说的话
        axios({
          url:'url/api/robot',
          method:'get',
          params:{
            spoken: 'content'
          }
        }).then(({data:res})=>{
          let text = res.data.info.text
          let left_li = document.createElement('li')
          left_li.className = 'left_word'
          left_li.innerHTML = `<img src="img/person01.png" /> <span>${text}</span>`
          // 4.
          ul.appendChild(left_li)
          ul.scrollTop = ul.scrollHeight
          // 3.文字转语音,需要写到then里面
          //代码的位置要注意(text参数的传递)
          getVoice(text)
        })

        //ajax请求,将机器人的文字转语音
        function getVoice(text){
          axios({
            url: 'url/api/synthesize',
            method: 'get',
            params:{   //注意这里,日常会出错。axios的get没有data,记得传递查询text
              text
            }
          }).then(({data:res})=>{
            console.log(res)
            //将机器人的文字转语音的结果voiceUrl赋值给src属性
            voice.src = res.voiceUrl
          })
        }
    }
  </script>
</body>

</html>

四、from表单(了解)

4.1 表单

<from>
    <div>
        <span>登录</span>
        <input type = 'txt' name = 'username'/>
    </div>
 <div>
        <span>密码</span>
        <input type = 'password' name = 'password'/>
    </div>
 <div>
        <button type = 'submit'>登录</button>
 </div>

</from>

注意:每个表单域必须包含 name 属性,否则用户填写的信息无法被采集到!

① type=“submit” 表示提交按钮的意思

② type 属性的默认值就是 submit,因此type=“submit” 可以省略不写

<form>标签的属性分别是 action、method 简介信息如下表所示:

属性 可选值 说明
action 接口的 url 地址 把表单采集到的数据,提交到哪个接口
method GET 或 POST 数据的提交方式(默认值为 GET)
<from  action = 'url' method = 'GET'>
    <div>
        <span>登录</span>
        <input type = 'txt' name = 'username'/>
    </div>
 <div>
        <span>密码</span>
        <input type = 'password' name = 'password'/>
    </div>
 <div>
        <button>登录</button>
 </div>

</from>

由于 method 属性的默认值就是 GET,因此上述的 method=“GET” 可以被省略!

<from  action = 'url' method = 'POST'>
    <div>
        <span>登录</span>
        <input type = 'txt' name = 'username'/>
    </div>
 <div>
        <span>密码</span>
        <input type = 'password' name = 'password'/>
    </div>
 <div>
        <button type = 'submit'>登录</button>
 </div>

</from>

注意:采用get方式是属于明文传输数据,属于是在浏览器上就可以看到,极不安全。而post方式则没有出现这种情况,post会安全一些。

4.2 表单的缺点

表单身兼数职:既负责采集数据,又负责把数据提交到服务器!表单的默认提交行为会导致页面的跳转。

解决方案:(符合:职能单一的原则)

表单只负责采集数据;

Ajax 负责将数据提交到服务器。

4.3 Ajax提交表单数据

<body>
  <div class="login-box">
    <form>
      <div class="form-group mb-3">
        <label for="username">Account</label>
        <!-- 账号 -->
        <input type="text" class="form-control" name="username" id="username" autocomplete="off" />
        <small id="emailHelp" class="form-text text-muted">The available account is <strong>admin</strong></small>
      </div>
      <div class="form-group mb-3">
        <!-- 密码 -->
        <label for="password">Password</label>
        <input type="password" class="form-control" name="password" id="password" />
        <small id="emailHelp" class="form-text text-muted">The available password is <strong>123456</strong></small>
      </div>
      <button type="submit" class="btn btn-primary" id="btnLogin">Submit</button>
    </form>
  </div>

  <script src="./lib/axios.js"></script>
  <script>
    // 请求方式 POST
    // 地址 yourPOSTURL/api/login/login
    // 参数:  username 用户名     password  密码

    // 使用Ajax提交表单数据的步骤
    //  1. 监听表单的 submit 提交事件
    //  2. 阻止默认提交行为
    //  3. 基于 axios 发起请求
    //  4. 指定请求方式、请求地址、指定请求体数据
    document.querySelector('form').addEventListener('submit',(e)=>{
      e.preventDefault()
      let username = document.querySelector('#username').value
      let password = document.querySelector('#password').value
      axios({
        method: 'post',
        url: 'yourPOSTURL/api/login',
        data: {
          username,  //键值对 名字一致可以简写成一个
          password
        }
      }).then(({ data: res }) => {
        alert(res.msg)
      })
    })

  </script>
</body>

只有一两个参数的填写的表单可以用上述的方式实现post。但是如果是大量的表单数据的时候,则需要一个插件,帮忙处理表单数据:https://www.npmjs.com/package/form-serialize

获取到表单数据

serialize(form)  //获得字符串  'username=admin&password=123456'
serialize(form, { hash: true })  // js对象 {username: 'admin', password: '123456'}

<body>
  <div class="login-box">
    <form>
      <div class="form-group mb-3">
        <label for="username">Account</label>
        <!-- 账号 -->
        <input type="text" class="form-control" name="username" id="username" autocomplete="off" />
        <small id="emailHelp" class="form-text text-muted">The available account is <strong>admin</strong></small>
      </div>
      <div class="form-group mb-3">
        <!-- 密码 -->
        <label for="password">Password</label>
        <input type="password" class="form-control" name="password" id="password" />
        <small id="emailHelp" class="form-text text-muted">The available password is <strong>123456</strong></small>
      </div>
      <button type="submit" class="btn btn-primary" id="btnLogin">Submit</button>
    </form>
  </div>

  <script src="./lib/axios.js"></script>
  <script src="./lib/form-serialize.js"></script>

  <script>
    // 请求方式 POST
    // 地址 yourPOSTURL/api/login/login
    // 参数:  username 用户名     password  密码

    // 使用Ajax提交表单数据的步骤
    //  1. 监听表单的 submit 提交事件
    //  2. 阻止默认提交行为
    //  3. 基于 axios 发起请求
    //  4. 指定请求方式、请求地址、指定请求体数据

    // 引入form-serialize插件,提供 serialize 函数,从而获取表单数据
    // serialize(form表单的DOM对象)

    // let form = document.querySelector('form')

    // 都可以获取到表单数据
    // serialize(form)  // 'username=admin&password=123456'
    // serialize(form, { hash: true })  // js对象 {username: 'admin', password: '123456'}


    document.querySelector('form').addEventListener('submit', function (e) {
      // 2.              //↑这里不能写成箭头函数,否则serialize获取不了数据
      e.preventDefault()

      // 提交的时候,serialize插件来获取表单数据
      let data = serialize(this, {
        // {} 配置对象 hash 配置,可以将 收集到的表单数据是个js对象格式
        hash: true
      })
      console.log(data)

      // 3. 
      axios({
        url: 'yourPOSTURL/api/login/login',
        method: 'post',
        // 这个data不能省,否则没有提交数据给服务器
        data
      }).then(({ data: res }) => {
        console.log(res)
      })
    })
  </script>
</body>

五、文件上传

5.1 FormData

FormData 是浏览器提供的一个 WebAPI,它以键值对的方式存储数据。

应用场景:FormData + Ajax 技术实现文件上传的功能。

5.2 FormData 的基本用法

FormData 是一个构造函数new FormData() 即可得到 FormData 对象:

const fd = new FormData()

调用 FormData 对象的 append(键, 值) 方法,可以向空白的 FormData 中追加键值对数据,其中:

  1. 键表示数据项的名字,必须是字符串
  2. 值表示数据项的值,可以是任意类型的数据
fd.append('username', '张三') // 键是 username,值是字符串类型
fd.append('age', 20) // 键是 age, 值是数字类型
fd.append('avatar', 图片文件) // 键是 avatar, 值是文件类型
let fd = new FormData()
fd.append('username','Barbatos')
fd.append('age',5000)

fd.forEach((key,value)=>{
  console.log(key,value)   //使用对象的forEach()遍历该对象的元素,显示内容
})

5.3 FormData收集文件数据

文件域有change事件(监听文件域的change事件),当选择的文件发生了改变就会触发change事件

文件域也有files属性,这里存在用户选择的文件的信息

<body>
  <input type="file" id="iptFile" accept="image/*" />
  <script>
    let iptFile = document.querySelector('#iptFile')

    // =========================== FormData的基本使用 ===========================

    // FormData 是构造函数,new FormData得到其实例对象

    let fd = new FormData() // 空的
    // fd.append(键, 值) // 键 必须是字符串类型
    // fd存选择好的文件数据
    // 文件域有change事件(监听文件域的change事件),当选择的文件发生了改变就会触发change事件
    iptFile.addEventListener('change', function () {

      console.log(this.files)
      console.log(this.files[0])

    })

  </script>
</body>

显示了如下效果:

使用FormData收集文件数据:

<body>
  <input type="file" id="iptFile" accept="image/*" />
  <script>
    let iptFile = document.querySelector('#iptFile')

    // =========================== FormData的基本使用 ===========================

    // FormData 是构造函数,new FormData得到其实例对象

    let fd = new FormData() // 空的

     fd.append(键, 值) // 键 必须是字符串类型

     fd.append('username', 'zs')
     fd.append('age', 19)

    // fd存选择好的文件数据

    // 文件域有change事件(监听文件域的change事件),当选择的文件发生了改变就会触发change事件
    iptFile.addEventListener('change', function () {
      // console.log('change事件触发了,文件改变了')

      // 如何去获取选择的文件 ==> FormData存文件
      // 需要借助于文件域的files属性,这存在用户选择的文件
      console.log(this.files)
      console.log(this.files[0])

      fd.append('photo', this.files[0])

       fd.forEach((value, key) => {
         console.log(key, value)
       })
    })

  </script>
</body>

案例:头像上传

api文档

<!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="./lib/bootstrap-v4.6.0.css">
  <style>
    .thumb-box {
      text-align: center;
      margin-top: 50px;
    }

    .thumb {
      width: 250px;
      height: 250px;
      object-fit: cover;
      border-radius: 50%;
    }
  </style>
</head>

<body>
  <div class="thumb-box">
    <!-- 头像 -->
    <img src="./images/cover.jpg" class="img-thumbnail thumb" alt="">
    <div class="mt-2">
      <!-- 文件选择框 -->
      <!-- accept 属性表示可选择的文件类型 -->
      <!-- image/* 表示只允许选择图片类型的文件 -->
      <input type="file" id="iptFile" accept="image/*" style="display: none;" />
      <!-- 选择头像图片的按钮 -->
      <button class="btn btn-primary" id="btnChoose">选择 & 上传图片</button>
    </div>
  </div>
  <script src="./lib/axios.js"></script>
  <script>
    

    let btnChoose = document.querySelector('#btnChoose')
    let iptFile = document.querySelector('#iptFile')
    let img = document.querySelector('.thumb')
    let webUrl = 'https://img.up.cdn.nahida.cn'

    // 功能
    //  1. 点击按钮,弹出文件选择框
    //  2. 实现文件的上传功能
    //  3. 因为文件域的iptFile样式难改且丑,所以使用模拟点击的方式调用该文件域的方式  iptFile.click()
    btnChoose.addEventListener('click',function () {
      iptFile.click()   //点击样式好看的按钮,实现模拟点击文件域选择文件按钮
    })
    iptFile.addEventListener('change',function () {
      console.log(this.files)
      if (!this.files){
        return    //因为只要文件修改就会触发change事件,当原本是有文件而没有选择文件时候也会触发。
           //所以需要判断是否选择了图片,如果没有,则直接退出本函数
      }
      let fd = new FormData
      fd.append('avata',this.files[0])
      axios({
        url : 'yourPOSTURL/avatar',
        method : 'post',
        data: fd
      }).then(function ({data:res}) {
        console.log(res.url)
        img.src = webUrl + res.url
      })
    })

  </script>
</body>

</html>

六、请求体类型

请求体类型 Content-Type,客户端告诉服务器实际发送的数据类型

属性值 应用场景
application/x-www-form-urlencoded 表单中不包含文件上传的场景,适用于普通数据的提交
multipart/form-data 表单中包含上传文件的场景
application/json 上传json格式数据

如何查看客户端发送的数据类型呢?

谷歌浏览器或者是其他的了浏览器,打开(快捷键F12),在控制面板NetWork:

<body>

  <!-- 栅格系统 -->
  <div class="container-fluid">
    <!-- 栅格系统中的一行 -->
    <div class="row">
      <!-- 左侧的表格,占了 8 列 -->
      <div class="col-sm-8">
        <table class="table table-bordered table-striped table-dark table-hover text-center">
          <thead>
            <!-- 表头行 -->
            <tr>
              <th scope="col">Id</th>
              <th scope="col">书名</th>
              <th scope="col">作者</th>
              <th scope="col">出版社</th>
              <th scope="col">操作</th>
            </tr>
          </thead>
          <tbody>
            <!-- 表格中的每一行 -->
            <tr>
              <th scope="row">xxx</th>
              <td>xxx</td>
              <td>xxx</td>
              <td>xxx</td>
              <td>
                <button type="button" class="btn btn-link btn-sm btn-del">删除</button>
              </td>
            </tr>
          </tbody>
        </table>
      </div>

      <!-- 右侧的添加区域,占了 4 列 -->
      <div class="col-sm-4">
        <!-- 添加图书的卡片 -->
        <div class="card text-white bg-secondary sticky-top">
          <div class="card-header">添加新图书</div>
          <form class="card-body bg-light" id="addForm">
            <!-- 书名 -->
            <div class="input-group mb-3">
              <div class="input-group-prepend">
                <span class="input-group-text">书名</span>
              </div>
              <input type="text" class="form-control" placeholder="请输入图书名称" name="bookname" />
            </div>
            <!-- 作者 -->
            <div class="input-group mb-3">
              <div class="input-group-prepend">
                <span class="input-group-text">作者</span>
              </div>
              <input type="text" class="form-control" placeholder="请输入作者名字" name="author" />
            </div>
            <!-- 出版社 -->
            <div class="input-group mb-3">
              <div class="input-group-prepend">
                <span class="input-group-text">出版社</span>
              </div>
              <input type="text" class="form-control" placeholder="请输入出版社名称" name="publisher" />
            </div>
            <!-- 添加按钮 -->
            <button class="btn btn-dark" type="submit">添加</button>
          </form>
        </div>
      </div>
    </div>
  </div>

  <script src="./lib/form-serialize.js"></script>
  <script src="./lib/axios.js"></script>
  <script>
    // 功能
    // 1. 渲染数据
    // 2. 添加数据
    // 3. 删除数据


    let tbody = document.querySelector('tbody')
    let addForm = document.querySelector('#addForm')
    //功能一. 渲染数据
    function getBooks() {
      axios({
        url:'http://getURL/api/getbooks',
        method:'get',

      }).then(({data:res})=>{
        console.log(res)
        if (res.status !==200){
          alert('图书获取失败')
        }else {
          let arr = res.data.map(function (item) {
            return `<tr>
              <th scope="row">${item.id}</th>
              <td>${item.bookname}</td>
              <td>${item.author}</td>
              <td>${item.publisher}</td>
              <td>
                <button type="button" class="btn btn-link btn-sm btn-del" data-id="${item.id}">删除</button>
              </td>`                              ////为了后续的删除操作,特意在这里加上data-id的自定义属性设置id
          })
          tbody.innerHTML = arr.join('')

        }
      })
    }
    getBooks()

    // 功能二. 渲染数据
    addForm.addEventListener('submit',function (e) {
      e.preventDefault();
      let data  = serialize(this,{hash:true})

      axios({
        url:'http://POSTURL/api/addbook',
        method: 'post',
        data
      }).then(({data:res})=>{
        console.log(res)
        if (res.status !== 201){
          alert(res.msg)
        }else if(res.status === 201){}
          getBooks()
        //清空表单 js自带方法:reset()
        addForm.reset()
      })

    })

    //功能三:删除图书
    //因为列表是动态创建的,所以需要事件委托做法(删除按钮动摇创建)
    //做法:把事件委托给父元素
    //原理:事件冒泡
    //触发条件是谁?
    //自定义属性(规范做法):在标签内部加上 data-自定义属性 = xxx特殊字符
    tbody.addEventListener('click',function (e) {
      if (e.target.classList.contains('btn-del')){
        //if成立 说明拥有 bet-del 类名的删除按钮
        //点击删除按钮的时候,需要自定义属性的值取出来
        let id = e.target.dataset.id
        console.log(id)
        axios({
          url:'http://YOURURL/api/delbook',
          method:'delete',
          params:{
            id
          }
        }).then(({data:res})=>{
          if (res.status === 200){
            getBooks()
          }else {
            alert(res.msg)
          }
          // console.log(res.msg)
        })
      }
    })



  </script>
</body>

扩展与回顾:自定义属性

自定义属性做法:data-自定义属性

在DOM对象上以dataset对象方式操作

<div class="box" data-id="19">
    
</div>
<script>
    let box = document.querySelector('.box')
    console.log(box.dataset.id)
</script>

七、axios 请求方法的别名

7.1 axios的请求简写

为了简化开发者的使用过程,axios 为所有支持的请求方法提供了别名:

  • axios.get(url[, config])
  • axios.delete(url[, config])
  • axios.post(url[, data[, config]])
  • axios.put(url[, data[, config]])
  • axios.patch(url[, data[, config]])

[,config] [,data [,config]]根据实际需求添加内容。

示例

<body>
  <button id="btnGET_1">GET不带参数</button>
  <button id="btnGET_2">GET带参数</button>

  <button id="btnPOST_1">POST不带请求体</button>
  <button id="btnPOST_2">POST带请求体</button>

  <script src="./lib/common.js"></script>
  <script src="./lib/axios.js"></script>

  <script>
    // GET请求接口地址: http://yourURL/get
    // POST请求接口地址: http://yourURL/post

    // ======================== 配置全局请求的根路径 ======================== 


    // axios的简写形式
    let getURL = 'http://yourURL/get'
    let postURL = 'http://yourURL/post'
    // ======================== 发送get请求,不带参数 ======================== 
    qs('#btnGET_1').addEventListener('click', function () {
        axios.get(getURL).then(function ({data:res}) {
          console.log(res)
        })
    })

    // ======================== 发送get请求,带参数 ======================== 
    qs('#btnGET_2').addEventListener('click', function () {
        axios.get(getURL,{
          params:{
            name:'zhangsan'
          }
        }).then(({data:res})=> console.log(res) )
    })

    // ======================== 发送post请求,不带参数 ======================== 
    qs('#btnPOST_1').addEventListener('click', function () {
        axios.post(postURL).then(({data:res})=>console.log(res))
    })

    // ======================== 发送post请求,带参数 ======================== 
    qs('#btnPOST_2').addEventListener('click', function () {
        axios.post(postURL,{
          //这个对象是data,放实体数据
            username:'barbatos',
            password:'123456'
          },{
            timeout:1000,   //config里边可以设置多种内容,比如设置超时时间,超过就取消响应
            params:{
              //带查询参数
              a:1,
              b:2
            }
        }).then(({data:res})=>console.log(res))


    })
  </script>
</body>

7.2 axios设置请求根路径

axios.defaults.baseURL = '请求根路径'

<body>
  <button id="btnGET_1">GET不带参数</button>
  <button id="btnGET_2">GET带参数</button>

  <button id="btnPOST_1">POST不带请求体</button>
  <button id="btnPOST_2">POST带请求体</button>

  <script src="./lib/common.js"></script>
  <script src="./lib/axios.js"></script>

  <script>
    // GET请求接口地址: http://yourURL/get
    // POST请求接口地址: http://yourURL/post

    // ======================== 配置全局请求的根路径 ======================== 


    // axios的简写形式
    axios.defaults.baseURL = 'https://www.luomoe.com:3333'
    // ======================== 发送get请求,不带参数 ======================== 
    qs('#btnGET_1').addEventListener('click', function () {
        axios.get('api/get').then(function ({data:res}) {
          console.log(res)
        })
    })

    // ======================== 发送get请求,带参数 ======================== 
    qs('#btnGET_2').addEventListener('click', function () {
        axios.get('api/get',{
          params:{
            name:'zhangsan'
          }
        }).then(({data:res})=> console.log(res) )
    })

    // ======================== 发送post请求,不带参数 ======================== 
    qs('#btnPOST_1').addEventListener('click', function () {
        axios.post('api/post').then(({data:res})=>console.log(res))
    })

    // ======================== 发送post请求,带参数 ======================== 
    qs('#btnPOST_2').addEventListener('click', function () {
        axios.post('api/post',{
          //这个对象是data,放实体数据
            username:'barbatos',
            password:'123456'
          },{
            timeout:1000,   //config里边可以设置多种内容,比如设置超时时间,超过就取消响应
            params:{
              //带查询参数
              a:1,
              b:2
            }
        }).then(({data:res})=>console.log(res))


    })
  </script>
</body>

7.3 案例

<body>

  <!-- 栅格系统 -->
  <div class="container-fluid">
    <!-- 栅格系统中的一行 -->
    <div class="row">
      <!-- 左侧的表格,占了 8 列 -->
      <div class="col-sm-8">
        <table class="table table-bordered table-striped table-dark table-hover text-center">
          <thead>
            <!-- 表头行 -->
            <tr>
              <th scope="col">Id</th>
              <th scope="col">书名</th>
              <th scope="col">作者</th>
              <th scope="col">出版社</th>
              <th scope="col">操作</th>
            </tr>
          </thead>
          <tbody>
            <!-- 表格中的每一行 -->
            <tr>
              <th scope="row">xxx</th>
              <td>xxx</td>
              <td>xxx</td>
              <td>xxx</td>
              <td>
                <button type="button" class="btn btn-link btn-sm btn-del">删除</button>
              </td>
            </tr>
          </tbody>
        </table>
      </div>

      <!-- 右侧的添加区域,占了 4 列 -->
      <div class="col-sm-4">
        <!-- 添加图书的卡片 -->
        <div class="card text-white bg-secondary sticky-top">
          <div class="card-header">添加新图书</div>
          <form class="card-body bg-light" id="addForm">
            <!-- 书名 -->
            <div class="input-group mb-3">
              <div class="input-group-prepend">
                <span class="input-group-text">书名</span>
              </div>
              <input type="text" class="form-control" placeholder="请输入图书名称" name="bookname" />
            </div>
            <!-- 作者 -->
            <div class="input-group mb-3">
              <div class="input-group-prepend">
                <span class="input-group-text">作者</span>
              </div>
              <input type="text" class="form-control" placeholder="请输入作者名字" name="author" />
            </div>
            <!-- 出版社 -->
            <div class="input-group mb-3">
              <div class="input-group-prepend">
                <span class="input-group-text">出版社</span>
              </div>
              <input type="text" class="form-control" placeholder="请输入出版社名称" name="publisher" />
            </div>
            <!-- 添加按钮 -->
            <button class="btn btn-dark" type="submit">添加</button>
          </form>
        </div>
      </div>
    </div>
  </div>

  <script src="./lib/form-serialize.js"></script>
  <script src="./lib/axios.js"></script>
  <script>
    // 功能
    // 1. 渲染数据
    // 2. 添加数据
    // 3. 删除数据


    let tbody = document.querySelector('tbody')
    let addForm = document.querySelector('#addForm')
    //全局配置
    axios.defaults.baseURL = 'http://www.luomeo.com:3006'
    //1. 渲染数据
    function getBooks() {
      axios.get('/api/getbooks').then(({data:res})=>{
        console.log(res)
        if (res.status !==200){
          alert('图书获取失败')
        }else {
          let arr = res.data.map(function (item) {
            return `<tr>
              <th scope="row">${item.id}</th>
              <td>${item.bookname}</td>
              <td>${item.author}</td>
              <td>${item.publisher}</td>
              <td>
                <button type="button" class="btn btn-link btn-sm btn-del" data-id="${item.id}">删除</button>
              </td>`                              ////为了后续的删除操作,特意在这里加上data-id的自定义属性设置id
          })
          tbody.innerHTML = arr.join('')

        }
      })
    }
    getBooks()

    // 2. 渲染数据
    addForm.addEventListener('submit',function (e) {
      e.preventDefault();
      let data  = serialize(this,{hash:true})
      axios.post('/api/addbook',data).then(({data:res})=>{
        console.log(res)
        if (res.status !== 201){
          alert(res.msg)
        }else if(res.status === 201){}
          getBooks()
        //清空表单 js自带方法:reset()
        addForm.reset()
      })

    })

    //功能三:删除图书
    //因为列表是动态创建的,所以需要事件委托做法(删除按钮动摇创建)
    //做法:把事件委托给父元素
    //原理:事件冒泡
    //触发条件是谁?
    //自定义属性(规范做法):在标签内部加上 data-自定义属性 = xxx特殊字符
    tbody.addEventListener('click',function (e) {
      if (e.target.classList.contains('btn-del')){
        //if成立 说明拥有 bet-del 类名的删除按钮
        //点击删除按钮的时候,需要自定义属性的值取出来
        let id = e.target.dataset.id
        console.log(id)
        axios.delete('/api/delbook',{
          params: {id}
        }) .then(({data:res})=>{
          if (res.status === 200){
            getBooks()
          }else {
            alert(res.msg)
          }
          // console.log(res.msg)
        })
      }
    })



  </script>
</body>

八、了解 XMLHttpRequest 的基本用法

什么是 XMLHttpRequest ?是浏览器内置的一个构造函数

作用:基于 new 出来的 XMLHttpRequest 实例对象,可以发起 Ajax 的请求。

axios 中的 axios.get()、axios.post()、axios() 方法,都是基于 XMLHttpRequest(简称:XHR)封装出来的!

请大家思考:我们能否不用 axios 封装的 Ajax 函数,直接基于 XMLHttpRequest 发起Ajax 请求?答案:完全可以!

8.1 使用 XMLHttpRequest 发起 GET 请求

主要的 4 个实现步骤:

① 创建 xhr 对象

② 调用 xhr.open() 函数

③ 调用 xhr.send() 函数

④ 监听 load 事件

<script>
  // =========================== 目标: 使用xhr发起GET请求 ===========================
  // 接口地址: yourURL/get
  // 请求方式: get

  // 步骤:
  // 1.创建一个xhr对象
  let xhr = new XMLHttpRequest()

  // 2.设置请求方式和请求地址 xhr.open("请求方式", "请求地址")
  xhr.open('get',' yourURL/get')
  //带参数的
    xhr.open('get',' yourURL/get?a=1&b=2')
  // 3.发送请求
  xhr.send()
  // 4.监听load(请求成功)事件
  xhr.addEventListener('load',function () {
    console.log(xhr.response)   //得到json字符串
    console.log(JSON.parse(xhr.response))


  })
</script>

8.2 使用 XMLHttpRequest 发起 POST 请求,并携带请求体数据

当需要携带请求体数据时,需要进行额外的两步操作:

① 在 xhr.open() 之后,调用 xhr.setRequestHeader() 函数,指定请求体的数据格式

② 在 xhr.send() 中,指定要提交的请求体数据

<body>
  <script>
    // =========================== 目标: 使用xhr发起POST请求 ===========================
    //  接口地址: http://psotURL/api/post
    //  请求方式:post

    // 步骤:
    // 1.创建一个xhr对象
    let xhr = new XMLHttpRequest()
    // 2.设置请求方式和请求地址 xhr.open("请求方式", "请求地址")
    xhr.open('post','http://psotURL/api/post')
    // 3.设置请求体对应的编码格式 对应的是key=value&key=value
    xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded')
    // 4.发送请求  send(请求体数据)
    xhr.send('a=1&b=2')
    // 5.监听load(请求成功)事件
    xhr.addEventListener('load',function () {
      console.log(JSON.parse(xhr.response))
    })
  </script>
</body>

8.3 注意与补充

// 请求体数据  
            // 注意: data的值必须是有{},这是错误的认知
            //  data的值是js对象,axios 发送请求体数据的时候,发送的是json格式数据
            //  看请求报文里面的Content-Type的值  application/json
            // data: {
            //     c: 3,
            //     d: 4
            // }
            
            
            // 键值对字符串格式   键=值&键=值
            // data的值也可以是键值对字符串格式,对应的Content-Type的值 application/x-www-form-urlencoded 
            // data: 'c=3&d=4'
            
            
            // data请求体数据也可以是FormData格式数据, 对应的Content-Type的值 multipart/form-data


九、数据交换格式

9.1 JSON 的本质和要求

JSON(全称:JavaScript Object Notation)是一种数据交换格式,它本质上是用字符串的方式来表示对象或数组类型的数据。

用字符串的方式来表示的对象或数组类型的数据,叫做 JSON 数据。

JSON 数据的格式有两种: ① 对象格式 ② 数组格式(JSON的本质是字符串)

对象格式的 JSON 数据,最外层使用 { } 进行包裹,内部的数据为 key: value 的键值对结构。

其中:

① key 必须使用英文的双引号进行包裹

② value 的值只能是字符串、数字、布尔值、null、数组、对象类型(可选类型只有这6 种):
    {
            "quality": 16,
            "format": "mp4",
            "new_description": "360P 流畅",
            "display_desc": "360P",
            "superscript": "",
            "codecs": [
                "avc1.64001E",
                "hev1.1.6.L120.90"
            ]
        }

**数组格式的 JSON 数据:**最外层使用 [ ] 进行包裹,内部的每一项数据之间使用英文的, 分隔。其中:每一项的值类型只能是字符串、数字、布尔值、null、数组、对象这 6 种类型之一。

9.2 JSON的2个方法

调用浏览器内置的 JSON.stringify() 函数,可以把 JS 数据转换为 JSON 格式的字符串(序列化)。

调用浏览器内置的 JSON.parse() 函数,可以把 JSON 格式的字符串转换为 JS 数据(反序列化)。

9.3 JSON 文件

在 JSON 文件中定义 JSON 格式的数据时,要遵守以下的 6 条规则:

属性名必须使用双引号包裹

字符串类型的值必须使用双引号包裹

③ JSON 中不允许使用单引号表示字符串

④ JSON 中不能写注释

⑤ JSON 的最外层必须是对象或数组格式

⑥ 不能使用 undefined函数作为 JSON 的值

9.4 原生xhr发送JSON数据

  <script>
    /*
       目标: 使用xhr发起POST请求 
             接口地址: http://yourURL/api/post
             请求体数据格式为JSON数据
    */

    let obj = {
      name: 'zs',
      age: 20
    }

    // 1. 创建一个xhr对象
    let xhr = new XMLHttpRequest()

    // 2. 设置请求方式和请求地址
    xhr.open('post','http://yourURL/api/post')
    // 3. 设置请求头 中 请求体数据的编码格式
    xhr.setRequestHeader('Content-Type','application/json')
    // 4. 发送请求
    //需要自己来处理(把js对象处理成json数据)
    xhr.send(JSON.stringify(obj))         //序列化
    // 5. 监听load(请求成功)事件
    xhr.addEventListener('load',function () {
      console.log(JSON.parse(xhr.response))    //反序列化
    })
  </script>

十、Promise

10.1 异步函数 和回调函数的说明

异步函数setTimeout setInterval ajax fs.readFile 异步函数的执行,由于是异步的,不会阻塞主线程代码的执行 。

什么是回调函数把一个函数当成参数传递, 将来特定的时机调用, 这个函数就叫回调函数。 一般异步操作,都会用到回调函数。

10.2 promise 的基本使用

promise解决回调地狱。

Promise 三种状态:

  1. pending 进行中
  2. fulfill 成功(已完成)
  3. reject 失败(未完成)

基本用法示例:

<script>
    // =========================== Promise 介绍 =========================== 
    // 把一部代码封装写入搭配Promise种
    //Promise 三种状态:
    // 1. pending 进行中
    // 2. fulfill  成功(已完成)
    // 3. reject  失败(未完成)

    // =========================== Promise 基本语法 ===========================
    // Promise 是个构造函数,new Promise创建了实例对象
    //步骤:
    //  1. 许下诺言
    //  2. 获取诺言的结果

    //1. 许下诺言
    let p = new Promise((resolve,reject)=>{
      //参数世隔函数,把异步操作封装到该函数中
      console.log('当我考研上岸,我就一定好好学习')


      //许诺成功
      //当调用resolve(),会把promise 的状态变成fulfilled,成功(完成)
      // resolve('考上xx大学计算机学院研究生')

      //许诺失败
      //当调用reject(),会把promise 的状态变成rejected,失败(未完成)
      reject('再来一年')
    })
    console.log(p)

 //2. 获取诺言的结果
    // then...catch 方法获取诺言成功和失败的结果
    p.then((res)=>{
      //诺言成功的结果
      console.log(res,'好好学习天天向上')
    }).catch((err)=>{
      //诺言失败的结果
      console.log(err)
    })

  </script>

10.3 promise状态凝固

<script>
  // =========================== 演示 promise 的状态是凝固的 ===========================
  // 一旦当promise状态发生改变,状态就会被凝固
  let p = new Promise((resolve, reject)=>{
    console.log('许诺了')

    resolve('许诺成功')  //fulfilled
    reject('许诺失败')   // rejected
  })
  console.log(p)


</script>

尽管后面跟了reject('许诺失败'),但是promise先调用了resolve('许诺成功'),这个promise的状态会凝固为完成状态,即fulfilled成功状态。如果这两个方法调用顺序互换,promise地状态则会凝固为rejected失败状态。

练习:

<script>
  // =========================== 需求 ===========================
  // 点击按钮,来判断分数是否过线,如果超过,准备复试,如果没有的话,再来一年

  document.querySelector("button").onclick = function () {
    // 点击按钮的时候,许下诺言
    let p = new Promise((resolve, reject)=>{
      let score = parseInt(Math.random() * 500)
      console.log(score)
      if (score >= 273 ){
        resolve('准备复试,好好学习')
      }else {
        reject('没过线,再来一年')
      }
    })

    p.then((res)=>{
      console.log(res)
    }).catch((err)=>{
      console.log(err)
    })
  }
</script>

10.4 then返回值

promise链式编程:如果上一个 .then 中返回了一个新的 promise 对象, 则可以交给下一个.then 继续处理在 Promise 的链式操作中如果发生了错误,可以使用 .catch 方法进行捕获和处理

<script>
  // =========================== Promise的链式调用 ===========================
  // then方法的返回值
  // 如果上一个.then() 方法中返回了一个新的Promise 实例对象,则可以通过下一个.then() 继续进行处理

  let p = new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve("结果1");    //状态 fulfilled 成功
    }, 2000);
  })

  // 获取到成功的结果
  p.then(res => {
    console.log("第一个then执行");
    console.log(res); // 
      
    // 再new一个promise  
    return new Promise((resolve, reject)=>{
      setTimeout(() => {
        resolve("结果2");    //状态 fulfilled 成功
      }, 2000);
    })
  }).then((res)=>{
    console.log('第二个then执行')
    console.log(res)
  })

</script>

10.5 优化

回调地狱:嵌套太多,头晕眼花

<script>
    // =========================== 回调地狱 ===========================

    // 需求:延时2s秒输出红色,在延时1s输出黄色,之后在延时3s输出绿色
    // 回调函数的嵌套 ==> 回调地狱

    setTimeout(function () {
        console.log('is red')

        // 在开启延时器
        setTimeout(() => {
            console.log('is yeelow')

            setTimeout(() => {
                console.log('is green')

                setTimeout(function () {
                    console.log('is ok1')

                    // 在开启延时器
                    setTimeout(() => {
                        console.log('is ok2')

                        setTimeout(() => {
                            console.log('is ok3')


                        }, 3000)

                    }, 1000)

                }, 2000)


            }, 3000)

        }, 1000)

    }, 2000)
</script>

使用then返回值实现promise链式操作

<script>
    // =========================== Promise解决回调地狱 ===========================
    // setTimeout(function () {
    //     console.log('is red')

    //     // 在开启延时器
    //     setTimeout(() => {
    //         console.log('is yeelow')

    //         setTimeout(() => {
    //             console.log('is green')


    //         }, 3000)

    //     }, 1000)

    // }, 2000)


    new Promise((resolve, reject) => {
        setTimeout(function () {
            resolve('is red') // 状态 fulfilled 成功  成功结果 is red
        }, 2000)
    }).then((res) => {
        console.log(res) // is red

        // return 新的promise 实例对象
        return new Promise((resolve, reject) => {
            setTimeout(function () {
                resolve('is yellow') // 状态 fulfilled 成功  成功结果 is yellow
            }, 1000)
        })
    }).then((res) => {
        console.log(res) // is yellow

        // return 新的promise 实例对象
        return new Promise((resolve, reject) => {
            setTimeout(function () {
                resolve('is green') // 状态 fulfilled 成功  成功结果 is green
            }, 3000)
        })
    }).then((res) => {
        console.log(res)
    })
</script>

优化

<script>
    // =========================== 优化Promise解决回调地狱 ===========================
    // 将创建 promise 对象的过程,封装到一个函数中,需要promise对象,调函数即可
    function fn(color,time) {
        return new Promise((resolve, reject)=>{
            setTimeout(function () {
                let randomNum = Math.random()
                if(randomNum >=0.5){
                    resolve(color)
                }else{
                    catch('失败')
                }
                
            },time)
        })
    }

    fn('is red',2000).then((msg)=>{
        console.log(msg)
        return fn('is yello',1000)         //这里的return不可以省略
    }).then((res)=>{
        console.log(res)
        return fn('is yello',3000)
    }).then((res)=>{
        console.log(res)
    }).catch((err)=>{
        conlog.log(err)
        })
</script>

10.6 promise的静态方法

all方法

Promise.all() 等待机制

语法:Promise.all([ promise1, promise2, ... ]).then( ... )

特征:发起并行的promise异步操作,会等待所有的异步操作都完成,才会走 .then

race方法

Promise.race() 赛跑机制

语法:Promise.race([ promise1, promise2, ... ]).then( ... )

特征:发起并行的promise异步操作,只要任何一个异步操作完成,就会走 .then

示例:

<script>
  // =========================== Promise的静态方法 ===========================

  // Promise.all([promise1, promise2, ... ]).then(... )
  //  参数是个数组,数组的每一项是promise对象,可以有若干个
  // 会等待所有的异步操作都完成,才会走.then

  // Promise.race([promise1, promise2, ... ]).then(... )
  // 只要任何一个异步操作完成,就会走.then





  // 演示
  // 获取图书接口       axios.get('/api/getbooks')
  // 获取新闻接口       axios.get('/api/news')
  // 测试get请求接口    axios.get('/api/get')

  // 设置接口的根路径
  axios.defaults.baseURL = 'http://www.luomoe.top:3006'


  // console.log(axios.get('/api/getbooks')) // promise实例对象
  // console.log(axios({})) // promise对象
  // console.log(axios.post()) // promise对象

  // axios.get('/api/getbooks').then(({ data: res }) => { })
 
  // 会等待所有的异步操作都完成,才会走.then  
  Promise.all([axios.get('/api/getbooks1232111'), axios.get('/api/news'), axios.get('/api/get')]).then((res) => {
    console.log(res) // 数组,里面每一项是axios请求成功的结果
  }).catch((err) => {
    // 只要有一个promise对象失败了,走到catch这
    console.log('在catch了', err)
  })

    
    
   //只要任何一个异步操作完成,就会走.then 
   Promise.race([axios.get('/api/getbooks'), axios.get('/api/news'), axios.get('/api/get')]).then((res) => {
     console.log(res) // 最先成功的结果(不是固定的)
   }).catch((err) => {
    // 所有的promise对象失败了,走到catch这
    console.log('在catch了', err)
  })
</script>

10.7、案例 - 封装自己的 Ajax 函数

<body>
  <button id="btnGet">发送get请求</button>
  <button id="btnPost">发送post请求</button>

  <script>
    //  GET   http://www.liulongbin.top:3009/api/get
    //  POST  http://www.liulongbin.top:3009/api/post

    //封装myAxios() ajax 异步操作====> 需要promise来封装Ajax异步代码
    function myAxios({method = 'get',url,params,data}) {      //method默认为get
      return new Promise((resolve, reject)=>{
          //这里写Ajax异步代码
         //发送请求+ 处理相应
        //原生xhr
        //1. 创建出新的xhr对象
        //2. 方法、url地址 ==>open()
        //3. send(请求体数据) 发送请求

        //1.
        let xhr = new XMLHttpRequest()

        //2.
        if(params){
          let arr = []
          for (let key in params) {
            if (typeof params[key]=='string'){
              //如果是params[key]字符串,需要转码
              console.log(params[key])
              params[key] = encodeURI(params[key])
              console.log(params[key])
            }
            arr.push(`${key}=${params[key]}`)
          }
          url +=`?${arr.join('&')}`
          console.log(url)
        }


        //优化,统一转为小写
        method = method.toLowerCase()

        xhr.open(method,url)

        //3.
        // xhr.send(请求体数据)
        // 需要判断,请求方式不是get,而且有data的话,就来处理data数据
        if (method === 'get'){
          xhr.send()
        }else {

          if (data){

            xhr.setRequestHeader('Content-Type','application/json')
            xhr.send(JSON.stringify(data))
          }

        }


        //4. 处理相应
        xhr.addEventListener('load',function () {
          //服务器响应的结果: 把promise的状态fulfilled
          resolve(JSON.parse(xhr.response))
        })

        xhr.addEventListener('error',function () {
          reject('网络异常,请求失败')
        })
      })
    }

    // 发送get请求
    document.querySelector('#btnGet').addEventListener('click', function () {
        myAxios({
          url:'http://yourURL/api/get',
          method:'GET',
          params:{
            name:'张三',
            age:16
          }
        }).then(function (res) {
            console.log(res)
          }).catch((err)=>{
          console.log(err)
        })

    })

    // 发送post请求
    document.querySelector('#btnPost').addEventListener('click', function () {
      myAxios({
        url:'http://yourURL/api/post',
        method:'post',
        params:{
          name:'zs',
          age:16
        },
        data:{
          a:1,
          b:2
        }
      }).then(function ({data:res}) {
        console.log(res)
      })
    })

  </script>
</body>

导航:

<body>
  <div class="container">
    <ul class="top">
      <li>
        <a href="javascript:;">首页</a>
        <ul class="sub">
          <li>
            <a href="javascript:;">
              <span>砂锅厨具</span>
              <img src="https://yanxuan.nosdn.127.net/3102b963e7a3c74b9d2ae90e4380da65.png?quality=95&imageView" alt="">
            </a>
          </li>
        </ul>
      </li>
    </ul>
  </div>

  <script src="https://static.cdn.luomoe.com/JavaScript_package/axios.js"></script>
  <script src="myAxios.js"></script>

  <script>
    // 具体看请求文档
    // 请求根路径为 http://yourURL.net
    let box = document.querySelector('.top')

    //获得一级目录
    myAxios({
      url:'http://yourURL.net/api/category/top',
      method:'get',
    }).then((res)=>{

      //继续发送请求,请求二级导航数据
      //map遍历数组并得到新数组
      let arr = res.data.map(item =>{
        return myAxios({
          url: 'http://yourURL.net/api/category/sub',
          method: 'get',
          params:{
            id: item.id
          }
        })
      })
      // console.log(arr)

      //如何获取所有的二级导航数据
      return Promise.all(arr)
    }).then((res)=>{

      box.innerHTML = res.map(items =>{

        //先处理二级导航
        let subStr = items.data.children.map(sub =>{
          return `
               <li>
                    <a href="javascript:;">
                      <span>${sub.name}</span>
                      <img src="${sub.picture}" alt="">
                    </a>
                </li>
          `
        }).join('')


        // 一级导航和二级导航
        return `
            <li>
              <a href="javascript:;">${items.data.name}</a>
              <ul class="sub">
                    ${subStr}
              </ul>
            </li>
        `
      }).join('')


    })


    // box.addEventListener()




  </script>

十一、async 和 await

11.1 基本使用

虽然我们用promise封装,解决了回调嵌套的问题=> 改链式了

但是多层嵌套时,链式的可维护性,可阅读性,也不太高,可以优化么? 需要学习 async 和 await

<script src="./lib/axios.js"></script>
<script>
    // async await基本使用
    //  异步终极解决方案:async  await
    //  是一对关键字,需要配合使用


    // ============================== 核心 ==============================
    // 1. async用于修饰一个函数, 表示一个函数是异步的
    //     如果async函数内没有await, 那么async没有意义的, 全是同步的内容
    //     只有遇到了await开始往下, 才是异步的开始
    // 2. await 要用在 async 函数中
    // 3. await 后面一般会跟一个promise对象,  await会阻塞async函数的执行,
    //     直到等到 promise成功的结果(resolve的结果)
    // 4. await 只会等待 promise 成功的结果, 如果失败了会报错, 需要 try catch


    // ============================== 核心1 ==============================
    // 1. async用于修饰一个函数, 表示一个函数是异步的
    //     如果async函数内没有await, 那么async没有意义的, 全是同步的内容
    //     只有遇到了await开始往下, 才是异步的开始

    // async function fn() {
    //     let num = await 1 + 10 // await 后面如果是表达式 ,await的结果就是该表达式的值
    //     console.log(num)  // 11
    // }
    // fn()
    // console.log(2)


    // ============================== 核心2 ==============================
    // await 要用在 async 函数中

    // function fn() {
    //     // error: await is only valid in async functions
    //     let num = await 1 + 10
    //     console.log(num)  // 11
    // }
    // fn()
    // console.log(2)


    // ============================== 核心3 ==============================
    // await 后面一般会跟一个promise对象,  await会阻塞async函数的执行,
    //     直到等到 promise成功的结果(resolve的结果)

    // axios.get('http://www.liulongbin.top:3006/api/getbooks')
    // axios.get('http://www.liulongbin.top:3006/api/getbooks').then(({data: res}) => {})

    // async function fn() {
    //     let { data: res } = await axios.get('http://www.yourURL:3006/api/getbooks')
    //     console.log(res) // axios请求成功的结果(图书)
    
   //先get完上面↑的promise对象,再完成下面↓的promise对象
    
    //     // 获取新闻列表数据
    //     let { data: resNews } = await axios.get('http://yourURL/api/news')
    //     console.log(resNews) // axios请求成功的结果(新闻)
    // }
    // fn()



    // ============================== 核心4 ==============================
    //   await 只会等待 promise 成功的结果, 如果失败了会报错, 需要 try catch

    // try catch 语法
    // try{
    //     // 放可以会出错的代码
    // }catch(e){
    //     // 一旦try中的代码报错了,会被catch捕获到,其中参数e就是错误消息
    // }

    async function fn() {

        try {
            let { data: res } = await axios.get('http://yourURL/api/getbooks')
            console.log(res) // axios请求成功的结果(图书)
        } catch (e) {
            console.log(e) // 错误消息
            alert('获取失败,请稍后重试~~~')
        }
    }
    fn()
</script>

11.2 解决回调地狱

<script>
    
    function fn(color,time) {
        return new Promise((resolve, reject)=>{
            setTimeout(function () {
                resolve(color)
            },time)
        })
    }

    // 使用async await 解决回调地狱问题
    async function demo() {

        //await 后面跟promise对象
        let color1 =await fn('is red',2000)
        console.log(color1)

        let color2 =await fn('is yello',3000)
        console.log(color2)

        let color3 =await fn('is green',1000)
        console.log(color3)
    }
    demo()   //记得调用async函数

</script>

11.3 优化

//立即执行函数:
//  ;(function(){})

;(async function () {

        //await 后面跟promise对象
        let color1 =await fn('is red',2000)
        console.log(color1)

        let color2 =await fn('is yello',3000)
        console.log(color2)

        let color3 =await fn('is green',1000)
        console.log(color3)
    })

;(async ()=> {

        //await 后面跟promise对象
        let color1 =await fn('is red',2000)
        console.log(color1)

        let color2 =await fn('is yello',3000)
        console.log(color2)

        let color3 =await fn('is green',1000)
        console.log(color3)
    })

导航栏的改写:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Document</title>
  <link rel="stylesheet" href="./css/index.css" />
</head>

<body>
  <div class="container">
    <ul class="top">
      <!-- <li>
          <a href="javascript:;">首页</a>
          <ul class="sub">
            <li>
              <a href="javascript:;">
                <span>砂锅厨具</span>
                <img src="https://yanxuan.nosdn.127.net/3102b963e7a3c74b9d2ae90e4380da65.png?quality=95&imageView" alt="" />
              </a>
            </li>
          </ul>
        </li> -->
    </ul>
  </div>
  <script src="./lib/axios.js"></script>
  <script>
    let box = document.querySelector('.top')

    // 配置请求根路径
    axios.defaults.baseURL = 'http://yourURL.net';
    async function getNav() {
      //获得所有一级导航栏的信息,获得一级导航的id,然后由此获得二级导航的信息
      let {data:{data:res}} = await axios.get('/api/category/top')
      console.log(res)

      //获得所有二级导航栏的信息
      let topArr = res.map(items =>{
        return axios.get('/api/category/sub',{params:{id:items.id}})
      })
      let subRes = await Promise.all(topArr)      //得到所有二级导航栏的信息
      console.log(subRes)
      //遍历得到的二级导航栏信息数组,并插入导航栏中
      box.innerHTML = subRes.map(items=>{
        //先处理二级导航栏的信息
        let subStr = items.data.data.children.map(sub=> {
          return `
               <li>
                    <a href="javascript:;">
                      <span>${sub.name}</span>
                      <img src="${sub.picture}" alt="" />
                    </a>
                </li>
          `
        }).join('')
        return `
            <li>
              <a href="javascript:;">${items.data.data.name}</a>
              <ul class="sub">
                    ${subStr}
              </ul>
            </li>
        `
      }).join('')




    }
    getNav()
  </script>
</body>

</html>

十二、宏任务微任务

12.1 Event Loop 事件循环队列

12.2 宏任务 和 微任务

宏任务

主代码块, 定时器, 延时器的代码内容等都属于宏任务

特征:上一个宏任务执行完, 才会考虑执行下一个宏任务

注意点:

  1. js 主线程遇到异步的内容, 交给浏览器去等待, 不会阻塞主线程
  2. 一定是满足条件后的任务, 才会被添加到任务队列

微任务

当前宏任务执行完,在下一个宏任务开始之前需要执行的任务

promise 的 .then .catch 中的代码都属于微任务

 // 宏任务:
    //  整个script标签代码
    //  定时器 延时器
    //  ajax代码

    // 微任务
    //  promise对象的then catch 代码

    // 宏任务 微任务 如何执行的
    //  1. 先执行宏任务
    //  2. 当宏任务执行结束了,再执行这个红任务的所有微任务(如果没有微任务,就不需要执行)
    //  3. 继续执行下一个宏任务了