【问题标题】:Looping through files for FileReader, output always contains last value from loop遍历 FileReader 的文件,输出始终包含循环中的最后一个值
【发布时间】:2012-04-06 14:54:55
【问题描述】:

我正在使用 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

【问题讨论】:

    标签: javascript html filereader dom-events


    【解决方案1】:

    问题是您正在运行循环现在,但您设置的回调正在运行稍后(当事件触发时)。当它们运行时,循环结束并保持在最后一个值。因此,在您的情况下,它将始终显示“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]);
    }
    

    【讨论】:

    • 我很想知道更多的解释。
    • @srph 它与javascript的变量范围有关。它为嵌套函数创建闭包,并且由于 i 的值每次迭代都会改变,因此在调用嵌套函数时,它将获得数组的最后一个值。因此,Ben Lee 修改代码的方式是确保函数在循环期间读取文件,以便维护索引。我希望这是有道理的。 (这在Effective Javascript 中有很好的描述)
    • 真的很喜欢你写这个答案,伙计。继续努力
    【解决方案2】:

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


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

    虽然它并不完全健壮或面向未来,但值得一提的是,这也可以通过FileReader 对象添加属性来实现:

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

    然后在onload回调函数中通过e访问它:

    li.innerHTML = e.target._NAME + "____" + text;
    


    为什么会这样:

    即使reader 变量在循环期间被多次替换,如inew FileReader 对象是唯一的并保留在内存中。它可以通过e 参数在reader.onload 函数中访问。通过在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/

    【讨论】:

      【解决方案3】:

      我遇到了同样的问题,通过使用 Array.from 解决了它

      let files = e.target.files || e.dataTransfer.files;
      
      Array.from(files).forEach(file => {
       // do whatever
      })
      

      【讨论】:

        【解决方案4】:

        不要使用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");
            }
        

        【讨论】:

          【解决方案5】:

          我认为解决此问题的最佳方法是递归调用读取 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();
          

          【讨论】:

            【解决方案6】:

            您可以为在循环中读取文件做出承诺/回调。

            承诺-

              fileBase64(file) {
                return new Promise((resolve, reject) => {
                  let reader = new FileReader();
                  reader.readAsDataURL(file);
                  reader.onload = function() {
                    resolve(reader.result);
                  };
                  reader.onerror = function(error) {
                    reject(error);
                  };
                });
              }
            

            我在 onClick 上调用这个函数

              onClick = async () => {
                for (var i = 0; i < this.state.company_bank_statement_array.length; i++) {
                  let file = document.getElementById(
                    this.state.company_bank_statement_array[i]
                  );
                  let fileData = await this.fileBase64(file.files[0]);
                  this.state.bankStatements.push({
                    data: fileData,
                    filename: file.files[0].name,
                  });
                }
              };
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2021-12-09
              • 2023-03-19
              • 1970-01-01
              相关资源
              最近更新 更多