It's not implicit at all, it's explicit in this line right here:
var view = new TodoView({model: todo});
This is creating a new TodoView
view and setting its model
property to the addOne
function's only parameter (todo
, which is a model).
Whenever a new model is added to the Todos
collection, the addOne
method is called with the new model as the parameter.
Todos.bind('add', this.addOne);
Then, in addOne
, a new view is created for that model and the relationship is explicity set, via {model: todo}
. This is what you're missing from your version of the code, I suspect.
What you appear to be attempting to do is link up the view and the model in the view's init function, and that's fine, but you're on your own if you do that- which means you need to set up the model <-> view relationship yourself (which you have solved by passing the model as a parameter to the view init function).
The documented interface doesn't actually specify who owns the array reference so you're on your own here. If you look at the implementation, you'll see (as you did) that get
just returns a reference straight out of the model's internal attributes
. This works fine with immutable types (such as numbers, strings, and booleans) but runs into problems with mutable types such as arrays: you can easily change something without Backbone having any way of knowing about it.
Backbone models appear to be intended to contain primitive types.
There are three reasons to call set
:
- That's what the interface specification says to do.
- If you don't call
set
, you don't trigger events.
- If you don't call
set
, you'll bypass the validation logic that set
has.
You just have to be careful if you're working with array and object values.
Note that this behavior of get
and set
is an implementation detail and future versions might get smarter about how they handle non-primitive attribute values.
The situation with array attributes (and object attributes for that matter) is actually worse than you might initially suspect. When you say m.set(p, v)
, Backbone won't consider that set
to be a change if v === current_value_of_p
so if you pull out an array:
var a = m.get(p);
then modify it:
a.push(x);
and send it back in:
m.set(p, a);
you won't get a "change"
event from the model because a === a
; Backbone actually uses Underscore's isEqual
combined with !==
but the effect is the same in this case.
For example, this simple bit of chicanery:
var M = Backbone.Model.extend({});
var m = new M({ p: [ 1 ] });
m.on('change', function() { console.log('changed') });
console.log('Set to new array');
m.set('p', [2]);
console.log('Change without set');
m.get('p').push(3);
console.log('Get array, change, and re-set it');
var a = m.get('p'); a.push(4); m.set('p', a);
console.log('Get array, clone it, change it, set it');
a = _(m.get('p')).clone(); a.push(5); m.set('p', a);
produces two "change"
events: one after the first set
and one after the last set
.
Demo: http://jsfiddle.net/ambiguous/QwZDv/
If you look at set
you'll notice that there is some special handling for attributes that are Backbone.Model
s.
The basic lesson here is simple:
If you're going to use mutable types as attribute values, _.clone
them on the way out (or use $.extend(true, ...)
if you need a deep copy) if there is any chance that you'll change the value.
Best Answer
When to use
model.get(property)
andmodel.set(...)
You should use
get
andset
to access the model's data. This means any attributes that are part of the model's serialized representation that is retrieved usingfetch
and persisted usingsave
.When to use
model.attributes.property
Never.
You should always use
get
, and especiallyset
, instead of accessing themodel.attributes
object directly, although I've seen conflicting opinions about this. I believe there is a contract between amodel
and it's consumers, which guarantees that the consumer can be notified of any changes to the model's data using thechange
event. If you modify the internal attributes object directly, events are not sent and this contract is broken. Backbone events are very fast, especially if you don't have any listeners attached to them, and it's not a point that benefits from over-optimization on your part.Although accessing the attributes directly instead of
get
is quite harmless on it's own, it should be avoided so theattributes
object can be considered totally, completely private.If you absolutely need to prevent some change triggering events, you can use the
silent:true
option:model.set({key:val}, {silent:true})
. This does break the aforementioned contract, and even Backbone's own documentation gives the following caveat:When to use
model.property
Any properties which are not data, i.e. temporary state variables, calculated properties etc. can be attached directly to the model entity. These properties should be considered temporary and transitive: they can be recreated upon model initialization or during its lifetime, but they should not be persisted, whether public or private. A typical naming convention is to prefix private properties with the
_
character as follows: