当前位置:首页 > 文章列表 > 文章 > 前端 > JavaScript FormData 怎样上传多个同名字段

JavaScript FormData 怎样上传多个同名字段

来源:17golang原创 2026-10-29 08:28:18 0浏览 收藏

FormData 上传多个同名字段时,对同一个字段名反复调用 append(),每个值单独追加一次即可。多个文件也逐个追加 File 对象。不要在循环里调用 set():它会把该名称已有的全部值替换成一个新值。发送时把 FormData 直接交给 fetch 的 body,并让浏览器生成 multipart 的 Content-Type 和 boundary。

官方文档地址:https://developer.mozilla.org/en-US/docs/Web/API/FormData

先约定一个字段名可以出现多次

以资料提交接口为例:一份资料有一个 title、若干 tag,以及若干 attachment。title 是单值;tag 和 attachment 是多值。前端需要表达的是“同名条目组成的集合”,不能先把所有字段放进只保留一个同名属性的普通对象,再期待接收端还原丢失的条目。

字段名约定写入方式
title一条文本一次 set
tag零条或多条文本每条 append
attachment一条或多条文件每个 File 一次 append

这里采用 tag 和 attachment 作为字面字段名。tag[] 是另一个名称,方括号不是 FormData 自动开启的数组开关;是否采用这种命名取决于接收端框架和接口约定。不要前端发送 attachment,接收端却查找 attachment[]。

用 append 追加,用 getAll 读取

FormData 可以保存同名的多个条目。append 表示增加一个条目,set 表示把该名称收敛成一个值。读取时,get 只取第一个匹配值;getAll 返回该名称的全部值,名称不存在时得到空数组。

// 多值字段逐条追加;标量字段单独设置。
const payload = new FormData();
payload.set("title", "设备巡检资料");
for (const tag of ["巡检", "待归档"]) {
  payload.append("tag", tag);
}

// 以下是按接口语义推导的值,供理解,不表示已在本机运行。
const tags = payload.getAll("tag"); // ["巡检", "待归档"]
const firstTag = payload.get("tag"); // "巡检"

// 如果业务要把全部标签改为一个标签,才使用 set。
payload.set("tag", "已归档");
const replacedTags = payload.getAll("tag"); // ["已归档"]

上述最后一次 set 专门展示覆盖语义,不应放进上传多个标签的构建循环。如果需求是替换成一组新标签,可以先 delete("tag"),再对新集合逐次 append;单次 set 不能表达多个独立同名值。

FormData同名条目、追加覆盖与读取方法的静态关系说明图
图1:同名字段结构说明图。写入语义分组区分追加与覆盖,条目分组表示可重复名称,读取语义分组区分首值与全值;连线表示对象关系,不表示执行顺序。

多文件逐个追加,不把数组直接塞进去

下面的表单有标签复选框和多文件控件,均声明 name。构建时直接使用 new FormData(form),浏览器会收集可提交的表单控件;已经由表单收集的文件,不要再循环追加一遍。未勾选的复选框不提交,禁用控件也不收集。


如果文件来自拖放区域或应用自己维护的 File 列表,可以从空 FormData 构建,而不是从表单重复收集。下面的构建函数与表单构建方式二选一:

function buildMaterialPayload(title, tags, files) {
  // 显式要求字符串和 File 集合,避免数组、对象被隐式转成字符串。
  if (typeof title !== "string" || title.trim() === "") {
    throw new TypeError("标题不能为空");
  }
  if (!Array.isArray(tags) || !tags.every(tag => typeof tag === "string")) {
    throw new TypeError("标签必须是字符串数组");
  }
  const fileList = Array.from(files);
  if (fileList.length === 0 || !fileList.every(file => file instanceof File)) {
    throw new TypeError("至少提供一份 File 附件");
  }
  const data = new FormData();
  data.set("title", title.trim());
  for (const tag of tags) data.append("tag", tag);
  for (const file of fileList) {
    // 第三个参数声明传给服务端的文件名,不会读取用户的本地路径。
    data.append("attachment", file, file.name);
  }
  return data;
}

不要写 data.append("tag", tags) 来代替循环。非字符串、非 Blob 的值会被转为字符串,数组不会因此展开成多个条目;普通对象也不会自动编码为 JSON。若接口明确要求 JSON 字段,应主动 JSON.stringify,并在接口中约定这个字段的解析方式。

让浏览器填写 multipart 边界并处理失败

下面接续前面的表单示例。/api/materials 是需要按自己项目实现的同源接口路径,示例没有提供后端,也没有假设返回 JSON。它把 HTTP 非成功状态与网络失败都展示给调用方,避免 fetch 收到 400 或 500 后仍显示成功。

const form = document.querySelector("#material-form");
const status = document.querySelector("#material-status");
const submit = form.querySelector('button[type="submit"]');

form.addEventListener("submit", async (event) => {
  event.preventDefault();
  // 这里选择从表单构建,不再额外追加同一批文件。
  const payload = new FormData(form);
  const files = payload.getAll("attachment");
  if (!files.some(file => file instanceof File && file.name !== "")) {
    status.textContent = "请先选择附件";
    return;
  }
  submit.disabled = true;
  status.textContent = "正在提交";
  try {
    const response = await fetch("/api/materials", {
      method: "POST",
      body: payload,
      // 不手写 Content-Type;浏览器需要填写与请求体匹配的 boundary。
    });
    if (!response.ok) {
      throw new Error(`提交失败,HTTP ${response.status}`);
    }
    status.textContent = "接口已返回成功状态";
  } catch (error) {
    // 使用 textContent,错误消息只作为文字显示。
    status.textContent = error instanceof Error ? error.message : "提交失败";
  } finally {
    submit.disabled = false;
  }
});

若项目的公共请求封装默认加入 application/json 或固定的 multipart/form-data,需要在发送 FormData 时移除该默认值。只写 multipart/form-data 而没有与实际请求体对应的 boundary,也会妨碍解析。HTTP 成功状态表示接口按约定响应,文件是否完成持久化仍由后端接口的成功语义定义。

FormData字段、multipart编码和接收端多值接口约定的静态关系说明图
图2:上传接口结构说明图。载荷分组包含文本与文件条目,编码分组关联 multipart 与 boundary,接收约定分组关联同名集合和接口契约;图示不是抓包或执行结果。

接收端按集合取值,避免转换时丢失数据

前端保留了重复条目,不代表任意后端绑定函数都会自动返回数组。接收端需要启用对应的 multipart 解析能力,并按照框架提供的多值字段或多文件接口读取。请求里的字面名称与接收端读取名称应一致;是否需要 [] 后缀必须共同约定。

在前端排查自己构建的载荷时,用 getAll("tag") 和 getAll("attachment") 查看集合。不要用 Object.fromEntries(payload) 判断重复字段是否存在:普通对象同名属性只能留下一个值,这种转换会丢失多值信息。保留条目对或按已知字段显式组织数组,才符合这个接口的语义。

现象需要区分的原因对应处理
每个标签只剩最后一个循环 set 或转成普通对象改用 append,保持条目集合
附件重复表单已收集后又手动 append选择一种构建来源
服务端只读到一项单值绑定函数或字段名约定不同使用框架的集合读取方式
multipart 无法解析默认请求头覆盖浏览器编码头移除手写 Content-Type

同名字段需要写成数组 JSON 吗?

仅当接口明确要求某个字段保存 JSON 时才这样做。重复文本条目、JSON 字符串字段和多个文件条目是不同的接口设计,不能随意互换。

文件名相同会不会覆盖 FormData 中前一个文件?

append 不会因为文件名相同而把之前的条目改成 set。后端如何命名存储文件是另一项约定,不能用客户端文件名充当唯一存储键。

空标签数组怎样表达?

对空数组不执行 append,则载荷里没有 tag 条目。接收端应明确缺省代表空集合、保持旧值还是参数错误;不要用一个空字符串条目冒充空集合。

可以直接链式调用 append 吗?

append 返回 undefined,不返回 FormData。应在同一个对象上逐次调用,然后把该对象交给 fetch。

参考文档:MDN append、MDN set、MDN getAll、MDN FormData 使用指南、MDN Fetch 使用指南。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
JavaScript PerformanceObserver.supportedEntryTypes 检查浏览器可用性能条目的配置方法JavaScript PerformanceObserver.supportedEntryTypes 检查浏览器可用性能条目的配置方法
上一篇
JavaScript PerformanceObserver.supportedEntryTypes 检查浏览器可用性能条目的配置方法
JavaScript setRangeText 替换文本后怎样保留选区
下一篇
JavaScript setRangeText 替换文本后怎样保留选区
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    767次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    764次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    845次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    815次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    644次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码