jQuery изнутри — атрибуты, свойства, данные

jQuery изнутри — атрибуты, свойства, данные

Последняя за новогодние каникулы, но не последняя в этой серии статья, посвященная внутренностям jQuery. Прошлая получилась очень быстрой и маленькой, но интерес хабражителей к теме, судя по опросу «стоит ли продолжать?», который висят в каждом посте некоторое время после его создания, не пропадает.

Тема для сегодняшнего поста достаточно большая и я постараюсь рассказать о ней поинтереснее и не слишком поверхностно. Рассмотрим мы сегодня методы 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 , который, опять же, не документирован и использовать его не желательно, тем не менее:

Хуки для attr ( jQuery.attrHooks ) и prop ( jQuery.propHooks ) — это обычные объекты, у которых может быть функция set и/или get . Занимаются они заданием и получением определенного значения. На примере будет более понятно:

Штука, может быть и удобная, но не документирована. Не используйте ее без крайней нужды.

Для attr есть интересный набор хуков boolHook, он автоматически применяется ко всем заранее заданным булевым атрибутам. Нужен он для того, чтобы делать вот так:

В этом случае хук дополнительно еще и задаст значение свойства 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 ее уже нет совсем. Так что если вы используете что-то такое, то у меня для Вас плохие новости:

Неймспейсы в событиях никуда не деваются и мы их обязательно рассмотрим в будущем.

acceptData

data работает не со всем, что движется, а только с тем, что проходит проверку функцией acceptData. Только ноды, не embed , applet или object (в этом случае за исключением Flash'а, определение идет по classid ).

jQuery.cache

Кеш в jQuery пользуется не только data . Для нашего случая с данными, в кеш что-то по элементу попадает при задании какого-то значения какому-то ключу. Объект jQuery.cache представляет собой обычный нумерованный объект, где ключ — значение expando -атрибута элемента. jQuery.expando — уникальный идентификатор, определяемый рандомно при инициализации библиотеки. Как только мы хотим записать в кеш что-то, элементу выделяется его порядковый номер (инкремент глобального счетчика jQuery.guid ) в кеше, который записывается в свойство элемента. В соответствующий номеру элемент кеша, в раздел «data» будет помещено само значение. На примере будет более понятно:

Помните мельком упомянутую cleanData в предыдущей статье? Она как раз чистит кеш по удаленным элементам, а удаленные порядковые номера сбрасывает в jQuery.deletedIds , чтобы потом взять следующий номер именно оттуда вместо генерации нового.

Что интересно, кеш с данными не для нод задается прямо внутри и библиотеке в этом случае не надо будет беспокоиться о чистке. У этого внутреннего объекта-кеша попутно задается пустой метод toJSON, дабы он не попал в вывод при сериализации в JSON:

camelCase

Все ключи для data преобразуются в camelCase как на чтении, так и на записи (к слову, dataset этим похвастаться не может, на ключи с тире он будет ругаться):

Запись данных

Для записи из ключа библиотека сначала пытается выделить 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. Обратите внимание что длинное число (больше 20 знаков) может вернуться и как число, и как строка (за наводку спасибо Silver_Clash), в случае, если после преобразования в число и обратно сравнение с оригиналом не будет пройдено. Полученное значение будет записано в кеш и возвращено разработчику.

Получение всего набора данных опять отделено от accessData и, опять же, не вызовет обработчик события getData . В этом случае будет получено все из кеша плюс библиотека пробежится по всем атрибутам элемента, название которых начинается с «data-» и так же запишет их себе в кеш, попутно выставив в кеше флажок parsedAttrs , чтобы на следующее получение целиком повторно все атрибуты уже не разбирать.

Заключение

Возможно, data следовало рассмотреть отдельной статьей от атрибутов и свойств, но тогда статья по ним получилась бы совсем маленькой. А так — самое то, чтобы начать свой первый рабочий день после долгих выходных. Мне получившаяся статья понравилась, так уж сложилось что мне жутко интересно ковырятья в подобном. Надеюсь, понравится и вам.

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

📎📎📎📎📎📎📎📎📎📎