Javascript's fragile "this" statement
Javascript’s this
statement must be the most fragile and confusing statement I’ve come across. Many languages
have the concept of “this”, but none mess with it to hard as javascript does. As I have recently discovered,
there are two fundamental issues with javascript’s this
:
this
does not get captured when storing an object member into a function object- you can never actually be certain what
this
will be
For object-oriented programming, these two facts are entirely terrifying. Let me illustrate each one:
1. this
does not get captured when storing an object member into a function object
function Obj() {
this.method = function() {
return "method() called - I am " + this;
};
this.toString = function() {
return "[Obj instance]";
}
}
Now, consider the following scenarios:
var obj = new Obj();
obj.method();
// returns "method() called - I am [Obj instance]"
var obj_method = obj.method;
obj_method();
// returns "method() called - I am [object DOMWindow]"
What happened to this
? I can find no explanation anywhere as to why it has been lost (and the window
object
used in its place), but it is consistent. This may not seem like a big deal, but even aside from the
awkwardness, it’s completely non-obvious - and therefore a great candidate for sneaky bugs.
2. You can never actually be certain what this
will be
Some see this as a feature, and it is in some cases. But the fact remains that the caller of any function can
set this
to be any object they choose is cause for great suspicion on the part of any callback code.
What’s worse, as a library writer there are cases where it’s impossible to not mess with the value of this
.
Other languages have the concept of a unsplat operator. Google it if you don’t know this term, but basically
it will turn a list of objects into an argument list. That is, func(1,2,3)
is the same as func(*[1,2,3])
(where *
is the un-splat operator). This is very important for higher-order / functional programming, where you might
write a proxy function that wraps a normal function call with some useful behaviour.
Anyways, javascript does have an unsplat operator. Kind of… The following code will work:
function call_other() {
var _arguments = Array.prototype.slice.call(arguments);
var func = _arguments[0];
var func_args = _arguments.slice(1);
// do whatever proxy stuff you need to do here
func.appy(null, func_args);
}
Except for that first parameter to the apply
function. Whatever you pass in there is what this
will be set
to in the context of the called function. With no discernible means of extracting what this
would normally
be for the given function, it becomes impossible not to clobber the otherwise extremely-useful this
statement.