Кратко
СкопированоForm
— это специальная коллекция данных, которая позволяет передавать данные в виде пар [ключ
на сервер при помощи fetch
или XML
. При этом используется точно такой же формат данных, какой использует тег <form>
с типом кодирования 'multipart
. Поэтому, значения в Form
, как и у обычной HTML формы, могут быть только строками или файлами.
Пример
СкопированоПредположим, что мы пишем функцию, которая отправляет на сервер два поля name
и email
. Значения полей она получает через аргументы:
async function sendData(name, email) { const data = new FormData() data.append('name', name) data.append('email', email) return await fetch('/api/subscribe/', { method: 'POST', body: data, })}
async function sendData(name, email) { const data = new FormData() data.append('name', name) data.append('email', email) return await fetch('/api/subscribe/', { method: 'POST', body: data, }) }
Данные отправляются на сервер с помощью объекта Form
. Мы используем метод, чтобы добавить значения, а затем передаём полученный объект функции fetch
.
Как пишется
СкопированоДля работы с Form
сначала с помощью конструктора new
создаётся объект этого типа: const form
. Затем у полученного объекта можно вызывать методы.
Основные методы для работы с Form
:
append
— добавляет значение для ключа с сохранением предыдущих значений;( ключ , значение ) set
— устанавливает значение для ключа, перезаписывая предыдущие значения;( ключ , значение ) get
— возвращает первое значение ключа;( ключ ) get
— возвращает все значения ключа;All ( ключ ) has
— проверяет наличие переданного ключа;( ключ ) entries
— возвращает итератор пар( ) [ключ
;, значение ] values
— возвращает итератор всех значений коллекции;( ) keys
— возвращает итератор всех ключей коллекции;( ) delete
— удаляет конкретное значение;( ключ )
Как понять
СкопированоПри отправке данных, и сервер, и клиент должны понимать друг друга, то есть использовать понятные обоим способы кодирования и декодирования данных. Таких способов существует большое количество, и Form
позволяет работать с одним из них — 'multipart
.
Form
похожа на коллекцию Map
— предоставляет удобные методы для добавления и удаления данных. Но, если передать её в качестве тела запроса при вызове fetch
(как в примере выше), данные «под капотом» будут преобразованы в нужный формат, а HTTP-заголовку Content
будет присвоено значение 'multipart
, чтобы сервер знал, что именно с этим форматом ему предстоит работать.
Form
является отражением данных обычной HTML-формы с атрибутом enctype
, поэтому пример выше можно представить следующим образом без JavaScript:
<form method="post" action="/api/subscribe/" enctype="multipart/form-data"> <input type="text" name="name" value=""> <input type="email" name="email" value=""> <button type="submit">Отправить</button></form>
<form method="post" action="/api/subscribe/" enctype="multipart/form-data" > <input type="text" name="name" value=""> <input type="email" name="email" value=""> <button type="submit">Отправить</button> </form>
Когда выбирать
СкопированоСуществует несколько популярных способов кодирования данных для отправки на сервер: 'application
, 'multipart
и 'application
. Иногда бывает так, что сервер поддерживает только какой-то определённый способ. Тогда выбирать не приходится. Но, чаще всего, современные решения на бэкенде поддерживают несколько способов, поэтому выбирать нужно в зависимости от задачи.
'application
— способ, который используют HTML-формы по умолчанию. Из-за особенностей преобразования, этот способ плохо подходит для больших объёмов данных. В особенности файлов или строк с большим количеством символов не из ASCII-таблицы (например, символы алфавита русского языка).
'application
— достаточно популярный формат из-за широкого распространения JSON как формата обмена данными. Из плюсов — поддерживает вложенные структуры, поэтому можно в одном запросе отправить, например, целый объект с данными. Однако, чтобы отправить файл при помощи этого формата, его необходимо дополнительно закодировать в строку каким-нибудь алгоритмом, например, Base64. Причём на сервере нужно декодировать эти данные обратно.
'multipart
— удобный способ для загрузки файлов, оптимален с точки зрения размера закодированных данных, но в качестве значений может хранить только строки или файлы.
Поэтому лучше всего использовать Form
для отправки файлов на сервер, или когда поддержка только строковых данных не является проблемой. Дополнительно, при создании Form
можно передать DOM-элемент формы (будет рассмотрено ниже), и коллекция вытащит из этой формы все данные. Поэтому, если стоит задача отправить данные какой-либо формы, Form
позволит сделать это с минимумом кода.
Создание FormData
СкопированоСоздать новый пустой объект Form
можно с помощью конструктора:
const data = new FormData()
const data = new FormData()
Также конструктор может принимать в качестве аргумента DOM-элемент формы, в этом случае Form
запишет текущие значения полей формы:
<form id="user-form"> <input type="text" name="name" value="Аня"> <input type="text" name="language" value="JavaScript"></form>
<form id="user-form"> <input type="text" name="name" value="Аня"> <input type="text" name="language" value="JavaScript"> </form>
const form = document.querySelector('#user-form')const data = new FormData(form)for (let [key, value] of data) { console.log(`${key} — ${value}`)}// 'name — Аня'// 'language — JavaScript'
const form = document.querySelector('#user-form') const data = new FormData(form) for (let [key, value] of data) { console.log(`${key} — ${value}`) } // 'name — Аня' // 'language — JavaScript'
Работа с коллекцией
СкопированоДля добавления данных в коллекцию используют метод append
:
const data = new FormData()data.append('name', 'Вася')
const data = new FormData() data.append('name', 'Вася')
Теперь в коллекции появилось одно значение с ключом name
и значением 'Вася'.
После выполнения этого кода, в коллекции будет два значения 'Вася' и 'Лена' для одного ключа name
:
const data = new FormData()data.append('name', 'Вася')data.append('name', 'Лена')
const data = new FormData() data.append('name', 'Вася') data.append('name', 'Лена')
Form
поддерживает ещё один метод для записи данных: set
. В отличие от append
, он перезапишет старые данные для переданного ключа, если они были:
const data = new FormData()data.set('name', 'Вася')data.set('name', 'Лена')
const data = new FormData() data.set('name', 'Вася') data.set('name', 'Лена')
В коллекции у ключа name
будет одно значение 'Лена', потому что прошлое значение было перезаписано.
Вот пример такого поведения. Записываем число 30
, но фактически записывается строка '30'
:
const data = new FormData()data.append('age', 30)console.log(data.get('age') === 30)// falseconsole.log(data.get('age') === '30')// trueconsole.log(typeof data.get('age'))// 'string'
const data = new FormData() data.append('age', 30) console.log(data.get('age') === 30) // false console.log(data.get('age') === '30') // true console.log(typeof data.get('age')) // 'string'
Для получения записанных значений есть два метода: get
и get
. get
вернёт первое значение для ключа или null
, если для указанного ключа значений не было:
const data = new FormData()console.log(data.get('name'))// nulldata.append('name', 'Вася')data.append('name', 'Лена')console.log(data.get('name'))// 'Вася'
const data = new FormData() console.log(data.get('name')) // null data.append('name', 'Вася') data.append('name', 'Лена') console.log(data.get('name')) // 'Вася'
В примере выше второе значение 'Лена' при помощи метода get
недоступно, потому что он всегда возвращает только первое значение. Поэтому, чтобы получить все значения, на помощь приходит get
. Он всегда возвращает массив значений для указанного ключа. Если значений не было, возвращаемый массив будет пустым:
const data = new FormData()console.log(data.getAll('name'))// []data.append('name', 'Вася')data.append('name', 'Лена')console.log(data.getAll('name'))// ['Вася', 'Лена']
const data = new FormData() console.log(data.getAll('name')) // [] data.append('name', 'Вася') data.append('name', 'Лена') console.log(data.getAll('name')) // ['Вася', 'Лена']
Чтобы проверить, есть ли в коллекции данные для определённого ключа, существует метод has
. Он вернёт true или false:
const data = new FormData()console.log(data.has('name'))// falsedata.append('name', 'Вася')console.log(data.has('name'))// true
const data = new FormData() console.log(data.has('name')) // false data.append('name', 'Вася') console.log(data.has('name')) // true
Для удаления значений для определённого ключа можно воспользоваться методом delete
. Важно помнить, что, если у указанного ключа несколько значений, то удалятся все значения:
const data = new FormData()data.append('name', 'Вася')data.append('name', 'Лена')data.delete('name')
const data = new FormData() data.append('name', 'Вася') data.append('name', 'Лена') data.delete('name')
После выполнения кода коллекция снова будет пустой. Мы удалили ключ целиком, поэтому оба значения по этому ключу исчезли.
Обход значений
СкопированоForm
предоставляет встроенный итератор для обхода значений:
const data = new FormData()data.append('name', 'Вася')data.append('name', 'Лена')data.append('language', 'JavaScript')for (let [key, value] of data) { console.log(`${key} — ${value}`)}// name — Вася// name — Лена// language — JavaScript
const data = new FormData() data.append('name', 'Вася') data.append('name', 'Лена') data.append('language', 'JavaScript') for (let [key, value] of data) { console.log(`${key} — ${value}`) } // name — Вася // name — Лена // language — JavaScript
Тот же итератор доступен при помощи метода entries
. Обратите внимание, что каждый элемент итератора — массив из двух элементов. Первый элемент — ключ, а второй — значение.
В дополнение к этому, Form
предоставляет два других итератора: только ключей при помощи метода keys
и только значений при помощи values
. Каждый ключ при перечислении ключей появляется ровно столько раз, сколько значений он содержит:
const data = new FormData()data.append('name', 'Вася')data.append('name', 'Лена')data.append('language', 'JavaScript')console.log('Проходимся по значениям:')for (let value of data.values()) { console.log(value)}// 'Вася'// 'Лена'// 'JavaScript'console.log('Проходимся по ключам:')for (let key of data.keys()) { console.log(key)}// 'name'// 'name' (ключ появился второй раз,// потому что содержит два значения)// 'language'
const data = new FormData() data.append('name', 'Вася') data.append('name', 'Лена') data.append('language', 'JavaScript') console.log('Проходимся по значениям:') for (let value of data.values()) { console.log(value) } // 'Вася' // 'Лена' // 'JavaScript' console.log('Проходимся по ключам:') for (let key of data.keys()) { console.log(key) } // 'name' // 'name' (ключ появился второй раз, // потому что содержит два значения) // 'language'
На практике
Скопированосоветует Скопировано
🛠 Сильной стороной Form
является загрузка файлов на сервер. Если при использовании 'application
файлы необходимо дополнительно кодировать каким-то способом, чтобы привести к строке (и точно так же декодировать на сервере), то Form
умеет это делать «из коробки».
Например, если мы хотим после выбора файла сразу же загрузить его на сервер, то нам понадобится следующий HTML:
<input id="file-input" type="file">
<input id="file-input" type="file">
Для отправки на сервер просто добавим файл в объект Form
и отправим его:
// Объявляем функцию загрузки файлаfunction sendFile(file) { const data = new FormData() // Добавляем файл data.append('document', file) return fetch('/api/upload/', { method: 'POST', body: data, })}const fileInput = document.querySelector('#file-input')fileInput.addEventListener('change', (event) => { // Получаем файл. Обратите внимание, что файлов может быть несколько, // если у поля стоит атрибут `multiple` const file = event.target.files[0] // Отправляем файл на сервер при помощи созданной функции sendFile(file) // Очищаем текущее значение поля. Если этого не делать, // то, при ошибке загрузки, повторный выбор того же файла // не вызовет событие change event.target.value = null})
// Объявляем функцию загрузки файла function sendFile(file) { const data = new FormData() // Добавляем файл data.append('document', file) return fetch('/api/upload/', { method: 'POST', body: data, }) } const fileInput = document.querySelector('#file-input') fileInput.addEventListener('change', (event) => { // Получаем файл. Обратите внимание, что файлов может быть несколько, // если у поля стоит атрибут `multiple` const file = event.target.files[0] // Отправляем файл на сервер при помощи созданной функции sendFile(file) // Очищаем текущее значение поля. Если этого не делать, // то, при ошибке загрузки, повторный выбор того же файла // не вызовет событие change event.target.value = null })