Skip to content

enctype 编码类型

enctype(编码类型)属性决定了表单数据在提交时如何被编码。不同的编码方式适用于不同类型的数据,选择错误的编码类型会导致服务器无法正确解析数据。本节将详细讲解三种编码类型的区别、适用场景,以及文件上传时必须使用 multipart 编码的原因。

前置知识

阅读本节前,建议先了解:action 与 method

基础概念

什么是 enctype

当浏览器提交表单时,需要将用户填写的数据按照一定的格式进行编码,然后放入 HTTP 请求中发送给服务器。enctype 属性就是用来指定这种编码格式的。

html
<form action="/submit" method="post" enctype="application/x-www-form-urlencoded">
  <input type="text" name="name">
  <button type="submit">提交</button>
</form>

三种编码类型

编码类型适用场景
URL 编码application/x-www-form-urlencoded默认值,普通文本数据
多部分编码multipart/form-data文件上传、包含二进制数据的表单
纯文本编码text/plain调试用途,不推荐生产使用

语法

enctype 的使用方式

html
<!-- 默认值:URL 编码(可省略不写) -->
<form method="post" enctype="application/x-www-form-urlencoded">

<!-- 文件上传必须用 multipart -->
<form method="post" enctype="multipart/form-data">

<!-- 纯文本编码(极少使用) -->
<form method="post" enctype="text/plain">

GET 方法忽略 enctype

当表单使用 method="get" 时,enctype 属性会被浏览器忽略。GET 请求始终使用 URL 编码方式将数据附加到查询字符串中。enctype 仅对 POST 方法有效。

详细说明

application/x-www-form-urlencoded(默认值)

这是表单的默认编码类型,也是使用最广泛的编码方式。它将表单数据编码为 key=value 键值对,用 & 符号连接,空格编码为 +,特殊字符编码为 %XX

编码后的数据格式:

username=zhangsan&email=zhangsan%40example.com&age=25&city=%E5%8C%97%E4%BA%AC

HTTP 请求示例:

http
POST /api/register HTTP/1.1
Host: example.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 68

username=zhangsan&email=zhangsan%40example.com&age=25&city=%E5%8C%97%E4%BA%AC
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>URL 编码示例</title>
</head>
<body>
  <!-- enctype 省略时默认使用 application/x-www-form-urlencoded -->
  <form action="/api/register" method="post">
    <label for="username">用户名:</label>
    <input type="text" id="username" name="username" value="张三">

    <label for="email">邮箱:</label>
    <input type="email" id="email" name="email" value="zhangsan@example.com">

    <label for="city">城市:</label>
    <input type="text" id="city" name="city" value="北京">

    <button type="submit">提交</button>
  </form>

  <!--
    提交的原始数据:
    username=%E5%BC%A0%E4%B8%89&email=zhangsan%40example.com&city=%E5%8C%97%E4%BA%AC

    服务器解码后:
    username=张三
    email=zhangsan@example.com
    city=北京
  -->
</body>
</html>

URL 编码规则:

原始字符编码后说明
空格+空格编码为加号
@%40特殊字符编码
&%26分隔符本身需要编码
=%3D等号本身需要编码
中文%XX%XX%XXUTF-8 编码后逐字节转为 %XX
+%2B加号本身需要编码

multipart/form-data(文件上传)

当表单包含文件上传控件(<input type="file">)时,必须使用 multipart/form-data 编码。这种编码方式将表单数据分成多个部分(part),每个部分可以有自己的 Content-Type,适合传输二进制文件。

HTTP 请求示例:

http
POST /api/upload HTTP/1.1
Host: example.com
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Length: 342

------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="title"

项目报告
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="file"; filename="report.pdf"
Content-Type: application/pdf

%PDF-1.4
...(二进制文件内容)...
------WebKitFormBoundary7MA4YWxkTrZu0gW--
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>multipart 编码示例</title>
</head>
<body>
  <!-- 包含文件上传的表单必须使用 multipart/form-data -->
  <form action="/api/upload" method="post" enctype="multipart/form-data">
    <label for="title">文件标题:</label>
    <input type="text" id="title" name="title" placeholder="输入文件标题">

    <label for="file">选择文件:</label>
    <!-- accept 限制可选择的文件类型 -->
    <input type="file" id="file" name="file" accept=".pdf,.doc,.docx">

    <label for="description">描述:</label>
    <textarea id="description" name="description" rows="3" placeholder="文件描述"></textarea>

    <button type="submit">上传文件</button>
  </form>
</body>
</html>

multipart 编码的关键特征:

特征说明
boundary分隔符,用于区分表单的各个部分,由浏览器自动生成
每个字段一个 part每个表单字段和文件各自成为一个独立的部分
文件部分有 filename文件部分包含原始文件名
文件部分有 Content-Type根据文件类型自动设置 MIME 类型
二进制安全可以正确传输任意二进制数据

严重错误

文件上传必须使用 enctype="multipart/form-data"。如果使用默认的 URL 编码,文件内容会尝试被当作文本编码,导致文件损坏或上传失败。

html
<!-- 错误!包含 file 控件但没有设置 enctype -->
<form action="/upload" method="post">
  <input type="file" name="avatar">
  <button type="submit">上传</button>
</form>
html
<!-- 正确:文件上传必须设置 enctype -->
<form action="/upload" method="post" enctype="multipart/form-data">
  <input type="file" name="avatar">
  <button type="submit">上传</button>
</form>

text/plain(纯文本)

text/plain 编码将表单数据以纯文本形式发送,不做特殊编码。每个字段占一行,格式为 key=value,不经过 URL 编码。

发送的数据格式:

name=张三
email=zhangsan@example.com
message=这是一条包含特殊字符的消息:& = +
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>text/plain 编码示例</title>
</head>
<body>
  <!--
    text/plain 编码极少使用
    主要用于调试,或发送邮件(mailto: 链接)
  -->
  <form action="mailto:contact@example.com" method="post" enctype="text/plain">
    <label for="subject">主题:</label>
    <input type="text" id="subject" name="subject" value="反馈">

    <label for="body">正文:</label>
    <textarea id="body" name="body" rows="5"></textarea>

    <button type="submit">发送邮件</button>
  </form>

  <!--
    提交的数据(原始格式):
    subject=反馈
    body=你好,这是我的反馈内容...
  -->
</body>
</html>

不推荐

text/plain 编码不适合生产环境使用。原因:

  1. 服务器端大多数框架(Express、Django、Spring 等)默认不支持解析 text/plain 格式的表单数据
  2. 空格、换行等特殊字符不经过编码,可能导致数据解析错误
  3. 无法传输文件
  4. 仅适合与 mailto: URL 配合使用,或作为调试手段

三种编码类型对比

对比维度application/x-www-form-urlencodedmultipart/form-datatext/plain
数据格式key=value&key=value多部分,每部分独立key=value 每行一个
空格处理编码为 +原样保留原样保留
特殊字符URL 编码(%XX原样保留原样保留
中文处理URL 编码原样保留(二进制)原样保留
文件支持不支持支持不支持
二进制安全
数据大小受 URL 限制无限制无限制
解析复杂度简单较复杂(需解析 boundary)简单
默认使用
浏览器兼容所有所有所有
服务器支持通用通用极少

JavaScript 中设置 enctype

在使用 fetchXMLHttpRequest 发送 AJAX 请求时,编码类型的行为有所不同:

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>JS 中处理编码类型</title>
</head>
<body>
  <form id="myForm">
    <input type="text" name="name" value="测试">
    <input type="file" name="file">
    <button type="submit">提交</button>
  </form>

  <script>
    const form = document.getElementById('myForm');

    form.addEventListener('submit', function(e) {
      e.preventDefault();

      // ---- 方式一:使用 FormData(自动处理编码) ----
      const formData = new FormData(form);

      // 发送 multipart/form-data
      fetch('/api/upload', {
        method: 'POST',
        body: formData
        // 不需要设置 Content-Type,浏览器自动设置(含 boundary)
      });

      // ---- 方式二:手动发送 URL 编码数据 ----
      const formData2 = new FormData(form);
      // 只获取文本字段(不含文件)
      const params = new URLSearchParams();
      params.append('name', formData2.get('name'));

      fetch('/api/submit', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/x-www-form-urlencoded'
        },
        body: params.toString()
      });

      // ---- 方式三:发送 JSON(不是表单编码,但常用) ----
      const jsonData = { name: '测试' };

      fetch('/api/json', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify(jsonData)
      });
    });
  </script>
</body>
</html>

实战示例

完整的文件上传表单

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;
      max-width: 600px;
      margin: 40px auto;
      padding: 20px;
    }

    .upload-area {
      border: 2px dashed #ccc;
      border-radius: 8px;
      padding: 30px;
      text-align: center;
      margin: 20px 0;
    }

    .upload-area:hover {
      border-color: #4a90d9;
    }

    .form-group {
      margin-bottom: 16px;
    }

    label {
      display: block;
      font-weight: 500;
      margin-bottom: 6px;
    }

    input[type="text"], textarea {
      width: 100%;
      padding: 10px;
      border: 1px solid #ddd;
      border-radius: 6px;
      box-sizing: border-box;
    }

    .btn {
      padding: 12px 24px;
      border: none;
      border-radius: 6px;
      cursor: pointer;
      font-size: 14px;
    }

    .btn-primary {
      background-color: #4a90d9;
      color: white;
    }
  </style>
</head>
<body>
  <h1>文件上传</h1>

  <!--
    文件上传表单的关键:
    1. method="post"
    2. enctype="multipart/form-data"
    3. <input type="file">
  -->
  <form id="uploadForm" action="/api/upload" method="post" enctype="multipart/form-data">
    <div class="form-group">
      <label for="filename">文件名称:</label>
      <input type="text" id="filename" name="filename" placeholder="为文件起个名字">
    </div>

    <div class="form-group">
      <label>选择文件:</label>
      <div class="upload-area">
        <!-- accept 限制文件类型,multiple 允许多选 -->
        <input type="file" id="files" name="files" accept="image/*" multiple>
        <p style="color: #999; margin-top: 10px;">支持图片格式:JPG、PNG、GIF、WebP</p>
      </div>
    </div>

    <div class="form-group">
      <label for="desc">文件描述:</label>
      <textarea id="desc" name="description" rows="3" placeholder="描述文件内容..."></textarea>
    </div>

    <div id="fileList"></div>

    <button type="submit" class="btn btn-primary">上传文件</button>
  </form>

  <script>
    const fileInput = document.getElementById('files');
    const fileList = document.getElementById('fileList');

    // 显示已选择的文件列表
    fileInput.addEventListener('change', function() {
      fileList.innerHTML = '';
      for (const file of this.files) {
        const sizeMB = (file.size / 1024 / 1024).toFixed(2);
        const item = document.createElement('p');
        item.textContent = `${file.name} (${sizeMB} MB, ${file.type})`;
        fileList.appendChild(item);
      }
    });
  </script>
</body>
</html>

多字段混合表单(文本 + 文件)

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>混合表单</title>
</head>
<body>
  <!--
    当表单同时包含文本字段和文件字段时,
    必须使用 multipart/form-data。
    所有字段(包括文本)都会以 part 的形式发送。
  -->
  <form action="/api/publish" method="post" enctype="multipart/form-data">
    <h2>发布文章</h2>

    <!-- 文本字段 -->
    <label for="title">文章标题:</label>
    <input type="text" id="title" name="title" required>

    <label for="content">文章内容:</label>
    <textarea id="content" name="content" rows="6" required></textarea>

    <label for="category">分类:</label>
    <select id="category" name="category">
      <option value="tech">技术</option>
      <option value="design">设计</option>
      <option value="product">产品</option>
    </select>

    <!-- 文件字段:封面图片 -->
    <label for="cover">封面图片:</label>
    <input type="file" id="cover" name="cover" accept="image/*">

    <!-- 文件字段:附件 -->
    <label for="attachment">附件:</label>
    <input type="file" id="attachment" name="attachment">

    <button type="submit">发布文章</button>
  </form>

  <!--
    提交的 multipart 数据结构:

    ------boundary
    Content-Disposition: form-data; name="title"

    我的第一篇文章
    ------boundary
    Content-Disposition: form-data; name="content"

    这是文章内容...
    ------boundary
    Content-Disposition: form-data; name="category"

    tech
    ------boundary
    Content-Disposition: form-data; name="cover"; filename="photo.jpg"
    Content-Type: image/jpeg

    (二进制图片数据)
    ------boundary
    Content-Disposition: form-data; name="attachment"; filename="doc.pdf"
    Content-Type: application/pdf

    (二进制文件数据)
    ------boundary--
  -->
</body>
</html>

注意事项

  1. 忘记设置 enctype 是常见错误:当表单包含 <input type="file"> 时,如果忘记设置 enctype="multipart/form-data",文件将无法正确上传。浏览器不会自动检测并切换编码类型。

  2. GET 方法不受 enctype 影响:无论设置什么 enctype,GET 请求始终使用 URL 编码将数据附加到查询字符串中。

  3. multipart 编码的数据量更大:由于 boundary 字符串和每个部分的 Content-Disposition 头,multipart 编码的总体积比 URL 编码大。对于纯文本数据,URL 编码更高效。

  4. 不要手动设置 Content-Type:使用 fetch 发送 FormData 时,不要手动设置 Content-Type。浏览器需要自动添加 boundary 参数,手动设置会覆盖这个参数。

  5. text/plain 的局限性:由于 text/plain 不对数据进行编码,包含 &=、换行符等特殊字符的值可能导致服务器解析错误。

  6. 后端框架的默认支持:大多数后端框架(Express 的 body-parser、Django、Laravel 等)默认同时支持 application/x-www-form-urlencodedmultipart/form-data,但通常不自动解析 text/plain

最佳实践

  1. 包含文件上传时,始终设置 enctype="multipart/form-data":这是最重要的规则。养成习惯,只要表单中有 <input type="file">,就加上这个属性。

  2. 纯文本表单不需要设置 enctype:默认的 application/x-www-form-urlencoded 已经足够处理绝大多数文本表单数据。

  3. 使用 FormData 处理 AJAX 文件上传:在前端使用 fetchXMLHttpRequest 上传文件时,直接使用 FormData 对象,让浏览器自动处理编码。

  4. 限制上传文件的大小和类型:使用 accept 属性限制文件类型,在服务器端进行文件大小和类型的验证。

  5. 避免使用 text/plain:除非有特殊需求(如 mailto: 链接),否则不要使用 text/plain 编码。

下一节

继续学习:autocomplete 与 novalidate

参考链接