Давайте поговорим о 15.5K FileSaver, как он работает?

внешний интерфейс
Давайте поговорим о 15.5K FileSaver, как он работает?

FileSaver.jsРешение для сохранения файлов на стороне клиента, идеально подходящее для веб-приложений, генерирующих файлы на стороне клиента. Он прост в использовании и совместим с большинством браузеров и используется в качестве зависимости в 63 000 проектов. В недавнем проекте брат Абао снова использовал его, поэтому я хотел написать статью, чтобы рассказать об этом прекрасном проекте с открытым исходным кодом.

1. Введение в FileSaver.js

FileSaver.jsЯвляется реализацией HTML5 saveAs() FileSaver. Он поддерживает большинство основных браузеров, и его совместимость показана на следующем рисунке:

(Источник изображения:GitHub.com/Eli GRE собирается/FI…

Следуйте «Дороге полного совершенствования», чтобы прочитать 3 бесплатные электронные книги (в общей сложности более 20 000 загрузок) и более 50 учебных пособий «Повторное изучение TS» от брата Абао.

1.1 saveAs API

FileSaver saveAs(Blob/File/Url, optional DOMString filename, optional Object { autoBom })

Метод saveAs поддерживает три параметра, первый параметр указывает, что он поддерживаетBlob/File/UrlТри типа, второй параметр представляет имя файла (необязательно), а третий параметр представляет объект конфигурации (необязательно). если тебе нужноFlieSaver.jsПодсказки кодировки текста Unicode предоставляются автоматически (ссылка:знак порядка байтов), необходимо установить{ autoBom: true}.

1.2 Сохранить текст

let blob = new Blob(["大家好,我是阿宝哥!"], { type: "text/plain;charset=utf-8" });
FileSaver.saveAs(blob, "hello.txt");

1.3 Сохранить онлайн-ресурсы

FileSaver.saveAs("https://httpbin.org/image", "image.jpg");

Если загруженный URL относится к тому же домену, что и текущий сайт, он будет использоватьсяa[download]способ скачать. В противном случае он будет использоваться первымСинхронизированный запрос HEADопределить, поддерживать лиCORSМеханизм, если он поддерживается, будет выполнять загрузку данных и использовать URL-адреса BLOB-объектов для загрузки файлов. если не поддерживаетсяCORSмеханизм, попробую использоватьa[download]способ скачать.

Стандартный файловый API W3CBlobИнтерфейс доступен не во всех браузерах, для решения этой проблемы вы можете использоватьBlob.jsдля решения проблем совместимости.

(Источник изображения:потрите news.com/?search=no…

1.4 Сохранение содержимого холста Canvas

let canvas = document.getElementById("my-canvas");
canvas.toBlob(function(blob) {
  saveAs(blob, "abao.png");
});

должен быть в курсеcanvas.toBlob()метод доступен не во всех браузерах, для этой проблемы вы можете использоватьcanvas-toBlob.jsдля решения проблем совместимости.

(Источник изображения:потрите newser.com/?search=to B…

В приведенных выше примерах мы видели Blob много раз, поэтому, представляя исходный код FileSaver.js, брат Бао кратко представит соответствующие знания о Blob.

2. Введение в блоб

Blob (Binary Large Object) представляет собой большой объект двоичного типа. В системе управления базами данных двоичные данные хранятся как набор одного объекта. Блобы обычно представляют собой изображения, звуковые или мультимедийные файлы.Объекты типа Blob в JavaScript представляют собой неизменяемые файловые объекты необработанных данных.

2.1 Конструктор больших двоичных объектов

Blobопциональной строкойtype(обычно тип MIME) иblobPartsсочинение:

MIME (многоцелевые расширения почты Интернета) — это тип многоцелевого расширения почты Интернета, который представляет собой способ установить файл с определенным расширением для открытия приложением.При доступе к файлу расширения браузер будет автоматически использовать указанный расширение приложение для открытия. Он в основном используется для указания некоторых определяемых клиентом имен файлов, а также некоторых методов открытия медиафайлов.

Общие типы MIME: язык гипертекстовой разметки text .html text/html, изображение PNG .png image/png, обычный текст .txt text/plain и т. д.

В JavaScript мы можем создавать объекты Blob с помощью конструктора Blob Синтаксис конструктора Blob следующий:

var aBlob = new Blob(blobParts, options);

Соответствующие параметры описываются следующим образом:

  • blobParts: это массив объектов, таких как ArrayBuffer, ArrayBufferView, Blob, DOMString и т. д. DOMStrings будут закодированы как UTF-8.
  • options: необязательный объект, содержащий следующие два свойства:
    • тип - значение по умолчанию"", который представляет тип MIME содержимого массива, которое будет помещено в большой двоичный объект.
    • окончания - значение по умолчанию"transparent", используется для указания окончания строки, включающей\nкак пишется строка. Это одно из следующих двух значений:"native", что означает, что символ конца строки заменяется символом новой строки, подходящим для файловой системы основной операционной системы, или"transparent", что означает, что терминатор, сохраненный в большом двоичном объекте, останется неизменным.

Теперь, когда мы рассмотрели большие двоичные объекты, давайте поговорим об URL-адресах больших двоичных объектов.

2.2 Blob URL

URL-адрес BLOB-объекта/URL-адрес объекта — это псевдопротокол, который позволяет использовать объекты Blob и File в качестве источников URL-адресов для изображений, ссылок для загрузки двоичных данных и многого другого. В браузере мы используемURL.createObjectURLметод создания URL-адреса BLOB-объекта, который принимаетBlobобъекта и создать для него уникальный URL видаblob:<origin>/<uuid>, соответствующий пример выглядит следующим образом:

blob:https://example.org/40a5fb5a-d56d-4a33-b4e2-0acf6a8e5f641

Внутри браузера для каждого проходаURL.createObjectURLСгенерированный URL-адрес сохраняет URL-адрес → Карта BLOB-объектов. Поэтому такие URL короче, но к ним можно получить доступBlob. Сгенерированный URL-адрес действителен только в том случае, если текущий документ открыт. это позволяет цитировать<img>,<a>серединаBlob, но если вы перейдете по URL-адресу большого двоичного объекта, которого больше не существует, вы получите ошибку 404 в своем браузере.

Приведенный выше URL-адрес большого двоичного объекта выглядит великолепно, но на самом деле он имеет побочные эффекты.Хотя сопоставление URL-адрес → BLOB-объект сохраняется, сам BLOB-объект все еще находится в памяти, и браузер не может его освободить. Сопоставление автоматически очищается, когда документ выгружается, поэтому объект Blob затем освобождается.. Однако, если приложение является долгоживущим, это не произойдет в ближайшее время. Таким образом, если мы создадим URL-адрес большого двоичного объекта, он будет существовать в памяти, даже если этот большой двоичный объект больше не нужен.

Для этой проблемы мы можем вызватьURL.revokeObjectURL(url) удаляет ссылку из внутренней карты, позволяя удалить большой двоичный объект, если нет других ссылок, и освобождает память.

Хорошо, теперь мы рассмотрели большие двоичные объекты и URL-адреса больших двоичных объектов. Если вы все еще не имеете ни малейшего представления и хотите глубже понять Blob, вы можете прочитатьКапли, о которых вы не зналиВ этой статье мы начнем анализировать исходный код FileSaver.js.

Если вы хотите узнать идеи и навыки чтения исходного кода, вы можете прочитатьИспользуя эти идеи и методы, я прочитал множество отличных проектов с открытым исходным кодом.Эта статья.

В-третьих, анализ исходного кода FileSaver.js.

В FileSaver.js есть три схемы для реализации сохранения файлов, поэтому мы представим эти три схемы отдельно.

3.1 Вариант 1

Когда FileSaver.js сохраняет файл, если тег a на текущей платформе поддерживаетdownloadсвойств, а не в среде MacOS WebView, он будет иметь приоритетa[download]чтобы сохранить файл. В процессе конкретного использования мы называемsaveAsспособ сохранения файла, метод определяется следующим образом:

FileSaver saveAs(Blob/File/Url, optional DOMString filename, optional Object { autoBom })

Наблюдая за сигнатурой метода saveAs, мы знаем, что метод поддерживает два типа параметров: string и Blob, поэтому эти два типа параметров необходимо обрабатывать отдельно в методе saveAs.Давайте сначала проанализируем ситуацию со строковыми параметрами.

3.1.1 Параметры строкового типа

В предыдущем примере мы продемонстрировали, как использоватьsaveAsСпособы сохранения изображений онлайн:

FileSaver.saveAs("https://httpbin.org/image", "image.jpg");

В схеме один,saveAsЛогика обработки метода следующая:

// Use download attribute first if possible (#193 Lumia mobile) unless this is a macOS WebView
function saveAs(blob, name, opts) {
  var URL = _global.URL || _global.webkitURL;
  var a = document.createElement("a");
  name = name || blob.name || "download";

  a.download = name;
  a.rel = "noopener";

  if (typeof blob === "string") {
    a.href = blob;
    if (a.origin !== location.origin) { // (1)
      corsEnabled(a.href)
        ? download(blob, name, opts)
        : click(a, (a.target = "_blank"));
    } else { // (2)
      click(a);
    }
  } else {
    // 省略处理Blob类型参数
  }
}

В приведенном выше коде, если обнаружится, что URL-адрес загруженного ресурса не находится в том же домене, что и текущий сайт, он будет использоваться первым.Синхронизированный запрос HEADопределить, поддерживать лиCORSмеханизм, если он поддерживается, вызоветdownloadспособ скачивания файлов. Сначала проанализируемcorsEnabledметод:

function corsEnabled(url) {
  var xhr = new XMLHttpRequest();
  xhr.open("HEAD", url, false);
  try {
    xhr.send();
  } catch (e) {}
  return xhr.status >= 200 && xhr.status <= 299;
}

corsEnabledРеализация метода очень проста, т.XMLHttpRequestAPI инициирует синхронный запрос HEAD, а затем определяет, находится ли возвращенный код состояния в[200 ~ 299]В диапазоне. Тогда давайте посмотримdownloadКонкретная реализация метода:

function download(url, name, opts) {
  var xhr = new XMLHttpRequest();
  xhr.open("GET", url);
  xhr.responseType = "blob";
  xhr.onload = function () {
    saveAs(xhr.response, name, opts);
  };
  xhr.onerror = function () {
    console.error("could not download file");
  };
  xhr.send();
}

такой жеdownloadРеализация метода также очень проста, в том числе за счетXMLHttpRequestЗнакомый всем API для инициации HTTP-запросовJSONФормат другой, нам нужно установитьresponseTypeимеет типblob. Кроме того, поскольку возвращаемый результат представляет собой данные типа blob, он будет продолжать вызываться внутри функции обратного вызова успеха.saveAsметод сохранения файла.

А для случая, когда не поддерживается механизм CORS или тот же домен, он вызовет внутреннийclickспособ завершения функции загрузки, конкретная реализация этого метода выглядит следующим образом:

// `a.click()` doesn't work for all browsers (#465)
function click(node) {
  try {
    node.dispatchEvent(new MouseEvent("click"));
  } catch (e) {
    var evt = document.createEvent("MouseEvents");
    evt.initMouseEvent(
      "click", true, true, window, 0, 0, 0, 80, 20, 
      false, false, false, false, 0, null
    );
    node.dispatchEvent(evt);
  }
}

существуетclickВнутри метода сначала будет вызываться метод объекта node.dispatchEventспособ отправкиclickсобытие. Когда возникает исключение, оно будетcatchОператор выполняет соответствующую обработку исключений,catchв предложенииMouseEvent.initMouseEvent()Метод используется для инициализации значения события мыши.Но следует отметить, что эта функция была удалена из веб-стандарта, хотя некоторые браузеры все еще поддерживают ее в настоящее время, но могут перестать поддерживать ее в будущем, пожалуйста, постарайтесь не использовать эту функцию..

3.1.2 параметры типа BLOB-объекта

Опять же, в предыдущем примере мы продемонстрировали, как использоватьsaveAsспособ сохранения данных типа Blob:

let blob = new Blob(["大家好,我是阿宝哥!"], { type: "text/plain;charset=utf-8" });
FileSaver.saveAs(blob, "hello.txt");

Логика обработки параметров типа blob определена вsaveAsВ другой ветке тела метода:

// Use download attribute first if possible (#193 Lumia mobile) unless this is a macOS WebView
function saveAs(blob, name, opts) {
  var URL = _global.URL || _global.webkitURL;
  var a = document.createElement("a");
  name = name || blob.name || "download";

  a.download = name;
  a.rel = "noopener";

  if (typeof blob === "string") {
     // 省略处理字符串类型参数
  } else {
    a.href = URL.createObjectURL(blob);
    setTimeout(function () {
      URL.revokeObjectURL(a.href);
    }, 4e4); // 40s
    setTimeout(function () {
      click(a);
    }, 0);
  }
}

Для параметров типа blob первый проходcreateObjectURLметод для создания URL-адреса объекта, а затем передатьclickМетод выполняет сохранение файла. Чтобы вовремя освободить память, в ветке обработки else запускается таймер для выполнения операции очистки. На данный момент мы уже представили первое решение, а второе решение, которое будет представлено далее, в основном предназначено для совместимости с браузерами IE.

3.2 Вариант 2

В браузерах Internet Explorer 10 методы msSaveBlob и msSaveOrOpenBlob позволяют пользователям сохранять файлы на клиенте, где метод msSaveBlob предоставляет только кнопку сохранения, аmsSaveOrOpenBlobМетод предоставляет кнопки сохранения и открытия, и соответствующее использование выглядит следующим образом:

window.navigator.msSaveBlob(blobObject, 'msSaveBlob_hello.txt');
window.navigator.msSaveOrOpenBlob(blobObject, 'msSaveBlobOrOpenBlob_hello.txt');

После понимания вышеуказанных знаний и решений, представленных на схеме 1corsEnabled,downloadиclickПосле метода посмотрите код второй схемы, он очень понятен. в удовлетворении"msSaveOrOpenBlob" in navigatorКогда условия соблюдены, FileSaver.js будет использовать вторую схему для сохранения файлов. Как и прежде, давайте проанализируемПараметр строкового типалогика обработки.

3.2.1 Параметры строкового типа
// Use msSaveOrOpenBlob as a second approach
function saveAs(blob, name, opts) {
  name = name || blob.name || "download";
  if (typeof blob === "string") {
    if (corsEnabled(blob)) { // 判断是否支持CORS
      download(blob, name, opts);
    } else {
      var a = document.createElement("a");
      a.href = blob;
      a.target = "_blank";
      setTimeout(function () {
        click(a);
      });
    }
  } else {
    // 省略处理Blob类型参数
  }
}
3.2.2 параметры типа BLOB-объекта
// Use msSaveOrOpenBlob as a second approach
function saveAs(blob, name, opts) {
  name = name || blob.name || "download";
  if (typeof blob === "string") {
    // 省略处理字符串类型参数
  } else {
    navigator.msSaveOrOpenBlob(bom(blob, opts), name); // 提供了保存和打开按钮
  }
}

3.3 Вариант 3

Если ни вариант 1, ни вариант 2 не поддерживаются, FileSaver.js будет понижен до использованияFileReaderAPI иopenAPI для открытия нового окна для сохранения файлов.

3.3.1 Параметры строкового типа
// Fallback to using FileReader and a popup
function saveAs(blob, name, opts, popup) {
  // Open a popup immediately do go around popup blocker
  // Mostly only available on user interaction and the fileReader is async so...
  popup = popup || open("", "_blank");
  if (popup) {
    popup.document.title = popup.document.body.innerText = "downloading...";
  }

  if (typeof blob === "string") return download(blob, name, opts);
	// 处理Blob类型参数
}
3.3.2 параметры типа BLOB-объекта

Для параметров типа blob в методе saveAs будут выбраны разные схемы в соответствии с разными средами.Например, в среде браузера Safari он будет использоватьFileReaderAPI сначала преобразует объект Blob в URL-адрес данных, а затем назначает URL-адрес данных вновь открытому окну или текущему окну.locationобъект, конкретный код выглядит следующим образом:

// Fallback to using FileReader and a popup
function saveAs(blob, name, opts, popup) {
  // Open a popup immediately do go around popup blocker
  // Mostly only available on user interaction and the fileReader is async so...
  popup = popup || open("", "_blank");
  if (popup) { // 设置新开窗口的标题
    popup.document.title = popup.document.body.innerText = "downloading...";
  }

  if (typeof blob === "string") return download(blob, name, opts);

  var force = blob.type === "application/octet-stream"; // 二进制流数据
  var isSafari = /constructor/i.test(_global.HTMLElement) || _global.safari;
  var isChromeIOS = /CriOS\/[\d]+/.test(navigator.userAgent);

  if (
    (isChromeIOS || (force && isSafari) || isMacOSWebView) &&
    typeof FileReader !== "undefined"
  ) {
    // Safari doesn't allow downloading of blob URLs
    var reader = new FileReader();
    reader.onloadend = function () {
      var url = reader.result;
      url = isChromeIOS
        ? url
        : url.replace(/^data:[^;]*;/, "data:attachment/file;"); // 处理成附件的形式
      if (popup) popup.location.href = url;
      else location = url;
      popup = null; // reverse-tabnabbing #460
    };
    reader.readAsDataURL(blob);
  } else {
    // 省略Object URL的处理逻辑
  }
}

На самом деле дляFileReaderAPI, помимо поддержки преобразования объектов File/Blob в URL-адреса данных, он также обеспечиваетreadAsArrayBuffer()иreadAsText()Методы преобразования объектов File/Blob в другие форматы данных. существуетИграйте с интерфейсным двоичным кодомВ статье брат Абао подробно представилFileReaderПрименение API во внешних сценариях обработки изображений, прочитав эту статью, вы сможете легко понять следующие диаграммы преобразования:

Наконец, давайте посмотрим на код ветки else:

function saveAs(blob, name, opts, popup) {
  popup = popup || open("", "_blank");
  if (popup) {
    popup.document.title = popup.document.body.innerText = "downloading...";
  }

  // 处理字符串类型参数
  if (typeof blob === "string") return download(blob, name, opts);

  if (
    (isChromeIOS || (force && isSafari) || isMacOSWebView) &&
    typeof FileReader !== "undefined"
  ) {
    // 省略FileReader API处理逻辑
  } else {
    var URL = _global.URL || _global.webkitURL;
    var url = URL.createObjectURL(blob);
    if (popup) popup.location = url;
    else location.href = url;
    popup = null; // reverse-tabnabbing #460
    setTimeout(function () {
      URL.revokeObjectURL(url);
    }, 4e4); // 40s
  }
}

На данный момент исходный код библиотеки FileSaver.js был проанализирован.Прочитав приведенный выше исходный код с Brother Abao, вы думаете, что написать совместимую и простую в использовании стороннюю библиотеку нелегко? В реальном проекте, если вам нужно сохранить очень большие файлы, которые превышают ограничение размера большого двоичного объекта, или если недостаточно места в памяти, вы можете рассмотреть возможность использования более продвинутогоStreamSaver.jsбиблиотека для реализации функции сохранения файлов.

Следуйте «Дорога к бессмертному совершенствованию с полным стеком», чтобы прочитать 3 бесплатные электронные книги (всего более 20 000 загрузок) и 8 серий руководств по анализу исходного кода, изначально написанных братом А. Бао.

4. Справочные ресурсы