removeEventListener em funções anônimas em JavaScript

102

Eu tenho um objeto que contém métodos. Esses métodos são colocados no objeto dentro de uma função anônima. Se parece com isso:

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);
    };
});  

(há muito mais código, mas isso é o suficiente para mostrar o problema)

Agora, quero interromper o ouvinte de eventos em alguns casos. Portanto, estou tentando fazer um removeEventListener, mas não consigo descobrir como fazer isso. Eu li em outras perguntas que não é possível chamar removeEventListener em funções anônimas, mas isso também acontece nesta situação?

Tenho um método em t criado dentro da função anônima e, portanto, pensei que fosse possível. Se parece com isso:

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

Por que não posso fazer isso?

Existe alguma outra (boa) maneira de fazer isso?

Informações de bônus; isso só precisa funcionar no Safari, daí a falta de suporte ao IE.

bitkid
fonte
Por que não salvar esta função? O manipulador de eventos não pode ser uma função anônima.
Kirilloid
2
Sei que é um pouco tarde, mas você também pode usar os métodos Node.setUserData /Node.getUserData para armazenar dados sobre um elemento. Por exemplo, quando você precisa definir um ouvinte anon (e ser capaz de removê-lo), primeiro defina userdata para uma função anon (Elem.setUserData('eventListener', function(e){console.log('Event fired.');}, null);e, em seguida, faça Elem.addEventListener ('event', Elem.getUserData ('eventListener'), false); ... e o mesmo para removeEventListener. Espero que você possa ver isso bem.
Chase
EDITAR: Conforme o comentário anterior, acho que só funciona no Firefox ... Acabei de experimentar o IE8 (IE9 desconhecido), Safari 5.1.2, Chrome (?), Opera 11 .. Sem dados
Chase
Possível duplicata do JavaScript: remover ouvinte de evento
Heretic Monkey

Respostas:

77

Acredito que seja o ponto de uma função anônima, falta um nome ou uma forma de referenciá-la.

Se eu fosse você, apenas criaria uma função nomeada ou a colocaria em uma variável para que você tenha uma referência a ela.

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);

Você pode então removê-lo por

window.document.removeEventListener("keydown", handler);   
Adam Heath
fonte
3
Obrigado por sua resposta. Eu fui com: var handler; window.document.addEventListener ("keydown", handler = function (e) {Mas o que eu não entendo é por que "this" não faz referência ao ouvinte de evento. O ouvinte de evento não deveria ser um objeto?
bitkid
1
A thispalavra-chave pode ser confusa. Um bom lugar para ler sobre isso é quirksmode.org/js/this.html
Adam Heath
Muito obrigado. Isso foi muito útil.
bitkid
Estou tentando fazer isso para bloquear um anúncio realmente persistente em um site. Eu sei que esse é o ponto das funções anônimas, mas isso não significa que eu não gostaria de saber como fazê-lo.
Wyatt8740
@bitkid: dentro de uma função de tratamento (assumindo que não seja uma função de seta), o thisrefere-se ao elemento ao qual o ouvinte é adicionado, não ao próprio evento (que seria o parâmetro e). Portanto this === e.currentTarget. leia developer.mozilla.org/en-US/docs/Web/API/EventTarget/…
chharvey
100

se você estiver dentro da função real, pode usar arguments.callee como referência para a função. como em:

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

EDITAR: Isso não funcionará se você estiver trabalhando no modo estrito ( "use strict";)

Otto Nascarella
fonte
2
Isso é bom, pois preserva as vantagens das funções anônimas (não poluindo o namespace etc.).
bompf
3
tentei fazer isso no aplicativo WinJS, recebi o próximo erro: "Acessar a propriedade 'callee' de um objeto de argumentos não é permitido no modo estrito"
Valentin Kantor
1
@ValentinKantor: Isso porque algo no código existe um "uso estrito"; declaração, e você não pode usar callee no modo estrito.
OMA de
19
Dê um nome à função inline e você poderá referenciá-la sem recorrer a arguments.callee:button.addEventListener('click', function handler() { this.removeEventListener('click', handler); });
Harry Love
4
Conforme declarado no Mozilla: "Aviso: A 5ª edição do ECMAScript (ES5) proíbe o uso de argumentos.callee () no modo estrito. Evite usar argumentos.callee () dando um nome às expressões de função ou use uma declaração de função onde uma função deve se chamar. "
cara,
50

Uma versão da solução de Otto Nascarella que funciona em modo estrito é:

button.addEventListener('click', function handler() {
      ///this will execute only once
      alert('only once!');
      this.removeEventListener('click', handler);
});
Melle
fonte
4
Bela solução!
Eric Norcross
2
Este pode não ser o caminho certo, mas é a coisa mais fácil.
Vignesh de
7
window.document.removeEventListener("keydown", getEventListeners(window.document.keydown[0].listener));  

Podem ser várias funções anônimas, tecla 1

Aviso: só funciona Chrome Dev Toolse não pode ser usado no código : link

Isayevskiy_Sergey
fonte
2
Obrigado, você resolveu um enigma, pelo menos no Chrome, para o que muitos curingas disseram ser impossível. Cara, você é como ... Batman!
JasonXA
20
getEventListenersparece fazer parte das ferramentas Chrome Dev, então não pode ser usado para nada além de depuração.
DBS de
1
Acabei de experimentar, confirmei que só está disponível no Devtools e não quando incluído em um script dentro de uma página.
Andres Riofrio,
5

em navegadores modernos, você pode fazer o seguinte ...

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

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

shunryu111
fonte
Esfrie até descobrir que nenhuma versão do IE nem o edge <16 realmente suporta esse recurso. Podemos usar isso pelo menos em 5 anos, já que o IE será (leia-se: deveria) obsoleto, o Edge terá seu lugar e usará o mecanismo do webkit em vez de seu próprio "EdgeHTML".
SidOfc
1
com este polyfill para entradas DOM Nível 4, você deve estar bem npmjs.com/package/dom4
shunryu111
2

Uma opção não tão 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 recebi feedback de Andy ( certo, mas como acontece com muitos exemplos, eu gostaria de mostrar uma expansão contextual da ideia ), aqui está uma exposição 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>

Isto permite uma estrutura de função efetivamente anônimo, evita o uso do praticamente obsoleto receptor e permite a remoção fácil.

Incidentalmente : a remoção do elemento de script imediatamente após definir o ouvinte é um truque engraçado para esconder o código que alguém preferiria que não fosse totalmente óbvio para olhos curiosos ( estragaria a surpresa ;-)

Portanto, o método ( mais simplesmente ) é:

element.addEventListener( action, function name () {
    doSomething();
    element.removeEventListener( action, name, capture );
}, capture );
Fred Gandt
fonte
2
Isso é muito complicado.
Ben Sinclair
@Andy concordo, mais ou menos, mas estava tentando mostrar que simplesmente não há como remover uma função anônima. Ela deve ser referenciada de alguma forma (até mesmo o callee (é ruim, M'Kay) está referenciando a função), e, portanto, fornecido um exemplo de apenas uma (outra) maneira como a função pode ser referenciada - e como ela é construída de partes que pode igualmente ser armazenado para referência posterior (a parte importante). Obviamente, uma função verdadeiramente anônima é construída em tempo real e, portanto, saber mais tarde qual ação / tipo de evento e se a captura foi usada também deve ser conhecido. Enfim, aqui está um método melhor :-)
Fred Gandt
Funcionou perfeitamente para mim. Não consegui ver outra maneira de passar argumentos para a função, pois não poderia ser anônima.
nicodemus13
2

Isso não é ideal, pois remove tudo, mas pode funcionar para suas necessidades:

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

A clonagem de um nó copia todos os seus atributos e seus valores, incluindo ouvintes intrínsecos (em linha). Ele não copia ouvintes de eventos adicionados usando addEventListener ()

Node.cloneNode ()

Comunidade
fonte
Isso é absolutamente brilhante
Ahmad Alfy
1

JavaScript : o método addEventListener registra o ouvinte especificado no EventTarget (Element | document | Window) em que é chamado.

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

Eventos de mouse, teclado Exemplo de teste no 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);

O método removeEventListener remove o ouvinte de evento registrado anteriormente com EventTarget.addEventListener ().

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

eu posso usar

Yash
fonte
1

Para fornecer uma abordagem mais atualizada para isso:

//one-time fire
element.addEventListener('mousedown', {
  handleEvent: function (evt) {
    element.removeEventListener(evt.type, this, false);
  }
}, false);
Jonatas Walker
fonte
2
Uma explicação seria legal.
Poul Bak
1

Encontrei o mesmo problema e esta foi a melhor solução que consegui:

/*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;

Lembre-se de que testei isso apenas para o windowelemento e para o 'mousemove'evento, portanto, pode haver alguns problemas com essa abordagem.

Gark Garcia
fonte
0

Possivelmente não é a melhor solução em termos do que você está pedindo. Ainda não determinei um método eficiente para remover a função anônima declarada em linha com a invocação do ouvinte de evento.

Eu pessoalmente uso uma variável para armazenar <target>e declarar a função fora da invocação do ouvinte de evento, por exemplo:

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

function myFunc(event) { function code; }

target.addEventListener('click', myFunc);

Em seguida, para remover o ouvinte:

target.removeEventListener('click', myFunc);

Não é a recomendação principal que você receberá, mas para remover funções anônimas, a única solução que achei útil é remover e substituir o elemento HTML. Tenho certeza de que deve haver um método JS vanilla melhor, mas ainda não o vi.

Shay Connolly
fonte
0

Sei que esse é um tópico bastante antigo, mas achei melhor colocar meus dois centavos para aqueles que o acharem útil.

O script (desculpas pelos nomes dos métodos não criativos):

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;
        }
    }
};

E você pode usá-lo assim:

// 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.
fonte
-4
window.document.onkeydown = function(){};
Dun
fonte
Por que não = indefinido? Realmente durão.
Ben Sinclair
2
Isso não removerá nenhum manipulador registrado addEventListener.
Luc125 de