我正在使用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>
如果输入包含2个文件:
file1 ---- "content1"
file2 ---- "content2"
我得到这个输出:
file2__content1
file2__content2
如何修复要显示的代码:
file1__content1
file2__content2
问题是你现在正在运行循环,但是你正在设置的回调将在以后运行(当事件触发时)。当它们运行时,循环结束并保持在最后一个值。因此,在您的情况下,它将始终显示“file2”作为名称。
解决方案是将文件名放在一个闭包中。一种方法是创建一个immediately-invoked function expression (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]);
}
或者,您可以定义命名函数并将其称为正常:
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]);
}
编辑:只需在循环中使用let
而不是var
。这解决了OP的问题(但仅在2015年推出)。
旧答案(一个有趣的解决方法):
虽然它不是非常强大或面向未来,但值得一提的是,这也可以通过向FileReader
对象添加属性来实现:
var reader = new FileReader();
reader._NAME = files[i].name; // create _NAME property that contains filename.
然后在e
回调函数中通过onload
访问它:
li.innerHTML = e.target._NAME + "____" + text;
尽管reader
变量在循环期间被多次替换,如i
,但new FileReader
对象是唯一的并且保留在内存中。它可以通过reader.onload
论证在e
函数中访问。通过在reader
对象中存储其他数据,它将保存在内存中,并可通过reader.onload
通过e.target
事件参数访问。
这解释了为什么你的输出是:
file2__content1 file2__content2
并不是:
file1__content1 file2__content2
内容显示正确,因为e.target.result
是FileReader
对象本身的属性。如果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/
关于问题本身的一些文章:
我有同样的问题,使用Array.from解决了它
let files = e.target.files || e.dataTransfer.files;
Array.from(files).forEach(file => {
// do whatever
})
而不是使用var,使用let作为声明的变量只能在一个循环中使用。
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");
}
我认为解决这个问题的最好方法是递归调用一个读取blob文件的函数。所以在我的情况下,我用following snippet code解决问题,可能有点复杂但它适用于我尝试过的任何场景。
请注意,我没有将数组和索引作为参数传递。您需要使用它们所属的对象来调用它们。
//Initialize blobs
var foo = new Blob(["Lorem ipsum dolor sit amet, consectetur adipiscing elit."], {
type: 'text/plain'
});
var bar = new Blob(["Sed tristique ipsum vitae consequat aliquet"], {
type: 'text/plain'
});
//Initialize array and index
var arrayOfBlobs = [foo, bar];
var arrayIndex = 0;
function fileRead () {
var me = this;
if (this.arrayIndex < this.arrayOfBlobs.length) {
var reader = new FileReader();
function bindedOnload(event) {
console.log("bindedOnload called");
console.log("reader results: ", event.target.result);
this.arrayIndex++; //Incrument the index
this.fileRead(); //Recursive call
}
//By Binding the onload event to the local scope we
//can have access to all local vars and functions
reader.onload = bindedOnload.bind(me);
reader.readAsText(this.arrayOfBlobs[arrayIndex]);
} else {
//This will executed when finishing reading all files
console.log("Finished");
}
}
//Call the fileRead for the first time
fileRead();