likes
comments
collection
share

前端数据传输——从Form表单开始了解前端如何处理数据

作者站长头像
站长
· 阅读数 5

写在前面

作为一名后端的学生,重心的确应该放在后端的业务开发上。但有一天突然好奇,我们后端接收到的数据,都是先经过前端的处理打包后发来的,但是前端的数据各式各样(文本、媒体、构成形式等),这到底是怎样处理的呢?

而在一般的业务开发当中,如果我们想要在一次请求当中,将一系列数据按照指定的格式打包发给服务器程序进行处理,常用的方式应该就是用Form表单了。随着时代的发展,有关前端数据处理,更加便捷、灵活且高效的技术也随之涌现。本篇文章我就想从最开始的Form表单提交开始,逐步认识前端是如何处理并发送数据到后端(服务器程序)的。

Form表单提交的基本形式

先来看一个最简单的表单,这是出发点,是之后应用各项技术的基础:

<form id="MyForm" method="get" action="localhost:8080/admin">
    Username: <input type="text" id="user_name" name="username" />
    Password: <input type="password" id="pass_word" name="password"/>
    <input type="submit" value="Submit" />
</form>

简单介绍这个表单当中的两个元素和属性:

  • idname:两者都是用来唯一标识元素的属性,只不过id是用在前端的,name则可以在后端充当参数(parameter)的名称(key)。可以用在大多数表单元素中。

  • value:元素对应的值

  • <form></form>:包含接下来要书写的表单体。

    • method:定义了提交数据的HTTP方法。主要有get,post。前者是默认方法,表单数据会直接附加在action属性的URL后,并以?作为分隔符;而post方法则会将表单数据封装在请求当中的Body部分,不直接暴露。
    • action:指定了表单数据要提交的目标URL
  • <input>:是最常用的表单元素之一,它可以创建文本输入框、密码框、单选按钮、复选按钮等等。

    • type:定义了输入框的类型

点击submit,数据也就这样提交了,没啥难度,也挺简单的对吧。让我们把视线转移到某个具体的业务场景当中。

前端数据传输——从Form表单开始了解前端如何处理数据 这是阿里云云解析DNS服务的购买页面,其他元素我们不关心,我们只看最后的提交按钮,即立即购买加入购物车。这两个提交按钮的意思无疑是同一份表单对应着两个不同的操作(action),但是一份表单里的action属性只能指定一个URL,也即指定一个操作,这可怎么办?

为Form表单添加事件,并规范化数据

在一个网页当中存在着各种各样的事件,其中敲击就是其一。在JavaScript当中,针对事件是有一系列的处理方法的。上面的一个业务场景,无非就是同一个表单可以触发的事件有多个,那么只要我们针对每一个按钮,使其对应一个事件就可以了。

在原有的HTML中,我们添加属性onclick,其值对应一个JS函数。

<script>
function submitForm(servletName) {
    var form = document.getElementById("MyForm");
    var servletUrl = getServletUrl(servletName);
    //设置表单的action属性为对应的后台程序URL
    form.setAttribute("action",servletUrl);
    //提交表单
    form.submit();
}

function getServletUrl(servletName) {
/*        根据 servletName 返回相应的 Servlet URL
    这里只是一个示例,你需要根据实际情况进行适当的修改*/
    if (servletName === 'BuyNow') {
        return "buyNow";
    } else if (servletName === 'addCart') {
        return "addShoppingCart";
    }
}
</script>
<form id="MyForm" method="post">
    <!--此处省略除提交以外其他表单元素 -->
    <input type="submit" value="立即购买" onclick="submitForm('BuyNow')">
    <input type="submit" value="添加购物车" onclick="submitForm('addCart')">
</form>

还是很好理解的,点击立即购买或添加购物车,调用一个submitForm()函数,并传入一个字符串参数BuyNowaddCart。在submitForm函数内部,根据字符串形参servletName映射为相应的URL,并设置为MyForm表单的action属性,最后提交submit就可以了。

利用JS为HTML页面设置一些动作事件,这样的前台数据处理是很常见的。JS本身也很强大,也足够灵活,基于JS,诞生了Ajax技术、Vue框架和React,这些都是前端耳熟能详的。这里围绕表单数据处理,再给大家介绍一个JS应用:

  1. 手机号格式验证——Form表单前台数据处理之规范化输入数据
<script>
    function isValid(){
        const regex=/^(?:\+?86)?1(?:3\d{3}|5[^4\D]\d{2}|8\d{3}|7(?:[0-35-9]\d{2}|4(?:0\d|1[0-2]|9\d))|9[0-35-9]\d{2}|6[2567]\d{2}|4(?:(?:10|4[01])\d{3}|[68]\d{4}|[579]\d{2}))\d{6}$/;
        const phoneNumberInput = document.getElementById('phoneNumber');
        const phoneNumber = phoneNumberInput.value;
        if(regex.test(phoneNumber)){
            submitForm('register');
        }else{
            alert('手机号格式不正确!');
        }
    }
</script>
<p>手机号注册</p>
<form id="MyForm" method="post" onsubmit="isValid()">
    手机号:<input type="tel" id="phoneNumber" name="phoneNumber" />
    密码:<input type="password" id="password" name="password" />
    <input type="submit" value="Submit">
</form>

regex是一个正则表达式,用于匹配字符串用的,在这里的作用就是为了在前端页面就告诉用户输入正确的手机号,便于后端的业务程序处理数据。在这里我们还调用了上述的submitForm函数,并把onclick属性改成了formonsubmit属性(这在只有一个操作的表单里是等价的)。

推广一下,我们不光可以对手机号做规范化处理,我们还可以对整个表单的数据都做规范化规定,便于后台处理数据的同时,也可以防止恶意的数据传输。

有关JS对Form表单的处理操作还有很多很多,但文章写到这里,也不过是使用了几个十分简单的JS函数,这对复杂的业务需求而言还是不足够的。倘若一份Form表单涉及的操作不光多,且有些还比较复杂,甚至需要进行实时的计算和数据回显——回到最开始我们举得阿里云云解析DNS服务购买的那个例子,在一份表单里,这里有非常多的套餐搭配,根据搭配的不同,后台程序会即时地计算结果,并回显到前台

前端数据传输——从Form表单开始了解前端如何处理数据

这时简单的JS逻辑代码就难以完成任务了,有没有更好的方法呢?

异步提交Form表单数据——Ajax技术

其实如果只是需要实时数据更新和回显,只用以上的技术不是不可以完成任务,但如果我们在一个页面不管进行的操作是大还是小,都要重新刷新一遍网页,对用户来说体验感极差,给服务端造成的负担也是很大的——要完整地显示一个网页,我们必须对网页中的每一项资源都分别发送请求,而显示一个网页平均有上百个的请求

前端数据传输——从Form表单开始了解前端如何处理数据

回到购买云解析DNS服务购买这个场景上,我只是更改了实例模型,就有必要刷新整个Form表单页面吗?不能吧,这应该只需要更改一下最终定价这个元素就足够了呀!

因此,包括优化Form表单数据提交在内的页面数据处理,我们需要更好的技术——这项更好的技术就是AJAX。

啥是AJAX?

AJAX(Asynchronous Javascript and XML)是客户端(通常可以等同于浏览器)与服务器进行异步交互的一项技术/编程模式,能够让浏览器局部提交变化数据,采用异步方式的工作机制。

同步(Synchronous)操作是按照顺序执行的操作,每个操作必须在前一个操作完成后才能开始执行。在同步操作中,代码会阻塞(即暂停执行)直到当前操作完成,然后才会继续执行下一个操作。这意味着在同步操作中,程序将按照代码的编写顺序一步一步地执行。同步就相当于将控制权都转交了服务器程序。

异步(Asynchronous)操作是不按照顺序执行的操作,每个操作可以在后台独立运行,而不会阻塞其他操作的执行。在异步操作中,代码不会等待当前操作完成,而是继续执行后续的代码(在我们这个例子中,异步意思是说用户这时还可以在页面做其他事情)。当操作完成后,会触发相应的回调函数、事件处理程序或Promise的处理函数,以通知程序操作已完成并返回结果。异步很明显,用户仍然掌握着控制权,浏览器以流的方式修改前台局部元素,这个过程用户还可以做别的事情。

在AJAX当中,XMLHttpRequest是最重要的一个对象(简写为XHR)。在传统的Web前后端交互中,Form表单数据提交到服务器,服务器程序处理后把结果通过转发的方式把数据发送给浏览器。而在AJAX当中,浏览器发出的请求会先发给XMLHttpRequest异步对象,对请求经过封装后在发送给服务器,与此同时XMLHttpRequest会不停监听服务器状态的变化,得到服务器返回的数据,就以的方式写到浏览器上(不是转发,因此不需要刷新)。

使用XMLHttpRequest发送异步请求大致可以分为五步:

  1. 用户从 UI 发送请求,JavaScript 中调用 XMLHttpRequest 对象。
  2. HTTP 请求由 XMLHttpRequest 对象发送到服务器。
  3. 服务器使用 JSPPHPServlet,ASP.net 等与数据库交互。
  4. 检索数据。
  5. 服务器将 XML 数据或 JSON 数据发送到 XMLHttpRequest 回调函数。(回调函数的作用就是XMLHttpRequest接收到服务端传来的结果应该做出的动作)
  6. HTMLCSS 数据显示在浏览器上。

前端数据传输——从Form表单开始了解前端如何处理数据

我们就把阿里云DNS云解析服务购买页面抽象一下,做一个实例,来更好地理解AJAX:

    <form id="MyForm">
        <label>实例类型:</label>
        <input type="radio" id="HPZ" name="TypeofInstance" value="HostedPublicZone" checked />
        <label for="HPZ">权威托管域名</label>
        <input type="radio" id="CPZ" name="TypeofInstance" value="CachedPublicZone" />
        <label for="CPZ">权威代理域名</label>
        <br>
        <label>版本选择:</label>
        <input type="radio" id="personal" name="Version" value="personal" checked />
        <label for="personal">个人版</label>
        <input type="radio" id="standardEE" name="Version" value="standardEE" />
        <label for="standardEE">企业标准版</label>
        <input type="radio" id="topEE" name="Version" value="topEE" />
        <label for="topEE">企业旗舰版</label>
        <!--略略略......-->
        <br>
        <button value="提交" onclick="calculateTotalCost()">提交</button>
        <br>
        总共花费:<div id="Totalcost"></div>
    </form>

这是核心的JS代码。我们先从表单当中获取数据封装成对象formData,创建好XMLHttpRequest对象,依次设置好请求行、请求头以及回调函数并将对象formData交给XMLHttpRequest,最后发送

<script type="text/javascript">
            var xmlhttpRequest;
            function calculateTotalCost() {
                //先获取表单数据
                var formData={
                    TypeofInstance:null,
                    Version:null,
                    //省略其他表单元素
                };
                let TypeofInstances=document.getElementsByName("TypeofInstance");
                for(let i=0;i<TypeofInstances.length;i++){
                    if(TypeofInstances[i]){
                        formData.TypeofInstance=TypeofInstances[i];
                        break;
                    }
                }
                let Versions=document.getElementsByName("Version");
                for(let i=0;i<Versions.length;i++){
                    if(Versions[i]){
                        formData.Version=Versions[i];
                        break;
                    }
                }
                console.log("获取表单数据完毕:实例类型为"+formData.TypeofInstance+",版本选择为:"+formData.Version);
                if(window.XMLHttpRequest) {
                    //在IE6以上的版本以及其他内核的浏览器(Mozilla)等
                    xmlhttpRequest = new XMLHttpRequest();
                }
                //创建http请求
                //这里的servlet可以根据自己需求指定
                xmlhttpRequest.open("POST", "http://localhost:8080/form-ajax", true);//URL必须是绝对路径
    
                //post方式需要设置请求头
                xmlhttpRequest.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
    
                //指定回调函数
                xmlhttpRequest.onreadystatechange = getResponse();
    
                //得到表单的数据并发送
                xmlhttpRequest.send(new URLSearchParams(formData).toString);
    
            }
    
            function getResponse() {
                //判断请求正常接收
                if(httpRequest.readyState==4 && httpRequest.status==200) {
                    //得到服务端返回的文本数据
                    var cost = httpRequest.responseText;
                    //把服务端返回的数据写在div上
                    var div = document.getElementById("Totalcost");
                    div.innerText = cost;
                }
            }
</script>

也许会有小伙伴觉得为了实现AJAX异步请求,而写一大串这样的JS代码比较麻烦,实际上如果我们使用JQuery库,Form表单提交异步请求会更加简单:

<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<span style="font-size:18px;">  $.ajax({  
    type: "POST",  
    url:your-url,  
    data:$('#yourformid').serialize(),  
    async: false,  
    error: function(request) {  
        alert("Connection error");  
    },  
    success: function(data) {  
        //接收后台返回的结果  
    }  
  });</span>  

AJAX异步请求应用得相当广泛,不信的话,你现在就可以F12,在Network项目下筛选Fetch/XHR类,就可以看到非常多XHR请求。

将异步请求封装,更加方便的发送请求——Axios

相信读到这里,你可能已经发现,我们上述使用的技术,应用在Form表单提交上只是其一。是的,我们当然可以做一个推广,这些技术都可以广泛应用在几乎所有前端像后端传输数据的场景下,打开F12,你会发现的确如此。

如果你已经认识到这一点,那么接下来所介绍的围绕Form表单提交的技术,你都可以看作是前端传输数据的通用方式。

AJAX异步请求应用之广泛,以致于我们又对AJAX进行了封装,并且为了便于发送请求、前后端分离,诞生了Axios异步框架。

前端数据传输——从Form表单开始了解前端如何处理数据 官方对Axios的解释为:

Axios是一个基于promise的网络请求库,作用于Node.js和浏览器中。它是isomorphic的(即同一套代码可以运行在浏览器和node.js中)。在服务器端它使用原生node.js http模块,而在客户端(浏览器)则使用XMLHttpRequests

从这一段文字中我们可以提取出axios的一些特点:

  1. 是基于promise的。promise简单来说,就是用于更好地处理异步操作的Javascript对象。
  2. 在服务器端使用原生的node.js
  3. 在客户端则使用XMLHttpRequests。这意味着使用axios发出的请求都是异步的。

值得一提的是,axios也并不是不能发送同步请求。但是在大多数情况下,我们还是避免使用同步请求,因为它会阻塞整个执行线程,导致性能问题和用户体验感下降。异步请求始终是更为常见和通用的方式。

Axios的内容还是比较少的,官方文档也没有多少字,但实用性相当高。先来举几个样例来说说它是如何使用的:

//简单的get请求
axios.get('http://localhost:8080/user',{
    params:{//参数键值对
        ID:123456
    }
}).then(function (response){
    //成功收到服务器响应
    //相关处理代码
}).catch(function (error){
    //异步操作失败,返回了错误
    //错误处理
}).finally(function(){
    //不管操作成功还是失败都会执行
});

//简单的post请求
//这里我们直接引入HTML Form,axios会自动帮我们转换成JSON
axios.post('http://localhost:8080/user'
            document.querySelector('MyForm'),{
    headers:{
        `Content-Type`:`application/json`
    }
}).then(function (response) {
    //成功收到服务器响应
}).catch(function (error) {
    //异步操作失败,返回了错误
});

至此,一条前端请求和处理响应的代码就写完了,很简洁对吧?虽然简洁,但异步请求需要的元素可一点儿没少:

  • .get.post对应我们两种请求方式,axios也支持put、patch、delete这些方法
  • 第一个参数就是后端服务器程序URL,接着你可以以JSON的格式加上参数,也可以对于简单的get请求而言,直接在URL后写参数;post请求的话,我们直接以Form表单作为参数就可以,axios会自动将其格式化为JSON格式
  • headers这里我们写清楚要传输的是什么类型的数据。根据类型的不同,axios可以将参数转换成不同的格式。
  • .then .catch .finally对应的是原来XMLHttpRequest当中的回调函数

axios足够轻便,接口也易于扩展,还支持并发请求、拦截器,上手起来也非常容易。这无疑让我们提交Form表单数据更加灵活,简单。

内容丰富的前端管理员——Vue

终于来到这里了,Vue在前端领域是非常受欢迎的,其本身到目前为止也具备了一个十分良好的生态。在数据传输方面,Vue不仅内置了axios,还有v-model数据绑定机制实现了视图模型的一致性。

<div id="testApp">
    <form :model="formData"></form>
    <input v-model="formData.userName" placeholder="请输入用户名" />
    <input v-model="formData.phoneNumber" placeholder="请输入手机号" />
    <input v-model="formData.password" type="password" placeholder="请输入密码" />

    <button @click="submitTheForm">登录</button>
</div>

来看一个简单的Vue用例——这种将数据和函数封装为一个对象的编程模式,是很具有复用性的.

    <script type="module" src="main.js"></script>
    <script>
        const test_app=createApp({
            methods:{
                submitTheForm(){
                    console.log(this.formData);
                    //发送ajax请求
                    var _this=this;
                    axios({
                        method:"post",
                        url:"http://localhost:8080/project/login",
                        data:_this.formData
                    })
                }
                //这里我们还可以基于Vue定义更多的函数
            },
            data(){
                return {
                    formData:{
                        userName:'',
                        phoneNumber:'',
                        password:''
                    }
                }
            }
        })
        test_app.mount('testApp');
    </script>

这里只是展示了Vue的基础用法之一,有关Vue的知识相当丰富,想要深入学习,官网提供了全套的学习教程,值得一看。

前端数据传输——从Form表单开始了解前端如何处理数据

小结

本篇文章是我作为一名后端学生探索前端世界的一个了解尝试,本身没有很多技术含量,只是单纯觉得从最开始的Form表单提交出发,逐步向前推进对前端传输数据的认识——这样一个过程我认为很有趣。这篇文章一定是存在相当多的纰漏的,如果有发现哪里有不妥当的地方,还请斧正。

参考资料

  1. AJAX 教程_w3cschool
  2. Axios中文文档 | Axios中文网 (axios-http.cn)
  3. Vue.js - 渐进式 JavaScript 框架 | Vue.js (vuejs.org)