Javascript <input type="file"> 即使选择了文件,文件列表也是空的

Javascript <input type="file"> has empty file list even when file is selected

我正在尝试在 HTML 和 Javascript 中进行简单的文件上传。

我设置它的方式是让一个 <input type="file" id="file-upload-input"/> 和一个 loadFile 函数订阅它的 onChange 事件。 (我把这个 input 隐藏起来只是因为它看起来很糟糕。)

然后我有一个 <button/> 函数 handleBrowseClick 订阅了它的 onclick 事件。

以上两个控件似乎工作正常:当我单击我的 <button/> 时,调用 handleBrowseClick,它单击我的 <input type="file">,导致文件对话框打开。但是,一旦我 select 一个文件并在对话框中按 "open" 就会出现问题(这会触发 <input type="file">onChange 事件,从而调用我的 loadFile功能)。

如您在 Javascript 中所见,此时尝试通过获取我的 <input type="file">、访问其 files 属性 并尝试读取文件获取该数组的第一个元素,以便我可以使用 FileReader 从中读取(效果很好)。

然而,无论我 select 是什么文件,files 数组总是空的(因此我的 alert 打印出 "# Files Selected: 0")!经过广泛调试和缩小问题范围后,我发现在 handleBrowseClick() 中,如果我删除稍微修改 HTML 正文的调试代码,那么文件上传突然就完美了! (alert 打印出 "# Files Selected: 1"。)

所以这是我的问题:为什么

阻止我的文件上传工作?

function handleBrowseClick()
{
    document.getElementById("upload-file-input").click();
    // if I comment out this line, then the file upload works
    document.body.innerHTML += "<br/>Browsing...";
}
function loadFile()
{
    var elem = document.getElementById("upload-file-input");
    var files = elem.files;
    alert("# Files Selected: " + files.length);
    var file = files[0];
    if (!file) return;
    
    var reader = new FileReader();
    reader.onload = function()
    {
 // do stuff with reader.result
 // example:
 document.getElementById("file-content").innerHTML = reader.result;
    };
    reader.readAsText(file);
}
<!DOCTYPE html>
<html>
  <head>
    <script src="course_chart.js"></script>
  </head>
  <body>
    <input type="file" accept=".txt" id="upload-file-input" style="display: none" onChange="javascript:loadFile()"/>
    <button onclick="javascript:handleBrowseClick()" id="choose-file-button">Load File</button>
    <div id="file-content"></div>
  </body>
</html>

使用 innerHTML += 解释 HTML,它不仅添加了一些文本,还删除了所有内容并重新创建了整个 HTML。见 https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML:

For that reason, it is recommended you not use innerHTML when inserting plain text; instead, use node.textContent. This doesn't interpret the passed content as HTML, but instead inserts it as raw text.

因此在您的情况下,调用之前的 input 元素与调用之​​后的元素不同。您可以在下面的 jsfiddle 中看到,比较调用之前和之后的节点,当您使用 innerHTML += 时,您会发现它不是同一个元素,即使 HTML 是相同的。

http://jsfiddle.net/6nja0v6b/1/

编辑

原始答案中的引用可能会导致混淆,虽然它描述了 innerHTML 的作用,但上下文不是问题之一。可以在此处找到更准确的描述:https://dvcs.w3.org/hg/innerhtml/raw-file/tip/index.html#widl-Element-innerHTML。特别是:

element . innerHTML [ = value ]

Returns a fragment of HTML or XML that represents the element's contents.

Can be set, to replace the contents of the element with nodes parsed from the given string.

On setting, these steps must be run:

Let fragment be the result of invoking the fragment parsing algorithm with the new value as markup, and the context object as the context element. Replace all with fragment within the context object.

这可以通过在 node 上添加一个 eventListener,然后调用 innerHTML 在该节点上插入完全相同的 HTML 来非常简单地说明。虽然文档看起来相同,但行为不一致,因为 innerHTML 重新创建节点结构,innerHTML 调用之前的节点与调用之后的节点不同。见片段:

var container = document.querySelector("#container");
var testElem = document.querySelector("#testElement");
var replaceBtn = document.querySelector("#replaceHtml");
var compareBtn = document.querySelector("#compare");

//We add the listener on original testElem
testElement.addEventListener('click', function(e) {
  alert("eventListener is working");
})

//After using innerHTML, event listener won't work and compare will be false
replaceBtn.onclick = function() {
  //This doesn't change the HTML, but replaces the node
  container.innerHTML = container.innerHTML;
}

//Use this to compare original testElement to new one. After innerHTML is used, it's not the same node.
compare.onclick = function() {
  alert("Is testElem \=\=\= document.querySelector(\"testElement\"): " + (testElem === document.querySelector("#testElement")));
}
#testElement {
  width: 100px;
  height: 100px;
  background-color: blue;
}
<div id="container">
  <div id="testElement"></div>
</div>
<button id="replaceHtml">Replace HTML</button>
<button id="compare">Compare nodes</button>