【前端43_前后端交互】Ajax、网络请求、同步和异步请求、FormData 实现文件上传、监控上传速度和进度


Ajax 简单介绍

Ajax 即 “Asynchronous Javascript And XML”(异步 JavaScript 和 XML)

它是一项技术,提高了用户的体验,为什么这么说呢?

场景

举个栗子,如果我们做个登录的功能,当用户输入完姓名后,我需要去请求后台,校验一下数据库中是否重名,如果没有 ajax,那么只能通过跳转进行后端访问,这个体验太糟糕了。

我们想的是:能否不需要刷新页面就可以访问后台接口?所以 Ajax 应运而生。

实例:验证用户名输入

根据刚才的场景,我们来写一下代码。顺便简单介绍 Ajax 的使用

前端:

  • 首先新建一个 XMLHttpRequest 对象

    let xhr = new XMLHttpRequest();
    
  • 然后配置请求参数 xhr.open();,其中 true 是异步,false 是同步,后面会说到

    xhr.open("get","/checkUserName",true); //true是异步,false是同步
    
  • 接着接受返还值

     xhr.onload = function(){
        let res = JSON.parse(xhr.responseText);
     }
    
  • 发送服务器

    xhr.send();
    

前端校验用户名的主要代码如下:

document.querySelector('.inputStyle').onblur = function () {
    let xhr = new XMLHttpRequest();
    xhr.open('get', `/checkUserName?username=${this.value}`, false)
    xhr.onload = function () {
        let res = JSON.parse(xhr.responseText);
        console.log(res);
        if (res.status === 1) {
            document.querySelector('.exchange').style.color = 'green'
        } else {
            document.querySelector('.exchange').style.color = 'red'
        }
        document.querySelector('.exchange').innerHTML = res.info;
    }
    xhr.send();
}

后端:

router.get('/checkUserName', async ctx => {
    let ctxUserName = ctx.query.username;
    let row = usersData.find((i) => i.username === ctxUserName);
    if (row) {
        ctx.body = {
            status: 1,
            info: '用户名正确'
        }
    } else {
        ctx.body = {
            status: 0,
            info: '用户名错误'
        }
    }
})

先感受一下 ajax


网络请求

GET

使用如下:

let xhr = new XMLHttpRequest(); 
xhr.open('get',`/checkUserName?username=${this.value}`, true)
xhr.onload = function(){
    console.log(xhr.responseText);
}
xhr.send();
注意点
  • get 通过 parmas 传参
  • get 和 querystring 的问题,通过 url 传参
传参方式

如果 get 是这样携带参数的话

// 这里传递了一个数字: 3
// 就是参数也用斜杠 / 给隔开了
xhr.open('get', '/getText/3', false);

后台就需要这样去接收,我声明了一个叫 id 的变量(这个变量名字也可以随便取),它来接受前端传来的 3

router.get('/getText/:id', ctx => {
    console.log(ctx.query); // 收不到
    console.log(ctx.params);    // 收得到
    ctx.body = {
        info: '请求成功'
    }
})

注意:要写这个冒号和名称,不然前端会报错 404

在这里插入图片描述


POST

前端发送数据是要声明编码格式,设置http正文头格式

// 发送数据时候需要设置http正文头格式;
xhr.setRequestHeader("Content-type","application/x-www-form-urlencoded");  //默认编码
xhr.setRequestHeader("Content-type","multipart/form-data");  //二进制编码
xhr.setRequestHeader("Content-type","application/json");  //json编码

以下是个栗子🌰

document.querySelector('#btn').onclick = function () {
    let xhr = new XMLHttpRequest();
    xhr.open('post', '/postText', false);
    xhr.onload = function () {
        console.log(xhr.responseText);
    }
    xhr.setRequestHeader('Content-type','application/x-www-form-urlencoded');
    let data = `username=王五&age=20`
    xhr.send(data);
}

后端接收,需要先引入 koa-body 框架,然后通过 ctx.request.body 来获取参数

router.post('/postText', ctx => {
    console.log(ctx.request.body);
    ctx.body = {
        info: '请求成功'
    }
})

在这里插入图片描述

前端也可以获得到后端返回的 头部信息

let allRes = xhr.getAllResponseHeaders();       // 获取响应头,有些属性是获取不到的,可以查看 w3c 的文档
let contentType = xhr.getResponseHeader('content-type')

栗子如下:

document.querySelector('#btn').onclick = function () {
    let xhr = new XMLHttpRequest();
    xhr.open('post', '/postText', false);
    xhr.onload = function () {
        console.log(xhr.responseText);
        let allRes = xhr.getAllResponseHeaders();       // 获取响应头,有些属性是获取不到的,可以查看 w3c 的文档
        console.log(allRes);
        let contentType = xhr.getResponseHeader('content-type')
        console.log(contentType);
    }
    let data = { username: "王五", age: 20 }
    xhr.send(data);
}
编码格式(自测笔记)
application/x-www-form-urlencoded

form 表单默认的编码格式如下,如果想设置其他值,在 form 标签上可以这样设置。

enctype="application/x-www-form-urlencoded"

在这里插入图片描述
传递数据的样子是这样的:

let data = `username=王五&age=20`
multipart/form-data

如果是上传文件的话可以用

xhr.setRequestHeader("Content-Type","multipart/form-data"); //二进制编码
application/json

如果是 JSON 的话

xhr.setRequestHeader("content-type","application/json");

那么数据的样子就要是这个

let data = JSON.stringify({
    username:"王五",
    age:20
})
xhr.send(data);

总结

  • post 是密文传输,
    • 他也可以通过 url 来传递参数,
    • 但是这样传递就失去了密文传输的意义了,没有大小显示,
    • 服务器可能会显示,如果没有限制的话,那就是无穷大了。
    • 在http 正文传参,需要(必须)设置编码格式
  • get
    • 明文传输
    • 有大小显示

其他知识补充

  • Koa 在 ctx.body 的时候会把对象自动帮我们 JSON.stringify()

Ajax 的同步和异步

实验

来个实验就很明了

实验:我在页面上放两个按钮,注意我点的顺序:我都是先点按钮1 然后再点按钮2,其中两个按钮的逻辑如下:

let btns = document.querySelectorAll('button');
// 按钮1 是个网络请求
btns[0].onclick = () => {
     let xhr = new XMLHttpRequest();
     xhr.open('get', '/getText/3', true);
     xhr.onload = function () {
         console.log(xhr.responseText);
     }
     xhr.send();
 }
// 按钮2 不需要网路请求,直接输出
btns[1].onclick = () => {
    console.log('我是按钮2');
}

逻辑介绍完了,此时我在设置同步和异步

同步情况

同步:不建议,会卡进程的,我改成同步的效果如下:

在这里插入图片描述

可以看到输出的顺序,等按钮1 的 Ajax 请求结束后才执行的 按钮2

异步情况

异步的情况如下:
在这里插入图片描述

Ajax 默认设置的是异步,也就是你不写参数的话,默认是异步的(你可以自行测试)。

测试的时候可以用 3g 网,浏览器里能调整。

在这里插入图片描述

xhr.onload 和 onreadystatechange

原先的请求成功,可以有两种写法

其中onreadystatechange 的写法如下

 xhr.onreadystatechange = function () {
    if (xhr.readyState == 4) {
        if (xhr.status == 200) {
            console.log(xhr.responseText);
        }
    }
}

onreadystatechange:存有处理服务器响应的函数,每当 readyState 改变时,onreadystatechange 函数就会被执行。

readyState 状态信息

readyState:存有服务器响应的状态信息。

  • 0: 请求未初始化(代理被创建,但尚未调用 open() 方法)
  • 1: 服务器连接已建立(open方法已经被调用)
  • 2: 请求已接收(send方法已经被调用,并且头部和状态已经可获得)
  • 3: 请求处理中(下载中,responseText 属性已经包含部分数据)
  • 4: 请求已完成,且响应已就绪(下载操作已完成)

status 常用状态码

HTTP状态码描述
100继续。继续响应剩余部分,进行提交请求
200成功
301永久移动。请求资源永久移动到新位置
302临时移动。请求资源零时移动到新位置
304未修改。请求资源对比上次未被修改,响应中不包含资源内容
401未授权,需要身份验证
403禁止。请求被拒绝
404未找到,服务器未找到需要资源
500服务器内部错误。服务器遇到错误,无法完成请求
503服务器不可用。临时服务过载,无法处理请求

如何返还并接收xml

后台返还一个 xml

router.get('/xml', ctx => {
    ctx.set('content-type', 'text/xml')
    ctx.body = `<?xml version ='1.0' encoding='utf-8' ?>
                    <books>
                        <nodejs>
                            <name>nodejs实战</name>
                            <price>56元</price>
                        </nodejs>
                        <react>
                            <name>react入门</name>
                            <price>50元</price>
                        </react>
                    </books>
    `
})

前端接收 xml

document.querySelector('button').onclick = function () {
    let xhr = new XMLHttpRequest();
    xhr.open('get', '/xml', true);
    xhr.onload = function () {
        // 这里有三种返还方式
        // console.log(xhr.responseText);
        console.log(xhr.responseXML);
        // console.log(xhr.response);  // 原始数据
        let name = xhr.responseXML.getElementsByTagName('name')[0].innerHTML;
        console.log(name);
    }
    xhr.send();
}

如果后端没有声明返还的文件是xml,也就是 ctx.set('content-type', 'text/xml') 这个,那么前端可以重新设置头

就是你觉得哎后端 返回来一堆东西优点像 xml 啊,但是后端它没设置,那么咱们前端也可以设置

xhr.overrideMimeType('text/xml')        // 前端设置 content-type 类型

举个使用场景的栗子:

document.querySelector('button').onclick = function () {
    let xhr = new XMLHttpRequest();
    xhr.open('get', '/xml', true);
    xhr.overrideMimeType('text/xml')        // 前端设置 content-type 类型
    xhr.onload = function () {
        let name = xhr.responseXML.getElementsByTagName('name')[0].innerHTML;
        console.log(name);
    }
    xhr.send();
}

利用 FormData 实现文件上传

简写上传

主要利用的也是 ajax,这里引入了新的对象 new FormData()

概括流程:我们要把文件数据获取并 appendFormData 这个对象中去,然后把 formdata 发送到服务器中。

前端这边代码如下:

 document.querySelector('button').onclick = function () {
    let files = document.querySelector('.myfile').files[0]; // 获取文件
    console.log(files); // 类数组
    let form = new FormData(); // 他会自动帮我设置 content-type
    form.append('img', files);
    form.append('name', '测试');

    let xmlHttpReq = new XMLHttpRequest();
    xmlHttpReq.open('post', '/upload', true)
    xmlHttpReq.onload = function () {
        console.log(xmlHttpReq.responseText);
    }
    xmlHttpReq.send(form)
}

后端接收注意要在后端这里允许上传文件

app.use(KoaBody({
    multipart: true
}))

路由如下:

router.post('/upload', ctx => {
    console.log(ctx.request.body);  // 接收文字
    console.log(ctx.request.files); // 接收文件信息
})

后端处理文件

node 会帮我们把文件存储到临时地址,我们可以通过 fs 模块拿到文件,然后写到自己想要的位置

后端检测文件夹是否存在,并且转存文件到指定目录

router.post('/upload', ctx => {
    try {
        console.log(ctx.request.body);
        console.log(ctx.request.files);
        let imgPath = ctx.request.files.img.path;
        console.log(imgPath);
        // 这个路径相当于 index.js 运行时的
        // 检测目标目录下是否有 名为 xx 的文件夹。如果没有就创建一个
        // 判断 images 文件夹是否存在,images/ 这个斜杠也是可以带的
        fs.exists('static/images/', isExists => {
            console.log('是否存在 img 文件夹', isExists);
            if (isExists === false) {
                fs.mkdirSync('static/images')
            }
            let imgName = ctx.request.files.img.name;
            let fileData = fs.readFileSync(imgPath)
            fs.writeFileSync('static/images/' + imgName, fileData)
        })
        ctx.body = {
            info: '上传成功'
        } 
    } catch (err) {
        console.log(err);
    }
})

前端 xhr.upload 上传钩子函数

大概有如下几个钩子(比较常用的)

xhr.upload.onprogress = (event) => {
    console.log('上传过程');
}
xhr.upload.onload = () => {
    console.log('上传成功');
}
xhr.upload.onloadend = () => {
    console.log('上传完成');
}
xhr.upload.onabort = () => {
    console.log('取消上传');
}

onprogress 这个函数是在上传过程中不断循环被执行的,其中有事件因子 event,里面会有上传中的信息

如果想要监控速度和进度的话,可以在上传的过程中计算出来

利用钩子函数计算下载速度和进度

思路就是求出一段时间的下载量和一段时间,然后做除法

在这里插入图片描述

let oldDataSize;
let oldTime;
xhr.onload = function () {
    let responseText = xhr.responseText;
    console.log("上传成功", responseText);
};
xhr.upload.onloadstart = () => {
    console.log("上传开始!");
    oldLoaded = 0;
    oldTime = new Date();
};
xhr.upload.onprogress = (enent) => {
    let duringLoaded = (event.loaded - oldLoaded) / 1024;
    let duringTime = (new Date() - oldTime) / 1000; // 时间戳,默认单位是毫秒
    
    // 记录旧的数据,下次循环的时候需要用的
    oldTime = new Date();
    oldLoaded = event.loaded;
    
    console.log("上传中");
    console.log(event);
};

代码 git 地址

高级-前后端交互

https://gitee.com/lovely_ruby/DailyPractice/tree/main
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值