由 form 表单来说说前后台数据之间的交互

1,990 阅读5分钟

前言

今天想要讲的东西比较杂乱,自己理了好久的思路感觉一直找不到一条线串联起这些碎片化的知识。然后就想着那就先写写看吧,写到哪算哪,最后再调整调整。所以童鞋们看的时候就不要太在意逻辑哈。

1、从form表单提交说起

为什么从表单提交说起呢?因为大部分与后台的交互都是在form表单中实现,恰巧我入职一个月来都是在处理与后台交互的数据整合中度过,期间也发现一些小坑,出于喜欢总结,所以才想写这篇小博客。

各位童鞋,可以先看一下这个例子:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>testing form group</title>
    <script type="text/javascript" src="http://cdn.bootcss.com/jquery/3.1.0/jquery.min.js"></script>
    <script type="text/javascript">
    function onSubmit(){
        var finalRes = $('#form').serializeArray().reduce(function(result, item){
             result[item.name] = item.value;
            return result;
        }, {})
    }
    </script>
</head>
<body>
    <form action="" method="post" name='info' id='form'>
        <input type="text" name="user" />
        <input type="text" name="email" />
    </form>
    <button type="button" name='submit' onclick="onSubmit()">提交</button>
</body>
</html>

Tips:

对于form表单有两种提交方法:

  1. 上面的使用button来收集表单信息然后校验并提交到服务器
  2. 直接在form表单中添加type=submit属性的input或者button类型的按钮,点击按钮后表单根据actionmethod的配置直接提交表单。

对于后一种提交表单的方式还有很多配置选项,包括enctypeacceptaccept-charset

当然上面可以配置的属性在我们即将要讲的Jquery的ajax方法中都是可以配置的。所以我们先说说上面代码中涉及到的几个知识点。

1.1、type="submit" vs type="button"

  1. type="submit"是将表单提交(即form.submit()方法)作为其onclick后的默认事件。
  2. type="submit"会自动将所有具有name属性的html输入元素(包括input标签、button标签、select标签等)都将作为键值对提交。

  3. type="submit"submit会有一个跳转,页面会刷新;

上面的3点对于type="button"统统都没有,type="button"只是一个单纯的点击,点击事件需要你自己添加,就如上面的代码那样。

如果一切都是你说的那样:type="submit"会自动提交所有的表单信息,那么我们如何校验参数呢?这时候便是使用表单事件处理函数onSubmit,你可以在函数内部校验参数,不成功就返回false,这样就可以阻止表单的提交了。

另外该事件处理函数不是肯定会被调用的,条件如下:

The submit event is raised when the user clicks a submit button in a form (<input type="submit"/>).
The submit event is not raised when the user calls form.submit() function directly.

1.2、.serializeArray()

这个是Jquery的方法,目的是为了搜集表单元素内部所有可以搜集的标签的name和value,然后组合成类似这种形式的对象值:{name:"xxx",value:"xxx"}。于是如果表单中有多个输入选项那么结果应该是这样的:

[{name:"xxx",value:"xxx"},{name:"xxx",value:"xxx"}....]

1.3、reduce()

这个是ES6新加的语法,具体用法可以参考MDN

然后我们在这里使用它是为了将刚才序列化之后的值转变为Json数据,以上面的代码为例子其结果应该是这样的:

{user:"xxxx",email:"xxxxxx"}

这个时候这个结果便是我们想要提交给后台服务器的数据,那么问题来了,我们是可以直接将这个数据提交给服务器吗?中间还需要做些什么吗?

2、使用$.ajax()

用过Jquery的童鞋肯定都用过$.ajax()这个函数,其最普通的用法便是:

$.ajax({
    type: 'POST',
    url: ,
    data: ,
    dataType: 'json',
    success: function(data){successCallback(data)},
    error: function(jqXHR){failureCallback(jqXHR)},
})

那么问题来了:你该如何配置正确的参数才能让你的前后台能够完美的协作呢?

2.1、ajax方法中几个重要的参数

2.1.1. contentType

默认值为(application/x-www-form-urlencoded; charset=UTF-8)。根据Jquery的API文档我们知道:

当将数据发送到服务器时,使用该内容类型(或者叫编码类型)。默认值是"application/x-www-form-urlencoded; charset=UTF-8",适合大多数情况。如果你明确地传递了一个内容类型(Content-Type)给 $.ajax(),那么他总是会发送给服务器(即使没有数据要发送)。从 jQuery 1.6 开始,你可以传递false来告诉jQuery,没有设置任何内容类型头信息。 注意:W3C的XMLHttpRequest的规范规定,数据将总是使用UTF-8字符集传递给服务器;指定其他字符集无法强制浏览器更改编码。 注意:对于跨域请求,内容类型设置为application/x-www-form-urlencoded, multipart/form-data, 或 text/plain以外, 将触发浏览器发送一个预检OPTIONS请求到服务器。

那么我们就想知道这个参数配置的值都是些什么东东呢?常见的编码类型有:

  1. application/x-www-form-urlencoded:窗体数据被编码为名称/值对。这是标准的编码格式。 (表单默认的提交数据的格式)

  2. multipart/form-data:窗体数据被编码为一条消息,页上的每个控件对应消息中的一个部分。 (上传文件之时使用)

  3. text/plain:窗体数据以纯文本形式进行编码,其中不含任何控件或格式字符。
  4. application/json:窗体数据以Json的数据格式来传递。(传递[{},{},{}]这种json数组格式)

2.1.2. data

发送到服务器的数据。它被转换成一个查询字符串,如果已经是一个字符串的话就不会转换。查询字符串将被追加到GET请求的URL后面。参见 processData 选项说明,以防止这种自动转换。对象必须为"{键:值}"格式。如果这个参数是一个数组,jQuery会按照traditional 参数的值, 将自动转化为一个同名的多值查询字符串。

补充一点就是即使是在对象里的key/value中value是数组,也会自动转换成一个同名多址的字符串。比如将上面例子的代码修改成:

<script type="text/javascript">
    function onSubmit(){
        /*var finalRes = $('#form').serializeArray().reduce(function(result, item){
             result[item.name] = item.value;
            return result;
        }, {})
        console.log(finalRes)
        */
        $.ajax({
            type: 'POST',
            url: 'http://blog.5udou.cn/test/first',
            data: {name: 'linxiaowu', pass: '123456', weekDays: [1,2,3,4]},
            dataType: 'json',
            contentType: 'application/x-www-form-urlencoded',
            success: function(data){console.log(data)},
            error: function(jqXHR){console.log(jqXHR)},
        })
    }
    </script>

那么浏览器将解析成这样的效果:

2.1.3. dataType

从服务器返回你期望的数据类型。 如果没有指定,jQuery将尝试通过MIME类型的响应信息来智能判断(一个XML MIME类型就被识别为XML,在1.4中 JSON将生成一个JavaScript对象,在1.4中 script 将执行该脚本,其他任何类型会返回一个字符串)。 可用的类型(以及结果作为第一个参数传递给成功回调函数)有:

"xml": 返回 XML 文档,可以通过 jQuery 处理。
"html": 返回纯文本 HTML 文本;包含的script标签会在插入DOM时执行。
"script": 把响应的结果当作 JavaScript 执行,并将其当作纯文本返回。默认情况下会通过在URL中附加查询字符串变量 ,_=[TIMESTAMP], 禁用缓存结果,除非设置了cache参数为true。注意: 在远程请求时(不在同一个域下),所有POST请求都将转为GET请求。(愚人码头注:因为将使用DOM的script标签来加载)
"json":把响应的结果当作 JSON 执行,并返回一个JavaScript对象。跨域"json" 请求转换为"jsonp",除非该请求在其请求选项中设置了jsonp:false。JSON 数据以严格的方式解析; 任何畸形的JSON将被拒绝,并且抛出解析错误信息。在jQuery1.9中,一个空响应也将被拒绝;服务器应该返回null或 {}响应代替。(见json.org的更多信息,正确的JSON格式。)
"jsonp": 以 JSONP 的方式载入 JSON 数据块。会自动在所请求的URL最后添加"?callback=?"。默认情况下会通过在URL中附加查询字符串变量 ,_=[TIMESTAMP], 禁用缓存结果,除非设置了cache参数为true。
"text": 返回纯文本字符串。
多个用空格分割的值:从 jQuery 1.5 开始, jQuery可以内容类型(Content-Type)头收到并转换一个您需要的数据类型。例如,如果你想要一个文本响应为XML处理,使用"text xml"数据类型。您也可以将一个JSONP的请求,以文本形式接受,并用jQuery以XML解析: "jsonp text xml"。同样地可以使用"jsonp xml"简写,首先会尝试从 jsonp 到 xml 的转换,如果转换失败,就先将 jsonp 转换成 text, 然后再由 text 转换成 xml。

2.2、不同的method组合不同的ContentType

我们在上面的代码中添加这样一段代码:

$.ajax({
    type: 'POST',
    url: 'http://blog.5udou.cn/test/first',
    data: finalRes,
    dataType: 'json',
    contentType: 'application/x-www-form-urlencoded',
    success: function(data){console.log(data)},
    error: function(jqXHR){console.log(jqXHR)},
})

几个关键的参数都已经配置,那么在method='POST'的时候,data部分是如何组装进请求中的呢?答案是:浏览器把form数据封装到http body中,然后发送到server,如图:

如果是methos='GET'的时候,则浏览器用x-www-form-urlencoded的编码方式把form数据转换成一个字串(name1=value1&name2=value2...),然后把这个字串append到url后面,用?分割,加载这个新的url。如图:

如果没有type=file的控件,用默认的application/x-www-form-urlencoded就可以了。 但是如果有type=file的话,就要用到multipart/form-data了。浏览器会把整个表单以控件为单位分割,并为每个部分加上Content-Disposition(form-data或者file),Content-Type(默认为text/plain),name(控件name)等信息,并加上分割符(boundary)。

2.3、特殊的application/json

如果你想要传递一个json Array的话,使用application/x-www-form-urlencoded是不行的,代码改成:

<script type="text/javascript">
    function onSubmit(){
        /*var finalRes = $('#form').serializeArray().reduce(function(result, item){
             result[item.name] = item.value;
            return result;
        }, {})
        console.log(finalRes)
        */
        $.ajax({
            type: 'POST',
            url: 'http://blog.5udou.cn/test/first',
            data: [{name: 'linxiaowu', pass: '123456'}, {name:'xiaomizha', pass:'123456'}],
            dataType: 'json',
            contentType: 'application/x-www-form-urlencoded',
            success: function(data){console.log(data)},
            error: function(jqXHR){console.log(jqXHR)},
        })
    }
    </script>

结果如下:

于是我们需要修改成这样:

<script type="text/javascript">
    function onSubmit(){
        /*var finalRes = $('#form').serializeArray().reduce(function(result, item){
             result[item.name] = item.value;
            return result;
        }, {})
        console.log(finalRes)
        */
        $.ajax({
            type: 'POST',
            url: 'http://blog.5udou.cn/test/first',
            data: [{name: 'linxiaowu', pass: '123456'}, {name:'xiaomizha', pass:'123456'}],
            dataType: 'json',
            contentType: 'application/json',
            success: function(data){console.log(data)},
            error: function(jqXHR){console.log(jqXHR)},
        })
    }
    </script>

结果如下:

咦,竟然报错!并且data的数据并没有加到request里面去,这是为什么?

其实控制台的错误在上面的例子都是有报错的,关键在于报错的顺序,为什么这么说呢?根据MDN的说明,我们发现:

  1. 如果使用的方法不是POST或者GET,或者使用了POST方法但是Content-Type的配置不是application/x-www-form-urlencoded, multipart/form-data, or text/plain中的任意一个
  2. 如果在请求中设置了自定义的头部(比如请求使用了类似X-PINGOTHER的头部)

上面两种情况都会执行preflighted request,也就是执行发送请求前的检查:"preflighted"请求首先会发送一个HTTP OPTIONS的请求头部到另外一个域下,这是为了决定实际的请求是不是可以安全地发送。

所以你在上图中看到红色框圈起来的METHOD为OPTIONS就是因为检测到你跨域了并且是使用的application/json + POST的请求方法,所以才会出现刚才的情况,找不到发送的数据,请求方法也是不对的。

那么针对这种情况,我们修改请求的URL为同源的即可,这里在本地假设一个Express服务器,于是有:

咦?还是有错误。这个时候就需要JSON.stringify函数出场了:

function onSubmit(){
        var data = JSON.stringify([{name: "linxiaowu", pass: "123456"}, {name:"xiaomizha", pass:"123456"}])
        $.ajax({
            type: 'POST',
            url: '/test/first',
            data: data,
            dataType: 'json',
            contentType: 'application/json',
            success: function(data){console.log(data)},
            error: function(jqXHR){console.log(jqXHR)},
        })
    }

这个时候结果就是我们想要的:

2.3.1、Tips

JSON.stringify turns a Javascript object into JSON text and stores that JSON text in a string.

JSON.parse turns a string of JSON text into a Javascript object.

2.3.2、问题

留给童鞋们一个问题:在上面的例子代码中直接将data的值赋给data,即:

$.ajax({
            type: 'POST',
            url: '/test/first',
            data: [{"name":"linxiaowu","pass":"123456"},{"name":"xiaomizha","pass":"123456"}],
            dataType: 'json',
            contentType: 'application/json',
            success: function(data){console.log(data)},
            error: function(jqXHR){console.log(jqXHR)},
        })

结果是错误的,识别不到这是一个JSON,这是为什么呢??

3、服务器的反应呢?

服务器这边的处理比较简单,可以直接判断请求的类型,如果不是想要地编码类型可以直接回应415(Unsupported Media Type),如果符合要求那么服务器将根据前后端约定的来获取请求的参数,以express为例子,获取参数有三种方法:官网介绍如下:

  1. Checks route params (req.params), ex: /user/:id
  2. Checks query string params (req.query), ex: ?id=12
  3. Checks urlencoded body params (req.body), ex: id=

分别举个例子:

  1. 例如:127.0.0.1:3000/index,这种情况下,我们为了得到index,我们可以通过使用req.params得到,通过这种方法我们就可以很好的处理Node中的路由处理问题,同时利用这点可以非常方便的实现MVC模式;
  2. 例如:127.0.0.1:3000/index?id=12,这种情况下,这种方式是获取客户端get方式传递过来的值,通过使用req.query.id就可以获得,类似于PHP的get方法;
  3. 例如:127.0.0.1:300/index,然后post了一个id=2的值,这种方式是获取客户端post过来的数据,可以通过req.body.id获取,类似于PHP的post方法;

4、后记

啊哈~~~最后写下来貌似还是有点逻辑的哈。看来文章有的时候还是需要在写的时候慢慢理清逻辑,貌似我get到了什么新技能。。。。

参考文章

  1. api.jquery.com/jQuery.ajax…
  2. www.css88.com/jqapi-1.9/j…
  3. tool.chinaz.com/pagestatus/