Тема для сегодняшнего поста достаточно большая и я постараюсь рассказать о ней поинтереснее и не слишком поверхностно. Рассмотрим мы сегодня методы attr, prop и data.
Последняя из них — самая интересная и мы отложим ее напоследок.
Все три функции работают через служебную access.
jQuery.access
Эта функция напоминает domManip
из предыдущей главы и нужна для подготовки аргументов, переданных в функцию, а затем — для выполнения указанного callback’а. В нашем случае этот callback — как раз те функции, которые будут производить операции с атрибутами, свойствами и данными.
Для начала, в функции проверятся наши аргументы и, если это — объект, то он будет «развернут» и для каждого ключа access
будет вызван отдельно. Образно, эти два варианта — одинаковы:
|
|
Дальше для каждого из элемента в нашем jQuery-объекте будет вызван callback для текущего ключа и значения. Как видно из примера выше, функции в значении тоже поддерживаются, в этом случае значение в callback будет расчитано в указанной функции, которая будет вызвана в контексте элемента, в параметры к ней попадут порядковый номер и текущее значение указанного атрибута.
Атрибуты и свойства
jQuery.fn.attr
Первым делом функция проверяет тип элемента, дабы отсечь попытки получить или задать атрибут у ATTRIBUTE_NODE
, COMMENT_NODE
, TEXT_NODE
.
Дальше идет проверка на наличие функции с заданным ключем в jQuery.fn
, но срабатывает эта проверка только в случае вызова jQuery.attr
из init
. В первой статье был пример на эту тему и я обещал о нем еще поговорить. Так вот, код слева будет «развернут» в код справа:
|
|
Не рекоммендую делать так с appendTo
просто потому, что это не очень красиво. Тем не менее, такое возможно для любой функции, которую мы можем найти в jQuery.fn
. В этом случае attr
найдет функции text и appendTo и вызовет их вместо продожения своей работы.
Если у элемента не существует вообще такого метода как getAttribute
, то будет вызван jQuery.prop
с тем же ключем и значением. Кейс этот довольно узкий и проявляется, судя по багрепорту, только в старых IE при работе не с HTML, а с XML-документом, который приходит из ajax-запроса, к примеру.
В случае, если значение атрибута передано в функцию и равно null
, будет вызвана функция jQuery.removeAttr, которая удалит атрибут (или атрибуты, если они были перечислены через пробел) и поставит соответсвующие boolean-свойства, если они есть, в значение false
.
Дальше значение атрибута будет задано с помощью соответствующего ему хука (если такой найдется) или обычного setAttribute
, либо будет получено через хук или getAttribute
.
jQuery.fn.prop
Долго задерживаться на этой функции не будем, потому что она работает примерно так же, как и attr
, только задает свойства элементу напрямую и попутно нормализует названия свойств. Нормализация происходит через служебный объект jQuery.propFix
, который, опять же, не документирован и использовать его не желательно, тем не менее:
jQuery.propFix.validMsg = 'validationMessage'; // результаты будут равны $('input:first').prop('validMsg') === $('input:first').prop('validationMessage');
Хуки
Хуки для attr
(jQuery.attrHooks
) и prop
(jQuery.propHooks
) — это обычные объекты, у которых может быть функция set
и/или get
. Занимаются они заданием и получением определенного значения. На примере будет более понятно:
<span class="user user-male" data-id="15">Игорь</span> <span class="user user-male" data-id="10">Дарья</span><!-- male - намеренно !--> <script src="http://code.jquery.com/jquery-1.8.3.js"></script> <script> var SEX_MALE = 0, SEX_FEMALE = 1, sexClassesMap = { 'user-male': SEX_MALE, 'user-female': SEX_FEMALE }; jQuery.propHooks.usersex = { get: function(elem) { var elementClasses = elem.className.split(/\s+/), i = elementClasses.length; for (; i > 0; i--) { if ('undefined' !== typeof sexClassesMap[elementClasses[i]]) { return sexClassesMap[elementClasses[i]]; } } }, set: function(elem, value) { var $element = $(elem), i; for (className in sexClassesMap) { $element.toggleClass( className, sexClassesMap[className] === value ); } } } // пройдет через хук и вернет male if (SEX_MALE === $('.user:first').prop('userSex')) { console.log('первый - мужчина!'); } // а так мы - можем поменять $('.user:last').prop('userSex', SEX_FEMALE); </script>
Штука, может быть и удобная, но не документирована. Не используйте ее без крайней нужды.
Для attr
есть интересный набор хуков boolHook, он автоматически применяется ко всем заранее заданным булевым атрибутам. Нужен он для того, чтобы делать вот так:
> $('<input>').attr('disabled', true) [<input disabled="disabled">]
В этом случае хук дополнительно еще и задаст значение свойства disabled
в true
.
Так же есть набор nodeHook
, но это своеобразный набор костылей, который пополняется на этапе инициализации jQuery, при проверках возможностей браузера (например, здесь). В современных браузерах он пустой.
Данные
Начнем с того, что Вы крупно ошибаетесь, если думаете, что jQuery что-то знает о такой штуке как dataset, пришедшей к нам вместе с HTML5. Понятия не имеет, оно нигде не используется в библиотеке, все делается вручную. Тем не менее, свойства, заданные через dataset
доступны через jQuery.data
(только если это не объект). А вот если из jQuery что-то задано через jQuery.data
, доступно через dataset
оно уже не будет, потому что библиотека все заданные значения хранит в своем кеше. Обо всем по порядку, еще и разобьем главу немножко.
namespace
Вскользь упомянем, что в jQuery 1.8.3 jQuery.fn.data
позволяет работать с так называемыми namespace для данных. Эта возможность помечена как deprecated еще в 1.7, а в 1.9 ее уже нет совсем. Так что если Вы используете что-то такое, то у меня для Вас плохие новости:
$('sometag').on('changeData.users', function(e) { console.dir(e); } ); // бабах, тут мы увидим, что обработчик события выполнился $('sometag').data('id.users', 10); // а вот тут - уже нет, такой вот баг (а чинить уже не надо - поддержки больше не будет) $('sometag').data( { 'id.users': 10 } );
Неймспейсы в событиях никуда не деваются и мы их обязательно рассмотрим в будущем.
acceptData
data
работает не со всем, что движется, а только с тем, что проходит проверку функцией acceptData. Только ноды, не embed
, applet
или object
(в этом случае за исключением Flash’а, определение идет по classid
).
jQuery.cache
Кеш в jQuery пользуется не только data
. Для нашего случая с данными, в кеш что-то по элементу попадает при задании какого-то значения какому-то ключу. Объект jQuery.cache
представляет собой обычный нумерованный объект, где ключ — значение expando
-атрибута элемента. jQuery.expando
— уникальный идентификатор, определяемый рандомно при инициализации библиотеки. Как только мы хотим что-то записать в кеш что-то, элементу выделяется его порядковый номер (инкремент глобального счетчика jQuery.guid
) в кеше, который записывается в свойство элемента. В соответствующий номеру элемент кеша, в раздел «data» будет помещено само значение. На примере будет более понятно:
var $span = $('<span>'), spanElement = $span[0]; // уникальный идентификатор, после рефреша страницы будет уже другим console.log(jQuery.expando); // jQuery18302642508496064693 console.log(spanElement[jQuery.expando]); // undefined // задаем данные по ключу id $span.data('id', 10); console.log(spanElement[jQuery.expando]); // 1 console.dir(jQuery.cache[1]); /* Object { data: Object { id: 10 } } */ $span.remove(); console.dir(jQuery.cache[1]); // undefined console.dir(jQuery.deletedIds); // [ 1 ]
Помните мельком упомянутую cleanData
из предыдущей статьи? Она как раз чистит кеш по удаленным элементам, а удаленные порядковые номера сбрасывает в jQuery.deletedIds
, чтобы потом взять следующий номер именно оттуда вместо генерации нового.
Что интересно, кеш с данными не для нод задается прямо внутри и, библиотеке в этом случае не надо будет беспокоиться о чистке. У этого внутреннего объекта-кеша попутно задается пустой метод toJSON, дабы он не попал в вывод при сериализации в JSON:
var $strangeObject = $( { 'test': 123 } ), strangeObject = $strangeObject[0]; $strangeObject.data('id', 10); console.dir(strangeObject); /* Object { jQuery18309172190900426358: Object { data: Object { id: 10 } toJSON: function () {} } test: 123 } */ console.log(JSON.stringify(strangeObject, null, 4)); /* { "test": 123 } */
camelCase
Все ключи для data
преобразуются в camelCase как на чтении, так и на записи (к слову, dataset
этим похвастаться не может, на ключи с тире он будет ругаться):
$('<span>').data('test-me', 10).data('testMe') // 10 $('<span>').data('testMe', 10).data('test-me') // 10
Запись данных
Для записи из ключа библиотека сначала пытается выделить namespace (то, что после точки), для использования потом в вызове события, о которых мы выше упоминали.
Затем через все тот же accessData
(вспоминаем поддержку получения значения из функции и пр.) пытается вызвать обработчик события setData
у элемента, записывает данные в кеш (вообще jQuery.data — как раз простыня для работы с кешом, о работе которого мы уже узнали чуть выше) и пытается вызвать обработчик события changeData
.
Для записи множественных данных, по объекту, для каждого ключа-значения дергается jQuery.data
, то есть запись напрямую, минуя accessData
и вызов соответствующих событий, что скорее всего баг в библиотеке (должен быть вызов себя, jQuery.fn.data
). Чинить ничего не надо, в 1.9 переписали этот кусок.
Чтение
Чтение элемента так же проходит через accessData
. Сначала данные библиотека пытается найти в кеше и, если не нашла, то пытается найти в data-атрибутах элемента, которые могли уже у него быть заданы вручную. В этом случае ключ антикемелизируется (ух какое слово, но смысл в том, что testMe будет преобразован в test-me) и по нему пытается быть получено значение соответствующего data-атрибута (data-test-me для примера из предыдущих скобок) и, если такое найдено, то оно будет распарсено. Если значение — null
или булево, то оно будет преобразовано в нативное (не строку), а вот если значение атрибута начинается на открытую фигурную скобку, то библиотека попробует вызвать jQuery.jsonParse. Полученное значение будет записано в кеш и возвращено разработчику.
Получение всего набора данных опять отделено от accessData
и, опять же, не вызовет обработчик события getData
. В этом случае будет получено все из кеша плюс библиотека пробежится по всем атрибутам элемента, название которых начинается с «data-» и так же запишет их себе в кеш, попутно выставив в кеше флажок parsedAttrs
, чтобы на следующее получение целиком повторно все атрибуты уже не разбирать.
Заключение
Возможно, data
следовало рассмотреть отдельной статьей от атрибутов и свойств, но тогда статья по ним получилась бы совсем маленькой. Мне получившаяся статья понравилась, так уж сложилось что мне жутко интересно ковырятья в подобном. Надеюсь, понравится и вам.
Как всегда, не стесняйтесь выражать свое мнение о статье, что-то предлагать и спрашивать.
Содержание цикла статей
- Введение
- Парсинг html
- Манипуляции с DOM
- Атрибуты, свойства, данные
ссылка на оригинал статьи http://habrahabr.ru/post/164805/
Добавить комментарий