循环遍历FileReader的文件,输出始终包含循环的最后一个值

use*_*241 40 html javascript filereader dom-events

我正在使用FileReader API来读取本地文件.

<input type="file" id="filesx" name="filesx[]" onchange="readmultifiles(this.files)" multiple="" />

<script>
function readmultifiles(files) {
    var ret = "";
    var ul = document.querySelector("#bag>ul");
    while (ul.hasChildNodes()) {
        ul.removeChild(ul.firstChild);
    }
    for (var i = 0; i < files.length; i++)  //for multiple files
    {
        var f = files[i];
        var name = files[i].name;
        alert(name);
        var reader = new FileReader();  
        reader.onload = function(e) {  
            // get file content  
            var text = e.target.result;
            var li = document.createElement("li");
            li.innerHTML = name + "____" + text;
            ul.appendChild(li);
        }
        reader.readAsText(f,"UTF-8");
    }
}
</script>
Run Code Online (Sandbox Code Playgroud)

如果输入包含2个文件:

file1 ---- "content1"
file2 ---- "content2"
Run Code Online (Sandbox Code Playgroud)

我得到这个输出:

file2__content1
file2__content2
Run Code Online (Sandbox Code Playgroud)

如何修复要显示的代码:

file1__content1
file2__content2
Run Code Online (Sandbox Code Playgroud)

Ben*_*Lee 91

问题是你正在运行的循环,现在,但你设置回调越来越运行(事件触发时).当它们运行时,循环结束并保持在最后一个值.因此,在您的情况下,它将始终显示"file2"作为名称.

解决方案是将文件名放在一个闭包中.一种方法是创建一个立即调用的函数表达式(IIFE)并将该文件作为参数传递给该函数:

for (var i = 0; i < files.length; i++) { //for multiple files          
    (function(file) {
        var name = file.name;
        var reader = new FileReader();  
        reader.onload = function(e) {  
            // get file content  
            var text = e.target.result; 
            var li = document.createElement("li");
            li.innerHTML = name + "____" + text;
            ul.appendChild(li);
        }
        reader.readAsText(file, "UTF-8");
    })(files[i]);
}
Run Code Online (Sandbox Code Playgroud)

或者,您可以定义命名函数并将其称为正常:

function setupReader(file) {
    var name = file.name;
    var reader = new FileReader();  
    reader.onload = function(e) {  
        // get file content  
        var text = e.target.result; 
        var li = document.createElement("li");
        li.innerHTML = name + "____" + text;
        ul.appendChild(li);
    }
    reader.readAsText(file, "UTF-8");
}

for (var i = 0; i < files.length; i++) {
    setupReader(files[i]);
}
Run Code Online (Sandbox Code Playgroud)

  • @srph它与javascript的变量范围有关.它为嵌套函数创建闭包,并且由于i的值改变了每次迭代,所以当嵌套函数被调用时,它将获得数组的最后一个值.那么Ben Lee如何修改代码就是确保函数在循环期间读取文件,以便维护索引.我希望这是有道理的.(这在[有效的Javascript](http://www.amazon.com/Effective-JavaScript-Specific-Software-Development/dp/0321812182)中有详细描述) (2认同)

bry*_*ryc 6

编辑:只使用let而不是var在循环中。这解决了OP的问题(但仅在2015年才引入)。


旧答案(一个有趣的解决方法):

虽然它不完全可靠或不适合未来,但值得一提的是,也可以通过FileReader对象添加属性来实现

var reader = new FileReader();
reader._NAME = files[i].name; // create _NAME property that contains filename.
Run Code Online (Sandbox Code Playgroud)

然后通过访问它e的内onload回调函数:

li.innerHTML = e.target._NAME + "____" + text;
Run Code Online (Sandbox Code Playgroud)


工作原理:

即使reader在像这样的循环中多次替换了变量inew FileReader对象也是唯一的,并保留在内存中。reader.onload通过e参数可以在函数中访问它。通过在存储附加数据reader对象时,它被保持在存储器中且可通过reader.onload经由e.target事件参数。

这解释了为什么您的输出是:

file2 __content1
file2__content2

并不是:

file1__content1
file2__content2

内容正确显示,因为它e.target.resultFileReader对象本身的属性。已经FileReader包含默认的文件名属性,它可能已被使用,这整个烂摊子完全避免。


注意事项

这称为扩展宿主对象(如果我了解本机对象之间的区别...)。FileReader是在这种情况下扩展的宿主对象。许多专业开发人员认为这样做是不好的做法和/或邪恶的。如果_NAME将来使用冲突,可能会发生冲突。此功能未在任何规范中进行记录,因此将来可能会中断,并且可能无法在较旧的浏览器中使用。

就个人而言,我没有通过向宿主对象添加其他属性来遇到任何问题。假定属性名称具有足够的唯一性,浏览器不会禁用它,并且以后的浏览器也不会对这些对象进行太多更改,那么它应该可以正常工作。

以下是一些可以很好地解释这一点的文章:

http://kendsnyder.com/extending-host-objects-evil-extending-native-objects-not-evil-but-risky/
http://perfectionkills.com/whats-wrong-with-extending-the-dom/

还有一些有关问题本身的文章:

http://tobyho.com/2011/11/02/callbacks-in-loops/


Kyo*_*agi 6

使用let代替var,因为声明的变量只能在一个循环中使用。

for (let i = 0; i < files.length; i++)  //for multiple files
    {
        let f = files[i];
        let name = files[i].name;
        alert(name);
        let reader = new FileReader();  
        reader.onload = function(e) {  
            // get file content  
            let text = e.target.result;
            let li = document.createElement("li");
            li.innerHTML = name + "____" + text;
            ul.appendChild(li);
        }
        reader.readAsText(f,"UTF-8");
    }
Run Code Online (Sandbox Code Playgroud)