admin管理员组

文章数量:1295267

I have a predicament: I want to send some data with an event listener but also be able to remove the listener. Here's the standard closure approach...

var fn = function(e){method(e,data)};
el.addEventListener('click',fn,false);
el.removeEventListener('click',fn,false);

and you could remove the event, just fine. But say, the element was removed from the DOM? Then, you'd be left with the fn function sitting around. After removing a couple thousand DOM elements, it will result in something of a memory leak.

I've considered attaching a DOMNodeRemoved event handler, that would remove any left over functions/data along with the removed node. But apparently, that event isn't cross-browser patible.

The only other option I've e up with would be modifying the element's DOM. Consider...

el.MyEventData = function(e){method(e,data)};
el.addEventListener('click',el.MyEventData,false);
el.removeEventListener('click',el.MyEventData,false);

Is modifying the DOM acceptable in this situation? The only sticky part of that solution is when you try to add more than one event listener. Let's say we made a custom function to parse the adding/removing of events...

function makeEvent(fn,data){
    var dataFn = function(e){fn(e,data)};
    //create object to hold all added events
    el.myEvents = {};
    //make ID for this specific event
    var eventID = ranString();
    //add the event to the events object
    el.myEvents[eventID] = [fn,dataFn];
    //finally add the listener
    el.addEventListener('click',dataFn,false);
}
function destroyEvent(fn){
    //find all fn references
    for(var id in el.myEvents){
        if (el.myEvents[id][0] == fn){
            el.removeEventListener('click',el.myEvents[id][1],false);
            el.myEvents[id] = null;
        }
    }
}

It still modifies the DOM, as before, and certainly isn't a very elegant solution either. Does anyone know of any alternative, better method for passing data?

EDIT: So, I've looked into a little of jQuery's data/event scripts. I don't pletely understand the code, so if someone would clarify, it would be helpful. But it seems as though they use a similar method, by making some type of el.cache property, that holds event data.

I have a predicament: I want to send some data with an event listener but also be able to remove the listener. Here's the standard closure approach...

var fn = function(e){method(e,data)};
el.addEventListener('click',fn,false);
el.removeEventListener('click',fn,false);

and you could remove the event, just fine. But say, the element was removed from the DOM? Then, you'd be left with the fn function sitting around. After removing a couple thousand DOM elements, it will result in something of a memory leak.

I've considered attaching a DOMNodeRemoved event handler, that would remove any left over functions/data along with the removed node. But apparently, that event isn't cross-browser patible.

The only other option I've e up with would be modifying the element's DOM. Consider...

el.MyEventData = function(e){method(e,data)};
el.addEventListener('click',el.MyEventData,false);
el.removeEventListener('click',el.MyEventData,false);

Is modifying the DOM acceptable in this situation? The only sticky part of that solution is when you try to add more than one event listener. Let's say we made a custom function to parse the adding/removing of events...

function makeEvent(fn,data){
    var dataFn = function(e){fn(e,data)};
    //create object to hold all added events
    el.myEvents = {};
    //make ID for this specific event
    var eventID = ranString();
    //add the event to the events object
    el.myEvents[eventID] = [fn,dataFn];
    //finally add the listener
    el.addEventListener('click',dataFn,false);
}
function destroyEvent(fn){
    //find all fn references
    for(var id in el.myEvents){
        if (el.myEvents[id][0] == fn){
            el.removeEventListener('click',el.myEvents[id][1],false);
            el.myEvents[id] = null;
        }
    }
}

It still modifies the DOM, as before, and certainly isn't a very elegant solution either. Does anyone know of any alternative, better method for passing data?

EDIT: So, I've looked into a little of jQuery's data/event scripts. I don't pletely understand the code, so if someone would clarify, it would be helpful. But it seems as though they use a similar method, by making some type of el.cache property, that holds event data.

Share edited Nov 15, 2015 at 22:41 Brian Tompsett - 汤莱恩 5,89372 gold badges61 silver badges133 bronze badges asked Oct 12, 2010 at 1:06 AzmisovAzmisov 7,2698 gold badges57 silver badges79 bronze badges 2
  • the jquery event is based on the idea of dean edward, is probably the best solution to you – Sep O Sep Commented Oct 18, 2010 at 20:49
  • I've looked his script up, but it has no further way of implementing data sending than the examples I've already listed. – Azmisov Commented Oct 18, 2010 at 21:40
Add a ment  | 

6 Answers 6

Reset to default 3 +25

Considering that you use addEventListener this is not an issue as all modern garbage collectors can take care of such situations. The problem with event listeners only exists in IE's implementation (7-).

Test - 10 000 addEventListener and remove element (see Windows Task Manager)

When a DOM object contains a reference to a JavaScript object (such an event handling function), and when that JavaScript object contains a reference to that DOM object, then a cyclic structure is formed. This is not in itself a problem. At such time as there are no other references to the DOM object and the event handler, then the garbage collector (an automatic memory resource manager) will reclaim them both, allowing their space to be reallocated. The JavaScript garbage collector understands about cycles and is not confused by them.

http://www.crockford./javascript/memory/leak.html

Did you consider .delegate()?

According to your jQuery question:

Each jQ object has a data property. It does not stored inside the element itself - it's very important. jQ use general storage for all elements - jQuery.cache. So when you add anything to the element like this:

$('#myEl').data('someValue', 1);

jQ do the following:

jQuery.cache[elementUniqId]['someValue'] = 1;

So element does not contain its data object. It only have an uniq id that is allows it to access to the data recorde at the global storage. (elementUniqId is autogenerated)

jQ events are stored into the element data as well:

$('#myEl').click(function() {  first listener });
$('#myEl').mouseenter(function() {  one more listener });
$('#myEl').click(function() {  anotheer listener });

Will be stored:

jQuery.cache[elementUniqId]['events'] = {
    click: [function() { first listener }, function() { anotheer listene }],
    mouseenter: [function() { one more listener }]
};

It allows jQ to store the order of execution for all listeners attached to each event. And later, when you delete dom element, using jQuery - .remove(), jQuery loops through the jQuery.cache[elementUniqId]['events'] and remove each listener from the element, and after removes element cache record. It allows jQ to preven memory leaks

A possible solution to maybe take you in a different direction: add the function as an inline sibling of the element.

<span id='element12345'>Test</span><script 
  type='text/javascript'>function fn12345() { /* ... */ }</script>

Then, when you remove all the event listeners that you want, you can also remove the "nextSibling()" of the element you're working with.

how about a setup like this? (using IE syntax since that's what I have available right now)

<div id="clickbox" style="width: 100px; height: 100px; border: 1px solid orange;">
    click here to test</div>
<input id="Button1" type="button" value="clear handler" />
<script>

    var data = "derp1";
    var el = document.getElementById('clickbox');
    var btn = document.getElementById('Button1');

    // methods
    var method = function (e, dat2) { alert(dat2); };
    var fn = function (e) { method(e, data) };
    var remover = null;

    // attachment
    el.attachEvent('onclick', fn, false);

    (function (id, handler) {

        // handler variable is local but points to the right function
        remover = function (e) {

            if (document.getElementById(id)) {
                // remove the original listener (handler is fn)
                document.getElementById(id).detachEvent('onclick', handler, false);
                alert('detached');
            }

            // remove last reference to the original method
            handler = null;
            alert('method nulled');

            // clean up the remover method
            e.srcElement.detachEvent('onclick', remover);
            remover = null;
        };

        btn.attachEvent('onclick', remover);

    })('clickbox', fn);

    // clear the original variable but the method still exists as an event listener
    fn = null;

    // you should be able to remove the div element and any references to it 
    // without leaving any stray bits around.

    setTimeout( function() {
                     var d = document.getElementById('clickbox');
                     if (d){ d.parentNode.removeChild(d) ; }
     } , 6000 );

    el = null;
    btn = null;

</script>

I'm assuming you don't want the listener removed immediately after adding it but rather want to be able to remove it at a later time. to deal with this, the cleanup routine is given its own scope by creating an anonymous function which is immediately invoked with fn as a parameter. the anon function then has its own reference to fn maintained in the handler variable. after that, fn can be cleaned up and the remaining references to the original method exist in the listener list for your element and in the scope of the anonymous function.

within the anonymous function scope, the function remover has access to the handler variable and can use it to detach the listener. remover then detaches and clears itself so there should be nothing left with access to any version of fn/handler.

I don't have any way to verify all this right now but I think it makes sense and should hold up in modern browsers.

why not take a look at this

Binding Events To Non-DOM Objects With jQuery

http://www.bennadel./blog/1520-Binding-Events-To-Non-DOM-Objects-With-jQuery.htm

本文标签: javascriptSending data with an event listenerStack Overflow