Как передать аргументы функции прослушивателя addEventListener?

304

Ситуация в некотором роде

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

Проблема в том, что значение функции someVarне видно внутри функции слушателя addEventListener, где оно, вероятно, рассматривается как новая переменная.

Абхишек Ядав
источник
8
Очень четкая статья по этому вопросу: toddmotto.com/avoiding-anonymous-javascript-functions
Nobita
Не самый чистый способ, но делает работу. Обратите внимание, что если someVar может быть только цифрой или текстом: eval ('someObj.addEventListener ("click", function () {some_function (' + someVar + ');});');
Ignas2526
Только что была эта проблема сегодня - решение, приведенное здесь, является правильным (другие решения имеют проблемы, такие как проблема петли и т. Д.) - stackoverflow.com/a/54731362/984471
Manohar Reddy Poreddy

Ответы:

207

В написанном вами коде нет ничего плохого. Оба some_functionи someVarдолжны быть доступны, если они были доступны в контексте, где анонимно

function() { some_function(someVar); } 

был создан.

Проверьте, дает ли предупреждение значение, которое вы искали, убедитесь, что оно будет доступно в области анонимной функции (если только у вас нет кода, который работает с той же someVarпеременной рядом с вызовом addEventListener)

var someVar; 
someVar = some_other_function();
alert(someVar);
someObj.addEventListener("click", function(){
    some_function(someVar);
}, false);
Сергей Ильинский
источник
86
Это не работает для цикла. Я всегда получаю последнее значение, а не то, которое принадлежало этой итерации. Любое решение?
iMatoria
6
Кто-нибудь знает, почему это не работает в цикле? В чем причина такого поведения?
Морфидон
3
@Morfidon, потому что функция с глобальными переменными действует как замыкания в javascript, что означает, что они запоминают среду за пределами своей лексической области видимости. Если вы просто создадите разные функции в одной среде, они будут ссылаться на одну и ту же среду.
bugwheels94
16
@Morfidon: В цикле значение someVar - это не значение, которое оно имело при добавлении слушателя, а значение, которое оно имеет при выполнении слушателя. Когда слушатель выполняется, цикл уже завершен, поэтому значение someVar будет значением, которое он имел, когда цикл завершился.
www.admiraalit.nl
4
@iMatoria я только что обнаружил , что создание с bound functionпомощью .bind()метода позволит решить проблему с петлями developer.mozilla.org/en/docs/Web/JavaScript/Reference/...
Люк T О'Брайен
354

Почему бы просто не получить аргументы из целевого атрибута события?

Пример:

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 - это ориентированный на прототип язык, помните!

Zaloz
источник
16
Это правильный ответ, потому что он позволяет нам использовать после функции «removeEventListener».
user5260143
14
Не должно ли это быть evt.currentTarget.myParam? Если внутри 'someInput' есть другой элемент, это evt.targetможет быть ссылка на внутренний элемент. ( jsfiddle.net/qp5zguay/1 )
Гербертуш
Это сохраняет this! Использование этого метода в машинописи требует, чтобы элемент был anyили имел подтип.
Старый Бадман Грей
1
Мои переменные продолжают возвращаться как неопределенные ... есть мысли о том, как это исправить?
Nomaam
1
Если addEventListenerдля document, evt.target.myParamне работал для меня. Я должен был использовать evt.currentTarget.myParamвместо этого.
turrican_34
67

Этот вопрос старый, но я решил предложить альтернативу, использующую ES5 .bind () - для будущих поколений. :)

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

Просто имейте в виду, что вам нужно настроить функцию слушателя с первым параметром в качестве аргумента, который вы передаете в bind (ваша другая функция), а второй параметр теперь является событием (а не первым, как это было бы) ,

Брайан Мур
источник
1
Function.prototype.bind () - действительно лучший способ решить эту проблему. Кроме того, он работает интуитивно внутри циклов - вы получаете желаемую лексическую область видимости. Никаких анонимных функций, IIFE или специальных свойств, привязанных к объектам.
Клинт Пахл
Смотрите плюсы и минусы IIFE против bind () .
Клинт Пахл
1
При использовании Function.prototype.bind()вы не можете удалить прослушиватель событий , лучше вместо этого использовать функцию каррирования (см. Ответ @ tomcek112)
pldg
ПРИМЕЧАНИЕ: some_other_funcпеременная, вы можете передать любое значение, которое вы хотите some_func.
hitautodestruct
28

Вы можете просто связать все необходимые аргументы с помощью 'bind':

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

Таким образом, вы всегда получите event , arg1и другие вещи , переданные myPrettyHandler.

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

Александр Ткаленко
источник
Спасибо! Уже пробовал, .bind()но без нуля в качестве первого параметра. который не работал.
Larphoid
нет необходимости null, он отлично работает .bind(event, arg1), по крайней мере, с VueJS.
DevonDahon
27

Довольно старый вопрос, но у меня была такая же проблема сегодня. Самое чистое решение, которое я нашел, - это использовать концепцию карри.

Код для этого:

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

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

Называя функцию curried, она позволяет вам вызывать Object.removeEventListener для отмены регистрации eventListener в более позднее время выполнения.

tomcek112
источник
4
Рад встрече с этим ответом упоминания карри функции. Как бы вы удалили обработчик событий?
Боб
3
Здорово видеть хорошую терминологию. Вы должны иметь возможность удалить прослушиватель событий, назвав функцию curried. Я предложу изменить.
Мэтью Брент
Этот ответ регистрирует функцию столько раз, сколько вызывается addEventListener, поскольку some_function (var) каждый раз возвращает вновь созданную функцию.
Яхья
мне не нравится мысль о необходимости называть функцию curried, чтобы удалить слушателя, потому что вы имеете дело с двумя пространствами имен diff, которые вы должны отслеживать
oldboy
19

Вы можете добавлять и удалять списки событий с аргументами, объявив функцию как переменную.

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

newSrcэто метод с myaudio в качестве параметра funcNameявляется переменной имени функции

Вы можете удалить слушателя с помощью myaudio.removeEventListener('ended',func,false);

Ганеш Кришнан
источник
12

Вы можете передать somevar по значению (не по ссылке) через функцию JavaScript, известную как закрытие :

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

Или вы можете написать общую функцию обтекания, такую ​​как 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'

Вот wrapEventCallback(func,var1,var2)как:

func.bind(null, var1,var2)
ahuigo
источник
1
Большое спасибо за этот ответ! ОП не искал этого, но я думаю, что люди, которые введут «Как передать аргументы в addEventListener» в Google, будут искать ваш ответ. Просто нужно немного больше объяснений :) Я редактирую это.
Синдарус
9

someVarЗначение должно быть доступно только в some_function()контексте, а не из слушателя. Если вам нравится иметь его внутри слушателя, вы должны сделать что-то вроде:

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

и использовать newVarвместо.

Другой способ - вернуть someVarзначение из some_function()для дальнейшего использования в слушателе (как новая локальная переменная):

var someVar = some_function(someVar);
Thevs
источник
9

Вот еще один способ (этот работает внутри для циклов):

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

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

false);
Привет мир
источник
2
Это лучший способ. Ужасно, но эффективно внутри циклов, так как отправка аргумента в анонимную функцию захватит переменную.
Боб
9

Function.prototype.bind () - это способ привязать целевую функцию к определенной области и, при необходимости, определить thisобъект в целевой функции.

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

Или захватить часть лексической области видимости, например, в цикле:

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

Наконец, если thisпараметр не нужен в целевой функции:

someObj.addEventListener("click", some_function.bind(null, arg1, arg2), false);
eclos
источник
7

использование

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

И если вы хотите передать любое пользовательское значение в эту анонимную функцию, то самый простой способ сделать это

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

Отлично работает в моем проекте. Надеюсь, это поможет

КТА
источник
Да, определенно помогло, так как он также поддерживал ожидаемый объем в forцикле.
j4v1
4
    $form.addEventListener('submit', save.bind(null, data, keyword, $name.value, myStemComment));
    function save(data, keyword, name, comment, event) {

Вот как я получил событие прошло правильно.

chovy
источник
Отлично, так я почти закончил - просто ошибочно передал дополнительное событие в bind, когда его там нет (как в угловом), что автоматически происходит в этом случае.
Манохар Редди Поредди
3

Отправка аргументов в функцию обратного вызова eventListener требует создания изолированной функции и передачи аргументов этой изолированной функции.

Вот хорошая маленькая вспомогательная функция, которую вы можете использовать. На основе приведенного выше примера "Привет, мир!"

Также необходимо сохранить ссылку на функцию, чтобы мы могли аккуратно удалить слушателя.

// 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);
боб
источник
Это ответ 15-го года, но это именно то, что мне нужно для решения этой проблемы с помощью ловушки useRef. Если вы используете ловушку ref и вам нужен слушатель, который вы можете очистить при размонтировании компонента, то это оно. 4-й аргумент storeFuncдолжен быть вашей переменной ref. Поместите удаление слушателя в такой эффект, и все готово:useEffect(() => { return () => { window.removeEventListener('scroll', storeFunc, false); } }, [storeFunc])
Rob B
3

Один из способов сделать это с помощью внешней функции :

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

Этот метод обертывания анонимной функции в скобках и немедленного ее вызова называется IIFE немедленного ( немедленного функции)

Вы можете проверить пример с двумя параметрами в 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));
Фелипе
источник
3

Если я не ошибаюсь, использование вызова функции bindфактически создает новую функцию, которая возвращается bindметодом. Это вызовет проблемы позже или если вы захотите удалить прослушиватель событий, так как это в основном похоже на анонимную функцию:

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

Так что имейте это в виду.

Если вы используете ES6, вы можете сделать то же самое, что предложено, но немного чище:

someObject.addEventListener('event', () => myCallback(params));
developer82
источник
3

хорошая альтернатива в одну строку

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

 //some action...

}
Gon82
источник
2

Также попробуйте это (IE8 + Chrome. Я не знаю для 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

Надеюсь нет опечаток :-)

DMike92
источник
Насколько сложно было бы дать полный ответ? Должен ли я проверить это на FF? Ну, я не буду беспокоиться ...
StefanNch
2

Я застрял в этом, поскольку использовал его в цикле для поиска элементов и добавления к нему списка. Если вы используете его в цикле, то это будет отлично работать

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);
     });
}
Сунил Кумар
источник
2

В 2019 году много изменений API, лучший ответ больше не работает, без исправления ошибки.

поделитесь рабочим кодом.

Вдохновлен всем приведенным выше ответом.

 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
источник
1

Внутри всех функций есть специальная переменная: arguments . Вы можете передавать свои параметры как анонимные параметры и получать к ним доступ (по порядку) через переменную arguments .

Пример:

var someVar = some_other_function();
someObj.addEventListener("click", function(someVar){
    some_function(arguments[0]);
}, false);
Стане
источник
Хм ... В чем причина понижения? Если это было не то, что вы искали, то, пожалуйста, объясните более четко, что вы имеете в виду (я знаю, что на вопрос уже был дан ответ). Но разве мой код не отвечает на то, что вы просили? Специальная переменная «arguments» дает вам доступ ко всем параметрам внутри функции.
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();
Геннадий Щербаха
источник
1

Следующий подход работал хорошо для меня. Модифицировано отсюда .

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
источник
0

Следующий ответ правильный, но приведенный ниже код не работает в IE8, если предположить, что вы сжали файл js с помощью yuicompressor. (На самом деле, большинство людей в США все еще используют IE8)

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

Таким образом, мы можем исправить вышеуказанную проблему следующим образом, и она отлично работает во всех браузерах

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

Надеюсь, это будет полезно для тех, кто сжимает файл js в производственной среде.

Удачи!!

Asik
источник
0

Следующий код работал нормально для меня (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);
}

Вывод:

0
1
2
Serg
источник
Что в мире это?
NiCk Ньюман
0

Тебе нужно:

newElem.addEventListener('click', {
    handleEvent: function (event) {
        clickImg(parameter);
    }
});
Виктор Бехар
источник
0

Вероятно, не оптимально, но достаточно просто для тех, кто не супер смекалка. Поместите функцию, которая вызывает addEventListener, в свою собственную функцию. Таким образом, любые значения функций, передаваемые в него, сохраняют свою область видимости, и вы можете выполнять итерацию по этой функции столько раз, сколько захотите.

Пример Я работал с чтением файла, так как мне нужно было захватить и сделать предварительный просмотр изображения и имени файла. Мне потребовалось некоторое время, чтобы избежать асинхронных проблем при использовании нескольких типов загрузки файлов. Я бы случайно увидел одно и то же «имя» на всех рендерах, несмотря на загрузку разных файлов.

Первоначально все функции readFile () находились внутри функции readFiles (). Это вызвало асинхронные проблемы с областями видимости.

    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
источник
0

просто хотел бы добавить. если кто-то добавляет функцию, которая обновляет флажки для прослушивателя событий, вам придется использовать event.targetвместо thisобновления флажки.

Брюс Тонг
источник
0

У меня очень упрощенный подход. Это может работать для других, поскольку это помогло мне. Это ... Когда у вас есть несколько элементов / переменных, которым назначена одна и та же функция, и вы хотите передать ссылку, самое простое решение ...

function Name()
{

this.methodName = "Value"

}

Вот и все. Это сработало для меня. Так просто.

ААЮШ ШАХ
источник
-1

Другая альтернатива, возможно, не такая элегантная, как использование bind, но она действительна для событий в цикле

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

Он был протестирован для расширений Google Chrome и, возможно, e.srcElement должен быть заменен на e.source в других браузерах.

Я нашел это решение, используя комментарий, опубликованный Imatoria, но не могу пометить его как полезное, потому что у меня недостаточно репутации: D

fhuertas
источник
-1

Это решение может хорошо смотреться

var some_other_function = someVar => function() {
}

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

или связывать валидные тоже будет хорошо

fnclovers
источник