removeEventListener en funciones anónimas en JavaScript

102

Tengo un objeto que tiene métodos. Estos métodos se colocan en el objeto dentro de una función anónima. Se parece a esto:

var t = {};
window.document.addEventListener("keydown", function(e) {
    t.scroll = function(x, y) {
        window.scrollBy(x, y);
    };
    t.scrollTo = function(x, y) {
        window.scrollTo(x, y);
    };
});  

(hay mucho más código, pero esto es suficiente para mostrar el problema)

Ahora quiero detener el detector de eventos en algunos casos. Por lo tanto, estoy tratando de hacer un removeEventListener pero no puedo averiguar cómo hacerlo. He leído en otras preguntas que no es posible llamar a removeEventListener en funciones anónimas, pero ¿es este también el caso en esta situación?

Tengo un método creado dentro de la función anónima y, por lo tanto, pensé que era posible. Se ve como esto:

t.disable = function() {
    window.document.removeEventListener("keydown", this, false);
}

¿Por qué no puedo hacer esto?

¿Hay alguna otra (buena) forma de hacer esto?

Información adicional; esto solo tiene que funcionar en Safari, de ahí la falta de compatibilidad con IE.

bitkid
fuente
¿Por qué no guardar esta función? El controlador de eventos puede no ser una función anónima.
kirilloid
2
Me doy cuenta de que esto es un poco tarde, pero también puede usar los métodos Node.setUserData /Node.getUserData para almacenar datos sobre un elemento. Por ejemplo, cuando necesite configurar un oyente anon (y poder eliminarlo), primero configure userdata en una función anon (Elem.setUserData('eventListener', function(e){console.log('Event fired.');}, null);y luego haga Elem.addEventListener ('event', Elem.getUserData ('eventListener'), false); ... y lo mismo para removeEventListener. Espero que puedas ver esto bien.
Chase
EDITAR: Según el comentario anterior, supongo que solo funciona en Firefox ... Acabo de probar IE8 (IE9 desconocido), Safari 5.1.2, Chrome (?), Opera 11..Sin dados
Chase
Posible duplicado de JavaScript: eliminar el detector de eventos
Heretic Monkey

Respuestas:

77

Creo que ese es el objetivo de una función anónima, carece de un nombre o una forma de referenciarla.

Si yo fuera usted, simplemente crearía una función con nombre o la pondría en una variable para que tenga una referencia a ella.

var t = {};
var handler = function(e) {
    t.scroll = function(x, y) {
        window.scrollBy(x, y);
    };
    t.scrollTo = function(x, y) {
        window.scrollTo(x, y);
    };
};
window.document.addEventListener("keydown", handler);

A continuación, puede eliminarlo

window.document.removeEventListener("keydown", handler);   
Adam Heath
fuente
3
Gracias por su respuesta. Fui con: var handler; window.document.addEventListener ("keydown", handler = function (e) {Pero lo que no entiendo es por qué "this" no hace referencia al detector de eventos. ¿No debería ser el detector de eventos un objeto?
bitkid
1
La thispalabra clave puede resultar confusa. Un buen lugar para leer sobre el tema es quirksmode.org/js/this.html
Adam Heath
Muchas gracias. Esto fue de gran ayuda.
bitkid
Estoy tratando de hacer esto para bloquear un anuncio realmente persistente en un sitio web. Sé que este es el objetivo de las funciones anónimas, pero eso no significa que no me gustaría saber cómo hacerlo.
Wyatt8740
@bitkid: dentro de una función de controlador (asumiendo que no es una función de flecha), se thisrefiere al elemento al que se agrega el oyente, no al evento en sí (que sería el parámetro e). Por tanto this === e.currentTarget. leer developer.mozilla.org/en-US/docs/Web/API/EventTarget/…
chharvey
100

si está dentro de la función real, puede usar argumentos.callee como referencia a la función. como en:

button.addEventListener('click', function() {
      ///this will execute only once
      alert('only once!');
      this.removeEventListener('click', arguments.callee);
});

EDITAR: Esto no funcionará si está trabajando en modo estricto ( "use strict";)

Otto Nascarella
fuente
2
Esto es bueno ya que conserva las ventajas de las funciones anónimas (no contamina el espacio de nombres, etc.).
bompf
3
probé esto en la aplicación WinJS, obtuve el siguiente error: "No se permite acceder a la propiedad 'callee' de un objeto de argumentos en modo estricto"
Valentin Kantor
1
@ValentinKantor: Eso porque algo en el código hay un "uso estricto"; declaración, y no puede usar callee en modo estricto.
OMA
19
Dar a la función en línea un nombre y se puede hacer referencia a ella sin tener que recurrir a arguments.callee:button.addEventListener('click', function handler() { this.removeEventListener('click', handler); });
Harry Love
4
Como se indica en Mozilla: "Advertencia: la quinta edición de ECMAScript (ES5) prohíbe el uso de debe llamarse a sí mismo ".
dude
50

Una versión de la solución de Otto Nascarella que funciona en modo estricto es:

button.addEventListener('click', function handler() {
      ///this will execute only once
      alert('only once!');
      this.removeEventListener('click', handler);
});
Melle
fuente
4
¡Hermosa solución!
Eric Norcross
2
Puede que esta no sea la forma correcta, pero es lo más fácil.
Vignesh
7
window.document.removeEventListener("keydown", getEventListeners(window.document.keydown[0].listener));  

Puede haber varias funciones anónimas, keydown 1

Advertencia: solo funciona Chrome Dev Toolsy no se puede usar en el código : enlace

Isayevskiy_Sergey
fuente
2
Gracias, has resuelto un acertijo, al menos en Chrome, por lo que muchos bromistas dijeron que era imposible. Hombre, eres como ... ¡Batman!
JasonXA
20
getEventListenersparece ser parte de las herramientas de desarrollo de Chrome, por lo que en realidad no se puede utilizar para nada más que para depurar.
DBS
1
Solo lo probé, confirmó que solo está disponible en Devtools y no cuando se incluye en un script dentro de una página.
Andrés Riofrío
5

en los navegadores modernos, puede hacer lo siguiente ...

button.addEventListener( 'click', () => {
    alert( 'only once!' );
}, { once: true } );

https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Parameters

shunryu111
fuente
Genial hasta que descubras que ninguna versión de IE ni edge <16 realmente admite esta función. Al menos en 5 años podemos usar esto ya que entonces IE (léase: debería) quedará obsoleto, Edge tomará su lugar y usará el motor webkit en lugar de su propio "EdgeHTML".
SidOfc
1
con este polyfill para entradas de DOM Nivel 4, debería estar bien npmjs.com/package/dom4
shunryu111
2

Una opción no tan anónima

element.funky = function() {
    console.log("Click!");
};
element.funky.type = "click";
element.funky.capt = false;
element.addEventListener(element.funky.type, element.funky, element.funky.capt);
// blah blah blah
element.removeEventListener(element.funky.type, element.funky, element.funky.capt);

Desde que recibí comentarios de Andy ( muy bien, pero como con muchos ejemplos, deseaba mostrar una expansión contextual de la idea ), aquí hay una exposición menos complicada :

<script id="konami" type="text/javascript" async>
    var konami = {
        ptrn: "38,38,40,40,37,39,37,39,66,65",
        kl: [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]
    };
    document.body.addEventListener( "keyup", function knm ( evt ) {
        konami.kl = konami.kl.slice( -9 );
        konami.kl.push( evt.keyCode );
        if ( konami.ptrn === konami.kl.join() ) {
            evt.target.removeEventListener( "keyup", knm, false );

            /* Although at this point we wish to remove a listener
               we could easily have had multiple "keyup" listeners
               each triggering different functions, so we MUST
               say which function we no longer wish to trigger
               rather than which listener we wish to remove.

               Normal scoping will apply to where we can mention this function
               and thus, where we can remove the listener set to trigger it. */

            document.body.classList.add( "konami" );
        }
    }, false );
    document.body.removeChild( document.getElementById( "konami" ) );
</script>

Esto permite una estructura de función efectivamente anónima, evita el uso de llamadas prácticamente obsoletas y permite una fácil eliminación.

Por cierto : la eliminación del elemento de script inmediatamente después de configurar el oyente es un lindo truco para ocultar el código que uno preferiría que no fuera tan obvio para las miradas indiscretas ( estropearía la sorpresa ;-)

Entonces el método ( más simplemente ) es:

element.addEventListener( action, function name () {
    doSomething();
    element.removeEventListener( action, name, capture );
}, capture );
Fred Gandt
fuente
2
Esto es demasiado complicado.
Ben Sinclair
@Andy Estoy de acuerdo, algo así, pero estaba tratando de mostrar que simplemente no hay forma de eliminar una función anónima. Se debe hacer referencia de alguna manera (incluso callee (es malo, M'Kay) hace referencia a la función) y, por lo tanto, se proporciona un ejemplo de solo una (otra) forma en que se puede hacer referencia a la función, y cómo se construye con partes que igualmente se puede almacenar para referencia posterior (la parte importante). Obviamente, una función verdaderamente anónima se construye sobre la marcha, por lo que también se debe saber más tarde qué acción / tipo de evento y si se utilizó la captura. De todos modos, aquí hay un método mejor :-)
Fred Gandt
Funcionó perfectamente para mí. No pude ver otra forma de pasar argumentos a la función, ya que no podía ser anónima.
nicodemus13
2

Esto no es ideal, ya que elimina todo, pero podría funcionar para sus necesidades:

z = document.querySelector('video');
z.parentNode.replaceChild(z.cloneNode(1), z);

La clonación de un nodo copia todos sus atributos y sus valores, incluidos los oyentes intrínsecos (en línea). No copia los detectores de eventos agregados usando addEventListener ()

Node.cloneNode ()

Comunidad
fuente
Esto es absolutamente brillante
Ahmad Alfy
1

JavaScript : el método addEventListener registra el oyente especificado en EventTarget (Elemento | documento | Ventana) al que se llama.

EventTarget. addEventListener ( event_type , handler_function, Bubbling | Capturing );

Mouse, eventos de teclado Prueba de ejemplo en WebConsole:

var keyboard = function(e) {
    console.log('Key_Down Code : ' + e.keyCode);
};
var mouseSimple = function(e) {
    var element = e.srcElement || e.target;
    var tagName = element.tagName || element.relatedTarget;
    console.log('Mouse Over TagName : ' + tagName);    
};
var  mouseComplex = function(e) {
    console.log('Mouse Click Code : ' + e.button);
} 

window.document.addEventListener('keydown',   keyboard,      false);
window.document.addEventListener('mouseover', mouseSimple,   false);
window.document.addEventListener('click',     mouseComplex,  false);

El método removeEventListener elimina el detector de eventos registrado previamente con EventTarget.addEventListener ().

window.document.removeEventListener('keydown',   keyboard,     false);
window.document.removeEventListener('mouseover', mouseSimple,  false);
window.document.removeEventListener('click',     mouseComplex, false);

Puedo usar

Yash
fuente
1

Para dar un enfoque más actualizado a esto:

//one-time fire
element.addEventListener('mousedown', {
  handleEvent: function (evt) {
    element.removeEventListener(evt.type, this, false);
  }
}, false);
Jonatas Walker
fuente
2
Una explicación estaría bien.
Poul Bak
1

Me he encontrado con el mismo problema y esta fue la mejor solución que pude obtener:

/*Adding the event listener (the 'mousemove' event, in this specific case)*/
element.onmousemove = function(event) {
    /*do your stuff*/
};
/*Removing the event listener*/
element.onmousemove = null;

Tenga en cuenta que solo he probado esto para el windowelemento y para el 'mousemove'evento, por lo que podría haber algunos problemas con este enfoque.

Gark García
fuente
0

Posiblemente no sea la mejor solución en términos de lo que está pidiendo. Todavía no he determinado un método eficiente para eliminar la función anónima declarada en línea con la invocación del detector de eventos.

Yo personalmente uso una variable para almacenar <target>y declarar la función fuera de la invocación del detector de eventos, por ejemplo:

const target = document.querySelector('<identifier>');

function myFunc(event) { function code; }

target.addEventListener('click', myFunc);

Luego, para eliminar el oyente:

target.removeEventListener('click', myFunc);

No es la principal recomendación que recibirá, pero para eliminar funciones anónimas, la única solución que he encontrado útil es eliminar y luego reemplazar el elemento HTML. Estoy seguro de que debe haber un método JS de vainilla mejor, pero aún no lo he visto.

Shay Connolly
fuente
0

Sé que este es un hilo bastante antiguo, pero pensé que podría poner mis dos centavos para aquellos que lo encuentren útil.

El guión (disculpas por los nombres de métodos poco creativos):

window.Listener = {
    _Active: [],
    remove: function(attached, on, callback, capture){
        for(var i = 0; i < this._Active.length; i++){
            var current = this._Active[i];
            if(current[0] === attached && current[1] === on && current[2] === callback){
                attached.removeEventListener(on, callback, (capture || false));
                return this._Active.splice(i, 1);
            }
        }
    }, removeAtIndex(i){
        if(this._Active[i]){
            var remove = this._Active[i];
            var attached = remove[0], on = remove[1], callback = remove[2];
            attached.removeEventListener(on, callback, false);
            return this._Active.splice(i, 1);
        }
    }, purge: function(){
        for(var i = 0; i < this._Active.length; i++){
            var current = this._Active[i];
            current[0].removeEventListener(current[1], current[2]);
            this._Active.splice(i, 1);
        }
    }, declare: function(attached, on, callback, capture){
        attached.addEventListener(on, callback, (capture || false));
        if(this._Active.push([attached, on, callback])){
            return this._Active.length - 1;
        }
    }
};

Y puedes usarlo así:

// declare a new onclick listener attached to the document
var clickListener = Listener.declare(document, "click" function(e){
    // on click, remove the listener and log the clicked element
    console.log(e.target);
    Listener.removeAtIndex(clickListener);
});

// completely remove all active listeners 
// (at least, ones declared via the Listener object)
Listener.purge();

// works exactly like removeEventListener
Listener.remove(element, on, callback);
GROVER.
fuente
-4
window.document.onkeydown = function(){};
Pardo
fuente
¿Por qué no = indefinido? Real rudo.
Ben Sinclair
2
Esto no eliminará ningún controlador con el que se haya registrado addEventListener.
Luc125