form 表单标签
<form> 是 HTML 表单的核心容器元素,用于收集用户输入并将其提交到服务器。几乎所有用户交互数据的收集——从登录、注册到搜索、评论——都离不开 <form> 元素。本节将全面介绍 <form> 元素的基础概念、核心属性以及表单嵌套规则。
前置知识
阅读本节前,建议先了解:无障碍表格设计
基础概念
什么是表单
表单(Form)是 Web 应用中用户与应用程序进行数据交互的核心机制。<form> 元素作为一个容器,将各种表单控件(如输入框、按钮、下拉框等)组织在一起,形成一个完整的数据提交单元。
表单的基本工作流程:
- 用户在表单控件中填写或选择数据
- 用户点击提交按钮(或通过其他方式触发表单提交)
- 浏览器收集所有表单控件的数据
- 浏览器按照指定的编码方式和请求方法将数据发送到目标 URL
- 服务器处理数据并返回响应
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>最简单的表单</title>
</head>
<body>
<!-- 一个最基础的表单 -->
<form action="/submit" method="post">
<label for="username">用户名:</label>
<input type="text" id="username" name="username">
<label for="pwd">密码:</label>
<input type="password" id="pwd" name="pwd">
<button type="submit">登录</button>
</form>
</body>
</html>form 元素在 DOM 中的角色
<form> 元素在 DOM 中属于 流式内容(Flow Content) 和 可感知内容(Palpable Content),可以出现在 <body> 内部大多数允许流式内容的位置。同时,它也是一个 表单相关元素(Form-associated element) 的上下文,内部包含的表单控件会自动归属于该表单。
规范定义
在 HTML 规范中,<form> 的内容模型允许包含流式内容,但不能包含另一个 <form> 元素(即表单不能嵌套)。
语法
基本语法结构
<form
action="提交地址"
method="请求方法"
enctype="编码类型"
name="表单名称"
id="表单ID"
target="目标窗口"
autocomplete="自动补全"
novalidate
rel="链接关系"
>
<!-- 表单控件 -->
</form>核心属性一览
| 属性 | 值 | 说明 |
|---|---|---|
action | URL 字符串 | 表单提交的目标地址 |
method | get / post / dialog | 提交使用的 HTTP 方法 |
enctype | 编码类型字符串 | 提交数据的编码方式 |
name | 字符串 | 表单的名称,用于 JS 和旧版 DOM 访问 |
id | 字符串 | 表单的唯一标识符 |
target | _self / _blank / _parent / _top | 提交后的响应显示位置 |
autocomplete | on / off | 是否启用浏览器的自动补全 |
novalidate | 布尔属性 | 提交时是否跳过浏览器内置验证 |
rel | 空格分隔的链接类型 | 提交链接的关系说明 |
详细说明
name 属性
name 属性为表单命名,主要用于以下场景:
- JavaScript 中通过名称访问表单:
document.forms['myForm']可以直接获取表单元素 - 旧版 DOM 接口:
document.forms是一个 HTMLCollection,通过name建立索引 - 表单标识:在包含多个表单的页面中区分不同表单
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>form name 属性</title>
</head>
<body>
<form name="loginForm" id="loginForm">
<input type="text" name="username">
<button type="submit">提交</button>
</form>
<form name="searchForm" id="searchForm">
<input type="text" name="keyword">
<button type="submit">搜索</button>
</form>
<script>
// 通过 name 访问表单
const login = document.forms['loginForm'];
console.log(login.name); // 输出:loginForm
// 通过 id 访问表单(更推荐)
const search = document.getElementById('searchForm');
console.log(search.name); // 输出:searchForm
</script>
</body>
</html>建议
在现代开发中,推荐使用 id 而非 name 来标识表单元素。name 主要用于向后兼容和特定场景(如 document.forms 集合)。
id 属性
id 属性为表单提供全局唯一的标识符,主要用途:
- CSS 选择器:通过
#myForm为表单添加样式 - JavaScript 获取元素:
document.getElementById('myForm') - label 的 for 属性关联:虽然 label 通常关联具体控件,但在某些框架中也可能引用表单
- 表单控件的 form 属性:控件可以通过
form="formId"关联到不在其父级中的表单
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>form id 属性</title>
<style>
/* 通过 id 选择器设置表单样式 */
#registerForm {
max-width: 400px;
margin: 20px auto;
padding: 20px;
border: 1px solid #ddd;
border-radius: 8px;
}
#registerForm label {
display: block;
margin-top: 10px;
font-weight: bold;
}
</style>
</head>
<body>
<form id="registerForm" action="/register" method="post">
<label for="email">邮箱:</label>
<input type="email" id="email" name="email" required>
<label for="password">密码:</label>
<input type="password" id="password" name="password" required>
<button type="submit">注册</button>
</form>
<!-- 表单控件可以在 form 外部,通过 form 属性关联 -->
<input type="hidden" name="source" value="landing-page" form="registerForm">
<script>
const form = document.getElementById('registerForm');
console.log(form.id); // 输出:registerForm
</script>
</body>
</html>表单控件的 form 属性关联
HTML5 引入了 form 属性,允许表单控件在 <form> 元素外部定义,但通过 form 属性关联到指定表单。这在布局上有更大的灵活性。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>控件外部关联表单</title>
</head>
<body>
<!-- 表单容器 -->
<form id="orderForm" action="/order" method="post">
<h2>订单信息</h2>
<label for="product">商品名称:</label>
<input type="text" id="product" name="product">
</form>
<!-- 提交按钮在表单外部,通过 form 属性关联 -->
<div class="actions" style="margin-top: 10px;">
<!-- 这个按钮提交 orderForm -->
<button type="submit" form="orderForm">提交订单</button>
</div>
<!-- 这个输入框也属于 orderForm -->
<input type="hidden" name="token" value="abc123" form="orderForm">
</body>
</html>注意
form 属性的值必须与目标 <form> 的 id 完全匹配。如果引用的 id 不存在,该控件不会参与任何表单的提交。
表单嵌套规则
HTML 规范明确禁止表单嵌套。一个 <form> 元素内部不能包含另一个 <form> 元素。这是由于表单嵌套会导致数据提交时的歧义——浏览器无法判断内部表单的提交行为应该归属于哪个外层表单。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>表单嵌套规则</title>
</head>
<body>
<!-- 正确:两个独立的表单 -->
<form id="searchForm" action="/search" method="get">
<label for="keyword">搜索:</label>
<input type="search" id="keyword" name="q">
<button type="submit">搜索</button>
</form>
<form id="loginForm" action="/login" method="post">
<label for="user">用户名:</label>
<input type="text" id="user" name="user">
<button type="submit">登录</button>
</form>
<!-- 错误!表单不能嵌套 -->
<!--
<form action="/outer">
<form action="/inner">
...
</form>
</form>
-->
</body>
</html>如果确实需要在一个页面中实现类似"表单内嵌表单"的效果,可以使用以下替代方案:
方案一:使用多个独立表单
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>多表单方案</title>
</head>
<body>
<h2>用户中心</h2>
<!-- 修改个人信息的表单 -->
<section>
<h3>修改个人信息</h3>
<form id="profileForm" action="/profile" method="post">
<label for="nickname">昵称:</label>
<input type="text" id="nickname" name="nickname">
<button type="submit">保存资料</button>
</form>
</section>
<!-- 修改密码的表单 -->
<section>
<h3>修改密码</h3>
<form id="passwordForm" action="/change-password" method="post">
<label for="oldPwd">旧密码:</label>
<input type="password" id="oldPwd" name="old_password">
<label for="newPwd">新密码:</label>
<input type="password" id="newPwd" name="new_password">
<button type="submit">修改密码</button>
</form>
</section>
</body>
</html>方案二:使用 form 属性将控件关联到不同表单
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>form 属性替代嵌套</title>
</head>
<body>
<!-- 两个表单,控件通过 form 属性分别关联 -->
<form id="mainForm" action="/main" method="post"></form>
<form id="subForm" action="/sub" method="post"></form>
<div>
<h3>主表单控件</h3>
<input type="text" name="field1" form="mainForm">
<button type="submit" form="mainForm">提交主表单</button>
</div>
<div>
<h3>子表单控件</h3>
<input type="text" name="field2" form="subForm">
<button type="submit" form="subForm">提交子表单</button>
</div>
</body>
</html>表单中的事件
<form> 元素支持多种事件,常用于在提交前进行验证或拦截提交行为:
| 事件 | 触发时机 | 常见用途 |
|---|---|---|
submit | 表单提交时(点击提交按钮或按回车) | 验证数据、拦截提交、发送 AJAX 请求 |
reset | 表单重置时(点击重置按钮) | 清理状态 |
formdata | 构建 FormData 对象时 | 获取表单数据 |
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>表单事件</title>
</head>
<body>
<form id="eventForm" action="/submit" method="post">
<label for="name">姓名:</label>
<input type="text" id="name" name="name" required>
<label for="email">邮箱:</label>
<input type="email" id="email" name="email" required>
<button type="submit">提交</button>
<button type="reset">重置</button>
</form>
<div id="message"></div>
<script>
const form = document.getElementById('eventForm');
const message = document.getElementById('message');
// 监听表单提交事件
form.addEventListener('submit', function(event) {
// 阻止默认的表单提交行为
event.preventDefault();
// 获取表单数据
const formData = new FormData(form);
const name = formData.get('name');
const email = formData.get('email');
// 验证逻辑
if (name.length < 2) {
message.textContent = '姓名至少需要2个字符';
message.style.color = 'red';
return;
}
message.textContent = '验证通过,准备提交...';
message.style.color = 'green';
// 可以在这里使用 fetch 发送 AJAX 请求
// fetch('/submit', { method: 'POST', body: formData });
});
// 监听表单重置事件
form.addEventListener('reset', function() {
message.textContent = '表单已重置';
message.style.color = 'gray';
});
</script>
</body>
</html>表单的 DOM 接口
<form> 元素在 JavaScript 中对应 HTMLFormElement 接口,提供了丰富的属性和方法:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>表单 DOM 接口</title>
</head>
<body>
<form id="apiForm" name="myForm" action="/api" method="post">
<input type="text" name="username" value="zhangsan">
<input type="email" name="email" value="zhangsan@example.com">
<button type="submit">提交</button>
</form>
<script>
const form = document.getElementById('apiForm');
// ---- 只读属性 ----
console.log(form.elements); // HTMLFormControlsCollection,所有表单控件
console.log(form.length); // 表单控件的数量
console.log(form.action); // 提交地址
console.log(form.method); // 请求方法
console.log(form.encoding); // 等同于 enctype
console.log(form.enctype); // 编码类型
// ---- 可读写属性 ----
form.action = '/new-api'; // 修改提交地址
form.method = 'get'; // 修改请求方法
form.target = '_blank'; // 在新窗口打开
// ---- 方法 ----
// 提交表单(不触发 submit 事件中的验证)
// form.submit();
// 重置表单(触发 reset 事件)
// form.reset();
// 检查表单是否通过验证
console.log(form.checkValidity()); // true 或 false
// ---- 访问表单控件 ----
// 通过 name 或 id 访问
console.log(form.elements['username'].value); // 输出:zhangsan
console.log(form.elements['email'].value); // 输出:zhangsan@example.com
// 通过索引访问
console.log(form.elements[0].value); // 输出:zhangsan
// 通过点属性访问(name 属性作为属性名)
console.log(form.username.value); // 输出:zhangsan
</script>
</body>
</html>实战示例
完整的登录表单
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>登录表单</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
margin: 0;
background-color: #f5f5f5;
}
.login-card {
background: white;
padding: 40px;
border-radius: 12px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
width: 360px;
}
.login-card h1 {
font-size: 24px;
margin-bottom: 24px;
text-align: center;
}
.form-group {
margin-bottom: 16px;
}
.form-group label {
display: block;
margin-bottom: 6px;
font-weight: 500;
color: #333;
}
.form-group input {
width: 100%;
padding: 10px 12px;
border: 1px solid #d0d0d0;
border-radius: 6px;
font-size: 14px;
box-sizing: border-box;
}
.form-group input:focus {
outline: none;
border-color: #4a90d9;
box-shadow: 0 0 0 3px rgba(74, 144, 217, 0.15);
}
.btn-login {
width: 100%;
padding: 12px;
background-color: #4a90d9;
color: white;
border: none;
border-radius: 6px;
font-size: 16px;
cursor: pointer;
}
.btn-login:hover {
background-color: #3a7bc8;
}
</style>
</head>
<body>
<div class="login-card">
<h1>用户登录</h1>
<form id="loginForm" action="/api/login" method="post" name="login">
<div class="form-group">
<label for="username">用户名</label>
<input
type="text"
id="username"
name="username"
placeholder="请输入用户名"
required
minlength="3"
maxlength="20"
autocomplete="username"
>
</div>
<div class="form-group">
<label for="password">密码</label>
<input
type="password"
id="password"
name="password"
placeholder="请输入密码"
required
minlength="6"
autocomplete="current-password"
>
</div>
<button type="submit" class="btn-login">登录</button>
</form>
</div>
</body>
</html>使用 JavaScript 拦截表单提交
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>AJAX 表单提交</title>
</head>
<body>
<form id="ajaxForm" action="/api/contact" method="post">
<label for="name">姓名:</label>
<input type="text" id="name" name="name" required>
<label for="message">留言:</label>
<textarea id="message" name="message" rows="4" required></textarea>
<button type="submit">发送留言</button>
</form>
<div id="result"></div>
<script>
const form = document.getElementById('ajaxForm');
const result = document.getElementById('result');
form.addEventListener('submit', async function(e) {
e.preventDefault(); // 阻止默认提交
// 检查浏览器验证
if (!form.checkValidity()) {
form.reportValidity(); // 显示验证提示
return;
}
try {
// 构建 FormData
const formData = new FormData(form);
// 发送 AJAX 请求
const response = await fetch(form.action, {
method: form.method,
body: formData
});
const data = await response.json();
if (response.ok) {
result.textContent = '留言发送成功!';
result.style.color = 'green';
form.reset(); // 清空表单
} else {
result.textContent = '发送失败:' + data.message;
result.style.color = 'red';
}
} catch (error) {
result.textContent = '网络错误,请重试';
result.style.color = 'red';
}
});
</script>
</body>
</html>注意事项
name 属性不能省略:表单控件的
name属性决定了提交时数据的键名。没有name的控件不会随表单提交。id 必须唯一:同一页面中,每个元素的
id必须唯一,否则会导致 JavaScript 获取错误元素和 label 关联异常。表单不能嵌套:浏览器会忽略嵌套的
<form>标签或产生不可预期的行为。如果需要"表单中的表单",使用form属性关联外部表单。form 属性的兼容性:
form属性在所有现代浏览器中都得到支持,但在非常旧的浏览器(如 IE11)中不支持。submit 事件的默认行为:
submit事件触发时,如果不调用event.preventDefault(),浏览器会执行默认的表单提交(页面跳转)。使用 AJAX 提交时务必阻止默认行为。form.submit() 不触发验证:通过 JavaScript 调用
form.submit()方法会直接提交表单,不会触发submit事件,也不会执行浏览器内置验证。如果需要验证,应先调用form.checkValidity()或form.reportValidity()。
最佳实践
始终为表单和控件提供
id:便于 CSS 样式和 JavaScript 操作,也便于<label>的for属性关联。为表单控件设置有意义的
name:name会出现在提交的数据中,应该使用符合后端接口约定的命名。使用语义化的表单结构:结合
<fieldset>、<legend>对表单控件进行分组,提高可读性和无障碍性。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>语义化表单</title>
</head>
<body>
<form action="/register" method="post">
<!-- 用 fieldset 和 legend 对相关控件分组 -->
<fieldset>
<legend>基本信息</legend>
<label for="realname">真实姓名:</label>
<input type="text" id="realname" name="realname" required>
<label for="email">电子邮箱:</label>
<input type="email" id="email" name="email" required>
</fieldset>
<fieldset>
<legend>安全设置</legend>
<label for="pwd">设置密码:</label>
<input type="password" id="pwd" name="password" required>
<label for="pwdConfirm">确认密码:</label>
<input type="password" id="pwdConfirm" name="password_confirm" required>
</fieldset>
<button type="submit">注册</button>
</form>
</body>
</html>使用
form属性实现灵活布局:当提交按钮需要与表单控件分离时,使用form属性而非嵌套表单。统一使用
id或name来标识表单:不要混用,保持代码风格一致。现代项目推荐使用id。
下一节
继续学习:action 与 method