Removendo o ouvinte de evento que foi adicionado com a ligação

164

Em JavaScript, qual é a melhor maneira de remover uma função adicionada como ouvinte de eventos usando bind ()?

Exemplo

(function(){

    // constructor
    MyClass = function() {
        this.myButton = document.getElementById("myButtonID");
        this.myButton.addEventListener("click", this.clickListener.bind(this));
    };

    MyClass.prototype.clickListener = function(event) {
        console.log(this); // must be MyClass
    };

    // public method
    MyClass.prototype.disableButton = function() {
        this.myButton.removeEventListener("click", ___________);
    };

})();

A única maneira de pensar é acompanhar todos os ouvintes adicionados ao bind.

Exemplo acima com este método:

(function(){

    // constructor
    MyClass = function() {
        this.myButton = document.getElementById("myButtonID");
        this.clickListenerBind = this.clickListener.bind(this);
        this.myButton.addEventListener("click", this.clickListenerBind);
    };

    MyClass.prototype.clickListener = function(event) {
        console.log(this); // must be MyClass
    };

    // public method
    MyClass.prototype.disableButton = function() {
        this.myButton.removeEventListener("click", this.clickListenerBind);
    };

})();

Existem maneiras melhores de fazer isso?

takfuruya
fonte
2
O que você está fazendo, exceto this.clickListener = this.clickListener.bind(this);ethis.myButton.addEventListener("click", this.clickListener);
Esailija 19/07/12
Isso é muito legal. Este pode ser um tópico diferente, mas me fez pensar se devo vincular (this) ao restante dos meus métodos que usam a palavra-chave "this", mesmo que isso torne as chamadas de método ineficientes.
2141212
Eu sempre faço isso como uma primeira coisa no construtor para todos os métodos que serão passados ​​para algum lugar, independentemente de eu removê-los mais tarde. Mas não para todos os métodos, apenas aqueles que são repassados.
Esailija 19/07/12
O que você está fazendo faz sentido. Mas se isso fazia parte de uma biblioteca, por exemplo, você nunca pode saber quais métodos do MyClass (documentados como "públicos") seriam repassados.
2141212
Apenas para sua informação, a biblioteca Underscore tem uma bindAllfunção que simplifica os métodos de ligação. Dentro do inicializador de objetos, você apenas faz _.bindAll(this)para definir todos os métodos no seu objeto para uma versão vinculada. Alternativamente, se você só quer ligar alguns métodos (que eu recomendo, para evitar vazamentos de memória acidentais), você pode fornecê-los como argumentos: _.bindAll(this, "foo", "bar") // this.baz won't be bound.
Machineghost

Respostas:

274

Embora o que o @machineghost disse seja verdade, que os eventos sejam adicionados e removidos da mesma maneira, a parte que faltava da equação era a seguinte:

Uma nova referência de função é criada após .bind()ser chamada!

Consulte O bind () altera a referência da função? | Como definir permanentemente?

Portanto, para adicionar ou remover, atribua a referência a uma variável:

var x = this.myListener.bind(this);
Toolbox.addListener(window, 'scroll', x);
Toolbox.removeListener(window, 'scroll', x);

Isso funciona como esperado para mim.

Ben
fonte
4
Excelente, esta deve ser a resposta aceita. Obrigado por atualizar um tópico antigo, esse tópico surgiu no mecanismo de pesquisa como o número um e não tinha uma solução adequada até você postar isso agora.
precisa saber é o seguinte
Isso não é diferente (e não é melhor que) o método mencionado na pergunta.
Peter Tseng 12/02
Eu não posso entender, como é que u fazê-lo funcionar com um evento de clique, graças
Alberto Acuña
@ AlbertoAcuña Os navegadores modernos usam .addEventListener(type, listener)e .removeEventListener(type, listener)adicionam e removem eventos em um elemento. Para ambos, você pode passar a referência de função descrita na solução como listenerparâmetro, com "click"como tipo. developer.mozilla.org/pt-BR/docs/Web/API/EventTarget/…
Ben
1
isso me ajuda, embora essa resposta tenha sido postada 4 anos atrás :) #
user2609021:
46

Para quem tem esse problema ao registrar / remover o ouvinte do componente React de / para a loja Flux, adicione as linhas abaixo ao construtor do seu componente:

class App extends React.Component {
  constructor(props){
    super(props);
    // it's a trick! needed in order to overcome the remove event listener
    this.onChange = this.onChange.bind(this);  
  }
  // then as regular...
  componentDidMount (){
    AppStore.addChangeListener(this.onChange);
  }
  
  componentWillUnmount (){
    AppStore.removeChangeListener(this.onChange);
  }

  onChange () {
    let state = AppStore.getState();
    this.setState(state);
  }
  
  render() {
    // ...
  }
  
}

Raichman Sergey
fonte
7
Bom truque, mas o que o React / Flux tem a ver com alguma coisa?
Peter Tseng 12/02
Essa parece ser a abordagem correta ao adicionar e remover ouvintes de eventos de diferentes classes ou funções de protótipo, que eu acredito que a conexão com isso também se aplique aos componentes / classes do React. Você está vinculando-o em um nível de instância comum (por exemplo, raiz).
Keith DC
1
this.onChange = this.onChange.bind(this)na verdade, era isso que eu estava procurando. A função ligado na thispara sempre :)
Paweł
2

Não importa se você usa uma função vinculada ou não; você o remove da mesma maneira que qualquer outro manipulador de eventos. Se o seu problema é que a versão vinculada é sua própria função exclusiva, você pode acompanhar as versões associadas ou usar a removeEventListenerassinatura que não aceita um manipulador específico (embora, é claro, isso remova outros manipuladores de eventos do mesmo tipo )

(Como uma observação lateral, addEventListenernão funciona em todos os navegadores; você realmente deve usar uma biblioteca como o jQuery para fazer suas conexões de eventos de uma maneira entre navegadores. Além disso, o jQuery tem o conceito de eventos no espaço de nomes, que permitem você deve vincular a "click.foo"; quando quiser remover o evento, pode dizer ao jQuery "remova todos os eventos foo" sem precisar conhecer o manipulador específico ou remover outros manipuladores.)

machineghost
fonte
Estou ciente do problema do IE. Estou desenvolvendo um aplicativo que depende muito da tela para que o IE7- seja lançado. O IE8 suporta tela, mas no mínimo. O IE9 + suporta addEventListener. Os Eventos Namespaced do jQuery parecem muito legais. A única coisa que me preocupa é a eficiência.
2171212
O pessoal do jQuery trabalha muito para manter o desempenho da biblioteca, então eu não me preocuparia muito com isso. No entanto, considerando os requisitos rigorosos do navegador, você pode conferir o Zepto. É como uma versão reduzida do jQuery, que é mais rápida, mas não suporta navegadores mais antigos (e tem outros limites).
Machineghost
Os eventos no namespace do JQuery são amplamente utilizados e praticamente não têm problemas de desempenho. Dizer a alguém para não usar uma ferramenta que tornará seu código mais fácil e (sem dúvida mais importante) mais fácil de entender seria um conselho horrível, especialmente se feito com um medo irracional de JQuery e problemas de desempenho imaginário.
machineghost
1
Qual assinatura seria essa? A página MDN em removeEventListener mostra que os dois primeiros argumentos são necessários.
Coderer
Meu erro. Faz anos desde que escrevi essa resposta, mas devo ter pensado no jQuery offou no unbindmétodo. Para remover todos os ouvintes de um elemento, você deve acompanhá-los à medida que são adicionados (o que é algo que o jQuery ou outras bibliotecas podem fazer por você).
precisa
1

solução jQuery:

let object = new ClassName();
let $elem = $('selector');

$elem.on('click', $.proxy(object.method, object));

$elem.off('click', $.proxy(object.method, object));
Ed Kolosovsky
fonte
1

Tivemos esse problema com uma biblioteca que não pudemos alterar. UI do Office Fabric, o que significava que não era possível alterar a maneira como os manipuladores de eventos foram adicionados. A maneira que resolvemos foi sobrescrever addEventListenero EventTargetprotótipo.

Isso adicionará uma nova função aos objetos element.removeAllEventListers("click")

(postagem original: Remover manipulador de cliques da sobreposição de caixa de diálogo de malha )

        <script>
            (function () {
                "use strict";

                var f = EventTarget.prototype.addEventListener;

                EventTarget.prototype.addEventListener = function (type, fn, capture) {
                    this.f = f;
                    this._eventHandlers = this._eventHandlers || {};
                    this._eventHandlers[type] = this._eventHandlers[type] || [];
                    this._eventHandlers[type].push([fn, capture]);
                    this.f(type, fn, capture);
                }

                EventTarget.prototype.removeAllEventListeners = function (type) {
                    this._eventHandlers = this._eventHandlers || {};
                    if (type in this._eventHandlers) {
                        var eventHandlers = this._eventHandlers[type];
                        for (var i = eventHandlers.length; i--;) {
                            var handler = eventHandlers[i];
                            this.removeEventListener(type, handler[0], handler[1]);
                        }
                    }
                }

                EventTarget.prototype.getAllEventListeners = function (type) {
                    this._eventHandlers = this._eventHandlers || {};
                    this._eventHandlers[type] = this._eventHandlers[type] || [];
                    return this._eventHandlers[type];
                }

            })();
        </script>
Peter
fonte
0

Aqui está a solução:

var o = {
  list: [1, 2, 3, 4],
  add: function () {
    var b = document.getElementsByTagName('body')[0];
    b.addEventListener('click', this._onClick());

  },
  remove: function () {
    var b = document.getElementsByTagName('body')[0];
    b.removeEventListener('click', this._onClick());
  },
  _onClick: function () {
    this.clickFn = this.clickFn || this._showLog.bind(this);
    return this.clickFn;
  },
  _showLog: function (e) {
    console.log('click', this.list, e);
  }
};


// Example to test the solution
o.add();

setTimeout(function () {
  console.log('setTimeout');
  o.remove();
}, 5000);
Nazar Vynnytskyi
fonte
0

pode usar sobre o ES7:

class App extends React.Component {
  constructor(props){
    super(props);
  }
  componentDidMount (){
    AppStore.addChangeListener(this.onChange);
  }

  componentWillUnmount (){
    AppStore.removeChangeListener(this.onChange);
  }

  onChange = () => {
    let state = AppStore.getState();
    this.setState(state);
  }

  render() {
    // ...
  }

}
chiic
fonte
-1

Se você quiser usar o 'onclick', como sugerido acima, tente o seguinte:

(function(){
    var singleton = {};

    singleton = new function() {
        this.myButton = document.getElementById("myButtonID");

        this.myButton.onclick = function() {
            singleton.clickListener();
        };
    }

    singleton.clickListener = function() {
        console.log(this); // I also know who I am
    };

    // public function
    singleton.disableButton = function() {
        this.myButton.onclick = "";
    };
})();

Espero que ajude.

Diogo Schneider
fonte
-2

Já faz um tempo, mas o MDN tem uma super explicação sobre isso. Isso me ajudou mais do que as coisas aqui.

MDN :: EventTarget.addEventListener - O valor de "this" dentro do manipulador

Ele fornece uma ótima alternativa para a função handleEvent.

Este é um exemplo com e sem ligação:

var Something = function(element) {
  this.name = 'Something Good';
  this.onclick1 = function(event) {
    console.log(this.name); // undefined, as this is the element
  };
  this.onclick2 = function(event) {
    console.log(this.name); // 'Something Good', as this is the binded Something object
  };
  element.addEventListener('click', this.onclick1, false);
  element.addEventListener('click', this.onclick2.bind(this), false); // Trick
}

Um problema no exemplo acima é que você não pode remover o ouvinte com ligação. Outra solução é usar uma função especial chamada handleEvent para capturar qualquer evento:

Noitidart
fonte