I have several questions about how Javascript object work.
I understood that Javascript work by inheritance. But there are things that I not able to understand
First, when we digging through some objects in javascript, It happens that we found not all object property. But when we create our own objects, the debug show everything.
For example, a Bootstrap Dropdown object will show this :
$(".dropdown").dropdown()
(9) [li.dropdown.active, li.dropdown.test, a#addConsumption.dropdown.toggle, li.dropdown.dropdown-accordion, a#addConsumption.dropdown.toggle, li.dropdown, a#addTemperature.dropdown.toggle, li.dropdown, a#addPresence.dropdown.toggle, prevObject: r.fn.init(1)]
$(".dropdown").data("bs.dropdown")
Dropdown {}__proto__: Object
When a MyObject object will output the following :
DropdownAccordion {accordions: Array(0), component: r.fn.init(1), dropdown_menu: r.fn.init(1), menu_panel_group: r.fn.init(1)}
property1
:
[]
property2
:
[div.panel-group, prevObject: r.fn.init(1)]
proto
:
Object
So how do this work ?
And, for example in Bootstrap, how DOM with class like ".dropdown" are instantiated? In fact, I dug through the dropdown.js and tried to find when instantiation occurs, but it never happens. So maybe there is another concept of Javascript instantiation that I don't know existed.
// This section appear to never happen, unless we instantiate like "$(selector).dropdown()
function Plugin(option) {
return this.each(function () {
var $this = $(this)
var data = $this.data('bs.dropdown')
if (!data) $this.data('bs.dropdown', (data = new Dropdown(this)))
if (typeof option == 'string') data[option].call($this)
})
}
Your array that gets printed out to the console is a HTMLCollection (or a jQuery Collection), basically a list of HTMLElements. When you put them out in the console, your browser will format the output differently, such that when you hover above the output elements, they will also be marked in the DOM, as if you would inspect them. However, you can still get the prototype methods of the class with $(".dropdown").__proto__ for example.
Regarding your second question: Usually these elements, which produce new html elements in the DOM, get rendered from javascript, so you should find something in the code, maybe not in the dropdown.js.
Related
I understand how the pushstack function works and I also understand how to use it for my plugins (I guess that's what its most used for , just for internal use and for end() to function properly and other similar methods) .
now below is the jquery source of pushstack , have a look :
pushStack: function( elems ) {
// Build a new jQuery matched element set
var ret = jQuery.merge( this.constructor(), elems );
// Add the old object onto the stack (as a reference)
ret.prevObject = this;
ret.context = this.context;
// Return the newly-formed element set
return ret;
},
theres a lot going on in that function really and I kind of get most part of it , but I have a small problem , understanding the below lines of code :-
ret.prevObject = this;
ret.context = this.context;
what is prevObject and context ? can somebody give me a clue , it definitely does't seem to be a javascript thing ?
Basically prevObject is used to allow flexibility for the jquery selectors chaining syntax.
Most of jQuery's DOM traversal methods operate on a jQuery object instance and produce a new one, matching a different set of DOM elements. When this happens, it is as if the new set of elements is pushed onto a stack that is maintained inside the object. Each successive filtering method pushes a new element set onto the stack
Everytime you make a new filter a new jquery instance is constructed matching your selector and the previous one is stored in it to allow the .end() and the addBack() functions to work (most of the DOM transversal functions use pushStack internally). If this property were not used every selector will forget about the previous one and it will not behave like a stack. Think parent instead of prevObject and it will make it easier to understand.
The context property is deprecated in jQuery 1.10 and only used for supporting live() method but it must be added because the returning object is constructed merging an empty jQuery constructor as first parameter and a set of elements as the second.
var ret = jQuery.merge( this.constructor(), elems );
Because .merge returns the first array modified it's context property might not have the correct value so is overwritten with the correct one this.context
Check this jsfiddle and open the console. You will see that the first value of prevObject is the document as we are making a new selector and then is the ul element since we filter using find. Moreover you can go to the last filter and lookup the whole chain of selectors up to the document again.
I recommend you to use the JQuery API instead of this property as reference in production but this will allow you for example to know the result of all the selectors that have been applied to obtain a given set of DOM elements.
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 you do console.log($('some selector')) in the browser, it returns what looks like an array (first line):
But notice that it's not an instanceof Array, but it's actually the jQuery object.
When you do console.dir($('h1')), it shows it's actually the jQuery object.
The question is, how are they making it look like it's an array in the web console? I noticed in the jQuery source here they add reference to a few Array and Object methods, and here they add toArray (and slice and others) to the jQuery object. Is the web console somehow checking for these methods and if it finds one (toArray, indexOf, slice, etc.), it prints it as an Array? I would like to get this behavior out of any custom object, such as the Ember.ArrayProxy. Currently when you log the Ember.ArrayProxy it shows > Object or whatever, but it would be nice to show it as an array.
Any ideas?
You make your object inherit Array using the prototype, like so:
function SomeType() {
this.push(16);
}
SomeType.prototype = [];
SomeType.prototype.constructor = SomeType; // Make sure there are no unexpected results
console.log(new SomeType()); // Displays in console as [16]
And, of course, all jQuery objects are instances of the jQuery function/constructor, so that's how jQuery does it. As a bonus, because of the inheritance, you get all the methods from Array, and the indexing that comes with it too!
I am trying to learn debugging Javascript in Chrome's Javascript console. However, I don't understand how the console displays the data type of an object . For instance, in the Javascript Console, it shows this:
In this picture, I am using JQuery. I tried to do a console.log on a few variables but how do I know if a particular variable is a JQuery object or a raw DOM object? Is the HTMLDivElement or the other one that shows the div tag listed in the console a JQuery object or a raw DOM object?
In general, how should I know the data type of an object or variable in Javascript in a debugger console like the Chrome's Javascript console? In languages such as Java, the data type of a variable is shown clearly in the debugger; I can know from the debugger what kind of object the variable is, whether it is an instance of Class A or instance of Class B, etc.
if (variable instanceof jQuery) // Or variable.jquery
// jQuery object.
Live DEMO
instanceof docs on MDN:
The instanceof operator tests whether an object has in its prototype chain the prototype property of a constructor.
The way jQuery checks for DOM elements is with nodeType:
// Handle $(DOMElement)
if ( selector.nodeType ) {
The way jQuery checks for jQuery object is with the jquery property:
// HANDLE: $(expr, $(...))
else if ( !context || context.jquery ) {
Those are both jQuery objects.
The console recognizes them as array-like objects containing DOM elements.
you can view the type in the debugger if you go to Scripts tab.
then on the right press on the + sign under Watch Expressions and add whatever you like.
JQuery objects are actually arrays of DOM elements, Weile DOM elements are just DOM elements.
On Firefox, when hovering the mouse over a certain table in my webpage a strange [object HTMLTableCellElement] tooltip appears:
What needs to happen for this to occur? I am having a hard time narrowing what part of my code is the culprit because I don't even know what to look for.
Edit, solved the problem:
This table belongs to a Dojo Dijit template. I accidentaly gave one of the template's nodes the dojo-attach-point "title", causing Dojo to, during the rendering of the Widget, do a
this.title = the_node_with_the_bad_attach_point
The node is then converted to a string and that is where the "[object HTMLTableElement]" comes from.
Probably this:
cell.title = cell;
which can be described as a failed attempt to store the reference to the cell inside its own title property (which holds the string that is shown inside the tooltip on mouse-over).
Live demo: http://jsfiddle.net/VwYRP/
Object references - when coerced to strings - usually look like so: '[object Constructor]', where Constructor is the constructor function which the browser uses to instantiate that object.
Install Firebug and inspect the element. Do you see a title in the text? Check the DOM attributes. Maybe you can spot it there? Or maybe some JavaScript does not work properly. You'll get that message if you do an alert(document.createElement('th'));.