如何在FileList对象中设置File对象和length属性,使得这些文件也反映在FormData对象中?

68
可以将<input type="file">元素的.files属性设置为来自不同<input type="file">元素的.files属性或DataTransfer.files属性的FileList。请参见使 .files 可设置 #2866在上传文件到 HTML 表单和提交它之间发生了什么?FileList对象具有Symbol.iterator属性,我们可以使用它来设置可迭代的File对象,但是.files .length仍然设置为0,并且传递一个具有使用上述方法设置.files<form>,会产生一个File对象,其.size设置为0
如何将File设置为FileList,并将FileList.length设置为设置的文件数,在该文件集设置于FormData()对象中?

const input = document.createElement("input");

const form = document.createElement("form");

const [...data] = [
  new File(["a"], "a.txt")
, new File(["b"], "b.txt")
];

input.type = "file";

input.name = "files";

input.multiple = true;
// set `File` objects at `FileList`
input.files[Symbol.iterator] = function*() {
   for (const file of data) {
     yield file
   };
};

form.appendChild(input);

const fd = new FormData(form);

for (const file of input.files) {
  console.log(file); // `File` objects set at `data`
}

for (const [key, prop] of fd) {
  // `"files"`, single `File` object having `lastModified` property
  // set to a time greater than last `File` object within `data`
  // at Chromium 61, only `"files"` at Firefox 57
  console.log(key, prop); 
}

console.log(input.files.length); // 0


已在FF57中修复,请参阅https://developer.mozilla.org/zh-CN/Firefox/Releases/57#DOM。 - Walle Cyril
1个回答

110

编辑:

正如OP他的代码片段中所证明的那样,实际上有一种方法可以做到...

DataTransfer构造函数(目前仅由Blink支持,以及FF > = 62)应该创建一个可变的FileList (chrome目前总是返回一个新的FileList,但对我们来说并不重要),可通过DataTransferItemList访问。

如果我没弄错的话,这目前是唯一符合规范的方式,但Firefox在ClipboardEvent构造函数的实现中存在一个bug,其中相同的DataTransferItemList被设置为读/写模式,这允许在FF < 62的情况下绕过。 我不确定我的规范解释是否正确,但我认为通常不应该访问它)。

因此,guest271314找到设置FileList上任意文件的方法如下:

const dT = new DataTransfer();
dT.items.add(new File(['foo'], 'programmatically_created.txt'));
inp.files = dT.files;
<input type="file" id="inp">

这一发现导致了这个新提议,默认情况下使FileList对象可变,因为不再有不这样做的意义。

然而,尽管这实际上是来自规范行为的,但这更像是规范中的一个漏洞,仍应被视为一种hack。不要在生产环境中使用它,而是使用简单的Array和FormData来控制将哪些文件发送到服务器。


以前的(过时的)答案

你不能。 FileList对象不能被脚本修改*。

您只能将输入的FileList交换为其他FileList,但不能修改它*。
(*除了用input.value = null清空之外)。

您也无法从头开始创建FileList,只能创建无法创建的DataTransfer对象,并且input [type = file]会创建此类对象。

为了向您展示即使将的FileList设置为另一个输入的FileList时,也不会创建新的FileList:

var off = inp.cloneNode(); // an offscreen input

inp.onchange = e => {
  console.log('is same before', inp.files === off.files);
  off.files = inp.files; // now 'off' does have the same FileList as 'inp'
  console.log('is same after', inp.files === off.files);
  console.log('offscreen input FileList', off.files);
  console.log('resetting the offscreen input');
  off.value = null;
  console.log('offscreen input FileList', off.files);         
  console.log('inscreen input FileList', inp.files);
}
<input type="file" id="inp">

哦,我差点忘了FormData部分,说实话我并没有完全理解...

所以,如果我没弄错的话,你只需要使用FormData.append()就可以了:

var fd = new FormData();

fd.append("files[]", new Blob(['a']), 'a.txt');
fd.append("files[]", new Blob(['b']), 'b.txt');

for(let pair of fd.entries()) {
   console.log(pair[0], pair[1]); 
}


1
@SamuelLiew 新的部分仍然主要是一个hack。新的规范(截至今天只在Blink中实现)允许这样做,但这并不是真正的设计,更多的是巧合。FF回退是对其实现中的一个小错误的滥用。因此,虽然这是一个有趣的hack,可能会导致真正的API,但在目前,我相信这个答案的第二部分仍然是必要的,而且即使有FileList构造函数,它仍然有效:您将无法修改FileList,只能创建新的FileList。 - Kaiido
1
很抱歉@Kaiido,我们在Safari(和可能在IE/Edge)中仍然无法创建新的“DataTransfer”,对吗? - mb21
@mb21我最近没有在Edge中检查,但据我所知,只有Chrome开始了DataTransfer构造函数的实现,而FF漏洞仅适用于FF。 顺便说一句,你链接的错误报告并不真正涉及我正在谈论的错误(我打开了一个错误报告,所以我将在编辑中添加一个链接),尽管看到他们正在解决问题很好。 - Kaiido
3
在Safari、IE/Edge浏览器中,我们仍然无法调用DataTransfer构造函数。 - Volodymyr Khmil
1
DataTransfer构造函数现在在基于Chromium的Edge中可用。 - derekbaker783

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接