Person = Backbone.Model.extend({
defaults: {
name: 'Fetus',
age: 0,
children: []
},
initialize: function(){
alert("Welcome to this world");
},
adopt: function( newChildsName ){
var children_array = this.get("children");
children_array.push( newChildsName );
this.set({ children: children_array });
}
});
var person = new Person({ name: "Thomas", age: 67, children: ['Ryan']});
person.adopt('John Resig');
var children = person.get("children"); // ['Ryan', 'John Resig']
In this example code we have:
children_array = this.get(“children”)
I was thinking this would just point to the same array in memory (and so would be O(1)). However then I thought that would be a design floor because one could manipulate the array without using this.set() and then event listeners wouldn’t fire.
So I’m guessing it (somehow magically) copies the array??
http://backbonejs.org/#Model-set
What happens?
edit: I just found the implementation in the backbone source code at https://github.com/documentcloud/backbone/blob/master/backbone.js (I’ve pasted relevant code at bottom)
Get returns:
return this.attributes[attr]
so this would just point to the same array in memory right? So one could change the array without using set() and that would be bad.. ? am i correct?
get: function(attr) {
return this.attributes[attr];
},
// Get the HTML-escaped value of an attribute.
escape: function(attr) {
var html;
if (html = this._escapedAttributes[attr]) return html;
var val = this.get(attr);
return this._escapedAttributes[attr] = _.escape(val == null ? '' : '' + val);
},
// Returns `true` if the attribute contains a value that is not null
// or undefined.
has: function(attr) {
return this.get(attr) != null;
},
// Set a hash of model attributes on the object, firing `"change"` unless
// you choose to silence it.
set: function(key, value, options) {
var attrs, attr, val;
// Handle both `"key", value` and `{key: value}` -style arguments.
if (_.isObject(key) || key == null) {
attrs = key;
options = value;
} else {
attrs = {};
attrs[key] = value;
}
// Extract attributes and options.
options || (options = {});
if (!attrs) return this;
if (attrs instanceof Model) attrs = attrs.attributes;
if (options.unset) for (attr in attrs) attrs[attr] = void 0;
// Run validation.
if (!this._validate(attrs, options)) return false;
// Check for changes of `id`.
if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
var changes = options.changes = {};
var now = this.attributes;
var escaped = this._escapedAttributes;
var prev = this._previousAttributes || {};
// For each `set` attribute...
for (attr in attrs) {
val = attrs[attr];
// If the new and current value differ, record the change.
if (!_.isEqual(now[attr], val) || (options.unset && _.has(now, attr))) {
delete escaped[attr];
(options.silent ? this._silent : changes)[attr] = true;
}
// Update or delete the current value.
options.unset ? delete now[attr] : now[attr] = val;
// If the new and previous value differ, record the change. If not,
// then remove changes for this attribute.
if (!_.isEqual(prev[attr], val) || (_.has(now, attr) !== _.has(prev, attr))) {
this.changed[attr] = val;
if (!options.silent) this._pending[attr] = true;
} else {
delete this.changed[attr];
delete this._pending[attr];
}
}
// Fire the `"change"` events.
if (!options.silent) this.change(options);
return this;
},
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
getjust returns a reference straight out of the model’s internalattributes. 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:set, you don’t trigger events.set, you’ll bypass the validation logic thatsethas.You just have to be careful if you’re working with array and object values.
Note that this behavior of
getandsetis 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 thatsetto be a change ifv === current_value_of_pso if you pull out an array:then modify it:
and send it back in:
you won’t get a
"change"event from the model becausea === a; Backbone actually uses Underscore’sisEqualcombined with!==but the effect is the same in this case.For example, this simple bit of chicanery:
produces two
"change"events: one after the firstsetand one after the lastset.Demo: http://jsfiddle.net/ambiguous/QwZDv/
If you look at
setyou’ll notice that there is some special handling for attributes that areBackbone.Models.The basic lesson here is simple: