I am trying to pass function reference as event handler in jQuery. I would like to use a shorthand like in the simple example below...
$("a").click(console.debug.bind(undefined,this));
...rather than passing explicitly the whole function body:
$("a").click(function() {
console.debug(this)
});
Moreover, I would like to access elements selected by jQuery in my shorthand function (and pass them as a parameter). In other words: I expect to have a result of $("a") as a this (or any other code that will retrieve the result).
So far I've tried:
var a = function() {
console.debug(this);
};
var b = console.debug.bind(undefined,this);
$("a").click(function() {console.debug(this)}); // prints link
$("a").click(a); // prints link
b(); // prints Window
$("a").click(console.debug.bind(undefined,this)); // prints Window & jQuery.Event
Here is the fiddle:
https://jsfiddle.net/hbqw2z93/1/
My questions are:
Is it possible to use such construction and meet all requirements, without definition of additional variables - just one line as shown above?
Is it possible to access jQuery's selection result using described approach?
Why in the given scope this becomes 'merged' Window and jQuery.Event object?
You already using it, aren't you? :) It's limited, but it works in your own fiddle
jQuery will pass event object to your specified function. You can use function bind to pass that as an argument (you already have this working in your fiddle)
It doesn't. See what's happening:
jQuery passed one argument to click handler function - event object. You pass console.debug.bind(undefined, this) as a handler function so jQuery will call it with one argument.
Then, when you are binding you are asking to use 'undefined' as a 'this' object inside the function and sending an extra argument - 'this', which is a Window at this scope because you are binding at the highest level.
So when actual click happens, jQuery calls console.debug with two parameters - Window object that was bound during click() and jQuery event that is always passed to click handler. console.debug() can accept and display multiple objects, which is exactly what you see in the developer console.
The first parameter of bind is the new context to use for this. By passing undefined you are essentially not passing the first parameter.
The second and further parameters are passed into the function as the first values.
Note also that this when in the global scope, refers to the window object.
So here, b...
console.debug.bind(undefined,this);
is identical to...
function(){ console.debug(window); }
..since you're passing this (which is window) as the first parameter to debug.
By default, when you attach an event to the element, this will automatically point to the element which caught the event, so bind shouldn't even be necessary, which is why $("a").click(a); worked without using bind.
Related
I have seen this kind of code in one of Google's google maps documentation. My question is about the listener. Instead of the callback function passed immediately after the 'click' action the showArrays() function is called but not passed nothing as parameter. On the other hand showArrays() function uses event as parameter. Please explain me this kind of calling the function.
element.addListener('click', showArrays);
//some code here
}
function showArrays(event) {
// some code here
}
Think of the names of functions as variables themselves. showArrays is a variable, that, when given an event, does something with it.
You can pass the functions name as a parameter to addListener so that it can call the callback when the element is clicked on. It's important to note that you are not calling the function in the first line, only passing a reference to that function.
You can show this property in the browser's console with this test:
function test() { console.log("Test was called"); }
Notice if you say var x = test nothing is printed to the console. But if you say var x = test() you see the print. Finally, if you do var x = test; x() you will see the print out, because you called the test function after assigning it a new name.
Notice that in the element.addListener('click', showArrays) line, showArrays does NOT have brackets after it. That means it's not being called. Instead, the entire function is being passed as a parameter to the addListener method.
Event listeners in JS will take the handler function you provide when you attach them with addListener (or addEventListener, more commonly), and, when the event occurs, they will call that function and pass an event object to it.
In other words, showArrays is not being called until the element is clicked, and all event listeners inherently get passed an event object at that point, detailing the specific properties of the event.
One of the syntax cases for describing a function is:
var showArrays = function (event) {
// of the code here
}
and it is precisely this argument value that the addEventListener method uses, and even other functions such as setTimeout or setInterval, among others.
addEventListener method always sends the "event" object as an argument to the callback function. When you use the anonymous function, it is obvious to see it:
element.addEventListener('click', function(event) {
// some code here
});
But when you send to the addEventListener method a link to the function you want to be called when the event occures (in your case it is a link to showArrays function), addEventListener sends "event" object as an argument to this function just on itself. So, although it's not obvious to see it, but the "event" object is being sent to showArrays function automatically.
element.addEventListener('click', showArrays); // the event object will be sent automatically
And you will have an access to the "event" object inside the showArrays function. But, of corse, in showArrays function declaration you should have a parameter for catching the "event" object.
function showArrays() {} // it's not going to work
function showArrays(event) {} // it will work
In a codebase I see bind used to make bound copies of functions on the prototype, used as callbacks for DOM events.
Why might this idiom be used, rather than, for example, using the methods on the prototype directly?
Does this offer some benefits in terms of memory consumption/the ability to free memory use when events are unbound from DOM events?
function F() {
var onFoo = this._onFoo.bind(this); // Why?
document.getElementById('foo').onClick(onFoo);
}
F.prototype._onFoo = function () { /*...*/ }
The issue is that event handlers set their own value for this when they call the callback. That value will typically be related to the event handler, not to the object that the method is bound to. For example, in your example:
document.getElementById('foo').onClick(myObj.myFunc);
The this pointer in myFunc will be set to the DOM element that had the event handler attached (in this case, the foo element). But that isn't myObj so myFunc in that case could not access any of it's own instance variables via the this pointer (the normal way that methods access their instance data).
So, if you have a method that wants to access it's own instance data when it is called directly by an event handler, you have to do something other than just pass the method to the event handler. There are a couple ways to work around this issue.
One way of doing so it so use .bind() which returns a new stub function who's function is to set this before calling your function like this:
document.getElementById('foo').addEventListener('click', myObj.myFunc.bind(myObj));
In this case .bind() actually returns a new stub function who's function is to set the value of this to myObj before it calls myFunc.
You could also do that manually yourself like this:
document.getElementById('foo').addEventListener('click', function(e) {
myObj.myFunc();
});
But, as you can see, .bind() provides a shortcut that takes less code (which is why it was invented).
A potential disadvantage to using .bind() in some cases is that you may no longer have access to the value of this that the caller of your callback would have set itself because .bind() threw that value away and replaced it with your own. In the event handler example above, this is not an issue because the original source of the event can be accesses via the e argument that is passed to the event handler so it is not lost if you need it.
I am aware of no meaningful difference in memory consumption or garbage collection between the two methods above. Both create a new function that is used to call the original and control the value of this when calling the original function. Both will have the same garbage collection lifetime.
It appears that one thing that is confusing you is that objects in Javascript are assigned or passed by pointer (some call it by reference, but that has some connotations that don't apply here so I'll use the phrase by pointer).
var x = {};
x.myFunc = function() {console.log("hello");};
x.myFunc(); // generates "hello" in the console
var t = x.myFunc; // save reference to the function that x.myFunc currently points to
delete x.myFunc; // remove property myfunc from the x object
t(); // generates "hello" in the console
t() still works event after x.myFunc has been removed because both t and x.myFunc had a reference (or pointer) to the same function. Doing a delete x.myFunc simply removed the myFunc property from the x object. The function that x.myFunc points to will only be "freed" by the GC when there are no other references to it. But, there is another reference to that function in t, so it is not freed and t() can use it for as long as t exists.
I don't know the meaning of the sentence 'function(event)'
Event.add(apple,'click',function(event) {
Event.stopPropagation(event);
});
Isn't the argument 'event' is the unique keyword of javascript?
Is keyword can be an argument of some function?
I understand the meaning of below code :
function(test) {
alert(test);
}
But I don't understand this one :
function(event)...
Can any one give an explanation about that to me?
The event object is always passed to the handler and contains a lot of useful information what has happened.
Different types of events provide different properties. For example, the onclick event object contains:
event.target - the reference to clicked element. IE uses event.srcElement instead.
event.clientX / event.clientY - coordinates of the pointer at the moment of click.
Information about which button was clicked and other properties.
Please visit this link.
It answers all your questions very simply
Source http://javascript.info/tutorial/obtaining-event-object
Example:
Like if in HTML you have assigned an event like this
<button onclick="alert(event)">See the event</button>
then
function alert(event) {
// event.type contains whether this event was invoked in the result of a click etc
// event.target would contain the reference to the element which invoked this method/event
}
It is an anonymous function, that is a function without name, that sends the event object. That object contains information about the event itself. It is always passed as first object/variable.
It is defining an anonymous function object. This code:
function foo(bar) { ... }
Is functionally similar to:
var foo = function (bar) { ... };
(Except that in the first case the name foo and the creation and assignment of the function object are hoisted to the top of the scope, while in the second case only the name foo is hoisted; foo won't hold the function until the assignment executes.)
Effectively, the code you posted is calling Event.add() and passing a function to it as the third argument, but rather than declaring the function ahead of time it is creating the function object inline.
Another way to write the code block in your question is:
function handler(event) {
Event.stopPropagation(event);
}
Event.add(apple, 'click', handler);
Except that the code in your question does not introduce the handler name.
Note that there is no such method Event.stopPropagation(). However, the event object will have a stopPropagation(), so the capital E was probably a typo. It's likely that the intent was to use function (event) { event.stopPropagation(); }.
event is just a variable that's passed to event listener functions such as Event.add, element.on. It's not reserved (although Event is, which is why you can use Event.add), and you can name it whatever you like.
The event argument is used to pass information about the event that has happened (the click on apple in this case), which can be used to retrieve data about the event or manipulate it.
function(){...} is an anonymous function, which means that you don't need to name it, you can just declare it inline, and the function will be passed as an argument, as if you said
function foo (event) {
...
}
Event.add(apple, "click", foo);
but you don't need to declare it before hand. It does come at the disadvantage of not being duplicable, for instance when clearing an event handler.
Look at the event variable and you will all understand :)
function (event) {
console.log({ event });
}
I am trying to set up an on-click callback for an HTML that causes another node to become visible. Along the way, I was surprised to find out that the following two statements are not equivalent:
$("#title").click($("#content").toggle);
$("#title").click(function() {
$("#content").toggle();
}
The first statement ultimately results in a TypeError when the element is finally clicked, with a message of "undefined is not a function," which I surmised to indicate that whatever I was assigned to the onclick callback ended up being undefined and somehow doesn't persist in memory.
The workaround is simple (just use the statement of the second form), but what I really want to understand is why passing the toggle function as an object doesn't work when it finally gets called. I can see that the two are semantically different: the first executes the $("#content") call when binding the event and the other executes it when the event occurs, but I don't understand why that should matter.
In case it is relevant to the answer, the code in question is located inside of a function (that has presumably returned by the time the user can click anything).
In the first example, you're passing the toggle function for jQuery to execute as the event handler.
However, when jQuery executes the event handler, it sets the value of this to be the DOM element the event fired on.
toggle, however, expects it to be the jQuery object (which it would be if called normally), as it uses this.animate() in its implementation.
This is why you see "undefined is not a function", as this.animate is "undefined", and you're trying to call it as a function.
It's important to appreciate the resolution of this inside a function is deferred until the function is executed. This means a single function can see a different this value between invocations. The value of this can be altered using bind(), new, call(), apply() or by referencing the object differently; for more info see here, or How does the "this" keyword work?
The jQuery function, as in this -> $(), is just a function, think of it as
var $ = function(selector, context) {
// do stuff with selector etc
}
That's really simplified, but when you're calling the jQuery function (as in $()) with a valid selector, it gets the DOM node and returns something like this.
[
0 : <div id="title"></div>,
context : document,
selector : "#title",
jquery : "1.11.0",
.....
etc
]
this is the array-like object jQuery returns, and as you can see 0 is the native DOM node, and it's the reason we can do $('#title')[0] to get the native DOM node.
There is however something that one really can't see from a simple console.log, and that's the methods that are prototyped onto that array-like object, we could however use a for..in loop to see them in the console.
var title = $('#title');
for (var key in title)
console.log(key)
FIDDLE
This would return a long list of all the prototyped and non-prototyped methods available on this object
get
each
map
first
last
eq
extend
find
filter
not
is
has
closest
....
etc
Notice that these are all the jQuery methods added to the $() function with $.prototype, but jQuery uses a shorter name, $.fn, but it does the same thing.
So all the jQuery functions we know are added to the main $() function as properties, and the new keyword is used internally to return a new instance of the $() function with those prototyped properties, and that's why we can use dot notation, or for that matter bracket notation and chain on methods to the $() function, like this
$().find()
// or
$()[find]()
When objects are extended with prototyped properties like this, the value of this is also set inside the methods, so now that we understand a little bit about how it works, we can recreate a really simple jQuery version
var $ = function(selector, context) {
if (this instanceof $) {
this.context = context || document;
this[0] = this.context.querySelector(selector);
return this;
}else{
return new $(selector, context);
}
}
This is simplified a lot from how jQuery works, but in principle it's the same, when $() is called, it checks if it's an instance of itself, otherwise it creates a new instance with the new keyword and calls itself again as a new instance.
When it is a new instance, it gets the element and the other properties it needs, and returns those.
If we were to prototype on a method to that instance, we could chain it like jQuery does, so lets try that
$.prototype.css = function(style, value) {
this[0].style[style] = value;
}
and now we can do this
$('#title').css('color', 'red');
we've almost created jQuery, only 10000 lines of code to go.
FIDDLE
Notice how we have to use this[0] to get the element, we don't have to do that in jQuery when we use something like click, we can just use this, so how does that work ?
Lets simplify that as well, as it's crucial to understand why the code in the question doesn't work
$.prototype.click = function(callback) {
var element = this[0]; // we still need [0] to get the element
element.addEventListener('click', callback.bind(element), false);
return this;
}
What we did there was use bind() to set the value of this inside the callback function so we don't have to use this[0], we can simply use this.
FIDDLE
Now that's cool, but now we can no longer use any of the other methods we've created and prototyped to the object, as this is no longer the object, it's the DOM node, so this fails
$('#element').click(function() {
this.css('color', 'red'); // error, <div id="element".. has no css()
// however this would work, as we now have the DOM node
this.style.color = 'red';
});
The reason it fails is because we now have the native DOM node, and not the jQuery object.
So finally to answer the question asked.
The reason this works ...
$("#title").click(function() {
$("#content").toggle();
});
... is because you're calling the toggle() function, and the correct value of this is set, in this case it would be the jQuery object containing #content as toggle() has no callback that uses bind(), it simply passes the jQuery object, an object similar to what we can see at the top of this answer
Internally toggle() does
$.prototype.toggle = function() {
this.animate();
}
see how it uses this directly whithout doing anything other than calling another jQuery function, it requires that this is a jQuery object, not a native DOM element.
Lets repeat that, toggle() requires that this inside the function is a jQuery object, it can not be anything other than a jQuery object.
-
Now lets move on back to click again, when you do
$("#title").click(function() {
console.log(this)
});
the console would show the native DOM element, something like <div id="title"></div>
Now we can reference a named function instead
$("#title").click(myClickHandler);
function myClickHandler() {
console.log(this)
});
and the result would be exactly the same, we would get the native DOM element in the console -> <div id="title"></div>, which is not suprising as this is exactly the same as the one above using an anonymous function.
What you're doing is referencing the toggle() function like this
$("#title").click($("#content").toggle);
It's exactly the same as the example above, but now you're referencing toggle(), and when called it will be called with the value of this set to the native DOM element in the click function, it would go like this
$("#title").click($("#content").toggle);
$.prototype.toggle = function() {
console.log(this); // would still be <div id="title"></div>
this.animate(); // fails as <div id="title"></div> has no animate()
}
This is what is happening, toggle() is expecting this to be a jQuery object, but instead it gets the native DOM node for the element in the click handler.
Read that again, this inside the toggle() function would be the native #title element, which isn't even the correct element, as that's how javascript and jQuery works, see the long explanation above for how this is set in the prototyped methods etc.
When you use this in a JS function, it refers to whatever object the function is currently being called on, not where it was defined. For instance, you can define a function and copy it onto another object, like this:
foo = {'name': 'foo'}; bar = {'name': 'bar'};
foo.test= function() { console.log(this.name); }
bar.test= foo.test;
foo.test(); // logs 'foo'
bar.test(); // logs 'bar'
When you run foo.test(), this is set to point at foo; but when you run the same function as bar.test(), this is set to bar instead. There is nothing in the function that knows it was originally part of foo, so you basically have two separate but identical functions, like this:
foo.test = function() { console.log(this.name); }
bar.test = function() { console.log(this.name); }
When you run $("#title").click($("#content").toggle);, a similar thing happens - you get a reference to the toggle function, and copy that function into jQuery's list of event handlers. When the callback runs, the $("#content") part is forgotten, just like the foo was above, so when the implementation in jQuery looks at this to see what you want to toggle, it will find the wrong thing.
Exactly what it finds instead has an extra little quirk: jQuery sets this on click handlers to be the DOM element that was clicked on (there are various ways in JS of explicitly telling a function what it should use as this). The exact error comes about because the implementation of toggle is expecting a jQuery object, not a native DOM object, but even if a jQuery object was set as this, it would be the wrong node: you clicked on $('#title'), but want to toggle $('#content'), and jQuery has no way of knowing that.
For completeness, to explain why $("#title").click(function() { $("#content").toggle(); } does work: here the function being saved in jQuery is an anonymous function, which doesn't make any use of this, so doesn't care what it gets set to when the callback finally fires. When the event runs (when you click) it calls toggle with an explicit context (the object returned by the $('#content') lookup), which is exactly what it's expecting.
What does the 'function' do in the following?
$('.event-row').on('mouseover',function(){
arc.event_handler.event_row_over();
});
$('.event-row').on('mouseover',arc.event_handler.event_row_over );
There's a very important difference.
The first one will call the function with the context its this value as the event_handler object.
The second one will call the function with the context its this value as the DOM element to which the handler is bound.
So the first one preserves the expected calling context this value, which may be required by the function.
In the first case with the anonymous function this inside that function is bound to the DOM element that caused the event. This is a convention that is common in browsers and also done when binding events natively. When calling arc.event_handler.event_row_over(); however, this is re-bound to arc.event_handler inside event_row_over; as it's called as an object method and in such a case this points to the object on which the method was called. The method will be called without any arguments.
In the second case you register the function arc.event_handler.event_row_over for the event. When called jQuery sets this to the related element so inside event_row_over, this points to that element. arc.event_handler is not available in there unless there is some other variable that points to it. jQuery also passes the event object as the first argument so the method is called with that argument.
Usually object methods expect this to be their object, so in almost every case you want to use the anonymous function to wrap the call. In case the element matters, pass this as an argument to the method.
Another way, without an anonymous function, would be using the bind() method every function has:
$('.event-row').on('mouseover', arc.event_handler.event_row_over.bind(arc.event_handler));
However, only modern browsers support this natively.
In the first case you are enclosing the function call in an anonymous function.
In the second case you are just assigning the function pointer..
First off, it seems like there is an extra dot in there.. arc.event_handler.event_row_over.(); should probably be just arc.event_handler.event_row_over();
And all the anonymous function does is it calls a member function named event_row_over of the arc.event_handler object; and it doesn't return anything.
The 'function' keyword will creates a new closure and encapsulate the scope. Good article on closures https://developer.mozilla.org/en-US/docs/JavaScript/Guide/Closures.
The first case, you have an additional function wrapper. This is useful when you want to do something else before calling the real event handler 'arc.event_handler.event_row_over()' for example you may do something like below:
$('.event-row').on('mouseover',function(){
doPreEventHandling();
arc.event_handler.event_row_over();
doPostEventHandling();
});
On the other hand you may even extract that annonymous function to be a named function and call as below:
var eventHandler = function(){
doPreEventHandling();
arc.event_handler.event_row_over();
doPostEventHandling();
};
$('.event-row').on('mouseover', eventHandler);
All above will be just similar in behavior, but more wrapper functions you have more abstraction you gain. But it will compromise performance and sometimes readability.
The context/scope of the function will not be the same.
Also, with the second one,
$('.event-row').on('mouseover',arc.event_handler.event_row_over );
you're getting the event object as an argument.