前端文件下载和文件读取方法研究

前言

之前因为工作需要对geojson进行数据处理,在操作的过程中我就发现,自己手动去下载和读取文件就很麻烦。因此我就想在这里探讨一下如何在前端实现文件自动读取和下载。

一、文件读取

读取文件功能的技术点其实主要有两个:一是<input type="file">元素 ; 二是 FileReader类型。前者的作用是在浏览器中实现"文件选择"这样一个交互动作;后者的作用则是读取和解析所选择的文件。

1.文件选择器元素

<input type="file">元素的作用是允许用户从他们的设备中选择一个或多个文件。选择后,这些文件可以通过JS代码和文件API进行操作。

1.1 基本示例

HTML 复制代码
<input type="file" >

点击之后就会打开如下的窗口:

1.2 获取文件

点击<input type="file">元素可以打开一个窗口选择文件,那么所选择的文件我们如何拿到呢?

这个问题其实又可以拆解成两个问题,什么时候拿?和 在哪儿拿?

首先回答什么时候拿的问题,当<input type="file">元素上传一个文件时会触发change事件,因此在onchange事件处理程序中去拿,就可以保证此时的文件是存在的,是可以拿的到的。

再回答在哪儿拿的问题,所选择的文件被保存在 <input> 元素的 files属性上,其属性值是一个FileList,FileList是一个类数组结构,其中的每个元素都是File对象,我们可以通过方括号或item()读取其中的元素。

所以这时我们就可以尝试去获取一个文件了:

HTML 复制代码
<!DOCTYPE html>
<html lang="en">
  <body>
    <input type="file" onchange="readFile(event)" />

    <script>
        function readFile(event) {
            const file = event.target.files[0]
            console.log(file);
        }
    </script>
  </body>
</html>

上面打印的就是拿到的文件,它是一个File对象,后续我们还要通过 FileReader 去读取他。

1.3 附加属性

<input type="file">元素还有一些附加属性,这些属性可以帮助我们拓展文件读取功能。

首先是multiple属性,它可以允许<input>选择多个文件。

HTML 复制代码
<!DOCTYPE html>
<html lang="en">
  <body>
    <input type="file" multiple onchange="readFile(event)" />

    <script>
        function readFile(event) {
            const file = event.target.files[0]
            console.log(event.target.files);
        }
    </script>
  </body>
</html>

添加了multiple属性后,我们在选择窗口中就可以选择多个文件了

此时,flies属性中的文件也就有多个

另一个重要的附加属性是accept,它可以指定文件input接收的文件类型,从而实现"限制可接收的文件类型"的功能。accept属性值是一个字符串,这个字符串是一个以逗号为分隔的唯一文件类型说明符列表。

那么这个所谓的"唯一文件类型说明符" 该怎么写呢?它其实是包括以下的这些形式:

  • 一个以英文句号(".")开头的合法的不区分大小写的文件名扩展名。例如:.jpg、.pdf 或 .doc。
  • 一个不带扩展名的 MIME 类型字符串。
  • 字符串 audio/*,表示"任何音频文件"。
  • 字符串 video/*,表示"任何视频文件"。
  • 字符串 image/*,表示"任何图片文件"。

因此,假如我们的文件选择器只需要读取图片,包括标准的图片格式和 PDF 文件,那么大概是这样的:

HTML 复制代码
<input type="file" accept="image/*,.pdf" />

此时所打开的选择窗口中就只剩下我们所限定类型的文件了。

2.使用FileReader解析文件

通过文件选择器我们可以在项目中拿到文件,但此时的文件是 File类型的,这种状态的文件我们无法直接处理和使用。这时就需要使用FileReader解析文件。

2.1基本使用

FileReader使用分为三步:

第一步:实例化一个文件读取器

JavaScript 复制代码
const reader = new FileReader()

第二步:调用文件读取器的某个读取方法,开始读取文件

JavaScript 复制代码
reader.readAsText(file ,'UTF-8') //这个方法可以将文件读取为字符串

第三步: 侦听读取器的onload事件或者onloadend, 当文件读取完毕后,会触发这两个事件。在事件处理程序中通过读取器的result属性可以拿到读取的结果

JavaScript 复制代码
reader.onload = function(){
  this.result //读取的结果
}

读取一个txt文件

此时我们就可以尝试去读取一个文件。首先创建如下的txt文件。

可以读取出txt文件中的内容

HTML 复制代码
<!DOCTYPE html>
<html lang="en">
  <body>
    <input type="file"  onchange="readFile(event)" />

    <script>
      function readFile(event) {
        const file = event.target.files[0]

        const reader = new FileReader()

        reader.readAsText(file ,'UTF-8')

        reader.onload = function () {
          console.log('读取的结果', this.result)
        }
      }
    </script>
  </body>
</html>

2.2 读取方法介绍

FileReader的读取方法可以帮助我们将Bobl、file文件转换为其它的格式。常用的读取方法有三个:

  • readAsArrayBuffer,将文件读取为ArrayBuffer数据对象
  • readAsDataURL,将文件读取为一个URL
  • readAsText,将文件读取为字符串

readAsArrayBuffer方法所转化的结果是一个ArrayBuffer对象是一个用于存储二进制数据的内存,对于

ArrayBuffer我还不是很理解,也不知道在什么情况下使用,等以后研究明白了再做介绍。readAsText在我写的读取文件的工具中就使用了这个方法,让它帮助我读取geojson文件,目前感觉这个方法是最实用的。readAsDataURL方法我看到有的文章中用它来读取图片,下面我就来尝试一下。

读取图片并显示

HTML 复制代码
<!DOCTYPE html>
<html lang="en">
  <head> </head>
  <body>
    <input type="file" accept="image/*" onchange="readImg(event)" />
    <br />
    <img src="" alt="请选择一个图片" width="800" />
    <script>
      function readImg(event) {
        const file = event.target.files[0]

        const reader = new FileReader()

        reader.readAsDataURL(file)

        reader.onloadend = function () {
          console.log('读取的结果', this.result)

          document.images[0].src = this.result
        }
      }
    </script>
  </body>
</html>

二、文件下载

文件下载也分为两步,第一步是创建一个<a>元素(当<a>元素设置了download属性后,浏览器就会将链接的 URL 视为下载资源);第二步是将我们的数据(或文件)转换为URL 。

1.文件下载链接

<a>元素的download属性可以将连接变为文件下载链接。download属性的值为一个字符串,这个字符串就是fileName即下载文件的文件名。

HTML 复制代码
<a href="" download="xxx.json">

2.文件转URL

由于我是需要将js中的数据下载为json文件,所以这里就讨论一下 "如何将js数据转换为url"。

2.1 FileReader方案

首先将数据转换为Bolb对象,Blob对象接收两个参数:

  • Array,一个可迭代对象,包含 ArrayBufferTypedArrayDataViewBlob、字符串或者任意这些元素的混合。
  • Options,一个对象,其中可以指定以下属性。
    • type,将会被存储到 blob中的数据的 MIME 类型;
JavaScript 复制代码
data = JSON.stringify(data)

const blob = new Blob([data], { type: 'Application/json' })

然后再通过FileReader对象的readAsDataURL方法转换为URL

JavaScript 复制代码
const reader = new FileReader()
reader.onload = function () {
  a.href = this.result //读取到的URL
  a.click()
}
reader.readAsDataURL(blob)

2.2 URL.createObjectURL方案

也可以直接使用URL.createObjectURL方法将 blob对象转换为URL。(为了获得最佳性能和内存使用状况,建议在URL对象使用完毕后,通过URL.revokeObjectURL方法将其释放)

JavaScript 复制代码
// 创建blob对象
data = JSON.stringify(data)
const blob = new Blob([data], { type: 'Application/json' })

// 将blob转换为URL对象
const url = URL.createObjectURL(blob)

// 设置url并点击下载
a.href = url
a.click()

// 释放URL对象
URL.revokeObjectURL(url)

三、实现一个先文件读取后文件下载的小工具

为了方便工作中的数据处理,我编写了一个小工具,它可以读取geojson文件,然后处理其中的数据,最后将数据下载为json文件。

1.读取文件函数

JavaScript 复制代码
function readFile() {
  return new Promise((resolve) => {
    const fileElement = document.createElement('input')
    fileElement.type = 'file'
    fileElement.multiple = true
    fileElement.accept = ".json,.geojson"

    fileElement.onchange = (event) => {
      // 获取文件
      let fileArr = Array.from(event.target.files)

      Promise.all(
        fileArr.map(
          (file) =>
            new Promise((success) => {
              // 实例化一个文件读取器对象
              const reader = new FileReader()
              // 让读取器开始读取
              reader.readAsText(file)
              // 当文件读取完成后处理数据
              reader.onload = (e) => {
                const data = JSON.parse(e.target.result)

                success(data)
              }
            })
        )
      ).then((res) => {
        resolve(res)
      })
    }
    fileElement.click()
  })
}

读取文件基本还是按照之前介绍的思路进行实现的,这里重点介绍一下我在实现这个readFile函数时遇到一个难点。

为了提高函数的泛用性,我希望可以将读取到的文件数据return出去。为什么return会成为一个问题能?因为实际上这个函数中的文件读取是一组复杂的异步任务。

于是我使用期约来处理这里的异步任务:

JavaScript 复制代码
function readFile() {
  return new Promise((resolve) => {
    // 第一个Promise用于处理文件选择器的change事件
    // ......
      //Promise.all()用于实现多个文件的读取操作并行,保证能够拿到所有文件的数据
      Promise.all(
        fileArr.map(
          (file) =>
            new Promise((success) => {
              // ......
              //  第二个Promise用于处理FileReader的load事件
                success(data)
            })
        )
      ).then((res) => {
        resolve(res)
      })
  })
}

2.下载文件函数

JavaScript 复制代码
//下载文件
function download(files) {
  if (!files) return alert('请先读取一个文件')

  files.forEach((el) => {
    const a = document.createElement('a')
    a.download = el.name || getNowTime()
    const fileData = JSON.stringify(el.data)
    const blob = new Blob([fileData], { type: 'Application/json' })
    a.href = URL.createObjectURL(blob)
    a.click()
  })
  alert('文件下载成功')
}

下载文件的函数就很简单了,这里我也就不过多介绍了。

3.完整代码

index.html

HTML 复制代码
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="https://unpkg.com/@turf/turf/turf.min.js"></script>
    <script src="./util/util.js"></script>
    <script src="./util/File.js"></script>
    <script src="./main/handle.js"></script>
  </head>
  <body>
    <button id="read">读取</button>
    <button id="handle">处理数据</button>
    <button id="download">下载</button>

    <script>
      let files

      document.getElementById('read').addEventListener('click', () => {
        readFile().then((data) => {
          console.log(data)
          files = data
          alert('文件读取成功')
        })
      })

      document.getElementById('handle').addEventListener('click', () => {
        handleData(files).then((res) => {
          console.log(res)

          files = res

          alert('数据处理成功')
        })
      })

      document.getElementById('download').addEventListener('click', () => {
        download(files)
      })
    </script>
  </body>
</html>

File.js

HTML 复制代码
function readFile() {
  return new Promise((resolve) => {
    const fileElement = document.createElement('input')
    fileElement.type = 'file'
    fileElement.multiple = true
    fileElement.accept = ".json,.geojson"

    fileElement.onchange = (event) => {
      // 获取文件
      let fileArr = Array.from(event.target.files)

      Promise.all(
        fileArr.map(
          (file) =>
            new Promise((success) => {
              // 实例化一个文件读取器对象
              const reader = new FileReader()
              // 让读取器开始读取
              reader.readAsText(file)
              // 当文件读取完成后处理数据
              reader.onload = (e) => {
                const data = JSON.parse(e.target.result)

                success(data)
              }
            })
        )
      ).then((res) => {
        resolve(res)
      })
    }
    fileElement.click()
  })
}

//下载文件
function download(files) {
  if (!files) return alert('请先读取一个文件')

  files.forEach((el) => {
    const a = document.createElement('a')
    a.download = el.name || getNowTime()
    const fileData = JSON.stringify(el.data)
    const blob = new Blob([fileData], { type: 'Application/json' })
    a.href = URL.createObjectURL(blob)
    a.click()
  })
  alert('文件下载成功')
}

// 获取当前时间
function getNowTime() {
  var date = new Date() //时间戳为10位需*1000,时间戳为13位的话不需乘1000
  var Y = date.getFullYear() + '-'
  var M =
    (date.getMonth() + 1 < 10
      ? '0' + (date.getMonth() + 1)
      : date.getMonth() + 1) + '-'
  var D = (date.getDate() < 10 ? '0' + date.getDate() : date.getDate()) + ' '
  var h = (date.getHours() < 10 ? '0' + date.getHours() : date.getHours()) + ':'
  var m =
    (date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes()) + ':'
  var s = date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds()
  return Y + M + D + h + m + s
}

参考资料

  1. 纯前端下载json文件-CSDN博客
  2. 【前端下载文件流详解】前端实现多种类型文件(word,excel,pdf,rar,zip等)的下载,接口返回文件流形式(附源码)-CSDN博客
  3. js 读取文件内容_前端读取本地文件-CSDN博客
  4. - HTML(超文本标记语言) | MDN
  5. FileReader - Web API 接口参考 | MDN
  6. FileReader.readAsDataURL() - Web API 接口参考 | MDN
  7. :锚元素 - HTML(超文本标记语言) | MDN
相关推荐
To_OC17 分钟前
LC 17 电话号码的字母组合:我的回溯算法,就是从这道题开窍的
javascript·算法·leetcode
vipbic25 分钟前
中后台越做越乱后,我用插件化把它救回来了
前端·vue.js
Hyyy31 分钟前
Computer Use 适合做什么,不适合做什么——一次真实使用后的思考
前端
小和尚同志1 小时前
前端 AI 单元测试思考与落地
前端·人工智能·aigc
invicinble2 小时前
c端系统,其实更像一个信息展示平台
前端
你怎么知道我是队长2 小时前
JavaScript的变量和数据类型介绍
开发语言·javascript·ecmascript
李姆斯3 小时前
管理是否可以被完全量化
前端·产品经理·团队管理
名字还没想好☜4 小时前
Next.js 中间件实战:鉴权、重定向与 A/B 分流
开发语言·前端·javascript·中间件·react·next.js
广州灵眸科技有限公司4 小时前
瑞芯微RV1126B开发板(EASY-EAI-PI2) INI文件操作
java·前端·javascript·网络·人工智能
心中有国也有家4 小时前
AtomGit Flutter 鸿蒙客户端:ModalBottomSheet 实战
android·javascript·学习·flutter·华为·harmonyos