URL-адреса в JavaScript

URL-адреса являются важной частью любого веб-приложения. Если ваше приложение отправляет запросы к API, важно создать правильные URL-адреса для этих запросов. URL API, поддерживаемый всеми современными браузерами, предоставляет способ анализа URL-адресов и манипулирования ими. Это обеспечивает легкий доступ к различным частям URL-адреса.

Понимание частей URL-адреса

Рассмотрим следующий URL-адрес:

https://example.com/api/search?query=foo&sort=asc#results

Этот URL-адрес состоит из следующих компонентов:

  • Protocol: https
  • Host: example.com
  • Path name: /api/search
  • Query string: ?query=foo&sort=asc
  • Hash: #results

С помощью современного JavaScript мы можем анализировать URL-адреса и извлекать эти различные части по мере необходимости.

Синтаксический анализ URL-адресов

В старых браузерах, до появления URL API, одним из способов анализа URL-адресов разработчиками было использование элемента <a>. Этот элемент обеспечивает базовый анализ URL-адресов. Например, вот способ извлечения строки запроса из URL-адреса:

function getQueryString(url) {
  const link = document.createElement('a');
  link.href = url;
  return url.search;
}

Однако такой подход имеет некоторые недостатки:

  • Для него требуется среда DOM, а это значит, что он не будет работать в такой среде, как Node.js.
  • Он также не имеет обработки ошибок — если атрибуту передается недействительный URL href, ошибка не выдается.

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

Использовать URL API для синтаксического анализа URL-адресов очень просто. Просто передайте URL-адрес, который вы хотите проанализировать, в конструктор URL-адресов. Если строка URL-адреса верна, вы получите обратно объект URL со свойствами для различных частей URL-адреса:

const url = new URL('https://example.com/api/search?query=foobar');
console.log(url.host); // example.com
console.log(url.pathname); // /api/search
console.log(url.search); // ?query=foobar

Разбор строки запроса

Вы можете получить доступ к строке запроса URL-адреса двумя способами:

  • Свойство search, представляющее собой строку, содержащую полную строку запроса (включая ?символ)
  • Имущество searchParams, являющееся URLSearchParamsобъектом

Если вас интересует значение определенного параметра в строке запроса, вы можете использовать его метод get, чтобы получить параметр по его имени:

const url = new URL('https://example.com/api/search?query=foobar&maxResults=10');
console.log(url.searchParams.get('query'); // foobar
console.log(url.searchParams.get('maxResults'); // 10

Если существует несколько параметров с одинаковым именем, вы можете использовать GetAll, чтобы получить массив, содержащий все значения для этого имени:

const url = new URL('https://example.com/api/search?tag=tag1&tag=tag2&tag=tag3');
console.log(url.searchParams.getAll('tag')); // ['tag1', 'tag2', 'tag3']

Построение строк запроса

Построение строки запроса вручную может быть сложной задачей, особенно если какие-либо параметры запроса содержат специальные символы, которые необходимо экранировать. Например, если параметр запроса должен содержать символ &, вам нужно будет закодировать его как %26. Чтобы охватить эти ситуации, вам необходимо использовать функцию encodeURIComponent:

let queryString = 'foo=bar';
queryString += '&baz=qux';
queryString += '&tag=' + encodeURIComponent('one&two');
console.log(queryString); // foo=bar&baz=qux&tag=one%26two

Вы можете более безопасно создать строку запроса, используя объект URLSearchParams:

const params = new URLSearchParams();
params.append('foo', 'bar');
params.append('baz', 'qux');
params.append('tag', 'one&two');
console.log(params.toString()); // foo=bar&baz=qux&tag=one%26two

Преимущества использования URLSearchParams включают в себя:

  • Вам не нужно беспокоиться о &символах, разделяющих параметры.
  • Вам не нужно кодировать URI-значения параметров.
  • Вам не нужно использовать конкатенацию строк

Перебор параметров запроса

Без объекта URLSearchParams перебирать параметры в строке запроса немного сложно. Вам нужно было бы разделить строки несколько раз - сначала на группы пар ключ/значение, а затем снова разделить ключ и значение:

function listQueryParams(queryString) {
  queryString.split('&').forEach(param => {
    const [key, value] = param.split('=');
    console.log(`${key}: ${value}`);
  });
}

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

function listQueryParams(queryString) {
  queryString.split('&').forEach(param => {
    const [key, value] = param.split('=');
    console.log(`${key}: ${decodeURIComponent(value)}`);
  });
}

Вместо этого вы можете использовать метод URLSearchParams entries для перебора пар ключ/значение:

function listQueryParams(queryString) {
  const params = new URLSearchParams(queryString);
  params.entries().forEach(([key, value]) => console.log(`${key}: ${value}`));
}

Создание полного URL-адреса

Вот полный пример создания URL-адреса с базовым URL-адресом и некоторыми параметрами запроса:

const url = new URL('https://example.com/api/search');
url.searchParams.append('query', 'test');
url.searchParams.append('tag', 'tag1');
url.searchParams.append('tag', 'tag2');

// https://example.com/api/search?query=test&tag=tag1&tag=tag2
console.log(url.toString());

Проверка правильных URL-адресов

Вы можете попробовать использовать регулярное выражение для проверки URL-адреса, но, как известно, очень сложно создать регулярное выражение, которое полностью отражает действительную строку URL-адреса.

Вместо этого вы можете обратиться к API URL. Конструктор URL выдаст ошибку, если вы укажете недопустимый URL. Вы можете использовать это, чтобы проверить, является ли URL допустимым:

function isValidURL(url) {
  try {
    new URL(url);
    return true;
  } catch (error) {
    return false;
  }
}

С новыми браузерами это стало еще проще. Существует новый статический метод URL.canParse, который выполняет аналогичную проверку с помощью одной строки кода. Как и функция isValidURL, описанная выше, она принимает потенциальную строку URL и возвращает значение true или false в зависимости от допустимости строки URL.

Создание относительных URL-адресов

URL API обладает мощным механизмом разрешения относительных URL-адресов. Обычно аргумент конструктора URL выдает ошибку, если он не является полным и допустимым URL-адресом. Однако вы можете указать второй аргумент, который служит основой для построения относительного URL-адреса. Если вы используете подход с двумя аргументами, то первый аргумент не обязательно должен быть допустимым URL-адресом, но второй должен быть допустимым.

Давайте сначала рассмотрим простой случай:

new URL('/about', 'https://example.com').href;

Конструктор URL принимает базовый URL из https://example.com и добавляет относительный путь /about, в результате чего получается https://example.com/about.

А как насчет этого:

new URL('profile', 'https://example.com/users').href;

Вы могли бы ожидать, что это будет https://example.com/users/profile, но на самом деле получается https://example.com/profile. Это ведет себя точно так же, как относительная ссылка; она берет родительский сегмент пути, который является корнем example.com, а затем добавляет profile.

Давайте рассмотрим еще один пример использования относительного URL-адреса. Вы также можете использовать .. для возврата вверх по иерархии путей:

new URL('../profile', 'https://example.com/users/123').href;

В этом случае получается https://example.com/profile. Помните, что относительные URL-адреса начинаются с родительского сегмента пути. Тогда в этом есть .., который ведет вверх еще на один сегмент пути.

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

new URL('../profile', '/about'); // error!
new URL('../profile'); // error

Работа с окном.расположение объекта

Возможно, вы знакомы с window.объект location, который представляет URL текущей страницы. У этого объекта также есть такие свойства, как href и pathname, поэтому вы можете подумать, что это объект URL. Это другой объект, Location, у которого есть некоторые общие свойства с URL, но также отсутствуют некоторые (например, свойство searchParams).

Даже если это не объект URL, вы все равно можете использовать window.location для создания новых объектов URL. Вы можете передать window.перейдите к конструктору URL-адресов, чтобы создать новый полноценный URL-адрес с параметрами поиска и всем остальным на основе текущего URL-адреса, или вы даже можете использовать его в качестве базового URL-адреса при создании относительных URL-адресов:

new URL('/profile', window.location).href;

Сопоставление шаблонов в URL-адресе с помощью URLPattern

Использование URL-адреса упрощает получение пути по URL-адресу. Например, в URL-адресе https://example.com/api/users/123/profile имя пути /api/users/123/profile. Что, если мы захотим получить только идентификатор пользователя, 123, по этому URL-адресу?

Как мы уже обсуждали ранее, может быть сложно создать правильные регулярные выражения для проверки и извлечения частей URL-адреса.

Пока что он доступен не во всех браузерах, но вы можете использовать URLPattern API для сопоставления и извлечения частей URL-адреса в соответствии с указанными вами шаблонами. Это может быть особенно полезно для таких вещей, как маршрутизация на стороне клиента в одностраничном приложении (SPA).

Используя URL-адрес профиля пользователя в качестве примера, давайте создадим шаблон URL-адреса, чтобы получить идентификатор пользователя. Мы можем использовать начальный символ : для обозначения именованного заполнителя, который может быть использован позже для сопоставления с этой частью URL-адреса:

const pattern = new URLPattern('https://example.com/api/users/:userId/profile');
const matcher = pattern.exec('https://example.com/api/users/123/profile');
console.log(matcher.pathname.groups.userId); // 123

Когда вы вызываете exec по URLPattern, ему нужен действительный URL-адрес. Он возвращает объект сопоставления, содержащий свойства для каждой части URL-адреса (протокол, хост, путь и т.д.). Каждое из этих свойств также имеет свойство groups, которое сопоставляет имена-заполнители, такие как :userId, с их значениями в URL-адресе.

Если вас интересует соответствие только одной части URL-адреса, например имени пути, как мы это сделали здесь, вы также можете указать подстановочные знаки в шаблоне URL-адреса. Или же вместо строки URL-адреса вы можете передать объект, содержащий те части URL-адреса, которые вас интересуют в сопоставлении:

new URLPattern('https://*/api/users/:userId/profile');
new URLPattern({ pathname: '/api/users/:userId/profile' });

API URLPattern по-прежнему доступен не во всех браузерах. На момент написания статьи он еще не поддерживался в Firefox или Safari. Вы можете ознакомиться с последней информацией о поддержке браузера по адресу CanIUse.com.

Резюме

URL API - это универсальный интерфейс для создания, проверки и манипулирования URL-адресами в JavaScript. Он более безопасен и менее подвержен ошибкам в использовании, чем ручной синтаксический анализ или регулярные выражения. Используя объект URLSearchParams, вы можете создать строку запроса, не беспокоясь о конкатенации строк или кодировании специальных символов.