Установить или изменить атрибут: jQuery и нативный JS

Содержание

О проверке кода. Примеры не запускались в браузере — в отличие от материалов по Node.js, где приводится фактический вывод. Код дан по документации jQuery и MDN. Говорим прямо, а не ставим бейдж «проверено» без проверки.

Установка атрибута — та же задача, что и чтение, с той же главной ловушкой и одной дополнительной, уже про безопасность.

Как установить

// jQuery
$('#img').attr('src', '/media/schema.jpg');
$('#link').attr('href', '/about');

// нативно
document.querySelector('#img').setAttribute('src', '/media/schema.jpg');
document.querySelector('#img').src = '/media/schema.jpg';   // через свойство-ярлык

У популярных атрибутов есть свойства-ярлыки (src, href, alt, id), и через них короче. Разница есть, и она неочевидна:

img.setAttribute('src', '/a.jpg');
img.getAttribute('src');   // "/a.jpg"          — как записали
img.src;                   // "https://site.ru/a.jpg" — абсолютный URL!

Свойство src возвращает разрешённый абсолютный адрес, атрибут — исходную строку. Для сравнения путей это принципиально.

Несколько атрибутов разом

Единственное место, где jQuery объективно короче:

// jQuery: объектом
$('#img').attr({
  src: '/media/schema.jpg',
  alt: 'Схема работы',
  width: 800,
});

// нативно: цикл
Object.entries({ src: '/media/schema.jpg', alt: 'Схема работы', width: 800 })
  .forEach(([k, v]) => img.setAttribute(k, v));

Разница — три строки против одной. Если ради этого в проекте держат jQuery, размен сомнительный, но признаем: тут он удобнее.

Та же ловушка: формы меняются через свойство

// ❌ не сработает так, как ждёте
$('#agree').attr('checked', true);
$('#name').attr('value', 'Аня');

// ✅ правильно
$('#agree').prop('checked', true);
$('#name').prop('value', 'Аня');
document.querySelector('#agree').checked = true;

Причина та же, что при чтении: атрибут — это разметка, свойство — живое состояние. Установка атрибута checked меняет значение по умолчанию, а не текущее. После того как пользователь тронул поле, атрибут на него уже не влияет вовсе.

Куда писать значение: три развилки
  1. 1 Это поле формы?checked, value, disabled, selected → только свойство: .prop() или el.value
  2. 2 Это класс или стиль?classList.add() и el.style.x, а не setAttribute — иначе затрёте всё разом
  3. 3 Значение от пользователя?href, src, action → белый список протоколов. javascript: выполнит код
  4. 4 Ничего из этогоsetAttribute(name, value) — обычный путь

setAttribute затирает всё значение

Неочевидная деталь, на которой теряют классы:

// ❌ снесёт ВСЕ классы, оставит один
el.setAttribute('class', 'active');
$el.attr('class', 'active');

// ✅ добавит, не трогая остальные
el.classList.add('active');
$el.addClass('active');

Атрибут — одна строка целиком, и вы её заменяете. Для классов почти всегда нужен classList, для стилей — style, а не attr('style', ...), который так же снесёт все инлайновые правила.

Безопасность: не всякий атрибут безобиден

Раздела, которого не было в оригинале, а он тут важнее синтаксиса.

// ❌ дыра, если url пришёл от пользователя
$link.attr('href', userUrl);

Если userUrl равен javascript:alert(document.cookie), клик по ссылке выполнит код. То же с src у iframe и data:-URL.

// ✅ белый список протоколов
function safeUrl(raw) {
  try {
    const url = new URL(raw, location.origin);
    return ['http:', 'https:', 'mailto:'].includes(url.protocol) ? url.href : '#';
  } catch {
    return '#';
  }
}
link.setAttribute('href', safeUrl(userUrl));

Логика та же, что у параметризованных запросов в SQL и execFile вместо exec: не фильтруйте плохое, а разрешайте только известное хорошее. Чёрные списки обходят, белые — нет.

Опасные атрибуты, куда нельзя подставлять пользовательские данные без проверки: href, src, action, formaction, любые on* (onclick и подобные), style.

Атрибуты доступности: не забывать при динамике

Про этот класс атрибутов вспоминают в последнюю очередь, а устанавливать их нужно вместе с каждым изменением состояния. Меняете вид элемента из кода — обновляйте и то, что он сообщает скринридеру:

// раскрываем меню — и говорим об этом ассистивным технологиям
function toggleMenu(button, menu) {
  const open = menu.hidden;
  menu.hidden = !open;
  button.setAttribute('aria-expanded', String(open));   // "true" / "false"
}

Ключевая деталь: aria-* принимает строку "true"/"false", а не булево. setAttribute('aria-expanded', true) запишет строку "true" (атрибуты всегда строки), но полагаться на автопревращение не стоит — приводите явно через String(...).

Типичный набор, который забывают обновлять при динамике:

  • aria-expanded — раскрыт ли аккордеон, меню, выпадашка;
  • aria-hidden — скрыт ли элемент от скринридера (не то же, что hidden или display: none);
  • aria-pressed — нажата ли кнопка-переключатель;
  • aria-busy — идёт ли загрузка.

Правило: меняете состояние — меняйте и его ARIA-отражение в той же функции. Разъехались визуальное состояние и aria-* — интерфейс работает для зрячих и врёт для тех, кто пользуется скринридером.

Класс и стиль — тоже «установка», но других инструментов

Короткое напоминание, потому что через setAttribute их и пытаются ставить:

Задача Не через setAttribute А через
добавить класс attr('class', ...) затрёт всё classList.add
поменять стиль attr('style', ...) затрёт всё el.style.x
поле формы attr('checked', ...) не сработает .prop() / el.checked
data-* работает, но лучше el.dataset.x

setAttribute заменяет всё значение атрибута целиком — для class и style, которые устроены как списки, это почти всегда не то, что нужно.

Что было на этой странице в 2017 году

Первая версия вышла 10 августа 2017 года, разбирала .attr() с одним и двумя аргументами и передачу объекта. Синтаксис описан верно и не изменился ни на символ.

Чего в ней не было — двух вещей, которые сегодня и составляют половину ответа:

  • Разделения атрибут/свойство. Установка attr('checked', true) подавалась как рабочий способ отметить чекбокс. Он работает ровно до момента, когда пользователь первый раз кликнул по нему сам.
  • Безопасности. Про подстановку данных пользователя в href не думали вообще. Тогда фронтенд редко работал с чужими данными напрямую — сегодня это норма, и вопрос из теоретического стал ежедневным.

Смежные темы: получить значение атрибута; удалить атрибут; изменение стилей; уроки JavaScript.

Частые вопросы

Как установить сразу несколько атрибутов?
В jQuery — передать объект: $el.attr({ src: '/a.jpg', alt: 'Схема' }). Нативно готового способа нет, но Object.entries(attrs).forEach(([k, v]) => el.setAttribute(k, v)) решает задачу в одну строку.
Почему изменение `value` через `.attr()` не работает?
Потому что после ввода пользователем поле живёт своим свойством, а не атрибутом. Меняйте .prop('value', ...) или el.value — иначе визуально ничего не изменится.
Можно ли ставить атрибуты через функцию?
В jQuery да: $el.attr('title', (i, old) => old + '!') — колбэк получает индекс и старое значение и вызывается для каждого элемента. Нативного аналога нет, нужен цикл.
Опасно ли подставлять данные пользователя в атрибут?
Да, если это href, src или обработчик события. Значение вида javascript:... в href выполнит код. Проверяйте протокол по белому списку, а не фильтруйте строку.