Xpoint
   [напомнить пароль]

Атрибуты и свойства: модель DOM и отличия в Internet Explorer

В результате ряда дискуссий на Xpoint (Куда уходят атрибуты (MSIE vs setAttribute), IE6: При операциях с DOM не подхватываются стили) стало ясно, что реализация атрибутов в Internet Explorer радикально отличается от той, которую предписывает стандарт DOM и которая реализована во всех остальных браузерах. Internet Explorer не различает атрибуты и свойства, что делает атрибуты практически бесполезными. Более того, это способствует тому, что большинство веб-программистов не понимает разницы между атрибутами и свойствами.

Эта статья объясняет назначение атрибутов и свойств и представляет примеры их использования. Далее обсуждаются различия реализации в Internet Explorer, последствия этих различий и написание кроссбраузерных приложений. Возможно эта статья поможет кому-нибудь не наступить на грабли, о которые уже споткнулось множество программистов.

Атрибуты и свойства в модели DOM

Примеры этого раздела лучше всего выполнять в Gecko-браузерах, к примеру Firefox или SeaMonkey.

Для каждого тега в XML и HTML может быть указано произвольное количество атрибутов. Атрибут представляет из себя пару строк имя/значение и задается в коде документа:

<input id="textField" type="text" value="5" />

Здесь у тега input три атрибута: id, type и value со значениями textField, text и 5 соответственно. Заметим, что в HTML регистр букв в имени атрибута не имеет значения, в отличие от XML/XHTML.

DOM позволяет читать атрибуты из скриптов с помощью методов hasAttribute() и getAttribute():

var inputTag = document.getElementById("textField");
var defaultValue = "";
if (inputTag.hasAttribute("value"))               // если есть атрибут value
  defaultValue = inputTag.getAttribute("value");  // читаем атрибут value

Можно и менять атрибуты, для этого существуют методы setAttribute() и removeAttribute():

inputTag.removeAttribute("value");                // удаляем значение по умолчанию
inputTag.setAttribute("type", "submit");          // меняем тип поля на submit

В наших примерах мы работает с объектом inputTag, который соответствует тегу input документа. У этого объекта есть ряд свойств, некоторые из которых названы так же, как и атрибуты тега:

if (inputTag.type == "text")                      // проверяем тип поля
  inputTag.type = "submit";                       // меняем тип поля на submit

Если судить по этому примеру, то атрибут type и свойство type ничем не отличаются. У них всегда одно и то же значение, а результат при изменении атрибута такой же, как и при изменении свойства. Более того, если изменить атрибут, то меняется и свойство:

inputTag.setAttribute("type", "submit");
alert(inputTag.type);                             // показывает: submit

Можно изменить и свойство, атрибут тоже автоматически изменится. Это связано с тем, что браузер автоматически синхронизирует значения атрибутов и свойств. И все же они не всегда идентичны:

inputTag.setAttribute("type", "abrakadabra");     // присваиваем атрибуту недопустимое значение
alert(inputTag.getAttribute("type"));             // показывает: abrakadabra
alert(inputTag.type);                             // показывает: text

Это хорошо показывает разницу атрибута и свойства: атрибут может быть любой строкой, он лишь показывает, что написано в исходном коде документа. Свойства же отображают текущее состояние тега, они не могут содержать недопустимых значений.

Еще более явной становится разница, если посмотреть на атрибут value и свойство value.

inputTag.setAttribute("value", "10");             // не меняет текст в текстовом поле
inputTag.value = 20;                              // пишет строку 20 в текстовое поле

Здесь атрибут и свойство имеют разные значения: атрибут value определяет начальное значение для текстового поля. Текущее же значение хранится в свойстве value и меняется через него же.

Не всегда у атрибута и связанного с ним по смыслу свойства одинаковые названия. Типичным исключением является атрибут class, которому соответствует свойство className. Часто отличается и тип данных, к примеру значение атрибута onclick (как и в принципе всех атрибутов) строкового типа, а у соответствующего свойства onclick значением является функция.

Реализация атрибутов в Internet Explorer

Разработчики Internet Explorer упростили модель DOM до предела уравняв атрибуты и свойства. В этом браузере следующие две строки абсолютно эквивалентны:

inputTag.setAttribute("abraKadabra", "abcd");
inputTag.abraKadabra = "abcd";

При таком подходе немедленно возникает множество осложнений. Первое связано с тем, что регистр букв в имени свойст играет роль, в то время как в имени атрибутов он игнорируется (как минимум в HTML). В Internet Explorer же следующие две строки вдруг приобретают разное значение:

inputTag.setAttribute("abrakadabra", "abcd1");
inputTag.setAttribute("abraKadabra", "abcd2");

Эти строки устанавливают два разных свойства объекта. Возникает вопрос — что вернет в этой ситуации inputTag.getAttribute("ABRAKADABRA")? Ответ: либо abcd1, либо abcd2. В Internet Explorer getAttribute() ищет первое свойство с именем, которое соответствует запрошенному атрибуту за вычетом регистра букв. Если таких свойст несколько, то невозможно сказать, какое именно он вернет.

Следующее осложнение возникает с атрибутами, название которых не идентично названию свойства:

inputTag.setAttribute("class", "my-class");

Эта строчка корректно изменит класс тега во всех браузерах. Лишь в Internet Explorer она установит свойство class, не имеющее никакого значение — нужное свойство называется className. С этой проблемой сталкиваются настолько часто, что она даже заслужила упоминания в MSDN:

When setting the CLASS attribute using this method, set the sName to be "className", which is the corresponding Dynamic HTML (DHTML) property.

Иначе говоря, для изменения класса рекомендуется установить несуществующий атрибут className (менять регистр букв нельзя!). С такой рекомендацией Microsoft сложно согласиться — в кроссбраузерных приложениях придется устанавливать два атрибута, один из которых не имеет никакого смысла. Проще изменить свойство className, это работает одинаково надежно во всех браузерах.

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

inputTag.setAttribute("onclick", "alert('clicked')");

После выполнения этого кода в Internet Explorer щелчки на текстовом поле не вызовут никакой реакции. Причина станет ясна, если сравнить значения свойства onclick в Internet Explorer и каком-нибудь другом браузере:

alert(typeof inputTag.onclick);
alert(inputTag.onclick);

Это покажет, что Internet Explorer записал в свойство onclick строку, в то время как другие браузеры корректно преобразовали значение атрибута в функцию. А при возникновении события Internet Explorer не находит в свойстве onclick функции, которую можно выполнить. Чтобы присвоить обработчик события в Internet Explorer надо написать:

inputTag.setAttribute("onclick", function() {alert('clicked')});

Стандарт DOM, однако, явно предписывает, что второй параметр метода setAttribute() является строкой. Такой код, опять же, не станет работать ни в одном браузере, корректно реализующем стандарты.

Выводы

К сожалению, Internet Explorer на данный момент все еще является лидером на рынке браузеров. Из-за этого в большинстве веб-приложений приходится поддерживать Internet Explorer, то есть использование атрибутов весьма усложнено. Internet Explorer 7.0, который должен скоро выйти, вряд ли улучшит ситуацию, серьезных улучшений движка до версии 8.0 точно не будет (да и там это еще очень сомнительно). Вытеснение этого браузера из интернета в ближайшее время тоже не ожидается.

Проще всего, конечно, вообще отказаться от использования атрибутов и пользоваться только свойствами. Для большинства приложений этого достаточно, но существуют задачи, где без атрибутов не обойтись (к примеру копирование DOM-дерева с параллельным его изменением). В таких случаях можно выполнять для Internet Explorer другой код, который дает похожий результат (возможно, что не идеальный, но лучше, чем вообще никакого результата):

document.body.setAttribute("class", "attribute-test");
if (document.body.className == "attribute-test")
{
  // Атрибуты работают корректно (не Internet Explorer или будущая исправленная версия)

  // Работаем с атрибутами
}
else
{
  // Использовать атрибуты нельзя (Internet Explorer)

  // Альтернативный код только для Internet Explorer
}
Powered by POEM™ Engine Copyright © 2002-2005