does Backbone.Models this.get() copy an entire array or point to the same array in memory

 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??

What happens?

edit: I just found the implementation in the backbone source code at (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) = 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 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:

  1. That's what the interface specification says to do.
  2. If you don't call set, you don't trigger events.
  3. 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:


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

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.


If you look at set you'll notice that there is some special handling for attributes that are Backbone.Models.

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.


Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us

©2020 All rights reserved.