写在前面
作为一名后端的学生,重心的确应该放在后端的业务开发上。但有一天突然好奇,我们后端接收到的数据,都是先经过前端的处理打包后发来的,但是前端的数据各式各样(文本、媒体、构成形式等),这到底是怎样处理的呢?
而在一般的业务开发当中,如果我们想要在一次请求当中,将一系列数据按照指定的格式 打包发给服务器程序进行处理,常用的方式应该就是用Form表单了。随着时代的发展,有关前端数据处理,更加便捷、灵活且高效的技术也随之涌现。本篇文章我就想从最开始的Form表单提交开始,逐步认识前端是如何处理并发送数据到后端(服务器程序)的。
Form表单提交的基本形式
先来看一个最简单的表单,这是出发点,是之后应用各项技术的基础:
html
<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>
简单介绍这个表单当中的两个元素和属性:
-
id
和name
:两者都是用来唯一标识元素的属性,只不过id
是用在前端的,name
则可以在后端充当参数(parameter)的名称(key)。可以用在大多数表单元素中。 -
value
:元素对应的值 -
<form></form>
:包含接下来要书写的表单体。method
:定义了提交数据的HTTP方法。主要有get
,post
。前者是默认方法,表单数据会直接附加在action
属性的URL后,并以?
作为分隔符;而post
方法则会将表单数据封装在请求当中的Body
部分,不直接暴露。action
:指定了表单数据要提交的目标URL
-
<input>
:是最常用的表单元素之一,它可以创建文本输入框、密码框、单选按钮、复选按钮等等。type
:定义了输入框的类型
点击submit,数据也就这样提交了,没啥难度,也挺简单的对吧。让我们把视线转移到某个具体的业务场景当中。
这是阿里云云解析DNS服务的购买页面,其他元素我们不关心,我们只看最后的提交按钮,即立即购买 和加入购物车 。这两个提交按钮的意思无疑是同一份表单 对应着两个不同的操作 (action),但是一份表单里的action
属性只能指定一个URL,也即指定一个操作,这可怎么办?
为Form表单添加事件,并规范化数据
在一个网页当中存在着各种各样的事件,其中敲击就是其一。在JavaScript
当中,针对事件是有一系列的处理方法的。上面的一个业务场景,无非就是同一个表单可以触发的事件有多个,那么只要我们针对每一个按钮,使其对应一个事件就可以了。
在原有的HTML中,我们添加属性onclick
,其值对应一个JS函数。
html
<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()
函数,并传入一个字符串参数BuyNow
或addCart
。在submitForm
函数内部,根据字符串形参servletName
映射为相应的URL,并设置为MyForm
表单的action
属性,最后提交submit
就可以了。
利用JS为HTML页面设置一些动作事件,这样的前台数据处理是很常见的。JS本身也很强大,也足够灵活,基于JS,诞生了Ajax
技术、Vue
框架和React
,这些都是前端耳熟能详的。这里围绕表单数据处理,再给大家介绍一个JS应用:
- 手机号格式验证------Form表单前台数据处理之规范化输入数据
html
<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
属性改成了form
的onsubmit
属性(这在只有一个操作的表单里是等价的)。
推广一下,我们不光可以对手机号做规范化处理,我们还可以对整个表单的数据都做规范化规定,便于后台处理数据的同时,也可以防止恶意的数据传输。
有关JS对Form表单的处理操作还有很多很多,但文章写到这里,也不过是使用了几个十分简单的JS函数,这对复杂的业务需求而言还是不足够的。倘若一份Form表单涉及的操作不光多,且有些还比较复杂,甚至需要进行实时的计算和数据回显 ------回到最开始我们举得阿里云云解析DNS服务购买的那个例子,在一份表单里,这里有非常多的套餐搭配,根据搭配的不同,后台程序会即时地计算结果,并回显到前台。
这时简单的JS逻辑代码就难以完成任务了,有没有更好的方法呢?
异步提交Form表单数据------Ajax技术
其实如果只是需要实时数据更新和回显,只用以上的技术不是不可以完成任务,但如果我们在一个页面不管进行的操作是大还是小,都要重新刷新一遍网页,对用户来说体验感极差,给服务端造成的负担也是很大的 ------要完整地显示一个网页,我们必须对网页中的每一项资源都分别发送请求,而显示一个网页平均有上百个的请求。
回到购买云解析DNS服务购买这个场景上,我只是更改了实例模型,就有必要刷新整个Form表单页面吗?不能吧,这应该只需要更改一下最终定价这个元素就足够了呀!
因此,包括优化Form表单数据提交在内的页面数据处理,我们需要更好的技术------这项更好的技术就是AJAX。
啥是AJAX?
AJAX(Asynchronous Javascript and XML)是客户端(通常可以等同于浏览器)与服务器进行异步交互 的一项技术/编程模式,能够让浏览器局部提交变化数据,采用异步方式的工作机制。
同步(Synchronous)操作是按照顺序执行的操作,每个操作必须在前一个操作完成后才能开始执行。在同步操作中,代码会阻塞(即暂停执行)直到当前操作完成,然后才会继续执行下一个操作。这意味着在同步操作中,程序将按照代码的编写顺序一步一步地执行。同步就相当于将控制权都转交了服务器程序。
异步(Asynchronous)操作是不按照顺序执行的操作,每个操作可以在后台独立运行,而不会阻塞其他操作的执行。在异步操作中,代码不会等待当前操作完成,而是继续执行后续的代码(在我们这个例子中,异步意思是说用户这时还可以在页面做其他事情)。当操作完成后,会触发相应的回调函数、事件处理程序或Promise的处理函数,以通知程序操作已完成并返回结果。异步很明显,用户仍然掌握着控制权,浏览器以流的方式修改前台局部元素,这个过程用户还可以做别的事情。
在AJAX当中,XMLHttpRequest
是最重要的一个对象(简写为XHR
)。在传统的Web前后端交互中,Form表单数据提交到服务器,服务器程序处理后把结果通过转发 的方式把数据发送给浏览器。而在AJAX当中,浏览器发出的请求会先发给XMLHttpRequest
异步对象,对请求经过封装后在发送给服务器,与此同时XMLHttpRequest
会不停监听服务器状态的变化,得到服务器返回的数据,就以流的方式写到浏览器上(不是转发,因此不需要刷新)。
使用XMLHttpRequest
发送异步请求大致可以分为五步:
- 用户从
UI
发送请求,JavaScript
中调用XMLHttpRequest
对象。 HTTP
请求由XMLHttpRequest
对象发送到服务器。- 服务器使用
JSP
,PHP
,Servlet
,ASP.net 等与数据库交互。 - 检索数据。
- 服务器将
XML
数据或JSON
数据发送到XMLHttpRequest
回调函数。(回调函数的作用就是XMLHttpRequest
接收到服务端传来的结果应该做出的动作) HTML
和CSS
数据显示在浏览器上。
我们就把阿里云DNS云解析服务购买页面抽象一下,做一个实例,来更好地理解AJAX:
html
<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
,最后发送
html
<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表单提交异步请求会更加简单:
javascript
<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
异步框架。
官方对Axios的解释为:
Axios是一个基于
promise
的网络请求库,作用于Node.js
和浏览器中。它是isomorphic
的(即同一套代码可以运行在浏览器和node.js
中)。在服务器端它使用原生node.js http
模块,而在客户端(浏览器)则使用XMLHttpRequests
。
从这一段文字中我们可以提取出axios
的一些特点:
- 是基于
promise
的。promise
简单来说,就是用于更好地处理异步操作的Javascript
对象。 - 在服务器端使用原生的
node.js
- 在客户端则使用
XMLHttpRequests
。这意味着使用axios
发出的请求都是异步的。
值得一提的是,
axios
也并不是不能发送同步请求。但是在大多数情况下,我们还是避免使用同步请求,因为它会阻塞整个执行线程,导致性能问题和用户体验感下降。异步请求始终是更为常见和通用的方式。
Axios
的内容还是比较少的,官方文档也没有多少字,但实用性相当高。先来举几个样例来说说它是如何使用的:
javascript
//简单的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数据绑定 机制实现了视图 与模型的一致性。
vue
<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
用例------这种将数据和函数封装为一个对象的编程模式,是很具有复用性的.
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表单提交出发,逐步向前推进对前端传输数据的认识------这样一个过程我认为很有趣。这篇文章一定是存在相当多的纰漏的,如果有发现哪里有不妥当的地方,还请斧正。