Skip to content

action 与 method

actionmethod<form> 元素的两个核心属性,决定了表单数据发送到哪里以及如何发送。理解这两个属性的工作机制,是构建正确、安全、高效的表单的基础。本节将详细对比 GET 与 POST 两种提交方式,讲解 action URL 的各种写法,以及 FormData 对象的使用方法。

前置知识

阅读本节前,建议先了解:form 表单标签

基础概念

action 属性

action 属性指定表单数据提交的目标 URL。当用户提交表单时,浏览器会将数据发送到这个地址。

html
<!-- 提交到指定 URL -->
<form action="https://example.com/api/login" method="post">
  ...
</form>

method 属性

method 属性指定发送数据使用的 HTTP 请求方法。HTML 表单支持三种方法:

方法说明数据位置
get数据附加到 URL 查询字符串URL 中
post数据放在请求体中请求体中
dialog关闭对话框,不发送数据

语法

action 的取值方式

action 属性可以接受以下几种值:

html
<!-- 绝对 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 的语法

html
<!-- GET 请求(默认值) -->
<form action="/search" method="get">

<!-- POST 请求 -->
<form action="/login" method="post">

<!-- 关闭对话框 -->
<form method="dialog">

详细说明

GET vs POST 完整对比

对比维度GETPOST
数据位置URL 查询字符串(?key=value&...HTTP 请求体(Request Body)
URL 可见性数据暴露在地址栏数据不可见于 URL
数据长度受 URL 长度限制(约 2048 字符)理论上无限制,实际受服务器配置限制
数据类型仅支持 ASCII 字符(非 ASCII 会被编码)支持任意类型(含二进制文件)
安全性低(数据在 URL 中,会出现在日志、历史记录中)较高(数据在请求体中)
缓存可被浏览器缓存和收藏不会被缓存
幂等性幂等(相同请求结果相同)非幂等(可能产生不同结果)
书签可被添加为书签(含参数)不可被添加为书签
回退/刷新安全(浏览器会提示重新提交)可能导致重复提交
适用场景搜索、筛选、分页登录、注册、修改数据、文件上传

GET 请求的工作方式

当表单使用 GET 方法提交时,浏览器会将所有表单数据编码为 key=value 对,用 & 连接,附加到 action URL 后面,以 ? 分隔。

html
<!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 中。

html
<!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

html
<!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> 元素中的表单提交。它不会将数据发送到服务器,而是关闭当前对话框

html
<!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> 元素的所有数据一键收集,也可以手动创建和操作。

html
<!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 的进阶用法

html
<!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)

html
<!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>

注意事项

  1. GET 请求不适合敏感数据:GET 请求的参数会出现在 URL、浏览器历史、服务器日志、Referer 头等多个位置,密码等敏感数据绝不能使用 GET。

  2. GET 请求不适合大数据量:URL 长度有限制(不同浏览器和服务器限制不同,通常约 2048~8192 字符),超过限制的数据会被截断。

  3. POST 请求的刷新问题:用户刷新 POST 请求的响应页面时,浏览器会弹出"确认重新提交表单"的警告。可以通过 PRG(Post-Redirect-Get)模式避免。

  4. method 默认值是 GET:如果省略 method 属性,表单会使用 GET 方法提交。对于修改数据的表单,务必显式设置 method="post"

  5. FormData 与文件上传:上传文件时,必须使用 FormData 对象(或设置 enctype="multipart/form-data"),因为普通的 URL 编码无法处理二进制文件数据。

  6. action 中的片段标识符action 中的 URL 片段(#hash)会被浏览器忽略。表单提交的目标 URL 不会包含片段标识符。

最佳实践

  1. 根据操作性质选择 method

    • 获取数据(搜索、筛选、分页)用 GET
    • 修改数据(创建、更新、删除)用 POST
    • 关闭对话框用 dialog
  2. GET 请求的 URL 应该是"可分享的":好的 GET 请求 URL 应该让用户可以直接复制链接分享给他人,打开后看到相同的搜索结果。

  3. 使用 PRG 模式处理 POST:服务器处理完 POST 请求后,返回 302 重定向到 GET 页面,避免用户刷新导致重复提交。

  4. AJAX 提交使用 FormData:使用 fetchXMLHttpRequest 发送表单数据时,FormData 是最方便的数据载体,它自动处理编码和文件上传。

下一节

继续学习:enctype 编码类型

参考链接