JavaScript事件处理程序带参数

127

我想创建一个 eventHandler,它可以传递事件和一些参数。问题是函数没有获取到元素。以下是一个示例:

doClick = function(func){
    var elem = .. // the element where it is all about
    elem.onclick = function(e){
        func(e, elem);
    }
}
doClick(function(e, element){
    // do stuff with element and the event
});

elem必须在匿名函数之外定义。我怎样才能在匿名函数内使用传递的元素?有什么方法可以做到这一点吗?

那么addEventListener呢?似乎我无法通过addEventListener传递事件,是吗?

更新:

我好像用 'this' 解决了这个问题。

doClick = function(func){
    var that = this;
    this.element.onclick = function(e){
        func(e, that);
    }
}

在这里,this.element 包含了可以在函数中访问的内容。

addEventListener

但是我想知道关于 addEventListener

function doClick(elem, func){
    element.addEventListener('click', func(event, elem), false);
}

对不起,我试图举一个例子来说明我的情况,但效果不佳,我已经更新了代码。 - sebas2day
你是否在寻找 e.target / e.currentTarget - Rolf
1
如果我正在使用以下代码,如何发送参数: ok.addEventListener("click", this.changeText.bind(this) ); 答:如何发送参数? - Alberto Acuña
9个回答

135

我不完全理解你的代码尝试做什么,但是你可以利用函数闭包的优势在任何事件处理程序中使变量可用:

function addClickHandler(elem, arg1, arg2) {
    elem.addEventListener('click', function(e) {
        // in the event handler function here, you can directly refer
        // to arg1 and arg2 from the parent function arguments
    }, false);
}

根据您的具体编程情况,您几乎总是可以创建某种类型的闭包来为您保留对变量的访问。

根据您的评论,如果您想要完成以下操作:

element.addEventListener('click', func(event, this.elements[i]))

然后,你可以通过一个自执行函数(IIFE)来实现,该函数在执行时捕获你想要的参数并将其保存在闭包中,并返回实际的事件处理函数:

element.addEventListener('click', (function(passedInElement) {
    return function(e) {func(e, passedInElement); };
}) (this.elements[i]), false);

关于 IIFE 的更多信息,请参见以下其他参考资料:

JavaScript 将代码包装在匿名函数中

JavaScript 立即调用的函数表达式(IIFE)- 传递 jQuery

JavaScript 自执行匿名函数的好用例子是什么?

最后一个版本可以这样更容易看出它正在做什么:

// return our event handler while capturing an argument in the closure
function handleEvent(passedInElement) {
    return function(e) {
        func(e, passedInElement); 
    };
}

element.addEventListener('click', handleEvent(this.elements[i]));

使用.bind()也可以在回调函数中添加参数。你传递给.bind()的任何参数将会被添加到回调函数本身即将拥有的参数之前。所以,你可以这样做:

elem.addEventListener('click', function(a1, a2, e) {
    // inside the event handler, you have access to both your arguments
    // and the event object that the event handler passes
}.bind(elem, arg1, arg2));

7
@sebas2day - 你不能这样做:element.addEventListener('click', func(event, this.elements[i]))。JavaScript不支持这种方式。虽然可以使用闭包等其他方法来实现类似效果,但你不能按照你写的方式来做。addEventListener调用回调函数时只传递一个参数(事件),无法改变它的行为。 - jfriend00
我在答案的末尾添加了另一个做这个的示例。 - jfriend00
1
@jfriend00 你可以使用bind方法。 function eventFunction (arg, evt) { console.log(arg[0],arg[1],arg[2]) console.log(evt) } var el = document.getElementById('elementID'); el.addEventListener('click', eventFunction.bind(el,[1,2,3]))请注意,事件参数始终是函数参数中的最后一个,并且在bind方法中没有明确声明。 - Knight Yoshi
@jfriend00,我知道已经过去3年了,但你(或其他人)能解释一下最后一行吗? "} (this.elements[i]) , false);" 参数“this.elements[i]”是如何传递的?我知道它可以工作,但我没有找到任何关于为什么它可以工作的信息,甚至不知道这种JavaScript在函数定义之后传递值的能力被称为什么。 - Oscar Vasquez
2
@Bosh19 - 看看我在答案末尾添加的内容,来解释你所问的结构。这是一个立即调用的函数表达式(IIFE),它本质上是一个匿名声明的函数,会立刻执行。这是定义函数并立即调用的快捷方式,在你想将函数体嵌入行内且不会在其他任何地方被调用时非常有用,因此不需要名称。 - jfriend00
显示剩余3条评论

57

这是一个旧问题,但是常见的一个。所以让我在这里补充一下。

使用箭头函数语法可以更简洁地实现它,因为它是词法绑定的并且可以被链接。

箭头函数表达式是普通函数表达式的一种语法上紧凑的替代方式,虽然没有自己的 this、arguments、super 或 new.target 绑定。

const event_handler = (event, arg) => console.log(event, arg);
el.addEventListener('click', (event) => event_handler(event, 'An argument'));

如果您需要清理事件监听器:

// Let's use use good old function sytax
function event_handler(event, arg) {
  console.log(event, arg);
}

// Assign the listener callback to a variable
var doClick = (event) => event_handler(event, 'An argument'); 

el.addEventListener('click', doClick);

// Do some work...

// Then later in the code, clean up
el.removeEventListener('click', doClick);

这是一个疯狂的一行代码:

// You can replace console.log with some other callback function
el.addEventListener('click', (event) => ((arg) => console.log(event, arg))('An argument'));

更加温顺的版本: 适用于任何理智的工作。

el.addEventListener('click', (event) => ((arg) => {
  console.log(event, arg);
})('An argument'));

3
这似乎是一个合理的解决方案,但当您必须删除相同的事件侦听器时该怎么办? - Dushyant Sabharwal
@SnnSnn:+1。我用这个来为事件处理程序提供类属性。我只需传入 this,处理程序就可以访问它所需的内容。 - Robotronx
1
如果我理解正确的话,你实际上会将一个匿名函数作为事件处理程序传递:(e) => { .... } 而不是你的EventHandlerFunction()。这会导致问题,如果您希望以后删除事件处理程序,因为您一开始就传递了一个匿名函数??? - PowerAktar
答案清楚地解释了如何移除事件监听器,所以没有问题。 - snnsnn

11

你可以尝试使用bind方法,我认为这可以实现你要求的功能。如果没有其他办法,它仍然非常有用。

function doClick(elem, func) {
  var diffElem = document.getElementById('some_element'); //could be the same or different element than the element in the doClick argument
  diffElem.addEventListener('click', func.bind(diffElem, elem))
}

function clickEvent(elem, evt) {
  console.log(this);
  console.log(elem); 
  // 'this' and elem can be the same thing if the first parameter 
  // of the bind method is the element the event is being attached to from the argument passed to doClick
  console.log(evt);
}

var elem = document.getElementById('elem_to_do_stuff_with');
doClick(elem, clickEvent);

3

简短回答:

x.addEventListener("click", function(e){myfunction(e, param1, param2)});

... 

function myfunction(e, param1, param1) {
    ... 
} 

2
这个不起作用。如果您的参数值每次事件处理程序设置时都会更改,那么它将产生意外结果,因为它没有关闭param1param2的值。如果这些值发生变化,它们将不会被设置为事件处理程序创建时的值。它们只会被设置为当前值。此外,这与在事件监听器函数范围之外设置变量并在事件处理程序中引用该变量没有任何区别,因此根本无法实现传递参数。 - TetraDev
1
不,它不起作用。我按照你的方式进行了测试,我的观点仍然有效。如果 param1 第一次调用 addEventListener 时是 noodle,第二次调用时 paramcheese,那么无论何时事件监听器 click 触发,param1 都将被设置为 cheese 而不是 noodle。因此,它不使用“闭包”来记住添加事件监听器时存在的值。有些情况下需要多次添加事件监听器,这不需要说明也能证明我的观点是正确的。 - TetraDev
1
关于闭包,请花几分钟时间阅读此文,它会让您成为更熟练的JavaScript程序员:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures“而且它确实会改变,因为这就是将参数传递给函数的目的。”对于事件处理程序来说,这并不正确。一个“click”事件处理程序在发生点击时不会传递任何函数参数。因此,在声明时的值很重要。我会发布一个js片段供您看看我的意思。 - TetraDev
1
好的,实际上,在重新创建我的场景后,您是正确的,您所描述的方法确实有效,我提到的“闭包”方法也是如此。请参见此代码沙箱:https://codesandbox.io/s/6vpm3q95z3请注意,它将立即打开3个带有单个按钮的弹出窗口。 单击该按钮将在codesandbox控制台中打印弹出窗口实例的名称和一些传递给它的参数。 单击codesandbox底部的“控制台”按钮以查看。 此外,单击6个文本段落也会打印一些参数。 看起来两种方法都可以正常工作。 - TetraDev
2
谢谢您查看,我一定也会访问您发布的链接。此外,我必须说您非常有礼貌和诚实,先生,请继续保持并祝您有愉快的一天 :) - gpa
显示剩余7条评论

2
鉴于原始问题的更新,似乎在传递事件处理程序时存在上下文(“this”)问题。 基础知识在这里解释: http://www.w3schools.com/js/js_function_invocation.asp 您的示例的简单工作版本可能如下:
var doClick = function(event, additionalParameter){
    // do stuff with event and this being the triggering event and caller
}

element.addEventListener('click', function(event)
{
  var additionalParameter = ...;
  doClick.call(this, event, additionalParameter );
}, false);

请参见JavaScript call() & apply() vs bind()?

1
尝试,
const handleOnChange = (event, otherParameter) => {
    console.log(`${event.target.value}`);
    console.log(`${otherParameter}`);
}

<Select onchange= (event) => {
    handleOnChange(event, otherParameter);
}>

0

doThings内的this是窗口对象。请尝试使用以下代码:

var doThings = function (element) {
    var eventHandler = function(ev, func){
        if (element[ev] == undefined) {
            return;
        }

        element[ev] = function(e){
            func(e, element);
        }
    };

    return {
        eventHandler: eventHandler
    };
};

0

这是一个旧话题,@jfriend00非常好地解释了闭包函数调用背后的基础知识。

让我们将代码片段更新到ES6,并使用另一种技术来帮助我们克服这种情况。

首先,让我们定义一个函数handler,这个函数将接收两个参数fncval,其中fnc是处理事件的自定义函数,通常命名为on+theEventName,在这种情况下是onClickEvent。这里没有什么新东西。现在,让我们在元素red上定义onclick监听器,并将其称为clickEventHandler,如下所示:

red.onclick = clickEventHandler

正如我们注意到的那样,clickEventHandler没有任何参数。Teo,我该如何将eventindex值发送到处理程序中呢?

好问题,首先让我们调用handler函数来创建我们自定义的onClickEvent处理程序,就像这样:

handler(onClickEvent, index)

但是Teo,onClickEvent也没有参数,我们怎么发送闭包函数onClickEvent以及由onclick产生的eventindex值?
耐心点,我的年轻学徒。首先,哪个函数将处理event?在我们的场景中,将是onClickEvent,所以让我们定义此函数来接收eventeindexv
function onClickEvent(e, v) {
    console.log(`${e.currentTarget.className} -> [x: ${e.x}, y: ${e.y}] | val: ${v}`)
}

现在让我们来实现我们的处理函数。这个函数将返回一个闭包函数的定义,该闭包函数将执行在fnc中定义的函数,并将闭包函数接收的参数和val参数合并,如下所示:
function handler(fnc, val) {
    return (...params) => {
    const ctx = this
    const arg = [...params, val]
    fnc.apply(ctx, arg)
    }
}

为了调用fnc函数,我们使用apply()方法。该方法使用给定的上下文ctx和作为数组提供的参数arg来调用指定的函数。因此,fnc函数在闭包函数上下文ctx中执行。

以下是此场景的工作示例。

const red = document.querySelector('.red')
const blue = document.querySelector('.blue')

function handler(fnc, val) {
  return (...params) => {
    const ctx = this
    const arg = [...params, val]
    fnc.apply(ctx, arg)
  }
}

function onClickEvent(e, v) {
  console.log(`${e.currentTarget.className} -> [x: ${e.x}, y: ${e.y}] | val: ${v}`)
}

const index = 50

// Define the handler function
const clickEventHandler = handler(onClickEvent, index)

// Call the debounced function on every mouse move
red.onclick = clickEventHandler

blue.addEventListener('click', clickEventHandler)
.boxes {
  width: 100%;
  height: 120px;
  display: flex;
  flex-flow: row nowrap;
}

.red {
  background: red;
}

.blue {
  background: blue;
}

.box {
  width: 50%;
  display: flex;
  justify-content: center;
  align-items: center;
  color: white;
}
<div class='boxes'>
  <div class='red box'>Click Me</div>
  <div class='blue box'>Not ready</div>
</div>

现在让我们将这个概念应用到您的情况:

const red = document.querySelector('.red')
const blue = document.querySelector('.blue')
red.clicked = false

let clickEventHandler

function handler(fnc, val) {
  return (...params) => {
    const ctx = this
    const arg = [...params, val]
    fnc.apply(ctx, arg)
  }
}

function onRedClickEvent(e) {
  if (red.clicked) return

  red.clicked = true
  red.textContent = 'Clicked'
  blue.textContent = 'Ready'
  console.log(`${e.currentTarget.className} -> blue is ready`)

  // Define the handler function
  clickEventHandler = handler(onClickEvent, red)

  // Call the debounced function on every mouse move
  blue.addEventListener('click', clickEventHandler)
}

function onClickEvent(e, elem) {
  red.clicked = false
  red.textContent = 'Click Me'
  blue.textContent = 'Not Ready'

  console.log(`${e.currentTarget.className} -> [x: ${e.x}, y: ${e.y}] | elem: ${elem.className}`)

  blue.removeEventListener('click', clickEventHandler)
}



red.onclick = onRedClickEvent
.boxes {
  width: 100%;
  height: 120px;
  display: flex;
  flex-flow: row nowrap;
}

.red {
  background: red;
}

.blue {
  background: blue;
}

.box {
  width: 50%;
  display: flex;
  justify-content: center;
  align-items: center;
  color: white;
  user-select: none;
}
<div class='boxes'>
  <div class='red box'>Click Me</div>
  <div class='blue box'>Not ready</div>
</div>


-2
let obj = MyObject();

elem.someEvent( function(){ obj.func(param) } );

//calls the MyObject.func, passing the param.

参数也可以是回调函数(在-say-全局上下文中),因此可以轻松地回调,并使用请求的参数。 - Baznr

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接