removeEventListener na anonimowych funkcjach w JavaScript

103

Mam obiekt, który zawiera metody. Te metody są umieszczane w obiekcie wewnątrz anonimowej funkcji. To wygląda tak:

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

(kodu jest dużo więcej, ale to wystarczy, aby pokazać problem)

Teraz chcę w niektórych przypadkach zatrzymać detektor zdarzeń. Dlatego próbuję zrobić removeEventListener, ale nie mogę dowiedzieć się, jak to zrobić. W innych pytaniach przeczytałem, że nie jest możliwe wywołanie funkcji removeEventListener na funkcjach anonimowych, ale czy tak też jest w tej sytuacji?

Mam metodę in t utworzoną wewnątrz funkcji anonimowej i dlatego pomyślałem, że to możliwe. Wygląda tak:

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

Dlaczego nie mogę tego zrobić?

Czy jest jakiś inny (dobry) sposób, aby to zrobić?

Informacje o bonusach; musi to działać tylko w Safari, stąd brak obsługi IE.

bitkid
źródło
Dlaczego nie zapisać tej funkcji? Program obsługi zdarzeń może nie być funkcją anonimową.
kirilloid
2
Zdaję sobie sprawę, że jest to trochę za późno, ale możesz również użyć metod Node.setUserData /Node.getUserData do przechowywania danych o elemencie. Na przykład, gdy musisz ustawić odbiornik anon (i móc go usunąć), najpierw ustaw userdata na funkcję anon, (Elem.setUserData('eventListener', function(e){console.log('Event fired.');}, null);a następnie wykonaj Elem.addEventListener ('event', Elem.getUserData ('eventListener'), false); ... i to samo dla removeEventListener. Mam nadzieję, że widzisz to dobrze.
Ścigaj
EDYCJA: Jak w poprzednim komentarzu, myślę, że działa tylko w Firefoksie ... Właśnie wypróbowałem IE8 (nieznany IE9), Safari 5.1.2, Chrome (?), Opera 11..Brak kości
Chase
Możliwy duplikat JavaScript: usuń nasłuchiwanie zdarzeń
Heretic Monkey

Odpowiedzi:

78

Uważam, że to jest sens funkcji anonimowej, brakuje jej nazwy ani sposobu, aby się do niej odwołać.

Gdybym był tobą, utworzyłbym po prostu nazwaną funkcję lub umieścił ją w zmiennej, abyś miał do niej odniesienie.

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

Możesz go następnie usunąć przez

window.document.removeEventListener("keydown", handler);   
Adam Heath
źródło
3
Dziękuję za odpowiedź. Poszedłem z: var handler; window.document.addEventListener ("keydown", handler = function (e) {Ale nie rozumiem, dlaczego "this" nie odwołuje się do detektora zdarzeń. Czy detektor zdarzeń nie powinien być obiektem?
bitkid
1
Słowo thiskluczowe może być mylące. Dobrym miejscem do przeczytania jest quirksmode.org/js/this.html
Adam Heath,
Dziękuję Ci bardzo. To było bardzo pomocne.
bitkid
Próbuję to zrobić, aby zablokować naprawdę trwałą reklamę w witrynie. Wiem, że o to chodzi w funkcjach anonimowych, ale to nie znaczy, że nie chciałbym wiedzieć, jak to zrobić.
Wyatt8740
@bitkid: Wewnątrz funkcji obsługi (zakładając, że nie jest to funkcja strzałkowa), thisodwołuje się do elementu, do którego jest dodawany detektor, a nie do samego zdarzenia (które byłoby parametrem e). Dlatego this === e.currentTarget. przeczytaj developer.mozilla.org/en-US/docs/Web/API/EventTarget/…
chharvey.
100

jeśli jesteś wewnątrz właściwej funkcji, możesz użyć arguments.callee jako odniesienia do funkcji. jak w:

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

EDYCJA: to nie zadziała, jeśli pracujesz w trybie ścisłym ( "use strict";)

Otto Nascarella
źródło
2
Jest to miłe, ponieważ zachowuje zalety funkcji anonimowych (nie zanieczyszcza przestrzeni nazw itp.).
bompf,
3
próbowałem tego w aplikacji WinJS, otrzymałem następny błąd: „Dostęp do właściwości 'callee' obiektu arguments nie jest dozwolony w trybie ścisłym”
Valentin Kantor
1
@ValentinKantor: To dlatego, że coś w kodzie jest „użyj ścisłego”; i nie możesz używać callee w trybie ścisłym.
OMA
19
Nadaj funkcji wbudowanej nazwę i możesz odwoływać się do niej bez uciekania się do argumentów. Callee:button.addEventListener('click', function handler() { this.removeEventListener('click', handler); });
Harry Love
4
Jak stwierdzono w Mozilli: „Ostrzeżenie: piąte wydanie ECMAScript (ES5) zabrania używania arguments.callee () w trybie ścisłym. Unikaj używania arguments.callee () przez nadanie wyrażeniom funkcji nazwy lub użycie deklaracji funkcji, w której funkcja musi się nazywać ”.
koleś
50

Wersja rozwiązania Otto Nascarelli , która działa w trybie ścisłym, to:

button.addEventListener('click', function handler() {
      ///this will execute only once
      alert('only once!');
      this.removeEventListener('click', handler);
});
Melle
źródło
4
Piękne rozwiązanie!
Eric Norcross
2
Może to nie jest właściwa droga, ale to jest najłatwiejsza rzecz.
Vignesh
7
window.document.removeEventListener("keydown", getEventListeners(window.document.keydown[0].listener));  

Może być kilka funkcji anonimowych, keydown 1

Ostrzeżenie: działa tylko w Chrome Dev Toolsi nie może być użyte w kodzie : link

Isayevskiy_Sergey
źródło
2
Dzięki, rozwiązałeś zagadkę, przynajmniej w Chrome, ponieważ to, co wielu żartownisiów powiedziało, było niemożliwe. Człowieku, jesteś jak ... Batman!
JasonXA
20
getEventListenerswydaje się być częścią narzędzi deweloperskich Chrome, więc nie nadaje się do niczego innego niż debugowanie.
DBS
1
Właśnie go wypróbowałem, potwierdziłem, że jest dostępny tylko w Devtools, a nie w skrypcie na stronie.
Andres Riofrio
5

w nowoczesnych przeglądarkach możesz wykonać następujące czynności ...

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

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

shunryu111
źródło
Fajnie, dopóki nie okaże się, że żadna wersja IE ani Edge <16 nie obsługuje tej funkcji. Przynajmniej za 5 lat będziemy mogli z tego korzystać, ponieważ wtedy IE (czytaj: powinno) być przestarzałe, Edge zajmie jego miejsce i będzie korzystał z silnika webkit zamiast własnego "EdgeHTML".
SidOfc,
1
z tym wypełnieniem dla wpisów DOM Level 4 powinno być dobrze npmjs.com/package/dom4
shunryu111
2

Niezbyt anonimowa opcja

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

Odkąd otrzymałem informację zwrotną od Andy'ego ( całkiem słusznie, ale tak jak w przypadku wielu przykładów, chciałem pokazać kontekstowe rozwinięcie pomysłu ), oto mniej skomplikowany wykład:

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

Pozwala to na efektywnie anonimową strukturę funkcji, pozwala uniknąć użycia praktycznie przestarzałego odbiorcy i umożliwia łatwe usunięcie.

Nawiasem mówiąc : usunięcie elementu skryptu natychmiast po ustawieniu słuchacza jest uroczą sztuczką do ukrycia kodu, który wolałby nie był oczywisty dla wścibskich oczu ( zepsułoby niespodziankę ;-)

Więc metoda ( prościej ) to:

element.addEventListener( action, function name () {
    doSomething();
    element.removeEventListener( action, name, capture );
}, capture );
Fred Gandt
źródło
2
To jest zbyt skomplikowane.
Ben Sinclair
@Andy Zgadzam się, trochę, ale próbowałem pokazać, że po prostu nie ma sposobu na usunięcie anonimowej funkcji. Musi być w jakiś sposób przywoływany (nawet wywoływany (jest zły, M'Kay) odwołuje się do funkcji), a zatem stanowi przykład tylko jednego (innego) sposobu, w jaki można się do niej odwoływać - i jak jest zbudowana z części, które można je również przechowywać do późniejszego wykorzystania (ważna część). Oczywiście prawdziwie anonimowa funkcja jest w pewnym sensie tworzona w locie, więc późniejsze informacje o tym, jakie działanie / typ zdarzenia i czy użyto przechwytywania, muszą być również znane. Tak czy
inaczej
U mnie zadziałało idealnie. Nie widziałem innego sposobu przekazywania argumentów do funkcji, ponieważ nie mogłaby być anonimowa.
nicodemus13
2

Nie jest to idealne rozwiązanie, ponieważ usuwa wszystko, ale może działać dla twoich potrzeb:

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

Klonowanie węzła powoduje skopiowanie wszystkich jego atrybutów i ich wartości, w tym wewnętrznych (wbudowanych) odbiorników. Nie kopiuje detektorów zdarzeń dodanych za pomocą addEventListener ()

Node.cloneNode ()

Społeczność
źródło
To jest absolutnie genialne
Ahmad Alfy,
1

Metoda JavaScript : addEventListener rejestruje określony detektor w EventTarget (Element | dokument | Okno), na którym jest wywoływany.

EventTarget. addEventListener ( EVENT_TYPE , handler_function, Bubbling | Przechwytywanie );

Zdarzenia myszy, klawiatury Przykładowy test w konsoli internetowej:

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

removeEventListener usuwa detektor zdarzeń zarejestrowany wcześniej przez EventTarget.addEventListener ().

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

mogę uzyć

Yash
źródło
1

Aby przedstawić bardziej aktualne podejście do tego zagadnienia:

//one-time fire
element.addEventListener('mousedown', {
  handleEvent: function (evt) {
    element.removeEventListener(evt.type, this, false);
  }
}, false);
Jonatas Walker
źródło
2
Wyjaśnienie byłoby miłe.
Poul Bak
1

Natknąłem się na ten sam problem i było to najlepsze rozwiązanie, jakie mogłem uzyskać:

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

Pamiętaj, że przetestowałem to tylko dla windowelementu i 'mousemove'wydarzenia, więc mogą wystąpić problemy z tym podejściem.

Gark Garcia
źródło
0

Prawdopodobnie nie jest to najlepsze rozwiązanie pod względem tego, o co prosisz. Nadal nie ustaliłem skutecznej metody usuwania funkcji anonimowej zadeklarowanej w trakcie wywołania detektora zdarzeń.

Osobiście używam zmiennej do przechowywania <target>i deklarowania funkcji poza wywołaniem detektora zdarzeń, np .:

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

function myFunc(event) { function code; }

target.addEventListener('click', myFunc);

Następnie, aby usunąć słuchacza:

target.removeEventListener('click', myFunc);

Nie jest to najlepsza rekomendacja, którą otrzymasz, ale aby usunąć anonimowe funkcje, jedynym rozwiązaniem, które uznałem za przydatne, jest usunięcie, a następnie zastąpienie elementu HTML. Jestem pewien, że musi być lepsza metoda waniliowego JS, ale jeszcze jej nie widziałem.

Shay Connolly
źródło
0

Wiem, że to dość stary wątek, ale pomyślałem, że mógłbym włożyć swoje dwa centy dla tych, którzy uznają to za przydatne.

Skrypt (przepraszam za nietwórcze nazwy metod):

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

I możesz go używać w ten sposób:

// 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.
źródło
-4
window.document.onkeydown = function(){};
Bury
źródło
Dlaczego nie = niezdefiniowane? Prawdziwy badass.
Ben Sinclair
2
Nie usunie to żadnego programu obsługi, który został zarejestrowany w addEventListener.
Luc125