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

IT技术 javascript form-data fileapi filelist
2021-02-03 20:20:32

可以将元素的.files属性设置<input type="file">FileList来自例如不同的<input type="file">元素.files属性或DataTransfer.files属性。请参阅Make .files settable #2866将文件上传到 HTML 表单和提交之间会发生什么?.

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

如何将FileatFileList和 set .lengthof设置为设置FileList文件数,其中文件设置为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

1个回答

编辑:

正如OP所证明的那样,在他们的一个要点中,实际上有一种方法可以做到这一点......

数据传递构造函数(目前只有闪烁的支持,以及FF> = 62),应该创建一个可变的文件清单(目前铬总是返回一个新的文件清单,但它其实并不重要,对我们来说),通过DataTransferItemList访问。

如果我没记错的话,这是目前唯一符合规范的方法,但是 FirefoxClipboardEvent 构造函数的实现中有一个错误,其中相同的 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 对象可变,因为没有必要再不这样做了。

然而,虽然这实际上源自规范行为,但这更像是规范中的一个漏洞,仍应被视为一种黑客行为。不要在生产中使用它,而是更喜欢简单的 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]); 
}

@SamuelLiew 新部分仍然主要是 hack新规范(截至今天仅在 Blink 中实现)允许这样做,但这并不是真正的设计,这更像是巧合。FF 回退是滥用其实施中的一个小错误。因此,虽然这是一个有趣的 hack,它可能会导致一个真正的 API,但目前,我确实相信这个答案的第二部分仍然需要,而且,它仍然有效,即使使用 FileList 构造函数:你赢了'不能修改 FileList,只能创建新的。
2021-03-11 20:20:32
@mb21 我最近没有在 Edge 中检查,但是是的,据我所知,只有 Chrome 已经开始实现 DataTransfer 构造函数,而 FF 漏洞利用仅适用于 FF。顺便说一句,你链接的错误报告并没有真正指我正在谈论的错误(我确实打开了一个,所以我会在编辑中添加一个链接),尽管很高兴看到他们正在处理它。
2021-03-15 20:20:32
我们仍然无法DataTransfer在 Safari、IE/Edge 中调用构造函数
2021-03-21 20:20:32
@Kaiido 不幸的是,我们仍然无法DataTransfer在 Safari(可能还有 IE/Edge)中创建一个新的,对吗?
2021-03-29 20:20:32
DataTransfer 构造函数现在可以在基于 Chromium 的 Edge 中工作。
2021-03-30 20:20:32