Forum Moderators: open

Message Too Old, No Replies

jQuery .on followed by function(), why?

         

csdude55

6:39 pm on Nov 12, 2022 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



I have code that looks like this:

$(element)
.mouseover(function() {
console.log('a')
})
.mouseout(console.log('b'))
.click(console.log('c'));


The question I have is about the function() { ... } bit.

When I leave off the function() bit (like I did with .mouseout and .click), the contents run immediately instead of waiting for the event. Meaning, when I load the page, "b" and "c" show in the console without any triggers.

Assuming that this is a feature and not a bug, can you suggest a time that this would ever be used?

If not, why is it written so that I have to manually type function() { ... } every single time?

The logic doesn't make sense to me. It reads to me like, "on click, run this". I can't see the value in it reading like, "on click, create a nameless function and then run it".

Fotiman

3:43 am on Nov 13, 2022 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



It's not really a feature. It's more of a bug in your implementation.

$(element)

You're executing a function there (passing in element as an argument). That function returns an object which has functions like mouseover and mouseout. You're then immediately invoking the mouseover function, passing in the function that will be executed when the mouseover event is triggered. The mouseover function returns the same object again (which allows chaining), and then you're invoking the mouseout function. Because of the order of operations, if you pass in an expression in the parenthesis (as you did with mouseout), that expression is evaluated first, and the result of that expression is what you're passing as the first argument to mouseout. In this case, you're executing console.log('b'), and then passing the return value of that (undefined) to mouseout. So you'll see the console.log('b') execute once, but since you passed undefined to your mouseout function, it doesn't have a function to call when that event is triggered. Likewise for click.

Does that help any? Note, the function you pass in doesn't need to be nameless. You could always do something like this:

function outputA() {
console.log('a')
}
function outputB() {
console.log('b')
}
$(element)
.mouseover(outputA)
.mouseout(outputB)
.click(() => console.log('c'));

csdude55

8:23 pm on Nov 13, 2022 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



you're executing console.log('b'), and then passing the return value of that (undefined) to mouseout

I see. So it only returns the return value of what was ran, instead of actually running it?

Can you suggest a real world example where that might be useful?

Note, the function you pass in doesn't need to be nameless.

That's where I get lost. In my mind, console.log is a named function, so I can't see why "outputA()" runs but "console.log()" doesn't.

robzilla

11:34 pm on Nov 13, 2022 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



Once your code is loaded, the interpreter instantly creates a bunch of event listeners through binders like mouseover() and click(). Those binders expect to receive a function to execute once the event fires (docs [api.jquery.com]). This can be a new, anonymous function or a reference to a named function, but it has to be a function, because something has to happen once the event fires.

Importantly, console.log() is not a function but the invocation of a function (because of the parentheses). So at runtime, if you've written something like mouseover(console.log('X')) then what you're passing to mouseover() is actually the return value of console.log(), i.e. nothing or "undefined", rather than a reference to a function to invoke once the event fires. In this case that won't work, because the binder expects a function.

So it only returns the return value of what was ran, instead of actually running it?

No, it does both, but only at runtime. console.log('b') is executed as runtime and its return value is stored (again at runtime) as the value to pass to mouseout. Then when the mouseout event later fires, that return value will be read by the event listener. It will not execute console.log('b') again. The value will be "undefined" so nothing will happen.

console.log is a named function

Yes, but if you were to reference it like mouseover(console.log) you wouldn't be able to pass any parameters, i.e. something to actually log to the console, because mouseover(console.log('whatever')) would invoke the function. To work around that, you can create an anonymous function like mouseover(function() { console.log('whatever'); }).

csdude55

5:00 am on Nov 14, 2022 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



I gotcha. OK, that makes sense. Thanks for the explanations!

csdude55

9:54 pm on Nov 14, 2022 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



Note, the function you pass in doesn't need to be nameless.

BTW, @Fotiman. I tried this like you suggested, but it still ran the function at runtime instead of when the element is triggered. The actual code I used was:

function buttonClick() {
console.log('made it to .button click');
}

$('.button').click(buttonClick());

Any thoughts on that?

robzilla

10:52 pm on Nov 14, 2022 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



As noted earlier, buttonClick() invokes the function, so you're again passing the return value (nothing) as the function to run when the click event fires, rather than a reference to any existing function (like buttonClick).

Use $('.button').click(buttonClick); instead.

csdude55

11:25 pm on Nov 14, 2022 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



Ohhhh, I see. The infamous () is what messed me up.

Since we're clarifying, is there a way to send (e) (or presumably other variables) with this format? Or would I have to use the lengthier $('.button').click((e) => buttonClick(e));?

robzilla

11:37 pm on Nov 14, 2022 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



Or would I have to use the lengthier $('.button').click((e) => buttonClick(e));?

I thought so but if I read the docs correctly now you might also be able to do $('.button').click(e, buttonClick);

[api.jquery.com...]

Not sure, though, can't test it right now.

(It would be passed as the first parameter to the buttonClick function.)

csdude55

1:07 am on Nov 15, 2022 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



This part is becoming overly complicated... starting with your suggestion and going down the rabbit hole, I got this to work:

$('.button')
.click({ name: "foo" }, buttonClick);

function buttonClick(bar) {
console.log('the param is ' + bar.data.name);
}

If I sent it as a string instead of a JSON object, though, it just printed "[object Object]". So I had to use a full object as the param.

Why do I have "data" in there? No clue, but it didn't work without it.

Worse, that doesn't give me a way to send the event. I tried these:

$('.button').click(e, buttonClick);
$('.button').click((e), buttonClick);
$('.button').click({ event: e }, buttonClick);

but just got an error that e is not defined.

I'm not sure that any of those are really shorter or less complicated than $('.button').click(e => buttonClick(e));, anyway, so I guess it doesn't really matter.

Fotiman

4:02 am on Nov 15, 2022 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



The event object is passed to the handler function, so this would give you the event data:

$('.button').click(handler);
function handler(event) {
console.log(event);
}