Como passar argumentos para a função de ouvinte addEventListener?

304

A situação é um pouco parecida.

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

O problema é que o valor de someVarnão é visível dentro da função listener do addEventListener, onde provavelmente está sendo tratado como uma nova variável.

Abhishek Yadav
fonte
8
Um artigo muito claro sobre o assunto: toddmotto.com/avoiding-anonymous-javascript-functions
Nobita
Não é a maneira mais limpa, mas faz o trabalho. Observe que, se someVar puder ser apenas um dígito ou texto: eval ('someObj.addEventListener ("click", function () {some_function (' + someVar + ');});');
Ignas2526
Apenas tive esse problema hoje - solução dada aqui é correto (outras soluções têm problemas como a emissão loop, etc.) - stackoverflow.com/a/54731362/984471
Manohar Reddy Poreddy

Respostas:

207

Não há absolutamente nada de errado com o código que você escreveu. Ambos some_functione someVardevem estar acessíveis, caso estejam disponíveis no contexto em que

function() { some_function(someVar); } 

foi criado.

Verifique se o alerta fornece o valor que você estava procurando, verifique se ele estará acessível no escopo da função anônima (a menos que você tenha mais código que opera na mesma someVarvariável ao lado da chamada para addEventListener)

var someVar; 
someVar = some_other_function();
alert(someVar);
someObj.addEventListener("click", function(){
    some_function(someVar);
}, false);
Sergey Ilinsky
fonte
86
Isso não funciona no loop for. Eu sempre recebo o valor mais recente e não o que pertencia a essa iteração. Qualquer solução?
iMatoria 25/06
6
Alguém sabe por que não funciona em loop? Qual é a razão desse comportamento?
Morfidon 15/05
3
@Morfidon porque a função com variáveis ​​globais atua como fechamento em javascript, o que significa que elas lembram o ambiente fora de seu escopo lexical. Se você apenas criar uma função diferente no mesmo ambiente, eles farão referência ao mesmo ambiente.
bugwheels94
16
@Morfidon: Em um loop, o valor de someVar não é o valor que tinha quando o ouvinte foi adicionado, mas o valor que ele tem quando o ouvinte é executado. Quando o ouvinte é executado, o loop já terminou, portanto o valor de someVar será o valor que tinha quando o loop terminou.
Www.admiraalit.nl
4
@iMatoria Acabo de descobrir que a criação de uma bound functionusando o .bind()método vai resolver a questão com loops developer.mozilla.org/en/docs/Web/JavaScript/Reference/...
Luke T O'Brien
353

Por que não apenas obter os argumentos do atributo target do evento?

Exemplo:

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 é uma linguagem orientada a protótipo, lembre-se!

Zaloz
fonte
16
Esta é a resposta correta, pois ela nos permite usar após a função 'removeEventListener'.
user5260143
14
Não deveria ser evt.currentTarget.myParam? Se houver outro elemento dentro de 'someInput', evt.targetpode ser que se refira ao elemento interno. ( jsfiddle.net/qp5zguay/1 )
Herbertusz
Isso preserva this! O uso desse método no texto datilografado exige que o elemento seja anyou crie um subtipo.
Old Badman Grey
1
Minhas variáveis ​​continuam voltando como indefinidas ... alguma idéia de como consertar isso?
nomaam 5/09/18
1
Se addEventListenerfor para document, evt.target.myParamnão funcionou para mim. Eu tive que usar em seu evt.currentTarget.myParamlugar.
8609 Turtle_34
67

Esta pergunta é antiga, mas pensei em oferecer uma alternativa usando o .bind () do ES5 - para a posteridade. :)

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

Lembre-se de que você precisa configurar sua função de ouvinte com o primeiro parâmetro como o argumento que você está passando para o bind (sua outra função) e o segundo parâmetro agora é o evento (em vez do primeiro, como teria sido) .

Brian Moore
fonte
1
Function.prototype.bind () é realmente a melhor maneira de resolver esse problema. Além disso, ele funciona intuitivamente dentro de loops - você obtém o escopo lexical desejado. Nenhuma função anônima, IIFEs ou propriedades especiais associadas aos objetos.
Clint Pachl
Veja os prós e contras de IIFE vs bind () .
Clint Pachl
1
Usando Function.prototype.bind()você não pode remover o ouvinte de evento , melhor usar uma função currying vez (ver @ tomcek112 resposta)
PLDG
NOTA: some_other_funcé uma variável, você pode passar o valor que desejar some_func.
hitautodestruct
28

Você pode simplesmente ligar todos os argumentos necessários com 'bind':

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

Desta forma, você sempre terá a event, arg1e outras coisas passadas para myPrettyHandler.

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

Oleksandr Tkalenko
fonte
Obrigado! Já havia tentado, .bind()mas sem o nulo como primeiro parâmetro. o que não funcionou.
Larphoid
sem necessidade null, ele funciona bem com .bind(event, arg1), pelo menos com o VueJS.
DevonDahon 18/10/19
27

Questão antiga e antiga, mas tive o mesmo problema hoje. A solução mais limpa que encontrei é usar o conceito de curry.

O código para isso:

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

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

Ao nomear a função ao curry, é possível chamar Object.removeEventListener para cancelar o registro do eventListener em um momento de execução posterior.

tomcek112
fonte
4
Fico feliz em encontrar esta resposta mencionando a função ao curry. Como você removeria o ouvinte de eventos?
24917 bob
3
Incrível ver uma boa terminologia. Você poderá remover o ouvinte de eventos nomeando a função ao curry. vou propor uma edição.
Matthew Brent
Esta resposta registrará a função quantas vezes for chamado addEventListener, pois some_function (var) está retornando uma função recém-criada toda vez.
Yahia
Eu não gosto da idéia de ter nomear a função curry, a fim de remover o ouvinte cuz então ur negociação com 2 namespaces diff que u tenho que manter o controle de
Oldboy
19

Você pode adicionar e remover listener de eventos com argumentos declarando uma função como uma variável.

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

newSrcé o método com myaudio como parâmetro funcNameé a variável do nome da função

Você pode remover o ouvinte com myaudio.removeEventListener('ended',func,false);

Ganesh Krishnan
fonte
12

Você pode passar algum valor por valor (não por referência) por meio de um recurso javascript conhecido como encerramento :

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

Ou você pode escrever uma função de quebra automática comum, como 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'

Aqui wrapEventCallback(func,var1,var2)está como:

func.bind(null, var1,var2)
ahuigo
fonte
1
Muito obrigado por esta resposta! O OP não estava procurando por isso, mas acho que as pessoas que digitarem "Como passar argumentos para addEventListener" no Google estarão procurando sua resposta. Só precisa de um pouco mais de explicação :) Estou editando.
Sindarus 10/08/16
9

someVarO valor deve ser acessível apenas no some_function()contexto, não no ouvinte. Se você deseja tê-lo no ouvinte, deve fazer algo como:

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

e use em seu newVarlugar.

A outra maneira é retornar o someVarvalor some_function()para usá-lo ainda mais no listener (como uma nova var local):

var someVar = some_function(someVar);
Thevs
fonte
9

Aqui está outra maneira (esta funciona dentro de loops):

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

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

false);
Olá Mundo
fonte
2
Esta é a melhor maneira. Feio, mas eficaz dentro de loops, pois o envio de um argumento para uma função anônima capturará o var.
bob
9

Function.prototype.bind () é a maneira de vincular uma função de destino a um escopo específico e, opcionalmente, definir o thisobjeto na função de destino.

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

Ou para capturar parte do escopo lexical, por exemplo, em um loop:

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

Por fim, se o thisparâmetro não for necessário na função de destino:

someObj.addEventListener("click", some_function.bind(null, arg1, arg2), false);
eclos
fonte
7

Usar

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

E se você deseja passar qualquer valor personalizado para essa função anônima, a maneira mais fácil de fazer isso é

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

Funciona perfeitamente no meu projeto. Espero que isso ajude

kta
fonte
Sim, definitivamente ajudou, pois também manteve o escopo esperado dentro de um forloop.
j4v1 25/03
4
    $form.addEventListener('submit', save.bind(null, data, keyword, $name.value, myStemComment));
    function save(data, keyword, name, comment, event) {

É assim que eu recebo o evento corretamente.

chovy
fonte
Excelente, é assim que eu estava quase concluindo - apenas um evento extra passado incorretamente no bind quando ele não está lá (como no angular), o que ocorre automaticamente neste caso.
Manohar Reddy Poreddy 20/10/19
3

O envio de argumentos para a função de retorno de chamada de eventListener requer a criação de uma função isolada e a passagem de argumentos para essa função isolada.

Aqui está uma pequena função auxiliar que você pode usar. Com base no exemplo "olá mundo" acima.)

Uma coisa que também é necessária é manter uma referência à função para que possamos remover o ouvinte corretamente.

// 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);
prumo
fonte
Esta resposta é de '15, mas é exatamente o que eu precisava para lidar com esse problema usando um gancho useRef. Se você estiver usando um gancho ref e precisar de um ouvinte para que possa limpar a desmontagem de componentes, é isso. O quarto argumento a storeFuncdeve ser sua variável ref. Coloque a remoção do seu ouvinte em um useEffect como este e você está pronto para ir:useEffect(() => { return () => { window.removeEventListener('scroll', storeFunc, false); } }, [storeFunc])
Rob B
3

Uma maneira é fazer isso com uma função externa :

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

Esse método de agrupar uma função anônima entre parênteses e chamá-la imediatamente é chamado de IIFE (Expressão de Função Imediatamente Invocada)

Você pode verificar um exemplo com dois parâmetros em 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
fonte
3

Se não me engano, chamar a função com bindcria uma nova função retornada pelo bindmétodo Isso causará problemas mais tarde ou se você deseja remover o ouvinte de eventos, pois é basicamente como uma função anônima:

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

Então, tenha isso em mente.

Se você estiver usando o ES6, poderá fazer o mesmo que o sugerido, mas um pouco mais limpo:

someObject.addEventListener('event', () => myCallback(params));
developer82
fonte
3

boa alternativa de uma linha

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

 //some action...

}
Gon82
fonte
2

Tente também estes (IE8 + Chrome. Não sei por 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

Espero que não haja erros de digitação :-)

DMike92
fonte
Quão difícil seria colocar uma resposta completa? Devo testar isso no FF? Bem, eu não vou incomodar ...
StefanNch
2

Eu estava preso nisso enquanto o usava em um loop para encontrar elementos e adicionar listner a ele. Se você estiver usando em loop, isso funcionará perfeitamente

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

Em 2019, muitas alterações na API, a melhor resposta não funciona mais, sem correção de bug.

compartilhe algum código de trabalho.

Inspirado por todas as respostas acima.

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

Há uma variável especial dentro de todas as funções: argumentos . Você pode passar seus parâmetros como parâmetros anônimos e acessá-los (por ordem) através da variável de argumentos .

Exemplo:

var someVar = some_other_function();
someObj.addEventListener("click", function(someVar){
    some_function(arguments[0]);
}, false);
StanE
fonte
Hmm ... Qual o motivo do voto negativo? Se não era o que você procurava, explique com mais clareza o que você quer dizer (sei que a pergunta já foi respondida). Mas o meu código não está respondendo ao que você pediu? A variável especial "argumentos" fornece acesso a todos os parâmetros dentro de uma função.
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
fonte
1

A abordagem a seguir funcionou bem para mim. Modificado a partir daqui .

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

A resposta a seguir está correta, mas o código abaixo não está funcionando no IE8 se suponha que você compactou o arquivo js usando o yuicompressor. (De fato, ainda a maioria dos povos dos EUA usa o IE8)

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

Portanto, podemos corrigir o problema acima da seguinte maneira e ele funciona bem em todos os navegadores

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

Espero que seja útil para alguém que está compactando o arquivo js no ambiente de produção.

Boa sorte!!

Asik
fonte
0

O código a seguir funcionou bem para mim (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);
}

Resultado:

0
1
2
Šerg
fonte
O que é isso no mundo?
NiCk Newman 06/07/2015
0

Você precisa:

newElem.addEventListener('click', {
    handleEvent: function (event) {
        clickImg(parameter);
    }
});
Victor Behar
fonte
0

Provavelmente não é o ideal, mas é simples o suficiente para aqueles que não são super experientes. Coloque a função que chama addEventListener em sua própria função. Dessa forma, quaisquer valores de função passados ​​para ele mantêm seu próprio escopo e você pode iterar sobre essa função quantas vezes quiser.

Exemplo Eu trabalhei com a leitura de arquivos, pois precisava capturar e renderizar uma visualização da imagem e do nome do arquivo. Levei um tempo para evitar problemas assíncronos ao utilizar um tipo de upload de vários arquivos. Eu veria acidentalmente o mesmo 'nome' em todas as renderizações, apesar de enviar arquivos diferentes.

Originalmente, toda a função readFile () estava dentro da função readFiles (). Isso causou problemas de escopo assíncrono.

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

apenas gostaria de adicionar. se alguém estiver adicionando uma função que atualize caixas de seleção para um ouvinte de evento, você precisará usar em event.targetvez de thisatualizar as caixas de seleção.

Bruce Tong
fonte
0

Eu tenho uma abordagem muito simplista. Isso pode funcionar para os outros, pois me ajudou. É ... Quando você está tendo vários elementos / variáveis ​​atribuídos a uma mesma função e deseja passar a referência, a solução mais simples é ...

function Name()
{

this.methodName = "Value"

}

É isso aí. Funcionou para mim. Tão simples.

AAYUSH SHAH
fonte
-1

Outra alternativa, talvez não tão elegante quanto o uso de bind, mas é válida para eventos em um loop

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

Foi testado para extensões do google chrome e talvez o e.srcElement deva ser substituído pelo e.source em outros navegadores

Encontrei esta solução usando o comentário postado por Imatoria, mas não posso marcar como útil porque não tenho reputação suficiente: D

fhuertas
fonte
-1

Esta solução pode ser boa para procurar

var some_other_function = someVar => function() {
}

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

ou vincular valores válidos também será bom

fnclovers
fonte