如何在文件也反映在 FormData 对象的 FileList 对象处设置 File 对象和长度 属性?

How to set File objects and length property at FileList object where the files are also reflected at FormData object?

可以将 <input type="file"> 元素的 .files 属性 设置为 FileList 来自例如不同的 <input type="file"> 元素 .files 属性 或 DataTransfer.files 属性。参见 Make .files settable #2866,

FileList 对象有一个 Symbol.iterator 属性,我们可以用它来设置一个可迭代的 File 对象,但是 .files .length 仍然设置为 0 并传递具有 <input type="file"> 设置的 <form> 使用上述方法设置 .files 会产生一个 File 具有 .size 设置为 0.

如何设置FileListFileFileList.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

编辑:

OP证明,在 their gist 之一中,实际上有一种方法可以做到...

DataTransfer constructor (currently only supported by Blink, and FF >= 62), 应该创建一个可变的 FileList (chrome 目前总是 return 一个新的 FileList,但这对我们来说并不重要) ,可通过 DataTransferItemList 访问。

如果我没记错的话,这是目前唯一符合规范的方法,但是 Firefox 在 ClipboardEvent constructor 的实现中有一个 bug ,其中相同的 DataTransferItemList 被设置为模式 read/write,这允许 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">

这一发现导致 new Proposal 使 FileList 对象默认可变,因为没有必要再不这样做了。

然而,虽然这实际上是从规范行为中派生出来的,但这更像是规范中的一个漏洞,仍应被视为黑客行为。不要在生产中使用它,而是更喜欢使用简单的 Array 和 FormData 来控制将哪些文件发送到服务器。


上一个(过时的)答案

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

您只能将输入的 FileList 交换为另一个 FileList,但不能对其进行修改*。
(*用 input.value = null 清空除外)。

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

向您展示,即使将 input[type=file] 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]); 
}