Thierry J
Thierry J

Reputation: 2189

Passing a method as callback

I am trying to pass a method as a callback reference to a method of another class:

function A() {
    this.callback = function(){return 0;};
}

A.prototype.set = function(cb) {
    this.callback = cb;
};

A.prototype.test = function(){
    console.log("test " + this.callback());
};

function B(o) {
    this.prop = "blah B";
    o.set(this.blah);
}

B.prototype.blah = function() {
    console.log(this);
    return this.prop;
};

What I would expect from the execution

a = new A();
b = new B(a);
a.test();

is the result

>B { prop="blah B", blah=function()}
>test blah B

But instead the console shows

>A { cb=function(), set=function(), test=function()}
>test undefined

as if the method blah has been assigned to A for the execution..

Why am I getting this result?
And how can I get the result I expected?

Upvotes: 3

Views: 239

Answers (4)

user1636522
user1636522

Reputation:

"as if the method blah has been assigned to A for the execution", you almost got it, just replace "A" with "a" in your sentence. Indeed, by default, this refers to the instance which owns the method.

As it happens, o.set(this.blah) can be translated into a.callback = b.blah, which means that a.callback and b.blah now refer to the same function. In other words, the same function is now owned by both instances a and b :

a.callback() // "return (this -> a).prop" -> undefined
b.blah()     // "return (this -> b).prop" -> "blah B"

Roughly speaking, you need a way to "redirect" this to b inside callback.

You could use either a closure :

var me = this;
o.set(function () {
    // this -> a
    // me   -> b
    return me.blah();
});

Or bind (not supported by IE8 and below) :

o.set(this.blah.bind(this));

Upvotes: 3

subham.saha1004
subham.saha1004

Reputation: 832

When blah method is called inside the test method, this points to an A instance. Since A does not have a prop property it returns undefined.

You need to change the line o.set(this.blah); to this o.set(this.blah.bind(this));

Now blah is always bound to an instance of B and when test is called this.prop will return "blah B" which is what is intended.

Upvotes: 1

jacquard
jacquard

Reputation: 1307

The problem is because of execution context - how this of a function in JavaScript is bound to. When the final line a.test() is called, the callback looks for a property called prop in the context of the object a. In your case, the object a did not have a prop property and hence the undefined. When you supply a prop to a then it works fine. Final fiddle with console.log here. Here the callback is called twice, once without the assignment of prop and the second with the assignment of the same:

function B(o) {
    this.prop = "blah B";
    o.set(this.blah);
    o.prop = this.prop;
}

Upvotes: 2

sbking
sbking

Reputation: 7680

Yes, that is how JavaScript's this keyword works - it refers to the current execution context, which is the object it is called on in this case. You are calling it as if it was a method of A. You need to call it as a method on your B instance. If you don't need to support IE8 or lower, you can use Function.prototype.bind to bind this in the function so it always points to the B instance. Otherwise, the A instance needs a reference to the B instance on which to call the method.

You could manually bind the method with a var self = this; hack. You could also do this in the B constructor:

function B(o) {
    var self = this;
    this.prop = "blah B";
    o.set(function() { self.blah(); });
}

But if you could expand a bit on what exactly you're trying to do, there might be a more elegant solution.

Upvotes: 2

Related Questions