DEV Community

Yao
Yao

Posted on

如何使用 JavaScript 下载文件

下载文件是上网的一个重要方面。每天从 Internet 下载大量文件,从二进制文件(如应用程序、图像、视频和音频)到纯文本文件。
Web 开发人员想将此功能添加到应用程序中,那么可以按照以下方法进行操作。

我们将检查 3 种不同的方法:

  • 仅使用 HTML 元素的基本模式;
  • 使用带有 Fetch API 和 HTML 元素的 JavaScript;
  • 使用 XMLHttpRequest 和 HTML 元素,但在更复杂的场景中,我们实施一个系统来衡量进度;

方法一

第一种也是最简单的方法意味着创建一个具有 download 属性的 a 标签。
根据定义,download 属性指定当用户单击超链接时将下载目标(href 属性中指定的文件)。

此外,使用此下载属性,我们可以在下载文件后指定文件的新名称。因此,如果我们想下载具有特定名称的文件,我们可以使用此属性来控制它。但是,当本机下载窗口出现时,用户仍然可以更改名称,但我们提供的名称将是默认名称。

如果省略该值,则使用原始文件名。

<a href="https://t-bucket.sfo3.digitaloceanspaces.com/100mb.txt" download>Download 100mb.txt</a>
Enter fullscreen mode Exit fullscreen mode

当我们不需要根据这个下载过程做任何操作时,这个方法就很棒。同时,即使我们无法使用 JavaScript 呈现 a 标签,我们也可以使用此方法:

function downloadUsingAnchorElement() {
  const anchor = document.createElement('a');
  anchor.href = IMG_URL;
  anchor.download = FILE_NAME;

  document.body.appendChild(anchor);
  anchor.click();
  document.body.removeChild(anchor);
}
Enter fullscreen mode Exit fullscreen mode

上面的函数做同样的事情,只是我们动态创建 a 标签,仅用于此下载操作,然后我们将其删除。

  • IMG_URL 是我们要下载的图片的URL;

  • FILE_NAME 为文件下载后的新名称;

此方法的局限性在于它必须遵循同源策略,因此该属性适用于同源 URL。

一个常见的场景是,当你想从另一台服务器下载图像时,浏览器没有下载它,而是在新选项卡中打开它。

此方法的关键方面是下载过程将自动开始,并且可以在浏览器中本地查看。

方法二

第二种方法和第三种方法使用与 a 标签相同的技术,但我们不提供图像 URL,而是将文件的内容转换为 Blob,然后使用 createObjectURL 方法从中创建 DOMString。

async function downloadUsingFetch() {
  const image = await fetch(IMG_URL);
  const imageBlog = await image.blob();
  const imageURL = URL.createObjectURL(imageBlog);

  const anchor = document.createElement('a');
  anchor.href = imageURL;
  anchor.download = FILE_NAME;

  document.body.appendChild(anchor);
  anchor.click();
  document.body.removeChild(anchor);

  URL.revokeObjectURL(imageURL);
}
Enter fullscreen mode Exit fullscreen mode

请注意,最后我们使用了 URL.revokeObjectURL ,这在内存管理方面很重要。使用 URL.createObjectURL 时会创建一个新的对象 URL,即使它是使用相同的 blob 对象调用的。

无论何时创建对象 URL,它都会在创建它的文档的整个生命周期内保持不变。浏览器将在卸载文档时释放所有对象 URL。但是,重要的是在不再需要对象 URL 时释放它们,以提高性能并最大限度地减少内存使用。

此方法的关键方面是下载过程将自动开始,但在我们的应用程序内,只有在下载完成后才会传递给浏览器。

Image description
请注意,在上面的 GIF 中,一旦我们单击“下载”按钮,似乎什么也没有发生,因为下载在我们的应用程序中作为异步任务发生,一旦完成,它将传递给浏览器。

一旦该浏览器窗口出现并且我们单击保存,该文件将自动保存在我们的计算机上。

使用这种方法,我们现在可以下载任何类型的文件,而不管源服务器是什么。然而,问题是因为下载发生在我们的应用程序内部,用户可能认为他点击时什么也没发生,因此我们需要通过实施进度测量来管理大文件下载。

同时,当我们需要在文件下载完成后在应用程序中执行某些操作时,此方法很有用。显示一条消息,向后端发送请求渲染一个新页面,等等......

方法三

第三种方法与第二种方法类似,我们仍将使用 Blob 和 createObjectURL,但我们将使用 XMLHttpRequest 而不是使用 Fetch API。

我们使用 XMLHttpRequest 而不是 Fetch,因为目前 Fetch API 不提供进度测量接口,而 XMLHttpRequest 提供。

function downloadWithProgress() {
  const startTime = new Date().getTime();

  request = new XMLHttpRequest();

  request.responseType = "blob";
  request.open("get", IMG_URL, true);
  request.send();

  request.onreadystatechange = function () {
    if (this.readyState === 4 && this.status === 200) {
      const imageURL = window.URL.createObjectURL(this.response);

      const anchor = document.createElement("a");
      anchor.href = imageURL;
      anchor.download = FILE_NAME;
      document.body.appendChild(anchor);
      anchor.click();
    }
  };

  request.onprogress = function (e) {
    const percent_complete = Math.floor((e.loaded / e.total) * 100);

    const duration = (new Date().getTime() - startTime) / 1000;
    const bps = e.loaded / duration;

    const kbps = Math.floor(bps / 1024);

    const time = (e.total - e.loaded) / bps;
    const seconds = Math.floor(time % 60);
    const minutes = Math.floor(time / 60);
    console.log(
      `${percent_complete}% - ${kbps} Kbps - ${minutes} min ${seconds} sec remaining`
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

开始和 onreadystatechange 块类似于第二个函数。将响应下载为 Blob 对象,创建 DOMString,并使用锚元素下载文件。

Insideonprogress 我们使用 e.loaded 和 e.total 值来计算进度的百分比和经过的时间,以及下载速度和剩余时间。

Image description

请注意,在上面的 GIF 中,我们的行为与第二种方法相同,只是现在我们可以监控进度。文件下载完成后,将被发送到浏览器,然后立即保存到磁盘。

总结

上述每个方法都代表了对前一个方法的更新。

第一种方法是最简单的。在此,我们只是将下载过程转发给浏览器以在本地进行管理。当应用程序不必根据加载状态执行某些操作时,此方法是首选方法。

在第二种情况下,我们在内部管理下载并仅在下载完成后才将其发送到浏览器。通过这种方式,我们可以控制应用程序内部的下载,我们可以根据其状态做出反应。此方法适用于快速下载的小文件,但当文件太大时,如果 UI 上没有任何提示用户正在下载,用户可能会认为应用程序有问题。

在最后一个方法中,我们实现了自己的进度测量,这与浏览器中的类似。

Top comments (0)