Why does Backbone.js model's 'on()' ta

2019-03-31 13:38发布

问题:

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

回答1:

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.