JS Object this.method() breaks via jQuery
This is not jQuery's fault, it is integral to the way JavaScript handles objects.
Unlike in most other object-oriented languages, ‘this’ is not bound on a per-method level in JavaScript; instead, it's determined purely by how the function is called:
Bob= {
toString: function() { return 'Bob!'; },
foo: function() { alert(this); }
};
Brian= {
toString: function() { return 'Brian!'; },
};
Bob.foo(); // Bob!
Bob['foo'](); // Bob!
Brian.foo= Bob.foo;
Brian.foo(); // Brian! NOT Bob
var fn= Bob.foo;
fn(); // window NOT Bob
What you are doing in the case of:
$j('#MyButton').click( Bob.doSomething );
is like the last example with fn
: you are pulling the function doSomething
off Bob and passing it to jQuery's event handler setter as a pure function: it no longer has any connection to Bob or any other object, so JavaScript passes in the global window
object instead. (This is one of JavaScript's worst design features, as you might not immediately notice that window
isn't Bob
, and start accessing properties on it, causing weird and confusing interactions and errors.)
To remember Bob, you generally make a function as in nickyt's answer, to keep a copy of ‘Bob’ in a closure so it can be remembered at callback time and used to call the real method. However there is now a standardised way of doing that in ECMAScript Fifth Edition:
$j('#MyButton').click( Bob.doSomething.bind(Bob) );
(You can also put extra arguments in the bind
call to call doSomething
back with them, which is handy.)
For browsers that don't yet support Fifth Edition's bind() method natively (which, at this point, is most of them), you can hack in your own implementation of bind
(the Prototype library also does this), something like:
if (!Object.bind) {
Function.prototype.bind= function(owner) {
var that= this;
var args= Array.prototype.slice.call(arguments, 1);
return function() {
return that.apply(owner,
args.length===0? arguments : arguments.length===0? args :
args.concat(Array.prototype.slice.call(arguments, 0))
);
};
};
}
Change
$('#MyButton').click( Bob.doSomething );
to
$('#MyButton').click( function() { Bob.doSomething() } );
You could also add to your Bob object a private field var that = this
and use that everywhere in members instead of this
if you really want to avoid the anonymous function.
e.g.
var Bob = new function() {
// Private Fields
var that = this;
// Public Members
this.Stuff = '';
this.init = function() {
that.Stuff = arguments[0];
}
this.doSomething = function() {
console.log( that.Stuff );
}
}
The identity of this
is a common problem in javascript. It would also break if you tried to create a shortcut to doSomething
:
var do = Bob.doSomething;
do(); // this is no longer pointing to Bob!
It's good practice to not rely on the identity of this
. You can do that in a variety of ways, but the easiest is to explicitly reference Bob
instead of this
inside of doSomething
. Another is to use a constructor function (but then you lose the cool object-literal syntax):
var createBob = function() {
var that = {};
that.Stuff = '';
that.init = function() {
that.Stuff = arguments[0];
};
that.doSomething = function() {
console.log( that.Stuff );
};
return that;
}
var bob = createBob();