I assume it's well understood in practice that objects which implement EventTarget include all of their supported events as properties with on prefixes in their keys. For example, Window is paired with the WindowEventHandlers mixin, allowing code like:
window.addEventListener('print', foo);
// ...or
window.onprint = foo;
But does a standard mandate that for every possible event name X there must be an associated onX property on that same EventTarget? Or is that just a convention browsers happened to follow as a historical artifact?
The HTML living standard covers this.
Excerpt:
Event handlers are exposed in two ways.
The first way, common to all event handlers, is as an event handler IDL attribute.
The second way is as an event handler content attribute. Event handlers on HTML elements and some of the event handlers on Window objects are exposed in this way.
For both of these two ways, the event handler is exposed through a name, which is a string that always starts with "on" and is followed by the name of the event for which the handler is intended.
The IDL and content attributes do not determine the taxonomy of event names, but since IDL attributes are visible as properties in JS without a setAttribute or getAttribute, then the above passage basically says that every event handler must get exposed as a property with an on-prefixed name.
That being said, the JS object that has these property names might not be the actual target of the event. The living standard cites body as one example of an element with IDL attributes for handlers targeting a corresponding Window instance... Even though the body element itself implements EventTarget.
So in general, it's your responsibility to verify the target, but you can at least count on every standard event N corresponding onN property that you can set on some EventTarget in your JavaScript source.
Related
I have a inline event handler like <div onclick="checkFunction()">. I know if I want to pass the element I use this keyword, but besides the element, I wanna pass the event because I have other elements inside this div and I wanna do some things whit it based on the event.target. What should I do?
I suggest never using onxyz-attribute-style event handlers, but if you're using them, the way you do it is to pass event:
<div onclick="checkFunction(this, event)">`
The handler's first argument is the element, the second is the event.
That style of handler executes in an environment where event is in scope, either because it's a local variable (all modern browsers) or it's a global one (old Microsoft IE) (or both, since the global is now in the specification and even Firefox added it in the end). Either way, it's in scope for the code in the quotes, so you can use it to pass the event to the event handler function as its second argument.
But that style of handler can only call global functions, which is one reason to avoid them, and certain function names will mess you up (like animate), which is another reason to avoid them. Instead, I suggest hooking up the handler with addEventListener:
<div id="some-id">`
document.getElementById("some-id").addEventListener(checkFunction);
// ...
function checkFunction(event) {
// Here you can use `this` or `event.currentTarget` to access the element,
// and `event` to access the event
// ...
}
You do not need to use an id, you just need some means of identifying the element. You can use any CSS selector with querySelector instead of using getElementById.
Given the following, common scenario:
console.log(this); // window or any parent object
$('.selector').on('click', function(event) {
console.log(this); // clicked DOM element
});
var myFunc = function() {
console.log(this); // window or parent object
}
Since version 1.3 jQuery adds the event.currentTarget when binding event handlers for which counts event.currentTarget === this, so is there actually a good reason to manipulate this and switch context? Doesn't this behaviour generally go against the unspoken rule of "don't change keyword values" (like undefined = 'not defined')?
This "feature" of jQuery makes a lot of OOP less efficient and awkward imho, when we need to either cache the original this in a variable like self or use helpers like jQuery.proxy to reassign context to event handlers.
My question: is this just a relic of early jQuery implementations kept alive or is there an actual benefit which I cannot see (except maybe the slightly more convenient way than accessing event.currentTarget to get the element...)?
Let's say you've got an object with some methods on it:
var object = {
click: function() {
alert(this.property);
},
property: "Hello World"
}
You can call object.click() and, as you'd expect, you'll get "Hello World" in the alert.
You'd like to be able to use that "click" function as an event handler:
$("button").on("click", object.click);
However you discover that that doesn't work, because jQuery invokes the "click" function with this set to the DOM node for the clicked button. This is irritating. It's also inevitable because of the semantics of JavaScript function calls.
When you call the "click" function by way of a property reference, the language arranges for this to refer to that object. That's why object.click() works. However, when you fetch the reference to the function and pass it across a function boundary (as in the call to .on()), that relationship is lost. All that the jQuery method gets is a plain unadorned function that has absolutely no inherent relationship to the original object involved in its definition.
Thus, jQuery really has only two choices. The first is that it could make explicit the fact that the function is unconnected by arranging for this to be undefined. That wouldn't be very useful however. The other choice is to pick something interesting for this, and that's what the library does. Note that the native DOM level 0 event dispatch mechanism does the same thing.
The reason is that jQuery wants to mimic how regular event handlers (ones created without jQuery or any other library) works. In regular event handlers the value of this refers to the DOM node that triggers the event if there is one.
One could in fact consider that this is an example of jQuery not manipulating built-in behavior.
I know it's not possible to bind to all DOM events and I know you can bind to multiple events by supplying a space-separated list.
But is it possible to bind to all custom events (preferably filtered by a wildcard pattern like 'abc*' or name-space)?
Edit:
To clarify, I have created some custom widgets that respond to some custom events. For example, they all handle an event called stepReset and resets their internal models.
After I've written these, I realized events don't bubble down, so the call $(body).trigger('stepReset') basically does nothing. As a result, I am considering adding an umbrella event handler on all widgets' parent elements to propagate all relevant events down.
(I know this is not an elegant solution, but I forgot to tag elements with handlers with a common class, so there's no easy way to use select them all.)
With regards to your upcoming edit, you can retrieve all bound events by accessing the object's data:
var boundEvents = $.data(document, 'events');
From here, you can iterate over the resulting object and check each property for your chosen wildcard character, or iterate over that property's array elements and check the namespace property of each.
For instance,
$.each(boundEvents, function () {
if (this.indexOf("*")) // Checks each event name for an asterisk *
alert(this);
// alerts the namespace of the first handler bound to this event name
alert(this[0].namespace);
});
If I understood you correctly, you can iterate over the special events object to get a list of custom events (including those specified in the jQuery source code). Here's an ES5 example, you will need to adapt it yourself for older browsers or use a polyfill for Object.keys:
var evts = Object.keys(jQuery.event.special).join(" ");
$("#myDiv").on(evts, function (e) {
// your code here
});
(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()
If I have the following code in two functions of an object:
add: function()
{
// create trip.
var trip = new Trip();
// add the trip using its id.
this.trips[trip.id] = trip;
},
remove: function(tripId)
{
// remove trip.
delete this.trips[tripId];
}
NOTE: The constructor for the Trip object binds a bunch of custom jQuery event handlers to itself.
Will the event handlers bound to the Trip object be automatically destroyed/cleaned up when the Trip object is deleted?
Would the same occur for a DOM node if it was removed and had event handlers bound to it?
Also I read that objects are not cleaned up by the garbage collector until all references to them no longer exist, so do the event handlers bound to the object by itself count as references and prevent the object from being cleaned up, even when I am no longer referencing it?
The event will not be deleted as jQuery maintains a central repository of all bound event handlers, and does know if or when you deleted an associated object using delete. Try this little test to confirm. (jQuery 1.4.2 only)
jsfiddle link:
// 1. a regular JS object
var root = {};
// Since we can't delete anything created with var (except on Chrome),
// we use an object property here. An array value works just as well,
// which is already the case in your example.
root.o = {};
// 2. at this point, jQuery creates an internal property
// jQuery<UNIQ_ID>, for example jQuery1277242840125 inside object o
$(root.o).bind("myEvent", function() { alert("here"); });
// 3. get the *internal* index jQuery assigned this object:
// there's only 1 property, so we just enumerate and fetch it.
var internalIndex;
for(var prop in root.o) {
internalIndex = root.o[prop];
}
// 4. delete the object
delete root.o;
// 5. query jQuery internal cache with the internal index from step 3
console.log(jQuery.cache[internalIndex].events);
Step 5 should log an array of all event types that were associated with the ex-o object, including "myEvent", and it's associated handler, so no the bound events handlers will not delete automatically. Here's what I see get logged (stripped out irrelevant properties):
▾ Object
▾ myEvent: Array (1)
▾ 0: Object
▸ handler: function () { alert("here"); }
namespace: ""
type: "myEvent"
length: 1
The object deletion, however, is not affected, and that will be deleted as expected. However, it is a hole in the wall kind of a situation since there is associated data somewhere in jQuery's cache that will remain there.
It seems that although you can bind events to plain JavaScript objects, you cannot unbind them. It appears jQuery assumes the object is a DOM node when unbinding, and throws the following error:
Uncaught TypeError: Object #<an Object> has no method 'removeEventListener'
Even the part about being able to bind events to objects is, I believe, undocumented. So you have to be a little careful on this, as the following will not work when trying to clean up the event handler references for that object:
$(object).remove()
$(object).unbind(..)
As a workaround, when cleaning up the Trip object, you can explicitly call removeData to do the job.
$(object).removeData();
As I've already mentioned, it's getting knee-deep with jQuery's internals, so you might want to look at an alternative solution, or be wary that library upgrades can easily break your code, which is not very unlikely.
As far as I know, you can only bind event handlers to nodes, or, in special cases, the window, document, etc. For DOM nodes, the event handlers will be removed. Even if they weren't, they wouldn't be able to be triggered anyway. Deleting the object will remove the event handlers associated with it. The event handlers should not prevent the object from being garbage collected.
Would the same occur for a dom node if
it was removed and had event handlers
bound to it?
this.handlerClick = function () { ... };
$(this.testDomNode).bind('click', this.handlerClick);
this.testDomNode.parentNode.removeChild(this.testDomNode);
Using the above code and testing with FireQuery in FireFox removing the dom node does not unbind the handler from the event,
it seems you have to explicitly unbind the handler before removing the dom node as follows:
$(this.testDomNode).unbind('click', this.handlerClick);
this.testDomNode.parentNode.removeChild(this.testDomNode);