Last for the New Year holidays, but not the last article in this series on jQuery internals.
The past turned out to be very fast and small, but the interest of habrazhiteli to the topic, judging by the poll “should I continue?”, Which hangs in each post for some time after its creation, does not disappear.
The topic for today's post is quite large and I will try to tell about it more interestingly and not too superficially. Today we consider the methods
attr ,
prop and
data .
The last one is the most interesting and we will postpone it for last.
All three functions work through service
access .
')
jQuery.access
This function resembles the
domManip
from the previous chapter and is needed to prepare the arguments passed to the function, and then to execute the specified callback. In our case, this callback is just the functions that will perform operations with attributes, properties and data.
To begin with, our arguments are
checked in the function and, if this is an object, it will be “expanded” and
access
will be called separately for each key. Figuratively, these two options are the same:
$('.user-avatar').attr( { 'alt': '', 'title': function(idx, value) { return ' ' + value + ' (' + $(this).data('id') + ')'; } } ); | $('.user-avatar') .attr('alt', '') .attr('title', function(idx, value) { return ' ' + value + ' (' + $(this).data('id') + ')'; } ); |
Further, for each element in our jQuery-object, a callback for the current key and value
will be invoked . As can be seen from the example above, functions in the value are
also supported , in this case the value in the callback will be calculated in the specified function, which will be called in the context of the element, the parameters for it will be the sequence number and the current value of the specified attribute.
Attributes and properties
jQuery.fn.attr
First of all, the function
checks the type of the element in order to cut off attempts to get or set the attribute from
ATTRIBUTE_NODE
,
COMMENT_NODE
,
TEXT_NODE
.
Then there
is a check for the existence of a function with the name specified in the key in
jQuery.fn
, but this check
only works if
jQuery.attr
from
init
. In the first article there was an example on this topic and I promised to talk more about it. So, the code on the left will be “deployed” in the code on the right:
$('<span>', { 'title': '', 'text': ' ', 'appendTo': document.body } ); | $('<span>') .attr('title', '') .text(' ') .appendTo(document.body); |
I do not recommend doing so with
appendTo
simply because it is not very beautiful. However, this is possible for any function that we can find in
jQuery.fn
. In this case,
attr
will find the
text and
appendTo functions and call them instead of continuing with their work.
If the element does not have such a method as
getAttribute
, then
jQuery.prop
will be called with the same key and value. This case is rather narrow and appears, judging by the
bug report , only in old IE, when working not with HTML, but with an XML document that comes from an ajax request, for example.
If the attribute value is passed to the function and is
null
, the
jQuery.removeAttr function will be called, which
will remove the attribute (or attributes if they were listed with a space) and set the corresponding boolean properties, if any, to
false
.
Then the attribute value will be set using the corresponding hook (if there is one) or the usual
setAttribute
, or it will be obtained via the hook or
getAttribute
.
jQuery.fn.prop
We will not dwell on this function for a long time, because it works in much the same way as
attr
, it only sets the properties of the element directly and simultaneously
normalizes the names of the properties. Normalization occurs through the
jQuery.propFix
service object, which, again, is not documented and it is not advisable to use it, however:
jQuery.propFix.validMsg = 'validationMessage';
Hooks
Hooks for
attr
(
jQuery.attrHooks
) and
prop
(
jQuery.propHooks
) are ordinary objects that can have a
set
and / or
get
function. They are engaged in the task and obtaining a certain value. The example will be more clear:
<span class="user user-male"></span> <span class="user user-male"></span> <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 ); } } } </script>
A thing may be convenient, but not documented. Do not use it without extreme need.
For
attr
there is an interesting set of
boolHook hooks, it is automatically applied to all
predefined boolean attributes . We need it in order to do this:
> $('<input>').attr('disabled', true) [<input disabled=​"disabled">​]
In this case, the hook additionally also
sets the value of the disabled
property to
true
.
There is also a
nodeHook
set, but this is a peculiar set of crutches, which is replenished at the jQuery initialization stage, when checking the capabilities of the browser (for example,
here ). In modern browsers it is empty.
Data
Let's start with the fact that you are grossly mistaken if you think that jQuery knows something about such a thing as a
dataset that came to us along with HTML5. He has no idea, it is not used anywhere in the library, everything is done manually. However, properties set via
dataset
are available via
jQuery.data
(only if it is not an object). But if from jQuery something is set via
jQuery.data
, it will not be available via
dataset
, because the library stores all the specified values ​​in its cache. About everything in order, also we will break the chapter a little bit.
namespace
We briefly mention that in jQuery 1.8.3
jQuery.fn.data
allows
jQuery.fn.data
to work with the so-called namespace for data. This feature is marked as deprecated back in 1.7, and in 1.9 it is already gone. So if you use something like that, then I have
bad news for you:
$('sometag').on('changeData.users', function(e) { console.dir(e); } );
Namespaces in events do not disappear anywhere and we will definitely consider them in the future.
acceptData
data
does not work with everything that moves, but only with what is being tested by the
acceptData function. Only nodes that are not
embed
,
applet
or
object
(in this case, with the exception of Flash, the
definition follows
classid
).
jQuery.cache
The cache in jQuery uses not only
data
. For our case with data, something on the element gets into the cache when some value is assigned to some key. The
jQuery.cache
object is a normal numbered object, where the key is the value of the
expando
element.
jQuery.expando
is a unique identifier determined randomly when the library is initialized. As soon as we want to write something to the cache, the element is allocated its sequence number (increment of the
global jQuery.guid
counter ) in the cache, which is written to the property of the element. The value itself will be placed in the “data” section in the corresponding cache item. The example will be more clear:
var $span = $('<span>'), spanElement = $span[0];
Remember the glimpse of the
cleanData
mentioned in the previous article? It just
cleans the cache for the deleted items, and
jQuery.deletedIds
sequence numbers in
jQuery.deletedIds
, then to take the next number
from there instead of generating a new one.
Interestingly, the cache with the data
not for the nodes is set
right inside and in this case the library will not have to worry about cleaning. This internal cache object sets an empty
toJSON method
along the way so that it does not get to the output during serialization in JSON:
var $strangeObject = $( { 'test': 123 } ), strangeObject = $strangeObject[0]; $strangeObject.data('id', 10); console.dir(strangeObject); console.log(JSON.stringify(strangeObject, null, 4));
camelCase
All keys for
data
converted to camelCase both on reading and on writing (by the way,
dataset
cannot boast with this, it will swear on dash keys):
$('<span>').data('test-me', 10).data('testMe')
Data recording
To write from the key, the library first
tries to allocate a namespace (what is after the dot), for use later in the event call, which we mentioned above.
Then, through all the same
accessData
(remembering support for getting values ​​from a function, etc.), it tries to call the
setData
event handler on the element,
writes data to the cache (in general, jQuery.data is just a sheet for working with the cache, about which we already learned a little above) and tries to call the
changeData
event
changeData
.
To write multiple data on an object,
jQuery.data
for each key-value, that is, writing directly, bypassing
accessData
and
accessData
corresponding events, which is most likely a bug in the library (there must be a call to yourself,
jQuery.fn.data
). It is not necessary to repair anything, in 1.9 this piece was rewritten.
Reading
Reading an item also goes through
accessData
. At first, the
library tries to find the data in the cache and, if it has not found it, it
tries to find an element in the data-attributes that it may have already been set manually.
In this case, the key is anticimelizing (wow, what a word, but the point is that testMe will be converted to test-me) and the value of the corresponding data attribute (data-test-me for an example from the previous brackets)
tries to be obtained and, if such is found, it will be parsed. If the value is
null
or boolean, then it will be converted to native (not a string), but if the attribute value starts with an open brace, the library
will try to call jQuery.jsonParse . Please note that a long number (more than 20 characters) can return both as a number and as a string (thanks to
Silver_Clash for a tip), if after the conversion to a number and back the
comparison with the original is not passed. The resulting value will be written to the cache and returned to the developer.
Receiving the entire data
set is again separated from
accessData
and, again, will not call the
getData
event
getData
. In this case, everything from the cache will be received, plus the library will run through all the attributes of the element, whose name starts with "data-" and also write them to the cache, simultaneously setting the
parsedAttrs
flag in the cache, so that the next receipt will not be
parsedAttrs
disassemble.
Conclusion
Perhaps the
data
should be considered a separate article from the attributes and properties, but then the article on them would have turned out quite small. And so - the thing is to start your first working day after a long weekend. I liked the resulting article, so it turned out that I was terribly interested in picking things up like that. I hope you enjoy it.
As always, do not hesitate to express your opinion about the article, to offer and ask something.
Content of a series of articles
- Introduction
- Parsing html
- DOM manipulations
- Attributes, properties, data