编程语言研究书库

表单 — 让用户把数据交回来

表单 — 让用户把数据交回来

这一章讲一件事: 网页怎么开口向用户要数据——登录框、搜索框、订餐页背后都是同一个机制。 它分四层:<form> 决定数据发给谁、怎么发;<input> 决定收哪种数据; label 和 fieldset 决定人读不读得懂;校验属性决定坏数据能不能出门。 这一章是 HTML 部分的顶点:从这里起,网页从「展示」变成「对话」。

1. 先看现象:填完一个框按下按钮,数据去了哪

你在搜索框敲「烤牛肉」按回车,地址栏变成 search.html?q=烤牛肉,结果页出来了—— 你的一个词,从浏览器飞到了某个服务器,服务器拿它查了数据库,把结果页发回来。 表单就是这条往返线的 HTML 入口:页面上所有的输入框、下拉、勾选,都得先装进一个 <form>, 浏览器才知道「这一堆框是一伙的,提交时打包一起走」。

这一章的主线例子,是书里给 Anna's Kitchen 做的订餐表单:姓名、邮箱、电话一组,日期、时间、人数一组, 提交给服务器上一个处理脚本。下面每一节都是它的一块拼图。

2. 顶层全景:一张表单的四层结构

  <form action="处理脚本的地址" method="POST">   ← 第 1 层:发给谁(action)、怎么发(method)
      <fieldset>                                ← 第 2 层:分组(fieldset + legend)
          <legend>Personal Information</legend>
          <label for="name">Name:</label>      ← 第 3 层:每个字段的说明(label)
          <input type="text" id="name" name="name" placeholder="Enter name" required>
                                                ↑ 第 4 层:收数据的控件本身(input 的 type + 属性)
      </fieldset>
      <input type="submit" value="提交">       ← 触发打包发送的按钮
  </form>

  图说:四层各管一摊——form 管路由,input 管收数据,label/fieldset 管可读性,校验属性管质量。

3. 第 1 层:action 与 method——数据的路由

<form> 有两个决定命运的属性1:

action(动作):数据发给谁。 值是一个服务器端脚本的地址。 书里演示了一个关键点:action 不填,提交按钮点了什么都没发生——浏览器不知道该发给谁,干脆不发2。

method(方式):怎么发。 二选一,GET 或 POST,书里给了非常清楚的分工34:

GETPOST
数据放在哪拼在网址后面(search.html?q=beef&page=1)藏在 HTTP 报文的身体里,网址上看不见
适合什么不敏感、量小的数据:搜索词、筛选条件敏感的、大的数据:密码、订单、会改服务器数据的东西
为什么网址本来就是给人看的,书签、分享、回退都靠它密码不该出现在网址栏里;大 payload 也不该挤进网址

为什么要有两种? 因为 HTTP 协议(第 01 章那个「浏览器和服务器说话用的协议」)把「读」和「写」分开了: GET 语义是「给我看看」,POST 语义是「拿去处理」。这个区分在浏览器行为上有实际后果—— 比如刷新一个 GET 页面浏览器会直接重发,而 POST 刷新时浏览器会警告「要重新提交表单吗」, 就是在防止你把「付款」这种事不小心做两遍(后半句是 ④ 类通用知识补充,书里没展开)。

数据发出去之后

书里补完了这条线的终点:服务器上一个脚本(Node.js、Python、PHP、C# 任选)接到数据, 存数据库或发邮件5。本书只演示最小版本——一个十几行的 Node.js 小程序在本地 (http://localhost:3000/submit)接住表单数据,命令行里打印出来给你看6。 后端的世界就此一句带过:这本书的地界到「数据离开浏览器」为止。

4. 第 4 层(先讲,它是主角):input 的 type——收哪种数据

<input> 是表单里千变万化的那个标签:它本身没有内容,全靠属性定义自己是什么。 最核心的属性是 type(类型),书里最常教的一批7:

type 值控件长什么样额外的本事
text单行文本框兜底类型
password输入时显示圆点防 shoulder surfing(旁边人偷看)
email文本框提交时检查格式里有没有 @ 和域名
tel文本框手机上唤出数字键盘
number数字框(带增减箭头)只收数字,可配 min/max/step
date日期选择器自带日历弹层
checkbox方框,可勾多个各选项互不排斥
radio圆点,一组选一个同 name 的 radio 互斥

type 不只是换皮。 type="email" 和 type="text" 视觉上都是框,但前者自带格式检查、 手机上唤出带 @ 的键盘——选对 type,浏览器免费送你一堆行为和校验,选错就全得自己写。 这是 HTML5(2014 年定稿的现行 HTML 标准)最重要的贡献之一: 过去这些校验全要靠 JavaScript 手搓,现在类型本身替你兜了一半。

三个必配的属性:id、name、placeholder

书里给文本框的完整模板是8:

<input type="text" id="name" name="name" placeholder="Enter your name">

三个属性,三个不同的服务对象——这是全章最容易混的一组词,一次分清:

属性服务谁干什么
id文档内部:label 的锚点、JavaScript 的选择器(第 12 章)在一个页面里唯一标识这个元素
name服务器:数据打包时的字段名提交后服务器按 name 取数据:name=Alice
placeholder用户:框里的浅灰色提示字「这里该填什么」的现场说明,一输入就消失

id 和 name 经常写同一个词,但它们是两个互不相干的属性——一个对内,一个对外。 第 12 章你会看到 document.getElementById("name") 用的是 id;服务器脚本收到的表单数据用的是 name。

required:最常用的一张校验牌

required(必需)属性没有值,写上即生效:提交时这个框空了,浏览器直接拦下、标红、给提示9。 一行属性顶过去几十行 JavaScript——这就是「HTML5 校验」存在意义的缩影。

5. 第 3 层:label——表单的「读得懂」工程

一个孤零零的输入框,用户不知道它要什么。<label>(标签文字)就是框旁边的「姓名:」「邮箱:」10:

<label for="name">Name:</label>
<input type="text" id="name" name="name">

for 属性值 = 对应 input 的 id 值,两行就此挂钩11。挂钩之后的好处远超「文字挨着框」:

  • 用户点「Name:」这行字,焦点自动跳进框里——可点击区域大了好几倍;
  • 屏幕阅读器读到这个框时,会连 label 一起念:「姓名,编辑框」——没有这个挂钩,视障用户只听到「编辑框」。

label 是「无障碍」性价比最高的一行代码:两个标签、一个 for,换来键盘可用、读屏可懂。

6. 第 2 层:fieldset 与 legend——把长表单切成段落

字段一多,表单就成了问卷地狱。<fieldset> 是框,<legend> 是框上的标题,一组字段包一个12:

<fieldset>
    <legend>Personal Information</legend>
    <label for="name">Name:</label> <input … required><br><br>
    <label for="email">Email:</label> <input … required><br><br>
    <label for="phone">Phone:</label> <input …><br><br>
</fieldset>

书里给 Anna's Kitchen 的订餐表单分了两组:个人信息组(姓名、邮箱、电话)和订餐信息组(日期、时间、人数)13。 分组的作用书里说了两层:视觉上的分段;逻辑上的「哪些字段是一伙的」。 第 10 章会教你用 CSS 把 fieldset 的默认边框改造掉,变成现代样子——结构归 HTML,样子归 CSS,又一次兑现。

(书里用 <br><br> 换行和留白——这是教学捷径,不是布局正道;第 08 章之后你会用 flexbox 干这件事。)

7. 校验的全套属性:浏览器当门卫

HTML5 校验的思路是:在数据离开浏览器之前,由浏览器这个门卫先过一遍14。 门卫的工具箱,书里给全了15:

属性拦什么例子
required空字段<input … required>
type 本身格式不对(邮箱/数字/URL/日期)type="email"
min / max数值或日期越界min="18" max="99"
minlength / maxlength字数不够/超了minlength="8" maxlength="16"
pattern不匹配指定格式pattern="[0-9]{5}"(五位数字,如邮编)
step数字不按步进step="1"(整数)

pattern 用的是正则表达式——一种用一串符号描述「文本长什么样」的小语言, [0-9]{5} 读作「0 到 9 的数字,恰好 5 个」。正则本身是一门学问,本书只点到为止; 记住「pattern 接受这种格式,搜『正则入门』可以日后补」就够了。

校验的边界要说透: HTML5 校验是用户体验的第一道闸,不是安全的闸门。 它拦的是「手滑、看错、少填」;拦不住恶意——绕过它直接给服务器发数据,一行命令的事。 真正的安全校验必须在服务器端再做一遍——这是 ④ 类通用知识,书里没明说,但必须补, 否则读者会以为写了 required 就安全了。

判断(我们的,不是书里的): 这一章里最值得带走的设计观是「声明式校验」: 你不写「如果为空就弹窗」的逻辑,只声明「这个字段必填」,浏览器自己执行检查。 这是声明式语言(HTML/CSS 这一类)的共同套路——你描述规则,引擎负责执行。 理解了它,第 07 章 CSS 的「你描述想要的样子,浏览器算出怎么画」就不再陌生。 如果错,会错在: 声明式方案的天花板是「浏览器预置的那些规则」; 需求一旦超出(「密码必须同时含大小写和符号」这种复合规则),声明式就兜不住, 得回到 JavaScript——所以第 12 章之后你才会拥有完整的校验工具箱。

8. 主线例子走一遍:订一张桌子的数据之旅

把全章拼起来,Anna's Kitchen 的订餐表单走一遍完整旅程:

  用户在页面填:Name=Li Ming, Email=liming@example.com, Date=2026-11-01
      │ <form action="localhost:3000/submit" method="POST">
      │ 浏览器门卫先查:required 的姓名、邮箱都填了?邮箱格式对?→ 有一个不过就拦下标红
      ▼ 全部通过
  浏览器把数据打包成 name=Li+Ming&email=liming%40example.com&date=2026-11-01
      │ 藏进 POST 报文的身体,发给 action 指定的脚本
      ▼
  服务器脚本收到,命令行打印出这三个字段 → 演示到此为止(真实世界:存库或发邮件)

  图说:这张图上的每一步,本章都讲过一件对应的家伙——门卫是校验属性,打包规则由 method 定,
       路由由 action 定,字段名来自 name。

(这个例子里的人名、邮箱、日期是为演示编的,用来让每一步旁边的数据具体可见。)

9. 作者的判断与证据

书内作为事实给出的: GET/POST 的分工与数据位置34、id/name/placeholder 三属性分工8、 label 的 for=id 挂钩机制11、fieldset/legend 的分组作用12、六个校验属性的含义15。

作者的取舍,值得意识到: 这一章把「表单处理」讲成了一段演示脚本(本地 Node.js 接收数据、打印出来), 而不是一个真实后端——这是入门书合理的简化(把复杂的后端世界压小给你看),但你要清楚真实世界的处理脚本长得多: 安全检查、多步写入的一致性(几步操作要么全成、要么全不成)、出错页面,一个都少不了。 书里也明说了这只是「simple demonstration」16。

一处措辞要小心的: 书说 GET「data visibility is not a concern」时用于是搜索词, 容易让人以为「只有密码才用 POST」。其实判据是这条提交会不会改变服务器上的东西(下订单、改资料), 会就该 POST——搜索不该 POST,密码绝不 GET。这个「读写之分」书隐含了但没点破,我们补在 §3。

10. 边界与局限

  • 校验全靠浏览器默认提示框,样式、文案都改不动(定制它要 JavaScript,第 12 章的地盘)。
  • 没讲下拉框 <select>、多行文本 <textarea>、文件上传——章末总结里提了「drop-down menus」17但正文没教,是本书的小缺口;遇到时查 MDN 即可。
  • 没讲 <button> 与 input type="submit" 的区别,两者提交表单等价,语义略有差异。
  • 后端处理是最小演示,如 §9 所说,别把 server.js 当生产模板。

11. 可带走的

全章主线例子,一行写完: 填表 → 浏览器校验(required/type/min/max/pattern)→ 打包(name=value)→ 按 method 发向 action → 服务器脚本接住(存库或发邮件)。

  1. 表单 = <form> 包住所有控件;action 定发给谁,method 定怎么发1;
  2. GET 拼在网址上(适合「读」:搜索、筛选),POST 藏在报文体里(适合「写」:密码、订单)34;
  3. <input type="…"> 的类型决定控件和行为:选对 type,浏览器白送你校验和键盘;
  4. id 对内(label、脚本),name 对外(服务器取数据的字段名),placeholder 服务用户——三个互不相干8;
  5. <label for="id"> 挂钩,点字进框、读屏可懂,无障碍性价比最高的一行;
  6. <fieldset> + <legend> 给长表单分段;
  7. 校验六件套:required、type、min/max、minlength/maxlength、pattern、step15;
  8. HTML5 校验拦手滑不拦恶意,服务器端必须再校验一遍。

12. 原文地图

主题原书章原文位置
GET 与 POST 两种提交方式HTML Forms: Collecting User Inputtext/12-fm-introduction.txt:41(搜「two submission methods」)
GET:数据拼进网址HTML Forms: Collecting User Inputtext/12-fm-introduction.txt:43(搜「appends form data as a query」)
POST:数据在报文体HTML Forms: Collecting User Inputtext/12-fm-introduction.txt:47(搜「separate HTTP message body」)
form 的 action 与 methodHTML Forms: Collecting User Inputtext/12-fm-introduction.txt:55(搜「action attribute」)
action 缺失则提交无反应HTML Forms: Collecting User Inputtext/12-fm-introduction.txt:173(搜「nothing will happen」)
服务器端脚本处理数据HTML Forms: Collecting User Inputtext/12-fm-introduction.txt:51(搜「processed by a script」) · text/12-fm-introduction.txt:179(搜「localhost:3000」)
id / name / placeholder 分工HTML Forms: Collecting User Inputtext/12-fm-introduction.txt:103(搜「used to identify the input」)
label 与 for 挂钩HTML Forms: Collecting User Inputtext/12-fm-introduction.txt:119(搜「for attribute in the opening」)
fieldset 与 legend 分组HTML Forms: Collecting User Inputtext/12-fm-introduction.txt:131(搜「fieldset」) · text/12-fm-introduction.txt:133(搜「Personal details」)
HTML5 校验的总纲HTML Forms: Collecting User Inputtext/12-fm-introduction.txt:199(搜「HTML form validation ensures」)
required 属性HTML Forms: Collecting User Inputtext/12-fm-introduction.txt:201(搜「required attribute makes」)
pattern 与正则HTML Forms: Collecting User Inputtext/12-fm-introduction.txt:217(搜「pattern attribute」)

Footnotes

  1. 出处:「HTML Forms: Collecting User Input」第 55 段(text/12-fm-introduction.txt:55,搜「action attribute」)。原文:「This element contains all the form fields and defines a submission method using the action attribute.」 ↩ ↩2

  2. 出处:「HTML Forms: Collecting User Input」第 173 段(text/12-fm-introduction.txt:173,搜「nothing will happen」)。原文:「If we click on the submit button in our form, nothing will happen. This is because we have not set the action attribute.」 ↩

  3. 出处:「HTML Forms: Collecting User Input」第 43 段(text/12-fm-introduction.txt:43,搜「appends form data as a query」)。原文:「The GET method appends form data as a query parameter to the URL.」并说明适合搜索词、筛选这类不敏感数据。 ↩ ↩2 ↩3

  4. 出处:「HTML Forms: Collecting User Input」第 47 段(text/12-fm-introduction.txt:47,搜「separate HTTP message body」)。原文:「The POST method sends form data as a separate HTTP message body, which means the data is not visible in the URL.」 ↩ ↩2 ↩3

  5. 出处:「HTML Forms: Collecting User Input」第 51 段(text/12-fm-introduction.txt:51,搜「processed by a script」)。原文点名 Node.js、Python、PHP、C#,数据可存数据库或发邮件。 ↩

  6. 出处:「HTML Forms: Collecting User Input」第 177–189 段(text/12-fm-introduction.txt:179,搜「localhost:3000」)。原文:「In the following demo, a JavaScript Node.js app is running to process the form data.」 ↩

  7. 出处:「HTML Forms: Collecting User Input」第 79–97 段(text/12-fm-introduction.txt:81,搜「Text field」;:87,搜「Radio button」)。书里列的清单:text、password、email、number、tel、URL、checkbox、radio。 ↩

  8. 出处:「HTML Forms: Collecting User Input」第 103 段(text/12-fm-introduction.txt:103,搜「used to identify the input」)。原文对三个属性各给了一句定义,本节的「服务谁」三分是按原文定义整理的。 ↩ ↩2 ↩3

  9. 出处:「HTML Forms: Collecting User Input」第 113–115 段(text/12-fm-introduction.txt:113,搜「make the field mandatory」;:115,搜「required」)。 ↩

  10. 出处:「HTML Forms: Collecting User Input」第 119 段(text/12-fm-introduction.txt:119,搜「so the user knows what information」)。 ↩

  11. 出处:「HTML Forms: Collecting User Input」第 119 段(text/12-fm-introduction.txt:119,搜「for attribute in the opening」)。原文:「set this to match the ID attribute of the field we are labelling」。 ↩ ↩2

  12. 出处:「HTML Forms: Collecting User Input」第 131 段(text/12-fm-introduction.txt:131,搜「fieldset」)。原文:「To group fields, the <fieldset> and <legend> elements are used.」 ↩ ↩2

  13. 出处:「HTML Forms: Collecting User Input」第 133 段(text/12-fm-introduction.txt:133,搜「personal details」)。原文把姓名、邮箱、电话归入一个 fieldset,日期、时间、人数归入另一个。 ↩

  14. 出处:「HTML Forms: Collecting User Input」第 199 段(text/12-fm-introduction.txt:199,搜「HTML form validation ensures」)。原文:「These attributes are built into HTML5, making it easy to enforce input rules directly within the HTML code.」 ↩

  15. 出处:「HTML Forms: Collecting User Input」第 201–223 段(text/12-fm-introduction.txt:201,搜「required attribute makes」;:217,搜「pattern attribute」;:223,搜「step attribute」)。六个属性的定义与例子均出自这几段。 ↩ ↩2 ↩3

  16. 出处:「HTML Forms: Collecting User Input」第 195 段(text/12-fm-introduction.txt:195,搜「simple demonstration」)。 ↩

  17. 出处:「HTML Forms: Collecting User Input」第 227 段(text/12-fm-introduction.txt:227,搜「drop-down menus」)。正文未展开的下拉框在此被一笔带过,照实标为本章缺口。 ↩

On this page