FileReader的onload事件与结果和参数

26

我无法在onload函数中同时获取filereader的结果和一些参数。这是我的代码:

控件的HTML:

<input type="file" id="files_input" multiple/>

Javascript函数:

function openFiles(evt){
    var files = evt.target.files;
    for (var i = 0; i < files.length; i++) {
      var file=files[i];
      reader = new FileReader();
      reader.onload = function(){
          var data = $.csv.toArrays(this.result,{separator:'\t'});
      };
      reader.readAsText(file);
    }
  }

添加事件:

 files_input.addEventListener("change", openFiles, false);

我在onload函数中使用filereader.result。如果我给这个函数传递一个参数,比如文件,我将无法再访问结果。例如,我想在onload函数中使用file.name,该如何解决这个问题?


它能用于一个文件吗?尝试创建不同的读取器,添加“var reader = new FileReader();”。 - Jon Ander
使用一个简单的规则:在闭包的开头声明所有本地变量。不要在forin和其他类似的语句内部声明它们,因为这些语句不会创建闭包,因此声明的变量不是该语句的本地变量。这将帮助您确定何时需要创建额外的闭包。 - hon2a
6个回答

62

尝试将您的 onload 函数包装在另一个函数中。 在这里,闭包通过变量 f 为您提供对每个正在处理的文件的访问:

function openFiles(evt){
    var files = evt.target.files;

    for (var i = 0, len = files.length; i < len; i++) {
        var file = files[i];

        var reader = new FileReader();

        reader.onload = (function(f) {
            return function(e) {
                // Here you can use `e.target.result` or `this.result`
                // and `f.name`.
            };
        })(file);

        reader.readAsText(file);
    }
}

讨论为什么这里需要一个闭包,请参见以下相关问题:


3
如果浏览器支持 FileReader,我认为它也支持 forEachbind :) 我认为最好使用 bind 将文件添加到 onload 回调中。 - Alex
@Pinal 这是一个好建议。我不会更新我的答案来包括这些内容,因为我想保持我的答案尽可能接近 OP 提供的代码,并且只更改必要的部分(引入闭包),但知道这些特性是很好的。 - Chris
2
如果可以的话,我会给这个答案点赞1000次。谢谢! - Kris
谢谢伙计!帮了我很多忙! - user970470
非常感谢,这仍然是2019年最好的解决方案。 - Alexandre
显示剩余2条评论

7

您应该在' onload '处理程序中使用闭包。

示例:http://jsfiddle.net/2bjt7Lon/

reader.onload = (function (file) { // here we save variable 'file' in closure
     return function (e) { // return handler function for 'onload' event
         var data = this.result; // do some thing with data
     }
})(file);

5

使用

var that = this;

访问函数作用域中的外部变量。

function(){
    that.externalVariable //now accessible using that.___
}

我的场景 - 使用 Angular 9。 我为此苦苦挣扎了很长时间,似乎一直无法让它工作。 我发现以下是在 function() 块内访问外部变量的一种非常优雅的解决方案。

public _importRawData : any[];

importFile(file){
    var reader = new FileReader();
    reader.readAsArrayBuffer(file);

    var data;
    var that = this;  //the important bit

    reader.onloadend = await function(){
        //read data

        that._importRawData = data;  //external variables are now available in the function
    }

在以上代码中,重要的部分之一是var关键字,它在函数块外部定义变量的作用域。 然而,在函数块外部访问data的值时,它仍然是未定义的,因为该函数在其他代码之后执行。我尝试使用async和await,但无法使其正常工作。我也无法在此函数之外访问data
挽救的办法是var that = this行。 使用that允许在函数内访问外部变量。所以我可以在函数作用域内设置那个变量,不必担心代码何时被执行。一旦读取完成,它就可用了。
对于原始问题,代码将如下:
function openFiles(evt){
    var files = evt.target.files;
    for (var i = 0; i < files.length; i++) {
        var file=files[i];
      
        var that = this; //the magic happens
      
        reader = new FileReader();
        reader.onload = function(){
            var data = $.csv.toArrays(this.result,{separator:'\t'});
          
            that.file.name //or whatever you want to access.
        };
        reader.readAsText(file);
    }
}

3

对于Typescript:

for (var i = 0; i < files.length; i++) {
  var file = files[i];

  var reader = new FileReader();

  reader.onload = ((file: any) => {
    return (e: Event) => {
      //use "e" or "file"
    }
  })(file);

  reader.readAsText(file);
}

3
事件处理是异步的,因此它们会获取所有封闭局部变量(即闭包)的最新值。要将特定的局部变量绑定到事件,您需要遵循上面用户建议的代码或查看这个可行的示例: http://jsfiddle.net/sahilbatla/hjk3u2ee/
function openFiles(evt){
  var files = evt.target.files;
  for (var i = 0; i < files.length; i++) {
    var file=files[i];
    reader = new FileReader();
    reader.onload = (function(file){
      return function() {
        console.log(file)
      }
    })(file);
    reader.readAsText(file);
  }
}

#Using jQuery document ready
$(function() {
  files_input.addEventListener("change", openFiles, false);
});

这不太对。你应该将一个函数赋值给 reader.onload。你正在调用的 IIFE 没有返回任何东西,因此一旦读取器触发其加载事件,就不会运行任何代码。 - Chris
我不是很清楚返回一个函数的重要性,当代码片段只在onload事件上运行并且已绑定文件时。 - sahilbathla
目前,你的函数 (function(file){console.log(file)})(file); 每次迭代 for 循环时都会运行一次。它不返回任何内容,因此每次循环迭代时 reader.onload = undefined。因此,当 reader 触发其 load 事件时,虽然你的 console.log 打印了正确的文件名,但不会执行任何操作。 - Chris
谢谢你指出来,这是我的错,我应该修复它。 - sahilbathla

-1

由于变量 file 在作用域内,您可以在不将其传递给函数的情况下使用 file 变量。

function openFiles(evt){
    var files = evt.target.files;
    for (var i = 0; i < files.length; i++) {
          var file=files[i];    
          reader = new FileReader();
          reader.onload = function(){
              alert(file.name);
              alert(this.result);
          };
      reader.readAsText(file);
    }
  }

files_input.addEventListener("change", openFiles, false);
<input type="file" id="files_input" multiple/>


谢谢,我明白了 :) 但是当我测试每个文件名和内容时,似乎这个方法有效。请运行上面的代码片段。我想我可能漏掉了什么,会再看看的。 - Banu
以上代码在这种情况下有效,因为onload事件作为迭代的一部分发生(reader.readAsText)。而在https://dev59.com/sXM_5IYBdhLWcg3wRw11中,onclick事件在迭代完成后发生。 - Banu
虽然这看起来可能有效,但当您运行上面的代码片段并选择多个文件时,列表中的最后一个文件会被重复显示。因此,它并不能按预期工作。 - Chris

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接