Jak przekazać argumenty do funkcji detektora addEventListener?

304

Sytuacja jest trochę jak-

var someVar = some_other_function();
someObj.addEventListener("click", function(){
    some_function(someVar);
}, false);

Problem polega na tym, że wartość parametru someVarnie jest widoczna w funkcji nasłuchiwania addEventListener, gdzie prawdopodobnie jest ona traktowana jako nowa zmienna.

Abhishek Yadav
źródło
8
Bardzo jasny artykuł na ten temat: toddmotto.com/avoiding-anonymous-javascript-functions
Nobita
Nie najczystszy sposób, ale działa. Zauważ, że jeśli someVar może być tylko cyfrą lub tekstem: eval ('someObj.addEventListener („click”, function () {some_function (' + someVar + ');});');
Ignas2526
Właśnie miałem ten problem dzisiaj - podane tutaj rozwiązanie jest poprawne (inne rozwiązania mają problemy takie jak problem z pętlą itp.) - stackoverflow.com/a/54731362/984471
Manohar Reddy Poreddy

Odpowiedzi:

206

Napisany kod nie ma absolutnie nic złego. Zarówno some_functioni someVarpowinny być dostępne, na wypadek, gdyby były dostępne w kontekście, w którym anonimowy

function() { some_function(someVar); } 

powstał.

Sprawdź, czy alert daje poszukiwaną wartość, upewnij się, że będzie on dostępny w zakresie funkcji anonimowej (chyba że masz więcej kodu, który działa na tej samej someVarzmiennej obok wywołania do addEventListener)

var someVar; 
someVar = some_other_function();
alert(someVar);
someObj.addEventListener("click", function(){
    some_function(someVar);
}, false);
Siergiej Iliński
źródło
86
To nie działa dla pętli. Zawsze otrzymuję najnowszą wartość, a nie tę, która należała do tej iteracji. Jakieś rozwiązanie?
iMatoria,
6
Czy ktoś wie, dlaczego to nie działa w pętli? Jaki jest powód tego zachowania?
Morfidon
3
@Morfidon, ponieważ funkcja ze zmiennymi globalnymi działa jako zamknięcie w javascript, co oznacza, że ​​zapamiętuje środowisko poza ich zasięgiem leksykalnym. Jeśli po prostu utworzysz inną funkcję w tym samym środowisku, będą one odnosić się do tego samego środowiska.
bugwheels94
16
@Morfidon: W pętli wartość someVar nie jest wartością, którą miał, kiedy dodano detektor, ale wartością, którą ma, kiedy detektor jest wykonywany. Kiedy detektor jest wykonywany, pętla już się zakończyła, więc wartość someVar będzie wartością, którą miała, gdy pętla się zakończyła.
www.admiraalit.nl
4
@ iMatoria Właśnie odkryłem, że utworzenie metody bound functionprzy użyciu .bind()metody rozwiąże problem z pętlami developer.mozilla.org/en/docs/Web/JavaScript/Reference/…
Luke T O'Brien
353

Dlaczego nie pobrać argumentów z atrybutu target zdarzenia?

Przykład:

const someInput = document.querySelector('button');
someInput.addEventListener('click', myFunc, false);
someInput.myParam = 'This is my parameter';
function myFunc(evt)
{
  window.alert(evt.currentTarget.myParam);
}
<button class="input">Show parameter</button>

JavaScript jest językiem zorientowanym na prototypy, pamiętaj!

Zaloz
źródło
16
To poprawna odpowiedź, ponieważ pozwala nam korzystać po funkcji „removeEventListener”.
user5260143
14
Nie powinno tak być evt.currentTarget.myParam? Jeśli wewnątrz „someInput” znajduje się inny element, evt.targetmoże odnosić się do elementu wewnętrznego. ( jsfiddle.net/qp5zguay/1 )
Herbertusz
To zachowuje this! Użycie tej metody w maszynopisie wymaga jednak, aby element był anylub tworzył podtyp.
Old Badman Gray,
1
Moje zmienne wracają jako niezdefiniowane ... jakieś myśli, jak to naprawić?
nomaam
1
Jeśli addEventListenertak document, evt.target.myParamto nie działało dla mnie. Musiałem evt.currentTarget.myParamzamiast tego użyć .
turrican_34
67

To pytanie jest stare, ale pomyślałem, że zaoferuję alternatywę przy użyciu .bind () ES5 - dla potomności. :)

function some_func(otherFunc, ev) {
    // magic happens
}
someObj.addEventListener("click", some_func.bind(null, some_other_func), false);

Pamiętaj tylko, że musisz ustawić swoją funkcję nasłuchiwania z pierwszym parametrem jako argumentem, który przekazujesz do bind (twoja inna funkcja), a drugim parametrem jest teraz zdarzenie (zamiast pierwszego, jak by to było) .

Brian Moore
źródło
1
Function.prototype.bind () jest naprawdę najlepszym sposobem na rozwiązanie tego problemu. Ponadto działa intuicyjnie wewnątrz pętli - otrzymujesz pożądany zakres leksykalny. Żadnych anonimowych funkcji, IIFE ani specjalnych właściwości przypisywanych do obiektów.
Clint Pachl,
Zobacz zalety i wady IIFE vs bind () .
Clint Pachl,
1
Korzystając z niego Function.prototype.bind(), nie można usunąć detektora zdarzeń , lepiej zamiast tego użyć funkcji curry (patrz odpowiedź @ tomcek112)
pldg 17.09.19
UWAGA: some_other_funcjest zmienną, możesz przekazać dowolną wartość, którą chcesz some_func.
hitautodestruct
28

Możesz po prostu powiązać wszystkie niezbędne argumenty za pomocą polecenia „bind”:

root.addEventListener('click', myPrettyHandler.bind(null, event, arg1, ... ));

W ten sposób zawsze będziesz dostać event, arg1i inne rzeczy przekazanej myPrettyHandler.

http://passy.svbtle.com/partial-application-in-javascript-using-bind

Oleksandr Tkalenko
źródło
Dzięki! Próbowałem już, .bind()ale bez wartości zerowej jako pierwszego parametru. co nie działało.
Larphoid,
nie ma potrzeby null, działa dobrze z .bind(event, arg1), przynajmniej z VueJS.
DevonDahon
27

Pytanie dość stare, ale dzisiaj miałem ten sam problem. Najczystszym rozwiązaniem, jakie znalazłem, jest użycie koncepcji curry.

Kod do tego:

someObj.addEventListener('click', some_function(someVar));

var some_function = function(someVar) {
    return function curried_func(e) {
        // do something here
    }
}

Nadając nazwę funkcji curry, pozwala ona wywołać Object.removeEventListener w celu wyrejestrowania eventListener w późniejszym czasie wykonywania.

tomcek112
źródło
4
Cieszę się, że spotkałem się z tą odpowiedzią na temat funkcji curry. Jak jednak usunąć detektor zdarzeń?
Bob
3
Wspaniale jest widzieć dobrą terminologię. Powinieneś być w stanie usunąć detektor zdarzeń, nazywając funkcję curry. zaproponuję edycję.
Matthew Brent
Ta odpowiedź zarejestruje funkcję tyle razy, ile wywoływana jest funkcja addEventListener, ponieważ funkcja some_function (var) zwraca za każdym razem nowo utworzoną funkcję.
Yahia,
nie podoba mi się pomysł nazywania funkcji curry w celu usunięcia słuchacza, ponieważ wtedy masz do czynienia z 2 różnymi nazwami nazw, które musisz śledzić
oldboy
19

Możesz dodawać i usuwać zdarzenia z argumentami, deklarując funkcję jako zmienną.

myaudio.addEventListener('ended',funcName=function(){newSrc(myaudio)},false);

newSrcjest metodą z myaudio, ponieważ parametr funcNamejest zmienną nazwy funkcji

Możesz usunąć słuchacza za pomocą myaudio.removeEventListener('ended',func,false);

Ganesh Krishnan
źródło
12

Możesz przekazać wartość według wartości (nie przez odniesienie) za pomocą funkcji javascript znanej jako zamknięcie :

var someVar='origin';
func = function(v){
    console.log(v);
}
document.addEventListener('click',function(someVar){
   return function(){func(someVar)}
}(someVar));
someVar='changed'

Lub możesz napisać wspólną funkcję zawijania, taką jak wrapEventCallback:

function wrapEventCallback(callback){
    var args = Array.prototype.slice.call(arguments, 1);
    return function(e){
        callback.apply(this, args)
    }
}
var someVar='origin';
func = function(v){
    console.log(v);
}
document.addEventListener('click',wrapEventCallback(func,someVar))
someVar='changed'

Oto wrapEventCallback(func,var1,var2)jak:

func.bind(null, var1,var2)
ahuigo
źródło
1
Wielkie dzięki za tę odpowiedź! OP nie szukał tego, ale myślę, że ludzie, którzy wpisują „Jak przekazać argumenty do addEventListener” w google, będą szukać twojej odpowiedzi. Potrzebuje tylko trochę więcej wyjaśnień :) Edytuję to.
Sindarus,
9

someVarwartość powinna być dostępna tylko w some_function()kontekście, a nie od odbiornika. Jeśli chcesz mieć to w słuchaczu, musisz zrobić coś takiego:

someObj.addEventListener("click",
                         function(){
                             var newVar = someVar;
                             some_function(someVar);
                         },
                         false);

I użyć newVar zamiast tego.

Innym sposobem jest zwrócenie someVarwartości some_function()za używanie jej dalej w detektorze (jako nowej zmiennej lokalnej):

var someVar = some_function(someVar);
Thevs
źródło
9

Oto jeszcze inny sposób (ten działa wewnątrz pętli):

var someVar = some_other_function();
someObj.addEventListener("click", 

function(theVar){
    return function(){some_function(theVar)};
}(someVar),

false);
Witaj świecie
źródło
2
To najlepszy sposób. Brzydkie, ale skuteczne wewnątrz pętli, ponieważ wysłanie argumentu do anonimowej funkcji spowoduje przechwycenie var.
Bob
9

Function.prototype.bind () to sposób na powiązanie funkcji celu z określonym zakresem i opcjonalne zdefiniowanie thisobiektu w funkcji celu.

someObj.addEventListener("click", some_function.bind(this), false);

Lub uchwycić część zakresu leksykalnego, na przykład w pętli:

someObj.addEventListener("click", some_function.bind(this, arg1, arg2), false);

Wreszcie, jeśli thisparametr nie jest potrzebny w ramach funkcji docelowej:

someObj.addEventListener("click", some_function.bind(null, arg1, arg2), false);
eclos
źródło
7

Posługiwać się

   el.addEventListener('click',
    function(){
        // this will give you the id value 
        alert(this.id);    
    },
false);

A jeśli chcesz przekazać dowolną niestandardową wartość do tej anonimowej funkcji, najłatwiej to zrobić

 // this will dynamically create property a property
 // you can create anything like el.<your  variable>
 el.myvalue = "hello world";
 el.addEventListener('click',
    function(){
        //this will show you the myvalue 
        alert(el.myvalue);
        // this will give you the id value 
        alert(this.id);    
    },
false);

Działa idealnie w moim projekcie. Mam nadzieję, że to pomoże

kta
źródło
Tak, zdecydowanie pomogło, ponieważ utrzymało również oczekiwany zakres w forpętli.
j4v1
4
    $form.addEventListener('submit', save.bind(null, data, keyword, $name.value, myStemComment));
    function save(data, keyword, name, comment, event) {

W ten sposób poprawnie zdałem wydarzenie.

chovy
źródło
Doskonale, właśnie tak prawie doszedłem do wniosku - właśnie błędnie przekazałem dodatkowe zdarzenie w powiązaniu, gdy go nie ma (jak w kanciastym), co automatycznie przychodzi w tym przypadku.
Manohar Reddy Poreddy
3

Wysyłanie argumentów do funkcji zwrotnej eventListener wymaga utworzenia funkcji izolowanej i przekazania argumentów do tej funkcji izolowanej.

Oto fajna funkcja pomocnika, której możesz użyć. Na podstawie powyższego przykładu „hello world”).

Jedną rzeczą, która jest również potrzebna, jest utrzymanie odniesienia do funkcji, abyśmy mogli czysto usunąć słuchacza.

// Lambda closure chaos.
//
// Send an anonymous function to the listener, but execute it immediately.
// This will cause the arguments are captured, which is useful when running 
// within loops.
//
// The anonymous function returns a closure, that will be executed when 
// the event triggers. And since the arguments were captured, any vars 
// that were sent in will be unique to the function.

function addListenerWithArgs(elem, evt, func, vars){
    var f = function(ff, vv){
            return (function (){
                ff(vv);
            });
    }(func, vars);

    elem.addEventListener(evt, f);

    return f;
}

// Usage:

function doSomething(withThis){
    console.log("withThis", withThis);
}

// Capture the function so we can remove it later.
var storeFunc = addListenerWithArgs(someElem, "click", doSomething, "foo");

// To remove the listener, use the normal routine:
someElem.removeEventListener("click", storeFunc);
kok
źródło
Ta odpowiedź pochodzi z '15, ale właśnie tego potrzebowałem, aby poradzić sobie z tym problemem przy użyciu haka useRef. Jeśli używasz haka referencyjnego i potrzebujesz do niego detektora, który możesz wyczyścić po odmontowaniu komponentu, to jest to. Czwarty argument storeFuncpowinien być Twoją zmienną ref. Wykorzystaj usuwanie swojego słuchacza w taki sposób, aby zacząć:useEffect(() => { return () => { window.removeEventListener('scroll', storeFunc, false); } }, [storeFunc])
Rob B
3

Jednym ze sposobów jest robienie tego za pomocą funkcji zewnętrznej :

elem.addEventListener('click', (function(numCopy) {
  return function() {
    alert(numCopy)
  };
})(num));

Ta metoda zawijania anonimowej funkcji w nawiasy i natychmiastowe jej wywoływanie nazywa się IIFE (wyrażenie funkcji natychmiast wywołane )

Możesz sprawdzić przykład z dwoma parametrami w http://codepen.io/froucher/pen/BoWwgz .

catimg.addEventListener('click', (function(c, i){
  return function() {
    c.meows++;
    i.textContent = c.name + '\'s meows are: ' + c.meows;
  }
})(cat, catmeows));
Felipe
źródło
3

Jeśli się nie mylę przy użyciu wywołania funkcji z bindfaktycznie tworzy nową funkcję, która jest zwracana przez bindmetodę. Spowoduje to problemy później lub jeśli chcesz usunąć detektor zdarzeń, ponieważ jest to w zasadzie funkcja anonimowa:

// Possible:
function myCallback() { /* code here */ }
someObject.addEventListener('event', myCallback);
someObject.removeEventListener('event', myCallback);

// Not Possible:
function myCallback() { /* code here */ }
someObject.addEventListener('event', function() { myCallback });
someObject.removeEventListener('event', /* can't remove anonymous function */);

Więc weź to pod uwagę.

Jeśli używasz ES6, możesz zrobić to samo, co sugerowane, ale nieco czystsze:

someObject.addEventListener('event', () => myCallback(params));
developer82
źródło
3

fajna alternatywa dla jednej linii

element.addEventListener('dragstart',(evt) => onDragStart(param1, param2, param3, evt));
function onDragStart(param1, param2, param3, evt) {

 //some action...

}
Gon82
źródło
2

Spróbuj także tych (IE8 + Chrome. Nie wiem dla FF):

function addEvent(obj, type, fn) {
    eval('obj.on'+type+'=fn');
}

function removeEvent(obj, type) {
    eval('obj.on'+type+'=null');
}

// Use :

function someFunction (someArg) {alert(someArg);}

var object=document.getElementById('somObject_id') ;
var someArg="Hi there !";
var func=function(){someFunction (someArg)};

// mouseover is inactive
addEvent (object, 'mouseover', func);
// mouseover is now active
addEvent (object, 'mouseover');
// mouseover is inactive

Mam nadzieję, że nie ma literówek :-)

DMike92
źródło
Jak trudno byłoby udzielić pełnej odpowiedzi? Czy powinienem to przetestować na FF? Nie zawracam sobie głowy ...
StefanNch
2

Utknąłem w tym, ponieważ używałem go w pętli do wyszukiwania elementów i dodawania do niego listy. Jeśli używasz go w pętli, to będzie działać idealnie

for (var i = 0; i < states_array.length; i++) {
     var link = document.getElementById('apply_'+states_array[i].state_id);
     link.my_id = i;
     link.addEventListener('click', function(e) {   
        alert(e.target.my_id);        
        some_function(states_array[e.target.my_id].css_url);
     });
}
Suneel Kumar
źródło
2

W 2019 r. Wiele zmian interfejsu API, najlepsza odpowiedź już nie działa, bez naprawienia błędu.

udostępnij trochę działającego kodu.

Inspirowany wszystkimi powyższymi odpowiedziami.

 button_element = document.getElementById('your-button')

 button_element.setAttribute('your-parameter-name',your-parameter-value);

 button_element.addEventListener('click', your_function);


 function your_function(event)
   {
      //when click print the parameter value 
      console.log(event.currentTarget.attributes.your-parameter-name.value;)
   }
hoogw
źródło
1

We wszystkich funkcjach znajduje się specjalna zmienna: argumenty . Możesz przekazać parametry jako parametry anonimowe i uzyskać do nich dostęp (według kolejności) za pomocą argumentów zmienną .

Przykład:

var someVar = some_other_function();
someObj.addEventListener("click", function(someVar){
    some_function(arguments[0]);
}, false);
StanE
źródło
Hmm ... Jaki jest powód głosowania? Jeśli nie było to, czego szukasz, wyjaśnij bardziej dokładnie, co masz na myśli (wiem, że odpowiedź na to pytanie została już udzielona). Ale czy mój kod nie odpowiada na to, o co prosiłeś? Specjalna zmienna „argumenty” daje dostęp do wszystkich parametrów wewnątrz funkcji.
StanE
1
    var EV = {
        ev: '',
        fn: '',
        elem: '',
        add: function () {
            this.elem.addEventListener(this.ev, this.fn, false);
        }
    };

    function cons() {
        console.log('some what');
    }

    EV.ev = 'click';
    EV.fn = cons;
    EV.elem = document.getElementById('body');
    EV.add();

//If you want to add one more listener for load event then simply add this two lines of code:

    EV.ev = 'load';
    EV.add();
Gennadiy Sherbakha
źródło
1

Poniższe podejście działało dla mnie dobrze. Zmodyfikowano stąd .

function callback(theVar) {
  return function() {
    theVar();
  }
}

function some_other_function() {
  document.body.innerHTML += "made it.";
}

var someVar = some_other_function;
document.getElementById('button').addEventListener('click', callback(someVar));
<!DOCTYPE html>
<html>
  <body>
    <button type="button" id="button">Click Me!</button>
  </body>
</html>

Nate
źródło
0

Poniższa odpowiedź jest poprawna, ale poniższy kod nie działa w IE8, jeśli załóżmy, że skompresowałeś plik js za pomocą yuicompressor. (W rzeczywistości nadal większość ludzi w USA używa IE8)

var someVar; 
someVar = some_other_function();
alert(someVar);
someObj.addEventListener("click",
                         function(){
                          some_function(someVar);
                         },
                         false);

Tak więc możemy rozwiązać powyższy problem w następujący sposób i działa dobrze we wszystkich przeglądarkach

var someVar, eventListnerFunc;
someVar = some_other_function();
eventListnerFunc = some_function(someVar);
someObj.addEventListener("click", eventListnerFunc, false);

Mam nadzieję, że byłby użyteczny dla kogoś, kto kompresuje plik js w środowisku produkcyjnym.

Powodzenia!!

Asik
źródło
0

Poniższy kod działał dla mnie dobrze (Firefox):

for (var i=0; i<3; i++) {
   element = new ...   // create your element
   element.counter = i;
   element.addEventListener('click', function(e){
        console.log(this.counter);
        ...            // another code with this element
   }, false);
}

Wynik:

0
1
2
Šerg
źródło
Co to jest na świecie?
NiCk Newman
0

Potrzebujesz:

newElem.addEventListener('click', {
    handleEvent: function (event) {
        clickImg(parameter);
    }
});
Victor Behar
źródło
0

Prawdopodobnie nie jest optymalny, ale wystarczająco prosty dla tych, którzy nie znają się na superjs. Umieść funkcję wywołującą addEventListener we własnej funkcji. W ten sposób wszelkie przekazane do niego wartości funkcji zachowują swój własny zakres i możesz iterować tę funkcję tyle, ile chcesz.

Przykład Pracowałem z odczytem pliku, ponieważ potrzebowałem przechwycić i wyświetlić podgląd obrazu i nazwy pliku. Zajęło mi trochę czasu, aby uniknąć problemów asynchronicznych podczas korzystania z typu przesyłania wielu plików. Przypadkowo zobaczyłem tę samą „nazwę” na wszystkich rendererach pomimo przesyłania różnych plików.

Pierwotnie cała funkcja readFile () znajdowała się w funkcji readFiles (). Spowodowało to problemy z asynchronicznym zasięgiem.

    function readFiles(input) {
      if (input.files) {
        for(i=0;i<input.files.length;i++) {

          var filename = input.files[i].name;

          if ( /\.(jpe?g|jpg|png|gif|svg|bmp)$/i.test(filename) ) {
            readFile(input.files[i],filename);
          }
       }
      }
    } //end readFiles



    function readFile(file,filename) {
            var reader = new FileReader();

            reader.addEventListener("load", function() { alert(filename);}, false);

            reader.readAsDataURL(file);

    } //end readFile
Spoo
źródło
0

chciałbym tylko dodać. jeśli ktoś dodaje funkcję, która aktualizuje pola wyboru do detektora zdarzeń, należy użyć event.targetzamiast thiszaktualizować pola wyboru.

Bruce Tong
źródło
0

Mam bardzo uproszczone podejście. To może działać dla innych, ponieważ pomogło mi. To jest ... Jeśli masz wiele elementów / zmiennych przypisanych do tej samej funkcji i chcesz przekazać odwołanie, najprostszym rozwiązaniem jest ...

function Name()
{

this.methodName = "Value"

}

Otóż ​​to. To zadziałało dla mnie. Tak prosty.

AAYUSH SHAH
źródło
-1

Inna alternatywa, być może nie tak elegancka jak użycie bind, ale jest ważna dla zdarzeń w pętli

for (var key in catalog){
    document.getElementById(key).my_id = key
    document.getElementById(key).addEventListener('click', function(e) {
        editorContent.loadCatalogEntry(e.srcElement.my_id)
    }, false);
}

Został przetestowany pod kątem rozszerzeń Google Chrome i być może e.srcElement musi zostać zastąpiony przez e.source w innych przeglądarkach

Znalazłem to rozwiązanie, używając komentarza opublikowanego przez Imatorię, ale nie mogę oznaczyć go jako przydatny, ponieważ nie mam wystarczającej reputacji: D

fhuertas
źródło
-1

To rozwiązanie może się przydać

var some_other_function = someVar => function() {
}

someObj.addEventListener('click', some_other_function(someVar));

lub wiążące wartości będą również dobre

fnclovers
źródło