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

Posted

技术标签:

【中文标题】如何在 FileList 对象中设置文件对象和长度属性,其中文件也反映在 FormData 对象中?【英文标题】:How to set File objects and length property at FileList object where the files are also reflected at FormData object? 【发布时间】:2018-04-17 13:58:00 【问题描述】:

可以将<input type="file"> 元素的.files 属性设置为FileList,例如不同的<input type="file"> 元素.files 属性或DataTransfer.files 属性。见Make .files settable #2866、What happens between uploading a file to an html form and submitting it?。

FileList 对象有一个Symbol.iterator 属性,我们可以使用它来设置一个可迭代的File 对象,但是.files .length 仍然设置为0 并传递一个<form> <input type="file"> 设置其中使用上述方法设置.files 会产生一个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 中修复,请参阅 developer.mozilla.org/en-US/Firefox/Releases/57#DOM 【参考方案1】:

编辑:

正如由 OP 证明的那样,在 their gist 之一中,实际上有一种方法可以做到这一点...

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

如果我没记错的话,这是目前唯一符合规范的方法,但是 Firefox 在他们的ClipboardEvent constructor 实现中有一个错误,其中相同的 DataTransferItemList 和设置为 read/write 模式,它允许 FF 我不确定我对规范的解释,但我认为它不应该正常访问)。子>

所以发现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 对象可变,因为不再这样做是没有意义的。

然而,虽然这实际上源自规范行为,但这更像是规范中的一个漏洞,仍应被视为一种 hack。不要在生产中使用它,而是更喜欢使用简单的 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,可能是 lead to a real API,但我确实相信这个答案的第二部分仍然需要,而且,即使使用 FileList 构造函数,它仍然有效:你不会能够修改 FileList,只能创建新的。 @Kaiido 不幸的是,我们仍然无法在 Safari(可能还有 IE/Edge)中创建新的DataTransfer,对吗? @mb21 我最近没有在 Edge 中查看,但是是的,据我所知,只有 Chrome 已经开始实现 DataTransfer 构造函数,而 FF 漏洞利用仅适用于 FF。顺便说一句,您确实链接到的错误报告并没有真正提到我正在谈论的错误(我确实打开了一个,所以我将在编辑中添加一个链接),即使很高兴看到他们正在处理它。 我们仍然无法在 Safari、IE/Edge 中调用 DataTransfer 构造函数 DataTransfer 构造函数现在可以在基于 Chromium 的 Edge 中使用。

以上是关于如何在 FileList 对象中设置文件对象和长度属性,其中文件也反映在 FormData 对象中?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 JavaScript 中将 File 对象添加到 FileList 集合中?

如何在 IE 中设置对象的边距?

如何在预览中设置环境对象

如何在lua中设置文件写入函数调用的长度?

如何在反应中的对象数组中设置对象数组的状态

在 UserDefaults 中设置对象数组