set/attach invisible/unseen attribute/value into html element through vanilla js - javascript

EDIT
why i try to achieve this ?
Encapsulation purpose. generally every programmer encapsulate his/her code. also think about encrypt url or slug url and programmer also use uuid (Universally unique identifier) instead of id (integer id).
END EDIT
I set data attribute as a id to my html element and when I console log or debug html element I can see the data id (1,2)....
I want if anyone debug my html he can not see my data id(1,2) ...
how I will do it invisible or unseeable ? or is there is a way do it invisible or unseeable ?
below example..
const list = document.querySelectorAll('.item');
for(let x = 0; x < list.length; x++){
// list[x].setAttribute('data-id',(x+1));
list[x].dataset.id = x+1;
console.log( list[x]);
list[x].addEventListener('click',goCategoryPage)
}
function goCategoryPage(e){
e.preventDefault()
//
}
<ul class="list">
<li class="item">Category 1</li>
<li class="item">Category 2</li>
</ul>

The short and simple Answer is: Use something like list[x].myId = x+1; To set a custom property. As opposed to attributes, properties are not reflected in the DOM.
This gets the job done. But is considered bad practice. Because there are some caveats:
Your custom property today, might be a standard property in the future, but with a completely different meaning. To make sure this won't become an issue in the future, make sure to add a prefix like your apps name or at least "my" to your property.
It is okay to store primitive data like numbers, booleans or strings this way. But you might get issues with memory leaks if you start storing more complex data. Especially if you start to store references to other DOM Nodes.
More details on the topic of custom properties are discussed here: Can I add arbitrary properties to DOM objects? I think it is a question of personal taste and you really need to make sure you don't produce (future) name collisions and don't store complex data.
For primitive data like your id, I don't see a reason not to store it as a data attribute. There should be nothing to be ashamed of. It also is quite hard to keep a secret in JavaScript in the Browser.
There are two other approaches I'd consider if I had to store more complex data.
Use a WeakMap. You keep your WeakMap in a central place. It has the DOM Node as key and your data as Value. If the Node is garbage collected your data is too.
Create a new function (closure) for each node, which contains the ID and attach it to the click event of your Node. Event listeners are also garbage collected.
Here details on the closure approach:
const list = document.querySelectorAll('.item');
for(let x = 0; x < list.length; x++){
console.log(list[x]);
list[x].addEventListener('click', function () {
const id = x+1;
e.preventDefault();
// stuff to be done on click
});
}
For each iteration of the loop, a new function (with a new closure) is created. Inside this closure the value of x is the value of x in the current iteration - where the function is created.

Related

How does storing JQuery Fields in an array compare to just storing the selection string?

I am having to store a large array of JavaScriptobjects, these objects need to contain a message, and a way of selecting an associated HTML element.
Currently, I've been using the the following code to add to this array:
if (field[0].id != ""){
this.selectorString = "#" + field[0].id;
}
else if (field.attr("name") != ""){
this.selectorString = "[name='" + field.attr("name") + "']";
}
I didn't want to store the entire field, as I wasn't confident on JavaScripts/JQuery memory management or how it worked. Selection strings seemed the safer option, as opposed to a large array of fields. I could then just use the stored string to perform a JQuery selection statement and manipulate the field.
The Big Question
If I store the fields, will this take up a large amount of memory, or is it purely reference to an object that is already stored somewhere in the big black hole of JavaScript of which I know little?
Is there an alternative that anyone can think of that would enable me to achieve what I'm going for. The 'fields' can be divs/spans/input fields/anything, that might not necessarily have an ID/Name - which will cause problems if I'm not storing the field.
Many thanks.
If I store the fields, will this take up a large amount of memory, or is it purely reference to an object that is already stored somewhere in the big black hole of JavaScript of which I know little?
Just a reference (the "big black hole" is called the "DOM" or "DOM tree" or sometimes "page"). But note that if you remove the element you're referring to from the DOM at some point, but still have a reference to it from a JavaScript variable, the element is kept in memory (just not in the DOM tree) until/unless you assign a new value to that variable or that variable goes out of scope.
Concrete example: Say we have this on our page:
<div id="foo">....</div>
And we have this code:
var f = $("#foo");
Now we have a jQuery object wrapped around a reference to that element. It's not a copy of the element, just a reference to it.
Now suppose we did this:
f.remove();
That div is no longer in the DOM (on the page), but it still exists because we still have a reference to it from the f variable (indirectly; f refers to a jQuery object which, in turn, refers to the element). But if that variable goes out of scope, or we assign some other value to it (for instance, f = null;), then the element is not referenced from anywhere and can be reclaimed.
If you're not removing elements from the DOM (directly, or indirectly by replacing the contents of an an ancestor of theirs), then very little memory is used to simply refer to the existing element.

With vanilla JavaScript, how can I access data stored by jQuery's .data() method?

And before someone says:
document.querySelector('.myElem').getAttribute('data-*')
No, is not what I'm looking for. jQuery's data() has dual functions. 1) It queries an HTML5 data attribute which can be seen in the HTML code like this:
<div data-role="page" data-last-value="43" data-hidden="true" data-options='{"name":"John"}'></div>
$( "div" ).data( "role" ) === "page";
$( "div" ).data( "lastValue" ) === 43;
$( "div" ).data( "hidden" ) === true;
$( "div" ).data( "options" ).name === "John";
and 2) the ones that are set by .data(name, value) which are hidden in a jQuery internal Object, to which the documentation only says "to save information under the names 'events' and 'handle'", yet I haven't figured out a way to access them or what actually creates them.
So, my question stands, how can I access the jQuery data values from plain JavaScript?
Just so it's absolutely clear... let me put more emphasis: I don't want the data- HTML attributes, but the jQuery object data.
jQuery uses internal object called $.cache to store event handlers and data values. $.cache is a simple dictionary which can look something like this:
{ 1: { data: { role: "page"}, events: { click: ...} }, 2: { ... }}
The keys are all unique indexes corresponding to some DOM nodes. If your DOM element was previously touched by jQuery (attached events or to some data), you will see that it has a weird property similar to the one below:
jQuery17108624803440179676: 2
Here jQuery17108624803440179676 is a unique string generated by jQuery when it was loaded on the page. This unique identifier is called "expando" and is stored as
$.expando
Now that you know how to access an individual element's internal data by bypassing jQuery's data API...
$.cache[element[$.expando]]
...you may ask why jQuery uses an intermediate object to store all DOM data and events. The reason is that it is a way to avoid memory leaks, which would be the case if data and events handlers were stored directly in DOM elements properties (due to circular references).
Having said all that, I want to emphasize that you should not work with node data objects other than that via the jQuery Data API for the simple reason that it handles all the complex stuff behind the scenes.
From the comments, your motivation to bypass $(...).data seems to be based on the fact that it's causing performance issues.
I totally agree with #meagar on that point, $(...).data shouldn't be expensive enough to cause bottlenecks. However, if you continously re-query and re-wrap DOM elements as jQuery elements (e.g. $('#someEl').data(...) multiple times rather than caching $('#someEl') and do $someEl.data(...), then it might be an issue.
Please also note that if you find yourself attaching a lot of data to DOM elements, you probably got your design wrong. Your data shouldn't live in the presentation layer and you shouldn't have to query the DOM or get a reference to a DOM element to access your data. Obviously, you might in some situations, but these shouldn't be the norm.
If you still want to build your own data feature, then here's an example. It's not optimized, but you should get the idea:
Note that WeakMap is only available in modern browsers, but without it we would be leaking memory, unless you provide a mechanism to destroy the cached attributes expendo object when the associated DOM element gets destroyed.
JSFIDDLE
JSPERF (the test might not be fair, since I'm not sure my implementation does everyting $(...).data does)
var data = (function () {
var attributes = new WeakMap();
return function (el, attr, val) {
var elAttrs = attributes.get(el),
isSetOperation = arguments.length > 2;
if (isSetOperation) {
if (!elAttrs) attributes.set(el, elAttrs = {});
elAttrs[attr] = val;
} else {
return datasetOrCachedAttrsValue();
}
function datasetOrCachedAttrsValue() {
var attrVal = el.dataset[attr];
return typeof attrVal !== 'undefined'?
attrVal:
elAttrs && elAttrs[attr];
}
};
})();
var div = document.querySelector('div');
console.log(data(div, 'test')); //test
console.log(data(div, 'notexisting')); //undefined
data(div, 'exists', true);
console.log(data(div, 'exists')); //true
"would be leaking memory, unless you provide a mechanism to destroy the
cached" - plalx. Well even with WeakMap it still leaks for the same reason why
jQuery can cause leaks. See this demo. – dfsq
That was a good concern from #dfsq, but here's a demo that actually shows how WeakMap allows garbage collection once they key is unreachable, as opposed to jQuery's implementation that will hold on data (unless $().remove is used I believe).
Use the profiler and record heap allocations over time and compare the results. Here's what I got from 6 clicks on each button (one type of button per snapshot). We can clearly see that $().data is leaking while the custom data implementation using a WeakMap is not.
try to leak with $().data.
try to leak with data

If I use object.getElementsByTagName(tagName) in a for statement

If I use object.getElementsByTagName(tagName) in a for statement,
for (index = 0; index < object.getElementsByTagName(tagName).length; index++) {
object.getElementsByTagName(tagName)[index].property = value;
}
Does the browser instantiate a new nodeList object for every pass through the loop, or does the browser simply refer to a single generated list every time; or maybe, it instantiates a list, references the object specified and unloads the list object every pass through the loop?
I've been wondering if its better to store the nodeList object to a variable and reference it when neened.
This is way better code for the reasons listed below:
var elems = object.getElementsByTagName(tagName);
for (var index = 0, len = elems.length; index < len; index++) {
elems[index].property = value;
}
It cannot ever call getElementsByTagName() more than once. It only fetches the nodeList once.
It doesn't rely on any specific browser implementation in order to be efficient.
It preloads the length of the nodeList so even that isn't refetched each time through the loop.
It's just a safer way to make your code efficient.
Safer code makes the implementation questions you asked irrelevant.
Does the browser instantiate a new nodeList object for every pass through the loop or does the browser simply refer to a single generated list every time;
You can test this easily by testing whether two calls to getElementsByTagName return the same object:
document.getElementsByTagName('div') === document.getElementsByTagName('div')
// true
It seems that calling this method with the same argument does indeed return the same NodeList object (in Firefox and Chrome at least). It does not generate a new list every time.
So you might think that calling it in the loop over and over again won't make a difference. However, as I see it, there are multiple reasons why would want to store the list in a variable:
You have an unnecessary function call in each loop.
You don't know what actually happens behind the scenes. Even though the same object is returned, the might be procedures running which make sure that the list reflects the current state of the document, which would not happen if you didn't call the function.
Most importantly: The DOM specification doesn't seem to require that the same list is to be returned. That it does in Firefox and Chrome (where I tested it) might just be an implementation detail, so I wouldn't rely on this behavior. In fact, the specification explicitly states to return a new list:
Return Value: A new NodeList object containing all the matched Elements.
I've been wondering if its better to store the nodeList object to a variable and reference it when needed.
Yes it is. You don't even have to call getElementsByTagName again when elements are added or removed because the returned NodeList is live, i.e. any changes made to the document are directly reflected in the list, without you having it to update explicitly.
Certain operations, like accessing the length of the list will also trigger a reevaluation. So, additionally to storing the list in a variable, you might want to cache the length as well:
var nodes = object.getElementsByTagName(tagName);
for (var index = 0, l = nodes.length; index < l; index++) {
nodes[index].property = value;
}
This can be very handy or very confusing, depending on what you need. If you only want a list of nodes that exist at the moment the function is called, you have to convert the NodeList to an array:
var nodes = Array.prototype.slice.call(object.getElementsByTagName(tagName), 0);
this test shows that it first gets the array calculates the length each time!
var testObj = {
getArray: function () {
console.log( 'getting array' );
return this._array;
},
_array: ['a','b','c']
};
for ( var index = 0; index < testObj.getArray().length; index++ ){
document.write( testObj.getArray()[index] );
}
I would either store the elements first, and reference it's .length each time, or just store the length in a variable :)
Your browser is generating a new list each time.
But this code will work as index is changing every run.
It is a good practice to first save node list in some var:
var elements = object.getElementsByTagName(tagName);
for (index = 0; index < elements; index++) {
elements[index].property = value;
}
you can see your aproach as:
var index = 0;
while(index<object.getElementsByTagName(tagName))
{
elements[index].property = value;
index++;
}
What did you expect it to do? Read your mind? Did you expect the JS processor to have the semantic information that object.getElementsByTagName(tagName) will (probably) return the same value each time it is called, and is thus "loop-invariant" and can thus be hoisted out of the loop (see http://en.wikipedia.org/wiki/Loop-invariant_code_motion)?
Perhaps you imagined that the browser is somehow "caching" the list of all elements with a particular tagname under the element, and then just returning the same list in subsequent calls? Although it's hard to tell what a browser is doing inside without looking at the code, it may not be, or one browser might be and another browser not. Even if the browser does cache the result, granted that any performance hit is likely to be minor in the overall scheme of things, calling the API again and again will always be much less efficient that referring to a variable that's already set. And it is not impossible that what you do with each element of the nodelist, such as setting a property, could cause the cache to be reset.
As a minor point, assuming the list is created over and over again, it is not then "unloaded"; there is no such concept in JS. In technical terms, its status would be "just left sitting there". It will be garbage collected soon enough.
So yes, by all means call getElementsByTagName just once. Actually, you can call it once at the beginning of your program (assuming there's just one object and tagname you're interested in), since it returns a "live" list. See https://developer.mozilla.org/en-US/docs/Web/API/element.getElementsByTagName.

How does JavaScript distinguish between objects?

I have wriiten
document.createElement("p");
document.createElement("p")
How does Javascript intepreter knows do distinct between those two?
Id ? ( what is the Js property )? maybe something else ?
In this particular case:
document.createElement("p");
document.createElement("p")
the JavaScript runtime doesn't worry about the elements at all, because you didn't save the values returned. They're thrown away.
If you had written,
var p1 = document.createElementById('p');
var p2 = document.createElementById('p');
well then you've got two separate variables, and so you're keeping track of the difference.
It's important to be aware that the JavaScript interpreter itself doesn't really care too much about your DOM nodes. That's not really it's business. If you call methods and create objects, it's your problem to keep track of them.
Let's pick another real-life example: How does a human distinguish one orange from another one, in a basket? I look inside the basket, and notice that there're multiple orange-coloured, ball-shaped items
The JavaScript interpreter internally keeps track of the created objects. When an object isn't referred by anything (variables), the built-in Garbage Collector destroys the object.
It doesn't. createElement returns the object reference but if you don't assign it to anything or directly use it, it's lost.
var firstp = document.createElement("p");
var secondp = document.createElement("p");
Then you can assign an id or whatnot:
firstp.id = "first";
Keep in mind though, that this is not in the DOM yet. You have to insert it somewhere before it can be seen by the user/found with getElementById.
For that you can do something like this:
document.body.appendChild(firstp);
It doesn't. When you call document.createElement(), you create an element and it's inserted into the DOM. JavaScript can then grab certain p elements by ID, for example, or grab other p (and other) elements with getElementsByClassName().
If you'd assigned the returned values to variables, you'd be able to keep track of which one was which by referencing different variables. This is you distinguishing between them, not JavaScript, though.
You can refer to them by their index in the array of elemnts:
document.getElementsByTagName('p')[0]
You can assign id to them
document.getElementsByTagName('div')[0].id = 'first'
document.getElementsByTagName('div')[1].id = 'second'
And refer to them by id
document.getElementById('first')
It is up to you how you do it really...

Creating a constant time data structure for DOM element references

Relevant discussion.
I understand I can build an array of references to elements/nodes. I realize also that I could use the neat trick of treating an array like a heap (index 2n and 2n+1 for children) to build a (potentially wasteful) binary search tree using it.
But all that's still not enough for the premature optimizer in me. Besides, implementing a BST is going to be bug prone.
Here's my question. Can I somehow use an element reference as index into javascript's hashes (which are objects, or vice versa?). If not, can I conjure up a unique string from an element reference, which I can then use as my hash key? If not, how the hell does jQuery do it?
The easiest option is to just use your own attribute on the DOM object:
var element = document.getElementById("test");
element.myData = "whatever";
Here's the general idea behind how jQuery's .data() function works that you could use in your own plain javascript. It uses one custom attribute on the object and then stores everything else in a data structure indexed by the value of that custom attribute.
var idCntr = 0; // global cntr
var data = {};
var element = document.getElementById("test");
var id = element.uniqueID;
if (!id) {
id = idCntr++ + "";
element.uniqueID = id;
}
data[id] = "whatever";
// then some time later, you can do this
var element = document.getElementById("test");
console.log(data[element.uniqueID]); // whatever
It is a bit more involved to store multiple attributes for a given object in the data object, but this is the general idea.
And, if you can use jQuery, it's trivial:
$("#test").data("myData", "whatever"); // sets the data
console.log($("#test").data("myData")); // retrieves the data
If you want to really see how jQuery's .data() works, you can step through the first call to set data and then retrieve it when using the unminified jQuery. It's easy to see what it does.

Categories