I find myself assigning "this" to a variable so I can easily use it in callbacks and closures.
Is this bad practice? Is there a better way of referring back to the original function?
Here is a typical example.
User.prototype.edit = function(req, res) {
var self = this,
db = this.app.db;
db.User.findById('ABCD', function(err, user)) {
// I cannot use this.foo(user)
self.foo(user);
});
};
User.prototype.foo = function(user) {
};
Do you normally use this approach or have you found a cleaner solution?
There are three main ways to deal with this in callbacks:
1. Create a lexically-scoped variable, as you are currently doing
The two most common names for this new variable are that and self. I personally prefer using that because browsers have a global window property called self and my linter complains if I shadow it.
function edit(req, res) {
var that = this,
db.User.findById('ABCD', function(err, user){
that.foo(user);
});
};
One advantage of this approach is that once the code is converted to using that you can add as many inner callbacks as you want and they will all seamlessly work due to lexical scoping. Another advantage is that its very simple and will work even on ancient browsers.
2. Use the .bind() method.
Javascript functions have a .bind() method that lets you create a version of them that has a fixed this.
function edit(req, res) {
db.User.findById('ABCD', (function(err, user){
this.foo(user);
}).bind(this));
};
When it comes to handling this, the bind method is specially useful for one-of callbacks where having to add a wrapper function would be more verbose:
setTimeout(this.someMethod.bind(this), 500);
var that = this;
setTimeout(function(){ that.doSomething() }, 500);
The main disadvantage of bind is that if you have nested callbacks then you also need to call bind on them. Additionally, IE <= 8 and some other old browsers, don't natively implement the bind method so you might need to use some sort of shimming library if you still have to support them.
3. If you need more fine-grained control of function scope or arguments, fall back to .call() and .apply()
The more primitive ways to control function parameters in Javascript, including the this, are the .call() and .apply() methods. They let you call a function with whatever object as their this and whatever values as its parameters. apply is specially useful for implementing variadic functions, since it receives the argument list as an array.
For example, here is a version of bind that receives the method to bind as a string. This lets us write down the this only once instead of twice.
function myBind(obj, funcname){
return function(/**/){
return obj[funcname].apply(obj, arguments);
};
}
setTimeout(myBind(this, 'someMethod'), 500);
Unfortunately this is the well-established way to do this, although that is a widespread naming convention for this "copy".
You can also try:
db.User.findById('ABCD', this.foo.bind(this));
But this approach requires foo() to have exactly the same signature as the one expected by findById() (in your example you are skipping err).
You could create a proxy for the callback with:
var createProxy = function(fn, scope) {
return function () {
return fn.apply(scope, arguments);
};
};
Using this, you could do the following:
db.User.findById('ABCD', createProxy(function(err, user)) {
this.foo(user);
}, this));
jQuery does something similar with: $.proxy
And, as others have noted using bind, have a look here if compatibility is an issue:
https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind#Compatibility
Related
Often in my Backbone code I come across situations where I would be passing a closure to some function and lose context of 'this'.
My solution for some time had been to do what I had seen some others do:
var self = this;
this.deferred.done(function () {
self.render();
});
Or actually I switched to _this = this, but that's beside the point. It works, but it feels ugly and I sometimes have to do it quite often. So I'm trying to figure out a better way to do this. I learned I could do this:
this.deferred.done(function () {
this.render();
}.apply(this));
And I think I could also use Underscore to do this:
this.deferred.done(_.bind(function () {
self.render();
}, this));
The apply method seems the most succinct but I feel like it has a side effect (I just don't know what it is yet).
Edit:
Take a look at this JSbin where I use apply similar to as I mentioned:
http://jsbin.com/qobumu/edit?js,console
It works, yet it throws an error at the same time. If I change the apply to bind, it works and doesn't throw an error.
Function.bind is a native method, no need for underscore unless you're coding for antique browsers. Exactly what #dandavis said: this.deferred.done(this.render.bind(this)) (but note that bind can also bind function arguments, not just this)
if you're actually coding for cutting-edge browsers or node.js 4, you can use arrow functions which bind this lexically to whatever it is in the scope where the function is defined, so you could write:
this.deferred.done(() => { this.render() });
Those do different things.
// returns a function with `this` set to what you want.
_.bind(fn, this);
// or
fn.bind(this);
// EXECUTES the function with `this` set to what you want.
fn.apply(this);
So in your case it's not a callback at all. When you use apply you are executing the function when you think you are assigning the callback.
This is why you use bind.
I've stumbled over a problem. I have an object method foo defined as:
var obj = {
foo: function() {
$('.personName').mouseover(function() {
this.highlight($(this).attr('faceIndex'));
});
}
}
So what should happen is that whenever the mouse cursor is over an HTML object of type personName, the obj.highlight method should be called with the faceIndex value from the HTML object as an argument. However I apparently have a clash between two this's: the one of jQuery and the one of JavaScript (referencing to obj from inside obj).
What can (should) I do? Have I violated some good programming practice?
A typical pattern to work around this is to use a local variable to store the first this:
var obj = {
foo: function() {
var _this = this;
$('.personName').mouseover(function() {
_this.highlight($(this).attr('faceIndex'));
});
}
}
Using a language like TypeScript or an ES6 compiler makes it easier to use this pattern without having to write the _this by hand each time.
Short answer: do
$('.personName').mouseover(function(event) {
obj.highlight($(event.target).attr('faceIndex'));
});
Longer explanation:
Javascript doesn't really have a concept of this. At least not in the way you're used to thinking of it. Oh there's a keyword alright, and it kind of does the thing you expect a lot of the times but it doesn't work the way that you probably think.
The fact of the matter is that in javascipt, this is no different than any other parameter. Let me show you.
Most people are aware that in javascript you can invoke functions like this doSomething(param1, param2) or like this doSomething.call(null, param1, param2). If you wanted, you can write all function invocations using .call
See that null there? Anything you pass in there is what this gets set to.
doSomething.call(null, param1, param2);
doSomething.call(obj, param1, param2);
doSomething.call(window, param1, param2);
doSomething.call("foobar", param1, param2);
If you don't use .call the runtime just takes a guess at what value you want there.
So given this, consider that the only difference between this and any other parameter is that you don't get to give this a name! Your problem is that you have two function scopes and the inner one has a variable named this which hides the outer one's this.
Solution: don't use this. Most libraries in fact (jquery included), don't force you to use this and also pass in the value as a regular parameter
$('.personName').mouseover(function(event) {
obj.highlight($(event.target).attr('faceIndex'));
});
ambiguity solved!
Avoid using this in JavaScript, if at all possible. It is almost never necessary.
this in javascript is a very difficult thing to understand in callbacks because it may refer to virtually any instance. And that is because the callback is called from a different context.
The long story : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this
As an alternative to previous answers :
One way I prefer dealing with it is by using a bind, also known as a proxy (in JQuery). jQuery has one implemented here : jQuery.proxy.
It has the benefit of letting you chose who is your this in the callback function.
For example:
var obj = {
foo: function() {
$('.personName').mouseover($.proxy(function(event) {
// this refers here to obj instance
console.log(this);
// event is a jQuery decorated that holds the reference of your element
console.log(event);
}, this));
}
};
And the true benefit of it, is that it lets you construct components that don't have "ugly" nested callback anonymous functions:
var obj = {
foo: function() {
$('.personName').mouseover($.proxy(this.mouseOverCallback, this));
},
mouseOverCallback : function(event) {
// this refers here to obj instance
console.log(this);
// event is a jQuery decorated that holds the reference of your element
console.log(event);
}
};
Background: I am building a webapp that makes use of JQuery and the Facebook Javascript API. Since some users don't like Facebook, I have also built a thunking layer that emulates the necessary identity APIs for users who prefer not to use Facebook. To keep things clean and avoid code duplication, I have organized my Javascript code into a few classes.
Both Facebook and JQuery APIs make extensive use of callbacks. Since all of my functions are bound into objects, I found that I am using the following pattern frequently:
var obj_ref = this;
some_jquery_function_that_takes_a_callback(/* various args */,
function() { obj_ref.my_callback_method(); });
For readability, the obj in obj_ref is actually the name of the class.
Is there some nifty Javascript magic that is simpler or clearer, or is this as good as it gets?
Update: Some good commentary so far in the answers. I should have added that my callback methods generally needs to refer to variables or functions bound to the class. If I don't need that, then the anonymous function wrapper is unnecessary.
Update2: I've selected a best answer, but you should carefully read all of the answers. Each provides some useful insight into possible solutions.
If you need your this to be your obj_ref and you can assume an update to date JavaScript (which sadly you probably can't), you could use bind to do away with the wrappers:
Creates a new function that, when called, itself calls this function in the context of the provided this value, with a given sequence of arguments preceding any provided when the new function was called.
Then you could bind your methods to your objects and this:
some_jquery_function_that_takes_a_callback(/* various args */,
function() { obj_ref.my_callback_method(); });
would be the same as:
// In some initialization pass...
obj_ref.my_callback_method = obj_ref.my_callback_method.bind(obj_ref);
// And later...
some_jquery_function_that_takes_a_callback(/* various args */,
obj_ref.my_callback_method);
Then this would be obj_ref when my_callback_method is called.
Alternatively, you could pull in Underscore.js and use its bind or bindAll. You could always grab just bind or bindAll out of Underscore.js if you didn't want the whole thing.
Or, since you have jQuery in play already, you could use $.proxy in place of the standard bind:
Takes a function and returns a new one that will always have a particular context.
So you could do it like this:
// In some initialization pass...
obj_ref.my_callback_method = $.proxy(obj_ref.my_callback_method.bind, obj_ref);
// And later...
some_jquery_function_that_takes_a_callback(/* various args */,
obj_ref.my_callback_method);
Thanks to dsfq for reminding me about jQuery's version of bind.
Better to use binding to the your object to preserve invocation context:
var objRef = this;
// #1. In this case you will have wrong context inside myCallbackMethod
// often it's not what you want. e.g. you won't be able to call another objRef methods
someJqueryFunction(a, b, objRef.myCallbackMethod);
// #2. Proper way - using proxy (bind) function
someJqueryFunction(a, b, $.proxy(objRef.myCallbackMethod, objRef));
I have the same problem with callbacks. Here's how I've dealt with it.
Just like dfsq said, $.proxy does the job for you. You don't need any extra library like underscore.
Underscore js has it's own bind function which is like the $.proxy.
Apply and call (the javascript methods that can be called on functions) work great.
Let's say I have a object :
var Dog = {
name : "Wolfy",
bark : function(){
console.debug(this.name," barks!");
}
}
var AnotherDog = {
name : "Fang"
}
Dog.bark.call(AnotherDog); //--> Fang barks!
Dog.bark(); //--> Wolfy barks!
When you write your "classes", you could use this pattern to handle the invocation of callbacks.
In case you're not sure what proxy or bind do, they do something similar to this:
var Dog = {
name : "Wolfy",
bark : function(){
console.debug(this.name," barks!");
}
}
Dog.bark = funciton(){
var scope = Dog;
return Dog.bark.apply(scope,arguments);
}
Rewrites the bark function by wrapping it in a new functions which returns the result of the original function, but forces a specific scope to be used.
I usually only wrap the callback in a function when I have to pass in extra parameters:
var x = 42;
jQuery_something({}, function(y, z) { obj_ref.callback(x, y, z) });
And that is because you don't control the arguments passed into the callback yourself. But this is only if you need a reference to some var in the scope. And in some cases this means you will have to create a closure to capture the var, e.g. in a loop.
But if that's not the case, then I just pass in a reference to the function directly:
jQuery_something({}, obj_ref.callback);
Even this should work fine, since the callback reference is in scope in this particular example (so no need to copy it to obj_ref first:
jQuery_something({}, this.callback);
Obviously, the last example is about as simple and clean as it gets: "the callback argument for the jQuery method is this object's method named 'callback'"
Yes, this is quite correct. You can have a helper function which implements the pattern though to save some code.
I want to delegate some function calls from one object to another:
objA.feature = objA.component.feature;
(Assuming feature is supposed to be a function both in objA and in its component)
But that clearly doesn't work, because the reference to this gets stripped from the function and ends up being a completely different object when it gets called (it actually is objA instead of objB.component).
You need to write something like this in order to get it to work:
objA.feature = function() {
objA.component.feature.apply(objA.component, arguments);
};
Is there a shorter (idiomatic) way to do that?
There's a function called "bind" on the Function prototype in newer runtimes:
var newFunc = someFunc.bind(somethingThatShouldBeThis);
will make this refer to "somethingThatShouldBeThis" when "newFunc" is called.
You can steal code from something like the Functional library to provide a "bind" function in environments that lack a native one.
edit — here's the code from Functional:
Function.prototype.bind = function(object/*, args...*/) {
var fn = this;
var args = Array.slice(arguments, 1);
return function() {
return fn.apply(object, args.concat(Array.slice(arguments, 0)));
}
}
Pretty simple, eh? It just returns a function back to you that, when invoked, will call the first argument as a function, and also pass along whatever arguments you pass in to the result (that is, to "newFunc" in the example above). The main wrinkle is that this one also saves extra arguments passed in originally, which can sometimes be handy.
Here is a great article on scope:
Binding Scope in JavaScript
However, most JavaScript frameworks (MooTools, jQuery) have built in methods for this. jQuery uses "proxy" and MooTools uses "bind", so be sure to check the documentation of whatever framework you're using.
Your best bet is to use a createDelegate-style method like some of the frameworks do. A bare-bones implementation was posted on OdeToCode a few years ago.
The code below is used to note some methods to run in particular circumstances so they can be called using a simpler syntax.
var callbacks = {alter: SPZ.sequenceEditor.saveAndLoadPuzzle,
copy: SPZ.sequenceEditor.saveAsCopyAndLoadPuzzle,
justSave:SPZ.sequenceEditor.saveAndLoadPuzzle};
But the code keeps returning an empty object. I've checked with console.log that the methods are defined. I've also tried changing the names, invoking an empty object and then adding the properties as eg callbacks.alter, and tried other changes that shouldn't matter.
Why won't this work?
Demo
error is on line 238 of puzzle.js
What exactly is the problem? Will the properties be undefined or the calls just not work correctly?
If the latter, the problem is most likely that when calling the methods, this will no longer refer to SPZ.sequenceEditor, but your callbacks object; to solve this problem, use the helper function bind() (as defined by several frameworks) or wrap the calls yourself:
var callbacks = {
alter: function() {
return SPZ.sequenceEditor.saveAndLoadPuzzle.apply(
SPZ.sequenceEditor, arguments);
},
copy: function() {
return SPZ.sequenceEditor.saveAsCopyAndLoadPuzzle.apply(
SPZ.sequenceEditor, arguments);
},
justSave: function() {
return SPZ.sequenceEditor.saveAndLoadPuzzle.apply(
SPZ.sequenceEditor, arguments);
}
};
The apply() is only necessary if the methods take arguments. See details at MDC.