Neka
Neka

Reputation: 1664

Get right object in callback

I have target object

function Foo() {
    this.someVar = 'some var';
};

Foo.prototype.callback() {
    console.log(this);
};

And object, that will call this callback

function Bar(callback) {
    this.callback = callback;
};

Bar.prototype.onSomeAction = function() {
    this.callback();
};

And initial code

foo = new Foo();
bar = new Bar();

bar.callback = foo.callback;
bar.onSomeAction();

Result: i have logged to console Bar()'s context instead of Foo(). How can i get context of Foo() in the Foo() callback?

PS: I tried closures

Foo.prototype.callback() {

    var foo = this;
    return function(foo) {
        console.log(foo);
    };
};

but it does nothing. I have not fully understanding of the closures :(

Upvotes: 0

Views: 75

Answers (3)

James O'Doherty
James O'Doherty

Reputation: 2246

The reason your original code didn't work is that the value of this inside of a method call is the value of the object it's being called on. That means when you say:

bar.callback = foo.callback; 

And then you call:

bar.callback();

The code defined here:

Foo.prototype.callback = function () {
    console.log(this);
};

gets called with this being a reference to bar because bar is to the left of the . on the method call. So whenever you assign a function as an object property, calling it on that object will call it with the object as this.

You could also have written:

function callback() {
  console.log(this);
}
bar.callback = callback;
bar.callback();

And you would find that this still references bar.

In fact, if you call the plain function callback(); as defined above, you'll find that this is a reference to the global object, usually window in web browsers. That's because all global variables and functions are properties of window, so callback(); is implicitly window.callback();

The fact that the value of this depends on what object is calling a function can be a problem when passing callbacks around, since sometimes you want this to reference the original object the function was a property of. The bind method was design to solve this problem, and Yuri Sulyma gave the right answer:

bar.callback = foo.callback.bind(foo);

However, the way you would do this using closures is to capture an instance of Foo within an anonymous function that calls the correct method on the correct object:

foo = new Foo();
bar = new Bar();

bar.callback = function () {
  foo.callback();
};
bar.onSomeAction();

Which is essentially what bind does. In fact, we call write our own naive version of bind using a closure:

Function.prototype.bind = function (obj) {
  var fn = this;
  return function () {
    fn.call(obj);
  };
};

call let's you call a function with the value of this explicitly defined. This allows you to "set the context" the function is called in so that it's the same as calling obj.fn() when you call bar.callback(). Since when we call foo.callback.bind(foo);, obj is foo and fn is foo.callback, the result is that calling bar.callback() becomes the same as calling foo.callback().

That's where Dalorzo's answer comes from. He uses call to explicitly set the context.

There's also another function for setting the context called apply that also takes an array representing the arguments for the function as its second argument. This allows us to write a more complete version of bind by taking advantage of the special arguments variable:

Function.prototype.bind = function (obj) {
  var fn = this;
  return function () {
    fn.apply(obj, arguments);
  };
};

Upvotes: 2

Yuri Sulyma
Yuri Sulyma

Reputation: 413

bar.callback = foo.callback.bind(foo);

You can polyfill Function.prototype.bind() if necessary: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind#Compatibility

Upvotes: 0

Dalorzo
Dalorzo

Reputation: 20014

Try using these changes:

  1. Use call to set context:

    bar.onSomeAction.call(foo);
    
  2. And I think your callback function needs to change to:

    Foo.prototype.callback=function() {    
        console.log(this);    
    };
    

Upvotes: 0

Related Questions