action 与 method
action 和 method 是 <form> 元素的两个核心属性,决定了表单数据发送到哪里以及如何发送。理解这两个属性的工作机制,是构建正确、安全、高效的表单的基础。本节将详细对比 GET 与 POST 两种提交方式,讲解 action URL 的各种写法,以及 FormData 对象的使用方法。
前置知识
阅读本节前,建议先了解:form 表单标签
基础概念
action 属性
action 属性指定表单数据提交的目标 URL。当用户提交表单时,浏览器会将数据发送到这个地址。
<!-- 提交到指定 URL -->
<form action="https://example.com/api/login" method="post">
...
</form>method 属性
method 属性指定发送数据使用的 HTTP 请求方法。HTML 表单支持三种方法:
| 方法 | 说明 | 数据位置 |
|---|---|---|
get | 数据附加到 URL 查询字符串 | URL 中 |
post | 数据放在请求体中 | 请求体中 |
dialog | 关闭对话框,不发送数据 | 无 |
语法
action 的取值方式
action 属性可以接受以下几种值:
<!-- 绝对 URL -->
<form action="https://example.com/submit" method="post">
<!-- 相对 URL(当前路径下) -->
<form action="/api/register" method="post">
<!-- 相对 URL(当前目录下) -->
<form action="submit.php" method="post">
<!-- 空字符串:提交到当前页面 URL -->
<form action="" method="post">
<!-- 省略 action:提交到当前页面 URL -->
<form method="post">
<!-- JavaScript URI(不推荐) -->
<!-- <form action="javascript:void(0)"> -->method 的语法
<!-- GET 请求(默认值) -->
<form action="/search" method="get">
<!-- POST 请求 -->
<form action="/login" method="post">
<!-- 关闭对话框 -->
<form method="dialog">详细说明
GET vs POST 完整对比
| 对比维度 | GET | POST |
|---|---|---|
| 数据位置 | URL 查询字符串(?key=value&...) | HTTP 请求体(Request Body) |
| URL 可见性 | 数据暴露在地址栏 | 数据不可见于 URL |
| 数据长度 | 受 URL 长度限制(约 2048 字符) | 理论上无限制,实际受服务器配置限制 |
| 数据类型 | 仅支持 ASCII 字符(非 ASCII 会被编码) | 支持任意类型(含二进制文件) |
| 安全性 | 低(数据在 URL 中,会出现在日志、历史记录中) | 较高(数据在请求体中) |
| 缓存 | 可被浏览器缓存和收藏 | 不会被缓存 |
| 幂等性 | 幂等(相同请求结果相同) | 非幂等(可能产生不同结果) |
| 书签 | 可被添加为书签(含参数) | 不可被添加为书签 |
| 回退/刷新 | 安全(浏览器会提示重新提交) | 可能导致重复提交 |
| 适用场景 | 搜索、筛选、分页 | 登录、注册、修改数据、文件上传 |
GET 请求的工作方式
当表单使用 GET 方法提交时,浏览器会将所有表单数据编码为 key=value 对,用 & 连接,附加到 action URL 后面,以 ? 分隔。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>GET 请求示例</title>
</head>
<body>
<form action="/search" method="get">
<label for="keyword">搜索关键词:</label>
<input type="text" id="keyword" name="q" value="HTML教程">
<label for="category">分类:</label>
<select id="category" name="cat">
<option value="all">全部</option>
<option value="tutorial" selected>教程</option>
<option value="article">文章</option>
</select>
<label for="sort">排序:</label>
<select id="sort" name="sort">
<option value="relevance">相关度</option>
<option value="date" selected>最新</option>
</select>
<button type="submit">搜索</button>
</form>
<!--
提交后浏览器地址栏将变为:
/search?q=HTML%E6%95%99%E7%A8%8B&cat=tutorial&sort=date
服务器接收到的查询字符串:
q=HTML教程&cat=tutorial&sort=date
-->
</body>
</html>编码说明
GET 请求中的非 ASCII 字符(如中文)会被自动编码为 %XX 格式(URL 编码 / Percent-encoding)。空格会被编码为 + 或 %20。
POST 请求的工作方式
当表单使用 POST 方法提交时,数据放在 HTTP 请求体中,不会出现在 URL 中。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>POST 请求示例</title>
</head>
<body>
<form action="/api/register" method="post">
<label for="username">用户名:</label>
<input type="text" id="username" name="username" required>
<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>
<!--
提交后的 HTTP 请求:
POST /api/register HTTP/1.1
Host: example.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 65
username=zhangsan&email=zhangsan%40example.com&password=secret123
-->
</body>
</html>无 action 时的默认行为
当 action 属性被省略或设置为空字符串时,表单会提交到当前页面的 URL。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>无 action 的表单</title>
</head>
<body>
<!--
假设当前页面 URL 为:https://example.com/user/profile
以下三种写法效果相同,都提交到当前页面:
-->
<!-- 方式一:省略 action -->
<form method="post">
<input type="text" name="nickname">
<button type="submit">保存</button>
</form>
<!-- 方式二:action 为空字符串 -->
<form action="" method="post">
<input type="text" name="nickname">
<button type="submit">保存</button>
</form>
<!-- 方式三:action 为当前页面 URL(效果相同) -->
<form action="/user/profile" method="post">
<input type="text" name="nickname">
<button type="submit">保存</button>
</form>
</body>
</html>注意
如果省略 action 且使用 GET 方法,表单数据会附加到当前 URL 的查询参数中。如果当前 URL 本身已有查询参数,新参数会追加到后面(原有参数保留)。
method="dialog" 提交方式
HTML5 新增的 dialog 方法专门用于 <dialog> 元素中的表单提交。它不会将数据发送到服务器,而是关闭当前对话框。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>dialog 提交方式</title>
</head>
<body>
<button id="openDialog">打开确认对话框</button>
<dialog id="confirmDialog">
<form method="dialog">
<h3>确认操作</h3>
<p>你确定要删除这条记录吗?</p>
<!-- value 属性的值会通过 returnValue 返回 -->
<button type="submit" value="confirm">确定</button>
<button type="submit" value="cancel">取消</button>
</form>
</dialog>
<div id="result"></div>
<script>
const dialog = document.getElementById('confirmDialog');
const openBtn = document.getElementById('openDialog');
const result = document.getElementById('result');
openBtn.addEventListener('click', () => {
dialog.showModal();
});
dialog.addEventListener('close', () => {
// returnValue 就是点击的按钮的 value
if (dialog.returnValue === 'confirm') {
result.textContent = '用户确认了操作';
} else {
result.textContent = '用户取消了操作';
}
});
</script>
</body>
</html>FormData 对象
FormData 是一个 JavaScript API,用于方便地构建表单数据。它可以将 <form> 元素的所有数据一键收集,也可以手动创建和操作。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>FormData 使用</title>
</head>
<body>
<form id="uploadForm" action="/api/upload" method="post" enctype="multipart/form-data">
<label for="title">标题:</label>
<input type="text" id="title" name="title" value="测试文件">
<label for="file">文件:</label>
<input type="file" id="file" name="attachment">
<button type="submit">上传</button>
</form>
<div id="output"></div>
<script>
const form = document.getElementById('uploadForm');
const output = document.getElementById('output');
form.addEventListener('submit', function(e) {
e.preventDefault();
// 方式一:从 form 元素直接创建 FormData
const formData = new FormData(form);
// 方式二:手动创建 FormData
// const formData = new FormData();
// formData.append('title', '测试文件');
// formData.append('attachment', fileInput.files[0]);
// 遍历 FormData 中的数据
for (const [key, value] of formData.entries()) {
output.textContent += `${key}: ${value}\n`;
}
// 检查是否包含某个字段
console.log(formData.has('title')); // true
console.log(formData.has('missing')); // false
// 获取某个字段的值
console.log(formData.get('title')); // 输出:测试文件
// 获取某个字段的所有值(用于同名字段)
// formData.getAll('tags');
// 删除某个字段
// formData.delete('title');
// 修改某个字段
// formData.set('title', '新标题');
// 发送 AJAX 请求
fetch('/api/upload', {
method: 'POST',
body: formData // 注意:不要手动设置 Content-Type,浏览器会自动处理
}).then(response => {
output.textContent += '\n文件上传完成';
});
});
</script>
</body>
</html>重要
使用 fetch 发送 FormData 时,不要手动设置 Content-Type 请求头。浏览器会自动设置正确的 Content-Type(包含 boundary 字符串)。手动设置会导致服务器无法正确解析 multipart 数据。
FormData 的进阶用法
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>FormData 进阶</title>
</head>
<body>
<form id="advancedForm">
<label for="username">用户名:</label>
<input type="text" id="username" name="username" value="张三">
<!-- 同名字段:多个值 -->
<label>兴趣标签:</label>
<input type="checkbox" name="tags" value="html" checked> HTML
<input type="checkbox" name="tags" value="css" checked> CSS
<input type="checkbox" name="tags" value="js"> JavaScript
<button type="submit">提交</button>
</form>
<div id="debug"></div>
<script>
const form = document.getElementById('advancedForm');
const debug = document.getElementById('debug');
form.addEventListener('submit', function(e) {
e.preventDefault();
const formData = new FormData(form);
// 获取同名字段的所有值
const allTags = formData.getAll('tags');
debug.textContent = '标签:' + allTags.join(', ');
// 输出:标签:html, css
// 将 FormData 转换为普通对象
const data = Object.fromEntries(formData);
// 注意:Object.fromEntries 对于同名字段只保留最后一个值
console.log(data);
// { username: "张三", tags: "css" }
// 正确处理同名字段的方式
const obj = {};
for (const [key, value] of formData.entries()) {
if (obj[key]) {
// 如果已存在,转为数组
if (!Array.isArray(obj[key])) {
obj[key] = [obj[key]];
}
obj[key].push(value);
} else {
obj[key] = value;
}
}
console.log(obj);
// { username: "张三", tags: ["html", "css"] }
// 获取 URL 编码字符串
const queryString = new URLSearchParams(formData).toString();
debug.textContent += '\nURL 编码:' + queryString;
// username=%E5%BC%A0%E4%B8%89&tags=html&tags=css
});
</script>
</body>
</html>实战示例
搜索表单(GET)与数据提交表单(POST)
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>GET 与 POST 实战</title>
<style>
.container {
max-width: 600px;
margin: 20px auto;
padding: 20px;
}
.form-section {
margin-bottom: 30px;
padding: 20px;
border: 1px solid #e0e0e0;
border-radius: 8px;
}
.form-section h2 {
margin-top: 0;
font-size: 18px;
color: #333;
}
label {
display: block;
margin-top: 10px;
font-weight: 500;
}
input, select {
margin-top: 4px;
padding: 8px;
border: 1px solid #ccc;
border-radius: 4px;
}
button {
margin-top: 16px;
padding: 10px 24px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
}
.btn-search {
background-color: #4285f4;
color: white;
}
.btn-submit {
background-color: #34a853;
color: white;
}
.method-badge {
display: inline-block;
padding: 2px 8px;
border-radius: 4px;
font-size: 12px;
font-weight: bold;
color: white;
}
.badge-get {
background-color: #4285f4;
}
.badge-post {
background-color: #34a853;
}
</style>
</head>
<body>
<div class="container">
<!-- GET 搜索表单 -->
<section class="form-section">
<h2>
搜索
<span class="method-badge badge-get">GET</span>
</h2>
<form action="/search" method="get">
<label for="q">关键词:</label>
<input type="text" id="q" name="q" placeholder="输入搜索关键词" style="width: 300px;">
<label for="type">类型:</label>
<select id="type" name="type">
<option value="all">全部</option>
<option value="article">文章</option>
<option value="video">视频</option>
</select>
<button type="submit" class="btn-search">搜索</button>
</form>
<!-- 提交后 URL:/search?q=关键词&type=article -->
</section>
<!-- POST 数据提交表单 -->
<section class="form-section">
<h2>
留言板
<span class="method-badge badge-post">POST</span>
</h2>
<form id="messageForm" action="/api/messages" method="post">
<label for="author">昵称:</label>
<input type="text" id="author" name="author" required style="width: 300px;">
<label for="content">留言内容:</label>
<textarea id="content" name="content" rows="3" required style="width: 300px;"></textarea>
<button type="submit" class="btn-submit">发表留言</button>
</form>
<!-- 数据在请求体中,不会出现在 URL -->
</section>
</div>
</body>
</html>注意事项
GET 请求不适合敏感数据:GET 请求的参数会出现在 URL、浏览器历史、服务器日志、Referer 头等多个位置,密码等敏感数据绝不能使用 GET。
GET 请求不适合大数据量:URL 长度有限制(不同浏览器和服务器限制不同,通常约 2048~8192 字符),超过限制的数据会被截断。
POST 请求的刷新问题:用户刷新 POST 请求的响应页面时,浏览器会弹出"确认重新提交表单"的警告。可以通过 PRG(Post-Redirect-Get)模式避免。
method 默认值是 GET:如果省略
method属性,表单会使用 GET 方法提交。对于修改数据的表单,务必显式设置method="post"。FormData 与文件上传:上传文件时,必须使用
FormData对象(或设置enctype="multipart/form-data"),因为普通的 URL 编码无法处理二进制文件数据。action 中的片段标识符:
action中的 URL 片段(#hash)会被浏览器忽略。表单提交的目标 URL 不会包含片段标识符。
最佳实践
根据操作性质选择 method:
- 获取数据(搜索、筛选、分页)用
GET - 修改数据(创建、更新、删除)用
POST - 关闭对话框用
dialog
- 获取数据(搜索、筛选、分页)用
GET 请求的 URL 应该是"可分享的":好的 GET 请求 URL 应该让用户可以直接复制链接分享给他人,打开后看到相同的搜索结果。
使用 PRG 模式处理 POST:服务器处理完 POST 请求后,返回 302 重定向到 GET 页面,避免用户刷新导致重复提交。
AJAX 提交使用 FormData:使用
fetch或XMLHttpRequest发送表单数据时,FormData是最方便的数据载体,它自动处理编码和文件上传。
下一节
继续学习:enctype 编码类型