Do Javascript engines do this kind of optimisation? - javascript

NB I've done a bit of reading about JS engine optimisation, but most of it is too technical for me to understand enough to apply to this question. I'm also aware that not all engines are the same. I'd be interested in particular in handling by V8 and Rhino, I suppose.
If I create a table, and then rows, and then cells... And then I want to put identical key event listeners on all those cells.
Not only does the creation of these listeners for each cell take a certain amount of time, which could be significant with a biggish table, but in addition I'm supposing that each listener function is stored on its own, even though every listener function is actually identical.
The other key event listener approach which I can use is to put a key event listener on the TABLE, and to work out during the run, on each keydown event, which cell fired this event. I can do this by going
let elementOfInterest = document.activeElement;
"Get the currently focused element in the document" from here.
From my experiments, if you type inside a table cell, this TD does indeed have the focus and is indeed returned by the above call.
This way, I only have to create one listener, which will I assume be quicker and take less memory. The only (very) slight downside is that time then has to be spent getting this "active element" by means of the above call. And, just possibly, the risk that something will grab focus in an unexpected way - obviously if you want to listen to changes of text in a cell, the least error-prone technique must be to use a listener attached to that cell.
But I'm just wondering: maybe Javascript is cleverer than this: maybe if you create 100 separate cell listeners something somewhere identifies them as "all the same" and just makes one function in memory. This is the kind of optimisation you might typically expect from a Java compiler, for example.
Does any such optimisation ever occur? How clever is Javascript with a case like this? Or is it just "script and that's it": what you see is what you get?

The semantics of the language itself don't allow for two function expressions to be "merged" into one even if they were functionally equivalent:
> a = function(){return 'foo'};
ƒ (){return 'foo'}
> b = function(){return 'foo'};
ƒ (){return 'foo'}
> a === b
false
In addition, things get extra hairy when you start considering the closure of the function (e.g. the outer names it uses).
So no, that doesn't happen out of the box.
However, for your use case, there are two optimizations:
As you've found out, you can employ event bubbling and add the event listener on an ancestor element and use event.target (preferably instead of document.activeElement) to figure out where it was originally targeted (and event.currentTarget would be the node the handler is on)
If you can't use a common ancestor (tip: you almost always can; document is a valid target), you could define the function once (assuming it doesn't need to close over any dynamically changing variables) and again use event.target, e.g. event.target.dataset to figure out the data you're handling.
Below, a snippet demonstrating the two.
function createButton(parent, datum) {
const btn = document.createElement("button");
btn.dataset.datum = datum;
btn.innerHTML = datum;
parent.appendChild(btn);
return btn;
}
function eventHandler(event) {
if(event.target.tagName !== "BUTTON") return;
const msg = `real target: ${event.target} (datum="${event.target.dataset.datum}")\ncurrent target: ${event.currentTarget}`;
alert(msg);
}
const p2 = document.getElementById("parent2");
// bubbling listener
const p1 = document.getElementById("parent1");
p1.addEventListener("click", eventHandler, false);
for(var i = 0; i < 10; i++) {
createButton(p1, "p1-" + i);
}
// same function on multiple elements
for(var i = 0; i < 10; i++) {
createButton(p2, "p2-" + i).addEventListener("click", eventHandler, false);
}
<div id="parent1"></div>
<div id="parent2"></div>

Related

Efficiently tracking and updating an array of DOM elements with Javascript / jQuery?

Inside of a module I'm writing (its kind of a slider / timeline interface component) I've got a method that updates the controls which are a set of clickable elemetns along the bottom that are updated on click and when the user scrolls.
I'm doing the following to attach classes to the items up until the active one. While the approach I'm using works, its feels very inefficient as I'm looping over a set of DOM elements each time.
updateTimeLine : function(pos, cb) {
var p = pos;
var timeline = $('.timer').toArray();
if (p > 15)
p = 15;
$.each(timeline, function(index,value) {
var that = $(this);
if (index >= p) {
if (that.children('span').hasClass('active'))
that.children('span').removeClass('active');
} else {
that.children('span').addClass('active');
}
});
if (cb && typeof(cb) === "function") {
cb();
}
return this;
},
Is there a better way to do this? If so, how?
Is this a good use case for something like the observer pattern? which I don't fully get, having not spent any time with it yet, so if it is, I'd really like to know how to apply this pattern properly.
Observer patterns notify subscribed objects by looping through and invoking listeners on each subscriber when a relevant change occurs. Because of that, you'd probably end up using $.each anyways. I think what you have is equally efficient.
If you feel bad about iterating over the dom each time, consider this: there exists no such algorithm that can update each dom element without iterating through them. Caching the DOM array theoretically would improve performance, but my money says the browser's already doing that. Try it yourself on this jsperf...

updateevents for joint.uml.shapes.State element in jointjs

Merry Christmas, everyone !
I have to updatethe events for joint.shapes.uml.State element, which is introduced by JointJS library, after some events are triggered. I use the set('events', events) function. I print the element to the console, and find the element's events has already been updated. But the events shown on the graph haven't changed. The sample code can be accessed at:
http://jsfiddle.net/GJH_ICT/6pmbQ/5/.
You can drag the end of a link to trigger events. This is the update function:
function resetEvents(id, old, newer) {
var events = graph.getCell(id).get('events');
for(var i = 0; i < events.length; ++i) {
events[i] = events[i].replace(old, newer);
}
graph.getCell(id).set('events', events);
console.log(graph.getCell(id).get('events'));
}
Besides, I have noticed the updateEvents() function in joint.shapes.uml.State element, and tried that function, but it does not work fine. What does this function do?
Thanks!
This is because you're changing the events array in place. When you call .set() later on, the internal mechanism of set() thinks it is exactly the same object - the reference is the same - in other words, the === operator between the object passed and the one that has been previously stored returns true.

Without jQuery, make a "show more" "show less" a href

I know this is fairly straight forward using jQuery but for college assignment need to insert new paragraphs on clicking more link then remove them on clicking less links - we are not to use css or jQuery so my code so far looks like this - the insert works but the remove less() function doesn't any ideas why (even tried simple alert from teh less function and the return false on the a href doesn't work redirecting page to no javascript default.
window.onload= function()
{
var href = document.getElementById("more");
href.setAttribute("onclick","more(); return false;");
var more = document.getElementById("more");
more.onclick = function more()
{
var para1 = document.createElement("p");
para1.setAttribute("id", "para1");
var para1Cont = document.createTextNode("my text block 1");
para1.appendChild(para1Cont);
var more = document.getElementById("more");
more.parentNode.insertBefore(para1,more);
var para2 = document.createElement("p");
para2.setAttribute("id", "para2");
var para2Cont = document.createTextNode("My text block 2");
para2.appendChild(para2Cont);
more.parentNode.insertBefore(para2,more);
var toLess = more.setAttribute("id", "less");
var less = document.getElementById("less");
less.setAttribute("onclick", "less(); return false;");
less.innerHTML ="click here for less";
return false;
};
var less = document.getElementById("less");
less.onclick = function less()
{
var para1 = document.getElementById("para1");
var para2 = document.getElementById("para2");
alert("fr");
alert( para1.innerHTML);
para1.parentNode.removeChild(para1);
para2.parentNode.removeChild(para2);
var less = document.getElementById("less");
var toMore = less.setAttribute("id", "more");
var more = document.getElementById("more");
more.setAttribute("onclick", "more(); return false;");
more.innerHTML ="click here for more";
return false;
};
};
and the html code
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<title>Help meeeee</title>
<link rel="stylesheet" type="text/css" href="styles/style.css">
<link href="scripts/mystyles.css" rel="stylesheet" type="text/css" />
</head>
<body>
<div id="header">
<h1>test page</h1>
</div>
<div id="content">
click for more
</div>
<script type="text/javascript" src="scripts/myscript.js"></script>
</body>
Ok, You've got a lot of learning to do. I don't mean that in a bad way, but here's a vanillaJS example of how I'd tackle this:
window.addEventListener('load',function l()
{//use addEventListener, to avoid mem-leaks
"use strict";//for JSLint
var more = document.getElementById('more'),
less = document.getElementById('less'),
div = more.parentNode,//3 DOM reference, to be used by event handlers
added = [],//keep references to added elements, use as stack
rmHandle = function(e)
{//callback definition, don't bind unless less link should be usable
var rm = added.pop();
rm.parentNode.removeChild(rm);
if (added.length === 0)
{
less.removeEventListener('click', rmHandle, false);
}
e.preventDefault();
e.stopPropagation();
};
more.addEventListener('click',function(e)
{//add node:
var newP, count = added.length;
e.preventDefault();
e.stopPropagation();
if (count === 0)
{//bind less event handler here
less.addEventListener('click', rmHandle, false);
}
++count;
newP = document.createElement('p');//create node
newP.setAttribute('id','param'+count);//set id
newP.appendChild(document.createTextNode('New Paragraph #'+count));//add txt content
added.push(newP);//keep reference to node
div.insertBefore(newP, less);//append at end...
},false);
window.removeEventListener('load',l,false);//unbind load handler, this is the leak in IE
}, false);
Now, this by itself is a bit meaningless, so I've gone ahead and set up this fiddle
There are quite a few things left to be done (ie an unload event handler, hide the less link etc...)
Some clarification to help you understand the code:
addEventListener: Instead of setting attibutes, or directly binding event handlers using onload or onclick, I'm adding event listeners. These have the benefit of keeping everything JS-side. You're using setAttribute('onclick'...) somewhere in your code. This sets an attribute in the DOM, that refers back to JS. This is considered bad practice, and quite out-dated.
l-callback. My main callback (window.addEventListener('load', function l()... is called l. In this function, I query the DOM three times (sort of) and I assign the references to these DOM nodes to a variable: more, less and div. Next, I declare an array, to hold all nodes I'll be creating. A sort of stack, so I never need to query the dom to get a reference to those nodes I've created.
I also declare a function (rmHandle), which will handle the clicks on the less link. Because I declare this function within the scope of l, the function has access to all variables I previously declared (less, more, added). Again: I need never query the DOM...
more.addEventListener: This link has to work from the off, so I'm attaching my event listener to this DOM node on load.
no return false: Your question suggests that you know of/have used jQuery. return false in jQ and return false in JavaScript are not the same thing. if you attach an event handler to a form in VanillaJS return false might leave you gobsmacked, because at times: the form will still be submitted. Read about the W3C event model: the capturing and bubbling phases. quirksmode.org is a good resource for details. You'll understand why I'm calling these methods explicitly soon enough.
document.createTextNode: Now I will admit to using innerHTML every now and then, too. But since you're learning, I might aswell point out that innerHTML is not standard, the official standard is to use createTextNode.
At the end I remove the load event listener Because IE tends to leak memory if you don't. Then, the callback goes out of scope and there's nothing you can do about it. So everything can be flagged for GC, and there is no way anything can leak memory, still...
Edit:
I will admit that, listing up a few list-items, of which one just briefly touches on JS's way of resolving variable names in nested scopes isn't quite clear enough. It wasn't for me when I first started learning about closures, and it certainly isn't enough to explain why the code I posted is going to outperform yours by quite a bit.
So if you're up to it, I'm going to explain this a bit more, using a excerpt from your code, and walk you through a clean-up review:
var less = document.getElementById("less");
less.onclick = function less()
{
var para1 = document.getElementById("para1");
var para2 = document.getElementById("para2");
alert("fr");
alert( para1.innerHTML);
para1.parentNode.removeChild(para1);
para2.parentNode.removeChild(para2);
var less = document.getElementById("less");
var toMore = less.setAttribute("id", "more");
var more = document.getElementById("more");
more.setAttribute("onclick", "more(); return false;");
more.innerHTML ="click here for more";
return false;
};
This code should look familiar to you (it's copy-pasted from your question after all). Now why would I change this? First off: the DOM API (not part of JS BTW) is slow, clunky, illogical and a main source of frustration and using it too much kills woodland critters. In this snippet, we see this, though:
var less = document.getElementById("less");
less.onclick = function less()
{
//...
var less = document.getElementById("less");
}
So the name less is being used 3 times in an assignment context. Two of those assignments involve a DOM query. Not just a query, but exactly the same query: document.getElementById("less");! It's often said that one of the rules for writing good code is Do Not Repeat Yourself.
Another thing you might have heard is that, even when using loosely typed languages, it's not a bad idea to not assign different types to one variable. You're doing just that, though when you write function less(){}. Apart from a few (at times significant, but that's for some other time) semantic differences, this is basically the same as doing:
var less = function(){};
Each of these assignments is masking the previous one. If wou would've written:
var less = document.getElementById("less");
less.onclick = function less_func()
{
console.log(less);//logs a dom reference!
};
//or even:
less.onclick = function()
{//anonymous or lambda functions are valid... and quite common, too
console.log(less);
};
You wouldn't need that second DOM query witing the onclick function at all. This is because if JS's way of trying to resolve all variables to a previously declared variable. Consider this:
var evilGlobal = 'Do not use Globals';
function()
{
var goodLocal = 'Declared in function',
funcVar = function()
{
console.log(goodLocal);
console.log(evilGlobal);
},
func2 = function goodLocal(evilGlobal)
{
console.log(goodLocal);
console.log(evilGlobal);
console.log(funcVar());
};
funcVar();//logs Declared in function and Do not use Globals
func2();//logs itself (function), and undefined and then same as above
func2(goodLocal);//logs itself, Declared in Function and the same as funcVar
}
How does this come about? within funcVar it's fairly simple:
console.log(goodLocal);//<-- JS looks inside funcVar's function scope for var goodLocal
//not found? JS looks in the outer scope, that of the anonymous function that starts
//with var goodLocal = 'Declared in Function'
//This is the var used
The same applies to console.log(evilGlobal). Only this time, JS scans funcVar's scope, the anonymous function's scope and the global namespace. Why shouldn't you use globals? well, they're clearly slower, they can change state because functions can access them freely, and they clog the memory (the garbage collector only frees what is no longer referenced anywhere. The global namespace is always accessible).
The second case is a tad trickier, but not by much:
function goodLocal()//the goodLocal name is defined as the function!
this name is masks the variable in the outer scope. JS starts scanning the local scope, and finds goodLocal to be pointing to the function. It never checks the outer scope, so it never sees the goodLocal var in the parent function.
The same applies to evilGlobal:
function goodLocal(evilGlobal)
An argument is a variable, declared in the scope of the function. JS will never scan the global ns, because both names can be resolves localy, except for:
console.log(funcVar());
This will result in a scope scan of the parent function, which declares the funcVar variable, and assigns the previously discussed function to it. This function will still behave no different, as the function is called in its own scope/context.
Call contexts are quite tricky, too, so I'm going to gloss over this for a moment.
Back to your code: the other statements are actually repetitions of stuff you've written before, too: var para1 and var para2 are redundant, if you just keep them accessible in the outer scope.
Ah well, just keep reading, and keep learning, you'll get it soon enough...

Raphael.js - registering multiple events to element

my problem is that I need handle multiple events for rectangle. That sound simple,
for example this works
node.click(function(e){
click(); // this is function defined in same scope, it works ok
});
node.mouseout(function(e){
mouseout();
});
But, I want to automatize this, so it should looks like this:
var events = new Array("click", "mouseout");
for(var i in events){
node[events[i]](function(e){
events[i](); /*THIS is problem, no matter if it is click or mouseout
this always fires function with same name as last item
in events array (in this case mouseout)
*/
}
}
Do you have any idea why a how I should solve it?
Your handlers created in a loop are sharing a variable. By the time they are called, the variable is the last value in the loop.
You have to use a technique I call "freezing your closures" so that each handler gets a separate copy of the shared variable. In your case, the shared variable that changes is i
Your other problem is that you want to call your functions "click/mouseout" from a string, so you have to get a handle to the function, right now your code is attempting to call "hello"() which does not work
Your last problems (but not a bug yet) are that you shouldn't use the Array constructor and you shouldn't use a for in loop to iterate over arrays.
function createHandler(eventName) {
return function(e) {
window[eventName]();
}
}
var events = ["click", "mouseout"];
for(var i=0; i < events.length; i++){
node[events[i]](createHandler(events[i]));
}
The above example is easier to comprehend but you could use self invoking anonymous functions to do the same thing
var events = ["click", "mouseout"];
for(var i=0; i < events.length; i++){
node[events[i]]((function(eventName){
return function(e) {
window[eventName]();
};
})(events[i]));
}

Avoiding duplicate objects in JavaScript dojo

I'm creating an object onfocus of an DOM Element to handle various subsequent events. However, after the element looses focus, I need to destroy the object somehow, so that future focus on the element creates a fresh object with no reference to the original.
Right now, this issue I'm having is that subsequent clicks create another object and all the functions are called twice. Click again, three times. Etc. The code below shows an example of the click event and object creation.
Any help would be appreciated.
var videoTopicsHandler = DojoOn(videoTopicsInput, 'focus', function(e){
dropKeyPress(this, 'video-topics');
});
var dropKeyPress = function(input, ulId, scroll) {
var handler;
obj = new dropDownObj(ulId, scroll);
obj.attachEvents(obj, handler, input);
};
Try this:
var obj = null;
var dropKeyPress = function(input, ulId, scroll) {
var handler;
if(obj !== null) obj.destroyRecursively();
obj = new dropDownObj(ulId, scroll);
obj.attachEvents(obj, handler, input);
};
Instead of deleting, you may want to cache those objects you've created and pick them up from cache if it is already there when element regains focus. Pros: faster, no need to regenerate objects on each re-focus and destroy them; cons: unused objects will take memory, but this could be irrelevant if you either go to next page fast enough, or there's small number of those object and re-focusing happens often enough so you'll need them anyway.

Categories

Resources