初识ajax

189 阅读8分钟

1.前后端交互

  • 其实就是一个前后端通讯,是我们在开发中,必不可少的一个技能
    • 目前我们用到的技术就是ajax
    • 流程为 在前端开发中,在某一个时刻(页面首次打开渲染的时候,或者点击下一页需要新的数据的时候)
      • 此时通过ajax向后端(服务器)发送一个请求,拿到所谓的数据
      • 发送请求需要传一些参数(就是告诉后端你要什么东西),如果你不知道,那么问你的组长要一个'接口文档'

2.实现一个ajax请求

    // 实现一个ajax请求
    // 1. 创建一个ajax对象
    const xhr = new XMLHttpRequest()
    // 2. 配置ajax对象
    // xhr.open('请求的方式(不区分大小写)', '请求的地址', 一个布尔值(true/false))
    xhr.open('get', 'http://localhost:8888/test/first', true)
    // 3. 发送请求
    xhr.send()
    // 4. 接收响应
    xhr.onload = function() {
      // console.log('现在后端已经给我们返回了 我们想要的数据了')
      console.log(xhr.responseText)
    }

3.ajax的异步问题

  • ajax是否异步 第二步配置的第三个参数决定的,也就是那个布尔值
    • 默认为true 代表的是开启异步,如果传递的是false 代表关闭异步开启同步
    • 同步和异步的差别
        1. 创建一个ajax对象 (同步代码)
        1. 配置ajax对象 (同步代码,但是第三个参数决定了 下一步是否为异步)
        1. 发送请求 (根据上一步的配置,才能看出是否为异步)
        1. 接收响应 (同步代码)
    • 如果传递的是true或者没有传递,那么为异步,此时的运行流程
        1. 创建一个对象
        1. 配置对象
        1. 发送请求
        1. 接收响应
        1. 响应完成了
    • 如果传递的是false,那么为同步,此时的运行流程
        1. 创建一个对象
        1. 配置对象
        1. 发送一个请求,等待请求完成后,开始执行后边的代码
        1. 接收响应(前边三步已经把这个请求完全的运行结束了,所以此时不可能再触发这个函数了)
    • 如果传递的是false,那么为同步,此时的运行流程
      • 1.创建一个对象
        1. 配置对象
        1. 接收响应, 等到请求完成的时候, 会触发
        1. 发送一个请求,等待请求完成后,开始执行后边的代码
    • 如果传递的是异步,可以按照1234的流程书写(1243也可以)
    • 如果传递的是同步,必须按照1243的流程书写
    • 所以在开发的时候,为了方便起见,一般都会书写为1243
    // 1. 创建一个ajax对象
    const xhr = new XMLHttpRequest()
    // 2. 配置ajax对象
    // xhr.open('请求的方式(不区分大小写)', '请求的地址', 一个布尔值(true/false))
    xhr.open('get', 'http://localhost:8888/test/first', false)
    // 4. 接收响应
    xhr.onload = function() {
      // console.log('现在后端已经给我们返回了 我们想要的数据了')
      console.log(xhr.responseText)
    }
    // 3. 发送请求
    xhr.send()

4.http传输协议(了解)

  • 还有一个协议 https, 相对于http 安全一点点
  • 根据传输协议规定, 必须是由前端向后端发送请求,发送请求的时候如果要携带一些参数,必须是字符串格式的
      1. 建立连接
      • 浏览器和服务器 建立一个连接
      1. 发送请求
      • 要求前端必须以'请求报文'的形式发送;
      • 请求报文 由浏览器进行组装,我们只需要提供对应的信息即可,比如说:请求的方式,请求的地址,请求需要的参数
      1. 接收响应
      • 要求后端必须以'响应报文' 的形式返回
      • 响应报文内有一个东西叫做响应状态码
      1. 断开连接
      • 浏览器和服务端的连接断开
    • 响应状态码
      • 100~199 表明连接还在继续
      • 200~299 表明连接各种成功 但现在只会返回一个200
      • 300~399 表明请求重定向
      • 400~499 表明请求失败 但现在只会看到一些 403 404 401 400 一般4开头的是前端的问题
      • 500~599 表明服务端出错 跟前端无关,是后端的问题

5.ajax的状态码

  • 通过一个数字,表明ajax当前运行到哪一步了
    • 0: ajax 创建成功
    • 1: 当前ajax 配置成功
    • 2: 当前ajax 发送成功
    • 3: 当前浏览器正在解析服务端返回给我们的内容,
      • 如果返回的内容少这一步基本能接收完
      • 如果返回的内容很多这一步是接收的不完整的
    • 4: 表明 浏览器 已经把服务端返回的内容 全都解析完毕了~
    // 1. 创建一个ajax对象
    const xhr = new XMLHttpRequest()
    console.log(xhr.readyState) //0
    // 2. 配置ajax对象
    xhr.open('get', 'http://localhost:8888/test/first')
    console.log(xhr.readyState) //1
    // 4. 配置接收响应的函数
    // xhr.onload = function() {
    //   console.log(xhr.responseText)
    // }
    // 3. 发送请求
    xhr.send()

    // 配置接收响应的函数
    xhr.onreadystatechange = function() {
      // console.log(xhr.readyState)
      if(xhr.readyState === 4) {
        console.log('当前浏览器已经完全解析完毕 返回的数据', xhr.responseText)
      }
    }

6.请求方式的区别

  • get 偏向于获取的语义 (商品列表数据,用户详情,商品详情)
    • delete 偏向于获取的语义 (删除某一个内容)
  • post 偏向于修改的语义 (修改用户名,修改密码)
    • put 偏向于修改的语义 (修改库存, 修改收藏数量)
    • 等等...
  • 现在 市面公司中常用的方式只有两个,get/post
  • 请求方式不同,会导致传参的方式不同,除此之外对我们前端来说没有区别
    • => get: 直接将需要传递的参数拼接在路径后即可,注意使用?间隔
      • http://localhost:8888/test/first?key=value
      • http://localhost:8888/test/first?key=value&key2=value2
    • => post: 也是需要传递字符串,只不过不再放在地址路径后,而是放在请求体内书写(其实就是xhr.send())
      • 在传参的时候还需要配置一个请求头中的属性 content-type
      • content-type 赋值的时候,还要区分我们传递的是普通字符串,还是json格式的字符串
      • 普通字符串:xhr.setRequestHeader('content-type', 'application/x-www-form-urlencoded')
      • json字符串: xhr.setRequestHeader('content-type', application/json)

7.测试请求

    // 测试请求2
    // 创建一个ajax对象
    const xhr = new XMLHttpRequest()
    // 2. 配置ajax对象
    xhr.open('get', 'http://localhost:8888/test/second')
    // 4. 接收响应
    xhr.onload = function() {
      const res = JSON.parse(xhr.responseText)
      console.log(res)
    }
    // 3. 发送请求
    xhr.send()

    // 测试请求3
    // 创建一个ajax对象
    const xhr = new XMLHttpRequest()
    // 2. 配置ajax对象
    xhr.open('get', 'http://localhost:8888/test/third?name=zhangsan&age=18')
    // 4. 接收响应
    xhr.onload = function() {
      const res = JSON.parse(xhr.responseText)
      console.log(res)
    }
    // 3. 发送请求
    xhr.send()

    // 测试请求4
    /**
     * application/x-www-form-urlencoded 这是post格式传参时需要给content-type的属性值,这样传递表明需要的是普通字符串
     * 如果是json格式的字符串,需要传递为application/json
     * */ 
    // 创建一个ajax对象
    const xhr = new XMLHttpRequest()
    // 2. 配置ajax对象
    xhr.open('post', 'http://localhost:8888/test/fourth')
    // 4. 接收响应
    xhr.onload = function() {
      const res = JSON.parse(xhr.responseText)
      console.log(res)
    }
    // post 请求传参时需要配置content-type(请求头内的属性)
    xhr.setRequestHeader('content-type', 'application/x-www-form-urlencoded')
    // 3. 发送请求
    xhr.send('name=张三&age=18')

8.分页ajax版

    <!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>
    <style>
        * {
            margin: 0;
            padding: 0;
        }

        .header,
        .footer {
            width: 1200px;
            margin: 0 auto;
            background-color: skyblue;
            display: flex;
            justify-content: center;
            align-items: center;
            font-size: 50px;
            height: 120px;
            color: #fff;
        }

        .footer {
            height: 300px;
        }

        ul,
        li {
            list-style: none;
        }

        ul {
            width: 1200px;
            display: flex;
            flex-wrap: wrap;
            margin: 0 auto;
            justify-content: space-between;
            padding-top: 10px;
        }

        li {
            width: 290px;
            border: 1px solid #333;
            margin-bottom: 10px;
            padding: 5px;
            box-sizing: border-box;
        }

        li>img {
            width: 278px;
            display: block;
        }

        .pagination {
            width: 1200px;
            margin: 10px auto;
            height: 50px;
            display: flex;
            align-items: center;
        }

        .pagination>.prev,
        .pagination>.next {
            width: 50px;
            height: 30px;
            cursor: pointer;
            background-color: orange;
            font-size: 24px;
            color: #fff;
            display: flex;
            justify-content: center;
            align-items: center;
        }

        .pagination>.disable {
            cursor: not-allowed;
            background-color: #ccc;
        }

        .pagination>.total {
            font-size: 30px;
            font-weight: 700;
            margin: 0 20px;
        }

        .pagination>select {
            font-size: 22px;
            padding-left: 20px;
            margin-left: 30px;
        }
    </style>
</head>
<body>
    <div class="header">顶部导航</div>
    <div class="pagination">
        <span class="prev">&lt;</span>
        <span class="total">1 / 100</span>
        <span class="next">&gt;</span>
        <select>
            <option value="4">4</option>
            <option value="8">8</option>
            <option value="12">12</option>
            <option value="16">16</option>
        </select>
    </div>
    <ul></ul>
    <div class="footer">底部导航</div>
    <script>
        // 0. 获取元素
        var oUl = document.querySelector('ul')  // ul 标签, 内部放 商品的 li
        var total = document.querySelector('.total')    // 页码标签
        var prev = document.querySelector('.prev')  // 上一页按钮
        var next = document.querySelector('.next')  // 下一页按钮
        var oSelect = document.querySelector('select')  // 选择框
        // 0. 准备变量
        var currentNum = 1  // 默认当前页 为 第 1 页
        var pageSize = 4    // 默认打开时 每页展示 4 条数据
        var totalNum = 0    // 记录一下总页码
        // 0.向后端发送请求,拿到对应的数据
        function myAjax() {
            // 0.1 创建一个ajax对象
            const xhr = new XMLHttpRequest()
            // 0.2 配置ajax对象
            xhr.open('get', `http://localhost:8888/goods/list?current=${currentNum}&pagesize=${pageSize}`)
            // 0.3 配置接收响应的函数
            xhr.onload = function() {
                const res = JSON.parse(xhr.responseText)
                console.log(res.list)
                totalNum = res.total
                myFn(res.list)
            }
            // 0.4 发送请求
            xhr.send()
        }
        // 首次打开页面的时候,发送请求
        myAjax()
        // 0. 准备一个渲染函数
        function myFn(list) {
            console.log('myFn========',list)
            // var newList = list.slice((currentNum - 1) * pageSize, currentNum * pageSize)
            oUl.innerHTML = list.reduce(function (prev, item) {
                return prev + `
                    <li>
                        <img src="${item.img_big_logo}" alt="">
                        <p>${item.title}</p>
                        <p>城市: ${item.city}</p>
                        <p>地址: ${item.address}</p>
                        <p>价格: ${item.current_price}</p>
                        <p>时间: ${item.showTime}</p>
                    </li>`
            }, '')
            // 0.2 调整页码信息
            // totalNum = Math.ceil(list.length / pageSize)    // 通过向上取整, 计算总页码
            total.innerHTML = `${currentNum} / ${totalNum}`
            // 0.3 处理按钮
            // 0.3.1 如果当前在 第一页 禁用上一页按钮 (添加类名 disable)
            currentNum === 1 ? prev.classList.add('disable') : prev.classList.remove('disable')
            // 0.3.2 处理下一页按钮
            currentNum === totalNum ? next.classList.add('disable') : next.classList.remove('disable')
        }
        // 2. 点击下一页
        next.onclick = function () {
            if (currentNum === totalNum) return // 如果是在最后一页, 那么拦截函数执行
            // 如果执行到这个位置, 当前页一定不是最后一页
            currentNum++
            myAjax()
        }
        // 3. 点击上一页
        prev.onclick = function () {
            if (currentNum === 1) return    // 如果是在第一页, 那么拦截函数执行
            // 如果执行到这个位置, 当前页一定不是第一页
            currentNum--
            myAjax()
        }
        // 4. 改变每页展示数据
        oSelect.onchange = function () {
            // 当选择框内部的 options 改变的时候执行
            pageSize = oSelect.value
            myAjax()
        }
    </script>
</body>
</html>

9.封装ajax

  • 小问题: 如果让你封装,你需要处理些什么?比如参数需要些什么?
  • 参数:
      1. 请求的方式: 选填,默认值为get
      1. 请求的地址: 必填; 形参名:url
      1. 请求为同步还是异步:选填,默认值为true;形参名 async
      1. 请求需要携带参数:选填,默认为'';形参名data
  • 返回值:
    • 需要返回一个promise对象,后续可以通过 .then 或者 async/await去使用
    // function myAjax(type = 'get', url, async = true, data = '') {}
    // myAjax('post', 'http:xxx', false, 'name=zhangsan')
    function myAjax(options) {}
    myAjax({
      url: 'http:xxx',
      async: false,
      data: 'name=zhangsan'
    })
    
    // 0.向后端发送请求,拿到对应的数据
    // function myAjax() {
    //   // 0.1 创建一个ajax对象
    //   const xhr = new XMLHttpRequest()
    //   // 0.2 配置ajax对象
    //   xhr.open('get', `http://localhost:8888/goods/list?name=zhangsan&age=`)
    //   // 0.3 配置接收响应的函数
    //   xhr.onload = function () {
    //     const res = JSON.parse(xhr.responseText)
    //     console.log(res)
    //   }
    //   // 0.4 发送请求
    //   xhr.send()
    // }
    // // 首次打开页面的时候,发送请求
    // myAjax()

10.封装ajax

    function myAjax(options) {
      // 1. 验证参数中的url必传
      // console.log(options.url)
      if(options.url === undefined) throw new Error('参数中缺少必传项 url')// 返回一个错误
      //1.1 参数格式验证,拿请求方式举例:type 只接受 undefined 或者 string两种类型
      if(!(options.type === undefined || typeof(options.type) === 'string')) {
        // console.log('如果本行代码输出,说明传递的type 不是undefined或者string')
        throw new Error('参数中type值的类型必须为 string')
        // throw new Error('参数中type值的类型仅支持 string')
      }
      // 1.2 补全剩余参数的格式校验
      if(!(options.data === undefined || typeof(options.data) === 'string')) {
        throw new Error('参数中type值的类型必须为 string')
      }
      if(!(options.async === null || options.async === null || typeof(options.async) === 'boolean')) {
        throw new Error('参数中type值的类型必须为 string')
      }

      // 2. 封装默认值
      const _options = {
        url: options.url,
        type: options.type || 'get',
        data: options.data || '',
        // 控制检测符,该符号的特点 只会在 左侧的值为空值的时候返回右侧,比如左侧为null,undefined
        async: options.async ?? true
      }
      // console.log('如果代码执行到这个位置,可以发送请求了')
      // 请求的参数还没处理,后续需要继续完善
      const xhr = new XMLHttpRequest()
      xhr.open(_options.type,_options.url,_options.async)
      xhr.onload = function() {
        console.log(xhr.responseText)
      }
      xhr.send()
    }
    myAjax({
      url: 'http://localhost:8888/test/first',
      type: 'get',
      async: false,
      data: 'name=zhangsan'
    })