Удалить атрибут: jQuery .removeAttr() и нативный JS

Содержание

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

Как удалить

// jQuery
$('#img').removeAttr('title');
$('#img').removeAttr('title alt');       // несколько через пробел

// нативно
img.removeAttribute('title');
['title', 'alt'].forEach((a) => img.removeAttribute(a));

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

Пустое значение — это не удаление

Разница, которую стоит понимать:

img.setAttribute('alt', '');   // атрибут ЕСТЬ и пустой
img.removeAttribute('alt');    // атрибута НЕТ

img.hasAttribute('alt');       // false после remove, true после setAttribute('')
img.getAttribute('alt');       // null после remove, "" после setAttribute('')

Для большинства атрибутов разница чисто техническая. Но не для alt, и это тот случай, где ошибка стоит дорого:

Разметка Что понимает скринридер
<img alt="Схема потоков"> описание картинки
<img alt=""> картинка декоративная, пропустить
<img> alt забыли — прочитает имя файла

alt="" — осознанное решение: «здесь нечего озвучивать». Отсутствие alt — недоработка, и скринридер начнёт читать crud-annotated-mongodb-insertOne.bakedsvg.svg. Поэтому у декоративных картинок атрибут ставят пустым, а не удаляют.

Пустой атрибут и отсутствующий — не одно и то же

setAttribute('alt', '')

Атрибут ЕСТЬ и пустой

  • hasAttribute('alt')true
  • getAttribute('alt')""
  • Скринридер: «картинка декоративная, пропустить»
  • Осознанное решение

removeAttribute('alt')

Атрибута НЕТ

  • hasAttribute('alt')false
  • getAttribute('alt')null
  • Скринридер прочитает имя файла
  • Читается как недоработка

Ловушка: disabled и checked

Та же история, что при чтении и установке атрибутов:

// поле заблокировали из кода
$('#submit').prop('disabled', true);

// ❌ не поможет — атрибута в разметке и не было
$('#submit').removeAttr('disabled');

// ✅ снимаем свойство
$('#submit').prop('disabled', false);
document.querySelector('#submit').disabled = false;

Правило: если состояние ставили свойством — свойством и снимайте. Смешивать .prop() и .removeAttr() — гарантированный способ получить кнопку, которая «не разблокируется».

Оговорка: если атрибут disabled был в исходной разметке, то removeAttr сработает. Именно эта половинчатость и сбивает: иногда работает, иногда нет — в зависимости от того, откуда взялось состояние.

data-атрибуты

// нативно
delete post.dataset.userId;              // убирает data-user-id
post.removeAttribute('data-user-id');    // то же самое

// jQuery
$('#post').removeAttr('data-user-id');   // атрибут убран...
$('#post').data('userId');               // ...но тут ещё лежит старое значение из кэша!

Здесь важный нюанс: .data() в jQuery кэширует. Удалили атрибут из разметки — кэш об этом не узнает. Чтобы очистить и его, нужен .removeData('userId').

Отсюда правило, общее для чтения и записи data-атрибутов: работайте одним инструментом. Либо только dataset, либо только .data() + .removeData().

Удалить все атрибуты

Задача редкая, но встречается при санитизации чужого HTML:

[...el.attributes].forEach((attr) => el.removeAttribute(attr.name));

Копия массива через spread здесь обязательна: el.attributesживая коллекция, и удаление во время перебора собьёт индексы. Классическая ошибка — цикл for по живому списку, который пропускает каждый второй элемент.

Булевы атрибуты: удалять, а не ставить false

Отдельная категория, где удаление — единственный правильный способ выключить.

Булев атрибут работает по факту присутствия, а не по значению. disabled, required, readonly, checked, hidden — все они включены, если атрибут есть, независимо от того, что в нём написано:

<input disabled>              <!-- выключен -->
<input disabled="disabled">   <!-- выключен -->
<input disabled="false">      <!-- ВСЁ РАВНО выключен! -->
<input disabled="">           <!-- выключен -->

Строка "false" — это непустое значение, а значит атрибут присутствует, а значит поле заблокировано. Логика «поставлю false, чтобы включить» здесь не работает вовсе.

Выключается такой атрибут только удалением:

// ❌ не включит поле — атрибут по-прежнему на месте
el.setAttribute('disabled', 'false');

// ✅ убрать атрибут целиком
el.removeAttribute('disabled');
// или через свойство:
el.disabled = false;

Отсюда практическое следствие для генерации HTML: булев атрибут либо выводят целиком, либо не выводят вовсе. disabled={условие} в шаблоне должно давать либо disabled, либо пустоту — именно так это и делают React и другие фреймворки, а не disabled="false".

ARIA-атрибуты — исключение из правила булевых

Ловушка внутри ловушки, на которой спотыкаются при работе с доступностью.

aria-* атрибуты выглядят как булевы, но таковыми не являются. Их значение — настоящая строка "true" или "false", и удаление меняет смысл:

el.setAttribute('aria-expanded', 'false');   // свёрнуто — валидно
el.setAttribute('aria-expanded', 'true');    // развёрнуто
el.removeAttribute('aria-expanded');          // ← НЕ «свёрнуто», а «свойства нет вообще»

Для скринридера aria-expanded="false" и отсутствие атрибута — разные состояния: первое сообщает «этот элемент можно развернуть, сейчас свёрнут», второе — «элемент не разворачивается». Удалив атрибут «чтобы выключить», вы не выключаете, а ломаете семантику.

Правило: обычные булевы атрибуты выключают удалением, aria-* — установкой "false" строкой. Это прямо противоположные подходы к похоже выглядящим атрибутам, и именно поэтому их путают.

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

Первая версия вышла 10 августа 2017 года и описывала .removeAttr() — синтаксис, удаление нескольких атрибутов через пробел. Всё описано верно и работает по сей день.

Не хватало ровно того же, что и в соседних статьях цикла: разделения атрибут/свойство. Совет «уберите атрибут disabled, чтобы разблокировать кнопку» работает только в половине случаев — и понять, в какой именно, по той статье было невозможно.

Показательно, что три статьи про атрибуты 2017 года — чтение, запись и удаление — все обходили это различие стороной. Оно и есть настоящая тема цикла, а .attr() против .removeAttr() — просто синтаксис.


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

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

Почему `removeAttr('disabled')` не всегда разблокирует поле?
Потому что disabled — свойство, а не только атрибут. Если состояние меняли из кода через .prop(), удаление атрибута ни на что не повлияет. Ставьте .prop('disabled', false).
Чем удаление атрибута отличается от установки пустого значения?
Принципиально. removeAttribute('alt') — атрибута нет, hasAttribute вернёт false. setAttribute('alt', '') — атрибут есть и пустой. Для alt это разные вещи: пустой означает «картинка декоративная», отсутствующий — «забыли подписать».
Можно ли удалить несколько атрибутов сразу?
В jQuery — да, через пробел: .removeAttr('title alt'). Нативно нужен цикл по массиву имён.
Как удалить все data-атрибуты?
Пройти по el.dataset и удалить каждый ключ через delete el.dataset.key, либо собрать имена из el.attributes и вызвать removeAttribute для каждого.