📜 ⬆️ ⬇️

jQuery inside - attributes, properties, data

Last for the New Year holidays, but not the last article in this series on jQuery internals. 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'; //    $('input:first').prop('validMsg') === $('input:first').prop('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><!-- 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> 

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); } ); // ,   ,     $('sometag').data('id.users', 10); //    -  ,       $('sometag').data( { 'id.users': 10 } ); 

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]; //  ,       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 ] 

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); /* Object { jQuery18309172190900426358: Object { data: Object { id: 10 } toJSON: function () {} } test: 123 } */ console.log(JSON.stringify(strangeObject, null, 4)); /* { "test": 123 } */ 

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') // 10 $('<span>').data('testMe', 10).data('test-me') // 10 

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


  1. Introduction
  2. Parsing html
  3. Attributes, properties, data

Source: https://habr.com/ru/post/164805/


All Articles