0

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 :(

3 Answers 3

2

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);
  };
};
Sign up to request clarification or add additional context in comments.

3 Comments

Personally, I avoid using this in callback functions and stick to closures instead. That way, the callback will behave the same exact way when called no matter what object it's a property of.
Thanks for this detailed answer! But i'm not so familiar with closures, and this is my first complex JS based app.
A closure is just a fancy way of letting you say "run this code as though it were being run where it was written instead of where it's called". This is perfect for a callback in an event-based API, because most of the time, what you're doing next in the callback is in the same context as what you just did, so you want access to all the same variables and everything. All you have to do is wrap some code in a function and pass it along as a callback. Browse some NodeJS code sometime (there's plenty of good examples), and you'll find lots of anonymous functions as arguments to other functions.
0
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

1 Comment

Doesn't bind rely on call or apply ?
0

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);    
    };
    

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.