3

I'm just getting into Backbone, and one thing that I don't understand is why the 'on()' method for models always takes three arguments--event, handler, and context.

It seems that almost always 'this' is used for context and I haven't seen any other usage. Even if there were, since I haven't seen one yet it must be pretty rare.

So my question is: When does one use a context other than 'this', and why is Backbone designed this way? By the way, I do understand why you need to provide context, it's just that I wonder why the method syntax specifies that I use three arguments instead of making the last argument optional--which seems to be always 'this' and feels redundant. I'm sure I'm missing something. Please someone help me understand. Thank you!

  • [EDIT] Why can't one do something like:

    model.on = function(event, callback){
      model.on_with_three_args.call(this, event, callback, this);
    });
    
    model.on_with_three_args = function(event, callback){
      /* whatever the on() is supposed to do */
    });
    
Thaddeus Albers
  • 4,094
  • 5
  • 32
  • 42
Vlad
  • 8,038
  • 14
  • 60
  • 92

1 Answers1

9

Suppose we're in a view that's based on a model and we want to bind to the model's change event:

this.model.on('change', this.render);

The on call sees two things:

  1. The event name, a simple string.
  2. The handler, a function.

on has no way of knowing what this means in this.render, it just sees a function; on won't even know the difference between the call above and this:

this.model.on('change', function() { ... });

If your function needs a particular context then you have two choices:

  1. Create a bound function using _.bind, _.bindAll, Function.bind, $.proxy, CoffeeScripts =>, the var _this = this closure trick, or any of the ways of creating or simulating a bound function.
  2. Tell it which context you want by saying:

    this.model.on('change', this.render, this);
    

There's no way to unroll the call stack to see which this you want so you have to be explicit about it.

Backbone will call the callback like this:

node.callback.apply(node.context || this, ...);

where node.callback is the callback function and node.context is the third argument (if any) given to on. If you don't specify the context then you'll get whatever this happens to be when trigger is called; in the example above, this would end up being the model.

So the third argument to on actually is optional but the default value isn't terribly useful and there is no way to choose a better default, the information you need to choose a sensible context simply isn't accessible in JavaScript. This is why you see so much _.bindAll(this, ...) boilerplate in Backbone views.


If you tried something like this:

model.on = function(event, callback){
    model.on_with_three_args.call(this, event, callback, this);
});

then this in that context would usually be model so you'd really be saying:

model.on = function(event, callback){
    model.on_with_three_args.call(model, event, callback, model);
});

or

model.on = function(event, callback){
    model.on_with_three_args(event, callback, model);
});

and there's little point to any of that. The value of this inside on has little if anything to do with the value of this in the code that calls on. this in JavaScript is not a variable, it is a keyword which refers to the current calling context.

mu is too short
  • 426,620
  • 70
  • 833
  • 800
  • Thanks for the answer! I didn't know that it was optional. Let me make sure I understood correctly. According to your code node.callback.apply(node.context || this, ...); doesn't it mean that the default context is indeed 'this'? I didn't understand why you said it isn't terribly useful. – Vlad Oct 30 '12 at 05:39
  • Also, to clarify, _.bindAll(this, ...) is not really needed if we use the third argument 'this', is this correct? Why do so many people use _.bindAll in there code? – Vlad Oct 30 '12 at 05:40
  • @Vlad: The value of `this` always depends on context. What `this` is when you call `this.model.on(...)` isn't the same as what `this` is when `trigger` is called. `this` is not a variable, it just looks like one. – mu is too short Oct 30 '12 at 05:40
  • Why use `_.bindAll` when you can use the three argument `on`? History? Personal preference? To handle binding for cases that don't go through Backbone's `on` perhaps? – mu is too short Oct 30 '12 at 05:42
  • So basically the third argument is being tied into this.model.on's closure, so that even after it returns it's still tied to the 'this' that was the 'this' when model.on() was initially called? – Vlad Oct 30 '12 at 05:50
  • There's no closure involved, a bit of reading on [`apply`](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function/apply) might be fruitful. `apply` can be used to specify what `this` will be when you call a function. If you just say `f()` then `this` will be `window` inside `f` (barring bound functions of course) but you can use `apply` and `call` to provide a different context. – mu is too short Oct 30 '12 at 05:52
  • I've updated the question to ask why a code like that would not work. Basically I am wondering why binding the 'this' at the time of its initial call through a second method wouldn't work. I am sorry I'm bothering you too much with questions, but I would appreciate your answer. – Vlad Oct 30 '12 at 05:55
  • The value of `this` inside a function is determined **when the function is called**, not when the function is defined, not when you hand `on` a reference to the function, but when you call the function (barring bound functions of course). JavaScript's `this` is the calling context for the function and that's all it is, the calling context is determined, of course, when the calling happens. – mu is too short Oct 30 '12 at 06:03
  • Ah now I understand. I've been totally confused. Now I get it. Thank you! – Vlad Oct 30 '12 at 06:13