从代码生成的 INPUT 元素中选择文件后,不会触发事件 onChange

Event onChange won't trigger after files are selected from code-generated INPUT element

我正在玩 JavaScript 并编写了创建 INPUT 元素 (type="file") 并模拟点击的简单函数。

var createAndCallFileSelect = function () {
    var input = document.createElement ("input");
    input.setAttribute ("type", "file");
    input.addEventListener ("change", function () {
        console.log (this.files);
    }, false);
    input.click();
}

它大部分时间都很好用,但有时它不会触发 onChange 文件 selected 事件(或更多文件与 multiple 属性一起使用 INPUT).

我知道当您重新 select 相同的文件时 onChange 不会触发,但显然这里不是这种情况。它不会仅在我第一次使用此功能时触发事件,有时只会触发事件。如果从对话框中 select 编辑了某些内容,则每次下一次单击通常都会触发 onChange

已经尝试在这里和周围搜索这个问题,但似乎所有 onChange 问题和解决方案 都与再次重新 select 同一文件的著名问题有关。

我发现这发生在最新的 Opera 和 Firefox 上,从未在其他浏览器上测试过。还。我试图等待整个页面加载,但结果仍然相同 - 有时它不会在第一次调用时触发 onChange

任何人都可以向我解释为什么会这样吗? 我已经有了解决方法代码,这不是问题,只需要解释为什么在以这种方式创建和调用 INPUT 时会发生这种情况。

更新:

级联延迟

var function createAndCallFileSelect = function () {
    var input = document.createElement ("input");
    setTimeout (function () { // set type with 1s delay
        input.setAttribute ("type", "file");
        setTimeout (function () {  // attach event with 1s delay
            input.addEventListener ("change", function () {
                console.log (this.files);
            }, false);
            setTimeout (function () { // simulate click with 1s delay
                input.click();
            }, 1000);
        }, 1000);
    }, 1000);
}

这也不行。我试图延迟执行每一行以确保一切都按正确的顺序执行。调用后 3 秒,它会打开 file-select 对话框,但有时它不会在文件 selected.

后触发 onChange 事件

事件侦听器 input.addEventListener ("change" ...

没有立即注册。这就像在 setTimeout(fn, 0) 中包装代码,使其添加到执行队列的末尾。

但是input.click();立即打开一个文件选择弹出窗口,暂停 JavaScript(因此在弹出窗口关闭之前事件不会注册)。如果你把input.click包裹在setTimeout(function() { input.click(); }, 0)里,那么肯定会在事件注册后执行,这个理论可能是正确的。

我无法重现您的问题,所以这只是纯理论。

这是一个竞争条件。这取决于堆栈中的内容以及在调用同步文件浏览器以阻止堆栈的其余部分完成之前某些事情可能需要多长时间。使用 addeventlistener,它正在排队一个回调供以后使用,当堆栈清除时事件循环将拾取该回调。如果栈没有及时清空,就不会被及时调用。无法保证什么时候 运行。如果您按照 Pawel 的建议使用 setTimeout(fn, 0),您将在放置事件侦听器后将要调用的 click() 函数排队。

这是一个很棒的视频,它形象化了我所说的一切:https://www.youtube.com/watch?v=8aGhZQkoFbQ

更新: 在进一步研究之后,我注意到 chrome 有一些非常有趣的东西……它一次最多只允许创建 5 个这些元素。我这样做了:

for(var i = 0; i < 20; i += 1) {
    createAndCallFileSelect()
}

其中有几个不同的数字......每次,任何大于 5 的数字都只产生 5 个输入元素和 5 个回调,而 5 及以下产生正确的数量。

我也尝试过递归而不是使用 for 循环...结果相同。

另外,文件越大我select,耗时越长,但最终它会在处理完文件后调用回调。到目前为止,此测试已全部在 chrome.

中进行

你可以做这样的事情来触发点击动态创建的文件输入的变化

var input = document.createElement ("input");
input.setAttribute ("type", "file");

input.addEventListener('change', function(){
    input.addEventListener('click', function(){
      alert("Clicked");
      input.removeEventListener("click", function(){})
    }, false);
    input.click();
}, false); 

JS fiddle

我已经在 chrome、firefox、opera 和 IE 中对此进行了测试。有效

发生这种情况是因为当您关闭 "Open file" 对话框 window 时您的 input 元素不再存在,因此没有针对谁引发 onchange 事件的目标。可能是因为 JavaScript 的垃圾收集器已经收集了这个元素,或者由于其他原因。

要解决此问题,只需将 input 元素保存在 DOM:

中的某处
input.style.visibility='hidden';
document.body.appendChild(input);

另外不要忘记将 link 保存到此元素并在文件上传完成时将其从 DOM 中删除(我在这里使用 this.set()this.get() 函数来自Ext.js):

// after element initialization:
this.set("inputFileElement", input);
...
// in the "OnFileComplete" event handler or in some similar place:
var inputFileElement = this.get("input");
if(inputFileElement !== null && inputFileElement !== undefined)
{
    inputFileElement.parentNode.removeChild(inputFileElement);
}

我知道这个问题与解决方法无关,但我是来这里寻找解决方案的。这对我有用 Chrome。我只是将 let input 移到了函数之外。这是有效的,因为(如 bside 所建议的那样)它可以防止 input 被垃圾收集。我只希望一次打开一个打开文件对话框,所以单例模式在这里是可以的。

let input;
var createAndCallFileSelect = function () {
    input = document.createElement ("input");
    input.setAttribute ("type", "file");
    input.addEventListener ("change", function () {
        console.log (this.files);
    }, false);
    input.click();
}