And therefore adding a bit of computing load?
For those of you unfamiliar with the .one() jquery function it basically triggers an event just once. Such as if you wanted to add a div on the first time a page is scrolled.
To bring background to the matter, I came across this question:
How to alert when scroll page only first time using javascript?
I have been in projects where I had to add hundreds or thousands of events, so for me it’s always very important to optimize computing power, plus, I am a curious person so I just need to know.
One of the answers where the guy uses vanilla javascript is basically an endless loop where you switch a boolean on the first instance and basically have to continually enter the function to see if it has been already triggered.
var xxx;
$(window).scroll(function () {
if(!xxx)
{
xxx = true;
var div = $("#myDiv");
alert(div.height());
}
});
My idea is that jquery being already heavy on the page it probably just performs this same action under the hood, but I would like to be completely certain as for my future implementations.
No. jQuery's .one works similarly to, for example:
calling addEventListener, and then, in the callback, calling removeEventListener
calling addEventListener with { once: true } in the options object
in jQuery, like calling .on, and then, in the callback, calling .off
Once the listener runs once, it's de-attached; no further logic takes place when the event occurs in the future, because the listener is no longer connected at all.
So .one is very light on computing resources, even if you add lots and lots of .ones.
You can see the source code of one here:
if (one === 1) {
origFn = fn;
fn = function (event) {
// Can use an empty set, since event contains the info
jQuery().off(event); // <-------------------------------------------------
return origFn.apply(this, arguments);
};
// Use same guid so caller can remove using origFn
fn.guid = origFn.guid || (origFn.guid = jQuery.guid++);
}
return elem.each(function () {
jQuery.event.add(this, types, fn, data, selector);
});
where jQuery() returns a jQuery collection containing elements matching the current selector. When one is called, the callback is wrapped in another that calls .off as soon as the function is executed.
The sample code in the answer you linked to is quite inefficient, and should not be used, especially for scroll events, which fire very frequently.
Related
I am currently writing a program in JS using jQuery, which is basically a checkers game.
I am using jQuery's .on() and .off() functions to create events for each of the pieces. What happens is that the program will loop through each of the pieces and will set a function to be called when the piece is clicked. This function will then show the player the available moves that the piece can make.
This is setup using a for-loop and this code:
$("#" + String(playerPositions[i])).on('click', function() {movePiece(validMoves, this)});
This passes the valid moves of that piece as well as the id of that piece to the movePiece function which then deals with highlighting the moves.
The problem lies in my "clean up" function, where I want to remove the onClick handler from all the pieces once a move is made. I use this code:
var elements = $('.' + classToClean);
//clean off the onclick
elements.off("click"); <-- this doesn't work
//clean off the classes
elements.removeClass(classToClean);
The strange thing is that a) the .removeClass function works perfectly, and b) the onClick attribute only is removed from the piece that I have just moved.
I have tried using attaching an empty function to the piece, but this did not work. I also cannot use $('.validPieces').on('click', function () ... ) because I need to pass variables unique to the piece with each piece's onclick.
Thanks in advance for any help, and I apologise about the wall of text but I wanted to make sure everything was clear.
Using .off('click') should remove all event handlers of that type. If that doesn't work it is likely the element(s) you are removing from don't match the ones they were attached to.
If that removes more than you want, you will need to include a reference to your handler in the .off() call. To preserve the different validMoves variable for each call you will need to use a closure:
function move(validMoves) {
return function() {
movePiece(validMoves, this);
}
}
// within your for loop
keepMoveFn[i] = move(validMoves);
$("#" + String(playerPositions[i])).on('click', keepMoveFn[i] );
// elsewhere in your code:
//clean off the onclicks
keepMoveFn.forEach(function(fn) {
el.off("click", fn );
}
Note that you will need to either keep a reference to the move function or have access to it when you call the .off() function. In the snippet above I assume you are keeping an array of functions that you can then later iterate to remove the click events.
I have a situation where different elements need to be re-sized by JavaScript when the window is re-sized.
Currently for each element I am simply attaching a new event like so:
window.addEventListener('resize',function(){ self.resize(MyEl);}, false );
I'm starting to think this isn't a smart idea, because say i have to resize 50 elements, i am currently attaching 50 events (gross exaggeration, but you can see how it isn't a smart design). Not to mention, when i remove the element, the event is still there!
So i am wondering what would be a better way to handle the resize event so it will then process different resize functions that i define but can equally remove said functions when they are no longer relevant.
What is considered a good approach for something like this so i then only need one event attached.
I would use an object to map an element to function. For example:
var toResize = {'#banner': self.bigResizer, '.ads': self.smallResizer};
Then you can easily dynamically add/remove elements and their resize function.
toResize['#footer'] = self.bigResizer;
delete toResize['#banner'];
Then in your event handler you'd do something like:
for (var sel in toResize) {
if (toResize[sel]) {
toResize[sel].call(self, document.querySelector(sel));
}
}
I think, adding separate events for each element is the correct approach compared to what I will explain below. The reason is that browser will run the event function when its time comes, so the browser won't freeze.
Also, I am sure you know it already, you can remove the event function if it was a single function instead of anonymous function. So, turning the system into that style would be helpful.
Another approach, but this might freeze your browser whenever the window is resized.
Define an array. Each item of array is an htmlElement.
var elementsToBeResized = [];
Define a single function that receives the resize event for elements.
function element_resize( elIndex ){
var htmlElement = elementsToBeResized[ elIndex ];
if( !document.body.contains( htmlElement ) ){
elementsToBeResized[ elIndex ] = null;
return;
}
// ... do your resizing things ...
}
When window is resized, call a function that loops over the array elementsToBeResized. And instead of adding new event, either replace a null item in elementsToBeResized, or append it.
But do not forget, because all events are called sequentially without a break, it might create freezing issue as I am telling third time.
You can simply resize all the elements in one event handler.
The list of elements would need to be maintained in global object of some form though.
Using jQuery, for example:
var $all = $('div')
$( window ).resize( function(){
$all.each(function () {
console.log($(this).attr('class'));
// individual element resize logic goes here e.g.
// $(this).myresizefunc();
})
});
jsfiddle here:https://jsfiddle.net/jsheridan390/aLdap6j8/
I want to disable a whole bunch of objects on the page, and then re-enable them later. Since some of them are tags rather than buttons, I disable them by removing their onclick attr. I've tried to store the old handler in a .data(), but unfortunately when I attempt to restore them with $(obj).attr('onclick',$(obj).data('onclick')), it calls the function rather than restoring it to the attribute. And if I try to store it in a different attribute instead of a data, it doesn't store the function, it stores the return value for the function.
Is there any way to accomplish this without re-writing every tag and every onclick handler on my page?
if( doEnable) {
$(obj).attr('href', $(obj).data('href'));
$(obj).attr('onclick', $(obj).data('onclick'));
$(obj).removeClass(EIS.config.classes.disabled);
$(obj).show();
}
else {
// Save the things you're going to remove
$(obj).data('onclick', $(obj).attr('onclick'));
$(obj).data('href', $(obj).attr('href'));
$(obj).prop("href", null);
$(obj).prop("onclick", null);
$(obj).addClass(EIS.config.classes.disabled);
$(obj).show();
}
By the way, this code seems to work fine in Chrome and Firefox, but only sometimes in IE8 and never in IE6. Unfortunately the client tests first in IE6.
$(obj).attr('onclick', ...
is ambiguous, has results that differ in different versions of jQuery and different browsers. It probably doesn't do what you want. You should avoid using attr on event handlers.
The problem is the disconnect between the onclick attribute and the onclick property. jQuery has tried to brush the difference between an attribute and a property under the carpet in the past, using attr to access both, but they're quite different. This was changed in jQuery 1.6, and partially reverted in 1.6.1, to widespread controversy, confusion and incompatibility.
For many properties, the values of an attribute and the corresponding DOM property are the same; for others, including all properties that aren't strings, they aren't. Event handlers certainly aren't: the property is a Function object, whereas the string attribute might be (a) the original string of the onclick="..." attribute in the HTML, (b) nothing (if the onclick was assigned from script to be a Function object) or (c) unavailable (in older IE).
To access the event handler Function property, use prop() in jQuery 1.6:
$(obj).data('onclick', $(obj).prop('onclick'));
...
$(obj).prop('onclick', $(obj).data('onclick'));
or just use plain old JavaScript which is actually simpler and more readable; jQuery wins you nothing here.
obj._onclick= obj.onclick;
...
obj.onclick= obj._onclick;
Either way this is not going to reliably ‘disable’ elements since they can (and very likely will, if you're using jQuery) have other event listeners registered on them, using addEventListener/attachEvent rather than the old-school event handler interfaces.
It looks like saving a function via .data() works just fine:
var f1 = function() { console.log('invoked'); };
$('a').data('func', f1)
var f2 = $('a').data('func'); // 'invoked' is not printed
f1 === f2 // true
so how are you storing the function via .data? if you're doing something like
a = $('a');
a.data('onclick', a.click()); // click handler is invoked here
then you're actually invoking the click handler(s) prematurely, and storing the return value with .data().
--edit--
it appears that .attr(function) invokes the passed function. This is a feature of jQuery. I'd suggest using jQuery's .click() method to attach the function as a click handler.
a = $('a');
a.each(function() {
this.data('onclick', handler_fn);
this.bind('click', handler_fn);
});
// later
a.each(function() {
this.unbind('click');
});
// even later
a.each(function() {
this.bind('click', this.data('onclick'));
});
What about binding the event in jQuery instead of setting the onclick attribute?
$(obj).click($(obj).data('onclick'));
Can we see the code that you use to set the data attribute?
When working with content loaded asynchronously is there any difference from a performance point of view between:
// .live()
$('#mybutton').live('click', function(e){ doSomething(); });
and manually bind() the events we need every time after the content has been loaded:
// manual bind every time
$.ajax({
url: url,
success: function(data){
mycontainer.html(data); // data contains #mybutton
$('#mybutton').click(function(e){ doSomething(); });
}
});
?
There are different costs, let's look at them:
$('#mybutton').live('click', function(e){ doSomething(); });
There are 2 main costs here:
The #mybutton selector needs to run immediately for no reason (the result is thrown away, we just wanted the selector anyway...we're binding to document). In this case it's an #id selector so that's a very low cost...in other cases it's not cheap and very wasteful (for example [attr=something]).
Every click that bubbles up to document now has to be checked against this selector, a per-click evaluation cost, this varies with the number of clicks you expect.
Now let's look at the other method:
$('#mybutton').click(function(e){ doSomething(); });
There are 2 main costs here as well:
The #mybutton selector runs, but only once per ajax request. However, we're not wasting it, we're using the results.
The click handler is bound to an actual element, rather than document, so there's a binding cost each time it runs, rather than once
However, there's no per-click cost and the selector call itself isn't wasted...so it's better overall, since you're using an ID, this isn't true in other cases.
In your case, since you're dealing with an ID (and guaranteed a single element), this is much cheaper:
$('#mybutton').click(function(e){ doSomething(); });
In other cases, where you're binding hundreds of elements, .live() is the clear winner, though .delegate() would be even better.
Probably a little, but I wouldn't worry about it. To me the .live() method looks much easier to maintain, so I would use that. As long as nothing's going painfully slow there's no need to worry about performance in JavaScript.
From the looks of your success function, you're attaching an event because that element is now available in your html? Is that so?
If that is the case, then if the function called via the click is always the same then you can use 'live'. Live lets you bind to events that don't yet exist. So you can put this in even before your document.ready. Then as the ajax updates your main document, that event should always work. You won't need to assign it every time.
So you get the performance benefit of not having to do something every time you return from an ajax call, you do the setup without relying on document.ready and its guaranteed to work.
HTH.
(Note: I'm using jQuery below, but the question is really a general JavaScript one.)
Say I've got a div#formsection whose contents are repeatedly updated using AJAX, like this:
var formSection = $('div#formsection');
var newContents = $.get(/* URL for next section */);
formSection.html(newContents);
Whenever I update this div, I trigger a custom event, which binds event handlers to some of the newly-added elements, like this:
// When the first section of the form is loaded, this runs...
formSection.find('select#phonenumber').change(function(){/* stuff */});
...
// ... when the second section of the form is loaded, this runs...
formSection.find('input#foo').focus(function(){/* stuff */});
So: I'm binding event handlers to some DOM nodes, then later, deleting those DOM nodes and inserting new ones (html() does that) and binding event handlers to the new DOM nodes.
Are my event handlers deleted along with the DOM nodes they're bound to? In other words, as I load new sections, are lots of useless event handlers piling up in the browser memory, waiting for events on DOM nodes that no longer exist, or are they cleared out when their DOM nodes are deleted?
Bonus question: how can test this myself?
Event handler functions are subject to the same Garbage Collection that other variables are. That means they will be removed from memory when the interpreter determines that there is no possible means to obtain a reference to the function. Simply deleting a node however does not guarantee garbage collection. For instance, take this node and associated event handler
var node = document.getElementById('test');
node.onclick = function() { alert('hai') };
Now lets remove the node from the DOM
node.parentNode.removeChild(node);
So node will no longer be visible on your website, but it clearly still exists in memory, as does the event handler
node.onclick(); //alerts hai
As long as the reference to node is still accessible somehow, it's associated properties (of which onclick is one) will remain intact.
Now let's try it without creating a dangling variable
document.getElementById('test').onclick = function() { alert('hai'); }
document.getElementById('test').parentNode.removeChild(document.getElementById('test'));
In this case, there seems to be no further way to access the DOM node #test, so when a garbage collection cycle is run, the onclick handler should be removed from memory.
But this is a very simple case. Javascript's use of closures can greatly complicate the determination of garbage collectability. Lets try binding a slightly more complex event handler function to onclick
document.getElementById('test').onclick = function() {
var i = 0;
setInterval(function() {
console.log(i++);
}, 1000);
this.parentNode.removeChild(this);
};
So when you click on #test, the element will instantly be removed, however one second later, and every second afterwards, you will see an incremented number printed to your console. The node is removed, and no further reference to it is possible, yet it seems parts of it remain. In this case the event handler function itself is likely not retained in memory but the scope it created is.
So the answer I guess is; it depends. If there are dangling, accessible references to deleted DOM nodes, their associated event handlers will still reside in memory, along with the rest of their properties. Even if this is not the case, the scope created by the event handler functions might still be in use and in memory.
In most cases (and happily ignoring IE6) it is best to just trust the Garbage Collector to do its job, Javascript is not C after all. However, in cases like the last example, it is important to write destructor functions of some sort to implicitly shut down functionality.
jQuery goes to great lengths to avoid memory leaks when removing elements from the DOM. As long as you're using jQuery to delete DOM nodes, removal of event handlers and extra data should be handled by jQuery. I would highly recommend reading John Resig's Secrets of a JavaScript Ninja as he goes into great detail on potential leaks in different browsers and how JavaScript libraries like jQuery get around these issues. If you're not using jQuery, you definitely have to worry about leaking memory through orphaned event handlers when deleting DOM nodes.
You may need to remove those event handlers.
Javascript memory leaks after unloading a web page
In our code, which is not based on jQuery, but some prototype deviant, we have initializers and destructors in our classes. We found it's absolutely essential to remove event handlers from DOM objects when we destroy not only our application but also individual widgets during runtime.
Otherwise we end up with memory leaks in IE.
It's surprisingly easy to get memory leaks in IE - even when we unload the page, we must be sure the application "shuts down" cleanly, tidying away everything - or the IE process will grow over time.
Edit: To do this properly we have an event observer on window for the unload event. When that event comes, our chain of destructors is called to properly clean up every object.
And some sample code:
/**
* #constructs
*/
initialize: function () {
// call superclass
MyCompany.Control.prototype.initialize.apply(this, arguments);
this.register(MyCompany.Events.ID_CHANGED, this.onIdChanged);
this.register(MyCompany.Events.FLASHMAPSV_UPDATE, this.onFlashmapSvUpdate);
},
destroy: function () {
if (this.overMap) {
this.overMap.destroy();
}
this.unregister(MyCompany.Events.ID_CHANGED, this.onIdChanged);
this.unregister(MyCompany.Events.FLASHMAPSV_UPDATE, this.onFlashmapSvUpdate);
// call superclass
MyCompany.Control.prototype.destroy.apply(this, arguments);
},
Not necessarily
The documentation on jQuery's empty() method both answers my question and gives me a solution to my problem. It says:
To avoid memory leaks, jQuery removes
other constructs such as data and
event handlers from the child elements
before removing the elements
themselves.
So: 1) if we didn't do this explicitly, we'd get memory leaks, and 2) by using empty(), I can avoid this.
Therefore, I should do this:
formSection.empty();
formSection.html(newContents);
It's still not clear to me whether .html() would take care of this by itself, but one extra line to be sure doesn't bother me.
I wanted to know myself so after a little test, I think the answer is yes.
removeEvent is called when you .remove() something from the DOM.
If you want see it yourself you can try this and follow the code by setting a breakpoint. (I was using jquery 1.8.1)
Add a new div first:
$('body').append('<div id="test"></div>')
Check $.cache to make sure there is no events attached to it. (it should be the last object)
Attach a click event to it:
$('#test').on('click',function(e) {console.log("clicked")});
Test it and see a new object in $.cache:
$('#test').click()
Remove it and you can see the object in $.cache is gone as well:
$('#test').remove()