Ajax学习

Ajax------异步请求

优点:异步,不需要刷新获取数据,结合相关事件可以实现局部刷新

缺点:

  • 没有浏览历史,不能回退(history)
  • 存在跨域问题(浏览器同源问题)
  • 局部刷新获得的资源数据是不能被爬虫获得(对SEO不友好)

Ajax是基于Http协议进行数据传输

浏览器与服务器之间数据交互详细过程见:https://xhx.huagecloud.top/archives/ji-suan-ji-wang-luo-------http-xie-yi

这里详细补充TCP三次握手建立连接后是如何进行用http请求进行数据交互的(一般情况就是一来一回)

请求报文(来服务器)

  • 行 post /s?id=666 HTTP/1.1 方法 路径(url参数) 协议(版本)

  • Host: baidu.com 请求域名

    Cookie: name=sikara

    Content-Type: application/x-www-form-urlencoded 告知浏览器请求体参数的类型

    User-Agent: chrome 83

  • 空行

  • (请求)体 a=123&b=1235

响应报文(回浏览器)

  • 行 HTTP/1.1 200 OK 协议版本 响应状态码 响应状态字符串

  • Content-Type: text/html;charset=utf-8 对响应体进行相关描述

    content-encoding: gzip

    content-length: 2048

  • 空行

  • 响应体

    <html lang="en">
    <head>
    <title>Document</title>
    </head>
    <body>
      <h1>sikara</h1>
    </body>
    </html>
    
## 实践代码

浏览器

​```html
<!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">
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
    <title>Document</title>
</head>
<body>
    <button id="get">get</button>
    <button id="post">post</button>
    <button id="pa">pa</button>
    <button id="json">json</button>
    <button id="delay">delay</button>
    <button id="axios">axios</button>
    <div></div>
</body>

<script>
    let get = document.querySelector('#get');
    let post = document.querySelector('#post');
    let pa = document.querySelector('#pa');
    let json = document.querySelector('#json');
    let delay = document.querySelector('#delay');
    let div = document.querySelector('div');
    let axio = document.querySelector('#axios');
    

    get.onclick = function(){
        let xhr = new XMLHttpRequest();

        // xhr.open('get','http://localhost:80/a')
        xhr.open('get','http://localhost/a?asd=22&wa=35')
        
        //get请求体默认undefined
        xhr.send()
        xhr.onreadystatechange = function(){
            //服务端返回了所有的结果
            if(xhr.readyState === 4){
                //2xx都是成功
                if(xhr.status >=200 && xhr.status <300){
                    //状态码
                    console.log(xhr.status);
                    //状态字符串
                    console.log(xhr.statusText);
                    //所有响应头
                    console.log(xhr.getAllResponseHeaders());

                    div.innerHTML = xhr.response
                }
            }
        }
    }

    pa.onclick = function(){
        let xhr = new XMLHttpRequest();

        //添加时间戳,保证每次请求不同且最新
        xhr.open('get','http://localhost/c?t='+Date.now())
        
        //get请求体默认undefined
        xhr.send('21/26')

        xhr.onreadystatechange = function(){
            //服务端返回了所有的结果
            if(xhr.readyState === 4){
                //2xx都是成功
                if(xhr.status >=200 && xhr.status <300){
                    //状态码
                    console.log(xhr.status);
                    //状态字符串
                    console.log(xhr.statusText);
                    //所有响应头
                    console.log(xhr.getAllResponseHeaders());

                    div.innerHTML = xhr.response
                }
            }
        }
    }

    post.onclick = function(){
        let xhr = new XMLHttpRequest()

        xhr.open('post','http://localhost/b')

        //设置请求头    Content-Type设置请求体内容的类型    原生ajax不会帮你设置
         xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded')
        xhr.send('a=1&b=2&c=3')//queryString写法
        //json传参
        // xhr.setRequestHeader('Content-Type','application/json')
        // xhr.send(JSON.stringify({a:'sss'}))
        // xhr.send('1','2')

        xhr.onreadystatechange = function(){
            //服务端返回了所有的结果
            if(xhr.readyState === 4){
                //2xx都是成功
                if(xhr.status >=200 && xhr.status <300){
                    //状态码
                    console.log(xhr.status);
                    //状态字符串
                    console.log(xhr.statusText);
                    //所有响应头
                    console.log(xhr.getAllResponseHeaders());

                    div.innerHTML = xhr.response
                }
            }
        }
    }

    json.onclick = function(){
        let xhr = new XMLHttpRequest()

        xhr.open('post','http://localhost/json')

        //设置请求头    Content-Type设置请求体内容的类型    原生ajax不会帮你设置
        xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded')
        // xhr.send(JSON.stringify({a:'sss'}))
        xhr.send()

        //提前自动处理  设置响应数据类型 这样返回的数据直接就是对象类型了
        xhr.responseType = 'json'
        xhr.onreadystatechange = function(){
            //服务端返回了所有的结果
            if(xhr.readyState === 4){
                //2xx都是成功
                if(xhr.status >=200 && xhr.status <300){
                    //状态码
                    console.log(xhr.status);
                    //状态字符串
                    console.log(xhr.statusText);
                    //所有响应头
                    console.log(xhr.getAllResponseHeaders());
                    console.log(typeof xhr.response);
                    //手动处理
                    // let {name} = JSON.parse(xhr.response)
                    //自动处理
                    let {name} = xhr.response
                    div.innerHTML = name
                }
            }
        }
    }

    delay.onclick = (function(){
        
         //阻止重复请求的设计思路是抖动    当请求正在处理时间内,不允许发送新请求
        let xhr = new XMLHttpRequest()  //缺点全局变量容易产生污染
        let isSending = false   //判断重复请求标准

        //采用闭包方式
        return function () {
            //抖动方式阻止重复请求---在请求响应完成之前,isSending都会是true
            // if (isSending) {
            //         console.log(isSending);
            //         xhr.abort()
            // }
            //     isSending  = true
            
            //手动取消请求
            // setTimeout(() => {
            //     alert('abort手动取消请求');
            //     xhr.abort()
            // }, 1000);

            //网络异常回调
            xhr.onerror = function (params) {
                alert('网络不稳定')
            }

            //节流设计思路
            if(!isSending) {
                xhr.open('get','http://localhost/delay')

                xhr.send()

                xhr.onreadystatechange = function () {
                if (xhr.readyState === 4) {
                    // isSending = false//请求有可能失败,反正只需要响应完毕就可以状态变更
                    isSending = false//请求有可能失败,反正只需要响应完毕就可以状态变更
                    if (xhr.status >= 200 && xhr.status < 300) {
                        
                        console.log(xhr.response);
                        div.innerHTML = xhr.response
                        }
                    }
                }
            }

            isSending = true

            //超时2s设置    //属于自动取消请求
            // xhr.timeout = 2000 
            //超时回调
            xhr.ontimeout = function (params) {
                alert('请求超时')
            }
        }
        
    })()

    //全局默认域名
    axios.defaults.baseURL = 'http://localhost'

    axio.onclick = function () {
        axios({
            method:'post',

            url:'/axios',

            headers:{//请求头不能带中文,自己写的请求头是会触发预检请求的
                'name':123,
                'sikara':666
            },

            params:{//url的参数
                a:123
            },

            //请求体参数
            data: {
                firstName: 'Fred'
            },
        }).then(res=>{
            console.log(res.readyState);
            console.log(res.status);
            console.log(res.statusText);
            console.log(res.data);
            console.log(res.headers);
        })
    }

</script>

</html>

服务端(需要用到cors的部分配置)

const express = require("express");

const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: false }));

app.get("/a", (req, res) => {
  res.setHeader("Access-Control-Allow-Origin", "*");
  console.log(req.params, req.query, req.body);
  res.send({ a: 5343 });
});

//可以接受任意类型请求,发送预检请求需要有路由接收处理
app.all("/b", (req, res) => {
  res.setHeader("Access-Control-Allow-Origin", "*");
  //   res.setHeader("Access-Control-Allow-Methods", "*");
  res.setHeader("Access-Control-Allow-Headers", "*");
  console.log(req.params, req.query, req.body, req.body.a);
  res.send("111");
});

//针对IE缓存问题:服务端数据更新但客户端数据不能对应更新
app.get("/c", (req, res) => {
  res.setHeader("Access-Control-Allow-Origin", "*");
  console.log(req.params, req.query, req.body);
  res.send("cccc2222");
});

app.post("/json", (req, res) => {
  res.setHeader("Access-Control-Allow-Origin", "*");
  //   res.setHeader("Access-Control-Allow-Methods", "*");
  res.setHeader("Access-Control-Allow-Headers", "*");
  console.log(req.params, req.query, req.body, req.body.a);
  let data = {
    name: "siakra",
  };
  res.send(data); //已经做了JSON数据默认转换
  //   res.send(JSON.stringify(data));
});

//延迟响应
app.get("/delay", (req, res) => {
  res.setHeader("Access-Control-Allow-Origin", "*");
  console.log(req.params, req.query, req.body);
  setTimeout(() => {
    res.send("delay");
  }, 3000);
});

//axios
app.all("/axios", (req, res) => {
  res.setHeader("Access-Control-Allow-Origin", "*");
  res.setHeader("Access-Control-Allow-Headers", "*");
  console.log(req.params, req.query, req.body);
  res.send("axios");
});

app.listen(80, () => {
  console.log("start");
});

补充说明

  • get请求的请求体默认是undefined,因此原生ajax携带参数只能写在url上

  • 预检请求路径携带的参数也会跟着携带进服务器(请求体的数据则是正式请求再传输),但是会等正式请求再响应

  • 原生ajax对于返回的json数据是需要预处理的,axio不需要

  • 取消请求

    • 自动取消:使用timeout属性,超过时限自动取消
    • 手动取消:核心是调用abort方法------应用例子就是重复点击发送请求的次数处理(采用抖动,节流的设计思路就可实现)
  • 回调函数: 超时回调ontimeout 网络异常回调onerror