当前位置: 首页 > 知识库问答 >
问题:

javascript - 读取文件的 IO 操作,为什么要先实例化一个 FileReader 呢?

党源
2024-07-12

比如页面上有一个 <input type="file" onChange="todo"> 的选择器,我选择完以后绑定相对应的 onChange 事件。

function todo(){}
const fileReader = new FileReader()
const file=inputElement.files[0]
//接下来处理文件
//fileReader 的一些文件处理方法

  if (file) {
    fileReader.readAsDataURL(file)
  }

  fileReader.addEventListener(
    'load',
    () => {
      const result = fileReader.result
      const resultContainer = document.getElementById('result')
      const img = document.createElement('img')
      img.src = result
      resultContainer.append(img)
    },
    { once: true }
  )

这种 FilerReader 实例化以后再进行读取操作的方法我在学习 go 这门语言中也见到过类似的设计。

问题:我好奇的点在于,为什么我们总要创建一个 fileReader事例再去做一些事情?(为什么不直接给构造函数传参数 fileReader= new FileReader([file]) 然后假设就可以在 fileReader 实例的属性上进行读取操作。比如:fileReader.resultfileReader.readAsDataUrl

这种设计模式有什么好处吗?或者说 new FileReader(file) 直接传参有什么弊端吗?

共有4个答案

戚俊健
2024-07-12

回答:应该是JavaScript设计的WebApi和Java那种不一样,这个fileReader.read的相关操作都是异步的,写成调用函数传参的时候代入读取的内容,如file对象,可以比较好的在回调函数中使用这个对象,不然如果直接放在构造函数中去传入,重复使用这个fileReader就需要不停的重新调用构造函数,不免有些浪费了;而且这种基于事件回调类型的api很少通过构造函数直接传入,使用作用域大了,得考虑的事情会变多。

颜嘉誉
2024-07-12

类与实例的设计,方便我们将类的状态保存在实例中,以备将来使用。并提供多样性的方法,满足不同需求。

换句话说,如果一个需求比较简单,一个动作就能完成,也没有太多衍生操作,那么做成函数就比较合适。比如 atobbtoa。反之,如果需求比较复杂,或者要覆盖更多的场景,可能类与实例的模式就更合适。比如你问题中的文件。你只用到一个方法,所以觉得函数化更方便;但是实际上需求会更多样,所以目前这样的设计更有价值。

颜鸿云
2024-07-12

为什么要实例化FileReader再用?
实例化再用,更容易扩展逻辑。
比如你的例子,

const fileReader = new FileReader()
fileReader.readAsText(file)
fileReader.addEventListener('load', () => {
  console.log(fileReader.result)
})

如果后续有人反馈,文件很大,读取文件需要很长时间,希望加进度条,有了FileReader实例,我们就能方便地再加一个监听函数

const fileReader = new FileReader()
fileReader.readAsText(file)
fileReader.addEventListener('load', () => {
  console.log(fileReader.result)
})

// 新增进度监听
fileReader.addEventListener('progress', (ev) => {
  console.log(`${ev.loaded}/${ev.total}`)
})

如果后续又有人提出增加可以取消按钮,我们可以继续利用FileReader实例,调用abort()方法中断读取。

const fileReader = new FileReader()
fileReader.readAsText(file)
fileReader.addEventListener('load', () => {
  console.log(fileReader.result)
})

// 新增进度监听
fileReader.addEventListener('progress', (ev) => {
  console.log(`${ev.loaded}/${ev.total}`)
})

// 新增取消按钮
cancel.addEventListener('click', () => {
  fileReader.abort()
}, { once: true })

由上可见,实例化FileReader再操作,可以更灵活地利用FileReader提供的接口,做更多的事。


然后再说说为什么不是直接new FileReader(file)
如果做过java开发,会发现java确实是这么干的。

public static void main(String[] args) {
  char[] array = new char[100];
  FileReader input = new FileReader("file.txt");

  // Reads characters
  input.read(array);

  // Closes the reader
  input.close();
}

以下是我的推测了,不一定正确。
首先JS天生是异步的,文件的加载异步过程,一般不会设计在构造函数里。你肯定不会见过、也不允许写出这样的构造函数:

class MyClass {
  // 报错 'async' modifier cannot appear on a constructor declaration
  async constructor() {
    
  }
}

当然,你也可以说,构造函数里并不立刻加载文件,可以在调用fileReader.readAsDataUrl()再加载嘛。
这样的设计确实可行。但这需要考虑一点,就是避免不必要的理解成本。
new FileReader(file)fileReader.readAsDataUrl(),会给人造成错误的理解,误认为文件加载发生在构造函数中。如果换成new FileReader()fileReader.readAsDataUrl(file),那就能明确提示文件加载是发生在后一步里。

另外先new FileReader()fileReader.readAsDataUrl(file)还有一点好处,就是可以单例复用,比如:

const input = document.getElementById('input')

// 只用一个实例
const fileReader = new FileReader()

input.onchange = () => {
  const file = input.files?.item(0)
  if (!file) return

  fileReader.readAsText(file)
  fileReader.onload = () => {
    console.log(fileReader.result)
  }
}

这样可以减少FileReader的构造次数。


另外再提一下,FileReaderXMLHttpRequest差不多是一个年代的产物,那时候非常推崇OOP(面向对象),所以“先实例化再调用方法”这种API设计也很流行。比如XMLHttpRequest

const req = new XMLHttpRequest()
req.open("GET", "http://www.example.org/example.txt")
req.addEventListener("load", () => {
  console.log(req.responseText)
})
req.send()

是不是感觉跟FileReader有几分相像?
不过现在有了fetch,就不需要那么麻烦了。

const result = await fetch('http://www.example.org/example.txt').then(r => r.text())
console.log(result)

你可能会好奇FileReader有没有替代的方案?
有,但要具体分析,取决于你要获取怎样的数据。
还是以<input type="file" id="input">为例。
如果是加载文本:

const input = document.getElementById('input')

input.addEventListener('change', async () => {
  const file = input.files?.item(0)
  if (!file) return

  // 没错,File继承自Blob,自带text()方法了
  const text = await file.text()

  console.log(text)
})

如果是加载图片:

/** @type {HTMLInputElement} */
const input = document.getElementById('input')
/** @type {HTMLImageElement} */
const img = document.getElementById('img')

input.addEventListener('change', () => {
  const file = input.files?.item(0)
  if (!file) return

  // 创建一个虚拟URL给img标签使用
  const blobURL = URL.createObjectURL(file)
  img.src = blobURL

  // img标签加载完成后,释放虚拟URL
  img.addEventListener('load', () => {
    URL.revokeObjectURL(blobURL)
  }, { once: true })
})
呼延化
2024-07-12

回答

在 JavaScript 中,FileReader 对象的设计是为了异步读取存储在用户计算机上的文件(或原始数据缓冲区)的内容,使用 FileBlob 对象指定要读取的文件或数据。这种设计模式遵循了 JavaScript 中常见的异步编程模式,并且与 Web API 的其他部分保持一致。

为什么 FileReader 需要先实例化,然后再调用其方法来读取文件,而不是直接在构造函数中传入文件并立即读取,主要有以下几个原因:

  1. 异步性:文件读取可能是一个耗时的操作,特别是当文件很大或者网络条件不佳时。因此,JavaScript 提供了 FileReader API 来异步处理这些操作,以避免阻塞主线程。如果 FileReader 允许在构造函数中直接传入文件并立即读取,那么它就需要同步地等待读取完成,这可能会导致用户界面无响应或延迟。
  2. 可复用性:通过实例化 FileReader 对象,你可以多次使用同一个对象来读取不同的文件,或者对同一个文件执行不同的读取操作(如 readAsTextreadAsDataURL 等)。这使得 FileReader 对象更加灵活和可复用。
  3. 事件驱动FileReader API 是基于事件的。当你调用 readAsDataURLreadAsText 等方法时,文件读取操作会立即开始,但结果并不会立即返回。相反,当读取操作完成时,会触发一个 load 事件,你可以通过监听这个事件来获取读取结果。这种基于事件的设计模式允许你在文件读取过程中执行其他操作,并在读取完成时得到通知。
  4. 错误处理:除了 load 事件外,FileReader 还提供了 errorprogress 等其他事件。通过监听这些事件,你可以处理读取过程中的错误或监视读取进度。

如果将文件直接作为参数传递给 FileReader 构造函数,并在构造函数中立即读取文件,那么上述的所有优点都将不复存在。此外,直接在构造函数中读取文件也会破坏 JavaScript 的异步编程模型和事件驱动的设计模式。

因此,尽管直接在构造函数中传入文件并立即读取看起来可能更简洁,但这种设计并不符合 JavaScript 的异步编程模型和 Web API 的设计原则。通过先实例化 FileReader 对象,再调用其方法来读取文件,我们可以更好地利用 JavaScript 的异步性和事件驱动的特性,从而创建更加高效、灵活和可维护的代码。

 类似资料:
  • 由来 在FileUtil中本来已经针对文件的读操作做了大量的静态封装,但是根据职责分离原则,我觉得有必要针对文件读取单独封装一个类,这样项目更加清晰。当然,使用FileUtil操作文件是最方便的。 使用 在JDK中,同样有一个FileReader类,但是并不如想象中的那样好用,于是Hutool便提供了更加便捷FileReader类。 //默认UTF-8编码,可以在构造中传入第二个参数做为编码 Fi

  • 本文向大家介绍Python 3.6 读取并操作文件内容的实例,包括了Python 3.6 读取并操作文件内容的实例的使用技巧和注意事项,需要的朋友参考一下 所使用python环境为最新的3.6版本 Python中几种对文件的操作方法: 将A文件复制到B文件中去(保持原来格式) 读取文件中的内容,返回List列表 (加载本地词典库) 读取文件,返回文件内容 以上这篇Python 3.6 读取并操作文

  • 我已经完成了一些Java教程,它们都说在调用类时创建一个新变量。这是为什么?我已经测试了一些代码,但它没有这样做。我已经使用python很长一段时间了,所以我习惯于使用动态语言。 请看下面我一直在玩的一些代码: 谢谢你的时间。

  • 我对java比较陌生,对如何使用缓冲读取器读取文件很好奇。这是因为我正在上一门课,被分配做一个简单的ceaser密码,我应该解密一个文本文件,创建一个新文件,并将解密的文本放入该文件。我可以用扫描仪和一个10KB的小文件来完成,但是当我要测试的100MB的大文本文件的时候,它是非常慢的。这是我的代码,它应该是读取文件内容。 如果有人能给我指明正确的方向,那就太好了。 提前致谢

  • 本文向大家介绍JavaScript操作XML文件之XML读取方法,包括了JavaScript操作XML文件之XML读取方法的使用技巧和注意事项,需要的朋友参考一下 本文实例讲述了JavaScript操作XML文件之XML读取方法。分享给大家供大家参考。具体分析如下: 假设我们现在要读取下面的 info.xml 文件 接下来,读取并遍历info.xml 希望本文所述对大家的javascript程序设

  • 1. 打开和关闭文件 1.1 打开文件 访问文件前,需要使用用 Python 内置的 open() 函数打开一个文件: open(path, access_mode) path 是要访问的文件的路径名 access_mode 是文件的访问模式 可以是只读、读写、追加等模式,所有可能的取值见 1.2 小节 这个参数是可选的,缺省情况下,是以只读模式 r 打开文件 open 返回一个 file 对象