I have a method that passes up an argument in a nested view in Backbone, like such:
page.get('condition') ? this.trigger('someEvent', object) : this.trigger('someOtherEvent', object);
As I understand it, this code will pass object to 'someEvent' or 'someOtherEvent' as the first argument to the callback specified in the listener for that event.
My listener (in the parent view file) looks like:
this.parentView.on('someEvent', this.someFunction('extraArgument'), this);
By default, if the second parameter was just this.someFunction, I assume that the first argument to that function would be object.
My issue is that I want to pass 'extraArgument' in addition to the implicitly passed object.
As such, I structure the signature of this.someFunction like this:
someFunction: function(object, extraArg) {
...
}
However, debug statements within the function reveal that object is in fact the extraArgument I passed manually, and that extraArg is undefined in the scope of the function. How can I pass the extraArgument without overwriting the argument passed up from the child view?
When you say this:
this.parentView.on('someEvent', this.someFunction('extraArgument'), this);
you're calling this.someFunction right there when the argument list for this.parentView.on is being built. Then whatever this.someFunction('extraArgument') returns is bound as the callback for the 'someEvent' event. Your someFunction probably doesn't return a function so this on call won't do anything useful.
Sounds like you want to partial evaluate this.someFunction so that you create a new function that is just like this.someFunction but the first argument is always 'extraArgument'. Underscore offers _.partial for just that purpose:
partial _.partial(function, *arguments)
Partially apply a function by filling in any number of its arguments, without changing its dynamic this value.
So you'd say:
this.parentView.on(
'someEvent',
_(this.someFunction).partial('extraArgument'),
this
);
You can also use _.bind for this if you want to set the this along the way.
Related
I'm using React, and I saw that one common practice is to bind the functions in the constructor, which I also want to use. Though, I don't exactly get how bind works for functions that take parameters. For example, I have something like this:
class MyClass extends Component {
constructor(props) {
super(props);
this.onListClicked = this.onListClicked.bind(this);
}
onListClicked(id) {
// performs some action
}
render() {
return (
// partially removed for brevity, value of myId comes from a loop
<ListItem onClick={ () => this.onListClicked(myId) } />
);
}
}
Now this works in my case, but I'm not taking advantage of bind fully. If I change the ListItem to <ListItem onClick={this.onListClicked} /> it doesn't work as expected. This would have worked if onListClicked didn't accept any parameters. But, in this case I don't know how to take advantage of bind. Any ideas?
your question has little to do with binding, and is really about how React handles callback props.
Every React event-listener function is passed an instance of React's SyntheticEvent object as its first parameter.
onClick={this.onListClicked} will call the onListClicked function and pass it one parameter: the SyntheticEvent object provided by React.
onClick={this.onListClicked.bind(this)} is the same as the last example. onListClicked.bind() returns a wrapped version of onListClicked with its context object set to this (which in your case is your React component because that's what this is set to when you do the binding). This wrapped version of your function still only receives one parameter: a SyntheticEvent object.
onClick={(e) => this.onListClicked(myId)} will call the anonymous fat-arrow function and pass it one parameter: a SyntheticEvent object, because the anonymous fat-arrow function is the callback and all callbacks get that parameter. This anonymous fat-arrow function ignores its own parameters, and calls this.onListClicked with the value of myId.
onClick={() => this.onListClicked(myId)} is the same as the last example, except we are ignoring the SyntheticEvent because we don't care about it.
onClick={this.onListClicked.bind(this, myId)}, as suggested in another answer, will wrap and call the onListClicked function and pass it TWO parameters: the first is myId (since bind is injecting myId as a parameter as well as setting the context to this) and the second is a SyntheticEvent object.
So: depending on what exactly you are doing inside of onListClicked, you may or may not need to bind it to your React component (or to some other context). Do you actually need variables and functions defined inside a particular object? Then bind your callback context to that object, and call this.foo or this.bar all you need. But if you don't need access to those sorts of things, there's no need to use bind just because it's there.
bind function takes a context as its first argument and takes your original function arguments as the next set of arguments after this. The "binded" (excuse me for the horrendous grammar!) function that is returned has the same arguments "binded" to it so when you call it, it will be called with the same set of arguments that it was bound with.
So essentially a <ListItem onClick={ () => this.onListClicked(myId) } /> should be replaced by a <ListItem onClick={this.onListClicked.bind(this, myId)} />
I, however, don't see a way for you to generate the function you need in the constructor itself since you won't have those arguments there to start with. You could loop over the array in the constructor itself and create those "binded" functions there only but that would be just a waste and a much elegant solution would be to use the above said method.
Read more about bind from MDN.
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.
Take this very simple framework I am experimenting with (so that I can learn JavaScript's function prototype more in depth.)
(function(){
var app = {
ui: {
app: document.querySelector('.app'),
settings: document.querySelector('.settings'),
},
actions: 'click settings openSidebar'
,
functions: {
openSidebar: function(e){
console.log(this); // <- expected value of this is 'app.ui'
}
},
run: function(){
var a1 = this.actions.split("\n");
var a2 = this.actions.split(" ");
var self = this;
this.ui[a2[1]].addEventListener(a2[0], function(e){
app.functions.openSidebar.call(self.ui,e);
});
}
};
app.run();
})();
This works great. Output from console is:
Object {ui: Object, actions: "click settings openSidebar", functions: Object, run: function}
However, when I try to do it like this:
var self = this;
this.ui[a2[1]].addEventListener(a2[0], function(e){
app.functions.openSidebar(e);
}.bind(this));
The context of this in openSidebar() is openSidebar (aka bind has no effect). Console output:
Object {openSidebar: function}
However, when I try to use the apply function, like so:
app.functions.openSidebar.apply(self.ui,e);
It works fine (this in openSidebar is app.ui) EXCEPT that the argument (e) does not get passed, so e == undefined.
Here goes:
1. Why does not bind work (at all) in the first example?
2. Why does apply work without passing arguments (the e (event))?
And for added brownie points:
3. Why does call work as expected?
Why does not bind work (at all) in the first example?
It "works", it just doesn't do what you expect.
Inside your anon function this is indeed the value set by bind. However, when you then call a function that is also a property of an object (functions.openSidebar) then for that invocation this is automatically bound to that object inside the function (i.e. this === functions). The value of this from the parent context is never "inherited" down the call chain.
Why does apply work without passing arguments (the e (event))?
Because apply tries to pull out the arguments for the call from its own second argument e by treating it as an array. This means that its length property is checked first; your event object doesn't have a length so apply gets the undefined value produced and effectively behaves as if you had passed in an empty array. See the annotated ES5 reference for the details.
Why does call work as expected?
This is a strange question. Why would it not work? You are using it exactly like it's meant to be used.
You'll need to do this:
this.ui[a2[1]].addEventListener(a2[0], app.functions.openSidebar.bind(this));
bind returns you a new function with a manually set context of whatever you pass in. Because you're not using bind on the right function, you're calling your function with app.functions to the left of the invoked function, which henceforth is known as this inside the invoked function!
Apply takes arguments as an array, not named parameters...
I can't explain the third without saying that that is how call works!
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.
I've built a GUI which passes in a long JS Object as settings for an animation plugin. One of the settings allows for the user to call a function once an animation is complete. Unfortunately, it has to be passed to the JS Object as a string.
... [ 'functioncall()' ] ......
Inside my animation callback, I'm retrieving this string and trying to run it as a function.
First attempt works perfectly, but uses eval...
eval( OS.path_jscall[index].path_jscall[0][i] )
I've setup a more preferred approach like so:
var HookFunction=new Function( OS.path_jscall[index].path_jscall[0][i] );
HookFunction();
Both examples call the functioncall() function perfectly. I'm not sure how to pass (this) to the functioncall() though...
functioncall(obj){ console.log(obj); };
Everything keeps referring to the window. Any ideas? Thanks!
Assuming that HookFunction is the name, you can do either a call() or apply()
HookFunction.call(this,arg1,arg2,...,argN);
//or
HookFunction.apply(this,[arg1,arg2,...,argN]);
the basic difference of the 2 is that call() receives your "this" and an enumerated list of arguments after it, while apply() receives your "this" and an array of arguments
Use .call when calling your function. .call assigns the first parameter to the this variable.
var myFunction = function(arg1, arg2) {
alert(this);
}
myFunction.call(this, "arg1", "arg2");
Using your second example, you could do this:
HookFunction.call(this);