10

How do I completely unbind inline javascript events from their HTML elements?

I've tried:

  • undelegating the event from the body element
  • unbinding the event from the element
  • and even removing the event attribute from the HTML element

To my surprise at least, only removing the onchange attribute (.removeAttr('onchange')) was able to prevent the event from firing again.

<input type="text" onchange="validateString(this)"></input>

I know this is possible with delegates and that's probably the best way to go, but just play along here. This example is purely hypothetical just for the sake of proposing the question.


So the hypothetical situation is this:

I'm writing a javascript validation library that has javascript events tied to input fields via inline HTML attributes like so:

<input type="text" onchange="validateString(this)"></input>

But, I'd like to make the library a little better by unbinding my events, so that people working with this library in a single-page application don't have to manage my event handlers and so that they don't have to clutter their code at all by wiring up input events to functions in my hypothetical validation library... whatever. None of that's true, but it seems like a decent usecase.

Here's the "sample" code of Hypothetical Validation Library.js:

http://jsfiddle.net/CoryDanielson/jwTTf/


To test, just type in the textbox and then click elsewhere to fire the change event. Do this with the web inspector open and recording on the Timeline tab. Highlight the region of the timeline that correlates to when you've fired the change event (fire the change event multiple times) and you'll see the event listeners (in the window below) increase by 100 on each change event. If managed & removed properly, each event listener would be properly removed before rendering a new input, but I have not found a way to properly do that with inline javascript events.

What that code does is this:

  1. onChange, the input element triggers a validation function
  2. That function validates the input and colors the border if successful
  3. Then after 1 second (to demonstrate the memory leak) the input element is replaced with identical HTML 100 times in a row without unbinding the change event (because I don't know how to do that.. that's the problem here). This simulates changing the view within a single-page app. This creates 100 new eventListeners in the DOM, which is visible through the web inspector.

    • Interesting Note. $('input').removeAttr('onchange'); will actually prevent the onchange event from being fired in the future, but does not garbage collect the eventListener/DOM stuff that is visible in the web inspector.

This screenshot is after change event fires 3 times. Each time, 100 new DOM nodes are rendered with identical HTML and I've attempted to unbind the onchange event from each node before replacing the HTML.

enter image description here


Update: I came back to this question and just did a quick little test using the JSFiddle to make sure that the answer was valid. I ran the 'test' dozens of times and then waited -- sure enough, the GC came through and took care of business.

enter image description here

Cory Danielson
  • 14,314
  • 3
  • 44
  • 51

2 Answers2

3

I don't think you have anything to worry about. Although the memory can no longer be referenced and will eventually be garbage collected, it still shows up in the Web Inspector memory window. The memory will be garbage collected when the GC decides to garbage collect it (e.g., when the browser is low on memory or after some fixed time). The details are up to the GC implementer. You can verify this by just clicking the "Collect Garbage" button at the bottom of the Web Insepctor window. I'm running Chrome 23 and after I enter text in your validation box about 5 or 6 times, the memory usage comes crashing down, apparently due to garbage collection.

This phenomenon is not specific to inline events. I saw a similar pattern just by repeatedly allocating a large array and then overwriting the reference to that large array, leaving lots of orphaned memory for GC. Memory ramps up for a while, then the GC kicks in and does its job.

Will Nelson
  • 966
  • 8
  • 13
  • Hmm, this very well may be the best answer to the question. I did notice that it would eventually get garbage collected, but wondered if there was a way to explicitly handle that. I've noticed a lot of arrays floating around memory, especially when using Backbone and saving views into an array for reference. – Cory Danielson Feb 09 '13 at 08:02
  • There's no way to explicitly trigger the GC, in general. (See [this question](http://stackoverflow.com/questions/3034179/javascript-force-gc-collection-forcefully-free-object).) If you enter text in your text input repeatedly, do you eventually see the GC kick in and memory usage crash? That's what I see after about 5 or 6 entries. If you see the same thing, I'd chalk this up to the GC being a bit slow to react and keep your eyes out for more evidence of a genuine leak. – Will Nelson Feb 09 '13 at 08:10
0

My first sggestion would have been to use off('change') but it seems you've already tried that. It's possible that the reason it's not working is because the handler wasn't attached with .on('change'). I don't know too much about how jQuery handles listener like this internally, but try attaching with .on('change', function ()... or .bind('change', function ()... instead.

insertusernamehere
  • 23,204
  • 9
  • 87
  • 126
Echilon
  • 10,064
  • 33
  • 131
  • 217
  • Yes, explicitly attaching the listener would definitely allow for unbinding later on, but I was more focused on this edge-case situation. – Cory Danielson Feb 09 '13 at 08:04